| 248 | } |
| 249 | |
| 250 | func (s *Storage) GetAuthorizeCodeSession(ctx context.Context, code string, session fosite.Session) (fosite.Requester, error) { |
| 251 | var raw []byte |
| 252 | var active bool |
| 253 | err := s.db(ctx).QueryRow(ctx, ` |
| 254 | SELECT request, active FROM oauth_auth_codes WHERE signature = $1 |
| 255 | `, code).Scan(&raw, &active) |
| 256 | if errors.Is(err, pgx.ErrNoRows) { |
| 257 | return nil, fosite.ErrNotFound |
| 258 | } |
| 259 | if err != nil { |
| 260 | return nil, err |
| 261 | } |
| 262 | req, err := s.hydrate(ctx, raw, session) |
| 263 | if err != nil { |
| 264 | return nil, err |
| 265 | } |
| 266 | if !active { |
| 267 | // Reuse detection: the row exists but was invalidated. fosite's |
| 268 | // contract is to return BOTH the request and ErrInvalidatedAuthorizeCode |
| 269 | // so the caller can revoke downstream tokens (RFC 6749 §10.5). |
| 270 | return req, fosite.ErrInvalidatedAuthorizeCode |
| 271 | } |
| 272 | return req, nil |
| 273 | } |
| 274 | |
| 275 | // InvalidateAuthorizeCodeSession marks the code consumed via a CAS |
| 276 | // `WHERE signature = $1 AND active = TRUE`. The CAS is load-bearing: |