Can login with SSO & change Password
This commit is contained in:
@@ -98,7 +98,7 @@ fun Application.module(env: Env = PROD) {
|
|||||||
decode { values, _ ->
|
decode { values, _ ->
|
||||||
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
val id = values.singleOrNull()?.let { UUID.fromString(it) }
|
||||||
?: throw InternalError("Cannot convert $values to UUID")
|
?: throw InternalError("Cannot convert $values to UUID")
|
||||||
get<RepositoryCitizen>().findById(id) ?: throw InternalError("Citizen $values not found")
|
get<RepositoryCitizen>().findById(id, true) ?: throw InternalError("Citizen $values not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -156,8 +156,8 @@ fun Application.module(env: Env = PROD) {
|
|||||||
// trace { application.log.trace(it.buildText()) }
|
// trace { application.log.trace(it.buildText()) }
|
||||||
authenticate(optional = true) {
|
authenticate(optional = true) {
|
||||||
article(get())
|
article(get())
|
||||||
auth(get(), get())
|
auth(get(), get(), get())
|
||||||
citizen(get())
|
citizen(get(), get())
|
||||||
constitution(get())
|
constitution(get())
|
||||||
followArticle(get())
|
followArticle(get())
|
||||||
followConstitution(get())
|
followConstitution(get())
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ class Config {
|
|||||||
private var config = ConfigFactory.load()
|
private var config = ConfigFactory.load()
|
||||||
val sqlFiles = File(this::class.java.getResource("/sql").toURI())
|
val sqlFiles = File(this::class.java.getResource("/sql").toURI())
|
||||||
val envName: String = config.getString("app.envName")
|
val envName: String = config.getString("app.envName")
|
||||||
|
val domain: String = config.getString("app.domain")
|
||||||
|
|
||||||
val host: String = config.getString("db.host")
|
val host: String = config.getString("db.host")
|
||||||
var database: String = config.getString("db.database")
|
var database: String = config.getString("db.database")
|
||||||
@@ -23,11 +24,12 @@ class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
object JwtConfig {
|
object JwtConfig {
|
||||||
const val secret = "zAP5MBA4B4Ijz0MZaS48"
|
private const val secret = "zAP5MBA4B4Ijz0MZaS48"
|
||||||
private const val issuer = "dc-project.fr"
|
const val issuer = "dc-project.fr"
|
||||||
private const val validityInMs = 36_000_00 * 10 // 10 hours
|
private const val validityInMs = 3_600_000 * 10 // 10 hours
|
||||||
|
|
||||||
// TODO change to RSA512
|
// TODO change to RSA512
|
||||||
private val algorithm = Algorithm.HMAC512(secret)
|
val algorithm = Algorithm.HMAC512(secret)
|
||||||
|
|
||||||
val verifier: JWTVerifier = JWT
|
val verifier: JWTVerifier = JWT
|
||||||
.require(algorithm)
|
.require(algorithm)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package fr.dcproject
|
package fr.dcproject
|
||||||
|
|
||||||
import fr.dcproject.messages.Mailer
|
import fr.dcproject.messages.Mailer
|
||||||
|
import fr.dcproject.messages.SsoManager
|
||||||
import fr.postgresjson.connexion.Connection
|
import fr.postgresjson.connexion.Connection
|
||||||
import fr.postgresjson.connexion.Requester
|
import fr.postgresjson.connexion.Requester
|
||||||
import fr.postgresjson.migration.Migrations
|
import fr.postgresjson.migration.Migrations
|
||||||
@@ -50,4 +51,5 @@ val Module = module {
|
|||||||
single { Migrations(connection = get(), directory = config.sqlFiles) }
|
single { Migrations(connection = get(), directory = config.sqlFiles) }
|
||||||
|
|
||||||
single { Mailer(config.sendGridKey) }
|
single { Mailer(config.sendGridKey) }
|
||||||
|
single { SsoManager(get<Mailer>(), config.domain, get()) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,13 @@ import com.sendgrid.Method
|
|||||||
import com.sendgrid.Request
|
import com.sendgrid.Request
|
||||||
import com.sendgrid.SendGrid
|
import com.sendgrid.SendGrid
|
||||||
import com.sendgrid.helpers.mail.Mail
|
import com.sendgrid.helpers.mail.Mail
|
||||||
import com.sendgrid.helpers.mail.objects.Content
|
|
||||||
import com.sendgrid.helpers.mail.objects.Email
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class Mailer (
|
class Mailer (
|
||||||
private val key: String
|
private val key: String
|
||||||
) {
|
) {
|
||||||
fun sendEmail(from: String, to: String, content: String, subject: String): Boolean {
|
fun sendEmail(action: () -> Mail): Boolean {
|
||||||
val mail = Mail(
|
val mail = action()
|
||||||
Email(from),
|
|
||||||
subject,
|
|
||||||
Email(to),
|
|
||||||
Content("text/plain", content)
|
|
||||||
)
|
|
||||||
|
|
||||||
val sg = SendGrid(key)
|
val sg = SendGrid(key)
|
||||||
val request = Request()
|
val request = Request()
|
||||||
|
|||||||
41
src/main/kotlin/fr/dcproject/messages/SsoManager.kt
Normal file
41
src/main/kotlin/fr/dcproject/messages/SsoManager.kt
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
package fr.dcproject.messages
|
||||||
|
|
||||||
|
import com.sendgrid.helpers.mail.Mail
|
||||||
|
import com.sendgrid.helpers.mail.objects.Content
|
||||||
|
import com.sendgrid.helpers.mail.objects.Email
|
||||||
|
import fr.dcproject.JwtConfig
|
||||||
|
import io.ktor.http.URLBuilder
|
||||||
|
import fr.dcproject.entity.Citizen as CitizenEntity
|
||||||
|
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||||
|
|
||||||
|
class SsoManager (
|
||||||
|
private val mailer: Mailer,
|
||||||
|
private val domain: String,
|
||||||
|
private val citizenRepo: CitizenRepository
|
||||||
|
) {
|
||||||
|
fun sendMail(email: String, url: String) {
|
||||||
|
val citizen = citizenRepo.findByEmail(email) ?: error("No Citizen with this email")
|
||||||
|
mailer.sendEmail {
|
||||||
|
Mail(
|
||||||
|
Email("sso@$domain"),
|
||||||
|
"Connection",
|
||||||
|
Email(email),
|
||||||
|
Content("text/plain", generateContent(citizen, url))
|
||||||
|
).apply {
|
||||||
|
addContent(Content("text/html", generateHtmlContent(citizen, url)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateHtmlContent(citizen: CitizenEntity, url: String): String? {
|
||||||
|
val urlObject = URLBuilder(url)
|
||||||
|
urlObject.parameters.append("token", JwtConfig.makeToken(citizen.user ?: error("Citizen must have User")))
|
||||||
|
return "Click <a href=\"$urlObject\">here</a> for connect to $domain"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateContent(citizen: CitizenEntity, url: String): String {
|
||||||
|
val urlObject = URLBuilder(url)
|
||||||
|
urlObject.parameters.append("token", JwtConfig.makeToken(citizen.user ?: error("Citizen must have User")))
|
||||||
|
return "Copy this link into your browser for connect to $domain: \n$urlObject"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -30,6 +30,12 @@ class Citizen(override var requester: Requester) : RepositoryI<CitizenEntity> {
|
|||||||
.selectOne("username" to unsername)
|
.selectOne("username" to unsername)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun findByEmail(email: String): CitizenEntity? {
|
||||||
|
return requester
|
||||||
|
.getFunction("find_citizen_by_email")
|
||||||
|
.selectOne("email" to email)
|
||||||
|
}
|
||||||
|
|
||||||
fun find(
|
fun find(
|
||||||
page: Int = 1,
|
page: Int = 1,
|
||||||
limit: Int = 50,
|
limit: Int = 50,
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ class User(override var requester: Requester) : RepositoryI<UserEntity> {
|
|||||||
.selectOne("resource" to user)
|
.selectOne("resource" to user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun changePassword(user: UserEntity) {
|
||||||
|
requester
|
||||||
|
.getFunction("change_user_password")
|
||||||
|
.sendQuery("resource" to user)
|
||||||
|
}
|
||||||
|
|
||||||
class UserNotFound(override val message: String?, override val cause: Throwable?): Throwable(message, cause) {
|
class UserNotFound(override val message: String?, override val cause: Throwable?): Throwable(message, cause) {
|
||||||
constructor(id: UUID): this("No User with ID $id", null)
|
constructor(id: UUID): this("No User with ID $id", null)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,13 +3,19 @@ package fr.dcproject.routes
|
|||||||
import com.fasterxml.jackson.databind.exc.MismatchedInputException
|
import com.fasterxml.jackson.databind.exc.MismatchedInputException
|
||||||
import fr.dcproject.JwtConfig
|
import fr.dcproject.JwtConfig
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.User
|
||||||
|
import fr.dcproject.messages.SsoManager
|
||||||
|
import fr.dcproject.routes.AuthPaths.LoginRequest
|
||||||
|
import fr.dcproject.routes.AuthPaths.RegisterRequest
|
||||||
|
import fr.dcproject.routes.AuthPaths.SsoRequest
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
import io.ktor.auth.UserPasswordCredential
|
import io.ktor.auth.UserPasswordCredential
|
||||||
import io.ktor.features.BadRequestException
|
import io.ktor.features.BadRequestException
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.ktor.locations.Location
|
import io.ktor.locations.Location
|
||||||
import io.ktor.locations.post
|
import io.ktor.locations.post
|
||||||
import io.ktor.request.receive
|
import io.ktor.request.receive
|
||||||
|
import io.ktor.response.respond
|
||||||
import io.ktor.response.respondText
|
import io.ktor.response.respondText
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import io.ktor.util.KtorExperimentalAPI
|
import io.ktor.util.KtorExperimentalAPI
|
||||||
@@ -21,12 +27,19 @@ import fr.dcproject.repository.User as UserRepository
|
|||||||
object AuthPaths {
|
object AuthPaths {
|
||||||
@Location("/login") class LoginRequest
|
@Location("/login") class LoginRequest
|
||||||
@Location("/register") class RegisterRequest
|
@Location("/register") class RegisterRequest
|
||||||
|
@Location("/sso") class SsoRequest {
|
||||||
|
data class Content(val email: String, val url: String)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
@KtorExperimentalLocationsAPI
|
||||||
@KtorExperimentalAPI
|
@KtorExperimentalAPI
|
||||||
fun Route.auth(userRepo: UserRepository, citizenRepo: CitizenRepository) {
|
fun Route.auth(
|
||||||
post <AuthPaths.LoginRequest> {
|
userRepo: UserRepository,
|
||||||
|
citizenRepo: CitizenRepository,
|
||||||
|
ssoManager: SsoManager
|
||||||
|
) {
|
||||||
|
post <LoginRequest> {
|
||||||
try {
|
try {
|
||||||
val credentials = call.receive<UserPasswordCredential>()
|
val credentials = call.receive<UserPasswordCredential>()
|
||||||
val user = userRepo.findByCredentials(credentials) ?: throw BadRequestException("Username not exist or password is wrong")
|
val user = userRepo.findByCredentials(credentials) ?: throw BadRequestException("Username not exist or password is wrong")
|
||||||
@@ -36,11 +49,18 @@ fun Route.auth(userRepo: UserRepository, citizenRepo: CitizenRepository) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
post <AuthPaths.RegisterRequest> {
|
post <RegisterRequest> {
|
||||||
val citizen = call.receive<CitizenEntity>()
|
val citizen = call.receive<CitizenEntity>()
|
||||||
citizen.user?.roles = listOf(User.Roles.ROLE_USER)
|
citizen.user?.roles = listOf(User.Roles.ROLE_USER)
|
||||||
val created = citizenRepo.insertWithUser(citizen)?.user ?: throw BadRequestException("Bad request")
|
val created = citizenRepo.insertWithUser(citizen)?.user ?: throw BadRequestException("Bad request")
|
||||||
|
|
||||||
call.respondText(JwtConfig.makeToken(created))
|
call.respondText(JwtConfig.makeToken(created))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
post<SsoRequest> {
|
||||||
|
val content = call.receive<SsoRequest.Content>()
|
||||||
|
ssoManager.sendMail(content.email, content.url)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.NoContent)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,46 +2,71 @@ package fr.dcproject.routes
|
|||||||
|
|
||||||
import fr.dcproject.citizen
|
import fr.dcproject.citizen
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
|
import fr.dcproject.routes.CitizenPaths.ChangePasswordCitizenRequest
|
||||||
|
import fr.dcproject.routes.CitizenPaths.CitizenRequest
|
||||||
|
import fr.dcproject.routes.CitizenPaths.CitizensRequest
|
||||||
|
import fr.dcproject.routes.CitizenPaths.CurrentCitizenRequest
|
||||||
|
import fr.dcproject.security.voter.CitizenVoter.Action.CHANGE_PASSWORD
|
||||||
import fr.dcproject.security.voter.CitizenVoter.Action.VIEW
|
import fr.dcproject.security.voter.CitizenVoter.Action.VIEW
|
||||||
import fr.dcproject.security.voter.assertCan
|
import fr.dcproject.security.voter.assertCan
|
||||||
import fr.postgresjson.repository.RepositoryI
|
import fr.postgresjson.repository.RepositoryI.Direction
|
||||||
import io.ktor.application.call
|
import io.ktor.application.call
|
||||||
|
import io.ktor.http.HttpStatusCode
|
||||||
import io.ktor.locations.KtorExperimentalLocationsAPI
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
import io.ktor.locations.Location
|
import io.ktor.locations.Location
|
||||||
import io.ktor.locations.get
|
import io.ktor.locations.get
|
||||||
|
import io.ktor.locations.put
|
||||||
|
import io.ktor.request.receive
|
||||||
import io.ktor.response.respond
|
import io.ktor.response.respond
|
||||||
import io.ktor.routing.Route
|
import io.ktor.routing.Route
|
||||||
import fr.dcproject.repository.Citizen as CitizenRepository
|
import fr.dcproject.repository.Citizen as CitizenRepository
|
||||||
|
import fr.dcproject.repository.User as UserRepository
|
||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
@KtorExperimentalLocationsAPI
|
||||||
object CitizenPaths {
|
object CitizenPaths {
|
||||||
@Location("/citizens") class CitizensRequest(page: Int = 1, limit: Int = 50, val sort: String? = null, val direction: RepositoryI.Direction? = null, val search: String? = null) {
|
@Location("/citizens") class CitizensRequest(page: Int = 1, limit: Int = 50, val sort: String? = null, val direction: Direction? = null, val search: String? = null) {
|
||||||
val page: Int = if (page < 1) 1 else page
|
val page: Int = if (page < 1) 1 else page
|
||||||
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
val limit: Int = if (limit > 50) 50 else if (limit < 1) 1 else limit
|
||||||
}
|
}
|
||||||
@Location("/citizens/{citizen}") class CitizenRequest(val citizen: Citizen)
|
@Location("/citizens/{citizen}") class CitizenRequest(val citizen: Citizen)
|
||||||
@Location("/citizens/current") class CurrentCitizenRequest
|
@Location("/citizens/current") class CurrentCitizenRequest
|
||||||
@Location("/citizens/{citizen}/follows/articles") class CitizenFollowArticleRequest(val citizen: Citizen)
|
@Location("/citizens/{citizen}/password/change") class ChangePasswordCitizenRequest(val citizen: Citizen) {
|
||||||
@Location("/citizens/{citizen}/follows/constitutions") class CitizenFollowConstitutionRequest(val citizen: Citizen)
|
data class Content(val password: String)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@KtorExperimentalLocationsAPI
|
@KtorExperimentalLocationsAPI
|
||||||
fun Route.citizen(repo: CitizenRepository) {
|
fun Route.citizen(
|
||||||
get<CitizenPaths.CitizensRequest> {
|
repo: CitizenRepository,
|
||||||
|
userRepository: UserRepository
|
||||||
|
) {
|
||||||
|
get<CitizensRequest> {
|
||||||
val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
|
val citizens = repo.find(it.page, it.limit, it.sort, it.direction, it.search)
|
||||||
assertCan(VIEW, citizens.result)
|
assertCan(VIEW, citizens.result)
|
||||||
call.respond(citizens)
|
call.respond(citizens)
|
||||||
}
|
}
|
||||||
|
|
||||||
get<CitizenPaths.CitizenRequest> {
|
get<CitizenRequest> {
|
||||||
assertCan(VIEW, it.citizen)
|
assertCan(VIEW, it.citizen)
|
||||||
|
|
||||||
call.respond(it.citizen)
|
call.respond(it.citizen)
|
||||||
}
|
}
|
||||||
|
|
||||||
get<CitizenPaths.CurrentCitizenRequest> {
|
get<CurrentCitizenRequest> {
|
||||||
assertCan(VIEW, citizen)
|
assertCan(VIEW, citizen)
|
||||||
|
|
||||||
call.respond(citizen)
|
call.respond(citizen)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
put<ChangePasswordCitizenRequest> {
|
||||||
|
assertCan(CHANGE_PASSWORD, it.citizen)
|
||||||
|
val content = call.receive<ChangePasswordCitizenRequest.Content>()
|
||||||
|
|
||||||
|
val user = it.citizen.user ?: error("Citizen must have User")
|
||||||
|
|
||||||
|
user.plainPassword = content.password
|
||||||
|
userRepository.changePassword(user)
|
||||||
|
|
||||||
|
call.respond(HttpStatusCode.Created)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,19 +3,24 @@ package fr.dcproject.security.voter
|
|||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.User
|
||||||
import io.ktor.application.ApplicationCall
|
import io.ktor.application.ApplicationCall
|
||||||
|
import io.ktor.locations.KtorExperimentalLocationsAPI
|
||||||
|
|
||||||
|
@KtorExperimentalLocationsAPI
|
||||||
class CitizenVoter: Voter {
|
class CitizenVoter: Voter {
|
||||||
enum class Action: ActionI {
|
enum class Action: ActionI {
|
||||||
CREATE,
|
CREATE,
|
||||||
UPDATE,
|
UPDATE,
|
||||||
VIEW,
|
VIEW,
|
||||||
DELETE
|
DELETE,
|
||||||
|
CHANGE_PASSWORD
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
override fun supports(action: ActionI, call: ApplicationCall, subject: Any?): Boolean {
|
||||||
return (action is Action)
|
return (action is Action)
|
||||||
&&
|
&& (
|
||||||
(subject is List<*> || subject is Citizen?)
|
subject is List<*> ||
|
||||||
|
subject is Citizen?
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
override fun vote(action: ActionI, call: ApplicationCall, subject: Any?): Vote {
|
||||||
@@ -52,6 +57,15 @@ class CitizenVoter: Voter {
|
|||||||
return Vote.GRANTED
|
return Vote.GRANTED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action == Action.CHANGE_PASSWORD && user != null && subject is Citizen) {
|
||||||
|
val userToChange = subject.user ?: error("Citizen must have User")
|
||||||
|
return if (user.id == userToChange.id) {
|
||||||
|
Vote.GRANTED
|
||||||
|
} else {
|
||||||
|
Vote.ABSTAIN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (action is Action) {
|
if (action is Action) {
|
||||||
return Vote.DENIED
|
return Vote.DENIED
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ ktor {
|
|||||||
|
|
||||||
app {
|
app {
|
||||||
envName = prod
|
envName = prod
|
||||||
|
domain = dc-project.fr
|
||||||
}
|
}
|
||||||
|
|
||||||
db {
|
db {
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
create or replace function find_citizen_by_email(_email text, out resource json) language plpgsql as
|
||||||
|
$$
|
||||||
|
begin
|
||||||
|
select to_json(t) into resource
|
||||||
|
from (
|
||||||
|
select
|
||||||
|
z.*,
|
||||||
|
find_user_by_id(z.user_id) as "user"
|
||||||
|
from citizen as z
|
||||||
|
where z.email = _email
|
||||||
|
) as t;
|
||||||
|
end;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- drop function if exists find_citizen_by_email(text, out json);
|
||||||
@@ -19,7 +19,7 @@ create table citizen
|
|||||||
user_id uuid not null references "user" (id) unique,
|
user_id uuid not null references "user" (id) unique,
|
||||||
vote_anonymous boolean default true not null,
|
vote_anonymous boolean default true not null,
|
||||||
follow_anonymous boolean default true not null,
|
follow_anonymous boolean default true not null,
|
||||||
email text not null check ( email ~* '.+@.+\..+' )
|
email text not null check ( email ~* '.+@.+\..+' ) unique
|
||||||
);
|
);
|
||||||
|
|
||||||
create table workgroup
|
create table workgroup
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import com.sendgrid.helpers.mail.Mail
|
||||||
|
import com.sendgrid.helpers.mail.objects.Content
|
||||||
|
import com.sendgrid.helpers.mail.objects.Email
|
||||||
import fr.dcproject.Env
|
import fr.dcproject.Env
|
||||||
import fr.dcproject.messages.Mailer
|
import fr.dcproject.messages.Mailer
|
||||||
import fr.dcproject.module
|
import fr.dcproject.module
|
||||||
@@ -17,12 +20,16 @@ class MailerTest: KoinTest, AutoCloseKoinTest() {
|
|||||||
@Test
|
@Test
|
||||||
fun `can be send an email`() {
|
fun `can be send an email`() {
|
||||||
withTestApplication({ module(Env.TEST) }) {
|
withTestApplication({ module(Env.TEST) }) {
|
||||||
get<Mailer>().sendEmail(
|
get<Mailer>().sendEmail {
|
||||||
"reset-password@dc-project.fr",
|
Mail(
|
||||||
"fabrice.lecomte.be@gmail.com",
|
Email("sso@dc-project.fr"),
|
||||||
"Email Work !",
|
"Test",
|
||||||
"Test"
|
Email("fabrice.lecomte.be@gmail.com"),
|
||||||
)
|
Content("text/plain", "Email Work !")
|
||||||
|
).apply {
|
||||||
|
addContent(Content("text/html", "Email <b>Work</b> !"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package feature
|
package feature
|
||||||
|
|
||||||
import com.auth0.jwt.JWT
|
import com.auth0.jwt.JWT
|
||||||
import com.auth0.jwt.algorithms.Algorithm
|
|
||||||
import fr.dcproject.JwtConfig
|
import fr.dcproject.JwtConfig
|
||||||
import fr.dcproject.entity.Citizen
|
import fr.dcproject.entity.Citizen
|
||||||
import fr.dcproject.entity.User
|
import fr.dcproject.entity.User
|
||||||
@@ -31,7 +30,7 @@ class KtorServerAuthSteps: En, KoinTest {
|
|||||||
val citizen = Citizen(
|
val citizen = Citizen(
|
||||||
id = UUID.fromString(data["id"]),
|
id = UUID.fromString(data["id"]),
|
||||||
name = Citizen.Name(data["firstName"], data["lastName"]),
|
name = Citizen.Name(data["firstName"], data["lastName"]),
|
||||||
email = ((data["firstName"] + "-" + data["lastName"]).toLowerCase()) + "@gmail.com",
|
email = data["email"] ?: ((data["firstName"] + "-" + data["lastName"]).toLowerCase()) + "@dc-project.com",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = user
|
user = user
|
||||||
)
|
)
|
||||||
@@ -44,7 +43,7 @@ class KtorServerAuthSteps: En, KoinTest {
|
|||||||
val jwtAsString: String = JWT.create()
|
val jwtAsString: String = JWT.create()
|
||||||
.withIssuer("dc-project.fr")
|
.withIssuer("dc-project.fr")
|
||||||
.withClaim("id", id)
|
.withClaim("id", id)
|
||||||
.sign(Algorithm.HMAC512(JwtConfig.secret))
|
.sign(JwtConfig.algorithm)
|
||||||
|
|
||||||
val user = User(
|
val user = User(
|
||||||
id = UUID.fromString(id),
|
id = UUID.fromString(id),
|
||||||
@@ -54,7 +53,7 @@ class KtorServerAuthSteps: En, KoinTest {
|
|||||||
val citizen = Citizen(
|
val citizen = Citizen(
|
||||||
id = UUID.fromString(id),
|
id = UUID.fromString(id),
|
||||||
name = Citizen.Name(firstName, lastName),
|
name = Citizen.Name(firstName, lastName),
|
||||||
email = ("$firstName-$lastName".toLowerCase())+"@gmail.com",
|
email = ("$firstName-$lastName".toLowerCase())+"@dc-project.fr",
|
||||||
birthday = DateTime.now(),
|
birthday = DateTime.now(),
|
||||||
user = user
|
user = user
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -18,3 +18,28 @@ Feature: citizens routes
|
|||||||
Then the response status code should be 200
|
Then the response status code should be 200
|
||||||
And the response should contain object:
|
And the response should contain object:
|
||||||
| id | 64b7b379-2298-43ec-b428-ba134930cabd |
|
| id | 64b7b379-2298-43ec-b428-ba134930cabd |
|
||||||
|
|
||||||
|
Scenario: Can be connect with SSO
|
||||||
|
Given I have citizen:
|
||||||
|
| id | c606110c-ff0e-4d09-a79e-74632d7bf7bd |
|
||||||
|
| firstName | John |
|
||||||
|
| lastName | Doe |
|
||||||
|
| email | fabrice.lecomte.be@gmail.com |
|
||||||
|
When I send a POST request to "/sso" with body:
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"url": "https://dc-project.fr/password/reset",
|
||||||
|
"email": "fabrice.lecomte.be@gmail.com"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
Then the response status code should be 204
|
||||||
|
|
||||||
|
Scenario: Can be change my password
|
||||||
|
Given I am authenticated as Joe Patate with id "c211dca6-aa21-45c2-95ba-c7f2179ee37e"
|
||||||
|
When I send a PUT request to "/citizens/c211dca6-aa21-45c2-95ba-c7f2179ee37e/password/change" with body:
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"password": "qwerty"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
Then the response status code should be 201
|
||||||
|
|||||||
Reference in New Issue
Block a user