// Package authdoc provides an HTTP handler example for Nuva token exchange. package authdoc import ( "context" "encoding/json" "errors" "net/http" "strings" "time" ) type DefinitionRef struct { ID string `json:"id"` Revision string `json:"revision"` } type ClientTokenRequest struct { UserID string `json:"userId"` UISkills []DefinitionRef `json:"uiSkills"` Permissions json.RawMessage `json:"permissions"` } type ClientToken struct { Token string `json:"token"` ExpiresAt time.Time `json:"expiresAt"` } // TokenIssuer calls Nuva with the customer's server-held service credential. // Nuva, not this handler, issues and signs the restricted user token. type TokenIssuer interface { CreateClientToken(context.Context, ClientTokenRequest) (ClientToken, error) } // ResolveGrant uses the customer's verified login and server-side authorization policy. // User identity and effective permissions must not come directly from browser input. type ResolveGrant func(*http.Request) (ClientTokenRequest, error) var ( ErrUnauthenticated = errors.New("unauthenticated") ErrForbidden = errors.New("forbidden") ) func NewAuthHandler(issuer TokenIssuer, resolve ResolveGrant) (http.Handler, error) { if issuer == nil || resolve == nil { return nil, errors.New("token issuer and verified grant resolver are required") } return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Cache-Control", "no-store") w.Header().Set("Content-Type", "application/json; charset=utf-8") w.Header().Set("X-Content-Type-Options", "nosniff") fail := func(status int, code string) { w.WriteHeader(status) _ = json.NewEncoder(w).Encode(map[string]any{"error": map[string]string{"code": code}}) } if r.Method != http.MethodPost { w.Header().Set("Allow", http.MethodPost) fail(http.StatusMethodNotAllowed, "METHOD_NOT_ALLOWED") return } grant, err := resolve(r) if err != nil { switch { case errors.Is(err, ErrUnauthenticated): fail(http.StatusUnauthorized, "UNAUTHENTICATED") case errors.Is(err, ErrForbidden): fail(http.StatusForbidden, "FORBIDDEN") default: fail(http.StatusInternalServerError, "AUTH_CONFIGURATION_ERROR") } return } if strings.TrimSpace(grant.UserID) == "" || !json.Valid(grant.Permissions) || strings.TrimSpace(string(grant.Permissions)) == "null" { fail(http.StatusInternalServerError, "AUTH_CONFIGURATION_ERROR") return } if grant.UISkills == nil { // An empty list intentionally enables no customer overrides; platform defaults still apply. grant.UISkills = []DefinitionRef{} } token, err := issuer.CreateClientToken(r.Context(), grant) if errors.Is(err, ErrForbidden) { fail(http.StatusForbidden, "FORBIDDEN") return } if err != nil || token.Token == "" || !token.ExpiresAt.After(time.Now()) { fail(http.StatusBadGateway, "NUVA_AUTH_UNAVAILABLE") return } w.WriteHeader(http.StatusOK) _ = json.NewEncoder(w).Encode(token) }), nil }