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
+55 -42
View File
@@ -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 = () => (
<div className="flex size-full gradient-primary">
<Card className="w-3/4 h-3/4 min-w-[500px] m-auto p-8">
<Wizard
initialValues={{username: '', email: '', password: '', passwordRepeat: ''}}
onSubmit={(values: any) => UserEndpoint.registerInitialSuperAdmin({
username: values.username,
password: values.password,
email: values.email
function SetupView() {
const navigate = useNavigate();
return (
<div className="flex size-full gradient-primary">
<Card className="w-3/4 h-3/4 min-w-[500px] m-auto p-8">
<Wizard
initialValues={{username: '', email: '', password: '', passwordRepeat: ''}}
onSubmit={
async (values: any) => {
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!"))}
>
<WizardStep icon={<HandWaving/>}>
<WelcomeStep/>
</WizardStep>
<WizardStep icon={<Palette/>}>
<ThemeStep/>
</WizardStep>
<WizardStep
validationSchema={Yup.object({
username: Yup.string()
.required('Required'),
password: Yup.string()
.min(8, 'Password must be at least 8 characters long')
.required('Required'),
email: Yup.string()
.email()
.required('Required'),
passwordRepeat: Yup.string()
.equals([Yup.ref('password')], 'Passwords do not match')
.required('Required')
})}
icon={<User/>}
>
<UserStep/>
</WizardStep>
<WizardStep icon={<GearFine/>}>
<SettingsStep/>
</WizardStep>
</Wizard>
</Card>
</div>
);
<WizardStep icon={<HandWaving/>}>
<WelcomeStep/>
</WizardStep>
<WizardStep icon={<Palette/>}>
<ThemeStep/>
</WizardStep>
<WizardStep
validationSchema={Yup.object({
username: Yup.string()
.required('Required'),
password: Yup.string()
.min(8, 'Password must be at least 8 characters long')
.required('Required'),
email: Yup.string()
.email()
.required('Required'),
passwordRepeat: Yup.string()
.equals([Yup.ref('password')], 'Passwords do not match')
.required('Required')
})}
icon={<User/>}
>
<UserStep/>
</WizardStep>
<WizardStep icon={<GearFine/>}>
<SettingsStep/>
</WizardStep>
</Wizard>
</Card>
</div>
);
}
export default SetupView;
@@ -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() {
@@ -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.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)
}
}
@@ -20,11 +20,12 @@ class RoleService(
return r.users.size
}
fun toRoles(roles: Collection<String>): List<Role> {
return roles.mapNotNull { r -> roleRepository.findByRolename(r) }
fun toRoles(roles: Collection<Roles>): List<Role> {
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")
}
}
@@ -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<Roles>): 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)
}
}
@@ -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<Roles>): User {
user.password = passwordEncoder.encode(user.password)
user.roles = roleService.toRoles(roles)
return userRepository.save(user)
}
@@ -33,5 +33,5 @@ class User(
joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")],
inverseJoinColumns = [JoinColumn(name = "role_id", referencedColumnName = "id")]
)
var roles: Collection<Role>
var roles: Collection<Role> = emptyList()
)