MCPcopy Create free account
hub / github.com/bytebase/bytebase / Refresh

Method Refresh

backend/api/v1/auth_service.go:428–519  ·  view source on GitHub ↗

Refresh exchanges a refresh token for new access and refresh tokens.

(ctx context.Context, req *connect.Request[v1pb.RefreshRequest])

Source from the content-addressed store, hash-verified

426
427// Refresh exchanges a refresh token for new access and refresh tokens.
428func (s *AuthService) Refresh(ctx context.Context, req *connect.Request[v1pb.RefreshRequest]) (*connect.Response[v1pb.RefreshResponse], error) {
429 // 1. Extract refresh token from cookie
430 refreshToken := auth.GetRefreshTokenFromCookie(req.Header())
431 if refreshToken == "" {
432 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("refresh token not found"))
433 }
434
435 // 2. Look up and delete atomically
436 tokenHash := auth.HashToken(refreshToken)
437 stored, err := s.store.GetAndDeleteWebRefreshToken(ctx, tokenHash)
438 if err != nil {
439 return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err, "failed to get refresh token"))
440 }
441 if stored == nil {
442 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("invalid refresh token"))
443 }
444
445 // 3. Check expiration
446 if time.Now().After(stored.ExpiresAt) {
447 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("refresh token expired"))
448 }
449
450 // 4. Get user
451 user, err := s.store.GetUserByEmail(ctx, stored.UserEmail)
452 if err != nil {
453 return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err, "failed to get user"))
454 }
455 if user == nil || user.MemberDeleted {
456 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("user not found"))
457 }
458
459 // 5. Extract workspace from the access token cookie (still present because cookie
460 // outlives the JWT by 30 seconds). This ensures per-session workspace isolation.
461 // Also verify the token's subject matches the refresh token's user to prevent
462 // pairing a refresh token with an access token from a different session.
463 accessTokenStr, err := auth.GetTokenFromHeaders(req.Header())
464 if err != nil {
465 return nil, connect.NewError(connect.CodeUnauthenticated, errors.Wrap(err, "invalid access token header"))
466 }
467 if accessTokenStr == "" {
468 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("access token cookie required for refresh"))
469 }
470 tokenClaims, err := auth.ExtractClaimsFromExpiredToken(accessTokenStr, s.secret)
471 if err != nil || tokenClaims.WorkspaceID == "" {
472 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("failed to extract workspace from access token"))
473 }
474 if tokenClaims.Subject != stored.UserEmail {
475 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("access token does not match refresh token"))
476 }
477 workspaceID := tokenClaims.WorkspaceID
478
479 // Verify the user is still a member of the workspace.
480 ws, err := s.store.FindWorkspace(ctx, &store.FindWorkspaceMessage{
481 WorkspaceID: &workspaceID,
482 Email: user.Email,
483 IncludeAllUser: !s.profile.SaaS,
484 })
485 if err != nil {

Callers

nothing calls this directly

Calls 15

HashTokenFunction · 0.92
GetTokenFromHeadersFunction · 0.92
GetAccessTokenDurationFunction · 0.92
GenerateAccessTokenFunction · 0.92
GenerateOpaqueTokenFunction · 0.92
GetTokenCookieFunction · 0.92
GetRefreshTokenCookieFunction · 0.92
GetUserByEmailMethod · 0.80
ErrorfMethod · 0.80

Tested by

no test coverage detected