Skip to content

Commit 269d1de

Browse files
authored
Deprecate google login (#2385)
* cleanup user profile files * Add google deprecate page
1 parent cf345bf commit 269d1de

File tree

6 files changed

+135
-33
lines changed

6 files changed

+135
-33
lines changed

src/JoinRpg.Portal/Controllers/UserProfile/AccountController.cs

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System.Security.Claims;
22
using Joinrpg.Web.Identity;
33
using JoinRpg.Data.Interfaces;
4+
using JoinRpg.Interfaces;
45
using JoinRpg.Portal.Identity;
56
using JoinRpg.Portal.Infrastructure.Authentication;
67
using JoinRpg.Services.Interfaces.Notification;
@@ -355,8 +356,6 @@ public ActionResult ExternalLogin(string provider, string returnUrl)
355356
return Challenge(authenticationProperties, provider);
356357
}
357358

358-
//
359-
// GET: /Account/ExternalLoginCallback
360359
[AllowAnonymous]
361360
public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
362361
{
@@ -379,6 +378,7 @@ public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
379378
var email = loginInfo.Principal.FindFirstValue(ClaimTypes.Email);
380379

381380
// If sign in failed, may be we have user with same email. Let's bind.
381+
// TODO if we need to bind more google accounts
382382
if (!result.Succeeded && !string.IsNullOrWhiteSpace(email))
383383
{
384384
var user = await UserManager.FindByEmailAsync(email);
@@ -395,6 +395,11 @@ public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
395395

396396
if (result.Succeeded)
397397
{
398+
var isGoogle = loginInfo.LoginProvider == ProviderDescViewModel.Google.ProviderId;
399+
if (isGoogle)
400+
{
401+
return RedirectToAction("GoogleDeprecated", new { returnUrl });
402+
}
398403
return RedirectToLocal(returnUrl);
399404
}
400405

@@ -420,6 +425,19 @@ public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
420425

421426
}
422427

428+
public async Task<ActionResult> GoogleDeprecated(string returnUrl, [FromServices] ICurrentUserAccessor currentUserAccessor)
429+
{
430+
var user = await UserRepository.WithProfile(currentUserAccessor.UserId);
431+
432+
var viewModel = new GoogleDeprecatedViewModel(
433+
VkLogin: user.GetSocialLogins().Single(s => s.LoginProvider.ProviderId == ProviderDescViewModel.Vk.ProviderId),
434+
HasPassword: user.PasswordHash != null,
435+
RedirectUrl: Url.IsLocalUrl(returnUrl) ? returnUrl : "/",
436+
Email: currentUserAccessor.Email);
437+
438+
return View(viewModel);
439+
}
440+
423441
//
424442
// POST: /Account/ExternalLoginConfirmation
425443
[HttpPost]

src/JoinRpg.Portal/Infrastructure/Authentication/CurrentUserAccessor.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
using JoinRpg.Interfaces;
44
using JoinRpg.Portal.Infrastructure.Authentication;
55
using JoinRpg.PrimitiveTypes;
6-
#nullable enable
76

87
namespace JoinRpg.Web.Helpers;
98

src/JoinRpg.Portal/Infrastructure/Authentication/ExternalLoginProfileExtractor.cs

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
using Joinrpg.Web.Identity;
44
using JoinRpg.PrimitiveTypes;
55
using JoinRpg.Services.Interfaces;
6+
using JoinRpg.Web.Models;
67
using Microsoft.AspNetCore.Identity;
78

89
namespace JoinRpg.Portal.Infrastructure.Authentication;
910

10-
#nullable enable
1111
/// <summary>
1212
/// Task of this class is to extract useful data from social logins
1313
/// </summary>
@@ -24,42 +24,29 @@ public async Task TryExtractProfile(JoinIdentityUser user, ExternalLoginInfo log
2424

2525
if (TryGetVkId(loginInfo) is VkId vkId)
2626
{
27-
var vkAvatar = TryGetClaim(loginInfo, VkontakteAuthenticationConstants.Claims.PhotoUrl);
27+
var vkAvatar = loginInfo.Principal.FindFirstValue(VkontakteAuthenticationConstants.Claims.PhotoUrl);
2828

2929
if (vkAvatar is not null)
3030
{
3131
await userService.SetVkIfNotSetWithoutAccessChecks(user.Id, vkId,
3232
new AvatarInfo(new Uri(vkAvatar), 50, 50));
3333
}
3434
}
35-
36-
if (TryGetClaim(loginInfo, "urn:google:photo") is string googleAvatar)
37-
{
38-
await userService.SetGoogleIfNotSetWithoutAccessChecks(user.Id,
39-
new AvatarInfo(new Uri(googleAvatar), 96, 96));
40-
}
41-
//var googleProfileLink = loginInfo.Principal.FindFirstValue("urn:google:profile");
4235
}
4336

44-
/// <summary>
45-
/// This method is required, because FindFirstValue is not properly null-annotated
46-
/// </summary>
47-
private static string? TryGetClaim(ExternalLoginInfo loginInfo, string claimType)
48-
=> loginInfo.Principal.FindFirstValue(claimType);
49-
5037
private static UserFullName TryGetUserName(ExternalLoginInfo loginInfo)
5138
{
5239
var bornName = BornName.FromOptional(loginInfo.Principal.FindFirstValue(ClaimTypes.GivenName));
5340
var surName = SurName.FromOptional(loginInfo.Principal.FindFirstValue(ClaimTypes.Surname));
54-
var prefferedName = new PrefferedName(loginInfo.Principal.FindFirstValue(ClaimTypes.Name));
41+
var prefferedName = new PrefferedName(loginInfo.Principal.FindFirstValue(ClaimTypes.Name)!);
5542

5643
return new UserFullName(prefferedName, bornName, surName, FatherName: null);
5744
}
5845

5946
private static VkId? TryGetVkId(ExternalLoginInfo loginInfo)
6047
{
61-
return loginInfo.LoginProvider == "Vkontakte"
62-
&& TryGetClaim(loginInfo, ClaimTypes.NameIdentifier) is string id
48+
return loginInfo.LoginProvider == ProviderDescViewModel.Vk.ProviderId
49+
&& loginInfo.Principal.FindFirstValue(ClaimTypes.NameIdentifier) is string id
6350
? new VkId(id)
6451
: null;
6552
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
@using JoinRpg.Web.Models;
2+
@model JoinRpg.Web.Models.GoogleDeprecatedViewModel
3+
@{
4+
ViewBag.Title = "Проверка доступности аккаунта";
5+
}
6+
7+
<h2>@ViewBag.Title</h2>
8+
<div>
9+
<p class="alert alert-warning">
10+
<b>Прочтите это сообщение внимательно, чтобы не потерять доступ к своему аккаунту</b>. <br />
11+
Вы вошли в свой аккаунт, используя вход через Google. С 1 декабря 2023 года это способ входа будет отключен — это связано с тем, что с 1 декабря авторизация
12+
с использованием иностранных провайдеров запрещена на территории РФ. Чтобы не потерять доступ к своему аккаунту, убедитесь, что у вас настроены альтернативные способы
13+
входа.
14+
</p>
15+
16+
<table class="table">
17+
<tr><th>Способ входа</th><th>Статус</th><th>Действие</th></tr>
18+
<tr>
19+
<td>Вконтакте</td>
20+
<td>
21+
@if (Model.VkLogin.Present)
22+
{
23+
<span class="label label-success">Настроен</span>
24+
}
25+
else
26+
{
27+
<span class="label label-warning">Не подключена</span>
28+
}
29+
</td>
30+
<td>
31+
@if (Model.VkLogin.Present)
32+
{
33+
<text>Проверьте, что у вас есть доступ к <a href="@Model.VkLogin.ProviderLink">@Model.VkLogin.ProviderLink</a>.</text>
34+
}
35+
else
36+
{
37+
<form asp-controller="Manage" asp-action="LinkLogin" style="display:inline">
38+
<input type="hidden" value="@Model.VkLogin.LoginProvider.ProviderId" name="provider" />
39+
<button type="submit" class="btn btn-default">Подключить</button>
40+
</form>
41+
}
42+
</td>
43+
</tr>
44+
<tr>
45+
<td>Пароль</td>
46+
<td>
47+
@if (Model.HasPassword)
48+
{
49+
<span class="label label-success">Настроен</span>
50+
}
51+
else
52+
{
53+
<span class="label label-warning">Не установлен</span>
54+
}
55+
</td>
56+
<td>
57+
@if (Model.HasPassword)
58+
{
59+
<p>Попробуйте залогиниться по паролю, чтобы убедиться, что вы его помните.</p>
60+
@using (Html.BeginForm("Login", "Account", new { ReturnUrl = Model.RedirectUrl }, FormMethod.Post, antiforgery: true, new { @class = "form-horizontal", role = "form" }))
61+
{
62+
@Html.AntiForgeryToken()
63+
@Html.HiddenFor(m => m.Email)
64+
<div class="form-group">
65+
<label class="control-label col-md-2">Пароль</label>
66+
<div class="col-md-3">
67+
@Html.Password("Password", "", new { @class = "form-control" })
68+
</div>
69+
<div class="col-md-2">
70+
<input type="submit" value="Войти" class="btn btn-success" />
71+
</div>
72+
</div>
73+
<div class="form-group">
74+
75+
</div>
76+
}
77+
<text>Если вы не помните пароль, то @Html.ActionLink("сбросьте", "ForgotPassword") его.</text>
78+
}
79+
else
80+
{
81+
<a asp-controller="Manage" asp-action="SetPassword" class="btn btn-default">Установить пароль.</a>
82+
}
83+
</td>
84+
</tr>
85+
<tr>
86+
<td>Email</td>
87+
<td><span class="label label-success">Установлена</span>.</td>
88+
<td>
89+
Проверьте, что у вас есть доступ к почте <a href="@Model.Email">@Model.Email</a>. Это позволит вам сбросить пароль, если вы его забудете. Если у вас нет доступа, обратитесь в техподдержку.
90+
</td>
91+
</tr>
92+
</table>
93+
94+
<a href="@Model.RedirectUrl" class="btn btn-success" style="text-align: center">Я все проверил и хочу продолжить</a>
95+
96+
</div>

src/JoinRpg.WebPortal.Models/UserProfile/AccountViewModels.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@ public class ExternalLoginConfirmationViewModel
1919

2020
public class ExternalLoginListViewModel
2121
{
22-
public string ReturnUrl { get; set; }
23-
public List<AuthenticationDescriptionViewModel> ExternalLogins { get; set; }
22+
public required string ReturnUrl { get; set; }
23+
public required List<AuthenticationDescriptionViewModel> ExternalLogins { get; set; }
2424
}
2525

26+
public record GoogleDeprecatedViewModel(bool HasPassword, string RedirectUrl, UserLoginInfoViewModel VkLogin, string Email);
27+
2628
public class AuthenticationDescriptionViewModel
2729
{
2830
public string AuthenticationType { get; set; }

src/JoinRpg.WebPortal.Models/UserProfile/UserLoginInfoViewModel.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,15 @@
22

33
namespace JoinRpg.Web.Models;
44

5-
#nullable enable
65

7-
public record UserLoginInfoViewModel
6+
public record ProviderDescViewModel(string ProviderId, string FriendlyName)
87
{
9-
public record ProviderDescViewModel(string ProviderId, string FriendlyName)
10-
{
11-
public static readonly ProviderDescViewModel Google = new("Google", "Google");
12-
public static readonly ProviderDescViewModel Vk = new("Vkontakte", "ВК");
13-
}
8+
public static readonly ProviderDescViewModel Google = new("Google", "Google");
9+
public static readonly ProviderDescViewModel Vk = new("Vkontakte", "ВК");
10+
}
1411

12+
public record UserLoginInfoViewModel
13+
{
1514
public ProviderDescViewModel LoginProvider { get; init; } = null!;
1615

1716
public string? ProviderLink { get; set; }
@@ -21,15 +20,16 @@ public record ProviderDescViewModel(string ProviderId, string FriendlyName)
2120
public bool AllowLink { get; set; }
2221
public bool AllowUnlink { get; set; }
2322
public bool NeedToReLink { get; set; }
23+
public bool Present => ProviderLink is not null && ProviderKey is not null;
2424
}
2525

2626
public static class UserLoginInfoViewModelBuilder
2727
{
2828
public static IEnumerable<UserLoginInfoViewModel> GetSocialLogins(this User user)
2929
{
3030
var canRemoveLogins = user.PasswordHash != null || user.ExternalLogins.Count > 1;
31-
yield return GetModel(UserLoginInfoViewModel.ProviderDescViewModel.Google);
32-
var vk = GetModel(UserLoginInfoViewModel.ProviderDescViewModel.Vk);
31+
yield return GetModel(ProviderDescViewModel.Google);
32+
var vk = GetModel(ProviderDescViewModel.Vk);
3333
if (vk.ProviderKey is not null)
3434
{
3535
vk.ProviderLink = $"https://vk.com/id{vk.ProviderKey}";
@@ -44,7 +44,7 @@ public static IEnumerable<UserLoginInfoViewModel> GetSocialLogins(this User user
4444

4545
yield return vk;
4646

47-
UserLoginInfoViewModel GetModel(UserLoginInfoViewModel.ProviderDescViewModel provider)
47+
UserLoginInfoViewModel GetModel(ProviderDescViewModel provider)
4848
{
4949
if (user.ExternalLogins.SingleOrDefault(l =>
5050
l.Provider.ToLowerInvariant() == provider.ProviderId.ToLowerInvariant()

0 commit comments

Comments
 (0)