Fix authentication for websocket based endpoints

This commit is contained in:
grimsi
2025-05-18 09:52:16 +02:00
parent 2f2b18fade
commit 01cc758b07
4 changed files with 27 additions and 16 deletions
+10 -5
View File
@@ -1,12 +1,12 @@
import {useAuth} from "Frontend/util/auth"; import {useAuth} from "Frontend/util/auth";
import {useEffect, useState} from "react"; import {useEffect, useState} from "react";
import {Button, Card, CardBody, CardHeader, Link, useDisclosure} from "@heroui/react"; import {Button, Card, CardBody, CardHeader, Link, useDisclosure} from "@heroui/react";
import {useNavigate} from "react-router";
import {Form, Formik} from "formik"; import {Form, Formik} from "formik";
import Input from "Frontend/components/general/input/Input"; import Input from "Frontend/components/general/input/Input";
import PasswordResetModal from "Frontend/components/general/modals/PasswordResetModal"; import PasswordResetModal from "Frontend/components/general/modals/PasswordResetModal";
import SignUpModal from "Frontend/components/general/modals/SignUpModal"; import SignUpModal from "Frontend/components/general/modals/SignUpModal";
import {RegistrationEndpoint} from "Frontend/generated/endpoints"; import {RegistrationEndpoint} from "Frontend/generated/endpoints";
import {useNavigate} from "react-router";
export default function LoginView() { export default function LoginView() {
const {state, login} = useAuth(); const {state, login} = useAuth();
@@ -15,26 +15,31 @@ export default function LoginView() {
const passwordResetModal = useDisclosure(); const passwordResetModal = useDisclosure();
const signUpModal = useDisclosure(); const signUpModal = useDisclosure();
const [url, setUrl] = useState<string>();
const [signUpAllowed, setSignUpAllowed] = useState<boolean>(false); const [signUpAllowed, setSignUpAllowed] = useState<boolean>(false);
useEffect(() => { useEffect(() => {
if (state.user) { if (state.user) {
const path = url ? new URL(url, document.baseURI).pathname : '/' redirectAfterLogin();
navigate(path, {replace: true});
} else { } else {
RegistrationEndpoint.isSelfRegistrationAllowed().then(setSignUpAllowed); RegistrationEndpoint.isSelfRegistrationAllowed().then(setSignUpAllowed);
} }
}, [state.user]); }, [state.user]);
async function tryLogin(values: any, formik: any) { async function tryLogin(values: any, formik: any) {
const {error} = await login(values.username, values.password); const {defaultUrl, error, redirectUrl} = await login(values.username, values.password);
if (error) { if (error) {
formik.setFieldError("username", " "); // Mark the field red, but don't show an error message formik.setFieldError("username", " "); // Mark the field red, but don't show an error message
formik.setFieldError("password", "Invalid username and/or password."); formik.setFieldError("password", "Invalid username and/or password.");
} else {
redirectAfterLogin(redirectUrl, defaultUrl);
} }
} }
function redirectAfterLogin(redirectUrl?: string, defaultUrl?: string) {
const url = redirectUrl ?? defaultUrl ?? '/';
navigate(url, {replace: true});
}
return ( return (
<div className="flex size-full gradient-primary"> <div className="flex size-full gradient-primary">
<Card className="m-auto p-12"> <Card className="m-auto p-12">
@@ -1,12 +1,14 @@
package de.grimsi.gameyfin.config package de.grimsi.gameyfin.config
import com.vaadin.flow.server.auth.AnonymousAllowed
import com.vaadin.hilla.Endpoint import com.vaadin.hilla.Endpoint
import de.grimsi.gameyfin.config.dto.ConfigEntryDto import de.grimsi.gameyfin.config.dto.ConfigEntryDto
import de.grimsi.gameyfin.config.dto.ConfigUpdateDto import de.grimsi.gameyfin.config.dto.ConfigUpdateDto
import de.grimsi.gameyfin.core.Role import de.grimsi.gameyfin.core.Role
import de.grimsi.gameyfin.users.util.isAdmin
import jakarta.annotation.security.PermitAll import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed import jakarta.annotation.security.RolesAllowed
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
import reactor.core.publisher.Sinks import reactor.core.publisher.Sinks
@@ -23,9 +25,12 @@ class ConfigEndpoint(
return config.getAll(null) return config.getAll(null)
} }
// FIXME @PermitAll
@AnonymousAllowed fun subscribe(): Flux<ConfigUpdateDto> {
fun subscribe(): Flux<ConfigUpdateDto> = configUpdates.asFlux() val user = SecurityContextHolder.getContext().authentication.principal as UserDetails
return if (user.isAdmin()) configUpdates.asFlux()
else Flux.empty()
}
fun update(update: ConfigUpdateDto) { fun update(update: ConfigUpdateDto) {
config.update(update.updates) config.update(update.updates)
@@ -1,9 +1,12 @@
package de.grimsi.gameyfin.core.logging package de.grimsi.gameyfin.core.logging
import com.vaadin.flow.server.auth.AnonymousAllowed
import com.vaadin.hilla.Endpoint import com.vaadin.hilla.Endpoint
import de.grimsi.gameyfin.core.Role import de.grimsi.gameyfin.core.Role
import de.grimsi.gameyfin.users.util.isAdmin
import jakarta.annotation.security.PermitAll
import jakarta.annotation.security.RolesAllowed import jakarta.annotation.security.RolesAllowed
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import reactor.core.publisher.Flux import reactor.core.publisher.Flux
@Endpoint @Endpoint
@@ -16,9 +19,10 @@ class LogEndpoint(
logService.configureFileLogging() logService.configureFileLogging()
} }
// FIXME: see https://vaadin.com/forum/t/can-only-access-flux-endpoint-with-anonymousallowed/167117 @PermitAll
@AnonymousAllowed
fun getApplicationLogs(): Flux<String> { fun getApplicationLogs(): Flux<String> {
return logService.streamLogs() val user = SecurityContextHolder.getContext().authentication.principal as UserDetails
return if (user.isAdmin()) logService.streamLogs()
else Flux.empty()
} }
} }
@@ -1,6 +1,3 @@
@file:JvmName("Utils")
@file:JvmMultifileClass
package de.grimsi.gameyfin.users.util package de.grimsi.gameyfin.users.util
import de.grimsi.gameyfin.core.Role import de.grimsi.gameyfin.core.Role