diff --git a/extopy-backend/build.gradle.kts b/extopy-backend/build.gradle.kts index e8c6d66..cfb5e7a 100644 --- a/extopy-backend/build.gradle.kts +++ b/extopy-backend/build.gradle.kts @@ -52,7 +52,7 @@ kotlin { val ktorVersion = "2.3.9" val koinVersion = "3.5.0" val logbackVersion = "0.9.30" - val ktorxVersion = "2.2.4" + val ktorxVersion = "2.3.0" sourceSets { val commonMain by getting { @@ -87,8 +87,6 @@ kotlin { implementation("me.nathanfallet.ktorx:ktor-i18n-freemarker:$ktorxVersion") implementation("me.nathanfallet.ktorx:ktor-routers:$ktorxVersion") implementation("me.nathanfallet.ktorx:ktor-routers-locale:$ktorxVersion") - implementation("me.nathanfallet.ktorx:ktor-routers-auth:$ktorxVersion") - implementation("me.nathanfallet.ktorx:ktor-routers-auth-locale:$ktorxVersion") implementation("me.nathanfallet.ktorx:ktor-sentry:$ktorxVersion") implementation("me.nathanfallet.cloudflare:cloudflare-api-client:4.2.3") diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/AuthController.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/AuthController.kt index faba455..b63e088 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/AuthController.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/AuthController.kt @@ -1,90 +1,125 @@ package me.nathanfallet.extopy.controllers.auth +import io.ktor.http.* import io.ktor.server.application.* -import me.nathanfallet.extopy.models.auth.LoginPayload -import me.nathanfallet.extopy.models.auth.RegisterCodePayload -import me.nathanfallet.extopy.models.auth.RegisterPayload -import me.nathanfallet.ktorx.controllers.auth.AbstractAuthWithCodeController -import me.nathanfallet.ktorx.models.annotations.* -import me.nathanfallet.ktorx.models.auth.ClientForUser -import me.nathanfallet.ktorx.usecases.auth.* +import me.nathanfallet.extopy.models.application.Client +import me.nathanfallet.extopy.models.application.Email +import me.nathanfallet.extopy.models.auth.* +import me.nathanfallet.extopy.models.users.User +import me.nathanfallet.extopy.usecases.application.ICreateCodeInEmailUseCase +import me.nathanfallet.extopy.usecases.application.IDeleteCodeInEmailUseCase +import me.nathanfallet.extopy.usecases.application.IGetCodeInEmailUseCase +import me.nathanfallet.extopy.usecases.auth.* +import me.nathanfallet.ktorx.models.exceptions.ControllerException +import me.nathanfallet.ktorx.models.responses.RedirectResponse +import me.nathanfallet.ktorx.usecases.localization.IGetLocaleForCallUseCase import me.nathanfallet.ktorx.usecases.users.IRequireUserForCallUseCase import me.nathanfallet.usecases.auth.AuthRequest import me.nathanfallet.usecases.auth.AuthToken +import me.nathanfallet.usecases.emails.ISendEmailUseCase +import me.nathanfallet.usecases.localization.ITranslateUseCase +import me.nathanfallet.usecases.models.get.IGetModelSuspendUseCase class AuthController( - loginUseCase: ILoginUseCase, - registerUseCase: IRegisterUseCase, - createSessionForUserUseCase: ICreateSessionForUserUseCase, - setSessionForCallUseCase: ISetSessionForCallUseCase, - createCodeRegisterUseCase: ICreateCodeRegisterUseCase, - getCodeRegisterUseCase: IGetCodeRegisterUseCase, - deleteCodeRegisterUseCase: IDeleteCodeRegisterUseCase, - requireUserForCallUseCase: IRequireUserForCallUseCase, - getClientUseCase: IGetClientUseCase, - getAuthCodeUseCase: IGetAuthCodeUseCase, - createAuthCodeUseCase: ICreateAuthCodeUseCase, - deleteAuthCodeUseCase: IDeleteAuthCodeUseCase, - generateAuthTokenUseCase: IGenerateAuthTokenUseCase, -) : AbstractAuthWithCodeController( - loginUseCase, - registerUseCase, - createSessionForUserUseCase, - setSessionForCallUseCase, - createCodeRegisterUseCase, - getCodeRegisterUseCase, - deleteCodeRegisterUseCase, - requireUserForCallUseCase, - getClientUseCase, - getAuthCodeUseCase, - createAuthCodeUseCase, - deleteAuthCodeUseCase, - generateAuthTokenUseCase, -) { + private val loginUseCase: ILoginUseCase, + private val registerUseCase: IRegisterUseCase, + private val setSessionForCallUseCase: ISetSessionForCallUseCase, + private val clearSessionForCallUseCase: IClearSessionForCallUseCase, + private val requireUserForCallUseCase: IRequireUserForCallUseCase, + private val getClientUseCase: IGetModelSuspendUseCase, + private val getAuthCodeUseCase: IGetAuthCodeUseCase, + private val createAuthCodeUseCase: ICreateAuthCodeUseCase, + private val deleteAuthCodeUseCase: IDeleteAuthCodeUseCase, + private val generateAuthTokenUseCase: IGenerateAuthTokenUseCase, + private val createCodeInEmailUseCase: ICreateCodeInEmailUseCase, + private val getCodeInEmailUseCase: IGetCodeInEmailUseCase, + private val deleteCodeInEmailUseCase: IDeleteCodeInEmailUseCase, + private val sendEmailUseCase: ISendEmailUseCase, + private val getLocaleForCallUseCase: IGetLocaleForCallUseCase, + private val translateUseCase: ITranslateUseCase, +) : IAuthController { - @TemplateMapping("auth/login.ftl") - @LoginPath - override suspend fun login(call: ApplicationCall, @Payload payload: LoginPayload) { - super.login(call, payload) + override fun login() {} + + override suspend fun login(call: ApplicationCall, payload: LoginPayload, redirect: String?): RedirectResponse { + val user = loginUseCase(payload) ?: throw ControllerException( + HttpStatusCode.Unauthorized, "auth_invalid_credentials" + ) + setSessionForCallUseCase(call, SessionPayload(user.id)) + return RedirectResponse(redirect ?: "/") + } + + override suspend fun logout(call: ApplicationCall, redirect: String?): RedirectResponse { + clearSessionForCallUseCase(call) + return RedirectResponse(redirect ?: "/") } - @TemplateMapping("auth/register.ftl") - @RegisterPath - override suspend fun register(call: ApplicationCall, @Payload payload: RegisterPayload) { - super.register(call, payload) + override fun register() {} + + override suspend fun register(call: ApplicationCall, payload: RegisterPayload): Map { + val code = createCodeInEmailUseCase(payload.email) ?: throw ControllerException( + HttpStatusCode.BadRequest, "auth_register_email_taken" + ) + val locale = getLocaleForCallUseCase(call) + sendEmailUseCase( + Email( + translateUseCase(locale, "auth_register_email_title"), + translateUseCase(locale, "auth_register_email_body", listOf(code.code)) + ), + listOf(payload.email) + ) + return mapOf("success" to "auth_register_code_created") } - @TemplateMapping("auth/register.ftl") - @RegisterCodePath - override suspend fun register(call: ApplicationCall, code: String): RegisterPayload { - return super.register(call, code) + override suspend fun registerCode(call: ApplicationCall, code: String): RegisterPayload { + val codeInEmail = getCodeInEmailUseCase(code) ?: throw ControllerException( + HttpStatusCode.NotFound, "auth_code_invalid" + ) + return RegisterPayload(codeInEmail.email) } - @TemplateMapping("auth/register.ftl") - @RegisterCodeRedirectPath - override suspend fun register(call: ApplicationCall, code: String, @Payload payload: RegisterCodePayload) { - super.register(call, code, payload) + override suspend fun registerCode( + call: ApplicationCall, + code: String, + payload: RegisterCodePayload, + redirect: String?, + ): RedirectResponse { + val user = registerUseCase(code, payload) ?: throw ControllerException( + HttpStatusCode.InternalServerError, "error_internal" + ) + setSessionForCallUseCase(call, SessionPayload(user.id)) + deleteCodeInEmailUseCase(code) + return RedirectResponse(redirect ?: "/") } - @TemplateMapping("auth/authorize.ftl") - @AuthorizePath override suspend fun authorize(call: ApplicationCall, clientId: String?): ClientForUser { - return super.authorize(call, clientId) + val user = requireUserForCallUseCase(call) + val client = clientId?.let { getClientUseCase(it) } ?: throw ControllerException( + HttpStatusCode.BadRequest, "auth_invalid_client" + ) + return ClientForUser(client, user as User) } - @TemplateMapping("auth/authorize.ftl") - @AuthorizeRedirectPath - override suspend fun authorize(call: ApplicationCall, client: ClientForUser): String { - return super.authorize(call, client) + override suspend fun authorizeRedirect(call: ApplicationCall, clientId: String?): Map { + val client = authorize(call, clientId) + val code = createAuthCodeUseCase(client) ?: throw ControllerException( + HttpStatusCode.InternalServerError, "error_internal" + ) + return mapOf( + "redirect" to client.client.redirectUri.replace("{code}", code) + ) } - @APIMapping - @CreateModelPath("/token") - @DocumentedTag("Auth") - @DocumentedError(400, "auth_invalid_code") - @DocumentedError(500, "error_internal") - override suspend fun token(call: ApplicationCall, @Payload request: AuthRequest): AuthToken { - return super.token(call, request) + override suspend fun token(payload: AuthRequest): AuthToken { + val client = getAuthCodeUseCase(payload.code)?.takeIf { + it.client.clientId == payload.clientId && it.client.clientSecret == payload.clientSecret + } ?: throw ControllerException( + HttpStatusCode.BadRequest, "auth_invalid_code" + ) + if (!deleteAuthCodeUseCase(payload.code)) throw ControllerException( + HttpStatusCode.InternalServerError, "error_internal" + ) + return generateAuthTokenUseCase(client) } } diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/AuthRouter.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/AuthRouter.kt index 9e1d94e..f66e28a 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/AuthRouter.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/AuthRouter.kt @@ -1,36 +1,28 @@ package me.nathanfallet.extopy.controllers.auth import io.ktor.server.freemarker.* -import io.ktor.util.reflect.* -import me.nathanfallet.extopy.models.auth.LoginPayload -import me.nathanfallet.extopy.models.auth.RegisterCodePayload -import me.nathanfallet.extopy.models.auth.RegisterPayload -import me.nathanfallet.ktorx.controllers.auth.IAuthWithCodeController import me.nathanfallet.ktorx.routers.api.APIUnitRouter -import me.nathanfallet.ktorx.routers.auth.LocalizedAuthWithCodeTemplateRouter import me.nathanfallet.ktorx.routers.concat.ConcatUnitRouter +import me.nathanfallet.ktorx.routers.templates.LocalizedTemplateUnitRouter import me.nathanfallet.ktorx.usecases.localization.IGetLocaleForCallUseCase class AuthRouter( - controller: IAuthWithCodeController, + controller: IAuthController, getLocaleForCallUseCase: IGetLocaleForCallUseCase, ) : ConcatUnitRouter( - LocalizedAuthWithCodeTemplateRouter( - typeInfo(), - typeInfo(), - typeInfo(), + LocalizedTemplateUnitRouter( controller, - AuthController::class, + IAuthController::class, { template, model -> respondTemplate(template, model) }, getLocaleForCallUseCase, - null, - "/auth/login?redirect={path}", - "auth/redirect.ftl", + errorTemplate = null, + redirectUnauthorizedToUrl = "/auth/login?redirect={path}", + route = "auth", ), APIUnitRouter( controller, - AuthController::class, - route = "/auth", + IAuthController::class, + route = "auth", prefix = "/api/v1" ) ) diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/IAuthController.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/IAuthController.kt new file mode 100644 index 0000000..a814582 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/controllers/auth/IAuthController.kt @@ -0,0 +1,68 @@ +package me.nathanfallet.extopy.controllers.auth + +import io.ktor.server.application.* +import me.nathanfallet.extopy.models.auth.ClientForUser +import me.nathanfallet.extopy.models.auth.LoginPayload +import me.nathanfallet.extopy.models.auth.RegisterCodePayload +import me.nathanfallet.extopy.models.auth.RegisterPayload +import me.nathanfallet.ktorx.controllers.IUnitController +import me.nathanfallet.ktorx.models.annotations.* +import me.nathanfallet.ktorx.models.responses.RedirectResponse +import me.nathanfallet.usecases.auth.AuthRequest +import me.nathanfallet.usecases.auth.AuthToken + +interface IAuthController : IUnitController { + + @TemplateMapping("auth/login.ftl") + @Path("GET", "/login") + fun login() + + @TemplateMapping("auth/login.ftl") + @Path("POST", "/login") + suspend fun login( + call: ApplicationCall, + @Payload payload: LoginPayload, + @QueryParameter redirect: String?, + ): RedirectResponse + + @TemplateMapping("auth/login.ftl") + @Path("GET", "/logout") + suspend fun logout(call: ApplicationCall, @QueryParameter redirect: String?): RedirectResponse + + @TemplateMapping("auth/register.ftl") + @Path("GET", "/register") + fun register() + + @TemplateMapping("auth/register.ftl") + @Path("POST", "/register") + suspend fun register(call: ApplicationCall, @Payload payload: RegisterPayload): Map + + @TemplateMapping("auth/register.ftl") + @Path("GET", "/register/{code}") + suspend fun registerCode(call: ApplicationCall, @PathParameter code: String): RegisterPayload + + @TemplateMapping("auth/register.ftl") + @Path("POST", "/register/{code}") + suspend fun registerCode( + call: ApplicationCall, + @PathParameter code: String, + @Payload payload: RegisterCodePayload, + @QueryParameter redirect: String?, + ): RedirectResponse + + @TemplateMapping("auth/authorize.ftl") + @Path("GET", "/authorize") + suspend fun authorize(call: ApplicationCall, @QueryParameter clientId: String?): ClientForUser + + @TemplateMapping("auth/redirect.ftl") + @Path("POST", "/authorize") + suspend fun authorizeRedirect(call: ApplicationCall, @QueryParameter clientId: String?): Map + + @APIMapping + @Path("POST", "/token") + @DocumentedTag("Auth") + @DocumentedError(400, "auth_invalid_code") + @DocumentedError(500, "error_internal") + suspend fun token(@Payload payload: AuthRequest): AuthToken + +} diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt index 4cd5cfc..034acd6 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/plugins/Koin.kt @@ -3,6 +3,7 @@ package me.nathanfallet.extopy.plugins import io.ktor.server.application.* import me.nathanfallet.extopy.controllers.auth.AuthController import me.nathanfallet.extopy.controllers.auth.AuthRouter +import me.nathanfallet.extopy.controllers.auth.IAuthController import me.nathanfallet.extopy.controllers.notifications.NotificationsRouter import me.nathanfallet.extopy.controllers.posts.* import me.nathanfallet.extopy.controllers.timelines.ITimelinesController @@ -21,9 +22,6 @@ import me.nathanfallet.extopy.database.users.ClientsInUsersDatabaseRepository import me.nathanfallet.extopy.database.users.FollowersInUsersDatabaseRepository import me.nathanfallet.extopy.database.users.UsersDatabaseRepository import me.nathanfallet.extopy.models.application.Client -import me.nathanfallet.extopy.models.auth.LoginPayload -import me.nathanfallet.extopy.models.auth.RegisterCodePayload -import me.nathanfallet.extopy.models.auth.RegisterPayload import me.nathanfallet.extopy.models.posts.LikeInPost import me.nathanfallet.extopy.models.posts.Post import me.nathanfallet.extopy.models.posts.PostPayload @@ -40,7 +38,7 @@ import me.nathanfallet.extopy.services.emails.EmailsService import me.nathanfallet.extopy.services.emails.IEmailsService import me.nathanfallet.extopy.services.jwt.IJWTService import me.nathanfallet.extopy.services.jwt.JWTService -import me.nathanfallet.extopy.usecases.application.SendEmailUseCase +import me.nathanfallet.extopy.usecases.application.* import me.nathanfallet.extopy.usecases.auth.* import me.nathanfallet.extopy.usecases.posts.* import me.nathanfallet.extopy.usecases.timelines.GetTimelineByIdUseCase @@ -49,10 +47,8 @@ import me.nathanfallet.extopy.usecases.timelines.IGetTimelineByIdUseCase import me.nathanfallet.extopy.usecases.timelines.IGetTimelinePostsUseCase import me.nathanfallet.extopy.usecases.users.* import me.nathanfallet.i18n.usecases.localization.TranslateUseCase -import me.nathanfallet.ktorx.controllers.auth.IAuthWithCodeController import me.nathanfallet.ktorx.database.sessions.SessionsDatabaseRepository import me.nathanfallet.ktorx.repositories.sessions.ISessionsRepository -import me.nathanfallet.ktorx.usecases.auth.* import me.nathanfallet.ktorx.usecases.localization.GetLocaleForCallUseCase import me.nathanfallet.ktorx.usecases.localization.IGetLocaleForCallUseCase import me.nathanfallet.ktorx.usecases.users.IGetUserForCallUseCase @@ -133,8 +129,12 @@ fun Application.configureKoin() { val useCaseModule = module { // Application single { SendEmailUseCase(get()) } + single { ExpireUseCase() } single { TranslateUseCase() } single { GetLocaleForCallUseCase() } + single { GetCodeInEmailUseCase(get()) } + single { CreateCodeInEmailUseCase(get(), get()) } + single { DeleteCodeInEmailUseCase(get()) } single>(named()) { GetModelFromRepositorySuspendUseCase(get(named())) } @@ -143,29 +143,15 @@ fun Application.configureKoin() { single { HashPasswordUseCase() } single { VerifyPasswordUseCase() } single { GetJWTPrincipalForCallUseCase() } - single { CreateSessionForUserUseCase() } single { GetSessionForCallUseCase() } single { SetSessionForCallUseCase() } - single> { LoginUseCase(get(), get()) } - single> { RegisterUseCase(get(), get(named())) } - single> { GetCodeRegisterUseCase(get()) } - single> { - CreateCodeRegisterUseCase( - get(), - get(), - get(), - get(), - get() - ) - } - single { DeleteCodeRegisterUseCase(get()) } - single { GetClientFromModelUseCase(get(named())) } + single { ClearSessionForCallUseCase() } + single { LoginUseCase(get(), get()) } + single { RegisterUseCase(get(), get(named())) } single { CreateAuthCodeUseCase(get()) } - single { GetAuthCodeUseCase(get(), get(), get(named())) } + single { GetAuthCodeUseCase(get(), get(named()), get(named())) } single { DeleteAuthCodeUseCase(get()) } - single { - GenerateAuthTokenUseCase(get()) - } + single { GenerateAuthTokenUseCase(get()) } // Users single { RequireUserForCallUseCase(get()) } @@ -232,13 +218,16 @@ fun Application.configureKoin() { single { WebController() } // Auth - single> { + single { AuthController( get(), get(), get(), get(), get(), + get(named()), + get(), + get(), get(), get(), get(), diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/CreateCodeInEmailUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/CreateCodeInEmailUseCase.kt new file mode 100644 index 0000000..b6d9d48 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/CreateCodeInEmailUseCase.kt @@ -0,0 +1,35 @@ +package me.nathanfallet.extopy.usecases.application + +import io.ktor.http.* +import kotlinx.datetime.Clock +import kotlinx.datetime.DateTimeUnit +import kotlinx.datetime.TimeZone +import kotlinx.datetime.plus +import me.nathanfallet.extopy.extensions.generateId +import me.nathanfallet.extopy.models.application.CodeInEmail +import me.nathanfallet.extopy.repositories.application.ICodesInEmailsRepository +import me.nathanfallet.extopy.repositories.users.IUsersRepository +import me.nathanfallet.ktorx.models.exceptions.ControllerException + +class CreateCodeInEmailUseCase( + private val codesInEmailsRepository: ICodesInEmailsRepository, + private val usersRepository: IUsersRepository, +) : ICreateCodeInEmailUseCase { + + override suspend fun invoke(input: String): CodeInEmail? { + usersRepository.getForUsernameOrEmail(input, false)?.let { + return null + } + val code = String.generateId() + val expiresAt = Clock.System.now().plus(1, DateTimeUnit.HOUR, TimeZone.currentSystemDefault()) + return try { + codesInEmailsRepository.createCodeInEmail(input, code, expiresAt) + } catch (e: Exception) { + codesInEmailsRepository.updateCodeInEmail(input, code, expiresAt).takeIf { + it + } ?: throw ControllerException(HttpStatusCode.InternalServerError, "error_internal") + CodeInEmail(input, code, expiresAt) + } + } + +} diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/DeleteCodeInEmailUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/DeleteCodeInEmailUseCase.kt new file mode 100644 index 0000000..8666508 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/DeleteCodeInEmailUseCase.kt @@ -0,0 +1,13 @@ +package me.nathanfallet.extopy.usecases.application + +import me.nathanfallet.extopy.repositories.application.ICodesInEmailsRepository + +class DeleteCodeInEmailUseCase( + private val repository: ICodesInEmailsRepository, +) : IDeleteCodeInEmailUseCase { + + override suspend fun invoke(input: String) { + repository.deleteCodeInEmail(input) + } + +} diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/ExpireUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/ExpireUseCase.kt new file mode 100644 index 0000000..a79402c --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/ExpireUseCase.kt @@ -0,0 +1,9 @@ +package me.nathanfallet.extopy.usecases.application + +import kotlinx.datetime.Instant + +class ExpireUseCase : IExpireUseCase { + + override suspend fun invoke(input: Instant) {} + +} diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/GetCodeInEmailUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/GetCodeInEmailUseCase.kt new file mode 100644 index 0000000..c76f58b --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/GetCodeInEmailUseCase.kt @@ -0,0 +1,16 @@ +package me.nathanfallet.extopy.usecases.application + +import kotlinx.datetime.Clock +import me.nathanfallet.extopy.models.application.CodeInEmail +import me.nathanfallet.extopy.repositories.application.ICodesInEmailsRepository + +class GetCodeInEmailUseCase( + private val repository: ICodesInEmailsRepository, +) : IGetCodeInEmailUseCase { + + override suspend fun invoke(input: String): CodeInEmail? = + repository.getCodeInEmail(input)?.takeIf { + it.expiresAt > Clock.System.now() + } + +} diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/ICreateCodeInEmailUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/ICreateCodeInEmailUseCase.kt new file mode 100644 index 0000000..8bb3d81 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/ICreateCodeInEmailUseCase.kt @@ -0,0 +1,6 @@ +package me.nathanfallet.extopy.usecases.application + +import me.nathanfallet.extopy.models.application.CodeInEmail +import me.nathanfallet.usecases.base.ISuspendUseCase + +interface ICreateCodeInEmailUseCase : ISuspendUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/IDeleteCodeInEmailUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/IDeleteCodeInEmailUseCase.kt new file mode 100644 index 0000000..b1400cb --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/IDeleteCodeInEmailUseCase.kt @@ -0,0 +1,5 @@ +package me.nathanfallet.extopy.usecases.application + +import me.nathanfallet.usecases.base.ISuspendUseCase + +interface IDeleteCodeInEmailUseCase : ISuspendUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/IExpireUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/IExpireUseCase.kt new file mode 100644 index 0000000..96eda29 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/IExpireUseCase.kt @@ -0,0 +1,6 @@ +package me.nathanfallet.extopy.usecases.application + +import kotlinx.datetime.Instant +import me.nathanfallet.usecases.base.ISuspendUseCase + +interface IExpireUseCase : ISuspendUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/IGetCodeInEmailUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/IGetCodeInEmailUseCase.kt new file mode 100644 index 0000000..0f1da12 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/application/IGetCodeInEmailUseCase.kt @@ -0,0 +1,6 @@ +package me.nathanfallet.extopy.usecases.application + +import me.nathanfallet.extopy.models.application.CodeInEmail +import me.nathanfallet.usecases.base.ISuspendUseCase + +interface IGetCodeInEmailUseCase : ISuspendUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ClearSessionForCallUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ClearSessionForCallUseCase.kt new file mode 100644 index 0000000..b79f7ab --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ClearSessionForCallUseCase.kt @@ -0,0 +1,11 @@ +package me.nathanfallet.extopy.usecases.auth + +import io.ktor.server.application.* +import io.ktor.server.sessions.* +import me.nathanfallet.extopy.models.auth.SessionPayload + +class ClearSessionForCallUseCase : IClearSessionForCallUseCase { + + override fun invoke(input: ApplicationCall) = input.sessions.clear() + +} diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/CreateAuthCodeUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/CreateAuthCodeUseCase.kt index 7b636f3..fd1947d 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/CreateAuthCodeUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/CreateAuthCodeUseCase.kt @@ -4,21 +4,18 @@ import kotlinx.datetime.Clock import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.TimeZone import kotlinx.datetime.plus +import me.nathanfallet.extopy.models.auth.ClientForUser import me.nathanfallet.extopy.models.users.User import me.nathanfallet.extopy.repositories.users.IClientsInUsersRepository -import me.nathanfallet.ktorx.models.auth.ClientForUser -import me.nathanfallet.ktorx.usecases.auth.ICreateAuthCodeUseCase class CreateAuthCodeUseCase( private val repository: IClientsInUsersRepository, ) : ICreateAuthCodeUseCase { - override suspend fun invoke(input: ClientForUser): String? { - return repository.create( - (input.user as User).id, - input.client.clientId, - Clock.System.now().plus(1, DateTimeUnit.HOUR, TimeZone.currentSystemDefault()) - )?.code - } + override suspend fun invoke(input: ClientForUser): String? = repository.create( + (input.user as User).id, + input.client.clientId, + Clock.System.now().plus(1, DateTimeUnit.HOUR, TimeZone.currentSystemDefault()) + )?.code } diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/CreateCodeRegisterUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/CreateCodeRegisterUseCase.kt deleted file mode 100644 index f46e4b2..0000000 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/CreateCodeRegisterUseCase.kt +++ /dev/null @@ -1,60 +0,0 @@ -package me.nathanfallet.extopy.usecases.auth - -import io.ktor.http.* -import io.ktor.server.application.* -import kotlinx.datetime.Clock -import kotlinx.datetime.DateTimeUnit -import kotlinx.datetime.TimeZone -import kotlinx.datetime.plus -import me.nathanfallet.extopy.extensions.generateId -import me.nathanfallet.extopy.models.application.CodeInEmail -import me.nathanfallet.extopy.models.application.Email -import me.nathanfallet.extopy.models.auth.RegisterPayload -import me.nathanfallet.extopy.repositories.application.ICodesInEmailsRepository -import me.nathanfallet.extopy.repositories.users.IUsersRepository -import me.nathanfallet.ktorx.models.exceptions.ControllerException -import me.nathanfallet.ktorx.usecases.auth.ICreateCodeRegisterUseCase -import me.nathanfallet.ktorx.usecases.localization.IGetLocaleForCallUseCase -import me.nathanfallet.usecases.emails.ISendEmailUseCase -import me.nathanfallet.usecases.localization.ITranslateUseCase - -class CreateCodeRegisterUseCase( - private val codesInEmailsRepository: ICodesInEmailsRepository, - private val usersRepository: IUsersRepository, - private val sendEmailUseCase: ISendEmailUseCase, - private val translateUseCase: ITranslateUseCase, - private val getLocaleForCallUseCase: IGetLocaleForCallUseCase, -) : ICreateCodeRegisterUseCase { - - override suspend fun invoke(input1: ApplicationCall, input2: RegisterPayload): String { - val code = generateCode(input2.email) ?: throw ControllerException( - HttpStatusCode.BadRequest, "auth_register_email_taken" - ) - val locale = getLocaleForCallUseCase(input1) - sendEmailUseCase( - Email( - translateUseCase(locale, "auth_register_email_title"), - translateUseCase(locale, "auth_register_email_body", listOf(code.code)) - ), - listOf(input2.email) - ) - return code.code - } - - private suspend fun generateCode(email: String): CodeInEmail? { - usersRepository.getForUsernameOrEmail(email, false)?.let { - return null - } - val code = String.generateId() - val expiresAt = Clock.System.now().plus(1, DateTimeUnit.HOUR, TimeZone.currentSystemDefault()) - return try { - codesInEmailsRepository.createCodeInEmail(email, code, expiresAt) - } catch (e: Exception) { - codesInEmailsRepository.updateCodeInEmail(email, code, expiresAt).takeIf { - it - } ?: throw ControllerException(HttpStatusCode.InternalServerError, "error_internal") - CodeInEmail(email, code, expiresAt) - } - } - -} diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/CreateSessionForUserUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/CreateSessionForUserUseCase.kt deleted file mode 100644 index b28fda0..0000000 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/CreateSessionForUserUseCase.kt +++ /dev/null @@ -1,15 +0,0 @@ -package me.nathanfallet.extopy.usecases.auth - -import me.nathanfallet.extopy.models.auth.SessionPayload -import me.nathanfallet.extopy.models.users.User -import me.nathanfallet.ktorx.usecases.auth.ICreateSessionForUserUseCase -import me.nathanfallet.usecases.auth.ISessionPayload -import me.nathanfallet.usecases.users.IUser - -class CreateSessionForUserUseCase : ICreateSessionForUserUseCase { - - override fun invoke(input: IUser): ISessionPayload { - return SessionPayload((input as User).id) - } - -} diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/DeleteAuthCodeUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/DeleteAuthCodeUseCase.kt index 7e360fc..7866c05 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/DeleteAuthCodeUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/DeleteAuthCodeUseCase.kt @@ -1,14 +1,11 @@ package me.nathanfallet.extopy.usecases.auth import me.nathanfallet.extopy.repositories.users.IClientsInUsersRepository -import me.nathanfallet.ktorx.usecases.auth.IDeleteAuthCodeUseCase class DeleteAuthCodeUseCase( private val repository: IClientsInUsersRepository, ) : IDeleteAuthCodeUseCase { - override suspend fun invoke(input: String): Boolean { - return repository.delete(input) - } + override suspend fun invoke(input: String): Boolean = repository.delete(input) } diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/DeleteCodeRegisterUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/DeleteCodeRegisterUseCase.kt deleted file mode 100644 index cfadfaf..0000000 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/DeleteCodeRegisterUseCase.kt +++ /dev/null @@ -1,15 +0,0 @@ -package me.nathanfallet.extopy.usecases.auth - -import io.ktor.server.application.* -import me.nathanfallet.extopy.repositories.application.ICodesInEmailsRepository -import me.nathanfallet.ktorx.usecases.auth.IDeleteCodeRegisterUseCase - -class DeleteCodeRegisterUseCase( - private val repository: ICodesInEmailsRepository, -) : IDeleteCodeRegisterUseCase { - - override suspend fun invoke(input1: ApplicationCall, input2: String) { - repository.deleteCodeInEmail(input2) - } - -} diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GenerateAuthTokenUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GenerateAuthTokenUseCase.kt index d85f090..2c03b9f 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GenerateAuthTokenUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GenerateAuthTokenUseCase.kt @@ -1,22 +1,17 @@ package me.nathanfallet.extopy.usecases.auth -import me.nathanfallet.extopy.models.users.User +import me.nathanfallet.extopy.models.auth.ClientForUser import me.nathanfallet.extopy.services.jwt.IJWTService -import me.nathanfallet.ktorx.models.auth.ClientForUser -import me.nathanfallet.ktorx.usecases.auth.IGenerateAuthTokenUseCase import me.nathanfallet.usecases.auth.AuthToken class GenerateAuthTokenUseCase( private val jwtService: IJWTService, ) : IGenerateAuthTokenUseCase { - override suspend fun invoke(input: ClientForUser): AuthToken { - val userId = (input.user as User).id - return AuthToken( - jwtService.generateJWT(userId, input.client.clientId, "access"), - jwtService.generateJWT(userId, input.client.clientId, "refresh"), - userId - ) - } + override suspend fun invoke(input: ClientForUser) = AuthToken( + jwtService.generateJWT(input.user.id, input.client.clientId, "access"), + jwtService.generateJWT(input.user.id, input.client.clientId, "refresh"), + input.user.id + ) } diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetAuthCodeUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetAuthCodeUseCase.kt index e1463d0..1e1241b 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetAuthCodeUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetAuthCodeUseCase.kt @@ -1,17 +1,17 @@ package me.nathanfallet.extopy.usecases.auth import kotlinx.datetime.Clock +import me.nathanfallet.extopy.models.application.Client +import me.nathanfallet.extopy.models.auth.ClientForUser import me.nathanfallet.extopy.models.users.User import me.nathanfallet.extopy.models.users.UserContext import me.nathanfallet.extopy.repositories.users.IClientsInUsersRepository -import me.nathanfallet.ktorx.models.auth.ClientForUser -import me.nathanfallet.ktorx.usecases.auth.IGetAuthCodeUseCase -import me.nathanfallet.ktorx.usecases.auth.IGetClientUseCase +import me.nathanfallet.usecases.models.get.IGetModelSuspendUseCase import me.nathanfallet.usecases.models.get.context.IGetModelWithContextSuspendUseCase class GetAuthCodeUseCase( private val repository: IClientsInUsersRepository, - private val getClientUseCase: IGetClientUseCase, + private val getClientUseCase: IGetModelSuspendUseCase, private val getUserUseCase: IGetModelWithContextSuspendUseCase, ) : IGetAuthCodeUseCase { diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetCodeRegisterUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetCodeRegisterUseCase.kt deleted file mode 100644 index 59492db..0000000 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetCodeRegisterUseCase.kt +++ /dev/null @@ -1,19 +0,0 @@ -package me.nathanfallet.extopy.usecases.auth - -import io.ktor.server.application.* -import kotlinx.datetime.Clock -import me.nathanfallet.extopy.models.auth.RegisterPayload -import me.nathanfallet.extopy.repositories.application.ICodesInEmailsRepository -import me.nathanfallet.ktorx.usecases.auth.IGetCodeRegisterUseCase - -class GetCodeRegisterUseCase( - private val repository: ICodesInEmailsRepository, -) : IGetCodeRegisterUseCase { - - override suspend fun invoke(input1: ApplicationCall, input2: String): RegisterPayload? { - return repository.getCodeInEmail(input2)?.takeIf { - it.expiresAt > Clock.System.now() - }?.let { RegisterPayload(it.email) } - } - -} diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetJWTPrincipalForCallUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetJWTPrincipalForCallUseCase.kt index d692cbe..3fabeac 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetJWTPrincipalForCallUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetJWTPrincipalForCallUseCase.kt @@ -6,8 +6,6 @@ import io.ktor.server.auth.jwt.* class GetJWTPrincipalForCallUseCase : IGetJWTPrincipalForCallUseCase { - override fun invoke(input: ApplicationCall): JWTPrincipal? { - return input.principal() - } + override fun invoke(input: ApplicationCall): JWTPrincipal? = input.principal() } diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetSessionForCallUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetSessionForCallUseCase.kt index 168c564..e3c55b8 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetSessionForCallUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/GetSessionForCallUseCase.kt @@ -3,13 +3,9 @@ package me.nathanfallet.extopy.usecases.auth import io.ktor.server.application.* import io.ktor.server.sessions.* import me.nathanfallet.extopy.models.auth.SessionPayload -import me.nathanfallet.ktorx.usecases.auth.IGetSessionForCallUseCase -import me.nathanfallet.usecases.auth.ISessionPayload class GetSessionForCallUseCase : IGetSessionForCallUseCase { - override fun invoke(input: ApplicationCall): ISessionPayload? { - return input.sessions.get() - } + override fun invoke(input: ApplicationCall): SessionPayload? = input.sessions.get() } diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/HashPasswordUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/HashPasswordUseCase.kt index b4dbba9..da661d8 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/HashPasswordUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/HashPasswordUseCase.kt @@ -4,8 +4,6 @@ import at.favre.lib.crypto.bcrypt.BCrypt class HashPasswordUseCase : IHashPasswordUseCase { - override fun invoke(input: String): String { - return BCrypt.withDefaults().hashToString(12, input.toCharArray()) - } + override fun invoke(input: String): String = BCrypt.withDefaults().hashToString(12, input.toCharArray()) } diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IClearSessionForCallUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IClearSessionForCallUseCase.kt new file mode 100644 index 0000000..0ed30cc --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IClearSessionForCallUseCase.kt @@ -0,0 +1,6 @@ +package me.nathanfallet.extopy.usecases.auth + +import io.ktor.server.application.* +import me.nathanfallet.usecases.base.IUseCase + +interface IClearSessionForCallUseCase : IUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ICreateAuthCodeUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ICreateAuthCodeUseCase.kt new file mode 100644 index 0000000..5510e55 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ICreateAuthCodeUseCase.kt @@ -0,0 +1,6 @@ +package me.nathanfallet.extopy.usecases.auth + +import me.nathanfallet.extopy.models.auth.ClientForUser +import me.nathanfallet.usecases.base.ISuspendUseCase + +interface ICreateAuthCodeUseCase : ISuspendUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IDeleteAuthCodeUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IDeleteAuthCodeUseCase.kt new file mode 100644 index 0000000..04ef46e --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IDeleteAuthCodeUseCase.kt @@ -0,0 +1,5 @@ +package me.nathanfallet.extopy.usecases.auth + +import me.nathanfallet.usecases.base.ISuspendUseCase + +interface IDeleteAuthCodeUseCase : ISuspendUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IGenerateAuthTokenUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IGenerateAuthTokenUseCase.kt new file mode 100644 index 0000000..dc57794 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IGenerateAuthTokenUseCase.kt @@ -0,0 +1,7 @@ +package me.nathanfallet.extopy.usecases.auth + +import me.nathanfallet.extopy.models.auth.ClientForUser +import me.nathanfallet.usecases.auth.AuthToken +import me.nathanfallet.usecases.base.ISuspendUseCase + +interface IGenerateAuthTokenUseCase : ISuspendUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IGetAuthCodeUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IGetAuthCodeUseCase.kt new file mode 100644 index 0000000..55390f7 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IGetAuthCodeUseCase.kt @@ -0,0 +1,6 @@ +package me.nathanfallet.extopy.usecases.auth + +import me.nathanfallet.extopy.models.auth.ClientForUser +import me.nathanfallet.usecases.base.ISuspendUseCase + +interface IGetAuthCodeUseCase : ISuspendUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IGetSessionForCallUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IGetSessionForCallUseCase.kt new file mode 100644 index 0000000..0c69c04 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IGetSessionForCallUseCase.kt @@ -0,0 +1,7 @@ +package me.nathanfallet.extopy.usecases.auth + +import io.ktor.server.application.* +import me.nathanfallet.extopy.models.auth.SessionPayload +import me.nathanfallet.usecases.base.IUseCase + +interface IGetSessionForCallUseCase : IUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ILoginUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ILoginUseCase.kt new file mode 100644 index 0000000..6150f70 --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ILoginUseCase.kt @@ -0,0 +1,7 @@ +package me.nathanfallet.extopy.usecases.auth + +import me.nathanfallet.extopy.models.auth.LoginPayload +import me.nathanfallet.extopy.models.users.User +import me.nathanfallet.usecases.base.ISuspendUseCase + +interface ILoginUseCase : ISuspendUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IRegisterUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IRegisterUseCase.kt new file mode 100644 index 0000000..a7ca73a --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/IRegisterUseCase.kt @@ -0,0 +1,7 @@ +package me.nathanfallet.extopy.usecases.auth + +import me.nathanfallet.extopy.models.auth.RegisterCodePayload +import me.nathanfallet.extopy.models.users.User +import me.nathanfallet.usecases.base.IPairSuspendUseCase + +interface IRegisterUseCase : IPairSuspendUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ISetSessionForCallUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ISetSessionForCallUseCase.kt new file mode 100644 index 0000000..0f04c7c --- /dev/null +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/ISetSessionForCallUseCase.kt @@ -0,0 +1,7 @@ +package me.nathanfallet.extopy.usecases.auth + +import io.ktor.server.application.* +import me.nathanfallet.extopy.models.auth.SessionPayload +import me.nathanfallet.usecases.base.IPairUseCase + +interface ISetSessionForCallUseCase : IPairUseCase diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/LoginUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/LoginUseCase.kt index fcac47b..9f2e701 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/LoginUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/LoginUseCase.kt @@ -1,19 +1,17 @@ package me.nathanfallet.extopy.usecases.auth import me.nathanfallet.extopy.models.auth.LoginPayload +import me.nathanfallet.extopy.models.users.User import me.nathanfallet.extopy.repositories.users.IUsersRepository -import me.nathanfallet.ktorx.usecases.auth.ILoginUseCase -import me.nathanfallet.usecases.users.IUser class LoginUseCase( private val repository: IUsersRepository, private val verifyPasswordUseCase: IVerifyPasswordUseCase, -) : ILoginUseCase { +) : ILoginUseCase { - override suspend fun invoke(input: LoginPayload): IUser? { - return repository.getForUsernameOrEmail(input.username, true)?.takeIf { + override suspend fun invoke(input: LoginPayload): User? = + repository.getForUsernameOrEmail(input.username, true)?.takeIf { verifyPasswordUseCase(input.password, it.password ?: "") } - } } diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/RegisterUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/RegisterUseCase.kt index 9d25f0a..7d04fc9 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/RegisterUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/RegisterUseCase.kt @@ -1,27 +1,23 @@ package me.nathanfallet.extopy.usecases.auth -import io.ktor.server.application.* import me.nathanfallet.extopy.models.auth.RegisterCodePayload -import me.nathanfallet.extopy.models.auth.RegisterPayload import me.nathanfallet.extopy.models.users.CreateUserPayload import me.nathanfallet.extopy.models.users.User -import me.nathanfallet.ktorx.usecases.auth.IGetCodeRegisterUseCase -import me.nathanfallet.ktorx.usecases.auth.IRegisterUseCase +import me.nathanfallet.extopy.usecases.application.IGetCodeInEmailUseCase import me.nathanfallet.usecases.models.create.ICreateModelSuspendUseCase -import me.nathanfallet.usecases.users.IUser class RegisterUseCase( - private val getCodeRegisterUseCase: IGetCodeRegisterUseCase, + private val getCodeInEmailUseCase: IGetCodeInEmailUseCase, private val createUserUseCase: ICreateModelSuspendUseCase, -) : IRegisterUseCase { +) : IRegisterUseCase { - override suspend fun invoke(input1: ApplicationCall, input2: RegisterCodePayload): IUser? { - val code = getCodeRegisterUseCase(input1, input1.parameters["code"]!!) ?: return null + override suspend fun invoke(input1: String, input2: RegisterCodePayload): User? { + val codePayload = getCodeInEmailUseCase(input1) ?: return null return createUserUseCase( CreateUserPayload( username = input2.username, displayName = input2.displayName, - email = code.email, + email = codePayload.email, password = input2.password, birthdate = input2.birthdate, ) diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/SetSessionForCallUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/SetSessionForCallUseCase.kt index 7eba250..64d3795 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/SetSessionForCallUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/SetSessionForCallUseCase.kt @@ -3,14 +3,9 @@ package me.nathanfallet.extopy.usecases.auth import io.ktor.server.application.* import io.ktor.server.sessions.* import me.nathanfallet.extopy.models.auth.SessionPayload -import me.nathanfallet.ktorx.usecases.auth.ISetSessionForCallUseCase -import me.nathanfallet.usecases.auth.ISessionPayload class SetSessionForCallUseCase : ISetSessionForCallUseCase { - override fun invoke(input1: ApplicationCall, input2: ISessionPayload) { - if (input2 !is SessionPayload) return - input1.sessions.set(input2) - } + override fun invoke(input1: ApplicationCall, input2: SessionPayload) = input1.sessions.set(input2) } diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/VerifyPasswordUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/VerifyPasswordUseCase.kt index d8d50b2..4585dfd 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/VerifyPasswordUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/auth/VerifyPasswordUseCase.kt @@ -4,8 +4,7 @@ import at.favre.lib.crypto.bcrypt.BCrypt class VerifyPasswordUseCase : IVerifyPasswordUseCase { - override fun invoke(input1: String, input2: String): Boolean { - return BCrypt.verifyer().verify(input1.toCharArray(), input2).verified - } + override fun invoke(input1: String, input2: String): Boolean = + BCrypt.verifyer().verify(input1.toCharArray(), input2).verified } diff --git a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/users/GetUserForCallUseCase.kt b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/users/GetUserForCallUseCase.kt index f417495..ef7ecf4 100644 --- a/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/users/GetUserForCallUseCase.kt +++ b/extopy-backend/src/commonMain/kotlin/me/nathanfallet/extopy/usecases/users/GetUserForCallUseCase.kt @@ -2,11 +2,10 @@ package me.nathanfallet.extopy.usecases.users import io.ktor.server.application.* import io.ktor.util.* -import me.nathanfallet.extopy.models.auth.SessionPayload import me.nathanfallet.extopy.models.users.User import me.nathanfallet.extopy.models.users.UserContext import me.nathanfallet.extopy.usecases.auth.IGetJWTPrincipalForCallUseCase -import me.nathanfallet.ktorx.usecases.auth.IGetSessionForCallUseCase +import me.nathanfallet.extopy.usecases.auth.IGetSessionForCallUseCase import me.nathanfallet.ktorx.usecases.users.IGetUserForCallUseCase import me.nathanfallet.usecases.models.get.context.IGetModelWithContextSuspendUseCase import me.nathanfallet.usecases.users.IUser @@ -26,8 +25,7 @@ class GetUserForCallUseCase( override suspend fun invoke(input: ApplicationCall): IUser? { // Note: we cannot use `computeIfAbsent` because it does not support suspending functions return input.attributes.getOrNull(userKey)?.user ?: run { - val id = - getJWTPrincipalForCall(input)?.subject ?: (getSessionForCallUseCase(input) as? SessionPayload)?.userId + val id = getJWTPrincipalForCall(input)?.subject ?: getSessionForCallUseCase(input)?.userId val computed = UserForCall(id?.let { getUserUseCase(it, UserContext(it)) }) input.attributes.put(userKey, computed) computed.user diff --git a/extopy-backend/src/commonMain/resources/templates/auth/register.ftl b/extopy-backend/src/commonMain/resources/templates/auth/register.ftl index 6926b80..07e1624 100644 --- a/extopy-backend/src/commonMain/resources/templates/auth/register.ftl +++ b/extopy-backend/src/commonMain/resources/templates/auth/register.ftl @@ -8,7 +8,7 @@ - <#if codePayload??> + <#if item?? && item.email??>
@@ -18,7 +18,7 @@
- +
diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/auth/AuthRouterTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/auth/AuthRouterTest.kt index b726e83..5a76557 100644 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/auth/AuthRouterTest.kt +++ b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/auth/AuthRouterTest.kt @@ -46,8 +46,10 @@ class AuthRouterTest { @Test fun testGetLoginRoute() = testApplication { val client = installApp(this) + val controller = mockk() val getLocaleForCallUseCase = mockk() - val router = AuthRouter(mockk(), getLocaleForCallUseCase) + val router = AuthRouter(controller, getLocaleForCallUseCase) + every { controller.login() } returns Unit every { getLocaleForCallUseCase(any()) } returns Locale.ENGLISH routing { router.createRoutes(this) @@ -70,8 +72,10 @@ class AuthRouterTest { @Test fun testGetRegisterRoute() = testApplication { val client = installApp(this) + val controller = mockk() val getLocaleForCallUseCase = mockk() - val router = AuthRouter(mockk(), getLocaleForCallUseCase) + val router = AuthRouter(controller, getLocaleForCallUseCase) + every { controller.register() } returns Unit every { getLocaleForCallUseCase(any()) } returns Locale.ENGLISH routing { router.createRoutes(this) @@ -93,7 +97,7 @@ class AuthRouterTest { val controller = mockk() val getLocaleForCallUseCase = mockk() val router = AuthRouter(controller, getLocaleForCallUseCase) - coEvery { controller.register(any(), "code") } returns RegisterPayload("email@email.com") + coEvery { controller.registerCode(any(), "code") } returns RegisterPayload("email@email.com") every { getLocaleForCallUseCase(any()) } returns Locale.ENGLISH routing { router.createRoutes(this) diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/posts/PostsRouterTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/posts/PostsRouterTest.kt index 81aae48..9b80479 100644 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/posts/PostsRouterTest.kt +++ b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/posts/PostsRouterTest.kt @@ -79,7 +79,7 @@ class PostsRouterTest { assertEquals("listPostReply", get?.operationId) assertEquals(listOf("Post"), get?.tags) assertEquals("Get post replies by id", get?.description) - assertEquals(1, get?.parameters?.size) + assertEquals(3, get?.parameters?.size) assertEquals("postId", get?.parameters?.firstOrNull()?.name) assertEquals(3, get?.responses?.size) assertEquals( diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/users/UsersRouterTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/users/UsersRouterTest.kt index 5e1290c..0d0308d 100644 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/users/UsersRouterTest.kt +++ b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/controllers/users/UsersRouterTest.kt @@ -80,7 +80,7 @@ class UsersRouterTest { assertEquals("listUserPost", get?.operationId) assertEquals(listOf("User"), get?.tags) assertEquals("Get user posts by id", get?.description) - assertEquals(1, get?.parameters?.size) + assertEquals(3, get?.parameters?.size) assertEquals("userId", get?.parameters?.firstOrNull()?.name) assertEquals(3, get?.responses?.size) assertEquals( diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/ClearSessionForCallUseCaseTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/ClearSessionForCallUseCaseTest.kt new file mode 100644 index 0000000..c17ce88 --- /dev/null +++ b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/ClearSessionForCallUseCaseTest.kt @@ -0,0 +1,37 @@ +package me.nathanfallet.extopy.usecases.auth + +import io.ktor.client.request.* +import io.ktor.server.application.* +import io.ktor.server.config.* +import io.ktor.server.routing.* +import io.ktor.server.sessions.* +import io.ktor.server.testing.* +import me.nathanfallet.extopy.models.auth.SessionPayload +import kotlin.test.Test +import kotlin.test.assertEquals + +class ClearSessionForCallUseCaseTest { + + @Test + fun invoke() = testApplication { + environment { + config = ApplicationConfig("application.test.conf") + } + application { + install(Sessions) { + cookie("session", SessionStorageMemory()) + } + } + routing { + get { + val useCase = ClearSessionForCallUseCase() + call.sessions.set(SessionPayload("id")) + assertEquals(SessionPayload("id"), call.sessions.get()) + useCase(call) + assertEquals(null, call.sessions.get()) + } + } + client.get("/") + } + +} diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/CreateAuthCodeUseCaseTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/CreateAuthCodeUseCaseTest.kt index 4e386d5..4f6127f 100644 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/CreateAuthCodeUseCaseTest.kt +++ b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/CreateAuthCodeUseCaseTest.kt @@ -5,10 +5,10 @@ import io.mockk.mockk import kotlinx.coroutines.runBlocking import kotlinx.datetime.Clock import me.nathanfallet.extopy.models.application.Client +import me.nathanfallet.extopy.models.auth.ClientForUser import me.nathanfallet.extopy.models.users.ClientInUser import me.nathanfallet.extopy.models.users.User import me.nathanfallet.extopy.repositories.users.IClientsInUsersRepository -import me.nathanfallet.ktorx.models.auth.ClientForUser import kotlin.test.Test import kotlin.test.assertEquals diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/CreateCodeRegisterUseCaseTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/CreateCodeRegisterUseCaseTest.kt deleted file mode 100644 index 7bd569a..0000000 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/CreateCodeRegisterUseCaseTest.kt +++ /dev/null @@ -1,148 +0,0 @@ -package me.nathanfallet.extopy.usecases.auth - -import io.ktor.http.* -import io.ktor.server.application.* -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockk -import kotlinx.coroutines.runBlocking -import kotlinx.datetime.Clock -import me.nathanfallet.extopy.models.application.CodeInEmail -import me.nathanfallet.extopy.models.application.Email -import me.nathanfallet.extopy.models.auth.RegisterPayload -import me.nathanfallet.extopy.models.users.User -import me.nathanfallet.extopy.repositories.application.ICodesInEmailsRepository -import me.nathanfallet.extopy.repositories.users.IUsersRepository -import me.nathanfallet.ktorx.models.exceptions.ControllerException -import me.nathanfallet.ktorx.usecases.localization.IGetLocaleForCallUseCase -import me.nathanfallet.usecases.emails.ISendEmailUseCase -import me.nathanfallet.usecases.localization.ITranslateUseCase -import java.util.* -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith - -class CreateCodeRegisterUseCaseTest { - - @Test - fun testRegister() = runBlocking { - val codesInEmailsRepository = mockk() - val usersRepository = mockk() - val sendEmailUseCase = mockk() - val translateUseCase = mockk() - val getLocaleForCallUseCase = mockk() - val call = mockk() - val useCase = CreateCodeRegisterUseCase( - codesInEmailsRepository, usersRepository, - sendEmailUseCase, translateUseCase, getLocaleForCallUseCase - ) - coEvery { usersRepository.getForUsernameOrEmail("email", false) } returns null - coEvery { codesInEmailsRepository.createCodeInEmail("email", any(), any()) } returns CodeInEmail( - "email", "code", Clock.System.now() - ) - coEvery { sendEmailUseCase(any(), any()) } returns Unit - every { - translateUseCase( - Locale.ENGLISH, - any() - ) - } answers { "t:${secondArg()}" } - every { getLocaleForCallUseCase(call) } returns Locale.ENGLISH - every { - translateUseCase( - Locale.ENGLISH, - any(), - any() - ) - } answers { "t:${secondArg()}:${thirdArg>()}" } - assertEquals("code", useCase(call, RegisterPayload("email"))) - coVerify { - sendEmailUseCase( - Email( - "t:auth_register_email_title", - "t:auth_register_email_body:[code]" - ), - listOf("email") - ) - } - } - - @Test - fun testRegisterCodeTaken() = runBlocking { - val codesInEmailsRepository = mockk() - val usersRepository = mockk() - val sendEmailUseCase = mockk() - val translateUseCase = mockk() - val getLocaleForCallUseCase = mockk() - val call = mockk() - val useCase = CreateCodeRegisterUseCase( - codesInEmailsRepository, usersRepository, - sendEmailUseCase, translateUseCase, getLocaleForCallUseCase - ) - coEvery { usersRepository.getForUsernameOrEmail("email", false) } returns null - coEvery { codesInEmailsRepository.createCodeInEmail("email", any(), any()) } throws Exception() - coEvery { codesInEmailsRepository.updateCodeInEmail("email", any(), any()) } returns true - coEvery { sendEmailUseCase(any(), any()) } returns Unit - every { - translateUseCase( - Locale.ENGLISH, - any() - ) - } returns "t" - every { getLocaleForCallUseCase(call) } returns Locale.ENGLISH - every { - translateUseCase( - Locale.ENGLISH, - any(), - any() - ) - } returns "t" - useCase(call, RegisterPayload("email")) - coVerify { - codesInEmailsRepository.updateCodeInEmail("email", any(), any()) - } - } - - @Test - fun testRegisterInternalError() = runBlocking { - val codesInEmailsRepository = mockk() - val usersRepository = mockk() - val call = mockk() - val useCase = CreateCodeRegisterUseCase( - codesInEmailsRepository, usersRepository, - mockk(), mockk(), mockk() - ) - coEvery { usersRepository.getForUsernameOrEmail("email", false) } returns null - coEvery { codesInEmailsRepository.createCodeInEmail("email", any(), any()) } throws Exception() - coEvery { codesInEmailsRepository.updateCodeInEmail("email", any(), any()) } returns false - val exception = assertFailsWith { - useCase(call, RegisterPayload("email")) - } - assertEquals(HttpStatusCode.InternalServerError, exception.code) - assertEquals("error_internal", exception.key) - } - - @Test - fun testRegisterEmailTaken() = runBlocking { - val codesInEmailsRepository = mockk() - val usersRepository = mockk() - val sendEmailUseCase = mockk() - val translateUseCase = mockk() - val getLocaleForCallUseCase = mockk() - val call = mockk() - val useCase = CreateCodeRegisterUseCase( - codesInEmailsRepository, usersRepository, - sendEmailUseCase, translateUseCase, getLocaleForCallUseCase - ) - coEvery { usersRepository.getForUsernameOrEmail("email", false) } returns User( - "id", "displayName", "email" - ) - val exception = assertFailsWith { - useCase(call, RegisterPayload("email")) - } - assertEquals(HttpStatusCode.BadRequest, exception.code) - assertEquals("auth_register_email_taken", exception.key) - } - -} diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/CreateSessionForUserUseCaseTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/CreateSessionForUserUseCaseTest.kt deleted file mode 100644 index 0eeb9f5..0000000 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/CreateSessionForUserUseCaseTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -package me.nathanfallet.extopy.usecases.auth - -import me.nathanfallet.extopy.models.auth.SessionPayload -import me.nathanfallet.extopy.models.users.User -import kotlin.test.Test -import kotlin.test.assertEquals - -class CreateSessionForUserUseCaseTest { - - @Test - fun testInvoke() { - val useCase = CreateSessionForUserUseCase() - assertEquals( - SessionPayload("id"), - useCase( - User("id", "displayName", "username") - ) - ) - } - -} diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/DeleteCodeRegisterUseCaseTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/DeleteCodeRegisterUseCaseTest.kt deleted file mode 100644 index 01d6506..0000000 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/DeleteCodeRegisterUseCaseTest.kt +++ /dev/null @@ -1,21 +0,0 @@ -package me.nathanfallet.extopy.usecases.auth - -import io.mockk.coEvery -import io.mockk.coVerify -import io.mockk.mockk -import kotlinx.coroutines.runBlocking -import me.nathanfallet.extopy.repositories.application.ICodesInEmailsRepository -import kotlin.test.Test - -class DeleteCodeRegisterUseCaseTest { - - @Test - fun testInvoke() = runBlocking { - val repository = mockk() - val useCase = DeleteCodeRegisterUseCase(repository) - coEvery { repository.deleteCodeInEmail("code") } returns Unit - useCase(mockk(), "code") - coVerify { repository.deleteCodeInEmail("code") } - } - -} diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GenerateAuthTokenUseCaseTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GenerateAuthTokenUseCaseTest.kt index 65f8ea0..f52b57e 100644 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GenerateAuthTokenUseCaseTest.kt +++ b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GenerateAuthTokenUseCaseTest.kt @@ -4,9 +4,9 @@ import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.runBlocking import me.nathanfallet.extopy.models.application.Client +import me.nathanfallet.extopy.models.auth.ClientForUser import me.nathanfallet.extopy.models.users.User import me.nathanfallet.extopy.services.jwt.IJWTService -import me.nathanfallet.ktorx.models.auth.ClientForUser import me.nathanfallet.usecases.auth.AuthToken import kotlin.test.Test import kotlin.test.assertEquals diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GetAuthCodeUseCaseTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GetAuthCodeUseCaseTest.kt index f3a27ac..31dea9d 100644 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GetAuthCodeUseCaseTest.kt +++ b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GetAuthCodeUseCaseTest.kt @@ -5,12 +5,12 @@ import io.mockk.mockk import kotlinx.coroutines.runBlocking import kotlinx.datetime.* import me.nathanfallet.extopy.models.application.Client +import me.nathanfallet.extopy.models.auth.ClientForUser import me.nathanfallet.extopy.models.users.ClientInUser import me.nathanfallet.extopy.models.users.User import me.nathanfallet.extopy.models.users.UserContext import me.nathanfallet.extopy.repositories.users.IClientsInUsersRepository -import me.nathanfallet.ktorx.models.auth.ClientForUser -import me.nathanfallet.ktorx.usecases.auth.IGetClientUseCase +import me.nathanfallet.usecases.models.get.IGetModelSuspendUseCase import me.nathanfallet.usecases.models.get.context.IGetModelWithContextSuspendUseCase import kotlin.test.Test import kotlin.test.assertEquals @@ -24,7 +24,7 @@ class GetAuthCodeUseCaseTest { @Test fun testInvoke() = runBlocking { val repository = mockk() - val getClientUseCase = mockk() + val getClientUseCase = mockk>() val getUserUseCase = mockk>() val useCase = GetAuthCodeUseCase(repository, getClientUseCase, getUserUseCase) val clientForUser = ClientForUser( @@ -56,7 +56,7 @@ class GetAuthCodeUseCaseTest { @Test fun testInvokeBadClient() = runBlocking { val repository = mockk() - val getClientUseCase = mockk() + val getClientUseCase = mockk>() val useCase = GetAuthCodeUseCase(repository, getClientUseCase, mockk()) coEvery { repository.get("code") } returns ClientInUser("code", "uid", "cid", tomorrow) coEvery { getClientUseCase("cid") } returns null @@ -66,7 +66,7 @@ class GetAuthCodeUseCaseTest { @Test fun testInvokeBadUser() = runBlocking { val repository = mockk() - val getClientUseCase = mockk() + val getClientUseCase = mockk>() val getUserUseCase = mockk>() val useCase = GetAuthCodeUseCase(repository, getClientUseCase, getUserUseCase) val clientForUser = ClientForUser( diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GetCodeRegisterUseCaseTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GetCodeRegisterUseCaseTest.kt deleted file mode 100644 index 2505b92..0000000 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/GetCodeRegisterUseCaseTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -package me.nathanfallet.extopy.usecases.auth - -import io.mockk.coEvery -import io.mockk.mockk -import kotlinx.coroutines.runBlocking -import kotlinx.datetime.* -import me.nathanfallet.extopy.models.application.CodeInEmail -import me.nathanfallet.extopy.models.auth.RegisterPayload -import me.nathanfallet.extopy.repositories.application.ICodesInEmailsRepository -import kotlin.test.Test -import kotlin.test.assertEquals - -class GetCodeRegisterUseCaseTest { - - private val now = Clock.System.now() - private val tomorrow = now.plus(1, DateTimeUnit.DAY, TimeZone.currentSystemDefault()) - private val yesterday = now.minus(1, DateTimeUnit.DAY, TimeZone.currentSystemDefault()) - - @Test - fun invoke() = runBlocking { - val repository = mockk() - val useCase = GetCodeRegisterUseCase(repository) - coEvery { repository.getCodeInEmail("code") } returns CodeInEmail("email", "code", tomorrow) - assertEquals(RegisterPayload("email"), useCase(mockk(), "code")) - } - - @Test - fun invokeExpired() = runBlocking { - val repository = mockk() - val useCase = GetCodeRegisterUseCase(repository) - coEvery { repository.getCodeInEmail("code") } returns CodeInEmail("email", "code", yesterday) - assertEquals(null, useCase(mockk(), "code")) - } - - @Test - fun invokeNone() = runBlocking { - val repository = mockk() - val useCase = GetCodeRegisterUseCase(repository) - coEvery { repository.getCodeInEmail("code") } returns null - assertEquals(null, useCase(mockk(), "code")) - } - -} diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/RegisterUseCaseTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/RegisterUseCaseTest.kt index 1630d34..8e9cdc5 100644 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/RegisterUseCaseTest.kt +++ b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/auth/RegisterUseCaseTest.kt @@ -1,16 +1,15 @@ package me.nathanfallet.extopy.usecases.auth -import io.ktor.server.application.* import io.mockk.coEvery -import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.runBlocking +import kotlinx.datetime.Clock import kotlinx.datetime.LocalDate +import me.nathanfallet.extopy.models.application.CodeInEmail import me.nathanfallet.extopy.models.auth.RegisterCodePayload -import me.nathanfallet.extopy.models.auth.RegisterPayload import me.nathanfallet.extopy.models.users.CreateUserPayload import me.nathanfallet.extopy.models.users.User -import me.nathanfallet.ktorx.usecases.auth.IGetCodeRegisterUseCase +import me.nathanfallet.extopy.usecases.application.IGetCodeInEmailUseCase import me.nathanfallet.usecases.models.create.ICreateModelSuspendUseCase import kotlin.test.Test import kotlin.test.assertEquals @@ -19,34 +18,30 @@ class RegisterUseCaseTest { @Test fun testRegisterCodePayload() = runBlocking { - val getCodeRegisterUseCase = mockk>() - val call = mockk() + val getCodeInEmailUseCase = mockk() val createUserUseCase = mockk>() - val useCase = RegisterUseCase(getCodeRegisterUseCase, createUserUseCase) + val useCase = RegisterUseCase(getCodeInEmailUseCase, createUserUseCase) val payload = CreateUserPayload( "username", "displayName", "email", "password", LocalDate(2002, 12, 24) ) val user = User("id", "displayName", "username") - every { call.parameters["code"] } returns "code" - coEvery { getCodeRegisterUseCase(call, "code") } returns RegisterPayload("email") + coEvery { getCodeInEmailUseCase("code") } returns CodeInEmail("email", "code", Clock.System.now()) coEvery { createUserUseCase(payload) } returns user assertEquals( user, - useCase(call, RegisterCodePayload("password", "username", "displayName", LocalDate(2002, 12, 24))) + useCase("code", RegisterCodePayload("password", "username", "displayName", LocalDate(2002, 12, 24))) ) } @Test fun testRegisterCodePayloadNotExists() = runBlocking { - val getCodeRegisterUseCase = mockk>() - val call = mockk() - val useCase = RegisterUseCase(getCodeRegisterUseCase, mockk()) - every { call.parameters["code"] } returns "code" - coEvery { getCodeRegisterUseCase(call, "code") } returns null + val getCodeInEmailUseCase = mockk() + val useCase = RegisterUseCase(getCodeInEmailUseCase, mockk()) + coEvery { getCodeInEmailUseCase("code") } returns null assertEquals( null, - useCase(call, RegisterCodePayload("password", "username", "displayName", LocalDate(2002, 12, 24))) + useCase("code", RegisterCodePayload("password", "username", "displayName", LocalDate(2002, 12, 24))) ) } diff --git a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/users/GetUserForCallUseCaseTest.kt b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/users/GetUserForCallUseCaseTest.kt index 1efc62b..19d6c2a 100644 --- a/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/users/GetUserForCallUseCaseTest.kt +++ b/extopy-backend/src/jvmTest/kotlin/me/nathanfallet/extopy/usecases/users/GetUserForCallUseCaseTest.kt @@ -11,7 +11,7 @@ import me.nathanfallet.extopy.models.auth.SessionPayload import me.nathanfallet.extopy.models.users.User import me.nathanfallet.extopy.models.users.UserContext import me.nathanfallet.extopy.usecases.auth.IGetJWTPrincipalForCallUseCase -import me.nathanfallet.ktorx.usecases.auth.IGetSessionForCallUseCase +import me.nathanfallet.extopy.usecases.auth.IGetSessionForCallUseCase import me.nathanfallet.usecases.models.get.context.IGetModelWithContextSuspendUseCase import kotlin.test.Test import kotlin.test.assertEquals diff --git a/extopy-commons/build.gradle.kts b/extopy-commons/build.gradle.kts index 8879b85..6aa575e 100644 --- a/extopy-commons/build.gradle.kts +++ b/extopy-commons/build.gradle.kts @@ -59,7 +59,7 @@ kotlin { applyDefaultHierarchyTemplate() - val ktorxVersion = "2.2.4" + val ktorxVersion = "2.3.0" val usecasesVersion = "1.6.0" sourceSets { diff --git a/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/client/ExtopyClient.kt b/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/client/ExtopyClient.kt index cee0822..e1fd7ae 100644 --- a/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/client/ExtopyClient.kt +++ b/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/client/ExtopyClient.kt @@ -2,13 +2,13 @@ package me.nathanfallet.extopy.client import me.nathanfallet.extopy.models.application.ExtopyEnvironment import me.nathanfallet.extopy.models.application.ExtopyJson +import me.nathanfallet.extopy.repositories.auth.AuthAPIRemoteRepository import me.nathanfallet.extopy.repositories.posts.LikesInPostsRemoteRepository import me.nathanfallet.extopy.repositories.posts.PostsRemoteRepository import me.nathanfallet.extopy.repositories.timelines.TimelinesRemoteRepository import me.nathanfallet.extopy.repositories.users.FollowersInUsersRemoteRepository import me.nathanfallet.extopy.repositories.users.UsersRemoteRepository import me.nathanfallet.ktorx.models.api.AbstractAPIClient -import me.nathanfallet.ktorx.repositories.auth.AuthAPIRemoteRepository import me.nathanfallet.ktorx.usecases.api.IGetTokenUseCase class ExtopyClient( @@ -20,7 +20,7 @@ class ExtopyClient( ExtopyJson.json ), IExtopyClient { - override val auth = AuthAPIRemoteRepository(this, prefix = "/api/v1") + override val auth = AuthAPIRemoteRepository(this) override val users = UsersRemoteRepository(this) override val followersInUsers = FollowersInUsersRemoteRepository(this, users) override val posts = PostsRemoteRepository(this) diff --git a/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/client/IExtopyClient.kt b/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/client/IExtopyClient.kt index 6bc76e8..571a0e5 100644 --- a/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/client/IExtopyClient.kt +++ b/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/client/IExtopyClient.kt @@ -1,12 +1,12 @@ package me.nathanfallet.extopy.client +import me.nathanfallet.extopy.repositories.auth.IAuthAPIRemoteRepository import me.nathanfallet.extopy.repositories.posts.ILikesInPostsRemoteRepository import me.nathanfallet.extopy.repositories.posts.IPostsRemoteRepository import me.nathanfallet.extopy.repositories.timelines.ITimelinesRemoteRepository import me.nathanfallet.extopy.repositories.users.IFollowersInUsersRemoteRepository import me.nathanfallet.extopy.repositories.users.IUsersRemoteRepository import me.nathanfallet.ktorx.models.api.IAPIClient -import me.nathanfallet.ktorx.repositories.auth.IAuthAPIRemoteRepository interface IExtopyClient : IAPIClient { diff --git a/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/models/auth/ClientForUser.kt b/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/models/auth/ClientForUser.kt new file mode 100644 index 0000000..d6aa20e --- /dev/null +++ b/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/models/auth/ClientForUser.kt @@ -0,0 +1,11 @@ +package me.nathanfallet.extopy.models.auth + +import kotlinx.serialization.Serializable +import me.nathanfallet.extopy.models.application.Client +import me.nathanfallet.extopy.models.users.User + +@Serializable +data class ClientForUser( + val client: Client, + val user: User, +) diff --git a/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/repositories/auth/AuthAPIRemoteRepository.kt b/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/repositories/auth/AuthAPIRemoteRepository.kt new file mode 100644 index 0000000..4af00fd --- /dev/null +++ b/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/repositories/auth/AuthAPIRemoteRepository.kt @@ -0,0 +1,31 @@ +package me.nathanfallet.extopy.repositories.auth + +import io.ktor.client.call.* +import io.ktor.client.request.* +import io.ktor.http.* +import me.nathanfallet.ktorx.models.api.IAPIClient +import me.nathanfallet.ktorx.repositories.api.APIUnitRemoteRepository +import me.nathanfallet.usecases.auth.AuthRequest +import me.nathanfallet.usecases.auth.AuthToken +import me.nathanfallet.usecases.models.UnitModel +import me.nathanfallet.usecases.models.id.RecursiveId + +open class AuthAPIRemoteRepository( + client: IAPIClient, +) : APIUnitRemoteRepository( + client, + route = "auth", + prefix = "/api/v1" +), IAuthAPIRemoteRepository { + + override suspend fun token( + payload: AuthRequest, + ): AuthToken? = client.request( + HttpMethod.Post, + "${constructFullRoute(RecursiveId(Unit))}/token" + ) { + contentType(ContentType.Application.Json) + setBody(payload) + }.body() + +} diff --git a/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/repositories/auth/IAuthAPIRemoteRepository.kt b/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/repositories/auth/IAuthAPIRemoteRepository.kt new file mode 100644 index 0000000..7808152 --- /dev/null +++ b/extopy-commons/src/commonMain/kotlin/me/nathanfallet/extopy/repositories/auth/IAuthAPIRemoteRepository.kt @@ -0,0 +1,10 @@ +package me.nathanfallet.extopy.repositories.auth + +import me.nathanfallet.usecases.auth.AuthRequest +import me.nathanfallet.usecases.auth.AuthToken + +interface IAuthAPIRemoteRepository { + + suspend fun token(payload: AuthRequest): AuthToken? + +}