@@ -44,14 +44,18 @@ public async Task<IActionResult> ChangePassword([FromBody] ChangePasswordRequest
4444 {
4545 Guid userId = User . GetUserId ( ) ;
4646 string ? phc = await FindUserPhcAsync ( userId ) ;
47- if ( string . IsNullOrWhiteSpace ( phc ) )
47+ bool canSetIfNeverHad = await CanSetPasswordIfNeverHadAsync ( userId ) ;
48+ if ( string . IsNullOrWhiteSpace ( phc ) && ! canSetIfNeverHad )
4849 {
49- return this . ApiNotFound ( "User not found " ) ;
50+ return this . ApiNotFound ( "User or password does not exist " ) ;
5051 }
51- bool isValidPassword = _passwordHasher . Verify ( request . CurrentPassword , phc ) ;
52- if ( ! isValidPassword )
52+ if ( ! string . IsNullOrWhiteSpace ( phc ) )
5353 {
54- return this . ApiBadRequest ( "Invalid current password" ) ;
54+ bool isValidPassword = _passwordHasher . Verify ( request . CurrentPassword , phc ) ;
55+ if ( ! isValidPassword )
56+ {
57+ return this . ApiUnauthorized ( "Current password is incorrect" ) ;
58+ }
5559 }
5660 string newPhc = _passwordHasher . Hash ( request . NewPassword ) ;
5761 await SetUserPasswordPhcAsync ( userId , newPhc ) ;
@@ -79,7 +83,7 @@ public async Task<IActionResult> RefreshToken([FromBody] RefreshTokenRequestDto
7983 string newRefreshToken = StringHelpers . CreateRandomString ( 64 ) ;
8084 var roles = await GetUserRolesAsync ( userId . Value ) ;
8185 string accessToken = CreateAccessToken ( userId . Value , roles ) ;
82- await SaveAndRevokeRefreshTokenAsync ( userId . Value , request . RefreshToken , newRefreshToken ) ;
86+ await SaveAndRevokeRefreshTokenAsync ( userId . Value , request . RefreshToken , newRefreshToken , AuthType . Unknown ) ;
8387 await OnTokenRefreshedAsync ( userId . Value , newRefreshToken ) ;
8488 return Ok ( new TokenPairDto
8589 {
@@ -119,7 +123,7 @@ public async Task<IActionResult> Login([FromBody] LoginRequestDto request)
119123 var roles = await GetUserRolesAsync ( userId . Value ) ;
120124 string accessToken = CreateAccessToken ( userId . Value , roles ) ;
121125 string refreshToken = StringHelpers . CreateRandomString ( 64 ) ;
122- await SaveAndRevokeRefreshTokenAsync ( userId . Value , string . Empty , refreshToken ) ;
126+ await SaveAndRevokeRefreshTokenAsync ( userId . Value , string . Empty , refreshToken , AuthType . Credentials ) ;
123127 await OnUserLoggedInAsync ( userId . Value , AuthType . Credentials ) ;
124128 return Ok ( new TokenPairDto
125129 {
@@ -142,7 +146,7 @@ public async Task<IActionResult> Login([FromBody] LoginRequestDto request)
142146 /// email is not verified.</returns>
143147 /// <exception cref="InvalidOperationException">Thrown if user information cannot be retrieved from Google using the provided token.</exception>
144148 [ HttpPost ( "login/google" ) ]
145- public async Task < IActionResult > Login ( [ FromQuery ] string token )
149+ public async Task < IActionResult > LoginWithGoogle ( [ FromQuery ] string token )
146150 {
147151 const string url = "https://openidconnect.googleapis.com/v1/userinfo" ;
148152 using HttpClient http = new ( ) ;
@@ -154,19 +158,15 @@ public async Task<IActionResult> Login([FromQuery] string token)
154158 {
155159 return this . ApiUnauthorized ( "Email is not verified" ) ;
156160 }
157- Guid ? userId = await FindUserByUsernameAsync ( response . Email ) ;
161+ Guid ? userId = await CreateOrUpdateUserFromGoogleAsync ( response ) ;
158162 if ( ! userId . HasValue || userId == Guid . Empty )
159163 {
160- userId = await CreateUserFromGoogleAsync ( response ) ;
161- if ( ! userId . HasValue || userId == Guid . Empty )
162- {
163- return this . ApiUnauthorized ( "User not found" ) ;
164- }
164+ return this . ApiUnauthorized ( "User not found" ) ;
165165 }
166166 var roles = await GetUserRolesAsync ( userId . Value ) ;
167167 string accessToken = CreateAccessToken ( userId . Value , roles ) ;
168168 string refreshToken = StringHelpers . CreateRandomString ( 64 ) ;
169- await SaveAndRevokeRefreshTokenAsync ( userId . Value , string . Empty , refreshToken ) ;
169+ await SaveAndRevokeRefreshTokenAsync ( userId . Value , string . Empty , refreshToken , AuthType . Google ) ;
170170 await OnUserLoggedInAsync ( userId . Value , AuthType . Google ) ;
171171 return Ok ( new TokenPairDto
172172 {
@@ -192,8 +192,9 @@ public async Task<IActionResult> Login([FromQuery] string token)
192192 /// <param name="userId">The unique identifier of the user for whom the refresh token is being updated.</param>
193193 /// <param name="oldRefreshToken">The refresh token to be revoked. Cannot be null or empty.</param>
194194 /// <param name="newRefreshToken">The new refresh token to be saved for the user. Cannot be null or empty.</param>
195+ /// <param name="authType">The type of authentication used for the user when logged in for the first time.</param>
195196 /// <returns>A task that represents the asynchronous operation.</returns>
196- public abstract Task SaveAndRevokeRefreshTokenAsync ( Guid userId , string oldRefreshToken , string newRefreshToken ) ;
197+ public abstract Task SaveAndRevokeRefreshTokenAsync ( Guid userId , string oldRefreshToken , string newRefreshToken , AuthType authType ) ;
197198
198199 /// <summary>
199200 /// Asynchronously retrieves the unique identifier of a user associated with the specified refresh token.
@@ -227,6 +228,15 @@ public async Task<IActionResult> Login([FromQuery] string token)
227228 /// roles.</returns>
228229 public abstract Task < IEnumerable < string > > GetUserRolesAsync ( Guid userId ) ;
229230
231+ /// <summary>
232+ /// Determines whether a password can be set for the specified user if the user has never previously set a
233+ /// password.
234+ /// </summary>
235+ /// <param name="userId">The unique identifier of the user to evaluate. This value must correspond to an existing user.</param>
236+ /// <returns>A task that represents the asynchronous operation. The task result is <see langword="true"/> if the user is
237+ /// eligible to set a password for the first time; otherwise, <see langword="false"/>.</returns>
238+ public abstract Task < bool > CanSetPasswordIfNeverHadAsync ( Guid userId ) ;
239+
230240 /// <summary>
231241 /// Handles logic to be executed after a user has changed their password.
232242 /// </summary>
@@ -272,11 +282,12 @@ public virtual bool IsEmailVerificationRequired()
272282
273283 /// <summary>
274284 /// Creates a new user account based on the information provided by a Google OpenID response.
285+ /// If a user with the same email already exists, their information may be updated instead.
275286 /// </summary>
276287 /// <param name="dto">The Google OpenID response data containing user information to create the account. Cannot be null.</param>
277288 /// <returns>A task that represents the asynchronous operation. The task result contains the unique identifier of the
278289 /// newly created user if successful; otherwise, null.</returns>
279- public virtual Task < Guid ? > CreateUserFromGoogleAsync ( GoogleOpenIdResponseDto dto )
290+ public virtual Task < Guid ? > CreateOrUpdateUserFromGoogleAsync ( GoogleOpenIdResponseDto dto )
280291 {
281292 return Task . FromResult < Guid ? > ( null ) ;
282293 }
0 commit comments