Skip to content
This repository was archived by the owner on Jan 23, 2025. It is now read-only.

Commit 9df83da

Browse files
committed
#269. Expire passwords.
1 parent a964d4a commit 9df83da

91 files changed

Lines changed: 1047 additions & 5616 deletions

File tree

Some content is hidden

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

Pyro.Api/Directory.Packages.props

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
<PackageVersion Include="Microsoft.EntityFrameworkCore" Version="9.0.0" />
2424
<PackageVersion Include="Microsoft.EntityFrameworkCore.Sqlite" Version="9.0.0" />
2525
<PackageVersion Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.0" />
26+
<PackageVersion Include="Microsoft.Extensions.Options" Version="9.0.0" />
2627
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="9.0.0" />
2728
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" />
2829
<PackageVersion Include="MimeKit" Version="4.8.0" />

Pyro.Api/Pyro.ApiTests/Clients/PyroClient.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22
// Licensed under the GPL-3.0 license. See LICENSE file in the project root for full license information.
33

44
using Pyro.Contracts.Requests;
5+
using Pyro.Contracts.Requests.Identity;
56
using Pyro.Contracts.Requests.Issues;
67
using Pyro.Contracts.Responses;
8+
using Pyro.Contracts.Responses.Identity;
79

810
namespace Pyro.ApiTests.Clients;
911

Pyro.Api/Pyro.ApiTests/Tests/ProfileTests.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using Bogus;
55
using Pyro.ApiTests.Clients;
6-
using Pyro.Contracts.Requests;
76
using Pyro.Contracts.Requests.Identity;
87

98
namespace Pyro.ApiTests.Tests;

Pyro.Api/Pyro.Contracts/Requests/UpdateUserProfileRequest.cs renamed to Pyro.Api/Pyro.Contracts/Requests/Identity/UpdateUserProfileRequest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (c) Dmytro Kyshchenko. All rights reserved.
22
// Licensed under the GPL-3.0 license. See LICENSE file in the project root for full license information.
33

4-
namespace Pyro.Contracts.Requests;
4+
namespace Pyro.Contracts.Requests.Identity;
55

66
public record UpdateUserProfileRequest(string Name, string Status);

Pyro.Api/Pyro.Contracts/Responses/UserProfileResponse.cs renamed to Pyro.Api/Pyro.Contracts/Responses/Identity/UserProfileResponse.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// Copyright (c) Dmytro Kyshchenko. All rights reserved.
22
// Licensed under the GPL-3.0 license. See LICENSE file in the project root for full license information.
33

4-
namespace Pyro.Contracts.Responses;
4+
namespace Pyro.Contracts.Responses.Identity;
55

66
public record UserProfileResponse(string Name, string? Status);

Pyro.Api/Pyro.Domain.Identity.UnitTests/Commands/LockUserHandlerTests.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,12 @@ public void LockMissingUser()
2727
public void LockCurrentUser()
2828
{
2929
var currentUser = new CurrentUser(Guid.NewGuid(), "user", [], []);
30-
var user = new User { Id = currentUser.Id, Login = currentUser.Login };
30+
var user = new User
31+
{
32+
Id = currentUser.Id,
33+
Login = currentUser.Login,
34+
Profile = new UserProfile { Name = currentUser.Login },
35+
};
3136
var command = new LockUser(currentUser.Login);
3237

3338
var userRepository = Substitute.For<IUserRepository>();
@@ -48,7 +53,11 @@ public void LockCurrentUser()
4853
public async Task LockUser()
4954
{
5055
var currentUser = new CurrentUser(Guid.NewGuid(), "user", [], []);
51-
var user = new User { Login = "test" };
56+
var user = new User
57+
{
58+
Login = "test",
59+
Profile = new UserProfile { Name = "test" },
60+
};
5261
var command = new LockUser(user.Login);
5362

5463
var userRepository = Substitute.For<IUserRepository>();

Pyro.Api/Pyro.Domain.Identity.UnitTests/Commands/LoginHandlerTests.cs

Lines changed: 51 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ public async Task LoginWithInvalidUser()
1616
var command = new LoginCommand("test", "password");
1717

1818
var logger = Substitute.For<ILogger<LoginHandler>>();
19+
var timeProvider = Substitute.For<TimeProvider>();
1920
var repository = Substitute.For<IUserRepository>();
2021
var passwordService = Substitute.For<IPasswordService>();
2122
var tokenService = Substitute.For<ITokenService>();
22-
var handler = new LoginHandler(logger, repository, passwordService, tokenService);
23+
var handler = new LoginHandler(logger, timeProvider, repository, passwordService, tokenService);
2324

2425
var result = await handler.Handle(command);
2526

@@ -30,17 +31,50 @@ public async Task LoginWithInvalidUser()
3031
public async Task LoginWithLockedUser()
3132
{
3233
var command = new LoginCommand("test", "password");
33-
var user = new User { Login = "test" };
34+
var user = new User
35+
{
36+
Login = "test",
37+
Profile = new UserProfile { Name = "test" },
38+
};
3439
user.Lock();
3540

3641
var logger = Substitute.For<ILogger<LoginHandler>>();
42+
var timeProvider = Substitute.For<TimeProvider>();
3743
var repository = Substitute.For<IUserRepository>();
3844
repository
3945
.GetUserByLogin(command.Login)
4046
.Returns(user);
4147
var passwordService = Substitute.For<IPasswordService>();
4248
var tokenService = Substitute.For<ITokenService>();
43-
var handler = new LoginHandler(logger, repository, passwordService, tokenService);
49+
var handler = new LoginHandler(logger, timeProvider, repository, passwordService, tokenService);
50+
51+
var result = await handler.Handle(command);
52+
53+
Assert.That(result, Is.EqualTo(LoginResult.Fail()));
54+
}
55+
56+
[Test]
57+
public async Task LoginWithExpiredPassword()
58+
{
59+
var currentDate = DateTimeOffset.UtcNow;
60+
var command = new LoginCommand("test", "password");
61+
var user = new User
62+
{
63+
Login = "test",
64+
Profile = new UserProfile { Name = "test" },
65+
PasswordExpiresAt = currentDate.AddDays(-1),
66+
};
67+
68+
var logger = Substitute.For<ILogger<LoginHandler>>();
69+
var timeProvider = Substitute.For<TimeProvider>();
70+
timeProvider.GetUtcNow().Returns(currentDate);
71+
var repository = Substitute.For<IUserRepository>();
72+
repository
73+
.GetUserByLogin(command.Login)
74+
.Returns(user);
75+
var passwordService = Substitute.For<IPasswordService>();
76+
var tokenService = Substitute.For<ITokenService>();
77+
var handler = new LoginHandler(logger, timeProvider, repository, passwordService, tokenService);
4478

4579
var result = await handler.Handle(command);
4680

@@ -51,9 +85,14 @@ public async Task LoginWithLockedUser()
5185
public async Task LoginWithInvalidCredentials()
5286
{
5387
var command = new LoginCommand("test", "password");
54-
var user = new User { Login = "test" };
88+
var user = new User
89+
{
90+
Login = "test",
91+
Profile = new UserProfile { Name = "test" },
92+
};
5593

5694
var logger = Substitute.For<ILogger<LoginHandler>>();
95+
var timeProvider = Substitute.For<TimeProvider>();
5796
var repository = Substitute.For<IUserRepository>();
5897
repository
5998
.GetUserByLogin(command.Login)
@@ -63,7 +102,7 @@ public async Task LoginWithInvalidCredentials()
63102
.VerifyPassword(command.Password, user.Password, user.Salt)
64103
.Returns(false);
65104
var tokenService = Substitute.For<ITokenService>();
66-
var handler = new LoginHandler(logger, repository, passwordService, tokenService);
105+
var handler = new LoginHandler(logger, timeProvider, repository, passwordService, tokenService);
67106

68107
var result = await handler.Handle(command);
69108

@@ -74,12 +113,17 @@ public async Task LoginWithInvalidCredentials()
74113
public async Task LoginWithValidCredentials()
75114
{
76115
var command = new LoginCommand("test", "password");
77-
var user = new User { Login = "test" };
116+
var user = new User
117+
{
118+
Login = "test",
119+
Profile = new UserProfile { Name = "test" },
120+
};
78121
var jwtTokenPair = new JwtTokenPair(
79122
new Token(Guid.NewGuid(), "access", DateTimeOffset.UtcNow),
80123
new Token(Guid.NewGuid(), "refresh", DateTimeOffset.UtcNow));
81124

82125
var logger = Substitute.For<ILogger<LoginHandler>>();
126+
var timeProvider = Substitute.For<TimeProvider>();
83127
var repository = Substitute.For<IUserRepository>();
84128
repository
85129
.GetUserByLogin(command.Login)
@@ -92,7 +136,7 @@ public async Task LoginWithValidCredentials()
92136
tokenService
93137
.GenerateTokenPair(user)
94138
.Returns(jwtTokenPair);
95-
var handler = new LoginHandler(logger, repository, passwordService, tokenService);
139+
var handler = new LoginHandler(logger, timeProvider, repository, passwordService, tokenService);
96140

97141
var result = await handler.Handle(command);
98142

Pyro.Api/Pyro.Domain.Identity.UnitTests/Commands/RefreshTokenHandlerTests.cs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,11 @@ public async Task RefreshTokenWithInvalidUser()
4141
public async Task RefreshTokenWithLockedUser()
4242
{
4343
var command = new RefreshToken("token");
44-
var user = new User { Login = "test" };
44+
var user = new User
45+
{
46+
Login = "test",
47+
Profile = new UserProfile { Name = "test" },
48+
};
4549
user.Lock();
4650

4751
var jwtToken = new JwtToken
@@ -70,11 +74,53 @@ public async Task RefreshTokenWithLockedUser()
7074
Assert.That(result, Is.EqualTo(RefreshTokenResult.Fail()));
7175
}
7276

77+
[Test]
78+
public async Task RefreshTokenWithExpiredPassword()
79+
{
80+
var currentDate = DateTimeOffset.UtcNow;
81+
var command = new RefreshToken("token");
82+
var user = new User
83+
{
84+
Login = "test",
85+
Profile = new UserProfile { Name = "test" },
86+
PasswordExpiresAt = currentDate.AddDays(-1),
87+
};
88+
var jwtToken = new JwtToken
89+
{
90+
TokenId = Guid.NewGuid(),
91+
IssuedAt = 0,
92+
ExpiresAt = 0,
93+
UserId = user.Id,
94+
Login = user.Login,
95+
};
96+
97+
var logger = Substitute.For<ILogger<RefreshTokenHandler>>();
98+
var userRepository = Substitute.For<IUserRepository>();
99+
userRepository
100+
.GetUserById(jwtToken.UserId)
101+
.Returns(user);
102+
var tokenService = Substitute.For<ITokenService>();
103+
tokenService
104+
.DecodeTokenId(command.Token)
105+
.Returns(jwtToken);
106+
var timeProvider = Substitute.For<TimeProvider>();
107+
timeProvider.GetUtcNow().Returns(currentDate);
108+
var handler = new RefreshTokenHandler(logger, userRepository, tokenService, timeProvider);
109+
110+
var result = await handler.Handle(command);
111+
112+
Assert.That(result, Is.EqualTo(RefreshTokenResult.Fail()));
113+
}
114+
73115
[Test]
74116
public async Task RefreshTokenWithoutAuthToken()
75117
{
76118
var command = new RefreshToken("token");
77-
var user = new User { Login = "test" };
119+
var user = new User
120+
{
121+
Login = "test",
122+
Profile = new UserProfile { Name = "test" },
123+
};
78124
var jwtToken = new JwtToken
79125
{
80126
TokenId = Guid.NewGuid(),
@@ -106,7 +152,11 @@ public async Task RefreshTokenWithExpiredToken()
106152
{
107153
var currentDate = new DateTimeOffset(new DateTime(2024, 01, 01));
108154
var command = new RefreshToken("token");
109-
var user = new User { Login = "test" };
155+
var user = new User
156+
{
157+
Login = "test",
158+
Profile = new UserProfile { Name = "test" },
159+
};
110160
var authToken = AuthenticationToken.Create(Guid.NewGuid(), user, currentDate.AddMonths(-1));
111161
user.AddAuthenticationToken(authToken);
112162
var jwtToken = new JwtToken
@@ -143,7 +193,12 @@ public async Task RefreshToken()
143193
{
144194
var currentDate = new DateTimeOffset(new DateTime(2024, 01, 01));
145195
var command = new RefreshToken("token");
146-
var user = new User { Login = "test" };
196+
var user = new User
197+
{
198+
Login = "test",
199+
Profile = new UserProfile { Name = "test" },
200+
PasswordExpiresAt = currentDate.AddDays(90),
201+
};
147202
var authToken = AuthenticationToken.Create(Guid.NewGuid(), user, currentDate.AddMonths(1));
148203
user.AddAuthenticationToken(authToken);
149204
var jwtToken = new JwtToken

Pyro.Api/Pyro.Domain.Identity.UnitTests/Commands/UpdateUserHandlerTests.cs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ public class UpdateUserHandlerTests
1313
[Test]
1414
public void UpdateUserWithInvalidRole()
1515
{
16-
var user = new User { Login = "test" };
16+
var user = new User
17+
{
18+
Login = "test",
19+
Profile = new UserProfile { Name = "test" },
20+
};
1721
var updateUser = new UpdateUser(user, ["admin"]);
1822

1923
var repository = Substitute.For<IUserRepository>();
@@ -33,7 +37,11 @@ public async Task UpdateUser()
3337
new Role { Name = "admin" },
3438
new Role { Name = "user" },
3539
};
36-
var user = new User { Login = "test" };
40+
var user = new User
41+
{
42+
Login = "test",
43+
Profile = new UserProfile { Name = "test" },
44+
};
3745
user.AddRole(roles[1]);
3846

3947
var updateUser = new UpdateUser(user, [roles[0].Name]);

0 commit comments

Comments
 (0)