Skip to content

Commit

Permalink
Add wait time for resending invite (#197)
Browse files Browse the repository at this point in the history
* Add wait time for resending invite

* Resolve issues with tests
  • Loading branch information
biswajit-togai authored Apr 9, 2024
1 parent 4a0da01 commit 25bbc7d
Show file tree
Hide file tree
Showing 10 changed files with 48 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/main/kotlin/com/hypto/iam/server/configs/AppConfig.kt
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ data class AppConfig(
val cacheRefreshInterval: Long,
val passcodeValiditySeconds: Long,
val passcodeCountLimit: Long,
val resendInviteWaitTimeSeconds: Long,
val baseUrl: String,
val senderEmailAddress: String,
val signUpEmailTemplate: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,13 @@ object PasscodeRepo : BaseRepo<PasscodesRecord, Passcodes, String>() {
.execute()
return count > 0
}

suspend fun updateLastSent(
id: String,
lastSent: LocalDateTime,
) = ctx("passcodes.updateLastSent")
.update(PASSCODES)
.set(PASSCODES.LAST_SENT, lastSent)
.where(PASSCODES.ID.eq(id))
.execute()
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ class PasscodeServiceImpl : KoinComponent, PasscodeService {
this.organizationId = if (purpose == Purpose.signup) null else organizationId
this.subOrganizationName = subOrganizationName
this.validUntil = validUntil
this.lastSent = LocalDateTime.now()
this.purpose = purpose.toString()
this.createdAt = LocalDateTime.now()
this.metadata = metadata?.let { encryptMetadata(it) }
Expand Down Expand Up @@ -336,6 +337,9 @@ class PasscodeServiceImpl : KoinComponent, PasscodeService {
purpose = Purpose.invite,
email = email,
) ?: throw EntityNotFoundException("No invite email found for $email")
require(record.lastSent.plusSeconds(appConfig.app.resendInviteWaitTimeSeconds) < LocalDateTime.now()) {
"Resend invite email after some time"
}
val link = createPasscodeLink(passcode = record.id, email = email, purpose = Purpose.invite, organizationId = orgId)

val invitingUser = userRepo.findByHrn(principal.hrnStr) ?: throw EntityNotFoundException("User not found")
Expand All @@ -355,6 +359,7 @@ class PasscodeServiceImpl : KoinComponent, PasscodeService {
.build()
val response = sesClient.sendTemplatedEmail(emailRequest)
logger.info { "Resent invite email to $email with message id ${response.messageId()}" }
passcodeRepo.updateLastSent(record.id, LocalDateTime.now())
return true
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ fun GetDelegateTokenRequest.validate(): GetDelegateTokenRequest {

// Validations used by ValidationBuilders

const val NAME_REGEX = "^[a-zA-Z0-9_\\s]*\$"
const val RESOURCE_NAME_REGEX = "^[a-zA-Z0-9_-]*\$"
const val RESOURCE_NAME_REGEX_HINT = "Only characters A..Z, a..z, 0-9, _ and - are supported."
const val PREFERRED_USERNAME_REGEX = "^[a-zA-Z0-9_]*\$"
Expand Down Expand Up @@ -254,6 +255,7 @@ val nameOfUserCheck =
minLength(Constants.MIN_LENGTH) hint "Minimum length expected is ${Constants.MIN_LENGTH}"
maxLength(Constants.MAX_NAME_LENGTH) hint MAXIMUM_LENGTH_SUPPORTED_MSG +
"name is ${Constants.MAX_NAME_LENGTH} characters"
pattern(NAME_REGEX) hint "Only characters A..Z, a..z, 0-9, _ and [space] are supported."
noEndSpaces()
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
ALTER TABLE passcodes
ADD COLUMN last_sent TIMESTAMP;

UPDATE passcodes
SET last_sent = created_at;

ALTER TABLE passcodes
ALTER COLUMN last_sent SET NOT NULL;
1 change: 1 addition & 0 deletions src/main/resources/default_config.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"cache_refresh_interval": 300,
"passcode_validity_seconds": 86400,
"passcode_count_limit": 5,
"resend_invite_wait_time_seconds": 900,
"base_url": "localhost",
"sender_email_address": "<placeholder for senders email address>",
"sign_up_email_template": "<placeholder for signup user template name>",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ class ExceptionHandlerTest : AbstractContainerBaseTest() {
withTestApplication(Application::handleRequest) {
val orgName = "test-org" + IdGenerator.randomId()
val preferredUsername = "user" + IdGenerator.randomId()
val name = "test-name" + IdGenerator.randomId()
val name = "test name"
val testEmail = "test-user-email" + IdGenerator.randomId() + "@hypto.in"
val testPhone = "+919626012778"
val testPassword = "testPassword@Hash1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ internal class OrganizationApiKtTest : AbstractContainerBaseTest() {
}
val orgName = "test-org" + IdGenerator.randomId()
val preferredUsername = "user" + IdGenerator.randomId()
val name = "test-name" + IdGenerator.randomId()
val name = "test name"
val testEmail = "test-user-email" + IdGenerator.randomId() + "@hypto.in"
val testPassword = "testPassword@Hash1"

Expand Down Expand Up @@ -207,7 +207,7 @@ internal class OrganizationApiKtTest : AbstractContainerBaseTest() {
}
val orgName = "test-org" + IdGenerator.randomId()
val preferredUsername = "user" + IdGenerator.randomId()
val name = "test-name" + IdGenerator.randomId()
val name = "test name"
val testEmail = "test-user-email" + IdGenerator.randomId() + "@hypto.in"
val testPhone = "+919626012778"
val testPassword = "testPassword@Hash1"
Expand Down Expand Up @@ -263,7 +263,7 @@ internal class OrganizationApiKtTest : AbstractContainerBaseTest() {
}
val orgName = "test-org" + IdGenerator.randomId()
val preferredUsername = "user" + IdGenerator.randomId()
val name = "test-name" + IdGenerator.randomId()
val name = "test name"
val testEmail = "test-user-email" + IdGenerator.randomId() + "@hypto.in"
val testPhone = "+919999999999"
val testPassword = "testPassword@Hash1"
Expand Down Expand Up @@ -461,7 +461,7 @@ internal class OrganizationApiKtTest : AbstractContainerBaseTest() {
}
val orgName = "test-org" + IdGenerator.randomId()
val preferredUsername = "user" + IdGenerator.randomId()
val name = "test-name" + IdGenerator.randomId()
val name = "test name"
val testEmail = "test-user-email" + IdGenerator.randomId() + "@hypto.in"
val testPhone = "+919626012778"
val testPassword = "testPassword@Hash1"
Expand Down Expand Up @@ -509,7 +509,7 @@ internal class OrganizationApiKtTest : AbstractContainerBaseTest() {
}
val orgName = "test-org" + IdGenerator.randomId()
val preferredUsername = "user" + IdGenerator.randomId()
val name = "test-name" + IdGenerator.randomId()
val name = "test name"
val testEmail = "test-user-email" + IdGenerator.randomId() + "@hypto.in"
val testPhone = "+919626012778"
val testPassword = "testPassword@Hash1"
Expand Down Expand Up @@ -559,7 +559,7 @@ internal class OrganizationApiKtTest : AbstractContainerBaseTest() {
}
val orgName = "test-org" + IdGenerator.randomId()
val preferredUsername = "user" + IdGenerator.randomId()
val name = "test-name" + IdGenerator.randomId()
val name = "test name"
val testEmail = "test-user-email" + IdGenerator.randomId() + "@hypto.in"
val testPhone = "+919626012778"
val testPassword = "testPassword@Hash1"
Expand Down Expand Up @@ -610,7 +610,7 @@ internal class OrganizationApiKtTest : AbstractContainerBaseTest() {
val orgName = "test-org" + IdGenerator.randomId()
val orgDescription = "test-org-description"
val preferredUsername = "user" + IdGenerator.randomId()
val name = "test-name" + IdGenerator.randomId()
val name = "test name"
val testEmail = "test-user-email" + IdGenerator.randomId() + "@hypto.in"
val testPhone = "+919626012778"
val testPassword = "testPassword@Hash1"
Expand Down
12 changes: 10 additions & 2 deletions src/test/kotlin/com/hypto/iam/server/apis/PasscodeApiTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import io.ktor.http.contentType
import io.ktor.server.config.ApplicationConfig
import io.ktor.server.testing.testApplication
import io.mockk.coEvery
import io.mockk.mockk
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.koin.test.inject
Expand Down Expand Up @@ -201,7 +202,7 @@ internal class PasscodeApiTest : AbstractContainerBaseTest() {
val metadata =
mapOf<String, Any>(
"name" to "test-org" + IdGenerator.randomId(),
"rootUserName" to "test-name" + IdGenerator.randomId(),
"rootUserName" to "test name",
"rootUserVerified" to true,
"rootUserPreferredUsername" to "user" + IdGenerator.randomId(),
)
Expand Down Expand Up @@ -275,15 +276,22 @@ internal class PasscodeApiTest : AbstractContainerBaseTest() {
} coAnswers {
PasscodesRecord(
"test-id",
LocalDateTime.now(),
LocalDateTime.now().minusDays(1),
testEmail,
organizationId,
VerifyEmailRequest.Purpose.invite.value,
LocalDateTime.now(),
null,
null,
LocalDateTime.now().minusDays(1),
)
}

coEvery {
passcodeRepo.updateLastSent(any(), any())
} coAnswers {
mockk()
}
val resendEmailResponse =
client.post("/organizations/$organizationId/invites/resend") {
header(HttpHeaders.ContentType, ContentType.Application.Json.toString())
Expand Down
4 changes: 4 additions & 0 deletions src/test/kotlin/com/hypto/iam/server/apis/TokenApiTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ class TokenApiTest : AbstractContainerBaseTest() {
30,
600,
5,
300,
"https://localhost",
"mail@iam.com",
"signupTemplateId",
Expand Down Expand Up @@ -312,6 +313,7 @@ class TokenApiTest : AbstractContainerBaseTest() {
30,
600,
5,
300,
"https://localhost",
"mail@iam.com",
"signupTemplateId",
Expand Down Expand Up @@ -769,6 +771,7 @@ class TokenApiTest : AbstractContainerBaseTest() {
30,
600,
5,
300,
"https://localhost",
"mail@iam.com",
"signupTemplateId",
Expand Down Expand Up @@ -815,6 +818,7 @@ class TokenApiTest : AbstractContainerBaseTest() {
30,
600,
5,
300,
"https://localhost",
"mail@iam.com",
"signupTemplateId",
Expand Down

0 comments on commit 25bbc7d

Please sign in to comment.