issueTokens issues a new OAuth2 access token (and refresh token, when the grant supports it) bound to the given workspace. The workspace is sourced from the authorization code or refresh token being exchanged, not from the client — clients are workspace-agnostic.
(c *echo.Context, client *store.OAuth2ClientMessage, userEmail, workspaceID string)
| 300 | // from the authorization code or refresh token being exchanged, not from the |
| 301 | // client — clients are workspace-agnostic. |
| 302 | func (s *Service) issueTokens(c *echo.Context, client *store.OAuth2ClientMessage, userEmail, workspaceID string) error { |
| 303 | ctx := c.Request().Context() |
| 304 | |
| 305 | // Generate access token (JWT) with the workspace_id claim that |
| 306 | // downstream APIs (gRPC services, MCP middleware) use to scope requests. |
| 307 | accessToken, err := auth.GenerateOAuth2AccessToken(userEmail, client.ClientID, workspaceID, s.secret, accessTokenExpiry) |
| 308 | if err != nil { |
| 309 | return oauth2Error(c, http.StatusInternalServerError, "server_error", fmt.Sprintf("failed to generate access token with error: %v", err)) |
| 310 | } |
| 311 | |
| 312 | now := time.Now() |
| 313 | |
| 314 | // Generate refresh token if allowed |
| 315 | var refreshTokenStr string |
| 316 | if slices.Contains(client.Config.GrantTypes, "refresh_token") { |
| 317 | refreshTokenStr, err = generateRefreshToken() |
| 318 | if err != nil { |
| 319 | return oauth2Error(c, http.StatusInternalServerError, "server_error", "failed to generate refresh token") |
| 320 | } |
| 321 | |
| 322 | // Store refresh token with the workspace binding preserved so a |
| 323 | // subsequent /token refresh re-issues for the same workspace. |
| 324 | if _, err := s.store.CreateOAuth2RefreshToken(ctx, &store.OAuth2RefreshTokenMessage{ |
| 325 | TokenHash: auth.HashToken(refreshTokenStr), |
| 326 | ClientID: client.ClientID, |
| 327 | UserEmail: userEmail, |
| 328 | Workspace: workspaceID, |
| 329 | ExpiresAt: now.Add(refreshTokenExpiry), |
| 330 | }); err != nil { |
| 331 | return oauth2Error(c, http.StatusInternalServerError, "server_error", "failed to store refresh token") |
| 332 | } |
| 333 | } |
| 334 | |
| 335 | if err := s.store.UpdateOAuth2ClientLastActiveAt(ctx, client.ClientID); err != nil { |
| 336 | slog.Warn("failed to update OAuth2 client last active", slog.String("clientID", client.ClientID), log.BBError(err)) |
| 337 | } |
| 338 | |
| 339 | return c.JSON(http.StatusOK, &tokenResponse{ |
| 340 | AccessToken: accessToken, |
| 341 | TokenType: "Bearer", |
| 342 | ExpiresIn: int(accessTokenExpiry.Seconds()), |
| 343 | RefreshToken: refreshTokenStr, |
| 344 | }) |
| 345 | } |
no test coverage detected