diff --git a/auth/AuthProvider.tsx b/auth/AuthProvider.tsx deleted file mode 100644 index 829aafe..0000000 --- a/auth/AuthProvider.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { createContext, useContext, useEffect, useMemo, useState } from 'react' -import type { PropsWithChildren } from 'react' -import { userManager } from './oidc' -import type { User } from 'oidc-client-ts' - - -interface AuthCtx { -user: User | null -isAuthenticated: boolean -signIn: (returnTo?: string) => Promise -signOut: () => Promise -getAccessToken: () => string | null -} - - -const Ctx = createContext(null) - - -export function AuthProvider({ children }: PropsWithChildren) { -const [user, setUser] = useState(null) - - -useEffect(() => { -userManager.getUser().then(u => setUser(u)) - - -const onLoaded = (u: User) => setUser(u) -const onUnloaded = () => setUser(null) -const onExpired = async () => { -try { await userManager.signinSilent() } catch {/* ignore */} -} - - -userManager.events.addUserLoaded(onLoaded) -userManager.events.addUserUnloaded(onUnloaded) -userManager.events.addAccessTokenExpired(onExpired) -return () => { -userManager.events.removeUserLoaded(onLoaded) -userManager.events.removeUserUnloaded(onUnloaded) -userManager.events.removeAccessTokenExpired(onExpired) -} -}, []) - - -// Spegla token till sessionStorage så ky kan läsa den -// i useEffect som speglar token: -useEffect(() => { - const token = user?.access_token ?? null - if (token) { - sessionStorage.setItem('access_token', token) - console.debug('access_token set') - } else { - sessionStorage.removeItem('access_token') - } -}, [user]) - - - -const api: AuthCtx = useMemo(() => ({ -user, -isAuthenticated: !!user && !user.expired, -signIn: async (returnTo) => { -await userManager.signinRedirect({ state: { returnTo } }) -}, -signOut: async () => { -await userManager.signoutRedirect() -}, -getAccessToken: () => user?.access_token ?? null, -}), [user]) - - -return {children} -} - - -export function useAuth() { -const ctx = useContext(Ctx) -if (!ctx) throw new Error('useAuth must be used within ') -return ctx -} \ No newline at end of file diff --git a/src/app/providers.tsx b/src/app/providers.tsx index bb18377..16bc889 100644 --- a/src/app/providers.tsx +++ b/src/app/providers.tsx @@ -1,7 +1,7 @@ // src/app/providers.tsx import { type PropsWithChildren, useState } from 'react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' -import { AuthProvider } from 'auth/AuthProvider' +import AuthProvider from '@/auth/AuthProvider' export function AppProviders({ children }: PropsWithChildren) { const [client] = useState( diff --git a/src/app/router.tsx b/src/app/router.tsx index cc98a46..7ffbbeb 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -8,7 +8,7 @@ import { DueTomorrowPage } from '@/pages/DueTomorrowPage' import AuthCallbackPage from '@/pages/AuthCallbackPage' import SilentRenewPage from '@/pages/SilentRenewPage' import LogoutPage from '@/pages/LogoutPage' -import { RequireAuth } from 'auth/RequireAuth' +import { RequireAuth } from '@/auth/RequireAuth' const router = createBrowserRouter([ { path: '/auth/callback', element: }, diff --git a/src/auth/AuthProvider.tsx b/src/auth/AuthProvider.tsx new file mode 100644 index 0000000..ac6bee2 --- /dev/null +++ b/src/auth/AuthProvider.tsx @@ -0,0 +1,47 @@ +import { useEffect, useMemo, useState, type PropsWithChildren } from 'react' +import type { User } from 'oidc-client-ts' +import { userManager } from '@/auth/oidc' +import { Ctx, type AuthCtx } from './context' + +export default function AuthProvider({ children }: PropsWithChildren) { + const [user, setUser] = useState(null) + + useEffect(() => { + userManager.getUser().then(u => setUser(u)) + + const onLoaded = (u: User) => setUser(u) + const onUnloaded = () => setUser(null) + const onExpired = async () => { + try { await userManager.signinSilent() } catch { /* ignore */ } + } + + userManager.events.addUserLoaded(onLoaded) + userManager.events.addUserUnloaded(onUnloaded) + userManager.events.addAccessTokenExpired(onExpired) + return () => { + userManager.events.removeUserLoaded(onLoaded) + userManager.events.removeUserUnloaded(onUnloaded) + userManager.events.removeAccessTokenExpired(onExpired) + } + }, []) + + useEffect(() => { + const token = user?.access_token ?? null + if (token) sessionStorage.setItem('access_token', token) + else sessionStorage.removeItem('access_token') + }, [user]) + + const api: AuthCtx = useMemo(() => ({ + user, + isAuthenticated: !!user && !user.expired, + signIn: async (returnTo?: string) => { + await userManager.signinRedirect({ state: { returnTo } }) + }, + signOut: async () => { + await userManager.signoutRedirect() + }, + getAccessToken: () => user?.access_token ?? null, + }), [user]) + + return {children} +} diff --git a/auth/RequireAuth.tsx b/src/auth/RequireAuth.tsx similarity index 91% rename from auth/RequireAuth.tsx rename to src/auth/RequireAuth.tsx index 7209e73..6394a3d 100644 --- a/auth/RequireAuth.tsx +++ b/src/auth/RequireAuth.tsx @@ -1,7 +1,7 @@ import type { PropsWithChildren } from 'react' import { useEffect } from 'react' import { useLocation } from 'react-router-dom' -import { useAuth } from './AuthProvider' +import { useAuth } from './useAuth' export function RequireAuth({ children }: PropsWithChildren) { diff --git a/src/auth/context.ts b/src/auth/context.ts new file mode 100644 index 0000000..7bc303d --- /dev/null +++ b/src/auth/context.ts @@ -0,0 +1,12 @@ +import { createContext } from 'react' +import type { User } from 'oidc-client-ts' + +export interface AuthCtx { + user: User | null + isAuthenticated: boolean + signIn: (returnTo?: string) => Promise + signOut: () => Promise + getAccessToken: () => string | null +} + +export const Ctx = createContext(null) diff --git a/auth/oidc.ts b/src/auth/oidc.ts similarity index 100% rename from auth/oidc.ts rename to src/auth/oidc.ts diff --git a/src/auth/useAuth.ts b/src/auth/useAuth.ts new file mode 100644 index 0000000..f002b99 --- /dev/null +++ b/src/auth/useAuth.ts @@ -0,0 +1,8 @@ +import { useContext } from 'react' +import { Ctx } from './context' + +export function useAuth() { + const ctx = useContext(Ctx) + if (!ctx) throw new Error('useAuth must be used within ') + return ctx +} diff --git a/src/components/layout/RootLayout.tsx b/src/components/layout/RootLayout.tsx index 939c4a9..0503792 100644 --- a/src/components/layout/RootLayout.tsx +++ b/src/components/layout/RootLayout.tsx @@ -1,7 +1,7 @@ // src/components/layout/RootLayout.tsx import { Outlet, NavLink } from 'react-router-dom' import { MeBadge } from '../../features/me/MeBadge' -import { useAuth } from 'auth/AuthProvider' +import { useAuth } from '@/auth/useAuth' export function RootLayout() { const { isAuthenticated, signIn } = useAuth() diff --git a/src/pages/AuthCallbackPage.tsx b/src/pages/AuthCallbackPage.tsx index ce55efb..43fc7ab 100644 --- a/src/pages/AuthCallbackPage.tsx +++ b/src/pages/AuthCallbackPage.tsx @@ -1,7 +1,7 @@ // src/pages/AuthCallbackPage.tsx import { useEffect, useRef } from 'react' import { useNavigate } from 'react-router-dom' -import { userManager } from 'auth/oidc' // behåll alias/relativt enligt ditt projekt +import { userManager } from '@/auth/oidc' // behåll alias/relativt enligt ditt projekt // Hjälpare: plocka ut returnTo från okänt state function pickReturnTo(state: unknown): string | undefined { diff --git a/src/pages/LogoutPage.tsx b/src/pages/LogoutPage.tsx index d40a799..5776547 100644 --- a/src/pages/LogoutPage.tsx +++ b/src/pages/LogoutPage.tsx @@ -1,5 +1,5 @@ +import { useAuth } from '@/auth/useAuth' import { useEffect } from 'react' -import { useAuth } from 'auth/AuthProvider' export default function LogoutPage() { const { signOut } = useAuth() useEffect(() => { void signOut() }, [signOut]) diff --git a/src/pages/SilentRenewPage.tsx b/src/pages/SilentRenewPage.tsx index bdf85cc..8f3e8d3 100644 --- a/src/pages/SilentRenewPage.tsx +++ b/src/pages/SilentRenewPage.tsx @@ -1,5 +1,5 @@ import { useEffect } from 'react' -import { userManager } from 'auth/oidc' +import { userManager } from '@/auth/oidc' export default function SilentRenewPage() {