Skip to content

Commit

Permalink
Merge pull request #164 from VAuthenticator/password-reuse-prevention…
Browse files Browse the repository at this point in the history
…-2-iteration

Password reuse prevention 2 iteration
  • Loading branch information
mrFlick72 authored Oct 6, 2023
2 parents 9b7a4e1 + 4c76959 commit 49902f7
Show file tree
Hide file tree
Showing 18 changed files with 243 additions and 70 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.vauthenticator.server.account.mailverification

import com.vauthenticator.server.events.EventConsumer
import com.vauthenticator.server.events.SignUpEvent
import com.vauthenticator.server.events.VAuthenticatorEvent

class SendVerifyMailChallengeUponSignUpEventConsumer(
private val mailChallenge: SendVerifyMailChallenge
) : EventConsumer {
override fun accept(event: VAuthenticatorEvent) {
mailChallenge.sendVerifyMail(event.userName.content)
}

override fun handleable(event: VAuthenticatorEvent): Boolean = event is SignUpEvent

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ package com.vauthenticator.server.account.signup

import com.vauthenticator.server.account.Account
import com.vauthenticator.server.account.Email
import com.vauthenticator.server.account.mailverification.SendVerifyMailChallenge
import com.vauthenticator.server.account.repository.AccountRepository
import com.vauthenticator.server.account.welcome.SayWelcome
import com.vauthenticator.server.events.SignUpEvent
import com.vauthenticator.server.events.VAuthenticatorEventsDispatcher
import com.vauthenticator.server.oauth2.clientapp.ClientAppId
Expand All @@ -18,9 +16,7 @@ open class SignUpUse(
private val passwordPolicy: PasswordPolicy,
private val clientAccountRepository: ClientApplicationRepository,
private val accountRepository: AccountRepository,
private val sendVerifyMailChallenge: SendVerifyMailChallenge,
private val vAuthenticatorPasswordEncoder: VAuthenticatorPasswordEncoder,
private val sayWelcome: SayWelcome,
private val eventsDispatcher: VAuthenticatorEventsDispatcher
) {
open fun execute(clientAppId: ClientAppId, account: Account) {
Expand All @@ -33,16 +29,9 @@ open class SignUpUse(
password = encodedPassword
)
accountRepository.create(registeredAccount)
sayWelcome.welcome(registeredAccount.email)
sendVerifyMailChallenge.sendVerifyMail(account.email)

eventsDispatcher.dispatch(
SignUpEvent(
Email(account.email),
clientAppId,
Instant.now(),
Password(encodedPassword)
)
SignUpEvent(Email(account.email), clientAppId, Instant.now(), Password(encodedPassword))
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.vauthenticator.server.account.welcome

import com.vauthenticator.server.events.EventConsumer
import com.vauthenticator.server.events.SignUpEvent
import com.vauthenticator.server.events.VAuthenticatorEvent

class SendWelcomeMailUponSignUpEventConsumer(
private val sayWelcome: SayWelcome
) : EventConsumer {
override fun accept(event: VAuthenticatorEvent) {
sayWelcome.welcome(event.userName.content)
}

override fun handleable(event: VAuthenticatorEvent): Boolean = event is SignUpEvent

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.vauthenticator.server.config

import com.vauthenticator.server.account.mailverification.SendVerifyMailChallengeUponSignUpEventConsumer
import com.vauthenticator.server.account.welcome.SendWelcomeMailUponSignUpEventConsumer
import com.vauthenticator.server.events.*
import com.vauthenticator.server.password.UpdatePasswordHistoryUponSignUpEventConsumer
import org.springframework.context.ApplicationEventPublisher
Expand All @@ -20,11 +22,15 @@ class EventsConfig(private val eventConsumerConfig: EventConsumerConfig) {
@Bean
fun eventsCollector(
updatePasswordHistoryUponSignUpEventConsumer: UpdatePasswordHistoryUponSignUpEventConsumer,
sendWelcomeMailUponSignUpEventConsumer: SendWelcomeMailUponSignUpEventConsumer,
sendVerifyMailChallengeUponSignUpEventConsumer: SendVerifyMailChallengeUponSignUpEventConsumer,
loggerEventConsumer: EventConsumer
) =
SpringEventsCollector(
listOf(
loggerEventConsumer,
sendWelcomeMailUponSignUpEventConsumer,
sendVerifyMailChallengeUponSignUpEventConsumer,
updatePasswordHistoryUponSignUpEventConsumer
)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.vauthenticator.server.config
import com.hubspot.jinjava.Jinjava
import com.vauthenticator.document.repository.DocumentRepository
import com.vauthenticator.server.account.mailverification.SendVerifyMailChallenge
import com.vauthenticator.server.account.mailverification.SendVerifyMailChallengeUponSignUpEventConsumer
import com.vauthenticator.server.account.mailverification.VerifyMailChallengeSent
import com.vauthenticator.server.account.repository.AccountRepository
import com.vauthenticator.server.account.tiket.TicketRepository
Expand All @@ -19,34 +20,50 @@ import org.springframework.mail.javamail.JavaMailSender
class MailVerificationConfig {

@Bean
fun sendVerifyMailChallenge(clientAccountRepository: ClientApplicationRepository,
accountRepository: AccountRepository,
verificationTicketFactory: VerificationTicketFactory,
verificationMailSender: MailSenderService,
@Value("\${vauthenticator.host}") frontChannelBaseUrl: String) =
SendVerifyMailChallenge(
accountRepository,
verificationTicketFactory,
verificationMailSender,
frontChannelBaseUrl
)
fun sendVerifyMailChallenge(
clientAccountRepository: ClientApplicationRepository,
accountRepository: AccountRepository,
verificationTicketFactory: VerificationTicketFactory,
verificationMailSender: MailSenderService,
@Value("\${vauthenticator.host}") frontChannelBaseUrl: String
) =
SendVerifyMailChallenge(
accountRepository,
verificationTicketFactory,
verificationMailSender,
frontChannelBaseUrl
)

@Bean
fun verifyMailChallengeSent(accountRepository: AccountRepository,
ticketRepository: TicketRepository,
mfaMethodsEnrolmentAssociation : MfaMethodsEnrolmentAssociation
fun verifyMailChallengeSent(
accountRepository: AccountRepository,
ticketRepository: TicketRepository,
mfaMethodsEnrolmentAssociation: MfaMethodsEnrolmentAssociation
) =
VerifyMailChallengeSent(
accountRepository,
ticketRepository,
mfaMethodsEnrolmentAssociation
)
VerifyMailChallengeSent(
accountRepository,
ticketRepository,
mfaMethodsEnrolmentAssociation
)

@Bean
fun verificationMailSender(javaMailSender: JavaMailSender, documentRepository: DocumentRepository, noReplyMailConfiguration: NoReplyMailConfiguration) =
JavaMailSenderService(documentRepository,
javaMailSender,
JinjavaMailTemplateResolver(Jinjava()),
SimpleMailMessageFactory(noReplyMailConfiguration.from, noReplyMailConfiguration.welcomeMailSubject, MailType.EMAIL_VERIFICATION)
fun verificationMailSender(
javaMailSender: JavaMailSender,
documentRepository: DocumentRepository,
noReplyMailConfiguration: NoReplyMailConfiguration
) =
JavaMailSenderService(
documentRepository,
javaMailSender,
JinjavaMailTemplateResolver(Jinjava()),
SimpleMailMessageFactory(
noReplyMailConfiguration.from,
noReplyMailConfiguration.welcomeMailSubject,
MailType.EMAIL_VERIFICATION
)
)

@Bean
fun sendVerifyMailChallengeUponSignUpEventConsumer(mailChallenge: SendVerifyMailChallenge) =
SendVerifyMailChallengeUponSignUpEventConsumer(mailChallenge)
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,13 @@ class PasswordPolicyConfig {

@Bean
fun passwordHistoryRepository(
@Value("\${vauthenticator.dynamo-db.password-history.history-evaluation-limit}") historyEvaluationLimit: Int,
@Value("\${vauthenticator.dynamo-db.password-history.max-history-allowed-size}") maxHistoryAllowedSize: Int,
@Value("\${vauthenticator.dynamo-db.password-history.table-name}") dynamoPasswordHistoryTableName: String,
dynamoDbClient: DynamoDbClient
): DynamoPasswordHistoryRepository = DynamoPasswordHistoryRepository(
historyEvaluationLimit,
maxHistoryAllowedSize,
Clock.systemUTC(),
dynamoPasswordHistoryTableName,
dynamoDbClient
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@ class SingUpConfig {
passwordPolicy,
clientAccountRepository,
accountRepository,
sendVerifyMailChallenge,
vAuthenticatorPasswordEncoder,
sayWelcome,
vAuthenticatorEventsDispatcher
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.hubspot.jinjava.Jinjava
import com.vauthenticator.document.repository.DocumentRepository
import com.vauthenticator.server.account.repository.AccountRepository
import com.vauthenticator.server.account.welcome.SayWelcome
import com.vauthenticator.server.account.welcome.SendWelcomeMailUponSignUpEventConsumer
import com.vauthenticator.server.mail.*
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
Expand Down Expand Up @@ -34,4 +35,8 @@ class WelcomeConfig {
MailType.WELCOME
)
)

@Bean
fun sendWelcomeMailUponSignUpEventConsumer(sayWelcome: SayWelcome) =
SendWelcomeMailUponSignUpEventConsumer(sayWelcome)
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,15 @@ class LoggerEventConsumer(private val eventConsumerConfig: EventConsumerConfig)
private val logger = LoggerFactory.getLogger(LoggerEventConsumer::class.java)

override fun accept(event: VAuthenticatorEvent) {
if (handleable(event)) {
val logLine = """
val logLine = """
The user ${event.userName.content}
with the client id ${event.clientAppId.content}
has done an ${event.payload::class.simpleName} event
event at ${event.timeStamp.epochSecond}
event payload: ${event.payload}
""".trimIndent()
logger.info(logLine)
}
logger.info(logLine)

}

override fun handleable(event: VAuthenticatorEvent) = eventConsumerConfig.enable[LOGGER_EVENT_CONSUMER] ?: false
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,20 @@
package com.vauthenticator.server.events

import org.slf4j.LoggerFactory
import org.springframework.context.event.EventListener

class SpringEventsCollector(private val eventConsumers: List<EventConsumer>) : EventsCollector {

private val logger = LoggerFactory.getLogger(SpringEventsCollector::class.java)
@EventListener
override fun accept(event: VAuthenticatorEvent) {
eventConsumers.forEach { it.accept(event) }
logger.debug("event ${event::class.simpleName} is collected")
eventConsumers.forEach {
val handleable = it.handleable(event)
logger.debug("event ${event::class.simpleName} is handleable $handleable")
if(handleable){
it.accept(event)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.vauthenticator.server.password
import com.vauthenticator.server.extentions.asDynamoAttribute
import com.vauthenticator.server.extentions.valueAsStringFor
import software.amazon.awssdk.services.dynamodb.DynamoDbClient
import software.amazon.awssdk.services.dynamodb.model.AttributeValue
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest
import software.amazon.awssdk.services.dynamodb.model.QueryRequest
import java.time.Clock
Expand All @@ -11,17 +13,19 @@ import java.time.ZoneOffset

interface PasswordHistoryRepository {

fun store(userName : String, password: Password)
fun load(userName : String, ): List<Password>
fun store(userName: String, password: Password)
fun load(userName: String): List<Password>

}

class DynamoPasswordHistoryRepository(
private val historyEvaluationLimit: Int,
private val maxHistoryAllowedSize: Int,
private val clock: Clock,
private val dynamoPasswordHistoryTableName: String,
private val dynamoDbClient: DynamoDbClient
) : PasswordHistoryRepository {
override fun store(userName : String, password: Password) {
override fun store(userName: String, password: Password) {
dynamoDbClient.putItem(
PutItemRequest.builder()
.tableName(dynamoPasswordHistoryTableName)
Expand All @@ -41,15 +45,33 @@ class DynamoPasswordHistoryRepository(
.toInstant(ZoneOffset.UTC)
.toEpochMilli()

override fun load(userName : String, ): List<Password> {
return dynamoDbClient.query(
override fun load(userName: String): List<Password> {
val items = dynamoDbClient.query(
QueryRequest.builder()
.tableName(dynamoPasswordHistoryTableName)
.scanIndexForward(false)
.keyConditionExpression("user_name=:email")
.expressionAttributeValues(mapOf(":email" to userName.asDynamoAttribute())).build()
).items()
.map { Password(it.valueAsStringFor("password")) }

val allowedPassword = items.take(historyEvaluationLimit)

deleteUselessPasswordHistory(items)
return allowedPassword.map { Password(it.valueAsStringFor("password")) }
}

private fun deleteUselessPasswordHistory(itemsInTheHistory: List<Map<String, AttributeValue>>) {
val leftoverSize = itemsInTheHistory.size - maxHistoryAllowedSize
if (leftoverSize > 0) {
itemsInTheHistory.takeLast(leftoverSize)
.forEach { itemToDelete ->
dynamoDbClient.deleteItem(
DeleteItemRequest.builder()
.tableName(dynamoPasswordHistoryTableName)
.key(itemToDelete.filterKeys { it != "password" })
.build()
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,5 @@ class UpdatePasswordHistoryUponSignUpEventConsumer(
passwordHistoryRepository.store(event.userName.content, event.payload as Password)
}

override fun handleable(event: VAuthenticatorEvent): Boolean =
event::class.isInstance(SignUpEvent::class)

override fun handleable(event: VAuthenticatorEvent): Boolean = event is SignUpEvent
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.vauthenticator.server.account.mailverification

import com.vauthenticator.server.events.EventFixture.signUpEvent
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.junit5.MockKExtension
import io.mockk.just
import io.mockk.runs
import io.mockk.verify
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith

@ExtendWith(MockKExtension::class)
class SendVerifyMailChallengeUponSignUpEventConsumerTest {

@MockK
lateinit var mailChallenge: SendVerifyMailChallenge

@Test
fun `when the verify mail challenge is sent`() {
val uut = SendVerifyMailChallengeUponSignUpEventConsumer(mailChallenge)

every { mailChallenge.sendVerifyMail("AN_EMAIL") } just runs

uut.accept(signUpEvent)
assertEquals(true, uut.handleable(signUpEvent))

verify { mailChallenge.sendVerifyMail("AN_EMAIL") }
}
}
Loading

0 comments on commit 49902f7

Please sign in to comment.