finalizeLogin builds the response, sets cookies if needed, and updates the user profile.
(ctx context.Context, req *connect.Request[v1pb.LoginRequest], user *store.UserMessage, token string, workspaceID string, requireResetPassword bool)
| 1316 | |
| 1317 | // finalizeLogin builds the response, sets cookies if needed, and updates the user profile. |
| 1318 | func (s *AuthService) finalizeLogin(ctx context.Context, req *connect.Request[v1pb.LoginRequest], user *store.UserMessage, token string, workspaceID string, requireResetPassword bool) (*connect.Response[v1pb.LoginResponse], error) { |
| 1319 | response := &v1pb.LoginResponse{ |
| 1320 | RequireResetPassword: requireResetPassword, |
| 1321 | } |
| 1322 | resp := connect.NewResponse(response) |
| 1323 | |
| 1324 | if req.Msg.Web { |
| 1325 | if user.Type != storepb.PrincipalType_END_USER { |
| 1326 | return nil, connect.NewError(connect.CodePermissionDenied, errors.Errorf("only users can use web login")) |
| 1327 | } |
| 1328 | origin := req.Header().Get("Origin") |
| 1329 | cookie := auth.GetTokenCookie(ctx, s.store, s.licenseService, workspaceID, origin, token) |
| 1330 | resp.Header().Add("Set-Cookie", cookie.String()) |
| 1331 | |
| 1332 | // Issue refresh token for web login |
| 1333 | refreshToken, err := auth.GenerateOpaqueToken() |
| 1334 | if err != nil { |
| 1335 | return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err, "failed to generate refresh token")) |
| 1336 | } |
| 1337 | refreshTokenDuration := auth.GetRefreshTokenDuration(ctx, s.store, s.licenseService, workspaceID) |
| 1338 | if err := s.store.CreateWebRefreshToken(ctx, &store.WebRefreshTokenMessage{ |
| 1339 | TokenHash: auth.HashToken(refreshToken), |
| 1340 | UserEmail: user.Email, |
| 1341 | ExpiresAt: time.Now().Add(refreshTokenDuration), |
| 1342 | }); err != nil { |
| 1343 | return nil, connect.NewError(connect.CodeInternal, errors.Wrap(err, "failed to create refresh token")) |
| 1344 | } |
| 1345 | refreshCookie := auth.GetRefreshTokenCookie(origin, refreshToken, refreshTokenDuration) |
| 1346 | resp.Header().Add("Set-Cookie", refreshCookie.String()) |
| 1347 | } else { |
| 1348 | // For non-web clients (CLI, API), return the token in the response body. |
| 1349 | response.Token = token |
| 1350 | } |
| 1351 | |
| 1352 | if user.Type == storepb.PrincipalType_END_USER { |
| 1353 | if _, err := s.store.UpdateUser(ctx, user, &store.UpdateUserMessage{ |
| 1354 | Profile: &storepb.UserProfile{ |
| 1355 | LastLoginTime: timestamppb.Now(), |
| 1356 | LastChangePasswordTime: user.Profile.GetLastChangePasswordTime(), |
| 1357 | Source: user.Profile.GetSource(), |
| 1358 | LastLoginWorkspace: workspaceID, |
| 1359 | }, |
| 1360 | }); err != nil { |
| 1361 | slog.Error("failed to update user profile", log.BBError(err), slog.String("user", user.Email)) |
| 1362 | } |
| 1363 | |
| 1364 | v1User, err := convertToUser(ctx, s.iamManager, user) |
| 1365 | if err != nil { |
| 1366 | return nil, connect.NewError(connect.CodeInternal, errors.Wrapf(err, "failed to convert user")) |
| 1367 | } |
| 1368 | v1User.Workspace = common.FormatWorkspace(workspaceID) |
| 1369 | response.User = v1User |
| 1370 | } |
| 1371 | |
| 1372 | return resp, nil |
| 1373 | } |
| 1374 | |
| 1375 | // ExchangeToken exchanges an external OIDC token for a Bytebase access token. |
no test coverage detected