From d25313213f1f19a2e61228d9ae42a71aded8f220 Mon Sep 17 00:00:00 2001 From: Robin Munn Date: Wed, 25 Sep 2024 12:20:36 +0700 Subject: [PATCH] Don't use GQL queries, use alternate page Instead of GQL queries to check if the user already exists, we put the logic into LoginController. This works: users can now receive multiple invitation links, and only see the registration page if they have not registered yet. If they have registered before, they're taken to an alternate URL that logs them in immediately. Still to do: ensure that the acceptInvitation backend doesn't throw an error if the user is already a member of the project, but just silently returns success if we're already in the desired state. --- .../LexBoxApi/Controllers/LoginController.cs | 12 +++++- backend/LexBoxApi/GraphQL/LexQueries.cs | 21 ---------- backend/LexBoxApi/Services/EmailService.cs | 3 +- frontend/schema.graphql | 1 - .../acceptInvitationImmediately/+page.svelte | 38 +++++++++++++++++++ 5 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 frontend/src/routes/(unauthenticated)/acceptInvitationImmediately/+page.svelte diff --git a/backend/LexBoxApi/Controllers/LoginController.cs b/backend/LexBoxApi/Controllers/LoginController.cs index f7371c07e..c4a3f9412 100644 --- a/backend/LexBoxApi/Controllers/LoginController.cs +++ b/backend/LexBoxApi/Controllers/LoginController.cs @@ -14,6 +14,7 @@ using System.Security.Claims; using Microsoft.AspNetCore.Authentication.Google; using LexBoxApi.Services.Email; +using LexData.Entities; namespace LexBoxApi.Controllers; @@ -36,7 +37,8 @@ public class LoginController( [AllowAnyAudience] public async Task LoginRedirect( string jwt, // This is required because auth looks for a jwt in the query string - string returnTo) + string returnTo, + string? returnToIfEmailExists) { var user = loggedInContext.User; // A RegisterAccount token means there's no user account yet, so checking UpdatedDate makes no sense @@ -51,7 +53,13 @@ public async Task LoginRedirect( await HttpContext.SignInAsync(User, new AuthenticationProperties { IsPersistent = true }); - return Redirect(returnTo); + string destination = returnTo; + if (returnToIfEmailExists is not null && user.Email is not null) + { + var dbUser = await lexBoxDbContext.Users.FindByEmailOrUsername(user.Email); + if (dbUser is not null) destination = returnToIfEmailExists!; + } + return Redirect(destination); } [HttpGet("google")] diff --git a/backend/LexBoxApi/GraphQL/LexQueries.cs b/backend/LexBoxApi/GraphQL/LexQueries.cs index 4319da490..102064c78 100644 --- a/backend/LexBoxApi/GraphQL/LexQueries.cs +++ b/backend/LexBoxApi/GraphQL/LexQueries.cs @@ -1,4 +1,3 @@ -using HotChocolate.Authorization; using HotChocolate.Resolvers; using LexBoxApi.Auth; using LexBoxApi.Auth.Attributes; @@ -202,26 +201,6 @@ public IQueryable Users(LexBoxDbContext context) }; } - [AllowAnonymous] - public async Task UserById(LexBoxDbContext context, LoggedInContext loggedInContext, Guid userId) - { - var registeringUser = loggedInContext.User; - // Only admins can look up users other than themselves via this query - if (!registeringUser.IsAdmin && registeringUser.Id != userId) - { - throw new UnauthorizedAccessException(); - } - var user = await context.Users.FindAsync(userId); - if (user == null) return null; - return new MeDto - { - Id = user.Id, - Name = user.Name, - Email = user.Email, - Locale = user.LocalizationCode - }; - } - public async Task OrgMemberById(LexBoxDbContext context, IPermissionService permissionService, Guid orgId, Guid userId) { // Only site admins and org admins are allowed to run this query diff --git a/backend/LexBoxApi/Services/EmailService.cs b/backend/LexBoxApi/Services/EmailService.cs index b1a11a77c..726107919 100644 --- a/backend/LexBoxApi/Services/EmailService.cs +++ b/backend/LexBoxApi/Services/EmailService.cs @@ -168,10 +168,11 @@ private async Task SendInvitationEmail( var queryString = QueryString.Create("email", emailAddress); var returnTo = new UriBuilder { Path = "/acceptInvitation", Query = queryString.Value }.Uri.PathAndQuery; + var returnToIfEmailExists = new UriBuilder { Path = "/acceptInvitationImmediately", Query = queryString.Value }.Uri.PathAndQuery; var registerLink = _linkGenerator.GetUriByAction(httpContext, "LoginRedirect", "Login", - new { jwt, returnTo }); + new { jwt, returnTo, returnToIfEmailExists }); ArgumentException.ThrowIfNullOrEmpty(registerLink); if (isProjectInvitation) diff --git a/frontend/schema.graphql b/frontend/schema.graphql index a1bb2769c..721aa51c2 100644 --- a/frontend/schema.graphql +++ b/frontend/schema.graphql @@ -426,7 +426,6 @@ type Query { orgById(orgId: UUID!): OrgById users(skip: Int take: Int where: UserFilterInput orderBy: [UserSortInput!]): UsersCollectionSegment @authorize(policy: "AdminRequiredPolicy") me: MeDto - userById(userId: UUID!): MeDto @authorize(policy: "AllowAnyAudiencePolicy") orgMemberById(orgId: UUID! userId: UUID!): OrgMemberDto meAuth: LexAuthUser! testingThrowsError: LexAuthUser! diff --git a/frontend/src/routes/(unauthenticated)/acceptInvitationImmediately/+page.svelte b/frontend/src/routes/(unauthenticated)/acceptInvitationImmediately/+page.svelte new file mode 100644 index 000000000..4468f8998 --- /dev/null +++ b/frontend/src/routes/(unauthenticated)/acceptInvitationImmediately/+page.svelte @@ -0,0 +1,38 @@ + + +
+ +