refactor(auth): move context keys to domain and fix review feedback
- commit
- 530de44ac4b1a73d05afdcc41ac871a1259bcd1a
- author
- bnema · · 2026-01-28 21:55 UTC
- parent
- 7c9064b
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
8 files · +56 -31
M internal/adapters/in/cli/auth.go +11 -5
| @@ -656,7 +656,8 @@ func createAuthServiceForCLI(cfg *cliConfig, log zerowrap.Logger) (*auth.Service | |||
| 656 | 656 | ||
| 657 | 657 | // newAuthStatusCmd creates the auth status command. | |
| 658 | 658 | func newAuthStatusCmd() *cobra.Command { | |
| 659 | - | return &cobra.Command{ | |
| 659 | + | var remoteNameFlag string | |
| 660 | + | cmd := &cobra.Command{ | |
| 660 | 661 | Use: "status", | |
| 661 | 662 | Short: "Check authentication session status", | |
| 662 | 663 | Long: `Verify if stored authentication session is still valid. | |
| @@ -667,21 +668,26 @@ Examples: | |||
| 667 | 668 | gordon auth status Check status of active remote | |
| 668 | 669 | gordon auth status --remote prod Check specific remote`, | |
| 669 | 670 | RunE: func(cmd *cobra.Command, args []string) error { | |
| 670 | - | return runAuthStatus() | |
| 671 | + | return runAuthStatus(remoteNameFlag) | |
| 671 | 672 | }, | |
| 672 | 673 | } | |
| 674 | + | cmd.Flags().StringVar(&remoteNameFlag, "remote", "", "Check specific remote") | |
| 675 | + | return cmd | |
| 673 | 676 | } | |
| 674 | 677 | ||
| 675 | 678 | // runAuthStatus checks authentication status for the active remote. | |
| 676 | - | func runAuthStatus() error { | |
| 679 | + | func runAuthStatus(remoteNameArg string) error { | |
| 677 | 680 | // Load remotes config | |
| 678 | 681 | config, err := remote.LoadRemotes("") | |
| 679 | 682 | if err != nil { | |
| 680 | 683 | return fmt.Errorf("failed to load remotes: %w", err) | |
| 681 | 684 | } | |
| 682 | 685 | ||
| 683 | - | // Determine active remote | |
| 684 | - | remoteName := config.Active | |
| 686 | + | // Determine remote name | |
| 687 | + | remoteName := remoteNameArg | |
| 688 | + | if remoteName == "" { | |
| 689 | + | remoteName = config.Active | |
| 690 | + | } | |
| 685 | 691 | if remoteName == "" { | |
| 686 | 692 | return fmt.Errorf("no active remote configured. Use 'gordon remote use <name>' or --remote flag") | |
| 687 | 693 | } | |
| @@ -656,7 +656,8 @@ func createAuthServiceForCLI(cfg *cliConfig, log zerowrap.Logger) (*auth.Service | |||||
| 656 | 656 | ||||
| 657 | // newAuthStatusCmd creates the auth status command. | 657 | // newAuthStatusCmd creates the auth status command. | ||
| 658 | func newAuthStatusCmd() *cobra.Command { | 658 | func newAuthStatusCmd() *cobra.Command { | ||
| 659 | - | return &cobra.Command{ | 659 | + | var remoteNameFlag string |
| 660 | + | cmd := &cobra.Command{ | |||
| 660 | Use: "status", | 661 | Use: "status", | ||
| 661 | Short: "Check authentication session status", | 662 | Short: "Check authentication session status", | ||
| 662 | Long: `Verify if stored authentication session is still valid. | 663 | Long: `Verify if stored authentication session is still valid. | ||
| @@ -667,21 +668,26 @@ Examples: | |||||
| 667 | gordon auth status Check status of active remote | 668 | gordon auth status Check status of active remote | ||
| 668 | gordon auth status --remote prod Check specific remote`, | 669 | gordon auth status --remote prod Check specific remote`, | ||
| 669 | RunE: func(cmd *cobra.Command, args []string) error { | 670 | RunE: func(cmd *cobra.Command, args []string) error { | ||
| 670 | - | return runAuthStatus() | 671 | + | return runAuthStatus(remoteNameFlag) |
| 671 | }, | 672 | }, | ||
| 672 | } | 673 | } | ||
| 674 | + | cmd.Flags().StringVar(&remoteNameFlag, "remote", "", "Check specific remote") | |||
| 675 | + | return cmd | |||
| 673 | } | 676 | } | ||
| 674 | 677 | ||||
| 675 | // runAuthStatus checks authentication status for the active remote. | 678 | // runAuthStatus checks authentication status for the active remote. | ||
| 676 | - | func runAuthStatus() error { | 679 | + | func runAuthStatus(remoteNameArg string) error { |
| 677 | // Load remotes config | 680 | // Load remotes config | ||
| 678 | config, err := remote.LoadRemotes("") | 681 | config, err := remote.LoadRemotes("") | ||
| 679 | if err != nil { | 682 | if err != nil { | ||
| 680 | return fmt.Errorf("failed to load remotes: %w", err) | 683 | return fmt.Errorf("failed to load remotes: %w", err) | ||
| 681 | } | 684 | } | ||
| 682 | 685 | ||||
| 683 | - | // Determine active remote | 686 | + | // Determine remote name |
| 684 | - | remoteName := config.Active | 687 | + | remoteName := remoteNameArg |
| 688 | + | if remoteName == "" { | |||
| 689 | + | remoteName = config.Active | |||
| 690 | + | } | |||
| 685 | if remoteName == "" { | 691 | if remoteName == "" { | ||
| 686 | return fmt.Errorf("no active remote configured. Use 'gordon remote use <name>' or --remote flag") | 692 | return fmt.Errorf("no active remote configured. Use 'gordon remote use <name>' or --remote flag") | ||
| 687 | } | 693 | } | ||
M internal/adapters/in/http/admin/handler.go +7 -0
| @@ -1085,6 +1085,13 @@ func (h *Handler) handleAttachmentsConfigDelete(w http.ResponseWriter, r *http.R | |||
| 1085 | 1085 | func (h *Handler) handleAuthVerify(w http.ResponseWriter, r *http.Request) { | |
| 1086 | 1086 | ctx := r.Context() | |
| 1087 | 1087 | ||
| 1088 | + | // Only allow GET method | |
| 1089 | + | if r.Method != http.MethodGet { | |
| 1090 | + | w.Header().Set("Allow", http.MethodGet) | |
| 1091 | + | h.sendError(w, http.StatusMethodNotAllowed, "method not allowed") | |
| 1092 | + | return | |
| 1093 | + | } | |
| 1094 | + | ||
| 1088 | 1095 | // Call use case to get auth status | |
| 1089 | 1096 | status, err := h.authSvc.GetAuthStatus(ctx) | |
| 1090 | 1097 | if err != nil { | |
| @@ -1085,6 +1085,13 @@ func (h *Handler) handleAttachmentsConfigDelete(w http.ResponseWriter, r *http.R | |||||
| 1085 | func (h *Handler) handleAuthVerify(w http.ResponseWriter, r *http.Request) { | 1085 | func (h *Handler) handleAuthVerify(w http.ResponseWriter, r *http.Request) { | ||
| 1086 | ctx := r.Context() | 1086 | ctx := r.Context() | ||
| 1087 | 1087 | ||||
| 1088 | + | // Only allow GET method | |||
| 1089 | + | if r.Method != http.MethodGet { | |||
| 1090 | + | w.Header().Set("Allow", http.MethodGet) | |||
| 1091 | + | h.sendError(w, http.StatusMethodNotAllowed, "method not allowed") | |||
| 1092 | + | return | |||
| 1093 | + | } | |||
| 1094 | + | ||||
| 1088 | // Call use case to get auth status | 1095 | // Call use case to get auth status | ||
| 1089 | status, err := h.authSvc.GetAuthStatus(ctx) | 1096 | status, err := h.authSvc.GetAuthStatus(ctx) | ||
| 1090 | if err != nil { | 1097 | if err != nil { | ||
M internal/adapters/in/http/admin/handler_test.go +1 -1
| @@ -23,7 +23,7 @@ func testLogger() zerowrap.Logger { | |||
| 23 | 23 | ||
| 24 | 24 | func ctxWithScopes(scopes ...string) context.Context { | |
| 25 | 25 | ctx := context.Background() | |
| 26 | - | return context.WithValue(ctx, ContextKeyScopes, scopes) | |
| 26 | + | return context.WithValue(ctx, domain.ContextKeyScopes, scopes) | |
| 27 | 27 | } | |
| 28 | 28 | ||
| 29 | 29 | // Routes endpoint tests | |
| @@ -23,7 +23,7 @@ func testLogger() zerowrap.Logger { | |||||
| 23 | 23 | ||||
| 24 | func ctxWithScopes(scopes ...string) context.Context { | 24 | func ctxWithScopes(scopes ...string) context.Context { | ||
| 25 | ctx := context.Background() | 25 | ctx := context.Background() | ||
| 26 | - | return context.WithValue(ctx, ContextKeyScopes, scopes) | 26 | + | return context.WithValue(ctx, domain.ContextKeyScopes, scopes) |
| 27 | } | 27 | } | ||
| 28 | 28 | ||||
| 29 | // Routes endpoint tests | 29 | // Routes endpoint tests | ||
M internal/adapters/in/http/admin/middleware.go +6 -15
| @@ -16,16 +16,6 @@ import ( | |||
| 16 | 16 | "github.com/bnema/gordon/internal/domain" | |
| 17 | 17 | ) | |
| 18 | 18 | ||
| 19 | - | // contextKey is a custom type for context keys to avoid collisions. | |
| 20 | - | type contextKey string | |
| 21 | - | ||
| 22 | - | const ( | |
| 23 | - | // ContextKeyScopes is the context key for storing token scopes. | |
| 24 | - | ContextKeyScopes contextKey = "admin_scopes" | |
| 25 | - | // ContextKeySubject is the context key for storing token subject. | |
| 26 | - | ContextKeySubject contextKey = "admin_subject" | |
| 27 | - | ) | |
| 28 | - | ||
| 29 | 19 | // AuthMiddleware creates middleware that validates admin API authentication. | |
| 30 | 20 | // Both globalLimiter and ipLimiter can be nil to disable rate limiting. | |
| 31 | 21 | // The trustedNets parameter is used for proper IP extraction behind reverse proxies. | |
| @@ -110,8 +100,9 @@ func AuthMiddleware( | |||
| 110 | 100 | } | |
| 111 | 101 | ||
| 112 | 102 | // Add claims to context for downstream handlers | |
| 113 | - | ctx = context.WithValue(ctx, ContextKeyScopes, claims.Scopes) | |
| 114 | - | ctx = context.WithValue(ctx, ContextKeySubject, claims.Subject) | |
| 103 | + | ctx = context.WithValue(ctx, domain.ContextKeyScopes, claims.Scopes) | |
| 104 | + | ctx = context.WithValue(ctx, domain.ContextKeySubject, claims.Subject) | |
| 105 | + | ctx = context.WithValue(ctx, domain.TokenClaimsKey, claims) | |
| 115 | 106 | ||
| 116 | 107 | next.ServeHTTP(w, r.WithContext(ctx)) | |
| 117 | 108 | }) | |
| @@ -125,7 +116,7 @@ func RequireScope(resource, action string) func(http.Handler) http.Handler { | |||
| 125 | 116 | ctx := r.Context() | |
| 126 | 117 | ||
| 127 | 118 | // Get scopes from context | |
| 128 | - | scopes, ok := ctx.Value(ContextKeyScopes).([]string) | |
| 119 | + | scopes, ok := ctx.Value(domain.ContextKeyScopes).([]string) | |
| 129 | 120 | if !ok { | |
| 130 | 121 | sendForbidden(w, "no scopes in context") | |
| 131 | 122 | return | |
| @@ -144,13 +135,13 @@ func RequireScope(resource, action string) func(http.Handler) http.Handler { | |||
| 144 | 135 | ||
| 145 | 136 | // GetSubject retrieves the authenticated subject from the context. | |
| 146 | 137 | func GetSubject(ctx context.Context) string { | |
| 147 | - | subject, _ := ctx.Value(ContextKeySubject).(string) | |
| 138 | + | subject, _ := ctx.Value(domain.ContextKeySubject).(string) | |
| 148 | 139 | return subject | |
| 149 | 140 | } | |
| 150 | 141 | ||
| 151 | 142 | // GetScopes retrieves the token scopes from the context. | |
| 152 | 143 | func GetScopes(ctx context.Context) []string { | |
| 153 | - | scopes, _ := ctx.Value(ContextKeyScopes).([]string) | |
| 144 | + | scopes, _ := ctx.Value(domain.ContextKeyScopes).([]string) | |
| 154 | 145 | return scopes | |
| 155 | 146 | } | |
| 156 | 147 | ||
| @@ -16,16 +16,6 @@ import ( | |||||
| 16 | "github.com/bnema/gordon/internal/domain" | 16 | "github.com/bnema/gordon/internal/domain" | ||
| 17 | ) | 17 | ) | ||
| 18 | 18 | ||||
| 19 | - | // contextKey is a custom type for context keys to avoid collisions. | |||
| 20 | - | type contextKey string | |||
| 21 | - | ||||
| 22 | - | const ( | |||
| 23 | - | // ContextKeyScopes is the context key for storing token scopes. | |||
| 24 | - | ContextKeyScopes contextKey = "admin_scopes" | |||
| 25 | - | // ContextKeySubject is the context key for storing token subject. | |||
| 26 | - | ContextKeySubject contextKey = "admin_subject" | |||
| 27 | - | ) | |||
| 28 | - | ||||
| 29 | // AuthMiddleware creates middleware that validates admin API authentication. | 19 | // AuthMiddleware creates middleware that validates admin API authentication. | ||
| 30 | // Both globalLimiter and ipLimiter can be nil to disable rate limiting. | 20 | // Both globalLimiter and ipLimiter can be nil to disable rate limiting. | ||
| 31 | // The trustedNets parameter is used for proper IP extraction behind reverse proxies. | 21 | // The trustedNets parameter is used for proper IP extraction behind reverse proxies. | ||
| @@ -110,8 +100,9 @@ func AuthMiddleware( | |||||
| 110 | } | 100 | } | ||
| 111 | 101 | ||||
| 112 | // Add claims to context for downstream handlers | 102 | // Add claims to context for downstream handlers | ||
| 113 | - | ctx = context.WithValue(ctx, ContextKeyScopes, claims.Scopes) | 103 | + | ctx = context.WithValue(ctx, domain.ContextKeyScopes, claims.Scopes) |
| 114 | - | ctx = context.WithValue(ctx, ContextKeySubject, claims.Subject) | 104 | + | ctx = context.WithValue(ctx, domain.ContextKeySubject, claims.Subject) |
| 105 | + | ctx = context.WithValue(ctx, domain.TokenClaimsKey, claims) | |||
| 115 | 106 | ||||
| 116 | next.ServeHTTP(w, r.WithContext(ctx)) | 107 | next.ServeHTTP(w, r.WithContext(ctx)) | ||
| 117 | }) | 108 | }) | ||
| @@ -125,7 +116,7 @@ func RequireScope(resource, action string) func(http.Handler) http.Handler { | |||||
| 125 | ctx := r.Context() | 116 | ctx := r.Context() | ||
| 126 | 117 | ||||
| 127 | // Get scopes from context | 118 | // Get scopes from context | ||
| 128 | - | scopes, ok := ctx.Value(ContextKeyScopes).([]string) | 119 | + | scopes, ok := ctx.Value(domain.ContextKeyScopes).([]string) |
| 129 | if !ok { | 120 | if !ok { | ||
| 130 | sendForbidden(w, "no scopes in context") | 121 | sendForbidden(w, "no scopes in context") | ||
| 131 | return | 122 | return | ||
| @@ -144,13 +135,13 @@ func RequireScope(resource, action string) func(http.Handler) http.Handler { | |||||
| 144 | 135 | ||||
| 145 | // GetSubject retrieves the authenticated subject from the context. | 136 | // GetSubject retrieves the authenticated subject from the context. | ||
| 146 | func GetSubject(ctx context.Context) string { | 137 | func GetSubject(ctx context.Context) string { | ||
| 147 | - | subject, _ := ctx.Value(ContextKeySubject).(string) | 138 | + | subject, _ := ctx.Value(domain.ContextKeySubject).(string) |
| 148 | return subject | 139 | return subject | ||
| 149 | } | 140 | } | ||
| 150 | 141 | ||||
| 151 | // GetScopes retrieves the token scopes from the context. | 142 | // GetScopes retrieves the token scopes from the context. | ||
| 152 | func GetScopes(ctx context.Context) []string { | 143 | func GetScopes(ctx context.Context) []string { | ||
| 153 | - | scopes, _ := ctx.Value(ContextKeyScopes).([]string) | 144 | + | scopes, _ := ctx.Value(domain.ContextKeyScopes).([]string) |
| 154 | return scopes | 145 | return scopes | ||
| 155 | } | 146 | } | ||
| 156 | 147 | ||||
M internal/adapters/in/http/admin/middleware_test.go +2 -2
| @@ -340,7 +340,7 @@ func TestRequireScope(t *testing.T) { | |||
| 340 | 340 | ||
| 341 | 341 | req := httptest.NewRequest(http.MethodGet, "/admin/routes", nil) | |
| 342 | 342 | // Add scopes to context | |
| 343 | - | ctx := context.WithValue(req.Context(), ContextKeyScopes, tt.scopes) | |
| 343 | + | ctx := context.WithValue(req.Context(), domain.ContextKeyScopes, tt.scopes) | |
| 344 | 344 | req = req.WithContext(ctx) | |
| 345 | 345 | ||
| 346 | 346 | rec := httptest.NewRecorder() | |
| @@ -352,7 +352,7 @@ func TestRequireScope(t *testing.T) { | |||
| 352 | 352 | } | |
| 353 | 353 | ||
| 354 | 354 | func TestHasAccess(t *testing.T) { | |
| 355 | - | ctx := context.WithValue(context.Background(), ContextKeyScopes, []string{"admin:routes:read", "admin:secrets:*"}) | |
| 355 | + | ctx := context.WithValue(context.Background(), domain.ContextKeyScopes, []string{"admin:routes:read", "admin:secrets:*"}) | |
| 356 | 356 | ||
| 357 | 357 | assert.True(t, HasAccess(ctx, "routes", "read")) | |
| 358 | 358 | assert.False(t, HasAccess(ctx, "routes", "write")) | |
| @@ -340,7 +340,7 @@ func TestRequireScope(t *testing.T) { | |||||
| 340 | 340 | ||||
| 341 | req := httptest.NewRequest(http.MethodGet, "/admin/routes", nil) | 341 | req := httptest.NewRequest(http.MethodGet, "/admin/routes", nil) | ||
| 342 | // Add scopes to context | 342 | // Add scopes to context | ||
| 343 | - | ctx := context.WithValue(req.Context(), ContextKeyScopes, tt.scopes) | 343 | + | ctx := context.WithValue(req.Context(), domain.ContextKeyScopes, tt.scopes) |
| 344 | req = req.WithContext(ctx) | 344 | req = req.WithContext(ctx) | ||
| 345 | 345 | ||||
| 346 | rec := httptest.NewRecorder() | 346 | rec := httptest.NewRecorder() | ||
| @@ -352,7 +352,7 @@ func TestRequireScope(t *testing.T) { | |||||
| 352 | } | 352 | } | ||
| 353 | 353 | ||||
| 354 | func TestHasAccess(t *testing.T) { | 354 | func TestHasAccess(t *testing.T) { | ||
| 355 | - | ctx := context.WithValue(context.Background(), ContextKeyScopes, []string{"admin:routes:read", "admin:secrets:*"}) | 355 | + | ctx := context.WithValue(context.Background(), domain.ContextKeyScopes, []string{"admin:routes:read", "admin:secrets:*"}) |
| 356 | 356 | ||||
| 357 | assert.True(t, HasAccess(ctx, "routes", "read")) | 357 | assert.True(t, HasAccess(ctx, "routes", "read")) | ||
| 358 | assert.False(t, HasAccess(ctx, "routes", "write")) | 358 | assert.False(t, HasAccess(ctx, "routes", "write")) | ||
M internal/adapters/in/http/middleware/auth.go +3 -5
| @@ -14,11 +14,9 @@ import ( | |||
| 14 | 14 | "github.com/bnema/gordon/internal/domain" | |
| 15 | 15 | ) | |
| 16 | 16 | ||
| 17 | - | // contextKey is a type for context keys used by this package. | |
| 18 | - | type contextKey string | |
| 19 | - | ||
| 20 | - | // TokenClaimsKey is the context key for storing token claims. | |
| 21 | - | const TokenClaimsKey contextKey = "tokenClaims" | |
| 17 | + | // TokenClaimsKey is context key for storing token claims. | |
| 18 | + | // Using domain key for consistency across all auth flows. | |
| 19 | + | const TokenClaimsKey = domain.TokenClaimsKey | |
| 22 | 20 | ||
| 23 | 21 | // RegistryAuth middleware provides Docker Registry authentication. | |
| 24 | 22 | func RegistryAuth(username, password string, log zerowrap.Logger) func(http.Handler) http.Handler { | |
| @@ -14,11 +14,9 @@ import ( | |||||
| 14 | "github.com/bnema/gordon/internal/domain" | 14 | "github.com/bnema/gordon/internal/domain" | ||
| 15 | ) | 15 | ) | ||
| 16 | 16 | ||||
| 17 | - | // contextKey is a type for context keys used by this package. | 17 | + | // TokenClaimsKey is context key for storing token claims. |
| 18 | - | type contextKey string | 18 | + | // Using domain key for consistency across all auth flows. |
| 19 | - | 19 | + | const TokenClaimsKey = domain.TokenClaimsKey | |
| 20 | - | // TokenClaimsKey is the context key for storing token claims. | |||
| 21 | - | const TokenClaimsKey contextKey = "tokenClaims" | |||
| 22 | 20 | ||||
| 23 | // RegistryAuth middleware provides Docker Registry authentication. | 21 | // RegistryAuth middleware provides Docker Registry authentication. | ||
| 24 | func RegistryAuth(username, password string, log zerowrap.Logger) func(http.Handler) http.Handler { | 22 | func RegistryAuth(username, password string, log zerowrap.Logger) func(http.Handler) http.Handler { | ||
M internal/domain/auth.go +24 -0
| @@ -2,6 +2,7 @@ | |||
| 2 | 2 | package domain | |
| 3 | 3 | ||
| 4 | 4 | import ( | |
| 5 | + | "context" | |
| 5 | 6 | "fmt" | |
| 6 | 7 | "strings" | |
| 7 | 8 | "time" | |
| @@ -273,3 +274,26 @@ type AuthStatus struct { | |||
| 273 | 274 | ExpiresAt int64 // Unix timestamp, 0 if never expires | |
| 274 | 275 | IssuedAt int64 // Unix timestamp | |
| 275 | 276 | } | |
| 277 | + | ||
| 278 | + | // Context keys for auth-related domain concerns. | |
| 279 | + | type authContextKey string | |
| 280 | + | ||
| 281 | + | const ( | |
| 282 | + | // TokenClaimsKey is the context key for storing TokenClaims. | |
| 283 | + | // Allows use cases and adapters to access validated token claims from context. | |
| 284 | + | TokenClaimsKey authContextKey = "token_claims" | |
| 285 | + | // ContextKeyScopes is the context key for storing token scopes. | |
| 286 | + | ContextKeyScopes authContextKey = "token_scopes" | |
| 287 | + | // ContextKeySubject is the context key for storing token subject. | |
| 288 | + | ContextKeySubject authContextKey = "token_subject" | |
| 289 | + | ) | |
| 290 | + | ||
| 291 | + | // GetTokenClaims extracts TokenClaims from context. | |
| 292 | + | // Returns nil if claims are not present or type assertion fails. | |
| 293 | + | func GetTokenClaims(ctx context.Context) *TokenClaims { | |
| 294 | + | claims, ok := ctx.Value(TokenClaimsKey).(*TokenClaims) | |
| 295 | + | if !ok { | |
| 296 | + | return nil | |
| 297 | + | } | |
| 298 | + | return claims | |
| 299 | + | } | |
| @@ -2,6 +2,7 @@ | |||||
| 2 | package domain | 2 | package domain | ||
| 3 | 3 | ||||
| 4 | import ( | 4 | import ( | ||
| 5 | + | "context" | |||
| 5 | "fmt" | 6 | "fmt" | ||
| 6 | "strings" | 7 | "strings" | ||
| 7 | "time" | 8 | "time" | ||
| @@ -273,3 +274,26 @@ type AuthStatus struct { | |||||
| 273 | ExpiresAt int64 // Unix timestamp, 0 if never expires | 274 | ExpiresAt int64 // Unix timestamp, 0 if never expires | ||
| 274 | IssuedAt int64 // Unix timestamp | 275 | IssuedAt int64 // Unix timestamp | ||
| 275 | } | 276 | } | ||
| 277 | + | ||||
| 278 | + | // Context keys for auth-related domain concerns. | |||
| 279 | + | type authContextKey string | |||
| 280 | + | ||||
| 281 | + | const ( | |||
| 282 | + | // TokenClaimsKey is the context key for storing TokenClaims. | |||
| 283 | + | // Allows use cases and adapters to access validated token claims from context. | |||
| 284 | + | TokenClaimsKey authContextKey = "token_claims" | |||
| 285 | + | // ContextKeyScopes is the context key for storing token scopes. | |||
| 286 | + | ContextKeyScopes authContextKey = "token_scopes" | |||
| 287 | + | // ContextKeySubject is the context key for storing token subject. | |||
| 288 | + | ContextKeySubject authContextKey = "token_subject" | |||
| 289 | + | ) | |||
| 290 | + | ||||
| 291 | + | // GetTokenClaims extracts TokenClaims from context. | |||
| 292 | + | // Returns nil if claims are not present or type assertion fails. | |||
| 293 | + | func GetTokenClaims(ctx context.Context) *TokenClaims { | |||
| 294 | + | claims, ok := ctx.Value(TokenClaimsKey).(*TokenClaims) | |||
| 295 | + | if !ok { | |||
| 296 | + | return nil | |||
| 297 | + | } | |||
| 298 | + | return claims | |||
| 299 | + | } | |||
M internal/usecase/auth/service.go +2 -3
| @@ -13,7 +13,6 @@ import ( | |||
| 13 | 13 | "github.com/google/uuid" | |
| 14 | 14 | "golang.org/x/crypto/bcrypt" | |
| 15 | 15 | ||
| 16 | - | "github.com/bnema/gordon/internal/adapters/in/http/middleware" | |
| 17 | 16 | "github.com/bnema/gordon/internal/boundaries/out" | |
| 18 | 17 | "github.com/bnema/gordon/internal/domain" | |
| 19 | 18 | ) | |
| @@ -465,8 +464,8 @@ func (s *Service) GetAuthStatus(ctx context.Context) (*domain.AuthStatus, error) | |||
| 465 | 464 | } | |
| 466 | 465 | ||
| 467 | 466 | // Extract claims from context (set by AdminAuth middleware) | |
| 468 | - | claims, ok := ctx.Value(middleware.TokenClaimsKey).(*domain.TokenClaims) | |
| 469 | - | if !ok || claims == nil { | |
| 467 | + | claims := domain.GetTokenClaims(ctx) | |
| 468 | + | if claims == nil { | |
| 470 | 469 | return &domain.AuthStatus{Valid: false}, nil | |
| 471 | 470 | } | |
| 472 | 471 | ||
| @@ -13,7 +13,6 @@ import ( | |||||
| 13 | "github.com/google/uuid" | 13 | "github.com/google/uuid" | ||
| 14 | "golang.org/x/crypto/bcrypt" | 14 | "golang.org/x/crypto/bcrypt" | ||
| 15 | 15 | ||||
| 16 | - | "github.com/bnema/gordon/internal/adapters/in/http/middleware" | |||
| 17 | "github.com/bnema/gordon/internal/boundaries/out" | 16 | "github.com/bnema/gordon/internal/boundaries/out" | ||
| 18 | "github.com/bnema/gordon/internal/domain" | 17 | "github.com/bnema/gordon/internal/domain" | ||
| 19 | ) | 18 | ) | ||
| @@ -465,8 +464,8 @@ func (s *Service) GetAuthStatus(ctx context.Context) (*domain.AuthStatus, error) | |||||
| 465 | } | 464 | } | ||
| 466 | 465 | ||||
| 467 | // Extract claims from context (set by AdminAuth middleware) | 466 | // Extract claims from context (set by AdminAuth middleware) | ||
| 468 | - | claims, ok := ctx.Value(middleware.TokenClaimsKey).(*domain.TokenClaims) | 467 | + | claims := domain.GetTokenClaims(ctx) |
| 469 | - | if !ok || claims == nil { | 468 | + | if claims == nil { |
| 470 | return &domain.AuthStatus{Valid: false}, nil | 469 | return &domain.AuthStatus{Valid: false}, nil | ||
| 471 | } | 470 | } | ||
| 472 | 471 | ||||