From 00dd507fc4defc464cadd1b5a3e17095bfc1721d Mon Sep 17 00:00:00 2001 From: Vishesh 'ironeagle' Bangotra Date: Sat, 18 Jul 2026 19:48:47 +0530 Subject: [PATCH] wire spec-driven auth: remove VITE_AUTH_BASE_URL, reorder providers - main.jsx: remove VITE_AUTH_BASE_URL env var; add AppContent that reads authConfig from useAppContext() and passes to AuthProvider; reorder: AppProvider -> AuthProvider -> BrowserRouter - sync react-openapi: AuthConfig type, extractAuthConfig, loading fix - sync react-auth: authConfig prop, config-driven paths, server logout --- react-auth/contexts.tsx | 34 +++++++++++++++++------ react-auth/index.ts | 1 + react-openapi/index.ts | 2 +- react-openapi/src/context/AppContext.tsx | 3 +- react-openapi/src/context/AppProvider.tsx | 29 +++++++++++++++++-- react-openapi/src/types.ts | 10 +++++++ src/main.jsx | 28 +++++++++++-------- 7 files changed, 83 insertions(+), 24 deletions(-) diff --git a/react-auth/contexts.tsx b/react-auth/contexts.tsx index d2745c9..5808e75 100644 --- a/react-auth/contexts.tsx +++ b/react-auth/contexts.tsx @@ -3,6 +3,15 @@ import { tokenStore } from "./token"; import { createApiClient } from "./axios"; import { AuthUser } from "./models"; +export interface AuthServerConfig { + serverUrl: string; + loginPath: string; + registerPath: string; + logoutPath: string; + mePath: string; + introspectPath: string; +} + interface AuthContextModel { currentUser: AuthUser | null; token: string | null; @@ -17,24 +26,24 @@ const AuthContext = createContext(undefined); export function AuthProvider({ children, - authBaseUrl, + authConfig, }: { children: React.ReactNode; - authBaseUrl: string; + authConfig: AuthServerConfig; }) { const [currentUser, setCurrentUser] = useState(null); const [token, setToken] = useState(tokenStore.get()); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const auth = createApiClient(authBaseUrl); + const auth = createApiClient(authConfig.serverUrl); const login = async (username: string, password: string) => { try { setLoading(true); setError(null); - const res = await auth.post("/login", { username, password }); + const res = await auth.post(authConfig.loginPath, { username, password }); const { access_token, user } = res.data; tokenStore.set(access_token); @@ -52,7 +61,7 @@ export function AuthProvider({ setLoading(true); setError(null); - await auth.post("/register", { username, password }); + await auth.post(authConfig.registerPath, { username, password }); await login(username, password); } catch (e: any) { setError(e.response?.data?.detail ?? "Registration failed"); @@ -61,7 +70,12 @@ export function AuthProvider({ } }; - const logout = () => { + const logout = async () => { + try { + await auth.post(authConfig.logoutPath); + } catch { + // Server logout is best-effort; clear token locally regardless + } tokenStore.clear(); setToken(null); setCurrentUser(null); @@ -70,10 +84,12 @@ export function AuthProvider({ const fetchCurrentUser = async () => { if (!token) return; try { - const me = await auth.get("/me"); + const me = await auth.get(authConfig.mePath); setCurrentUser({ ...me.data }); } catch { - logout(); + tokenStore.clear(); + setToken(null); + setCurrentUser(null); } }; @@ -94,4 +110,4 @@ export function useAuth(): AuthContextModel { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used inside AuthProvider"); return ctx; -} +} \ No newline at end of file diff --git a/react-auth/index.ts b/react-auth/index.ts index 40ae68a..bd3438a 100644 --- a/react-auth/index.ts +++ b/react-auth/index.ts @@ -1,4 +1,5 @@ export { AuthProvider, useAuth } from "./contexts"; +export type { AuthServerConfig } from "./contexts"; export { createApiClient } from "./axios"; export { AuthPage } from "./AuthPage"; export { ProfileCreate } from "./ProfileCreate"; diff --git a/react-openapi/index.ts b/react-openapi/index.ts index 9eb97bf..21f743b 100644 --- a/react-openapi/index.ts +++ b/react-openapi/index.ts @@ -12,4 +12,4 @@ export { useItemSse } from "./src/hooks/useItemSse"; export { sanitizePayload } from "./src/utils/sanitize-payload"; export type { FkResolver } from "./src/utils/sanitize-payload"; export type { FilterComponentProps } from "./src/context/useResource"; -export type { SpecConfiguration, ResourceConfig, FieldConfig, FKFieldConfig, ResourceRelationship, ProfileOperation, ProfileComponents } from "./src/types"; +export type { SpecConfiguration, ResourceConfig, FieldConfig, FKFieldConfig, ResourceRelationship, ProfileOperation, ProfileComponents, AuthConfig } from "./src/types"; diff --git a/react-openapi/src/context/AppContext.tsx b/react-openapi/src/context/AppContext.tsx index 87cd2a9..34ab2b7 100644 --- a/react-openapi/src/context/AppContext.tsx +++ b/react-openapi/src/context/AppContext.tsx @@ -1,11 +1,12 @@ import { createContext, useContext } from "react"; -import type { ProfileComponents, ProfileOperation, ResourceConfig, SpecConfiguration, ValidationMessage } from "../types"; +import type { AuthConfig, ProfileComponents, ProfileOperation, ResourceConfig, SpecConfiguration, ValidationMessage } from "../types"; export interface AppContextValue { config: SpecConfiguration; resources: ResourceConfig[]; profileOperations: ProfileOperation[]; profileComponents: ProfileComponents; + authConfig: AuthConfig; schemas: Record; loading: boolean; errors: ValidationMessage[]; diff --git a/react-openapi/src/context/AppProvider.tsx b/react-openapi/src/context/AppProvider.tsx index 38cdd1f..8e7f894 100644 --- a/react-openapi/src/context/AppProvider.tsx +++ b/react-openapi/src/context/AppProvider.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState, useMemo } from "react"; -import type { ProfileOperation, SpecConfiguration, ResourceConfig, ValidationMessage } from "../types"; +import type { AuthConfig, ProfileOperation, SpecConfiguration, ResourceConfig, ValidationMessage } from "../types"; import { AppContext } from "./AppContext"; import { loadSpec } from "../spec-loader"; import { validateSpec } from "../spec-validator"; @@ -11,6 +11,19 @@ interface AppProviderProps { children: React.ReactNode; } +function extractAuthConfig(spec: any): AuthConfig { + const bearer: Record = + spec?.components?.securitySchemes?.bearerAuth ?? {}; + return { + serverUrl: bearer["x-server-url"] ?? "", + loginPath: bearer["x-login-path"] ?? "/login", + registerPath: bearer["x-register-path"] ?? "/register", + logoutPath: bearer["x-logout-path"] ?? "/logout", + mePath: bearer["x-me-path"] ?? "/me", + introspectPath: bearer["x-introspect-path"] ?? "/introspect", + }; +} + function extractProfileOperations(spec: any): ProfileOperation[] { const ops: ProfileOperation[] = []; const paths = spec.paths ?? {}; @@ -30,10 +43,20 @@ function extractProfileOperations(spec: any): ProfileOperation[] { return ops; } +const DEFAULT_AUTH_CONFIG: AuthConfig = { + serverUrl: "", + loginPath: "/login", + registerPath: "/register", + logoutPath: "/logout", + mePath: "/me", + introspectPath: "/introspect", +}; + export function AppProvider({ specConfiguration, children }: AppProviderProps) { const [loading, setLoading] = useState(true); const [resources, setResources] = useState([]); const [profileOperations, setProfileOperations] = useState([]); + const [authConfig, setAuthConfig] = useState(DEFAULT_AUTH_CONFIG); const [schemas, setSchemas] = useState>({}); const [errors, setErrors] = useState([]); const [warnings, setWarnings] = useState([]); @@ -59,6 +82,7 @@ export function AppProvider({ specConfiguration, children }: AppProviderProps) { setWarnings(warns); setSchemas(spec.components?.schemas ?? {}); setProfileOperations(extractProfileOperations(spec)); + setAuthConfig(extractAuthConfig(spec)); } if (errs.length === 0) { @@ -97,12 +121,13 @@ export function AppProvider({ specConfiguration, children }: AppProviderProps) { resources, profileOperations, profileComponents, + authConfig, schemas, loading, errors, warnings, }), - [specConfiguration, resources, profileOperations, profileComponents, schemas, loading, errors, warnings] + [specConfiguration, resources, profileOperations, profileComponents, authConfig, schemas, loading, errors, warnings] ); return React.createElement(AppContext.Provider, { value }, children); diff --git a/react-openapi/src/types.ts b/react-openapi/src/types.ts index 2dbf4f0..ec4e1e5 100644 --- a/react-openapi/src/types.ts +++ b/react-openapi/src/types.ts @@ -21,6 +21,16 @@ export interface SpecConfiguration { profileComponents?: ProfileComponents; } +/** Auth server config extracted from securitySchemes extensions. */ +export interface AuthConfig { + serverUrl: string; + loginPath: string; + registerPath: string; + logoutPath: string; + mePath: string; + introspectPath: string; +} + /** Represents a single operation marked with `x-profile: true` in the spec. */ export interface ProfileOperation { path: string; diff --git a/src/main.jsx b/src/main.jsx index 863daab..f31598c 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -15,7 +15,7 @@ import { import Home from './Home'; import FetchRequests from './FetchRequest/FetchRequestCreate'; import FetchRequestDetail from './FetchRequest/FetchRequestDetail'; -import { AppProvider, Admin, ProfileRoutes } from '../react-openapi'; +import { AppProvider, useAppContext, Admin, ProfileRoutes } from '../react-openapi'; import { AuthProvider, AuthPage, useAuth, ProfileCreate, ProfileEdit, ProfileView } from "../react-auth"; import Header from './Header'; import Footer from './Footer'; @@ -27,8 +27,6 @@ const queryClient = new QueryClient(); const rootElement = document.getElementById('root'); const root = createRoot(rootElement); -const AUTH_BASE = import.meta.env.VITE_AUTH_BASE_URL; - // Wire profile components from react-auth into the spec-driven admin specConfiguration.profileComponents = { create: ProfileCreate, @@ -81,11 +79,12 @@ const routerMapping = [ { path: "/profile/*", component: ProfileRoutes, headerTitle: "Profile" }, ]; -root.render( - - - - +/** Reads authConfig from AppProvider context and passes it to AuthProvider. */ +function AppContent() { + const { authConfig } = useAppContext(); + return ( + +
@@ -106,8 +105,15 @@ root.render(