Skip to content

Commit 8fde739

Browse files
authored
Merge pull request #5 from scottpd/possible-changes
Added some possible changes
2 parents a9e8de9 + 25eb60b commit 8fde739

File tree

14 files changed

+112
-84
lines changed

14 files changed

+112
-84
lines changed

http4k-example/src/main/kotlin/za/co/ee/learning/domain/security/JWTProvider.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ data class TokenInfo(
1010
)
1111

1212
interface JWTProvider {
13-
fun generate(user: User): TokenInfo
13+
fun generate(user: User): DomainResult<TokenInfo>
1414

1515
fun verify(jwt: String): DomainResult<UUID>
1616
}

http4k-example/src/main/kotlin/za/co/ee/learning/domain/security/PasswordProvider.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package za.co.ee.learning.domain.security
22

3+
import za.co.ee.learning.domain.DomainResult
4+
35
interface PasswordProvider {
4-
fun encode(password: String): String
6+
fun encode(password: String): DomainResult<String>
57

68
fun matches(
79
password: String,
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package za.co.ee.learning.domain.users
22

3+
import za.co.ee.learning.domain.DomainResult
34
import java.util.UUID
45

56
data class User(
67
val id: UUID,
78
val email: String,
8-
val passwordHash: String,
9+
val passwordHash: DomainResult<String>,
910
)

http4k-example/src/main/kotlin/za/co/ee/learning/domain/users/usecases/Authenticate.kt

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package za.co.ee.learning.domain.users.usecases
22

3-
import arrow.core.Option
43
import arrow.core.left
54
import arrow.core.raise.either
65
import arrow.core.right
@@ -31,8 +30,9 @@ class Authenticate(
3130
operator fun invoke(request: AuthenticateRequest): DomainResult<AuthenticateResponse> =
3231
either {
3332
val validatedRequest = validate(request).bind()
34-
val user = findUser(validatedRequest.email).bind()
35-
val authenticatedUser = checkPassword(user, validatedRequest).bind()
33+
val userOption = userRepository.findByEmail(validatedRequest.email).bind()
34+
val user = userOption.toEither { DomainError.InvalidCredentials }.bind()
35+
val authenticatedUser = authenticateUser(user, validatedRequest).bind()
3636
createToken(authenticatedUser).bind()
3737
}
3838

@@ -56,31 +56,23 @@ class Authenticate(
5656
return request.right()
5757
}
5858

59-
private fun findUser(email: String): DomainResult<User> =
60-
either {
61-
val optUser: Option<User> = userRepository.findByEmail(email).bind()
62-
return optUser.fold(
63-
ifEmpty = { DomainError.InvalidCredentials.left() },
64-
ifSome = { user -> user.right() },
65-
)
66-
}
67-
68-
private fun checkPassword(
59+
private fun authenticateUser(
6960
user: User,
7061
validatedRequest: AuthenticateRequest,
71-
): DomainResult<User> {
72-
if (passwordProvider.matches(validatedRequest.password, user.passwordHash)) {
73-
return user.right()
62+
): DomainResult<User> = either {
63+
val passwordHashString = user.passwordHash.bind()
64+
if (passwordProvider.matches(validatedRequest.password, passwordHashString)) {
65+
user
66+
} else {
67+
raise(DomainError.InvalidCredentials)
7468
}
75-
76-
return DomainError.InvalidCredentials.left()
7769
}
7870

79-
private fun createToken(authenticatedUser: User): DomainResult<AuthenticateResponse> {
80-
val tokenInfo: TokenInfo = jwtProvider.generate(authenticatedUser)
81-
return AuthenticateResponse(
71+
private fun createToken(authenticatedUser: User): DomainResult<AuthenticateResponse> = either {
72+
val tokenInfo: TokenInfo = jwtProvider.generate(authenticatedUser).bind()
73+
AuthenticateResponse(
8274
token = tokenInfo.token,
8375
expires = tokenInfo.expires,
84-
).right()
76+
)
8577
}
8678
}

http4k-example/src/main/kotlin/za/co/ee/learning/infrastructure/database/InMemoryUserRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package za.co.ee.learning.infrastructure.database
22

3+
import arrow.core.Either
34
import arrow.core.Option
45
import arrow.core.right
6+
import za.co.ee.learning.domain.DomainError
57
import za.co.ee.learning.domain.DomainResult
68
import za.co.ee.learning.domain.users.User
79
import za.co.ee.learning.domain.users.UserRepository

http4k-example/src/main/kotlin/za/co/ee/learning/infrastructure/security/BCryptPasswordProvider.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
package za.co.ee.learning.infrastructure.security
22

3+
import arrow.core.raise.either
34
import org.mindrot.jbcrypt.BCrypt
5+
import za.co.ee.learning.domain.DomainError
6+
import za.co.ee.learning.domain.DomainResult
47
import za.co.ee.learning.domain.security.PasswordProvider
58

69
class BCryptPasswordProvider : PasswordProvider {
7-
override fun encode(password: String): String = BCrypt.hashpw(password, BCrypt.gensalt())
10+
override fun encode(password: String): DomainResult<String> =
11+
either {
12+
try {
13+
BCrypt.hashpw(password, BCrypt.gensalt())
14+
} catch (e: Exception) {
15+
raise(DomainError.ValidationError("Error encoding password: ${e.message}"))
16+
}
17+
}
818

919
override fun matches(
1020
password: String,

http4k-example/src/main/kotlin/za/co/ee/learning/infrastructure/security/DefaultJWTProvider.kt

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ package za.co.ee.learning.infrastructure.security
33
import arrow.core.raise.either
44
import com.auth0.jwt.JWT
55
import com.auth0.jwt.algorithms.Algorithm
6+
import com.auth0.jwt.exceptions.JWTCreationException
67
import com.auth0.jwt.exceptions.JWTVerificationException
78
import za.co.ee.learning.domain.DomainError
89
import za.co.ee.learning.domain.DomainResult
910
import za.co.ee.learning.domain.security.JWTProvider
1011
import za.co.ee.learning.domain.security.TokenInfo
1112
import za.co.ee.learning.domain.users.User
1213
import java.time.Instant
13-
import java.util.Date
14-
import java.util.UUID
14+
import java.util.*
1515

1616
class DefaultJWTProvider(
1717
private val secret: String,
@@ -26,24 +26,30 @@ class DefaultJWTProvider(
2626
.withIssuer(issuer)
2727
.build()
2828

29-
override fun generate(user: User): TokenInfo {
30-
val now = Instant.now()
31-
val expiresAt = now.plusSeconds(expirationSeconds)
29+
override fun generate(user: User): DomainResult<TokenInfo> =
30+
either {
31+
try {
32+
val now = Instant.now()
33+
val expiresAt = now.plusSeconds(expirationSeconds)
3234

33-
val token =
34-
JWT
35-
.create()
36-
.withIssuer(issuer)
37-
.withSubject(user.id.toString())
38-
.withIssuedAt(Date.from(now))
39-
.withExpiresAt(Date.from(expiresAt))
40-
.sign(algorithm)
35+
val token = JWT
36+
.create()
37+
.withIssuer(issuer)
38+
.withSubject(user.id.toString())
39+
.withIssuedAt(Date.from(now))
40+
.withExpiresAt(Date.from(expiresAt))
41+
.sign(algorithm)
4142

42-
return TokenInfo(
43-
token = token,
44-
expires = expiresAt.epochSecond,
45-
)
46-
}
43+
TokenInfo(
44+
token = token,
45+
expires = expiresAt.epochSecond,
46+
)
47+
} catch (e: JWTCreationException) {
48+
raise(DomainError.JWTError("Token creation error: ${e.message}"))
49+
} catch (e: Exception) {
50+
raise(DomainError.JWTError("Token creation failed: ${e.message}"))
51+
}
52+
}
4753

4854
override fun verify(jwt: String): DomainResult<UUID> =
4955
either {

http4k-example/src/test/kotlin/za/co/ee/learning/domain/users/usecases/AuthenticateTest.kt

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package za.co.ee.learning.domain.users.usecases
22

3+
import arrow.core.Either
34
import arrow.core.left
45
import arrow.core.right
56
import arrow.core.some
@@ -33,12 +34,16 @@ class AuthenticateTest :
3334
User(
3435
id = UUID.randomUUID(),
3536
email = validEmail,
36-
passwordHash = passwordHash,
37+
passwordHash = Either.Right(passwordHash),
3738
)
39+
val testToken = "jwt.token.here"
40+
val testExpires = 1234567890L
3841
val tokenInfo =
39-
TokenInfo(
40-
token = "jwt.token.here",
41-
expires = 1234567890L,
42+
Either.Right(
43+
TokenInfo(
44+
token = testToken,
45+
expires = testExpires,
46+
)
4247
)
4348

4449
beforeTest {
@@ -57,10 +62,10 @@ class AuthenticateTest :
5762

5863
val value = result.shouldBeRight()
5964
value shouldBe
60-
AuthenticateResponse(
61-
token = tokenInfo.token,
62-
expires = tokenInfo.expires,
63-
)
65+
AuthenticateResponse(
66+
token = testToken,
67+
expires = testExpires,
68+
)
6469

6570
verify { userRepository.findByEmail(validEmail) }
6671
verify { passwordProvider.matches(validPassword, passwordHash) }

http4k-example/src/test/kotlin/za/co/ee/learning/domain/users/usecases/GetUsersTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package za.co.ee.learning.domain.users.usecases
22

3+
import arrow.core.Either
34
import arrow.core.left
45
import arrow.core.right
56
import io.kotest.assertions.arrow.core.shouldBeLeft
@@ -26,13 +27,13 @@ class GetUsersTest :
2627
User(
2728
id = UUID.randomUUID(),
2829
email = "[email protected]",
29-
passwordHash = "hash1",
30+
passwordHash = Either.Right("hash1"),
3031
)
3132
val user2 =
3233
User(
3334
id = UUID.randomUUID(),
3435
email = "[email protected]",
35-
passwordHash = "hash2",
36+
passwordHash = Either.Right("hash2"),
3637
)
3738

3839
beforeTest {

http4k-example/src/test/kotlin/za/co/ee/learning/infrastructure/api/AuthenticateEndpointTest.kt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package za.co.ee.learning.infrastructure.api
22

3+
import arrow.core.Either
34
import arrow.core.left
45
import arrow.core.right
56
import arrow.core.some
@@ -36,14 +37,17 @@ class AuthenticateEndpointTest :
3637
User(
3738
id = UUID.randomUUID(),
3839
email = validEmail,
39-
passwordHash = passwordHash,
40+
passwordHash = Either.Right(passwordHash),
4041
)
42+
val testToken = "jwt.token.here"
43+
val testExpires = 1234567890L
4144
val tokenInfo =
42-
TokenInfo(
43-
token = "jwt.token.here",
44-
expires = 1234567890L,
45+
Either.Right(
46+
TokenInfo(
47+
token = testToken,
48+
expires = testExpires,
49+
)
4550
)
46-
4751
beforeTest {
4852
clearAllMocks()
4953
}
@@ -60,8 +64,8 @@ class AuthenticateEndpointTest :
6064
val response = endpoint.handler(request)
6165

6266
response.status shouldBe Status.OK
63-
response.bodyString() shouldContain "\"token\":\"${tokenInfo.token}\""
64-
response.bodyString() shouldContain "\"expires\":${tokenInfo.expires}"
67+
response.bodyString() shouldContain "\"token\":\"$testToken\""
68+
response.bodyString() shouldContain "\"expires\":$testExpires"
6569

6670
verify { userRepository.findByEmail(validEmail) }
6771
verify { passwordProvider.matches(validPassword, passwordHash) }

0 commit comments

Comments
 (0)