diff --git a/src/main/java/fr/titionfire/ExampleResource.java b/src/main/java/fr/titionfire/ExampleResource.java index 04b56c7..c7aee61 100644 --- a/src/main/java/fr/titionfire/ExampleResource.java +++ b/src/main/java/fr/titionfire/ExampleResource.java @@ -1,16 +1,90 @@ package fr.titionfire; +import io.quarkus.oidc.IdToken; +import io.quarkus.oidc.RefreshToken; +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; -import jakarta.ws.rs.core.MediaType; +import org.eclipse.microprofile.jwt.JsonWebToken; +import org.jboss.resteasy.reactive.NoCache; @Path("/hello") public class ExampleResource { + /*@Inject + @IdToken + JsonWebToken idToken; + @GET @Produces(MediaType.TEXT_PLAIN) public String hello() { - return "Hello from RESTEasy Reactive"; + return "Hello, " + idToken.getClaim("name"); + }*/ + + /** + * Injection point for the ID Token issued by the OpenID Connect Provider + */ + @Inject + @IdToken + JsonWebToken idToken; + + /** + * Injection point for the Access Token issued by the OpenID Connect Provider + */ + @Inject + JsonWebToken accessToken; + + /** + * Injection point for the Refresh Token issued by the OpenID Connect Provider + */ + @Inject + RefreshToken refreshToken; + + @Inject + SecurityIdentity securityIdentity; + + /** + * Returns the tokens available to the application. This endpoint exists only for demonstration purposes, you should not + * expose these tokens in a real application. + * + * @return a HTML page containing the tokens available to the application + */ + @GET + @Produces("text/html") + @NoCache + public String getTokens() { + StringBuilder response = new StringBuilder().append("") + .append("") + .append("").append("").append("").toString(); } } diff --git a/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java new file mode 100644 index 0000000..a3ca9c3 --- /dev/null +++ b/src/main/java/fr/titionfire/ffsaf/rest/AuthEndpoints.java @@ -0,0 +1,45 @@ +package fr.titionfire.ffsaf.rest; + +import io.quarkus.security.Authenticated; +import io.quarkus.security.identity.SecurityIdentity; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.Produces; +import jakarta.ws.rs.core.MediaType; +import jakarta.ws.rs.core.Response; +import org.eclipse.microprofile.config.inject.ConfigProperty; + +import java.net.URI; +import java.net.URISyntaxException; + +@Path("/auth") +public class AuthEndpoints { + + @ConfigProperty(name = "login_redirect") + String redirect; + + @Inject + SecurityIdentity securityIdentity; + + @GET + @Produces(MediaType.TEXT_PLAIN) + public Boolean auth() { + return !securityIdentity.isAnonymous(); + } + + @GET + @Path("/userinfo") + @Produces(MediaType.TEXT_PLAIN) + public String userinfo() { + return securityIdentity.getPrincipal().getName(); + } + + @GET + @Path("/login") + @Authenticated + @Produces(MediaType.TEXT_PLAIN) + public Response login() throws URISyntaxException { + return Response.temporaryRedirect(new URI(redirect)).build(); + } +} diff --git a/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java b/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java index 8844662..ed30c5e 100644 --- a/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java +++ b/src/main/java/fr/titionfire/ffsaf/rest/CombEndpoints.java @@ -87,6 +87,7 @@ public class CombEndpoints { @GET @Path("{id}/photo") + @RolesAllowed("federation_admin") public Uni getPhoto(@PathParam("id") long id) throws URISyntaxException { Future> future = CompletableFuture.supplyAsync(() -> { FilenameFilter filter = (directory, filename) -> filename.startsWith(String.valueOf(id)); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index bd30282..0d62253 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -25,6 +25,8 @@ quarkus.oidc.tls.verification=required quarkus.http.limits.max-body-size=10M +quarkus.oidc.token-state-manager.split-tokens=true + database.prefix = test2_ database.database=ffsaf database.hostname=localhost diff --git a/src/main/webapp/src/components/Nav.jsx b/src/main/webapp/src/components/Nav.jsx index fc48b2f..6f83f62 100644 --- a/src/main/webapp/src/components/Nav.jsx +++ b/src/main/webapp/src/components/Nav.jsx @@ -2,7 +2,7 @@ import LogoIcon from '../assets/FFSSAF-bord-blanc-fond-transparent.webp' import './Nav.css' import {NavLink} from "react-router-dom"; import {useAuth, useAuthDispatch} from "../hooks/useAuth.jsx"; -import {logout, redirect_auth_page} from "../utils/auth.js"; +import {login, logout} from "../utils/auth.js"; export function Nav() { @@ -55,7 +55,7 @@ function LoginMenu() { return
  • {!is_authenticated ? ( -
    redirect_auth_page()}>Connexion
    +
    login()}>Connexion
    ) : (
    { logout(token, refresh).then(() => dispatch({type: 'invalidate'})) diff --git a/src/main/webapp/src/components/auhCallback.jsx b/src/main/webapp/src/components/auhCallback.jsx index 9215a11..15820d6 100644 --- a/src/main/webapp/src/components/auhCallback.jsx +++ b/src/main/webapp/src/components/auhCallback.jsx @@ -1,4 +1,4 @@ -import {sendTokenRequestPart2} from "../utils/auth.js"; +import {login_redirect} from "../utils/auth.js"; import {useEffect, useRef} from "react"; export const AuthCallback = () => { @@ -8,8 +8,7 @@ export const AuthCallback = () => { if (isInit.current) return; isInit.current = true - const params = new URLSearchParams(window.location.search); - sendTokenRequestPart2(params.get('code'), params.get('state')); + login_redirect(); }, []); diff --git a/src/main/webapp/src/hooks/useAuth.jsx b/src/main/webapp/src/hooks/useAuth.jsx index 74da112..ad42e19 100644 --- a/src/main/webapp/src/hooks/useAuth.jsx +++ b/src/main/webapp/src/hooks/useAuth.jsx @@ -1,5 +1,4 @@ import {createContext, useContext, useEffect, useReducer} from "react"; -import {sendTokenRequest} from "../utils/auth.js"; const AuthContext = createContext(undefined); const AuthDispatchContext = createContext(null); @@ -15,23 +14,6 @@ export function useAuthDispatch() { export function KeycloakContextProvider({children}) { const [auth, dispatch] = useReducer(authReducer, initialAuth); - useEffect(() => { - if (auth.token === undefined || !auth.is_authenticated) - return; - const jwt = JSON.parse(atob(auth.token.split('.')[1])); - - const ref = setTimeout(() => { - if (auth.refresh !== undefined && auth.refresh !== null) - sendTokenRequest(auth.refresh).then(data => dispatch({ - type: 'update', - token: data.access_token, - refresh: data.refresh_token, - }) - ).catch(() => dispatch({type: 'invalidate'})); - }, jwt.exp * 1000 - new Date().getTime() - 1000) - return () => clearTimeout(ref); - }, [auth]) - return {children} @@ -61,8 +43,6 @@ function authReducer(auth, action) { case 'invalidate': { return { ...auth, - token: undefined, - refresh: undefined, is_authenticated: false } } @@ -73,8 +53,6 @@ function authReducer(auth, action) { } const initialAuth = { - token: undefined, - refresh: undefined, is_authenticated: undefined, data: undefined, } \ No newline at end of file diff --git a/src/main/webapp/src/utils/auth.js b/src/main/webapp/src/utils/auth.js index 6365901..4bc1599 100644 --- a/src/main/webapp/src/utils/auth.js +++ b/src/main/webapp/src/utils/auth.js @@ -4,93 +4,30 @@ const auth_url = import.meta.env.VITE_AUTH_URL; const vite_url = import.meta.env.VITE_URL; const client_id = import.meta.env.VITE_CLIENT_ID; +const api_url = import.meta.env.VITE_API_URL; export function check_validity(online_callback = () => { }) { - const token = localStorage.getItem("access_token") - if (token !== undefined && token !== null) { - const jwt = JSON.parse(atob(token.split('.')[1])); - - if (jwt.exp < new Date().getTime() / 1000 + 1) - if (online_callback) online_callback(false); else return false; - - if (online_callback) { - return axios.get(`${auth_url}/userinfo`, { - headers: { - Authorization: `Bearer ${token}` - } - }).then(() => { - online_callback(true); - }).catch(() => { - online_callback(false); - }) - } - if (online_callback) online_callback(true); else return true; - } - - if (online_callback) online_callback(false); else return false; -} - -export function sendTokenRequest(refresh) { - return axios.post(`${auth_url}/token`, { - client_id: client_id, - redirect_uri: `${vite_url}/complete/auth/`, - grant_type: 'refresh_token', - refresh_token: refresh - }, { - headers: { - "Accept": "*/*", - "Content-Type": "application/x-www-form-urlencoded", - } - }).then(data => { - localStorage.setItem("access_token", data.data.access_token); - localStorage.setItem("refresh_token", data.data.refresh_token); - - return data.data; - }).catch(err => { - console.log(err) - alert("Failed get token from refresh"); + return axios.get(`${api_url}/auth`).then(data => { + console.log(data.data) + online_callback(data.data); + }).catch(() => { + online_callback(false); }) } -export function sendTokenRequestPart2(code, state) { - axios.post(`${auth_url}/token`, { - client_id: client_id, - redirect_uri: `${vite_url}/complete/auth/`, - code: code, - grant_type: 'authorization_code', - }, { - headers: { - "Accept": "application/x-www-form-urlencoded", - "Content-Type": "application/x-www-form-urlencoded", - } - }).then(data => { - localStorage.setItem("access_token", data.data.access_token); - localStorage.setItem("refresh_token", data.data.refresh_token); - - const next = localStorage.getItem("cb_" + state) - if (next) { - clearNext() - window.location.href = next - } else { - window.location.href = import.meta.env.VITE_URL - } - }).catch((err) => { - console.log(err) - // redirect_auth_page() - }) +export function login() { + sessionStorage.setItem("next", window.location.href) + window.location.href = `${api_url}/auth/login`; } -function clearNext() { - const arr = []; // Array to hold the keys - for (let i = 0; i < localStorage.length; i++) { - if (localStorage.key(i).substring(0, 3) === 'cb_') { - arr.push(localStorage.key(i)); - } - } - - for (let i = 0; i < arr.length; i++) { - localStorage.removeItem(arr[i]); +export function login_redirect() { + const next = sessionStorage.getItem("next") + if (next) { + sessionStorage.removeItem("next") + window.location.href = next + } else { + window.location.href = import.meta.env.VITE_URL } }