Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow multiple project invites for one user #1043

Merged
merged 20 commits into from
Sep 27, 2024

Conversation

rmunn
Copy link
Contributor

@rmunn rmunn commented Aug 29, 2024

Fixes #584.

Now that we have both org and project invites, it's going to be pretty common for a user to receive at least two invites in a short time: one to invite him to an org, and the other to a project (likely owned by the org).

Copy link

github-actions bot commented Aug 29, 2024

C# Unit Tests

71 tests  ±0   71 ✅ ±0   7s ⏱️ -1s
12 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 208556f. ± Comparison against base commit fc5232d.

♻️ This comment has been updated with latest results.

Copy link
Collaborator

@hahn-kev hahn-kev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

help me understand the workflow here. User clicks an invite link, they land at the register page and register. They click another invite and then what? they already registered, so they should just be added automatically. But it looks like they would be required to 'register' again which doesn't really make sense. They should just be able to click the link and the invite is accepted on the account they already created (assuming details match etc).

@rmunn
Copy link
Contributor Author

rmunn commented Aug 30, 2024

After looking at the code review, I think I got the flow wrong. I thought the accept-invite link went to the login controller and from there redirected to the AcceptEmailInvitation backend method. But it goes through the frontend first, and that's why my code didn't make sense: I was getting this PR quite wrong. I'll rewrite it now that I've corrected the mistake in my mental model of the code flow.

@rmunn rmunn self-assigned this Aug 30, 2024
@rmunn rmunn force-pushed the feat/allow-multiple-project-invites branch from 1ebb43e to 632e94f Compare September 24, 2024 03:43
Copy link

github-actions bot commented Sep 24, 2024

UI unit Tests

12 tests  ±0   12 ✅ ±0   0s ⏱️ ±0s
 4 suites ±0    0 💤 ±0 
 1 files   ±0    0 ❌ ±0 

Results for commit 208556f. ± Comparison against base commit fc5232d.

♻️ This comment has been updated with latest results.

@rmunn
Copy link
Contributor Author

rmunn commented Sep 24, 2024

I now have the frontend forbidding email changes. Next up is figuring out how to allow the register page to be skipped if the user already has an account. We could put this logic in LoginRedirect, but that seems like not quite the right solution: it's a special case, and I don't want to load up LoginRedirect with special cases. I'll think about it.

@rmunn rmunn force-pushed the feat/allow-multiple-project-invites branch from a900c17 to 05eda53 Compare September 25, 2024 02:55
@rmunn
Copy link
Contributor Author

rmunn commented Sep 25, 2024

Commit 05eda53 didn't work, as I couldn't figure out how to tell HotChocolate to allow the Register audience access to the GQL query I needed. So I tried d253132 instead, which adds logic into LoginRedirect to use an alternate URL if the email already exists. I'm not super happy with putting special-case logic into LoginRedirect, so if you have a better suggestion I'd love to try it. But this is at least working.

Still to do: display errors, if any, in the alternate acceptInvitation page, and probably center the turnstile component instead of sticking it off on the side of the page, since its spinner and green checkmark works nicely as a "Please wait... Hey, you're approved" indicator when things succeed. (Which is why we need to hide it away and display errors if things didn't work).

@rmunn rmunn force-pushed the feat/allow-multiple-project-invites branch from 8848847 to d253132 Compare September 25, 2024 05:53
@rmunn
Copy link
Contributor Author

rmunn commented Sep 25, 2024

Nope, bb04079 doesn't work because the Projects and Orgs lists haven't been populated in our query. https://www.github.com/dotnet/efcore/issues/4526 would fix this if it had ever been implemented, but it doesn't look like that will ever make it into EF Core from the tenor of the discussion so far. (AFAICT, SQL Server doesn't implement the necessary feature, and EF Core doesn't want to add features that start failing if you switch databases).

Simplest fix is probably to just do .Include() of Projects and Orgs. This is a rarely-used code path, so it's not a big deal if this DB query gets larger.

@rmunn
Copy link
Contributor Author

rmunn commented Sep 25, 2024

Not 100% sure if I should even have an error message for "unknown error" or not, but other than that minor quibble, this is working.

I'd welcome input from @hahn-kev on my approach with LoginController; I was worried that it could be used to expose whether a given email address belongs to a user on our system, but in order to do that the attacker would have to forge or capture a JWT from us containing that email address. If they can forge a JWT, we have much bigger problems. And if they have captured a JWT, they probably got it from the person's email, in which case they already know that person is receiving emails from Lexbox.

However, security concerns aside, I'm still wondering if there was a better way to do this. I would have preferred to have the acceptInvitation page do a GraphQL query to say "Hey, does this account already exist?" and skip showing the form if that's the case. But I couldn't figure out how to make that work, so I went with the LoginController approach instead.

@rmunn rmunn requested a review from hahn-kev September 25, 2024 07:20
Copy link
Collaborator

@hahn-kev hahn-kev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that login redirect isn't the right place to do this. I made a suggestion, sorry you went down so many paths to find a difficult solution.

backend/LexBoxApi/Controllers/LoginController.cs Outdated Show resolved Hide resolved
@rmunn
Copy link
Contributor Author

rmunn commented Sep 25, 2024

Marking as draft since 5b820df is NOT finished, or even tested. It's just a rough sketch of what I'll be implementing, so no code review needed yet.

@rmunn rmunn marked this pull request as draft September 25, 2024 09:47
frontend/schema.graphql Outdated Show resolved Hide resolved
@rmunn rmunn marked this pull request as ready for review September 26, 2024 06:03
@rmunn rmunn requested a review from hahn-kev September 26, 2024 06:27
@rmunn
Copy link
Contributor Author

rmunn commented Sep 26, 2024

@hahn-kev - Self-review completed; this is ready for final review now.

Copy link
Collaborator

@hahn-kev hahn-kev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks good, 2 minor requests

backend/LexBoxApi/Controllers/UserController.cs Outdated Show resolved Hide resolved
backend/LexBoxApi/Controllers/UserController.cs Outdated Show resolved Hide resolved
@rmunn rmunn requested a review from hahn-kev September 27, 2024 05:13
hahn-kev
hahn-kev previously approved these changes Sep 27, 2024
@rmunn rmunn enabled auto-merge (squash) September 27, 2024 07:10
Now that we use invitation links for both projects and orgs, it will
start to become rather common for someone to receive an org invitation
and a project invitation within the same few minutes. So we need to
allow users to click on invitation links even if they already have a
Lexbox account.
Without this check, it would be possible for someone who manages to
intercept someone else's email (by social engineering or other means) to
forge a project invitation request, reusing the still-valid JWT from the
genuine user.
No backend checks yet, this is just frontend for now.
This fails: despite the AllowAnonymous, the GraphQL query is being
rejected because the RegisterAccount audience is not allowed to use GQL
queries. I'll need to find some other way to do this.
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.
If a user clicks the same invitation link twice, he should not get an
error message, he should just be taken to his home page. He's already a
member of the project, so trying to join the project should not error
but just succeed silently.
This allows the turnstile component to double as a visual indicator to
the user that things are progressing.
Ensure that LoginRedirect can never be used to redirect to a different
site. In the future we may whitelist certain domains such as lexbox.org,
but for now we just strip off the hostname and make sure it has to be a
relative URL.
WIP. Not yet tested, probably not yet functional.
Rather than redirect to the frontend and then query the backend to find
out if the user already exists, let's redirect to a backend endpoint,
which will then decide which frontend page to redirect to. A double
redirect may not be ideal, but it's better than special-case code in
the LoginRedirect method.
Back to just a single frontend page for accepting invites — great.
This can only happen if an admin creates the account while the user was
filling out the registration page, so this probably will never happen.
These services are already injected in the constructor
@rmunn rmunn force-pushed the feat/allow-multiple-project-invites branch from 22dddf0 to 8b939a1 Compare September 27, 2024 08:19
@rmunn rmunn disabled auto-merge September 27, 2024 13:56
@rmunn rmunn merged commit e75aa06 into develop Sep 27, 2024
14 checks passed
@rmunn rmunn deleted the feat/allow-multiple-project-invites branch September 27, 2024 13:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

a user getting multiple invite emails is only able to click on one of them
2 participants