ExtractAPIKey requires authentication using a valid API key. It handles extending an API key if it comes close to expiry, updating the last used time in the database. If the configuration specifies that the API key is optional, a nil API key and authz object may be returned. False is returned if a
(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyConfig)
| 575 | // to the request and the caller should give up. |
| 576 | // nolint:revive |
| 577 | func ExtractAPIKey(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyConfig) (*database.APIKey, *rbac.Subject, bool) { |
| 578 | ctx := r.Context() |
| 579 | // Write wraps writing a response to redirect if the handler |
| 580 | // specified it should. This redirect is used for user-facing pages |
| 581 | // like workspace applications. |
| 582 | write := func(code int, response codersdk.Response) (apiKey *database.APIKey, subject *rbac.Subject, ok bool) { |
| 583 | if cfg.RedirectToLogin { |
| 584 | RedirectToLogin(rw, r, nil, response.Message) |
| 585 | return nil, nil, false |
| 586 | } |
| 587 | |
| 588 | // Add WWW-Authenticate header for 401/403 responses (RFC 6750 + RFC 9728) |
| 589 | if code == http.StatusUnauthorized || code == http.StatusForbidden { |
| 590 | rw.Header().Set("WWW-Authenticate", buildWWWAuthenticateHeader(cfg.AccessURL, r, code, response)) |
| 591 | } |
| 592 | |
| 593 | httpapi.Write(ctx, rw, code, response) |
| 594 | return nil, nil, false |
| 595 | } |
| 596 | |
| 597 | // optionalWrite wraps write, but will return nil, true if the API key is |
| 598 | // optional. |
| 599 | // |
| 600 | // It should be used when the API key is not provided or is invalid, |
| 601 | // but not when there are other errors. |
| 602 | optionalWrite := func(code int, response codersdk.Response) (*database.APIKey, *rbac.Subject, bool) { |
| 603 | if cfg.Optional { |
| 604 | return nil, nil, true |
| 605 | } |
| 606 | |
| 607 | write(code, response) |
| 608 | return nil, nil, false |
| 609 | } |
| 610 | |
| 611 | // --- Consume prechecked result if available --- |
| 612 | // Skip prechecked data when cfg has a custom SessionTokenFunc, |
| 613 | // because the precheck used the default token extraction and may |
| 614 | // have validated a different token (e.g. workspace app token |
| 615 | // issuance in workspaceapps/db.go). |
| 616 | var key *database.APIKey |
| 617 | var actor rbac.Subject |
| 618 | var userStatus database.UserStatus |
| 619 | var skipValidation bool |
| 620 | |
| 621 | if cfg.SessionTokenFunc == nil { |
| 622 | if pc, ok := ctx.Value(apiKeyPrecheckedContextKey{}).(APIKeyPrechecked); ok { |
| 623 | if pc.Err != nil { |
| 624 | // Validation failed at the top level (includes |
| 625 | // "no token provided"). |
| 626 | if pc.Err.Hard { |
| 627 | return write(pc.Err.Code, pc.Err.Response) |
| 628 | } |
| 629 | return optionalWrite(pc.Err.Code, pc.Err.Response) |
| 630 | } |
| 631 | // Valid — use prechecked data, skip to route-specific logic. |
| 632 | key = &pc.Result.Key |
| 633 | actor = pc.Result.Subject |
| 634 | userStatus = pc.Result.UserStatus |
no test coverage detected