mirror of
https://github.com/BrenBroZAYT/gameyfin.git
synced 2026-06-17 00:30:04 +00:00
Minor bugfixes in setup process
Improved UX with small hints
This commit is contained in:
@@ -7,16 +7,21 @@ import React, {useEffect, useState} from "react";
|
|||||||
import {useAuth} from "Frontend/util/auth";
|
import {useAuth} from "Frontend/util/auth";
|
||||||
import * as Yup from "yup";
|
import * as Yup from "yup";
|
||||||
import UserUpdateDto from "Frontend/generated/de/grimsi/gameyfin/users/dto/UserUpdateDto";
|
import UserUpdateDto from "Frontend/generated/de/grimsi/gameyfin/users/dto/UserUpdateDto";
|
||||||
import {EmailConfirmationEndpoint, UserEndpoint} from "Frontend/generated/endpoints";
|
import {EmailConfirmationEndpoint, MessageEndpoint, UserEndpoint} from "Frontend/generated/endpoints";
|
||||||
import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
|
import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
|
||||||
import {toast} from "sonner";
|
import {toast} from "sonner";
|
||||||
import {removeAvatar, uploadAvatar} from "Frontend/endpoints/AvatarEndpoint";
|
import {removeAvatar, uploadAvatar} from "Frontend/endpoints/AvatarEndpoint";
|
||||||
import Avatar from "Frontend/components/general/Avatar";
|
import Avatar from "Frontend/components/general/Avatar";
|
||||||
|
|
||||||
export default function ProfileManagement() {
|
export default function ProfileManagement() {
|
||||||
const [configSaved, setConfigSaved] = useState(false);
|
|
||||||
const auth = useAuth();
|
const auth = useAuth();
|
||||||
const [avatar, setAvatar] = useState<any>();
|
const [avatar, setAvatar] = useState<any>();
|
||||||
|
const [configSaved, setConfigSaved] = useState(false);
|
||||||
|
const [messagesEnabled, setMessagesEnabled] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
MessageEndpoint.isEnabled().then(setMessagesEnabled);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (configSaved) {
|
if (configSaved) {
|
||||||
@@ -122,8 +127,8 @@ export default function ProfileManagement() {
|
|||||||
isDisabled={auth.state.user?.managedBySso}/>
|
isDisabled={auth.state.user?.managedBySso}/>
|
||||||
<div className="flex flex-row gap-4">
|
<div className="flex flex-row gap-4">
|
||||||
<Input name="email" label="Email" type="email" autocomplete="email"
|
<Input name="email" label="Email" type="email" autocomplete="email"
|
||||||
isDisabled={auth.state.user?.managedBySso}/>
|
isDisabled={auth.state.user?.managedBySso || !messagesEnabled}/>
|
||||||
{auth.state.user?.emailConfirmed === false &&
|
{(auth.state.user?.emailConfirmed === false && !auth.state.user.managedBySso) &&
|
||||||
<Tooltip content="Resend email confirmation message">
|
<Tooltip content="Resend email confirmation message">
|
||||||
<Button isIconOnly
|
<Button isIconOnly
|
||||||
onPress={() => {
|
onPress={() => {
|
||||||
@@ -131,6 +136,7 @@ export default function ProfileManagement() {
|
|||||||
() => toast.success("You will receive an email shortly")
|
() => toast.success("You will receive an email shortly")
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
|
isDisabled={!messagesEnabled}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
className="size-14"
|
className="size-14"
|
||||||
>
|
>
|
||||||
@@ -139,6 +145,14 @@ export default function ProfileManagement() {
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
{!messagesEnabled &&
|
||||||
|
<div className="flex flex-row gap-2 text-warning -mt-5">
|
||||||
|
<Info/>
|
||||||
|
<small>
|
||||||
|
Email services are disabled. Please contact your administrator.
|
||||||
|
</small>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
<Section title="Security"/>
|
<Section title="Security"/>
|
||||||
<Input name="newPassword" label="New Password" type="password"
|
<Input name="newPassword" label="New Password" type="password"
|
||||||
autocomplete="new-password" isDisabled={auth.state.user?.managedBySso}/>
|
autocomplete="new-password" isDisabled={auth.state.user?.managedBySso}/>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import * as Yup from 'yup';
|
|||||||
import Wizard from "Frontend/components/wizard/Wizard";
|
import Wizard from "Frontend/components/wizard/Wizard";
|
||||||
import WizardStep from "Frontend/components/wizard/WizardStep";
|
import WizardStep from "Frontend/components/wizard/WizardStep";
|
||||||
import Input from "Frontend/components/general/Input";
|
import Input from "Frontend/components/general/Input";
|
||||||
import {GearFine, HandWaving, Palette, User} from "@phosphor-icons/react";
|
import {HandWaving, Palette, User} from "@phosphor-icons/react";
|
||||||
import {Card} from "@nextui-org/react";
|
import {Card} from "@nextui-org/react";
|
||||||
import {SetupEndpoint} from "Frontend/generated/endpoints";
|
import {SetupEndpoint} from "Frontend/generated/endpoints";
|
||||||
import {ThemeSelector} from "Frontend/components/theming/ThemeSelector";
|
import {ThemeSelector} from "Frontend/components/theming/ThemeSelector";
|
||||||
@@ -76,17 +76,6 @@ function UserStep() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SettingsStep() {
|
|
||||||
return (
|
|
||||||
<div className="flex flex-col size-full items-center">
|
|
||||||
<div className="flex flex-col w-1/2 min-w-[468px] gap-12 items-center">
|
|
||||||
<h4>Settings</h4>
|
|
||||||
<p>Configure your settings</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function SetupView() {
|
function SetupView() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
@@ -131,9 +120,6 @@ function SetupView() {
|
|||||||
>
|
>
|
||||||
<UserStep/>
|
<UserStep/>
|
||||||
</WizardStep>
|
</WizardStep>
|
||||||
<WizardStep icon={<GearFine/>}>
|
|
||||||
<SettingsStep/>
|
|
||||||
</WizardStep>
|
|
||||||
</Wizard>
|
</Wizard>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,18 +3,12 @@ package de.grimsi.gameyfin.setup
|
|||||||
import com.vaadin.flow.server.auth.AnonymousAllowed
|
import com.vaadin.flow.server.auth.AnonymousAllowed
|
||||||
import com.vaadin.hilla.Endpoint
|
import com.vaadin.hilla.Endpoint
|
||||||
import com.vaadin.hilla.exception.EndpointException
|
import com.vaadin.hilla.exception.EndpointException
|
||||||
import de.grimsi.gameyfin.core.Roles
|
|
||||||
import de.grimsi.gameyfin.users.RoleService
|
|
||||||
import de.grimsi.gameyfin.users.UserService
|
|
||||||
import de.grimsi.gameyfin.users.dto.UserInfoDto
|
import de.grimsi.gameyfin.users.dto.UserInfoDto
|
||||||
import de.grimsi.gameyfin.users.dto.UserRegistrationDto
|
import de.grimsi.gameyfin.users.dto.UserRegistrationDto
|
||||||
import de.grimsi.gameyfin.users.entities.User
|
|
||||||
|
|
||||||
@Endpoint
|
@Endpoint
|
||||||
class SetupEndpoint(
|
class SetupEndpoint(
|
||||||
private val setupService: SetupService,
|
private val setupService: SetupService
|
||||||
private val roleService: RoleService,
|
|
||||||
private val userService: UserService
|
|
||||||
) {
|
) {
|
||||||
@AnonymousAllowed
|
@AnonymousAllowed
|
||||||
fun isSetupCompleted(): Boolean {
|
fun isSetupCompleted(): Boolean {
|
||||||
@@ -24,15 +18,6 @@ class SetupEndpoint(
|
|||||||
@AnonymousAllowed
|
@AnonymousAllowed
|
||||||
fun registerSuperAdmin(superAdminRegistration: UserRegistrationDto): UserInfoDto {
|
fun registerSuperAdmin(superAdminRegistration: UserRegistrationDto): UserInfoDto {
|
||||||
if (setupService.isSetupCompleted()) throw EndpointException("Setup already completed")
|
if (setupService.isSetupCompleted()) throw EndpointException("Setup already completed")
|
||||||
|
return setupService.createInitialAdminUser(superAdminRegistration)
|
||||||
val user = User(
|
|
||||||
username = superAdminRegistration.username,
|
|
||||||
password = superAdminRegistration.password,
|
|
||||||
email = superAdminRegistration.email,
|
|
||||||
roles = setOf(roleService.toRole(Roles.SUPERADMIN))
|
|
||||||
)
|
|
||||||
|
|
||||||
val superAdmin = setupService.createInitialAdminUser(user)
|
|
||||||
return userService.toUserInfo(superAdmin)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3,6 +3,8 @@ package de.grimsi.gameyfin.setup
|
|||||||
import de.grimsi.gameyfin.core.Roles
|
import de.grimsi.gameyfin.core.Roles
|
||||||
import de.grimsi.gameyfin.users.RoleService
|
import de.grimsi.gameyfin.users.RoleService
|
||||||
import de.grimsi.gameyfin.users.UserService
|
import de.grimsi.gameyfin.users.UserService
|
||||||
|
import de.grimsi.gameyfin.users.dto.UserInfoDto
|
||||||
|
import de.grimsi.gameyfin.users.dto.UserRegistrationDto
|
||||||
import de.grimsi.gameyfin.users.entities.User
|
import de.grimsi.gameyfin.users.entities.User
|
||||||
import org.springframework.stereotype.Service
|
import org.springframework.stereotype.Service
|
||||||
|
|
||||||
@@ -24,7 +26,16 @@ class SetupService(
|
|||||||
/**
|
/**
|
||||||
* Creates the initial user with Super-Admin permissions
|
* Creates the initial user with Super-Admin permissions
|
||||||
*/
|
*/
|
||||||
fun createInitialAdminUser(superAdmin: User): User {
|
fun createInitialAdminUser(registration: UserRegistrationDto): UserInfoDto {
|
||||||
return userService.registerOrUpdateUser(superAdmin, Roles.SUPERADMIN)
|
val superAdmin = User(
|
||||||
|
username = registration.username,
|
||||||
|
password = registration.password,
|
||||||
|
email = registration.email,
|
||||||
|
roles = setOf(roleService.toRole(Roles.SUPERADMIN)),
|
||||||
|
enabled = true
|
||||||
|
)
|
||||||
|
|
||||||
|
val user = userService.registerOrUpdateUser(superAdmin, Roles.SUPERADMIN)
|
||||||
|
return userService.toUserInfo(user)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,6 @@ package de.grimsi.gameyfin.shared.token
|
|||||||
|
|
||||||
import de.grimsi.gameyfin.users.entities.User
|
import de.grimsi.gameyfin.users.entities.User
|
||||||
import io.github.oshai.kotlinlogging.KotlinLogging
|
import io.github.oshai.kotlinlogging.KotlinLogging
|
||||||
import jakarta.transaction.Transactional
|
|
||||||
|
|
||||||
abstract class TokenService<T : TokenType>(
|
abstract class TokenService<T : TokenType>(
|
||||||
private val type: T,
|
private val type: T,
|
||||||
@@ -11,7 +10,6 @@ abstract class TokenService<T : TokenType>(
|
|||||||
|
|
||||||
private val log = KotlinLogging.logger {}
|
private val log = KotlinLogging.logger {}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
open fun generate(user: User): Token<T> {
|
open fun generate(user: User): Token<T> {
|
||||||
val token = Token(
|
val token = Token(
|
||||||
creator = user,
|
creator = user,
|
||||||
@@ -26,7 +24,6 @@ abstract class TokenService<T : TokenType>(
|
|||||||
return tokenRepository.save(token)
|
return tokenRepository.save(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
open fun generateWithPayload(user: User, payload: Map<String, String>): Token<T> {
|
open fun generateWithPayload(user: User, payload: Map<String, String>): Token<T> {
|
||||||
val token = Token(
|
val token = Token(
|
||||||
creator = user,
|
creator = user,
|
||||||
@@ -42,7 +39,6 @@ abstract class TokenService<T : TokenType>(
|
|||||||
return tokenRepository.save(token)
|
return tokenRepository.save(token)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
open fun get(secret: String, type: T): Token<T>? {
|
open fun get(secret: String, type: T): Token<T>? {
|
||||||
val token = tokenRepository.findBySecret(secret) ?: return null
|
val token = tokenRepository.findBySecret(secret) ?: return null
|
||||||
|
|
||||||
@@ -55,12 +51,10 @@ abstract class TokenService<T : TokenType>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
open fun getPayload(secret: String): Map<String, String>? {
|
open fun getPayload(secret: String): Map<String, String>? {
|
||||||
return tokenRepository.findBySecret(secret)?.payload
|
return tokenRepository.findBySecret(secret)?.payload
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
open fun delete(token: Token<T>) {
|
open fun delete(token: Token<T>) {
|
||||||
try {
|
try {
|
||||||
tokenRepository.delete(token)
|
tokenRepository.delete(token)
|
||||||
@@ -69,7 +63,6 @@ abstract class TokenService<T : TokenType>(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Transactional
|
|
||||||
open fun validate(secret: String): TokenValidationResult {
|
open fun validate(secret: String): TokenValidationResult {
|
||||||
val token = tokenRepository.findBySecret(secret) ?: return TokenValidationResult.INVALID
|
val token = tokenRepository.findBySecret(secret) ?: return TokenValidationResult.INVALID
|
||||||
return if (token.expired) TokenValidationResult.EXPIRED else TokenValidationResult.VALID
|
return if (token.expired) TokenValidationResult.EXPIRED else TokenValidationResult.VALID
|
||||||
|
|||||||
@@ -179,10 +179,10 @@ class UserService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun registerUserFromInvitation(user: UserRegistrationDto, email: String): User {
|
fun registerUserFromInvitation(registration: UserRegistrationDto, email: String): User {
|
||||||
val user = User(
|
val user = User(
|
||||||
username = user.username,
|
username = registration.username,
|
||||||
password = passwordEncoder.encode(user.password),
|
password = passwordEncoder.encode(registration.password),
|
||||||
email = email,
|
email = email,
|
||||||
emailConfirmed = true,
|
emailConfirmed = true,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package de.grimsi.gameyfin.users.registration
|
|||||||
|
|
||||||
import de.grimsi.gameyfin.core.Utils
|
import de.grimsi.gameyfin.core.Utils
|
||||||
import de.grimsi.gameyfin.core.events.UserInvitationEvent
|
import de.grimsi.gameyfin.core.events.UserInvitationEvent
|
||||||
|
import de.grimsi.gameyfin.core.events.UserRegistrationEvent
|
||||||
import de.grimsi.gameyfin.shared.token.TokenDto
|
import de.grimsi.gameyfin.shared.token.TokenDto
|
||||||
import de.grimsi.gameyfin.shared.token.TokenRepository
|
import de.grimsi.gameyfin.shared.token.TokenRepository
|
||||||
import de.grimsi.gameyfin.shared.token.TokenService
|
import de.grimsi.gameyfin.shared.token.TokenService
|
||||||
@@ -48,7 +49,10 @@ class InvitationService(
|
|||||||
val email = invitationToken.payload[EMAIL_KEY] ?: return TokenValidationResult.INVALID
|
val email = invitationToken.payload[EMAIL_KEY] ?: return TokenValidationResult.INVALID
|
||||||
if (invitationToken.expired) return TokenValidationResult.EXPIRED
|
if (invitationToken.expired) return TokenValidationResult.EXPIRED
|
||||||
|
|
||||||
userService.registerUserFromInvitation(registration, email)
|
val user = userService.registerUserFromInvitation(registration, email)
|
||||||
|
super.delete(invitationToken)
|
||||||
|
eventPublisher.publishEvent(UserRegistrationEvent(this, user, Utils.getBaseUrl()))
|
||||||
|
|
||||||
return TokenValidationResult.VALID
|
return TokenValidationResult.VALID
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user