Skip to content

Commit eb2c1d2

Browse files
authored
Merge pull request #732 from Crypter-File-Transfer/stable
Merge 'stable' to 'main'
2 parents ebf484e + b001819 commit eb2c1d2

40 files changed

Lines changed: 1319 additions & 550 deletions

Crypter.API/Controllers/UserAuthenticationController.cs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
using Crypter.API.Methods;
3232
using Crypter.Common.Contracts;
3333
using Crypter.Common.Contracts.Features.UserAuthentication;
34+
using Crypter.Common.Contracts.Features.UserAuthentication.PasswordChange;
3435
using Crypter.Core.Features.UserAuthentication.Commands;
3536
using Crypter.Core.Features.UserAuthentication.Queries;
3637
using EasyMonads;
@@ -110,8 +111,7 @@ IActionResult MakeErrorResponse(LoginError error)
110111
return error switch
111112
{
112113
LoginError.UnknownError
113-
or LoginError.PasswordHashFailure => MakeErrorResponseBase(HttpStatusCode.InternalServerError,
114-
error),
114+
or LoginError.PasswordHashFailure => MakeErrorResponseBase(HttpStatusCode.InternalServerError, error),
115115
LoginError.InvalidUsername
116116
or LoginError.InvalidPassword
117117
or LoginError.InvalidTokenTypeRequested
@@ -208,6 +208,40 @@ IActionResult MakeErrorResponse(PasswordChallengeError error)
208208
MakeErrorResponse(PasswordChallengeError.UnknownError));
209209
}
210210

211+
/// <summary>
212+
/// Handle a request to change the password for an authorized user
213+
/// </summary>
214+
/// <param name="request"></param>
215+
/// <returns></returns>
216+
[HttpPost("password")]
217+
[Authorize]
218+
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(void))]
219+
[ProducesResponseType(StatusCodes.Status400BadRequest, Type = typeof(ErrorResponse))]
220+
[ProducesResponseType(StatusCodes.Status401Unauthorized, Type = typeof(void))]
221+
public async Task<IActionResult> PasswordChangeAsync([FromBody] PasswordChangeRequest request)
222+
{
223+
IActionResult MakeErrorResponse(PasswordChangeError error)
224+
{
225+
#pragma warning disable CS8524
226+
return error switch
227+
{
228+
PasswordChangeError.UnknownError
229+
or PasswordChangeError.PasswordHashFailure => MakeErrorResponseBase(HttpStatusCode.InternalServerError, error),
230+
PasswordChangeError.InvalidPassword
231+
or PasswordChangeError.InvalidOldPasswordVersion
232+
or PasswordChangeError.InvalidNewPasswordVersion => MakeErrorResponseBase(HttpStatusCode.BadRequest, error)
233+
};
234+
#pragma warning restore CS8524
235+
}
236+
237+
ChangeUserPasswordCommand command = new ChangeUserPasswordCommand(UserId, request);
238+
return await _sender.Send(command)
239+
.MatchAsync(
240+
MakeErrorResponse,
241+
_ => Ok(),
242+
MakeErrorResponse(PasswordChangeError.UnknownError));
243+
}
244+
211245
/// <summary>
212246
/// Clears the provided refresh token from the database, ensuring it cannot be used for subsequent requests.
213247
/// </summary>

Crypter.API/Crypter.API.csproj

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@
1818
<ProjectReference Include="..\Crypter.Core\Crypter.Core.csproj" />
1919
</ItemGroup>
2020
<ItemGroup>
21-
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.8">
21+
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="8.0.10">
2222
<PrivateAssets>all</PrivateAssets>
2323
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2424
</PackageReference>
25-
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.8">
25+
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.10">
2626
<PrivateAssets>all</PrivateAssets>
2727
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
2828
</PackageReference>
29-
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.7.3" />
30-
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.8" />
31-
<PackageReference Include="System.Drawing.Common" Version="8.0.8" />
29+
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.8.1" />
30+
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.10" />
31+
<PackageReference Include="System.Drawing.Common" Version="8.0.10" />
3232
</ItemGroup>
3333
</Project>

Crypter.Common.Client/HttpClients/Requests/UserAuthenticationRequests.cs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
using Crypter.Common.Client.Interfaces.HttpClients;
3030
using Crypter.Common.Client.Interfaces.Requests;
3131
using Crypter.Common.Contracts.Features.UserAuthentication;
32+
using Crypter.Common.Contracts.Features.UserAuthentication.PasswordChange;
3233
using EasyMonads;
3334

3435
namespace Crypter.Common.Client.HttpClients.Requests;
@@ -77,14 +78,20 @@ public async Task<Either<RefreshError, RefreshResponse>> RefreshSessionAsync()
7778
return response;
7879
}
7980

80-
public Task<Either<PasswordChallengeError, Unit>> PasswordChallengeAsync(
81-
PasswordChallengeRequest testPasswordRequest)
81+
public Task<Either<PasswordChallengeError, Unit>> PasswordChallengeAsync(PasswordChallengeRequest testPasswordRequest)
8282
{
8383
const string url = "api/user/authentication/password/challenge";
8484
return _crypterAuthenticatedHttpClient.PostEitherUnitResponseAsync(url, testPasswordRequest)
8585
.ExtractErrorCode<PasswordChallengeError, Unit>();
8686
}
8787

88+
public Task<Either<PasswordChangeError, Unit>> ChangePasswordAsync(PasswordChangeRequest passwordChangeRequest)
89+
{
90+
const string url = "api/user/authentication/password";
91+
return _crypterAuthenticatedHttpClient.PostEitherUnitResponseAsync(url, passwordChangeRequest)
92+
.ExtractErrorCode<PasswordChangeError, Unit>();
93+
}
94+
8895
public Task<Either<LogoutError, Unit>> LogoutAsync()
8996
{
9097
const string url = "api/user/authentication/logout";

Crypter.Common.Client/HttpClients/Requests/UserKeyRequests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,7 @@ public Task<Either<InsertMasterKeyError, Unit>> InsertMasterKeyAsync(InsertMaste
5555
.ExtractErrorCode<InsertMasterKeyError, Unit>();
5656
}
5757

58-
public Task<Either<GetMasterKeyRecoveryProofError, GetMasterKeyRecoveryProofResponse>>
59-
GetMasterKeyRecoveryProofAsync(GetMasterKeyRecoveryProofRequest request)
58+
public Task<Either<GetMasterKeyRecoveryProofError, GetMasterKeyRecoveryProofResponse>> GetMasterKeyRecoveryProofAsync(GetMasterKeyRecoveryProofRequest request)
6059
{
6160
const string url = "api/user/key/master/recovery-proof/challenge";
6261
return _crypterAuthenticatedHttpClient

Crypter.Common.Client/Interfaces/Requests/IUserAuthenticationRequests.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
using System;
2828
using System.Threading.Tasks;
2929
using Crypter.Common.Contracts.Features.UserAuthentication;
30+
using Crypter.Common.Contracts.Features.UserAuthentication.PasswordChange;
3031
using EasyMonads;
3132

3233
namespace Crypter.Common.Client.Interfaces.Requests;
@@ -38,5 +39,6 @@ public interface IUserAuthenticationRequests
3839
Task<Either<LoginError, LoginResponse>> LoginAsync(LoginRequest loginRequest);
3940
Task<Either<RefreshError, RefreshResponse>> RefreshSessionAsync();
4041
Task<Either<PasswordChallengeError, Unit>> PasswordChallengeAsync(PasswordChallengeRequest testPasswordRequest);
42+
Task<Either<PasswordChangeError, Unit>> ChangePasswordAsync(PasswordChangeRequest passwordChangeRequest);
4143
Task<Either<LogoutError, Unit>> LogoutAsync();
4244
}

Crypter.Common.Client/Interfaces/Requests/IUserKeyRequests.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,7 @@ public interface IUserKeyRequests
3535
Task<Either<GetMasterKeyError, GetMasterKeyResponse>> GetMasterKeyAsync();
3636
Task<Either<InsertMasterKeyError, Unit>> InsertMasterKeyAsync(InsertMasterKeyRequest request);
3737

38-
Task<Either<GetMasterKeyRecoveryProofError, GetMasterKeyRecoveryProofResponse>> GetMasterKeyRecoveryProofAsync(
39-
GetMasterKeyRecoveryProofRequest request);
38+
Task<Either<GetMasterKeyRecoveryProofError, GetMasterKeyRecoveryProofResponse>> GetMasterKeyRecoveryProofAsync(GetMasterKeyRecoveryProofRequest request);
4039

4140
Task<Either<GetPrivateKeyError, GetPrivateKeyResponse>> GetPrivateKeyAsync();
4241
Task<Either<InsertKeyPairError, Unit>> InsertKeyPairAsync(InsertKeyPairRequest request);

Crypter.Common.Client/Interfaces/Services/IUserKeysService.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,7 @@ public interface IUserKeysService
3939

4040
Task DownloadExistingKeysAsync(Username username, Password password, bool trustDevice);
4141
Task DownloadExistingKeysAsync(byte[] credentialKey, bool trustDevice);
42-
43-
Task<Maybe<RecoveryKey>> UploadNewKeysAsync(Username username, Password password,
44-
VersionedPassword versionedPassword, bool trustDevice);
45-
46-
Task<Maybe<RecoveryKey>> UploadNewKeysAsync(VersionedPassword versionedPassword, byte[] credentialKey,
47-
bool trustDevice);
42+
43+
Task<Maybe<RecoveryKey>> UploadNewKeysAsync(Username username, Password password, VersionedPassword versionedPassword, bool trustDevice);
44+
Task<Maybe<RecoveryKey>> UploadNewKeysAsync(VersionedPassword versionedPassword, byte[] credentialKey, bool trustDevice);
4845
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (C) 2024 Crypter File Transfer
3+
*
4+
* This file is part of the Crypter file transfer project.
5+
*
6+
* Crypter is free software: you can redistribute it and/or modify
7+
* it under the terms of the GNU Affero General Public License as published by
8+
* the Free Software Foundation, either version 3 of the License, or
9+
* (at your option) any later version.
10+
*
11+
* The Crypter source code is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14+
* GNU Affero General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Affero General Public License
17+
* along with this program. If not, see <http://www.gnu.org/licenses/>.
18+
*
19+
* You can be released from the requirements of the aforementioned license
20+
* by purchasing a commercial license. Buying such a license is mandatory
21+
* as soon as you develop commercial activities involving the Crypter source
22+
* code without disclosing the source code of your own applications.
23+
*
24+
* Contact the current copyright holder to discuss commercial license options.
25+
*/
26+
27+
using System.Threading.Tasks;
28+
using Crypter.Common.Contracts.Features.UserAuthentication.PasswordChange;
29+
using Crypter.Common.Primitives;
30+
using EasyMonads;
31+
32+
namespace Crypter.Common.Client.Interfaces.Services.UserSettings;
33+
34+
public interface IUserPasswordChangeService
35+
{
36+
Task<Either<PasswordChangeError, Unit>> ChangePasswordAsync(Password oldPassword, Password newPassword);
37+
}

Crypter.Common.Client/Services/UserKeysService.cs

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -93,33 +93,29 @@ public Task DownloadExistingKeysAsync(byte[] credentialKey, bool trustDevice)
9393
private Task<Maybe<byte[]>> DownloadAndDecryptMasterKey(byte[] credentialKey)
9494
{
9595
return _crypterApiClient.UserKey.GetMasterKeyAsync()
96-
.MapAsync<GetMasterKeyError, GetMasterKeyResponse, byte[]>(x =>
97-
_cryptoProvider.Encryption.Decrypt(credentialKey, x.Nonce, x.EncryptedKey))
96+
.MapAsync<GetMasterKeyError, GetMasterKeyResponse, byte[]>(x => _cryptoProvider.Encryption.Decrypt(credentialKey, x.Nonce, x.EncryptedKey))
9897
.ToMaybeTask();
9998
}
10099

101100
private Task<Maybe<byte[]>> DownloadAndDecryptPrivateKey(byte[] masterKey)
102101
{
103102
return _crypterApiClient.UserKey.GetPrivateKeyAsync()
104-
.MapAsync<GetPrivateKeyError, GetPrivateKeyResponse, byte[]>(x =>
105-
_cryptoProvider.Encryption.Decrypt(masterKey, x.Nonce, x.EncryptedKey))
103+
.MapAsync<GetPrivateKeyError, GetPrivateKeyResponse, byte[]>(x => _cryptoProvider.Encryption.Decrypt(masterKey, x.Nonce, x.EncryptedKey))
106104
.ToMaybeTask();
107105
}
108106

109107
#endregion
110108

111109
#region Upload New Keys
112110

113-
public Task<Maybe<RecoveryKey>> UploadNewKeysAsync(Username username, Password password,
114-
VersionedPassword versionedPassword, bool trustDevice)
111+
public Task<Maybe<RecoveryKey>> UploadNewKeysAsync(Username username, Password password, VersionedPassword versionedPassword, bool trustDevice)
115112
{
116113
return _userPasswordService
117114
.DeriveUserCredentialKeyAsync(username, password, _userPasswordService.CurrentPasswordVersion)
118115
.BindAsync(credentialKey => UploadNewKeysAsync(versionedPassword, credentialKey, trustDevice));
119116
}
120117

121-
public Task<Maybe<RecoveryKey>> UploadNewKeysAsync(VersionedPassword versionedPassword, byte[] credentialKey,
122-
bool trustDevice)
118+
public Task<Maybe<RecoveryKey>> UploadNewKeysAsync(VersionedPassword versionedPassword, byte[] credentialKey, bool trustDevice)
123119
{
124120
return UploadNewMasterKeyAsync(versionedPassword, credentialKey)
125121
.BindAsync(recoveryKey => UploadNewUserKeyPairAsync(recoveryKey.MasterKey)
@@ -134,8 +130,7 @@ private Task<Maybe<RecoveryKey>> UploadNewMasterKeyAsync(VersionedPassword versi
134130
byte[] encryptedMasterKey = _cryptoProvider.Encryption.Encrypt(credentialKey, nonce, newMasterKey);
135131
byte[] recoveryProof = _cryptoProvider.Random.GenerateRandomBytes(32);
136132

137-
InsertMasterKeyRequest request =
138-
new InsertMasterKeyRequest(versionedPassword.Password, encryptedMasterKey, nonce, recoveryProof);
133+
InsertMasterKeyRequest request = new InsertMasterKeyRequest(versionedPassword.Password, encryptedMasterKey, nonce, recoveryProof);
139134
return _crypterApiClient.UserKey.InsertMasterKeyAsync(request)
140135
.ToMaybeTask()
141136
.BindAsync(_ => new RecoveryKey(newMasterKey, request.RecoveryProof));

Crypter.Common.Client/Services/UserPasswordService.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,7 @@ public Task<Maybe<byte[]>> DeriveUserCredentialKeyAsync(Username username, Passw
7777
};
7878
}
7979

80-
public async Task<Maybe<VersionedPassword>> DeriveUserAuthenticationPasswordAsync(Username username,
81-
Password password, int passwordVersion)
80+
public async Task<Maybe<VersionedPassword>> DeriveUserAuthenticationPasswordAsync(Username username, Password password, int passwordVersion)
8281
{
8382
#pragma warning disable CS0618
8483
return passwordVersion switch
@@ -101,8 +100,7 @@ private VersionedPassword DeriveSha512AuthenticationPassword(Username username,
101100
return new VersionedPassword(hashedPassword, 0);
102101
}
103102

104-
private async Task<Maybe<VersionedPassword>> DeriveArgonAuthenticationPasswordAsync(Username username,
105-
Password password)
103+
private async Task<Maybe<VersionedPassword>> DeriveArgonAuthenticationPasswordAsync(Username username, Password password)
106104
{
107105
OnPasswordHashBeginEvent(PasswordHashType.AuthenticationKey);
108106
await Task.Delay(1);

0 commit comments

Comments
 (0)