Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/recover password #113

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import jakarta.validation.constraints.Size
import pt.up.fe.ni.website.backend.model.constants.AccountConstants as Constants

data class ChangePasswordDto(
@field:Size(min = Constants.Password.minSize, max = Constants.Password.maxSize)
val oldPassword: String,

@field:Size(min = Constants.Password.minSize, max = Constants.Password.maxSize)
LuisDuarte1 marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,13 @@ class AccountService(
}
val account = getAccountByEmail(jwt.subject)

val tokenPasswordHash = jwt.getClaim<String>("passwordHash")
?: throw InvalidBearerTokenException(ErrorMessages.missingHashClaim)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the hash is not in the payload (since we have that option)? You can simply let it be null and skip the next check if that is the case.


if (account.password != tokenPasswordHash) {
throw InvalidBearerTokenException(ErrorMessages.invalidRecoveryToken)
}

account.password = encoder.encode(dto.password)
return repository.save(account)
}
Expand Down
22 changes: 18 additions & 4 deletions src/main/kotlin/pt/up/fe/ni/website/backend/service/AuthService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -59,29 +59,43 @@ class AuthService(

fun generateRecoveryToken(id: Long): String {
BrunoRosendo marked this conversation as resolved.
Show resolved Hide resolved
val account = accountService.getAccountById(id)
return generateToken(account, Duration.ofMinutes(authConfigProperties.jwtRecoveryExpirationMinutes))
return generateToken(
account,
Duration.ofMinutes(authConfigProperties.jwtRecoveryExpirationMinutes),
usePasswordHash = true
)
}

fun getAuthenticatedAccount(): Account {
val authentication = SecurityContextHolder.getContext().authentication
return accountService.getAccountByEmail(authentication.name)
}

private fun generateToken(account: Account, expiration: Duration, isRefresh: Boolean = false): String {
private fun generateToken(
account: Account,
expiration: Duration,
isRefresh: Boolean = false,
usePasswordHash: Boolean = false
): String {
val roles = if (isRefresh) emptyList() else getAuthorities() // TODO: Pass account to getAuthorities()
val scope = roles
.stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(" "))
val currentInstant = Instant.now()
val claims = JwtClaimsSet
val claimsBuilder = JwtClaimsSet
.builder()
.issuer("self")
.issuedAt(currentInstant)
.expiresAt(currentInstant.plus(expiration))
.subject(account.email)
.claim("scope", scope)
.build()

if (usePasswordHash) {
claimsBuilder.claim("passwordHash", account.password)
}

val claims = claimsBuilder.build()
return jwtEncoder.encode(JwtEncoderParameters.from(claims)).tokenValue
}
DoStini marked this conversation as resolved.
Show resolved Hide resolved

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ object ErrorMessages {

const val expiredRecoveryToken = "password recovery token has expired"

const val missingHashClaim = "password hash claim is missing"
BrunoRosendo marked this conversation as resolved.
Show resolved Hide resolved

const val noGenerations = "no generations created yet"

const val noGenerationsToInferYear = "no generations created yet, please specify school year"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -638,22 +638,6 @@ class AccountControllerTest @Autowired constructor(
)
)

@NestedTest
@DisplayName("oldPassword")
inner class OldPasswordValidation {
@BeforeAll
fun setParam() {
validationTester.param = "oldPassword"
}

@Test
fun `should be required`() = validationTester.isRequired()

@Test
@DisplayName("size should be between ${Constants.Password.minSize} and ${Constants.Password.maxSize}()")
fun size() = validationTester.hasSizeBetween(Constants.Password.minSize, Constants.Password.maxSize)
}

@NestedTest
@DisplayName("newPassword")
inner class NewPasswordValidation {
Expand Down