package httpapi import ( "context" "encoding/json" "errors" "fmt" "net/http" "git.nianxx.cn/wangxuming/NianAIGC/backend/internal/identity" ) // AuthState is the runtime authentication configuration exposed by auth/me. type AuthState struct { Required bool Configured bool } // SessionResolver is the HTTP module's consumer-owned seam to Identity. type SessionResolver interface { Resolve(context.Context, string) (identity.Session, error) } type authMeHandler struct { state AuthState resolver SessionResolver } // NewAuthMeHandler returns the standalone current-session HTTP adapter. func NewAuthMeHandler(state AuthState, resolver SessionResolver) (http.Handler, error) { if state.Configured && resolver == nil { return nil, fmt.Errorf("auth/me: configured authentication requires a session resolver") } return &authMeHandler{state: state, resolver: resolver}, nil } func (handler *authMeHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/auth/me" { w.WriteHeader(http.StatusNotFound) return } switch r.Method { case http.MethodOptions: w.Header().Set("Allow", "GET, HEAD, OPTIONS") w.WriteHeader(http.StatusNoContent) return case http.MethodGet, http.MethodHead: // Continue below: HEAD deliberately executes the same authentication // work as GET and suppresses only the representation body. default: w.WriteHeader(http.StatusMethodNotAllowed) return } response, err := handler.currentSession(r) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } payload, err := json.Marshal(response) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) if r.Method == http.MethodGet { _, _ = w.Write(payload) } } type authMeResponse struct { Authenticated bool `json:"authenticated"` AuthRequired bool `json:"authRequired"` AuthConfigured bool `json:"authConfigured"` AuthMode *identity.AuthMode `json:"authMode"` User *publicUser `json:"user"` } type publicUser struct { ID string `json:"id"` Subject string `json:"subject"` Username string `json:"username,omitempty"` Phone string `json:"phone,omitempty"` DisplayName string `json:"displayName"` ClientID string `json:"clientId"` TenantID string `json:"tenantId,omitempty"` OrganizationID string `json:"organizationId,omitempty"` OrganizationName string `json:"organizationName,omitempty"` Role string `json:"role,omitempty"` Status string `json:"status,omitempty"` Authorities []string `json:"authorities"` Scope []string `json:"scope"` } func (handler *authMeHandler) currentSession(r *http.Request) (authMeResponse, error) { response := authMeResponse{ AuthRequired: handler.state.Required, AuthConfigured: handler.state.Configured, } if !handler.state.Configured { return response, nil } cookieValue, ok := readSessionCookie(r) if !ok || len(cookieValue) > identity.CookieMaxValueLength { return response, nil } session, err := handler.resolver.Resolve(r.Context(), cookieValue) if errors.Is(err, identity.ErrUnauthenticated) { return response, nil } if err != nil { return authMeResponse{}, err } authorities := session.User.Authorities if authorities == nil { authorities = []string{} } scope := session.User.Scope if scope == nil { scope = []string{} } response.Authenticated = true response.AuthMode = &session.AuthMode response.User = &publicUser{ ID: session.User.ID, Subject: session.User.Subject, Username: session.User.Username, Phone: session.User.Phone, DisplayName: session.User.DisplayName, ClientID: session.User.ClientID, TenantID: session.User.TenantID, OrganizationID: session.User.OrganizationID, OrganizationName: session.User.OrganizationName, Role: session.User.Role, Status: session.User.Status, Authorities: authorities, Scope: scope, } return response, nil } func readSessionCookie(r *http.Request) (string, bool) { protected := make(map[string]struct{}, identity.CookieMaxChunks) for _, name := range identity.CookieNames() { protected[name] = struct{}{} } values := make(map[string]string, identity.CookieMaxChunks) for _, cookie := range r.Cookies() { if _, ok := protected[cookie.Name]; !ok { continue } // Match the pinned Next.js RequestCookies parser: the final value for a // duplicated name wins. net/http's Request.Cookie would choose the first. values[cookie.Name] = cookie.Value } return identity.Reassemble(identity.SessionCookieName, func(name string) (string, bool) { value, ok := values[name] return value, ok }) }