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

Method SwitchWorkspace

backend/api/v1/auth_service.go:1154–1240  ·  view source on GitHub ↗

SwitchWorkspace switches the current user's active workspace and issues new tokens.

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

Source from the content-addressed store, hash-verified

1152
1153// SwitchWorkspace switches the current user's active workspace and issues new tokens.
1154func (s *AuthService) SwitchWorkspace(ctx context.Context, req *connect.Request[v1pb.SwitchWorkspaceRequest]) (*connect.Response[v1pb.LoginResponse], error) {
1155 request := req.Msg
1156 if request.Workspace == "" {
1157 return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("workspace is required"))
1158 }
1159
1160 workspaceID, err := common.GetWorkspaceID(request.Workspace)
1161 if err != nil {
1162 return nil, connect.NewError(connect.CodeInvalidArgument, errors.Wrap(err, "invalid workspace name"))
1163 }
1164
1165 user, ok := GetUserFromContext(ctx)
1166 if !ok || user == nil {
1167 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("user not found"))
1168 }
1169 if user.Type != storepb.PrincipalType_END_USER {
1170 return nil, connect.NewError(connect.CodeInvalidArgument, errors.New("only end users can switch workspaces"))
1171 }
1172
1173 // Reject OAuth2 tokens — they are bound to a specific workspace via the OAuth client
1174 // and must not be used to mint plain user tokens for other workspaces.
1175 accessTokenStr, _ := auth.GetTokenFromHeaders(req.Header())
1176 if accessTokenStr != "" {
1177 tokenClaims, err := auth.ExtractClaimsFromExpiredToken(accessTokenStr, s.secret)
1178 if err == nil && slices.Contains(tokenClaims.Audience, auth.OAuth2AccessTokenAudience) {
1179 return nil, connect.NewError(connect.CodePermissionDenied, errors.New("OAuth2 tokens cannot be used to switch workspaces"))
1180 }
1181 }
1182
1183 // Verify the user is a member of the target workspace.
1184 ws, err := s.store.FindWorkspace(ctx, &store.FindWorkspaceMessage{
1185 WorkspaceID: &workspaceID,
1186 Email: user.Email,
1187 IncludeAllUser: !s.profile.SaaS,
1188 })
1189 if err != nil {
1190 return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err, "failed to find workspace"))
1191 }
1192 if ws == nil {
1193 return nil, connect.NewError(connect.CodePermissionDenied, errors.Errorf("not a member of workspace %q", workspaceID))
1194 }
1195
1196 // Validate the target workspace's sign-in policies.
1197 if user.MemberDeleted {
1198 return nil, connect.NewError(connect.CodeUnauthenticated, errors.New("user has been deactivated"))
1199 }
1200 if err := validateEmailWithDomains(ctx, s.licenseService, s.store, workspaceID, user.Email, false); err != nil {
1201 return nil, err
1202 }
1203
1204 // Check MFA requirement for the target workspace.
1205 mfaSecondStep := request.GetMfaTempToken() != ""
1206 if mfaSecondStep {
1207 // Check MFA lockout before verifying.
1208 if err := s.checkMFALockout(ctx, user.Email); err != nil {
1209 return nil, err
1210 }
1211 // Verify the MFA temp token and OTP/recovery code.

Callers

nothing calls this directly

Calls 14

checkMFALockoutMethod · 0.95
challengeRecoveryCodeMethod · 0.95
checkMFARequiredMethod · 0.95
GetWorkspaceIDFunction · 0.92
GetTokenFromHeadersFunction · 0.92
GetUserFromContextFunction · 0.85
validateEmailWithDomainsFunction · 0.85
challengeMFACodeFunction · 0.85
ErrorfMethod · 0.80

Tested by

no test coverage detected