Skip to content

Commit 3a2b793

Browse files
committed
Complete Credential Reset & Added Tests
1 parent 1aafa3b commit 3a2b793

File tree

54 files changed

+1044
-333
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1044
-333
lines changed

samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Dialogs/CredentialDialog.razor

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,23 @@
1414
</TitleContent>
1515
<DialogContent>
1616
<MudForm @ref="@_form" OnEnterPressed="@ChangePasswordAsync">
17-
<MudStack Class="mud-width-full">
18-
<MudTextField @bind-Value="_oldPassword" Label="Old Password" Variant="Variant.Outlined" Required="true" />
19-
<MudTextField @bind-Value="_newPassword" Label="New Password" Variant="Variant.Outlined" Required="true" />
20-
<MudTextField @bind-Value="_newPasswordCheck" Label="New Password (Again)" Variant="Variant.Outlined" Required="true" Validation="@(new Func<string, string>(PasswordMatch))" />
21-
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="ChangePasswordAsync">Change Password</MudButton>
22-
</MudStack>
17+
<MudGrid Spacing="2">
18+
<MudItem xs="12" sm="6">
19+
<MudPasswordField @bind-Value="_oldPassword" @bind-PasswordMode="@_passwordMode1" Label="Old Password" Variant="Variant.Outlined" Immediate="true" Required="true" />
20+
</MudItem>
21+
22+
<MudItem xs="12" sm="6">
23+
<MudPasswordField @bind-Value="_newPassword" @bind-PasswordMode="@_passwordMode2" Label="New Password" Variant="Variant.Outlined" Immediate="true" Required="true" />
24+
</MudItem>
25+
26+
<MudItem xs="12" sm="6">
27+
<MudPasswordField @bind-Value="_newPasswordCheck" @bind-PasswordMode="@_passwordMode3" Label="New Password (Again)" Variant="Variant.Outlined" Immediate="true" Required="true" Validation="@(new Func<string, string>(PasswordMatch))" />
28+
</MudItem>
29+
30+
<MudItem xs="12">
31+
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="ChangePasswordAsync">Change Password</MudButton>
32+
</MudItem>
33+
</MudGrid>
2334
</MudForm>
2435
</DialogContent>
2536
<DialogActions>
@@ -33,6 +44,9 @@
3344
private string? _oldPassword;
3445
private string? _newPassword;
3546
private string? _newPasswordCheck;
47+
private bool _passwordMode1 = false;
48+
private bool _passwordMode2 = false;
49+
private bool _passwordMode3 = true;
3650

3751
[CascadingParameter]
3852
private IMudDialogInstance MudDialog { get; set; } = default!;
@@ -62,7 +76,6 @@
6276
return;
6377
}
6478

65-
6679
if (_newPassword != _newPasswordCheck)
6780
{
6881
Snackbar.Add("New password and check do not match", Severity.Error);
@@ -87,7 +100,7 @@
87100
}
88101
}
89102

90-
private string PasswordMatch(string arg) => _newPassword != arg ? "Passwords don't match" : null;
103+
private string PasswordMatch(string arg) => _newPassword != arg ? "Passwords don't match" : string.Empty;
91104

92105
private void Submit() => MudDialog.Close(DialogResult.Ok(true));
93106

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
@using CodeBeam.UltimateAuth.Core.Contracts
2+
@using CodeBeam.UltimateAuth.Credentials.Contracts
3+
@using CodeBeam.UltimateAuth.Users.Contracts
4+
@inject IUAuthClient UAuthClient
5+
@inject ISnackbar Snackbar
6+
@inject IDialogService DialogService
7+
@inject IUAuthStateManager StateManager
8+
@inject NavigationManager Nav
9+
10+
<MudDialog Class="mud-width-full" ContentClass="uauth-dialog">
11+
<TitleContent>
12+
<MudText>Reset Credential</MudText>
13+
</TitleContent>
14+
<DialogContent>
15+
<MudStack Class="mud-width-full">
16+
<MudAlert Severity="Severity.Info">
17+
This is a demonstration of how to implement a credential reset flow.
18+
In a production application, you should use reset token or code in email, SMS etc. verification steps.
19+
</MudAlert>
20+
<MudAlert Severity="Severity.Warning">
21+
Reset request always returns ok even with not found users due to security reasons.
22+
</MudAlert>
23+
<MudTextField @bind-Value="@_identifier" Label="Username or Email" Variant="Variant.Outlined" />
24+
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="RequestResetAsync">Request Reset</MudButton>
25+
@if (_resetRequested)
26+
{
27+
<MudText Align="Align.Center">Your reset code is: (Copy it before next step)</MudText>
28+
<MudText Align="Align.Center" Typo="Typo.h6"><b>@_resetCode</b></MudText>
29+
<MudText Align="Align.Center"><MudLink Class="cursor-pointer" Href="@($"/reset?identifier={_identifier}")">Use Reset Code</MudLink></MudText>
30+
}
31+
</MudStack>
32+
</DialogContent>
33+
<DialogActions>
34+
<MudButton OnClick="Cancel">Cancel</MudButton>
35+
<MudButton Color="Color.Primary" OnClick="Submit">OK</MudButton>
36+
</DialogActions>
37+
</MudDialog>
38+
39+
@code {
40+
private bool _resetRequested = false;
41+
private string? _resetCode;
42+
private string? _identifier;
43+
44+
[CascadingParameter]
45+
private IMudDialogInstance MudDialog { get; set; } = default!;
46+
47+
[Parameter]
48+
public UAuthState AuthState { get; set; } = default!;
49+
50+
private async Task RequestResetAsync()
51+
{
52+
var request = new BeginCredentialResetRequest
53+
{
54+
CredentialType = CredentialType.Password,
55+
ResetCodeType = ResetCodeType.Code,
56+
Identifier = _identifier ?? string.Empty
57+
};
58+
59+
var result = await UAuthClient.Credentials.BeginResetMyAsync(request);
60+
if (!result.IsSuccess || result.Value is null)
61+
{
62+
Snackbar.Add(result.Problem?.Detail ?? result.Problem?.Title ?? "Failed to request credential reset.", Severity.Error);
63+
return;
64+
}
65+
66+
_resetCode = result.Value.Token;
67+
_resetRequested = true;
68+
}
69+
70+
private void Submit() => MudDialog.Close(DialogResult.Ok(true));
71+
72+
private void Cancel() => MudDialog.Cancel();
73+
}

samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Layout/MainLayout.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
<MudLayout>
77
<MudAppBar Style="backdrop-filter: blur(10px)" Color="Color.Transparent" Dense="true" Elevation="0">
88
<UAuthLogo />
9-
<MudText Class="ml-2 cursor-pointer" Style="user-select: none" @onclick="@(() => Nav.NavigateTo("/home"))"><b>UltimateAuth</b></MudText>
9+
<MudText Class="ml-2 cursor-pointer" Style="user-select: none" @onclick="@(() => Nav.NavigateTo("/home", true))"><b>UltimateAuth</b></MudText>
1010
<MudDivider Class="ml-4 mr-2" Vertical="true" />
1111
<MudText Class="ml-2" Typo="Typo.subtitle2">Blazor Server Sample</MudText>
1212

samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Login.razor

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
@inject ISnackbar Snackbar
88
@inject IUAuthClientProductInfoProvider ClientProductInfoProvider
99
@inject IDeviceIdProvider DeviceIdProvider
10+
@inject IDialogService DialogService
1011

1112
<MudPage FullScreen="FullScreen.FullWithoutAppbar" Column="1" Row="1">
1213
<MudContainer Class="d-flex align-center justify-center" MaxWidth="MaxWidth.Medium">
@@ -111,6 +112,12 @@
111112
<MudButton Variant="Variant.Filled" Color="Color.Primary" OnClick="ProgrammaticLogin">Programmatic Login</MudButton>
112113
<MudText Typo="Typo.subtitle2" Align="Align.Center">Login programmatically as admin/admin.</MudText>
113114
</MudStack>
115+
116+
<MudDivider Class="my-2" />
117+
118+
<MudStack>
119+
<MudText Align="Align.Center"><MudLink Class="cursor-pointer" OnClick="OpenResetDialog">Forgot Password</MudLink></MudText>
120+
</MudStack>
114121
</MudPaper>
115122
</MudItem>
116123
</MudGrid>

samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/Components/Pages/Login.razor.cs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using CodeBeam.UltimateAuth.Client.Runtime;
33
using CodeBeam.UltimateAuth.Core.Contracts;
44
using CodeBeam.UltimateAuth.Core.Domain;
5+
using CodeBeam.UltimateAuth.Sample.BlazorServer.Components.Dialogs;
56
using MudBlazor;
67

78
namespace CodeBeam.UltimateAuth.Sample.BlazorServer.Components.Pages;
@@ -159,6 +160,29 @@ private void UpdateRemaining()
159160
}
160161
}
161162

163+
private async Task OpenResetDialog()
164+
{
165+
await DialogService.ShowAsync<ResetDialog>("Reset Credentials", GetDialogParameters(), GetDialogOptions());
166+
}
167+
168+
private DialogOptions GetDialogOptions()
169+
{
170+
return new DialogOptions
171+
{
172+
MaxWidth = MaxWidth.Medium,
173+
FullWidth = true,
174+
CloseButton = true
175+
};
176+
}
177+
178+
private DialogParameters GetDialogParameters()
179+
{
180+
return new DialogParameters
181+
{
182+
["AuthState"] = AuthState
183+
};
184+
}
185+
162186
public override void Dispose()
163187
{
164188
base.Dispose();
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
@page "/reset"
2+
@inherits UAuthFlowPageBase
3+
4+
@inject IUAuthClient UAuthClient
5+
@inject ISnackbar Snackbar
6+
7+
<MudPage Class="mud-width-full" FullScreen="FullScreen.FullWithoutAppbar" Column="1" Row="1">
8+
<MudContainer Class="pb-4" MaxWidth="MaxWidth.ExtraSmall">
9+
<MudForm @ref="_form">
10+
<MudStack>
11+
<MudTextField @bind-Value="@_code" Label="Code" Variant="Variant.Outlined" Immediate="true" Required="true" />
12+
<MudPasswordField @bind-Value="_newPassword" Label="New Password" Variant="Variant.Outlined" Immediate="true" Required="true" />
13+
<MudPasswordField @bind-Value="_newPasswordCheck" Label="New Password (Again)" Variant="Variant.Outlined" Immediate="true" Required="true" Validation="@(new Func<string, string>(PasswordMatch))" />
14+
<MudButton Color="Color.Primary" Variant="Variant.Filled" OnClick="ResetPasswordAsync">Change Password</MudButton>
15+
</MudStack>
16+
</MudForm>
17+
</MudContainer>
18+
</MudPage>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using CodeBeam.UltimateAuth.Credentials.Contracts;
2+
using MudBlazor;
3+
4+
namespace CodeBeam.UltimateAuth.Sample.BlazorServer.Components.Pages;
5+
6+
public partial class ResetCredential
7+
{
8+
private MudForm _form = null!;
9+
private string? _code;
10+
private string? _newPassword;
11+
private string? _newPasswordCheck;
12+
13+
private async Task ResetPasswordAsync()
14+
{
15+
await _form.Validate();
16+
if (!_form.IsValid)
17+
{
18+
Snackbar.Add("Please fix the validation errors.", Severity.Error);
19+
return;
20+
}
21+
22+
if (_newPassword != _newPasswordCheck)
23+
{
24+
Snackbar.Add("Passwords do not match.", Severity.Error);
25+
return;
26+
}
27+
28+
var request = new CompleteCredentialResetRequest
29+
{
30+
ResetToken = _code,
31+
NewSecret = _newPassword ?? string.Empty,
32+
Identifier = Identifier // Coming from UAuthFlowPageBase automatically if begin reset is successful
33+
};
34+
35+
var result = await UAuthClient.Credentials.CompleteResetMyAsync(request);
36+
37+
if (result.IsSuccess)
38+
{
39+
Snackbar.Add("Credential reset successfully. Please log in with your new password.", Severity.Success);
40+
Nav.NavigateTo("/login");
41+
}
42+
else
43+
{
44+
Snackbar.Add(result.Problem?.Detail ?? result.Problem?.Title ?? "Failed to reset credential. Please try again.", Severity.Error);
45+
}
46+
}
47+
48+
private string PasswordMatch(string arg) => _newPassword != arg ? "Passwords don't match" : string.Empty;
49+
}

samples/blazor-server/CodeBeam.UltimateAuth.Sample.BlazorServer/wwwroot/app.css

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ h1:focus {
6868
}
6969

7070
.uauth-login-paper {
71-
min-height: 60vh;
71+
min-height: 70vh;
7272
}
7373

7474
.uauth-login-paper.mud-theme-primary {
@@ -81,11 +81,7 @@ h1:focus {
8181
}
8282

8383
.uauth-logo-slide {
84-
transition: transform 1s cubic-bezier(.4, 0, .2, 1);
85-
}
86-
87-
.uauth-login-paper:hover .uauth-logo-slide {
88-
transform: translateY(200px) rotateY(360deg);
84+
animation: uauth-logo-float 30s ease-in-out infinite;
8985
}
9086

9187
.uauth-text-transform-none .mud-button {
@@ -95,3 +91,45 @@ h1:focus {
9591
.uauth-dialog {
9692
min-height: 62vh;
9793
}
94+
95+
@keyframes uauth-logo-float {
96+
0% {
97+
transform: translateY(0) rotateY(0);
98+
}
99+
100+
10% {
101+
transform: translateY(0) rotateY(0);
102+
}
103+
104+
15% {
105+
transform: translateY(200px) rotateY(360deg);
106+
}
107+
108+
35% {
109+
transform: translateY(200px) rotateY(360deg);
110+
}
111+
112+
40% {
113+
transform: translateY(200px) rotateY(720deg);
114+
}
115+
116+
60% {
117+
transform: translateY(200px) rotateY(720deg);
118+
}
119+
120+
65% {
121+
transform: translateY(0) rotateY(360deg);
122+
}
123+
124+
85% {
125+
transform: translateY(0) rotateY(360deg);
126+
}
127+
128+
90% {
129+
transform: translateY(0) rotateY(0);
130+
}
131+
132+
100% {
133+
transform: translateY(0) rotateY(0);
134+
}
135+
}

src/CodeBeam.UltimateAuth.Client/Components/UAuthFlowPageBase.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public abstract class UAuthFlowPageBase : UAuthReactiveComponentBase
1313
protected AuthFlowPayload? UAuthPayload { get; private set; }
1414
protected string? ReturnUrl { get; private set; }
1515
protected bool ShouldFocus { get; private set; }
16+
protected string? Identifier { get; private set; }
1617

1718

1819
protected virtual bool ClearQueryAfterParse => true;
@@ -39,6 +40,7 @@ protected override void OnParametersSet()
3940

4041
ShouldFocus = query.TryGetValue("focus", out var focus) && focus == "1";
4142
ReturnUrl = query.TryGetValue("returnUrl", out var ru) ? ru.ToString() : null;
43+
Identifier = query.TryGetValue("identifier", out var id) ? id.ToString() : null;
4244

4345
UAuthPayload = null;
4446

src/CodeBeam.UltimateAuth.Client/Services/ICredentialClient.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ public interface ICredentialClient
1010
Task<UAuthResult<AddCredentialResult>> AddMyAsync(AddCredentialRequest request);
1111
Task<UAuthResult<ChangeCredentialResult>> ChangeMyAsync(ChangeCredentialRequest request);
1212
Task<UAuthResult> RevokeMyAsync(RevokeCredentialRequest request);
13-
Task<UAuthResult> BeginResetMyAsync(BeginCredentialResetRequest request);
14-
Task<UAuthResult> CompleteResetMyAsync(CompleteCredentialResetRequest request);
13+
Task<UAuthResult<BeginCredentialResetResult>> BeginResetMyAsync(BeginCredentialResetRequest request);
14+
Task<UAuthResult<CredentialActionResult>> CompleteResetMyAsync(CompleteCredentialResetRequest request);
1515

1616
Task<UAuthResult<GetCredentialsResult>> GetUserAsync(UserKey userKey);
1717
Task<UAuthResult<AddCredentialResult>> AddUserAsync(UserKey userKey, AddCredentialRequest request);
1818
Task<UAuthResult> RevokeUserAsync(UserKey userKey, RevokeCredentialRequest request);
1919
Task<UAuthResult> ActivateUserAsync(UserKey userKey);
20-
Task<UAuthResult> BeginResetUserAsync(UserKey userKey, BeginCredentialResetRequest request);
21-
Task<UAuthResult> CompleteResetUserAsync(UserKey userKey, CompleteCredentialResetRequest request);
20+
Task<UAuthResult<BeginCredentialResetResult>> BeginResetUserAsync(UserKey userKey, BeginCredentialResetRequest request);
21+
Task<UAuthResult<CredentialActionResult>> CompleteResetUserAsync(UserKey userKey, CompleteCredentialResetRequest request);
2222
Task<UAuthResult> DeleteUserAsync(UserKey userKey);
2323
}

0 commit comments

Comments
 (0)