213 lines
6.4 KiB
Go
213 lines
6.4 KiB
Go
package http
|
|
|
|
import (
|
|
"net"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
userv1 "hyapp/api/proto/user/v1"
|
|
"hyapp/services/gateway-service/internal/auth"
|
|
)
|
|
|
|
// authTokenData 是 gateway 对外 auth API 的扁平响应格式。
|
|
type authTokenData struct {
|
|
UserID int64 `json:"user_id,omitempty"`
|
|
DisplayUserID string `json:"display_user_id,omitempty"`
|
|
DefaultDisplayUserID string `json:"default_display_user_id,omitempty"`
|
|
DisplayUserIDKind string `json:"display_user_id_kind,omitempty"`
|
|
DisplayUserIDExpiresAtMs int64 `json:"display_user_id_expires_at_ms"`
|
|
SessionID string `json:"session_id"`
|
|
AccessToken string `json:"access_token"`
|
|
RefreshToken string `json:"refresh_token"`
|
|
ExpiresInSec int64 `json:"expires_in_sec"`
|
|
TokenType string `json:"token_type"`
|
|
IsNewUser *bool `json:"is_new_user,omitempty"`
|
|
}
|
|
|
|
// loginPassword 处理 display_user_id + 密码登录。
|
|
func (h *Handler) loginPassword(writer http.ResponseWriter, request *http.Request) {
|
|
var body struct {
|
|
DisplayUserID string `json:"display_user_id"`
|
|
Password string `json:"password"`
|
|
DeviceID string `json:"device_id"`
|
|
}
|
|
if !decode(writer, request, &body) {
|
|
return
|
|
}
|
|
if h.userClient == nil {
|
|
writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error")
|
|
return
|
|
}
|
|
|
|
resp, err := h.userClient.LoginPassword(request.Context(), &userv1.LoginPasswordRequest{
|
|
Meta: authRequestMeta(request, body.DeviceID),
|
|
DisplayUserId: body.DisplayUserID,
|
|
Password: body.Password,
|
|
})
|
|
if err != nil {
|
|
writeRPCError(writer, request, err)
|
|
return
|
|
}
|
|
|
|
writeOK(writer, request, authData(resp.GetToken(), nil))
|
|
}
|
|
|
|
// loginThirdParty 处理三方登录即注册。
|
|
func (h *Handler) loginThirdParty(writer http.ResponseWriter, request *http.Request) {
|
|
var body struct {
|
|
Provider string `json:"provider"`
|
|
Credential string `json:"credential"`
|
|
DeviceID string `json:"device_id"`
|
|
}
|
|
if !decode(writer, request, &body) {
|
|
return
|
|
}
|
|
if h.userClient == nil {
|
|
writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error")
|
|
return
|
|
}
|
|
|
|
resp, err := h.userClient.LoginThirdParty(request.Context(), &userv1.LoginThirdPartyRequest{
|
|
Meta: authRequestMeta(request, body.DeviceID),
|
|
Provider: body.Provider,
|
|
Credential: body.Credential,
|
|
})
|
|
if err != nil {
|
|
writeRPCError(writer, request, err)
|
|
return
|
|
}
|
|
|
|
isNewUser := resp.GetIsNewUser()
|
|
writeOK(writer, request, authData(resp.GetToken(), &isNewUser))
|
|
}
|
|
|
|
// setPassword 处理已登录用户首次设置密码。
|
|
func (h *Handler) setPassword(writer http.ResponseWriter, request *http.Request) {
|
|
var body struct {
|
|
Password string `json:"password"`
|
|
}
|
|
if !decode(writer, request, &body) {
|
|
return
|
|
}
|
|
if h.userClient == nil {
|
|
writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error")
|
|
return
|
|
}
|
|
|
|
resp, err := h.userClient.SetPassword(request.Context(), &userv1.SetPasswordRequest{
|
|
Meta: authRequestMeta(request, ""),
|
|
UserId: auth.UserIDFromContext(request.Context()),
|
|
Password: body.Password,
|
|
})
|
|
if err != nil {
|
|
writeRPCError(writer, request, err)
|
|
return
|
|
}
|
|
|
|
writeOK(writer, request, map[string]bool{"password_set": resp.GetPasswordSet()})
|
|
}
|
|
|
|
// refreshToken 处理 refresh token 轮换。
|
|
func (h *Handler) refreshToken(writer http.ResponseWriter, request *http.Request) {
|
|
var body struct {
|
|
RefreshToken string `json:"refresh_token"`
|
|
DeviceID string `json:"device_id"`
|
|
}
|
|
if !decode(writer, request, &body) {
|
|
return
|
|
}
|
|
if h.userClient == nil {
|
|
writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error")
|
|
return
|
|
}
|
|
|
|
resp, err := h.userClient.RefreshToken(request.Context(), &userv1.RefreshTokenRequest{
|
|
Meta: authRequestMeta(request, body.DeviceID),
|
|
RefreshToken: body.RefreshToken,
|
|
})
|
|
if err != nil {
|
|
writeRPCError(writer, request, err)
|
|
return
|
|
}
|
|
|
|
writeOK(writer, request, authData(resp.GetToken(), nil))
|
|
}
|
|
|
|
// logout 处理 refresh session 失效。
|
|
func (h *Handler) logout(writer http.ResponseWriter, request *http.Request) {
|
|
var body struct {
|
|
RefreshToken string `json:"refresh_token"`
|
|
SessionID string `json:"session_id"`
|
|
}
|
|
if !decode(writer, request, &body) {
|
|
return
|
|
}
|
|
if h.userClient == nil {
|
|
writeError(writer, request, http.StatusBadGateway, codeUpstreamError, "upstream service error")
|
|
return
|
|
}
|
|
|
|
resp, err := h.userClient.Logout(request.Context(), &userv1.LogoutRequest{
|
|
Meta: authRequestMeta(request, ""),
|
|
SessionId: body.SessionID,
|
|
RefreshToken: body.RefreshToken,
|
|
})
|
|
if err != nil {
|
|
writeRPCError(writer, request, err)
|
|
return
|
|
}
|
|
|
|
writeOK(writer, request, map[string]bool{"revoked": resp.GetRevoked()})
|
|
}
|
|
|
|
// authRequestMeta 生成 user-service auth RPC 的统一追踪和审计元信息。
|
|
func authRequestMeta(request *http.Request, deviceID string) *userv1.RequestMeta {
|
|
return &userv1.RequestMeta{
|
|
RequestId: requestIDFromContext(request.Context()),
|
|
Caller: "gateway-service",
|
|
GatewayNodeId: "gateway-local",
|
|
SentAtMs: time.Now().UnixMilli(),
|
|
DeviceId: deviceID,
|
|
ClientIp: clientIP(request),
|
|
UserAgent: request.UserAgent(),
|
|
}
|
|
}
|
|
|
|
// authData 把 user-service 的 token response 转成外部 HTTP 扁平 data。
|
|
func authData(token *userv1.AuthToken, isNewUser *bool) authTokenData {
|
|
if token == nil {
|
|
return authTokenData{IsNewUser: isNewUser}
|
|
}
|
|
|
|
return authTokenData{
|
|
UserID: token.GetUserId(),
|
|
DisplayUserID: token.GetDisplayUserId(),
|
|
DefaultDisplayUserID: token.GetDefaultDisplayUserId(),
|
|
DisplayUserIDKind: token.GetDisplayUserIdKind(),
|
|
DisplayUserIDExpiresAtMs: token.GetDisplayUserIdExpiresAtMs(),
|
|
SessionID: token.GetSessionId(),
|
|
AccessToken: token.GetAccessToken(),
|
|
RefreshToken: token.GetRefreshToken(),
|
|
ExpiresInSec: token.GetExpiresInSec(),
|
|
TokenType: token.GetTokenType(),
|
|
IsNewUser: isNewUser,
|
|
}
|
|
}
|
|
|
|
// clientIP 提取 gateway 看到的客户端地址。
|
|
func clientIP(request *http.Request) string {
|
|
forwarded := strings.TrimSpace(request.Header.Get("X-Forwarded-For"))
|
|
if forwarded != "" {
|
|
parts := strings.Split(forwarded, ",")
|
|
return strings.TrimSpace(parts[0])
|
|
}
|
|
|
|
host, _, err := net.SplitHostPort(request.RemoteAddr)
|
|
if err != nil {
|
|
return request.RemoteAddr
|
|
}
|
|
|
|
return host
|
|
}
|