2026-04-25 13:21:39 +08:00

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
}