@@ -2,6 +2,8 @@ package auth
22
33import (
44 "context"
5+ "crypto/sha256"
6+ "crypto/subtle"
57 "fmt"
68 "net/http"
79 "strings"
@@ -100,11 +102,8 @@ func (s *Service) GetAuthorizationFromBearerToken(_ context.Context, headerToken
100102
101103 token := parts [1 ]
102104
103- if token != s .secretKey {
104- return "" , & api.HTTPError {
105- Message : "Invalid token" ,
106- StatusCode : http .StatusUnauthorized ,
107- }
105+ if err := s .validateSecretKey (token ); err != nil {
106+ return "" , err
108107 }
109108
110109 return token , nil
@@ -118,11 +117,8 @@ func (s *Service) GetAuthorizationFromQuery(_ context.Context, queryToken string
118117 }
119118 }
120119
121- if queryToken != s .secretKey {
122- return "" , & api.HTTPError {
123- Message : "Invalid token" ,
124- StatusCode : http .StatusUnauthorized ,
125- }
120+ if err := s .validateSecretKey (queryToken ); err != nil {
121+ return "" , err
126122 }
127123
128124 return queryToken , nil
@@ -152,3 +148,24 @@ func (s *Service) IsAdminFromOauth(ctx context.Context) (bool, error) {
152148
153149 return true , nil
154150}
151+
152+ // Compare the token against the secret in constant time to avoid leaking
153+ // information via timing differences. We hash both values first so the
154+ // comparison runs over fixed-length (32-byte) inputs; subtle.ConstantTimeCompare
155+ // would otherwise short-circuit on a length mismatch and leak the secret's length.
156+ //
157+ // Rate limiting (Cloudflare + API Gateway) is the primary defense against
158+ // brute-force/timing attacks; this is defense-in-depth in case those fail open.
159+ func (s * Service ) validateSecretKey (token string ) error {
160+ tokenHash := sha256 .Sum256 ([]byte (token ))
161+ secretKeyHash := sha256 .Sum256 ([]byte (s .secretKey ))
162+
163+ if subtle .ConstantTimeCompare (tokenHash [:], secretKeyHash [:]) != 1 {
164+ return & api.HTTPError {
165+ Message : "Invalid token" ,
166+ StatusCode : http .StatusUnauthorized ,
167+ }
168+ }
169+
170+ return nil
171+ }
0 commit comments