From a1de5f1b5da029e35350c6c23fd64d995baa1673 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Wed, 15 Jan 2025 10:42:10 -0500 Subject: [PATCH] CreateGuestUser now adds users to orgs --- backend/LexBoxApi/GraphQL/UserMutations.cs | 8 ++++++-- backend/LexBoxApi/Services/PermissionService.cs | 14 ++++++++++++++ .../ServiceInterfaces/IPermissionService.cs | 2 ++ frontend/schema.graphql | 2 +- frontend/src/lib/gql/gql-client.ts | 6 ++++++ frontend/src/lib/user.ts | 3 ++- .../(authenticated)/org/[org_id]/+page.svelte | 6 +++++- 7 files changed, 36 insertions(+), 5 deletions(-) diff --git a/backend/LexBoxApi/GraphQL/UserMutations.cs b/backend/LexBoxApi/GraphQL/UserMutations.cs index 7cb290a6a..ad9ce2e71 100644 --- a/backend/LexBoxApi/GraphQL/UserMutations.cs +++ b/backend/LexBoxApi/GraphQL/UserMutations.cs @@ -35,7 +35,7 @@ public record CreateGuestUserByAdminInput( string Locale, string PasswordHash, int PasswordStrength, - Guid? ProjectId); + Guid? OrgId); [Error] [Error] @@ -106,7 +106,7 @@ IEmailService emailService ) { using var createGuestUserActivity = LexBoxActivitySource.Get().StartActivity("CreateGuestUser"); - permissionService.AssertCanCreateGuestUserInProject(input.ProjectId); + permissionService.AssertCanCreateGuestUserInOrg(input.OrgId); var hasExistingUser = input.Email is null && input.Username is null ? throw new RequiredException("Guest users must have either an email or a username") @@ -134,6 +134,10 @@ IEmailService emailService CanCreateProjects = false }; createGuestUserActivity?.AddTag("app.user.id", userEntity.Id); + if (input.OrgId is not null) + { + userEntity.Organizations.Add(new OrgMember() { OrgId = input.OrgId.Value, Role = OrgRole.User }); + } dbContext.Users.Add(userEntity); await dbContext.SaveChangesAsync(); if (!string.IsNullOrEmpty(input.Email)) diff --git a/backend/LexBoxApi/Services/PermissionService.cs b/backend/LexBoxApi/Services/PermissionService.cs index 48cfec733..b4d582234 100644 --- a/backend/LexBoxApi/Services/PermissionService.cs +++ b/backend/LexBoxApi/Services/PermissionService.cs @@ -155,6 +155,20 @@ public async ValueTask AssertCanCreateGuestUserInProject(Guid? projectId) if (!await CanCreateGuestUserInProject(projectId)) throw new UnauthorizedAccessException(); } + public bool CanCreateGuestUserInOrg(Guid? orgId) + { + if (User is null) return false; + if (User.Role == UserRole.admin) return true; + // Site admins can create guest users even with no org, anyone else (like org admins) must specify an org ID + if (orgId is null) return false; + return CanEditOrg(orgId.Value); + } + + public void AssertCanCreateGuestUserInOrg(Guid? orgId) + { + if (!CanCreateGuestUserInOrg(orgId)) throw new UnauthorizedAccessException(); + } + public async ValueTask CanAskToJoinProject(Guid projectId) { if (User is null) return false; diff --git a/backend/LexCore/ServiceInterfaces/IPermissionService.cs b/backend/LexCore/ServiceInterfaces/IPermissionService.cs index 97233f5a5..f1e1f1297 100644 --- a/backend/LexCore/ServiceInterfaces/IPermissionService.cs +++ b/backend/LexCore/ServiceInterfaces/IPermissionService.cs @@ -22,6 +22,8 @@ public interface IPermissionService ValueTask AssertCanManageProjectMemberRole(Guid projectId, Guid userId); ValueTask CanCreateGuestUserInProject(Guid? projectId); ValueTask AssertCanCreateGuestUserInProject(Guid? projectId); + bool CanCreateGuestUserInOrg(Guid? orgId); + void AssertCanCreateGuestUserInOrg(Guid? orgId); ValueTask CanAskToJoinProject(Guid projectId); ValueTask CanAskToJoinProject(string projectCode); ValueTask AssertCanAskToJoinProject(Guid projectId); diff --git a/frontend/schema.graphql b/frontend/schema.graphql index 4fc2500ce..20ce63e85 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -727,7 +727,7 @@ input CreateGuestUserByAdminInput { locale: String! passwordHash: String! passwordStrength: Int! - projectId: UUID + orgId: UUID } input CreateOrganizationInput { diff --git a/frontend/src/lib/gql/gql-client.ts b/frontend/src/lib/gql/gql-client.ts index caf3f7e05..7e5dd8d82 100644 --- a/frontend/src/lib/gql/gql-client.ts +++ b/frontend/src/lib/gql/gql-client.ts @@ -37,6 +37,7 @@ import { type MutationBulkAddProjectMembersArgs, type MutationChangeOrgMemberRoleArgs, type MutationChangeUserAccountBySelfArgs, + type MutationCreateGuestUserByAdminArgs, type MutationCreateOrganizationArgs, type MutationCreateProjectArgs, type MutationDeleteDraftProjectArgs, @@ -99,6 +100,11 @@ function createGqlClient(_gqlEndpoint?: string): Client { cache.invalidate({__typename: 'Project', id: args.input.projectId}); } }, + createGuestUserByAdmin: (result, args: MutationCreateGuestUserByAdminArgs, cache, _info) => { + if (args.input.orgId) { + cache.invalidate({__typename: 'OrgById', id: args.input.orgId}); + } + }, createOrganization: (result: CreateOrgMutation, args: MutationCreateOrganizationArgs, cache, _info) => { cache.invalidate('Query', 'myOrgs'); }, diff --git a/frontend/src/lib/user.ts b/frontend/src/lib/user.ts index eb7ae5e8c..cbd14a2a8 100644 --- a/frontend/src/lib/user.ts +++ b/frontend/src/lib/user.ts @@ -120,13 +120,14 @@ export function register(password: string, passwordStrength: number, name: strin export function acceptInvitation(password: string, passwordStrength: number, name: string, email: string, locale: string, turnstileToken: string): Promise { return createUser('/api/User/acceptInvitation', password, passwordStrength, name, email, locale, turnstileToken); } -export async function createGuestUserByAdmin(password: string, passwordStrength: number, name: string, email: string, locale: string, _turnstileToken: string): Promise { +export async function createGuestUserByAdmin(password: string, passwordStrength: number, name: string, email: string, locale: string, _turnstileToken: string, orgId?: string): Promise { const passwordHash = await hash(password); const gqlInput: CreateGuestUserByAdminInput = { passwordHash, passwordStrength, name, locale, + orgId, }; if (email.includes('@')) { gqlInput.email = email; diff --git a/frontend/src/routes/(authenticated)/org/[org_id]/+page.svelte b/frontend/src/routes/(authenticated)/org/[org_id]/+page.svelte index d474c518e..b6214748f 100644 --- a/frontend/src/routes/(authenticated)/org/[org_id]/+page.svelte +++ b/frontend/src/routes/(authenticated)/org/[org_id]/+page.svelte @@ -120,6 +120,10 @@ } } + function createGuestUser(password: string, passwordStrength: number, name: string, email: string, locale: string, _turnstileToken: string): ReturnType { + return createGuestUserByAdmin(password, passwordStrength, name, email, locale, _turnstileToken, org.id); + } + let createUserModal: CreateUserModal; function onUserCreated(user: LexAuthUser): void { notifySuccess($t('admin_dashboard.notifications.user_created', { name: user.name }), Duration.Long); @@ -158,7 +162,7 @@ - onUserCreated(e.detail)} bind:this={createUserModal}/> + onUserCreated(e.detail)} bind:this={createUserModal}/> {/if}