diff --git a/src/main/frontend/components/administration/ProfileManagement.tsx b/src/main/frontend/components/administration/ProfileManagement.tsx
index d7b9b7b..d77a363 100644
--- a/src/main/frontend/components/administration/ProfileManagement.tsx
+++ b/src/main/frontend/components/administration/ProfileManagement.tsx
@@ -2,12 +2,12 @@ import Section from "Frontend/components/general/Section";
import Input from "Frontend/components/general/Input";
import {Button, Input as NextUiInput, Tooltip} from "@nextui-org/react";
import {Form, Formik} from "formik";
-import {Check, Info, Trash} from "@phosphor-icons/react";
+import {ArrowCounterClockwise, Check, Info, Trash} from "@phosphor-icons/react";
import React, {useEffect, useState} from "react";
import {useAuth} from "Frontend/util/auth";
import * as Yup from "yup";
import UserUpdateDto from "Frontend/generated/de/grimsi/gameyfin/users/dto/UserUpdateDto";
-import {UserEndpoint} from "Frontend/generated/endpoints";
+import {EmailConfirmationEndpoint, UserEndpoint} from "Frontend/generated/endpoints";
import {SmallInfoField} from "Frontend/components/general/SmallInfoField";
import {toast} from "sonner";
import {removeAvatar, uploadAvatar} from "Frontend/endpoints/AvatarEndpoint";
@@ -120,8 +120,25 @@ export default function ProfileManagement() {
-
+
+
+ {auth.state.user?.emailConfirmed === false &&
+
+
+
+ }
+
diff --git a/src/main/frontend/routes.tsx b/src/main/frontend/routes.tsx
index f7b3cdc..fca568f 100644
--- a/src/main/frontend/routes.tsx
+++ b/src/main/frontend/routes.tsx
@@ -15,6 +15,7 @@ import {ProfileView} from "Frontend/views/ProfileView";
import {MessageManagement} from "Frontend/components/administration/MessageManagement";
import {LogManagement} from "Frontend/components/administration/LogManagement";
import PasswordResetView from "Frontend/views/PasswordResetView";
+import EmailConfirmationView from "Frontend/views/EmailConfirmationView";
export const routes = protectRoutes([
{
@@ -57,6 +58,9 @@ export const routes = protectRoutes([
},
{
path: '/reset-password', element: , handle: {requiresLogin: false}
+ },
+ {
+ path: '/confirm-email', element: , handle: {requiresLogin: true}
}
],
}
diff --git a/src/main/frontend/views/EmailConfirmationView.tsx b/src/main/frontend/views/EmailConfirmationView.tsx
new file mode 100644
index 0000000..ced45bd
--- /dev/null
+++ b/src/main/frontend/views/EmailConfirmationView.tsx
@@ -0,0 +1,76 @@
+import {Card, CardBody, CardHeader} from "@nextui-org/react";
+import {useNavigate, useSearchParams} from "react-router-dom";
+import React, {useEffect, useState} from "react";
+import {CheckCircle, Warning, WarningCircle} from "@phosphor-icons/react";
+import TokenValidationResult from "Frontend/generated/de/grimsi/gameyfin/shared/token/TokenValidationResult";
+import {EmailConfirmationEndpoint} from "Frontend/generated/endpoints";
+import {useAuth} from "Frontend/util/auth";
+
+export default function EmailConfirmationView() {
+ const auth = useAuth();
+ const [searchParams, setSearchParams] = useSearchParams();
+ const navigate = useNavigate();
+ const [validationResult, setValidationResult] = useState(TokenValidationResult.INVALID);
+
+ useEffect(() => {
+ if (auth.state.user?.emailConfirmed === true) {
+ navigate("/");
+ }
+ }, []);
+
+ useEffect(() => {
+ let token = searchParams.get("token");
+ if (token) confirmEmail(token).then((result) => setValidationResult(result));
+ }, [searchParams]);
+
+ async function confirmEmail(token: string): Promise {
+ let result = await EmailConfirmationEndpoint.confirmEmail(token) as TokenValidationResult;
+
+ if (result === TokenValidationResult.VALID) {
+ setTimeout(() => window.location.reload(), 5000);
+ }
+
+ return result;
+ }
+
+ return (
+
+
+
+
+
+
+ {validationResult === TokenValidationResult.VALID ?
+
+
+
+ Email confirmed
+ You will be redirected shortly
+
+
+ : validationResult === TokenValidationResult.EXPIRED ?
+
+
+
+ Expired token
+ Please request a new one
+
+
+ :
+
+
+
+ Invalid token
+ Please try again
+
+
+ }
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/main/frontend/views/MainLayout.tsx b/src/main/frontend/views/MainLayout.tsx
index 10f8507..64f2023 100644
--- a/src/main/frontend/views/MainLayout.tsx
+++ b/src/main/frontend/views/MainLayout.tsx
@@ -5,13 +5,17 @@ import {Divider, Link, Navbar, NavbarBrand, NavbarContent, NavbarItem} from "@ne
import GameyfinLogo from "Frontend/components/theming/GameyfinLogo";
import * as PackageJson from "../../../../package.json";
import {Outlet, useNavigate} from "react-router-dom";
+import {useAuth} from "Frontend/util/auth";
export default function MainLayout() {
- const currentTitle = `Gameyfin - ${useRouteMetadata()?.title}` ?? 'Gameyfin';
- useEffect(() => {
- document.title = currentTitle;
- }, [currentTitle]);
const navigate = useNavigate();
+ const auth = useAuth();
+ const routeMetadata = useRouteMetadata();
+
+ useEffect(() => {
+ let newTitle = `Gameyfin - ${routeMetadata?.title}` ?? 'Gameyfin';
+ window.addEventListener('popstate', () => document.title = newTitle);
+ }, []);
return (
@@ -21,6 +25,13 @@ export default function MainLayout() {
+ {auth.state.user?.emailConfirmed === false ?
+
+ Please confirm your email
+
+ :
+ ""
+ }
diff --git a/src/main/kotlin/de/grimsi/gameyfin/core/SetupDataLoader.kt b/src/main/kotlin/de/grimsi/gameyfin/core/SetupDataLoader.kt
index 2c54f83..899412d 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/core/SetupDataLoader.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/core/SetupDataLoader.kt
@@ -70,7 +70,9 @@ class SetupDataLoader(
val superadmin = User(
username = "admin",
password = "admin",
- email = "admin@gameyfin.org"
+ email = "admin@gameyfin.org",
+ emailConfirmed = true,
+ enabled = true
)
registerUserIfNotFound(superadmin, Roles.SUPERADMIN)
@@ -78,7 +80,9 @@ class SetupDataLoader(
val user = User(
username = "user",
password = "user",
- email = "user@gameyfin.org"
+ email = "user@gameyfin.org",
+ emailConfirmed = true,
+ enabled = true
)
registerUserIfNotFound(user, Roles.USER)
diff --git a/src/main/kotlin/de/grimsi/gameyfin/core/events/Events.kt b/src/main/kotlin/de/grimsi/gameyfin/core/events/Events.kt
index 40e90fb..fe25f95 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/core/events/Events.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/core/events/Events.kt
@@ -1,6 +1,7 @@
package de.grimsi.gameyfin.core.events
import de.grimsi.gameyfin.shared.token.Token
+import de.grimsi.gameyfin.shared.token.TokenType.EmailConfirmation
import de.grimsi.gameyfin.shared.token.TokenType.PasswordReset
import de.grimsi.gameyfin.users.entities.User
import org.springframework.context.ApplicationEvent
@@ -11,6 +12,9 @@ class UserRegistrationWaitingForApprovalEvent(source: Any, val newUser: User) :
class UserRegistrationEvent(source: Any, val newUser: User, val baseUrl: String) : ApplicationEvent(source)
+class EmailNeedsConfirmationEvent(source: Any, val token: Token, val baseUrl: String) :
+ ApplicationEvent(source)
+
class RegistrationAttemptWithExistingEmailEvent(source: Any, val existingUser: User, val baseUrl: String) :
ApplicationEvent(source)
diff --git a/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt b/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt
index cb41a94..9cc497b 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/core/security/SecurityConfig.kt
@@ -37,6 +37,7 @@ class SecurityConfig(
http.authorizeHttpRequests { auth: AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry ->
auth.requestMatchers("/setup").permitAll()
.requestMatchers("/reset-password").permitAll()
+ .requestMatchers("/accept-invitation").permitAll()
.requestMatchers("/public/**").permitAll()
.requestMatchers("/images/**").permitAll()
}
@@ -70,7 +71,6 @@ class SecurityConfig(
}
}
- // TODO: Maybe switch to a database-backed client registration repository? Not sure if worth it.
@Bean
@Conditional(SsoEnabledCondition::class)
fun clientRegistrationRepository(): ClientRegistrationRepository? {
diff --git a/src/main/kotlin/de/grimsi/gameyfin/messages/MessageService.kt b/src/main/kotlin/de/grimsi/gameyfin/messages/MessageService.kt
index 2cc65b7..aec86a3 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/messages/MessageService.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/messages/MessageService.kt
@@ -1,9 +1,6 @@
package de.grimsi.gameyfin.messages
-import de.grimsi.gameyfin.core.events.PasswordResetRequestEvent
-import de.grimsi.gameyfin.core.events.RegistrationAttemptWithExistingEmailEvent
-import de.grimsi.gameyfin.core.events.UserRegistrationEvent
-import de.grimsi.gameyfin.core.events.UserRegistrationWaitingForApprovalEvent
+import de.grimsi.gameyfin.core.events.*
import de.grimsi.gameyfin.messages.providers.AbstractMessageProvider
import de.grimsi.gameyfin.messages.templates.MessageTemplateService
import de.grimsi.gameyfin.messages.templates.MessageTemplates
@@ -158,4 +155,25 @@ class MessageService(
mapOf("username" to user.username, "passwordResetLink" to event.baseUrl)
)
}
+
+ @Async
+ @EventListener(EmailNeedsConfirmationEvent::class)
+ fun onEmailNeedsConfirmation(event: EmailNeedsConfirmationEvent) {
+
+ if (!enabled) {
+ log.error { "No notification provider available, can't send email confirmation message" }
+ return
+ }
+
+ log.info { "Sending email confirmation notification" }
+
+ val user = event.token.user
+ val confirmationLink = event.baseUrl + "/confirm-email?token=${event.token.secret}"
+ sendNotification(
+ user.email,
+ "[Gameyfin] Email Confirmation",
+ MessageTemplates.EmailConfirmation,
+ mapOf("username" to user.username, "confirmationLink" to confirmationLink)
+ )
+ }
}
\ No newline at end of file
diff --git a/src/main/kotlin/de/grimsi/gameyfin/shared/token/Token.kt b/src/main/kotlin/de/grimsi/gameyfin/shared/token/Token.kt
index 85201e9..ff4ef41 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/shared/token/Token.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/shared/token/Token.kt
@@ -2,15 +2,11 @@ package de.grimsi.gameyfin.shared.token
import de.grimsi.gameyfin.core.security.EncryptionConverter
import de.grimsi.gameyfin.users.entities.User
-import jakarta.persistence.Convert
-import jakarta.persistence.Entity
-import jakarta.persistence.FetchType
-import jakarta.persistence.Id
-import jakarta.persistence.OneToOne
+import jakarta.persistence.*
import org.hibernate.annotations.CreationTimestamp
import org.hibernate.annotations.Type
import java.time.Instant
-import java.util.UUID
+import java.util.*
import kotlin.time.toJavaDuration
@Entity
@@ -29,5 +25,6 @@ class Token(
val createdOn: Instant? = null
) {
val expired: Boolean
- get() = createdOn?.plus(type.expiration.toJavaDuration())!!.isBefore(Instant.now())
+ get() = type.expiration.isFinite() &&
+ createdOn?.plus(type.expiration.toJavaDuration())!!.isBefore(Instant.now())
}
\ No newline at end of file
diff --git a/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenService.kt b/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenService.kt
index ffbc95d..85df0fb 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenService.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenService.kt
@@ -2,6 +2,7 @@ package de.grimsi.gameyfin.shared.token
import de.grimsi.gameyfin.users.entities.User
import io.github.oshai.kotlinlogging.KotlinLogging
+import jakarta.transaction.Transactional
abstract class TokenService(
private val type: T,
@@ -10,6 +11,7 @@ abstract class TokenService(
private val log = KotlinLogging.logger {}
+ @Transactional
open fun generate(user: User): Token {
val token = Token(
user = user,
@@ -24,7 +26,8 @@ abstract class TokenService(
return tokenRepository.save(token)
}
- fun get(secret: String, type: T): Token? {
+ @Transactional
+ open fun get(secret: String, type: T): Token? {
val token = tokenRepository.findBySecret(secret) ?: return null
return if (token.type == type) {
@@ -36,11 +39,17 @@ abstract class TokenService(
}
}
- fun delete(token: Token) {
- tokenRepository.delete(token)
+ @Transactional
+ open fun delete(token: Token) {
+ try {
+ tokenRepository.delete(token)
+ } catch (_: Exception) {
+ log.warn { "Token '$token' has already been deleted" }
+ }
}
- fun validate(secret: String): TokenValidationResult {
+ @Transactional
+ open fun validate(secret: String): TokenValidationResult {
val token = tokenRepository.findBySecret(secret) ?: return TokenValidationResult.INVALID
return if (token.expired) TokenValidationResult.EXPIRED else TokenValidationResult.VALID
}
diff --git a/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenType.kt b/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenType.kt
index bbc916a..57bf6b9 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenType.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenType.kt
@@ -8,6 +8,6 @@ sealed class TokenType(
val expiration: Duration
) {
data object PasswordReset : TokenType("password-reset", 15.minutes)
- data object EmailVerification : TokenType("email-verification", Duration.INFINITE)
+ data object EmailConfirmation : TokenType("email-verification", Duration.INFINITE)
data object Invitation : TokenType("invitation", Duration.INFINITE)
}
diff --git a/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenTypeUserType.kt b/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenTypeUserType.kt
index 50140d5..c6ebfd5 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenTypeUserType.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/shared/token/TokenTypeUserType.kt
@@ -14,7 +14,11 @@ class TokenTypeUserType : UserType {
override fun returnedClass(): Class = TokenType::class.java
- override fun equals(x: TokenType, y: TokenType): Boolean = x.key == y.key
+ override fun equals(x: TokenType?, y: TokenType?): Boolean {
+ if (x === y) return true
+ if (x == null || y == null) return false
+ return x.key == y.key
+ }
override fun hashCode(x: TokenType): Int = x.key.hashCode()
@@ -52,5 +56,13 @@ class TokenTypeUserType : UserType {
override fun disassemble(value: TokenType): Serializable = value.key
- override fun assemble(cached: Serializable, owner: Any?): TokenType = cached as TokenType
+ override fun assemble(cached: Serializable, owner: Any?): TokenType {
+ val key = cached as? String ?: throw IllegalArgumentException("Invalid cached value: $cached")
+ val tokenTypeClass = TokenType::class
+
+ return tokenTypeClass.sealedSubclasses
+ .map { it.objectInstance ?: it.createInstance() }
+ .firstOrNull { it.key == key }
+ ?: throw IllegalArgumentException("Unknown TokenType: $key")
+ }
}
\ 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 61540ad..6c67b8f 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/users/UserService.kt
@@ -4,12 +4,14 @@ import de.grimsi.gameyfin.config.ConfigProperties
import de.grimsi.gameyfin.config.ConfigService
import de.grimsi.gameyfin.core.Roles
import de.grimsi.gameyfin.core.Utils
+import de.grimsi.gameyfin.core.events.EmailNeedsConfirmationEvent
import de.grimsi.gameyfin.core.events.RegistrationAttemptWithExistingEmailEvent
import de.grimsi.gameyfin.core.events.UserRegistrationEvent
import de.grimsi.gameyfin.core.events.UserRegistrationWaitingForApprovalEvent
import de.grimsi.gameyfin.users.dto.UserInfoDto
import de.grimsi.gameyfin.users.dto.UserRegistrationDto
import de.grimsi.gameyfin.users.dto.UserUpdateDto
+import de.grimsi.gameyfin.users.emailconfirmation.EmailConfirmationService
import de.grimsi.gameyfin.users.entities.Avatar
import de.grimsi.gameyfin.users.entities.Role
import de.grimsi.gameyfin.users.entities.User
@@ -39,6 +41,7 @@ class UserService(
private val passwordEncoder: PasswordEncoder,
private val roleService: RoleService,
private val sessionService: SessionService,
+ private val emailConfirmationService: EmailConfirmationService,
private val config: ConfigService,
private val eventPublisher: ApplicationEventPublisher
) : UserDetailsService {
@@ -168,6 +171,11 @@ class UserService(
} else {
eventPublisher.publishEvent(UserRegistrationEvent(this, user, Utils.getBaseUrl()))
}
+
+ if (!user.emailConfirmed) {
+ val token = emailConfirmationService.generate(user)
+ eventPublisher.publishEvent(EmailNeedsConfirmationEvent(this, token, Utils.getBaseUrl()))
+ }
}
fun updateUser(username: String, updates: UserUpdateDto) {
@@ -183,6 +191,8 @@ class UserService(
updates.email?.let {
user.email = it
user.emailConfirmed = false
+ val token = emailConfirmationService.generate(user)
+ eventPublisher.publishEvent(EmailNeedsConfirmationEvent(this, token, Utils.getBaseUrl()))
}
userRepository.save(user)
diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/emailconfirmation/EmailConfirmationEndpoint.kt b/src/main/kotlin/de/grimsi/gameyfin/users/emailconfirmation/EmailConfirmationEndpoint.kt
new file mode 100644
index 0000000..8802082
--- /dev/null
+++ b/src/main/kotlin/de/grimsi/gameyfin/users/emailconfirmation/EmailConfirmationEndpoint.kt
@@ -0,0 +1,28 @@
+package de.grimsi.gameyfin.users.emailconfirmation
+
+import com.vaadin.hilla.Endpoint
+import de.grimsi.gameyfin.shared.token.TokenValidationResult
+import de.grimsi.gameyfin.users.UserService
+import jakarta.annotation.security.PermitAll
+import org.springframework.security.core.Authentication
+import org.springframework.security.core.context.SecurityContextHolder
+
+@Endpoint
+class EmailConfirmationEndpoint(
+ private val emailConfirmationService: EmailConfirmationService,
+ private val userService: UserService
+) {
+
+ @PermitAll
+ fun confirmEmail(token: String): TokenValidationResult {
+ return emailConfirmationService.confirmEmail(token)
+ }
+
+ @PermitAll
+ fun resendEmailConfirmation() {
+ val auth: Authentication = SecurityContextHolder.getContext().authentication
+ userService.getByUsername(auth.name)?.let {
+ emailConfirmationService.resendEmailConfirmation(it)
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/emailconfirmation/EmailConfirmationService.kt b/src/main/kotlin/de/grimsi/gameyfin/users/emailconfirmation/EmailConfirmationService.kt
new file mode 100644
index 0000000..e97fc96
--- /dev/null
+++ b/src/main/kotlin/de/grimsi/gameyfin/users/emailconfirmation/EmailConfirmationService.kt
@@ -0,0 +1,54 @@
+package de.grimsi.gameyfin.users.emailconfirmation
+
+import de.grimsi.gameyfin.core.Utils
+import de.grimsi.gameyfin.core.events.EmailNeedsConfirmationEvent
+import de.grimsi.gameyfin.shared.token.TokenRepository
+import de.grimsi.gameyfin.shared.token.TokenService
+import de.grimsi.gameyfin.shared.token.TokenType.EmailConfirmation
+import de.grimsi.gameyfin.shared.token.TokenValidationResult
+import de.grimsi.gameyfin.users.entities.User
+import de.grimsi.gameyfin.users.persistence.UserRepository
+import io.github.oshai.kotlinlogging.KLogger
+import io.github.oshai.kotlinlogging.KotlinLogging
+import org.springframework.context.ApplicationEventPublisher
+import org.springframework.stereotype.Service
+
+@Service
+class EmailConfirmationService(
+ tokenRepository: TokenRepository,
+ private val userRepository: UserRepository,
+ private val eventPublisher: ApplicationEventPublisher
+) : TokenService(EmailConfirmation, tokenRepository) {
+
+ val log: KLogger = KotlinLogging.logger {}
+
+ fun confirmEmail(secret: String): TokenValidationResult {
+ val emailConfirmationToken = get(secret, EmailConfirmation)
+ ?: return TokenValidationResult.INVALID
+
+ if (emailConfirmationToken.expired) {
+ return TokenValidationResult.EXPIRED
+ }
+
+ val user = emailConfirmationToken.user
+ confirmEmail(user)
+ delete(emailConfirmationToken)
+
+ return TokenValidationResult.VALID
+ }
+
+ fun resendEmailConfirmation(user: User) {
+ if (user.emailConfirmed) {
+ log.error { "User '${user.username}' has already confirmed their email address" }
+ return
+ }
+
+ val token = generate(user)
+ eventPublisher.publishEvent(EmailNeedsConfirmationEvent(user, token, Utils.getBaseUrl()))
+ }
+
+ private fun confirmEmail(user: User) {
+ user.emailConfirmed = true
+ userRepository.save(user)
+ }
+}
\ No newline at end of file
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 c8d538a..2d3d47d 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/users/entities/User.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/users/entities/User.kt
@@ -29,7 +29,7 @@ class User(
var emailConfirmed: Boolean = false,
- var enabled: Boolean = true,
+ var enabled: Boolean = false,
@Embedded
@Nullable
diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/PasswordResetEndpoint.kt b/src/main/kotlin/de/grimsi/gameyfin/users/passwordreset/PasswordResetEndpoint.kt
similarity index 91%
rename from src/main/kotlin/de/grimsi/gameyfin/users/PasswordResetEndpoint.kt
rename to src/main/kotlin/de/grimsi/gameyfin/users/passwordreset/PasswordResetEndpoint.kt
index 31cfab1..f6b89f5 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/users/PasswordResetEndpoint.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/users/passwordreset/PasswordResetEndpoint.kt
@@ -1,10 +1,11 @@
-package de.grimsi.gameyfin.users
+package de.grimsi.gameyfin.users.passwordreset
import com.vaadin.flow.server.auth.AnonymousAllowed
import com.vaadin.hilla.Endpoint
import de.grimsi.gameyfin.core.Roles
import de.grimsi.gameyfin.shared.token.TokenDto
import de.grimsi.gameyfin.shared.token.TokenValidationResult
+import de.grimsi.gameyfin.users.UserService
import jakarta.annotation.security.RolesAllowed
@Endpoint
diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/PasswordResetService.kt b/src/main/kotlin/de/grimsi/gameyfin/users/passwordreset/PasswordResetService.kt
similarity index 92%
rename from src/main/kotlin/de/grimsi/gameyfin/users/PasswordResetService.kt
rename to src/main/kotlin/de/grimsi/gameyfin/users/passwordreset/PasswordResetService.kt
index e85eba4..0526370 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/users/PasswordResetService.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/users/passwordreset/PasswordResetService.kt
@@ -1,10 +1,12 @@
-package de.grimsi.gameyfin.users
+package de.grimsi.gameyfin.users.passwordreset
import de.grimsi.gameyfin.core.Utils
import de.grimsi.gameyfin.core.events.PasswordResetRequestEvent
import de.grimsi.gameyfin.messages.MessageService
import de.grimsi.gameyfin.shared.token.*
import de.grimsi.gameyfin.shared.token.TokenType.PasswordReset
+import de.grimsi.gameyfin.users.SessionService
+import de.grimsi.gameyfin.users.UserService
import de.grimsi.gameyfin.users.entities.User
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.context.ApplicationEventPublisher
@@ -89,8 +91,8 @@ class PasswordResetService(
Thread.sleep(secureRandom.nextLong(1024))
}
- fun resetPassword(token: String, newPassword: String): TokenValidationResult {
- val passwordResetToken = get(token, PasswordReset)
+ fun resetPassword(secret: String, newPassword: String): TokenValidationResult {
+ val passwordResetToken = get(secret, PasswordReset)
?: return TokenValidationResult.INVALID
if (passwordResetToken.expired) {
diff --git a/src/main/kotlin/de/grimsi/gameyfin/users/RegistrationEndpoint.kt b/src/main/kotlin/de/grimsi/gameyfin/users/registration/RegistrationEndpoint.kt
similarity index 90%
rename from src/main/kotlin/de/grimsi/gameyfin/users/RegistrationEndpoint.kt
rename to src/main/kotlin/de/grimsi/gameyfin/users/registration/RegistrationEndpoint.kt
index 786f2bb..46dec4a 100644
--- a/src/main/kotlin/de/grimsi/gameyfin/users/RegistrationEndpoint.kt
+++ b/src/main/kotlin/de/grimsi/gameyfin/users/registration/RegistrationEndpoint.kt
@@ -1,8 +1,9 @@
-package de.grimsi.gameyfin.users
+package de.grimsi.gameyfin.users.registration
import com.vaadin.flow.server.auth.AnonymousAllowed
import com.vaadin.hilla.Endpoint
import de.grimsi.gameyfin.core.Roles
+import de.grimsi.gameyfin.users.UserService
import de.grimsi.gameyfin.users.dto.UserRegistrationDto
import jakarta.annotation.security.RolesAllowed