Finalize onboarding of super admin user

This commit is contained in:
Simon Grimme
2024-06-07 22:13:17 +02:00
parent 982c3b2221
commit 25a0b22d77
8 changed files with 124 additions and 67 deletions
+18 -5
View File
@@ -5,8 +5,9 @@ import WizardStep from "Frontend/components/wizard/WizardStep";
import Input from "Frontend/components/Input"; import Input from "Frontend/components/Input";
import {GearFine, HandWaving, Palette, User} from "@phosphor-icons/react"; import {GearFine, HandWaving, Palette, User} from "@phosphor-icons/react";
import {Card} from "@nextui-org/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 {ThemeSelector} from "Frontend/components/theming/ThemeSelector";
import {useNavigate} from "react-router-dom";
function WelcomeStep() { function WelcomeStep() {
return ( return (
@@ -82,17 +83,28 @@ function SettingsStep() {
); );
} }
const SetupView = () => ( function SetupView() {
const navigate = useNavigate();
return (
<div className="flex size-full gradient-primary"> <div className="flex size-full gradient-primary">
<Card className="w-3/4 h-3/4 min-w-[500px] m-auto p-8"> <Card className="w-3/4 h-3/4 min-w-[500px] m-auto p-8">
<Wizard <Wizard
initialValues={{username: '', email: '', password: '', passwordRepeat: ''}} initialValues={{username: '', email: '', password: '', passwordRepeat: ''}}
onSubmit={(values: any) => UserEndpoint.registerInitialSuperAdmin({ onSubmit={
async (values: any) => {
try {
await SetupEndpoint.registerSuperAdmin({
username: values.username, username: values.username,
password: values.password, password: values.password,
email: values.email email: values.email
});
navigate('/login');
} catch (e) {
alert("An error occurred while completing the setup. Please try again.")
}
}
} }
).then(() => alert("Successfully registered!"))}
> >
<WizardStep icon={<HandWaving/>}> <WizardStep icon={<HandWaving/>}>
<WelcomeStep/> <WelcomeStep/>
@@ -124,6 +136,7 @@ const SetupView = () => (
</Wizard> </Wizard>
</Card> </Card>
</div> </div>
); );
}
export default SetupView; export default SetupView;
@@ -34,19 +34,17 @@ class SetupDataLoader(
fun setupUsers() { fun setupUsers() {
val superadmin = User( val superadmin = User(
username = "admin", username = "admin",
password = "admin", password = "admin"
roles = listOf(roleRepository.findByRolename(Roles.SUPERADMIN.roleName)!!)
) )
userService.registerUser(superadmin) userService.registerUser(superadmin, Roles.SUPERADMIN)
val user = User( val user = User(
username = "user", username = "user",
password = "user", password = "user"
roles = listOf(roleRepository.findByRolename(Roles.USER.roleName)!!)
) )
userService.registerUser(user) userService.registerUser(user, Roles.USER)
} }
fun setupRoles() { fun setupRoles() {
@@ -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)
}
}
@@ -2,10 +2,13 @@ package de.grimsi.gameyfin.setup
import de.grimsi.gameyfin.config.Roles import de.grimsi.gameyfin.config.Roles
import de.grimsi.gameyfin.users.RoleService import de.grimsi.gameyfin.users.RoleService
import de.grimsi.gameyfin.users.UserService
import de.grimsi.gameyfin.users.entities.User
import org.springframework.stereotype.Service import org.springframework.stereotype.Service
@Service @Service
class SetupService( class SetupService(
private val userService: UserService,
private val roleService: RoleService private val roleService: RoleService
) { ) {
@@ -17,4 +20,11 @@ class SetupService(
fun isSetupCompleted(): Boolean { fun isSetupCompleted(): Boolean {
return roleService.getUserCountForRole(Roles.SUPERADMIN) > 0 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)
}
} }
@@ -20,11 +20,12 @@ class RoleService(
return r.users.size return r.users.size
} }
fun toRoles(roles: Collection<String>): List<Role> { fun toRoles(roles: Collection<Roles>): List<Role> {
return roles.mapNotNull { r -> roleRepository.findByRolename(r) } return roles.mapNotNull { r -> roleRepository.findByRolename(r.roleName) }
} }
fun toRole(role: String): Role { fun toRole(role: Roles): Role {
return roleRepository.findByRolename(role) ?: throw RuntimeException("Role $role does not exist") return roleRepository.findByRolename(role.roleName)
?: throw RuntimeException("Role ${role.roleName} does not exist")
} }
} }
@@ -6,11 +6,9 @@ import de.grimsi.gameyfin.users.dto.UserRegistration
import de.grimsi.gameyfin.users.entities.User import de.grimsi.gameyfin.users.entities.User
import dev.hilla.Endpoint import dev.hilla.Endpoint
import jakarta.annotation.security.PermitAll import jakarta.annotation.security.PermitAll
import org.springframework.http.HttpStatus
import org.springframework.security.core.Authentication import org.springframework.security.core.Authentication
import org.springframework.security.core.GrantedAuthority import org.springframework.security.core.GrantedAuthority
import org.springframework.security.core.context.SecurityContextHolder import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.web.server.ResponseStatusException
@Endpoint @Endpoint
class UserEndpoint( class UserEndpoint(
@@ -31,21 +29,13 @@ class UserEndpoint(
return userService.toUserInfo(user) 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<Roles>): User { private fun registerUser(registration: UserRegistration, roles: List<Roles>): User {
val user = User( val user = User(
username = registration.username, username = registration.username,
password = registration.password, password = registration.password,
email = registration.email, email = registration.email
roles = roles.map { r -> roleService.toRole(r.roleName) }
) )
return userService.registerUser(user) return userService.registerUser(user, roles)
} }
} }
@@ -1,5 +1,6 @@
package de.grimsi.gameyfin.users package de.grimsi.gameyfin.users
import de.grimsi.gameyfin.config.Roles
import de.grimsi.gameyfin.users.dto.UserInfo import de.grimsi.gameyfin.users.dto.UserInfo
import de.grimsi.gameyfin.users.entities.Role import de.grimsi.gameyfin.users.entities.Role
import de.grimsi.gameyfin.users.entities.User import de.grimsi.gameyfin.users.entities.User
@@ -18,7 +19,8 @@ import org.springframework.stereotype.Service
@Transactional @Transactional
class UserService( class UserService(
private val userRepository: UserRepository, private val userRepository: UserRepository,
private val passwordEncoder: PasswordEncoder private val passwordEncoder: PasswordEncoder,
private val roleService: RoleService
) : UserDetailsService { ) : UserDetailsService {
override fun loadUserByUsername(username: String): UserDetails { 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<Roles>): User {
user.password = passwordEncoder.encode(user.password) user.password = passwordEncoder.encode(user.password)
user.roles = roleService.toRoles(roles)
return userRepository.save(user) return userRepository.save(user)
} }
@@ -33,5 +33,5 @@ class User(
joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")], joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")],
inverseJoinColumns = [JoinColumn(name = "role_id", referencedColumnName = "id")] inverseJoinColumns = [JoinColumn(name = "role_id", referencedColumnName = "id")]
) )
var roles: Collection<Role> var roles: Collection<Role> = emptyList()
) )