From 23798e301b70516d253d93864a9df61cfa70d806 Mon Sep 17 00:00:00 2001 From: Urban Modig Date: Mon, 13 Oct 2025 09:53:31 +0200 Subject: [PATCH] feat(auth): header login/logout and /me badge using ky + react-query --- .drone.yml | 5 ++++ Dockerfile | 43 ++++++++++++---------------- src/app/router.tsx | 6 ++++ src/components/layout/RootLayout.tsx | 9 ++++++ src/features/me/MeBadge.tsx | 9 ++++++ src/features/me/api.ts | 3 ++ src/lib/http.ts | 23 +++++++++++++++ src/lib/queryKeys.ts | 4 +++ tsconfig.app.json | 1 + 9 files changed, 78 insertions(+), 25 deletions(-) create mode 100644 src/features/me/MeBadge.tsx create mode 100644 src/features/me/api.ts create mode 100644 src/lib/http.ts create mode 100644 src/lib/queryKeys.ts diff --git a/.drone.yml b/.drone.yml index 170e427..140f9ff 100644 --- a/.drone.yml +++ b/.drone.yml @@ -43,6 +43,11 @@ steps: build_args: - VITE_API_BASE_URL=https://rubble.se/hemhub/api/ - VITE_BASE_PATH=/hemhub/app/ + - VITE_OIDC_AUTHORITY=https://rubble.se/auth/realms/hemhub + - VITE_OIDC_CLIENT_ID=hemhub-public + - VITE_OIDC_REDIRECT_URI=https://rubble.se/hemhub/app/auth/callback + - VITE_OIDC_POST_LOGOUT_REDIRECT_URI=https://rubble.se/hemhub/app/ + - VITE_OIDC_SILENT_REDIRECT_URI=https://rubble.se/hemhub/app/auth/silent-renew # Taggar (latest + kort SHA) tags: diff --git a/Dockerfile b/Dockerfile index 1018e49..46f47eb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,48 +1,41 @@ # syntax=docker/dockerfile:1 -############################ -# Build stage -############################ FROM node:20-alpine AS build WORKDIR /app -# pnpm via Corepack ENV PNPM_HOME="/root/.local/share/pnpm" ENV PATH="$PNPM_HOME:$PATH" RUN corepack enable && corepack prepare pnpm@9.12.0 --activate -# Installera beroenden COPY package.json pnpm-lock.yaml* ./ RUN pnpm install --frozen-lockfile -# App-källor COPY . . -# --- Build args (styr Vite) --- -# Backend-url (kan sättas i Drone secrets) -ARG VITE_API_BASE_URL=https://rubble.se/hemhub/api -# Base path för proxy under /hemhub/ -ARG VITE_BASE_PATH=/hemhub/app -ENV VITE_API_BASE_URL=https://rubble.se/hemhub/api -ENV VITE_BASE_PATH=/hemhub/app +# --- Build args (med BRA defaults) --- +ARG VITE_API_BASE_URL=http://localhost:8080 +ARG VITE_BASE_PATH=/ + +# Gör ARG:arna synliga för Vite config (vite.config.ts läser process.env.VITE_*) +ENV VITE_API_BASE_URL=$VITE_API_BASE_URL +ENV VITE_BASE_PATH=$VITE_BASE_PATH + +ARG VITE_OIDC_AUTHORITY= +ARG VITE_OIDC_CLIENT_ID= +ARG VITE_OIDC_REDIRECT_URI= +ARG VITE_OIDC_POST_LOGOUT_REDIRECT_URI= +ARG VITE_OIDC_SILENT_REDIRECT_URI= +ENV VITE_OIDC_AUTHORITY=$VITE_OIDC_AUTHORITY \ +VITE_OIDC_CLIENT_ID=$VITE_OIDC_CLIENT_ID \ +VITE_OIDC_REDIRECT_URI=$VITE_OIDC_REDIRECT_URI \ +VITE_OIDC_POST_LOGOUT_REDIRECT_URI=$VITE_OIDC_POST_LOGOUT_REDIRECT_URI \ +VITE_OIDC_SILENT_REDIRECT_URI=$VITE_OIDC_SILENT_REDIRECT_URI -# Bygg (Vite läser env vid build) RUN pnpm build - -############################ -# Runtime stage (Nginx) -############################ FROM nginx:1.27-alpine AS runtime - -# Lägg in Nginx-konfig (SPA fallback) COPY ./.docker/nginx.conf /etc/nginx/conf.d/default.conf - -# Statiska filer från builden COPY --from=build /app/dist /usr/share/nginx/html - -# Hälsokoll (valfritt) HEALTHCHECK --interval=30s --timeout=3s --retries=3 CMD wget -qO- http://127.0.0.1/ || exit 1 - EXPOSE 80 CMD ["nginx", "-g", "daemon off;"] diff --git a/src/app/router.tsx b/src/app/router.tsx index da409c3..52fd257 100644 --- a/src/app/router.tsx +++ b/src/app/router.tsx @@ -5,8 +5,14 @@ import { DashboardPage } from '@/pages/DashboardPage' import { HouseholdBoardPage } from '@/pages/HouseholdBoardPage' import { ProjectBoardPage } from '@/pages/ProjectBoardPage' import { DueTomorrowPage } from '@/pages/DueTomorrowPage' +import AuthCallbackPage from '@/pages/AuthCallbackPage' +import SilentRenewPage from '@/pages/SilentRenewPage' +import LogoutPage from '@/pages/LogoutPage' const router = createBrowserRouter([ + { path: '/auth/callback', element: }, + { path: '/auth/silent-renew', element: }, + { path: '/logout', element: }, { path: '/', element: , diff --git a/src/components/layout/RootLayout.tsx b/src/components/layout/RootLayout.tsx index 953d6f1..939c4a9 100644 --- a/src/components/layout/RootLayout.tsx +++ b/src/components/layout/RootLayout.tsx @@ -1,7 +1,10 @@ // src/components/layout/RootLayout.tsx import { Outlet, NavLink } from 'react-router-dom' +import { MeBadge } from '../../features/me/MeBadge' +import { useAuth } from 'auth/AuthProvider' export function RootLayout() { + const { isAuthenticated, signIn } = useAuth() return (
@@ -11,6 +14,12 @@ export function RootLayout() { isActive?'font-semibold underline':'opacity-80 hover:opacity-100'}>Dashboard isActive?'font-semibold underline':'opacity-80 hover:opacity-100'}>Due tomorrow +
+ + {isAuthenticated + ? Logga ut + : } +
diff --git a/src/features/me/MeBadge.tsx b/src/features/me/MeBadge.tsx new file mode 100644 index 0000000..06bb4b8 --- /dev/null +++ b/src/features/me/MeBadge.tsx @@ -0,0 +1,9 @@ +import { useQuery } from '@tanstack/react-query' +import { fetchMe } from './api' + +export function MeBadge() { + const { data, isLoading, isError } = useQuery({ queryKey: ['me'], queryFn: fetchMe, retry: 0 }) + if (isLoading) return + if (isError) return ej inloggad + return {data?.name || data?.preferred_username || 'me'} +} diff --git a/src/features/me/api.ts b/src/features/me/api.ts new file mode 100644 index 0000000..f4c49d1 --- /dev/null +++ b/src/features/me/api.ts @@ -0,0 +1,3 @@ +import { api } from '@/lib/http' +export type Me = { name?: string; preferred_username?: string; email?: string } +export const fetchMe = () => api.get('me').json() diff --git a/src/lib/http.ts b/src/lib/http.ts new file mode 100644 index 0000000..5103048 --- /dev/null +++ b/src/lib/http.ts @@ -0,0 +1,23 @@ +// src/lib/http.ts +import ky from 'ky' + +export const api = ky.create({ + // pekar på ditt API, t.ex. https://rubble.se/hemhub/api/ i prod (sätts via VITE_API_BASE_URL) + prefixUrl: import.meta.env.VITE_API_BASE_URL ?? 'http://localhost:8080/', + hooks: { + beforeRequest: [ + async (req) => { + const token = sessionStorage.getItem('access_token') + if (token) req.headers.set('Authorization', `Bearer ${token}`) + }, + ], + afterResponse: [ + async (_req, _opts, res) => { + if (res.status === 401) { + // I Iteration 1, mjuk hantering — vi låter RequireAuth sköta redirecten + console.warn('401 from API – probably not logged in yet') + } + }, + ], + }, +}) diff --git a/src/lib/queryKeys.ts b/src/lib/queryKeys.ts new file mode 100644 index 0000000..0bceeb2 --- /dev/null +++ b/src/lib/queryKeys.ts @@ -0,0 +1,4 @@ +// src/lib/queryKeys.ts +export const qk = { + me: ['me'] as const, +} diff --git a/tsconfig.app.json b/tsconfig.app.json index 52cd593..1a2ec8b 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -1,4 +1,5 @@ { + "extends": "./tsconfig.json", "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "ES2022",