diff --git a/src/main/kotlin/ltd/hlaeja/configuration/SecurityConfiguration.kt b/src/main/kotlin/ltd/hlaeja/configuration/SecurityConfiguration.kt index 07baa5a..bd35828 100644 --- a/src/main/kotlin/ltd/hlaeja/configuration/SecurityConfiguration.kt +++ b/src/main/kotlin/ltd/hlaeja/configuration/SecurityConfiguration.kt @@ -35,7 +35,7 @@ class SecurityConfiguration { .anyExchange().authenticated() private fun AuthorizeExchangeSpec.adminPaths(): AuthorizeExchangeSpec.Access = pathMatchers( - "/account/**" + "/account/**", ) private fun AuthorizeExchangeSpec.publicPaths(): AuthorizeExchangeSpec.Access = pathMatchers( diff --git a/src/main/kotlin/ltd/hlaeja/controller/AccountController.kt b/src/main/kotlin/ltd/hlaeja/controller/AccountController.kt index 0998c02..bb2d332 100644 --- a/src/main/kotlin/ltd/hlaeja/controller/AccountController.kt +++ b/src/main/kotlin/ltd/hlaeja/controller/AccountController.kt @@ -1,11 +1,16 @@ package ltd.hlaeja.controller import ltd.hlaeja.dto.Pagination +import ltd.hlaeja.exception.UsernameDuplicateException +import ltd.hlaeja.form.AccountForm import ltd.hlaeja.service.AccountRegistryService +import ltd.hlaeja.util.toAccountRequest import org.springframework.stereotype.Controller import org.springframework.ui.Model import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.ModelAttribute import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.PostMapping import org.springframework.web.bind.annotation.RequestMapping import reactor.core.publisher.Mono @@ -19,6 +24,32 @@ class AccountController( const val DEFAULT_SIZE: Int = 25 } + @GetMapping("/create") + fun getCreateAccount( + model: Model, + ): Mono = Mono.just("account/create") + .doOnNext { model.addAttribute("accountForm", AccountForm("", "", "", "", true)) } + + @PostMapping("/create") + fun postCreateAccount( + @ModelAttribute("accountForm") accountForm: AccountForm, + model: Model, + ): Mono { + return accountRegistryService.addAccount(accountForm.toAccountRequest()) + .map { + model.addAttribute("success", true) + "redirect:/account" + } + .onErrorResume { error -> + val errorMessage = when (error) { + is UsernameDuplicateException -> "Username already exists. Please choose another." + else -> "An unexpected error occurred. Please try again later." + } + model.addAttribute("errorMessage", errorMessage) + Mono.just("account/create") + } + } + @GetMapping fun getDefaultAccounts( model: Model, @@ -49,4 +80,3 @@ class AccountController( } .then(Mono.just("account/users")) } - diff --git a/src/main/kotlin/ltd/hlaeja/exception/AccountRegistryException.kt b/src/main/kotlin/ltd/hlaeja/exception/AccountRegistryException.kt new file mode 100644 index 0000000..1adf9e8 --- /dev/null +++ b/src/main/kotlin/ltd/hlaeja/exception/AccountRegistryException.kt @@ -0,0 +1,23 @@ +package ltd.hlaeja.exception + +@Suppress("unused") +open class AccountRegistryException : HlaejaException { + + constructor() : super() + + constructor(message: String) : super(message) + + constructor(cause: Throwable) : super(cause) + + constructor( + message: String, + cause: Throwable, + ) : super(message, cause) + + constructor( + message: String, + cause: Throwable, + enableSuppression: Boolean, + writableStackTrace: Boolean, + ) : super(message, cause, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/ltd/hlaeja/exception/HlaejaException.kt b/src/main/kotlin/ltd/hlaeja/exception/HlaejaException.kt new file mode 100644 index 0000000..1acd6b4 --- /dev/null +++ b/src/main/kotlin/ltd/hlaeja/exception/HlaejaException.kt @@ -0,0 +1,23 @@ +package ltd.hlaeja.exception + +@Suppress("unused") +open class HlaejaException : RuntimeException { + + constructor() : super() + + constructor(message: String) : super(message) + + constructor(cause: Throwable) : super(cause) + + constructor( + message: String, + cause: Throwable, + ) : super(message, cause) + + constructor( + message: String, + cause: Throwable, + enableSuppression: Boolean, + writableStackTrace: Boolean, + ) : super(message, cause, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/ltd/hlaeja/exception/UsernameDuplicateException.kt b/src/main/kotlin/ltd/hlaeja/exception/UsernameDuplicateException.kt new file mode 100644 index 0000000..652d827 --- /dev/null +++ b/src/main/kotlin/ltd/hlaeja/exception/UsernameDuplicateException.kt @@ -0,0 +1,23 @@ +package ltd.hlaeja.exception + +@Suppress("unused") +open class UsernameDuplicateException : AccountRegistryException { + + constructor() : super() + + constructor(message: String) : super(message) + + constructor(cause: Throwable) : super(cause) + + constructor( + message: String, + cause: Throwable, + ) : super(message, cause) + + constructor( + message: String, + cause: Throwable, + enableSuppression: Boolean, + writableStackTrace: Boolean, + ) : super(message, cause, enableSuppression, writableStackTrace) +} diff --git a/src/main/kotlin/ltd/hlaeja/form/AccountForm.kt b/src/main/kotlin/ltd/hlaeja/form/AccountForm.kt new file mode 100644 index 0000000..c8542ff --- /dev/null +++ b/src/main/kotlin/ltd/hlaeja/form/AccountForm.kt @@ -0,0 +1,9 @@ +package ltd.hlaeja.form + +data class AccountForm( + val username: String, + val password: CharSequence, + val passwordConfirm: CharSequence, + val role: String, + val enabled: Boolean = false, +) diff --git a/src/main/kotlin/ltd/hlaeja/service/AccountRegistryService.kt b/src/main/kotlin/ltd/hlaeja/service/AccountRegistryService.kt index 87fb4b8..983429a 100644 --- a/src/main/kotlin/ltd/hlaeja/service/AccountRegistryService.kt +++ b/src/main/kotlin/ltd/hlaeja/service/AccountRegistryService.kt @@ -1,11 +1,13 @@ package ltd.hlaeja.service import io.github.oshai.kotlinlogging.KotlinLogging +import ltd.hlaeja.exception.AccountRegistryException import ltd.hlaeja.library.accountRegistry.Account import ltd.hlaeja.library.accountRegistry.Authentication import ltd.hlaeja.property.AccountRegistryProperty import ltd.hlaeja.util.accountRegistryAccounts import ltd.hlaeja.util.accountRegistryAuthenticate +import ltd.hlaeja.util.accountRegistryCreate import org.springframework.http.HttpStatus.BAD_REQUEST import org.springframework.security.authentication.AuthenticationServiceException import org.springframework.security.core.AuthenticationException @@ -54,4 +56,14 @@ class AccountRegistryService( size: Int, ): Flux = webClient.accountRegistryAccounts(page, size, property) .onErrorResume { error -> Flux.error(ResponseStatusException(BAD_REQUEST, error.message, error)) } + + fun addAccount( + request: Account.Request, + ): Mono = webClient.accountRegistryCreate(request, property) + .onErrorResume { error -> + when (error) { + is AccountRegistryException -> Mono.error(error) + else -> Mono.error(ResponseStatusException(BAD_REQUEST, error.message)) + } + } } diff --git a/src/main/kotlin/ltd/hlaeja/util/Mapping.kt b/src/main/kotlin/ltd/hlaeja/util/Mapping.kt index 28978a9..df6c447 100644 --- a/src/main/kotlin/ltd/hlaeja/util/Mapping.kt +++ b/src/main/kotlin/ltd/hlaeja/util/Mapping.kt @@ -1,10 +1,18 @@ package ltd.hlaeja.util -import org.springframework.security.core.Authentication as SpringAuthentication - +import ltd.hlaeja.form.AccountForm +import ltd.hlaeja.library.accountRegistry.Account import ltd.hlaeja.library.accountRegistry.Authentication +import org.springframework.security.core.Authentication as SpringAuthentication fun SpringAuthentication.toAuthenticationRequest(): Authentication.Request = Authentication.Request( principal as String, credentials as String, ) + +fun AccountForm.toAccountRequest(): Account.Request = Account.Request( + username = username, + password = password, + enabled = enabled, + roles = listOf("ROLE_${role.uppercase()}"), +) diff --git a/src/main/kotlin/ltd/hlaeja/util/WebClientCalls.kt b/src/main/kotlin/ltd/hlaeja/util/WebClientCalls.kt index 23c60b6..e4e7275 100644 --- a/src/main/kotlin/ltd/hlaeja/util/WebClientCalls.kt +++ b/src/main/kotlin/ltd/hlaeja/util/WebClientCalls.kt @@ -1,8 +1,12 @@ package ltd.hlaeja.util +import ltd.hlaeja.exception.AccountRegistryException +import ltd.hlaeja.exception.UsernameDuplicateException import ltd.hlaeja.library.accountRegistry.Account import ltd.hlaeja.library.accountRegistry.Authentication import ltd.hlaeja.property.AccountRegistryProperty +import org.springframework.http.HttpStatus.BAD_REQUEST +import org.springframework.http.HttpStatus.CONFLICT import org.springframework.http.HttpStatus.LOCKED import org.springframework.http.HttpStatus.NOT_FOUND import org.springframework.http.HttpStatus.UNAUTHORIZED @@ -28,8 +32,19 @@ fun WebClient.accountRegistryAuthenticate( fun WebClient.accountRegistryAccounts( page: Int, size: Int, - property: AccountRegistryProperty + property: AccountRegistryProperty, ): Flux = get() .uri("${property.url}/accounts?page=$page&size=$size".also(::logCall)) .retrieve() .bodyToFlux(Account.Response::class.java) + +fun WebClient.accountRegistryCreate( + request: Account.Request, + property: AccountRegistryProperty, +): Mono = post() + .uri("${property.url}/account".also(::logCall)) + .bodyValue(request) + .retrieve() + .onStatus(CONFLICT::equals) { throw UsernameDuplicateException() } + .onStatus(BAD_REQUEST::equals) { throw AccountRegistryException("Remote service returned 400") } + .bodyToMono(Account.Response::class.java) diff --git a/src/main/resources/templates/account/create.html b/src/main/resources/templates/account/create.html new file mode 100644 index 0000000..fdbfc3f --- /dev/null +++ b/src/main/resources/templates/account/create.html @@ -0,0 +1,90 @@ + + + + Home Pages + + + +
+

Test create user

+
+
+ +
+ Error Message +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + + +
+ + +
+ + +
+ + +
+ + +
+ + + +
+
+ Account + Logout
+
+ + + + diff --git a/src/main/resources/templates/account/users.html b/src/main/resources/templates/account/users.html index f861dec..ec112bb 100644 --- a/src/main/resources/templates/account/users.html +++ b/src/main/resources/templates/account/users.html @@ -34,7 +34,8 @@ Next
- Logout + Create Account
+ Logout