-
-
Notifications
You must be signed in to change notification settings - Fork 508
Replace Windows Live authentication with Microsoft #2403
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
base: main
Are you sure you want to change the base?
Changes from all commits
e49e424
7838e81
37f8942
0a233d3
e254ad2
a4c506a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,8 @@ public class AuthHandler( | |
| TimeProvider timeProvider, | ||
| ILogger<AuthHandler> logger) | ||
| { | ||
| private const string LegacyMicrosoftOAuthProvider = "WindowsLive"; | ||
| private const string MicrosoftOAuthProvider = "Microsoft"; | ||
| private readonly ScopedCacheClient _cache = new(cacheClient, "Auth"); | ||
| private static bool _isFirstUserChecked; | ||
| private static readonly TimeSpan IntercomJwtLifetime = TimeSpan.FromMinutes(60); | ||
|
|
@@ -285,7 +287,7 @@ public Task<Result<TokenResult>> Handle(FacebookLogin message) | |
| ); | ||
| } | ||
|
|
||
| public Task<Result<TokenResult>> Handle(LiveLogin message) | ||
| public Task<Result<TokenResult>> Handle(MicrosoftLogin message) | ||
| { | ||
| return ExternalLoginAsync(message.AuthInfo, message.Context, | ||
| authOptions.MicrosoftId, | ||
|
|
@@ -534,7 +536,11 @@ private async Task<Result<TokenResult>> ExternalLoginAsync(ExternalAuthInfo auth | |
| User? user; | ||
| try | ||
| { | ||
| user = await FromExternalLoginAsync(userInfo, authInfo.InviteToken, httpContext); | ||
| var result = await FromExternalLoginAsync(userInfo, authInfo.InviteToken, httpContext); | ||
| if (!result.IsSuccess) | ||
| return Result<TokenResult>.FromResult(result); | ||
|
|
||
| user = result.Value; | ||
| } | ||
| catch (ApplicationException ex) | ||
| { | ||
|
|
@@ -554,12 +560,13 @@ private async Task<Result<TokenResult>> ExternalLoginAsync(ExternalAuthInfo auth | |
| return new TokenResult { Token = await GetOrCreateAuthenticationTokenAsync(user) }; | ||
| } | ||
|
|
||
| private async Task<User> FromExternalLoginAsync(UserInfo userInfo, string? inviteToken, HttpContext httpContext) | ||
| private async Task<Result<User>> FromExternalLoginAsync(UserInfo userInfo, string? inviteToken, HttpContext httpContext) | ||
| { | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(userInfo.Id); | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(userInfo.ProviderName); | ||
| ArgumentException.ThrowIfNullOrWhiteSpace(userInfo.Email); | ||
|
|
||
| bool isMicrosoft = String.Equals(userInfo.ProviderName, MicrosoftOAuthProvider, StringComparison.OrdinalIgnoreCase); | ||
| var existingUser = await userRepository.GetUserByOAuthProviderAsync(userInfo.ProviderName, userInfo.Id); | ||
| using var _ = logger.BeginScope(new ExceptionlessState().Tag("External Login").Tag(userInfo.ProviderName).Identity(userInfo.Email).SetHttpContext(httpContext)); | ||
|
|
||
|
|
@@ -577,26 +584,38 @@ private async Task<User> FromExternalLoginAsync(UserInfo userInfo, string? invit | |
| } | ||
| else | ||
| { | ||
| if (RemoveLegacyMicrosoftOAuthAccounts(currentUser, userInfo.ProviderName)) | ||
| return await userRepository.SaveAsync(currentUser, o => o.Cache()); | ||
|
|
||
| return currentUser; | ||
| } | ||
| } | ||
|
|
||
| currentUser.AddOAuthAccount(userInfo.ProviderName, userInfo.Id, userInfo.Email); | ||
| RemoveLegacyMicrosoftOAuthAccounts(currentUser, userInfo.ProviderName); | ||
| return await userRepository.SaveAsync(currentUser, o => o.Cache()); | ||
| } | ||
|
|
||
| if (existingUser is not null) | ||
| { | ||
| if (!existingUser.IsEmailAddressVerified) | ||
| bool hasChanges = RemoveLegacyMicrosoftOAuthAccounts(existingUser, userInfo.ProviderName); | ||
| if (!isMicrosoft && !existingUser.IsEmailAddressVerified) | ||
| { | ||
| existingUser.MarkEmailAddressVerified(); | ||
| await userRepository.SaveAsync(existingUser, o => o.Cache()); | ||
| hasChanges = true; | ||
| } | ||
|
|
||
| if (hasChanges) | ||
| await userRepository.SaveAsync(existingUser, o => o.Cache()); | ||
|
|
||
| return existingUser; | ||
| } | ||
|
|
||
| var user = !String.IsNullOrEmpty(userInfo.Email) ? await userRepository.GetByEmailAddressAsync(userInfo.Email) : null; | ||
| // Microsoft Graph mail is editable and does not prove ownership of an existing account. | ||
| if (isMicrosoft && user is not null) | ||
| return Result.Forbidden("Sign in to your existing account first, then link Microsoft from your account settings."); | ||
|
Comment on lines
+616
to
+617
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When a Microsoft identity's email matches an existing account without a modern Microsoft identity—which includes the intended Windows Live migration cohort—this new branch returns the actionable 403 message, but the Svelte Useful? React with 👍 / 👎. |
||
|
|
||
| if (user is null) | ||
| { | ||
| if (!await IsAccountCreationEnabledAsync(inviteToken)) | ||
|
|
@@ -608,17 +627,37 @@ private async Task<User> FromExternalLoginAsync(UserInfo userInfo, string? invit | |
| await AddGlobalAdminRoleIfFirstUserAsync(user); | ||
| } | ||
|
|
||
| user.MarkEmailAddressVerified(); | ||
| if (isMicrosoft) | ||
| user.ResetVerifyEmailAddressTokenAndExpiration(timeProvider); | ||
| else | ||
| user.MarkEmailAddressVerified(); | ||
| user.AddOAuthAccount(userInfo.ProviderName, userInfo.Id, userInfo.Email); | ||
|
|
||
| if (String.IsNullOrEmpty(user.Id)) | ||
| await userRepository.AddAsync(user, o => o.Cache()); | ||
| else | ||
| await userRepository.SaveAsync(user, o => o.Cache()); | ||
|
|
||
| if (isMicrosoft) | ||
| await mailer.SendUserEmailVerifyAsync(user); | ||
|
|
||
| return user; | ||
| } | ||
|
|
||
| private static bool RemoveLegacyMicrosoftOAuthAccounts(User user, string providerName) | ||
| { | ||
| if (!String.Equals(providerName, MicrosoftOAuthProvider, StringComparison.OrdinalIgnoreCase)) | ||
| return false; | ||
|
|
||
| var legacyAccounts = user.OAuthAccounts | ||
| .Where(account => String.Equals(account.Provider, LegacyMicrosoftOAuthProvider, StringComparison.OrdinalIgnoreCase)) | ||
| .ToArray(); | ||
| foreach (var account in legacyAccounts) | ||
| user.OAuthAccounts.Remove(account); | ||
|
|
||
| return legacyAccounts.Length > 0; | ||
| } | ||
|
|
||
| private async Task<bool> IsAccountCreationEnabledAsync(string? token) | ||
| { | ||
| if (authOptions.EnableAccountCreation) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.