mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-13 16:40:01 +00:00
Finalize onboarding of super admin user
This commit is contained in:
@@ -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()
|
||||
)
|
||||
Reference in New Issue
Block a user