Tokens Uses Sessions.DefaultDuration for access token (API key) TTL and Sessions.RefreshDefaultDuration for refresh token TTL.
(db database.Store, lifetimes codersdk.SessionLifetime)
| 121 | // Uses Sessions.DefaultDuration for access token (API key) TTL and |
| 122 | // Sessions.RefreshDefaultDuration for refresh token TTL. |
| 123 | func Tokens(db database.Store, lifetimes codersdk.SessionLifetime) http.HandlerFunc { |
| 124 | return func(rw http.ResponseWriter, r *http.Request) { |
| 125 | ctx := r.Context() |
| 126 | app := httpmw.OAuth2ProviderApp(r) |
| 127 | |
| 128 | callbackURL, err := url.Parse(app.CallbackURL) |
| 129 | if err != nil { |
| 130 | httpapi.Write(ctx, rw, http.StatusInternalServerError, codersdk.Response{ |
| 131 | Message: "Failed to validate form values.", |
| 132 | Detail: err.Error(), |
| 133 | }) |
| 134 | return |
| 135 | } |
| 136 | |
| 137 | req, validationErrs, err := extractTokenRequest(r, callbackURL) |
| 138 | if err != nil { |
| 139 | if errors.Is(err, errConflictingClientAuth) { |
| 140 | httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, codersdk.OAuth2ErrorCodeInvalidRequest, "Conflicting client credentials between Authorization header and request body") |
| 141 | return |
| 142 | } |
| 143 | |
| 144 | // Check for specific validation errors in priority order |
| 145 | if slices.ContainsFunc(validationErrs, func(validationError codersdk.ValidationError) bool { |
| 146 | return validationError.Field == "grant_type" |
| 147 | }) { |
| 148 | httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, codersdk.OAuth2ErrorCodeUnsupportedGrantType, "The grant type is missing or unsupported") |
| 149 | return |
| 150 | } |
| 151 | |
| 152 | // Check for missing required parameters for authorization_code grant |
| 153 | for _, field := range []string{"code", "client_id", "client_secret"} { |
| 154 | if slices.ContainsFunc(validationErrs, func(validationError codersdk.ValidationError) bool { |
| 155 | return validationError.Field == field |
| 156 | }) { |
| 157 | httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, codersdk.OAuth2ErrorCodeInvalidRequest, fmt.Sprintf("Missing required parameter: %s", field)) |
| 158 | return |
| 159 | } |
| 160 | } |
| 161 | // Generic invalid request for other validation errors |
| 162 | httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, codersdk.OAuth2ErrorCodeInvalidRequest, "The request is missing required parameters or is otherwise malformed") |
| 163 | return |
| 164 | } |
| 165 | |
| 166 | var token codersdk.OAuth2TokenResponse |
| 167 | //nolint:gocritic,revive // More cases will be added later. |
| 168 | switch req.GrantType { |
| 169 | // TODO: Client creds, device code. |
| 170 | case codersdk.OAuth2ProviderGrantTypeRefreshToken: |
| 171 | token, err = refreshTokenGrant(ctx, db, app, lifetimes, req) |
| 172 | case codersdk.OAuth2ProviderGrantTypeAuthorizationCode: |
| 173 | token, err = authorizationCodeGrant(ctx, db, app, lifetimes, req) |
| 174 | default: |
| 175 | // This should handle truly invalid grant types |
| 176 | httpapi.WriteOAuth2Error(ctx, rw, http.StatusBadRequest, codersdk.OAuth2ErrorCodeUnsupportedGrantType, fmt.Sprintf("The grant type %q is not supported", req.GrantType)) |
| 177 | return |
| 178 | } |
| 179 | |
| 180 | if errors.Is(err, errBadSecret) { |
no test coverage detected