From 25a0b22d77db79c45f4e2a6c319a5628e444364e Mon Sep 17 00:00:00 2001 From: Simon Grimme Date: Fri, 7 Jun 2024 22:13:17 +0200 Subject: [PATCH] Finalize onboarding of super admin user --- frontend/views/SetupView.tsx | 97 +++++++++++-------- .../grimsi/gameyfin/setup/SetupDataLoader.kt | 10 +- .../de/grimsi/gameyfin/setup/SetupEndpoint.kt | 38 ++++++++ .../de/grimsi/gameyfin/setup/SetupService.kt | 10 ++ .../de/grimsi/gameyfin/users/RoleService.kt | 9 +- .../de/grimsi/gameyfin/users/UserEndpoint.kt | 14 +-- .../de/grimsi/gameyfin/users/UserService.kt | 11 ++- .../de/grimsi/gameyfin/users/entities/User.kt | 2 +- 8 files changed, 124 insertions(+), 67 deletions(-) create mode 100644 src/main/kotlin/de/grimsi/gameyfin/setup/SetupEndpoint.kt diff --git a/frontend/views/SetupView.tsx b/frontend/views/SetupView.tsx index 765c775..d764340 100644 --- a/frontend/views/SetupView.tsx +++ b/frontend/views/SetupView.tsx @@ -5,8 +5,9 @@ import WizardStep from "Frontend/components/wizard/WizardStep"; import Input from "Frontend/components/Input"; import {GearFine, HandWaving, Palette, User} from "@phosphor-icons/react"; import {Card} from "@nextui-org/react"; -import {UserEndpoint} from "Frontend/generated/endpoints"; +import {SetupEndpoint} from "Frontend/generated/endpoints"; import {ThemeSelector} from "Frontend/components/theming/ThemeSelector"; +import {useNavigate} from "react-router-dom"; function WelcomeStep() { return ( @@ -82,48 +83,60 @@ function SettingsStep() { ); } -const SetupView = () => ( -
- - UserEndpoint.registerInitialSuperAdmin({ - username: values.username, - password: values.password, - email: values.email +function SetupView() { + const navigate = useNavigate(); + + return ( +
+ + { + try { + await SetupEndpoint.registerSuperAdmin({ + username: values.username, + password: values.password, + email: values.email + }); + navigate('/login'); + } catch (e) { + alert("An error occurred while completing the setup. Please try again.") + } + } } - ).then(() => alert("Successfully registered!"))} - > - }> - - - }> - - - } > - - - }> - - - - -
-); + }> + + + }> + + + } + > + + + }> + + +
+
+
+ ); +} export default SetupView; \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/setup/SetupDataLoader.kt b/src/main/kotlin/de/grimsi/gameyfin/setup/SetupDataLoader.kt index 831b744..cfe6e6c 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/setup/SetupDataLoader.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/setup/SetupDataLoader.kt @@ -34,19 +34,17 @@ class SetupDataLoader( fun setupUsers() { val superadmin = User( username = "admin", - password = "admin", - roles = listOf(roleRepository.findByRolename(Roles.SUPERADMIN.roleName)!!) + password = "admin" ) - userService.registerUser(superadmin) + userService.registerUser(superadmin, Roles.SUPERADMIN) val user = User( username = "user", - password = "user", - roles = listOf(roleRepository.findByRolename(Roles.USER.roleName)!!) + password = "user" ) - userService.registerUser(user) + userService.registerUser(user, Roles.USER) } fun setupRoles() { diff --git a/src/main/kotlin/de/grimsi/gameyfin/setup/SetupEndpoint.kt b/src/main/kotlin/de/grimsi/gameyfin/setup/SetupEndpoint.kt new file mode 100644 index 0000000..b9ad8b7 --- /dev/null +++ b/src/main/kotlin/de/grimsi/gameyfin/setup/SetupEndpoint.kt @@ -0,0 +1,38 @@ +package de.grimsi.gameyfin.setup + +import com.vaadin.flow.server.auth.AnonymousAllowed +import de.grimsi.gameyfin.config.Roles +import de.grimsi.gameyfin.users.RoleService +import de.grimsi.gameyfin.users.UserService +import de.grimsi.gameyfin.users.dto.UserInfo +import de.grimsi.gameyfin.users.dto.UserRegistration +import de.grimsi.gameyfin.users.entities.User +import dev.hilla.Endpoint +import dev.hilla.exception.EndpointException + +@Endpoint +class SetupEndpoint( + private val setupService: SetupService, + private val roleService: RoleService, + private val userService: UserService +) { + @AnonymousAllowed + fun isSetupCompleted(): Boolean { + return setupService.isSetupCompleted() + } + + @AnonymousAllowed + fun registerSuperAdmin(superAdminRegistration: UserRegistration): UserInfo { + if (setupService.isSetupCompleted()) throw EndpointException("Setup already completed") + + val user = User( + username = superAdminRegistration.username, + password = superAdminRegistration.password, + email = superAdminRegistration.email, + roles = listOf(roleService.toRole(Roles.SUPERADMIN)) + ) + + val superAdmin = setupService.createInitialAdminUser(user) + return userService.toUserInfo(superAdmin) + } +} \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/setup/SetupService.kt b/src/main/kotlin/de/grimsi/gameyfin/setup/SetupService.kt index 9b6d675..74a6139 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/setup/SetupService.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/setup/SetupService.kt @@ -2,10 +2,13 @@ package de.grimsi.gameyfin.setup import de.grimsi.gameyfin.config.Roles import de.grimsi.gameyfin.users.RoleService +import de.grimsi.gameyfin.users.UserService +import de.grimsi.gameyfin.users.entities.User import org.springframework.stereotype.Service @Service class SetupService( + private val userService: UserService, private val roleService: RoleService ) { @@ -17,4 +20,11 @@ class SetupService( fun isSetupCompleted(): Boolean { return roleService.getUserCountForRole(Roles.SUPERADMIN) > 0 } + + /** + * Creates the initial user with Super-Admin permissions + */ + fun createInitialAdminUser(superAdmin: User): User { + return userService.registerUser(superAdmin, Roles.SUPERADMIN) + } } \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/RoleService.kt b/src/main/kotlin/de/grimsi/gameyfin/users/RoleService.kt index 0e78c5e..374de42 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/RoleService.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/RoleService.kt @@ -20,11 +20,12 @@ class RoleService( return r.users.size } - fun toRoles(roles: Collection): List { - return roles.mapNotNull { r -> roleRepository.findByRolename(r) } + fun toRoles(roles: Collection): List { + return roles.mapNotNull { r -> roleRepository.findByRolename(r.roleName) } } - fun toRole(role: String): Role { - return roleRepository.findByRolename(role) ?: throw RuntimeException("Role $role does not exist") + fun toRole(role: Roles): Role { + return roleRepository.findByRolename(role.roleName) + ?: throw RuntimeException("Role ${role.roleName} does not exist") } } \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt b/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt index 659760e..ee38448 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/UserEndpoint.kt @@ -6,11 +6,9 @@ import de.grimsi.gameyfin.users.dto.UserRegistration import de.grimsi.gameyfin.users.entities.User import dev.hilla.Endpoint import jakarta.annotation.security.PermitAll -import org.springframework.http.HttpStatus import org.springframework.security.core.Authentication import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.context.SecurityContextHolder -import org.springframework.web.server.ResponseStatusException @Endpoint class UserEndpoint( @@ -31,21 +29,13 @@ class UserEndpoint( return userService.toUserInfo(user) } - @PermitAll - fun registerInitialSuperAdmin(registration: UserRegistration): UserInfo { - if (roleService.getUserCountForRole(Roles.SUPERADMIN) > 0) throw ResponseStatusException(HttpStatus.UNAUTHORIZED) - val superAdmin: User = registerUser(registration, listOf(Roles.SUPERADMIN)) - return userService.toUserInfo(superAdmin) - } - private fun registerUser(registration: UserRegistration, roles: List): User { val user = User( username = registration.username, password = registration.password, - email = registration.email, - roles = roles.map { r -> roleService.toRole(r.roleName) } + email = registration.email ) - return userService.registerUser(user) + return userService.registerUser(user, roles) } } \ No newline at end of file diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt b/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt index 6f43716..2141325 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt @@ -1,5 +1,6 @@ package de.grimsi.gameyfin.users +import de.grimsi.gameyfin.config.Roles import de.grimsi.gameyfin.users.dto.UserInfo import de.grimsi.gameyfin.users.entities.Role import de.grimsi.gameyfin.users.entities.User @@ -18,7 +19,8 @@ import org.springframework.stereotype.Service @Transactional class UserService( private val userRepository: UserRepository, - private val passwordEncoder: PasswordEncoder + private val passwordEncoder: PasswordEncoder, + private val roleService: RoleService ) : UserDetailsService { override fun loadUserByUsername(username: String): UserDetails { @@ -36,8 +38,13 @@ class UserService( ) } - fun registerUser(user: User): User { + fun registerUser(user: User, role: Roles): User { + return registerUser(user, listOf(role)) + } + + fun registerUser(user: User, roles: List): User { user.password = passwordEncoder.encode(user.password) + user.roles = roleService.toRoles(roles) return userRepository.save(user) } diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/entities/User.kt b/src/main/kotlin/de/grimsi/gameyfin/users/entities/User.kt index a6dbb93..e3b901c 100644 --- a/src/main/kotlin/de/grimsi/gameyfin/users/entities/User.kt +++ b/src/main/kotlin/de/grimsi/gameyfin/users/entities/User.kt @@ -33,5 +33,5 @@ class User( joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")], inverseJoinColumns = [JoinColumn(name = "role_id", referencedColumnName = "id")] ) - var roles: Collection + var roles: Collection = emptyList() ) \ No newline at end of file