provisionWorkspaceForNewUser returns a workspace ID for a freshly-created user. If the email was pre-invited to existing workspaces (via IAM), returns the first one. Otherwise creates a new workspace (SaaS: per-user; self-hosted: joins the singleton). Called by both the Signup RPC and the email-code
(ctx context.Context, email string)
| 366 | // Otherwise creates a new workspace (SaaS: per-user; self-hosted: joins the singleton). |
| 367 | // Called by both the Signup RPC and the email-code signup branch of Login. |
| 368 | func (s *AuthService) provisionWorkspaceForNewUser(ctx context.Context, email string) (string, error) { |
| 369 | // Step 1: Resolve the target workspace. isMember indicates whether the user already has |
| 370 | // an IAM binding. For pre-invited users we must NOT patch IAM — PatchWorkspaceIamPolicy |
| 371 | // is a set-replacement that would downgrade an admin to member. |
| 372 | workspaceID, isMember, err := s.resolveWorkspaceIDByEmail(ctx, email) |
| 373 | if err != nil { |
| 374 | return "", err |
| 375 | } |
| 376 | |
| 377 | if workspaceID != "" { |
| 378 | if !s.profile.SaaS && !isMember { |
| 379 | // Self-hosted, new user joining the singleton workspace — add as member. |
| 380 | if _, err := s.store.PatchWorkspaceIamPolicy(ctx, &store.PatchIamPolicyMessage{ |
| 381 | Workspace: workspaceID, |
| 382 | Member: common.FormatUserEmail(email), |
| 383 | Roles: []string{common.FormatRole(store.WorkspaceMemberRole)}, |
| 384 | }); err != nil { |
| 385 | return "", errors.Wrapf(err, "failed to add user to workspace") |
| 386 | } |
| 387 | } |
| 388 | return workspaceID, nil |
| 389 | } |
| 390 | |
| 391 | // Step 2: No existing workspace — create a new one with the user as admin. |
| 392 | wsID, err := common.RandomString(16) |
| 393 | if err != nil { |
| 394 | return "", errors.Wrap(err, "failed to generate workspace ID") |
| 395 | } |
| 396 | ws, err := s.store.CreateWorkspace(ctx, &store.WorkspaceMessage{ |
| 397 | ResourceID: wsID, |
| 398 | Payload: &storepb.WorkspacePayload{Title: "Default workspace"}, |
| 399 | AdditionalSettings: s.getAdditionalWorkspaceSettings(), |
| 400 | }, email) |
| 401 | if err != nil { |
| 402 | return "", errors.Wrapf(err, "failed to create workspace") |
| 403 | } |
| 404 | |
| 405 | return ws.ResourceID, nil |
| 406 | } |
| 407 | |
| 408 | // Logout is the auth logout method. |
| 409 | func (s *AuthService) Logout(ctx context.Context, req *connect.Request[v1pb.LogoutRequest]) (*connect.Response[emptypb.Empty], error) { |
no test coverage detected