all kinds of frontend sec. adaptations
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
Urban Modig
2025-10-14 21:18:13 +02:00
parent 23798e301b
commit e80989aecd
5 changed files with 53 additions and 19 deletions

View File

@ -1,4 +1,5 @@
import { createContext, useContext, useEffect, useMemo, useState, PropsWithChildren } from 'react' import { createContext, useContext, useEffect, useMemo, useState } from 'react'
import type { PropsWithChildren } from 'react'
import { userManager } from './oidc' import { userManager } from './oidc'
import type { User } from 'oidc-client-ts' import type { User } from 'oidc-client-ts'
@ -42,13 +43,19 @@ userManager.events.removeAccessTokenExpired(onExpired)
// Spegla token till sessionStorage så ky kan läsa den // Spegla token till sessionStorage så ky kan läsa den
// i useEffect som speglar token:
useEffect(() => { useEffect(() => {
const token = user?.access_token ?? null const token = user?.access_token ?? null
if (token) sessionStorage.setItem('access_token', token) if (token) {
else sessionStorage.removeItem('access_token') sessionStorage.setItem('access_token', token)
console.debug('access_token set')
} else {
sessionStorage.removeItem('access_token')
}
}, [user]) }, [user])
const api: AuthCtx = useMemo(() => ({ const api: AuthCtx = useMemo(() => ({
user, user,
isAuthenticated: !!user && !user.expired, isAuthenticated: !!user && !user.expired,

View File

@ -1,6 +1,6 @@
import { UserManager, WebStorageStateStore, Log, type UserManagerSettings } from 'oidc-client-ts' import { UserManager, WebStorageStateStore, Log, type UserManagerSettings } from 'oidc-client-ts'
console.log('AUTHORITY:', import.meta.env.VITE_OIDC_AUTHORITY)
const settings: UserManagerSettings = { const settings: UserManagerSettings = {
authority: import.meta.env.VITE_OIDC_AUTHORITY!, authority: import.meta.env.VITE_OIDC_AUTHORITY!,
client_id: import.meta.env.VITE_OIDC_CLIENT_ID!, client_id: import.meta.env.VITE_OIDC_CLIENT_ID!,

View File

@ -8,6 +8,7 @@ import { DueTomorrowPage } from '@/pages/DueTomorrowPage'
import AuthCallbackPage from '@/pages/AuthCallbackPage' import AuthCallbackPage from '@/pages/AuthCallbackPage'
import SilentRenewPage from '@/pages/SilentRenewPage' import SilentRenewPage from '@/pages/SilentRenewPage'
import LogoutPage from '@/pages/LogoutPage' import LogoutPage from '@/pages/LogoutPage'
import { RequireAuth } from 'auth/RequireAuth'
const router = createBrowserRouter([ const router = createBrowserRouter([
{ path: '/auth/callback', element: <AuthCallbackPage /> }, { path: '/auth/callback', element: <AuthCallbackPage /> },
@ -15,7 +16,11 @@ const router = createBrowserRouter([
{ path: '/logout', element: <LogoutPage /> }, { path: '/logout', element: <LogoutPage /> },
{ {
path: '/', path: '/',
element: <RootLayout />, element: (
<RequireAuth>
<RootLayout />
</RequireAuth>
),
children: [ children: [
{ index: true, element: <DashboardPage /> }, { index: true, element: <DashboardPage /> },
{ path: 'households/:householdId/board', element: <HouseholdBoardPage /> }, { path: 'households/:householdId/board', element: <HouseholdBoardPage /> },

View File

@ -2,8 +2,16 @@ import { useQuery } from '@tanstack/react-query'
import { fetchMe } from './api' import { fetchMe } from './api'
export function MeBadge() { export function MeBadge() {
const { data, isLoading, isError } = useQuery({ queryKey: ['me'], queryFn: fetchMe, retry: 0 }) const hasToken = !!sessionStorage.getItem('access_token')
const { data, isLoading, isError } = useQuery({
queryKey: ['me'],
queryFn: fetchMe,
enabled: hasToken, // 👈 vänta tills token finns
retry: 0,
})
if (!hasToken) return <span className="opacity-60">ej inloggad</span>
if (isLoading) return <span className="opacity-60"></span> if (isLoading) return <span className="opacity-60"></span>
if (isError) return <span className="opacity-60">ej inloggad</span> if (isError) return <span className="opacity-60">fel</span>
return <span className="opacity-80 text-sm">{data?.name || data?.preferred_username || 'me'}</span> return <span className="opacity-80 text-sm">{data?.name || data?.preferred_username || 'me'}</span>
} }

View File

@ -1,15 +1,29 @@
import { useEffect } from 'react' // src/pages/AuthCallbackPage.tsx
import { useEffect, useRef } from 'react'
import { useNavigate } from 'react-router-dom' import { useNavigate } from 'react-router-dom'
import { userManager } from 'auth/oidc' import { userManager } from 'auth/oidc'
export default function AuthCallbackPage() { export default function AuthCallbackPage() {
const navigate = useNavigate() const navigate = useNavigate()
useEffect(() => { const handled = useRef(false)
userManager.signinRedirectCallback().then((res) => {
const target = (res?.state as any)?.returnTo || '/' useEffect(() => {
navigate(target, { replace: true }) if (handled.current) return // 👈 skydd mot StrictMode dubbelkörning
}) handled.current = true
}, [navigate])
return <p>Completing sign-in</p> ;(async () => {
} try {
const res = await userManager.signinRedirectCallback()
const target = (res?.state as any)?.returnTo || '/'
// Städa bort ?code&state ur URL:en:
window.history.replaceState({}, '', target)
navigate(target, { replace: true })
} catch (err) {
console.error('signinRedirectCallback failed:', err)
navigate('/', { replace: true })
}
})()
}, [navigate])
return <p>Completing sign-in</p>
}