From 482feb4546190144bf91e4038ce63670fca2ade8 Mon Sep 17 00:00:00 2001 From: janithjay Date: Thu, 20 Aug 2026 14:10:33 +0530 Subject: [PATCH] Extract Nuxt SDK API route paths into shared constants Signed-off-by: janithjay --- packages/nuxt/src/module.ts | 25 +++++----- .../src/runtime/components/ThunderIDRoot.ts | 13 ++--- .../src/runtime/composables/useThunderID.ts | 13 +++-- .../src/runtime/constants/NuxtAPIRoutes.ts | 50 +++++++++++++++++++ .../middleware/defineThunderIDMiddleware.ts | 5 +- .../nuxt/src/runtime/plugins/thunderid.ts | 11 ++-- .../runtime/server/plugins/thunderid-ssr.ts | 5 +- 7 files changed, 90 insertions(+), 32 deletions(-) create mode 100644 packages/nuxt/src/runtime/constants/NuxtAPIRoutes.ts diff --git a/packages/nuxt/src/module.ts b/packages/nuxt/src/module.ts index f91cb237..27d439d6 100644 --- a/packages/nuxt/src/module.ts +++ b/packages/nuxt/src/module.ts @@ -15,6 +15,7 @@ import { import type {Nuxt} from '@nuxt/schema'; import {VendorConstants} from '@thunderid/node'; import {defu} from 'defu'; +import NuxtAPIRoutes from './runtime/constants/NuxtAPIRoutes'; import type {ThunderIDNuxtConfig, ThunderIDSessionPayload, ThunderIDSSRData} from './runtime/types'; type ViteUserConfig = Parameters[0]>[0]; @@ -141,39 +142,39 @@ export default defineNuxtModule({ // Register server API routes const serverRoutes: ServerRoute[] = [ // ── Auth flow ────────────────────────────────────────────────────── - {handler: resolve('./runtime/server/routes/auth/session/signin.get'), route: '/api/auth/signin'}, + {handler: resolve('./runtime/server/routes/auth/session/signin.get'), route: NuxtAPIRoutes.SIGN_IN}, { handler: resolve('./runtime/server/routes/auth/session/signin.post'), method: 'post' as const, - route: '/api/auth/signin', + route: NuxtAPIRoutes.SIGN_IN, }, { handler: resolve('./runtime/server/routes/auth/session/signup.post'), method: 'post' as const, - route: '/api/auth/signup', + route: NuxtAPIRoutes.SIGN_UP, }, - {handler: resolve('./runtime/server/routes/auth/session/callback.get'), route: '/api/auth/callback'}, + {handler: resolve('./runtime/server/routes/auth/session/callback.get'), route: NuxtAPIRoutes.CALLBACK}, { handler: resolve('./runtime/server/routes/auth/session/callback.post'), method: 'post' as const, - route: '/api/auth/callback', + route: NuxtAPIRoutes.CALLBACK, }, { handler: resolve('./runtime/server/routes/auth/session/signout.post'), method: 'post' as const, - route: '/api/auth/signout', + route: NuxtAPIRoutes.SIGN_OUT, }, // ── Session / token ─────────────────────────────────────────────── - {handler: resolve('./runtime/server/routes/auth/session/session.get'), route: '/api/auth/session'}, - {handler: resolve('./runtime/server/routes/auth/session/token.get'), route: '/api/auth/token'}, - {handler: resolve('./runtime/server/routes/auth/session/meta.get'), route: '/api/auth/meta'}, + {handler: resolve('./runtime/server/routes/auth/session/session.get'), route: NuxtAPIRoutes.SESSION}, + {handler: resolve('./runtime/server/routes/auth/session/token.get'), route: NuxtAPIRoutes.TOKEN}, + {handler: resolve('./runtime/server/routes/auth/session/meta.get'), route: NuxtAPIRoutes.META}, // ── User ────────────────────────────────────────────────────────── - {handler: resolve('./runtime/server/routes/auth/user/user.get'), route: '/api/auth/user'}, - {handler: resolve('./runtime/server/routes/auth/user/profile.get'), route: '/api/auth/user/profile'}, + {handler: resolve('./runtime/server/routes/auth/user/user.get'), route: NuxtAPIRoutes.USER}, + {handler: resolve('./runtime/server/routes/auth/user/profile.get'), route: NuxtAPIRoutes.USER_PROFILE}, { handler: resolve('./runtime/server/routes/auth/user/profile.patch'), method: 'patch' as const, - route: '/api/auth/user/profile', + route: NuxtAPIRoutes.USER_PROFILE, }, ]; diff --git a/packages/nuxt/src/runtime/components/ThunderIDRoot.ts b/packages/nuxt/src/runtime/components/ThunderIDRoot.ts index 231226c7..311fcf09 100644 --- a/packages/nuxt/src/runtime/components/ThunderIDRoot.ts +++ b/packages/nuxt/src/runtime/components/ThunderIDRoot.ts @@ -5,6 +5,7 @@ import {generateFlattenedUserProfile} from '@thunderid/browser'; import type {AttributeSchema, FlowMetadataResponse, UpdateMeProfileConfig, User, UserProfile} from '@thunderid/node'; import {FlowMetaProvider, FlowProvider, I18nProvider, ThemeProvider, UserProvider} from '@thunderid/vue'; import {defineComponent, h, type Component, type Ref, type SetupContext, type VNode} from 'vue'; +import NuxtAPIRoutes from '../constants/NuxtAPIRoutes'; import type {ThunderIDAuthState, ThunderIDNuxtConfig} from '../types'; import {getAuthStateKey, getFlowMetaStateKey, getUserProfileStateKey, getUserSchemaStateKey} from '../utils/stateKeys'; import {useState, useRuntimeConfig} from '#imports'; @@ -100,7 +101,7 @@ const ThunderIDRoot: Component = defineComponent({ }; /** - * profile PATCH via the `/api/auth/user/profile` Nitro route. + * profile PATCH via the `NuxtAPIRoutes.USER_PROFILE` Nitro route. * Signature matches `UserProvider.updateProfile` exactly. * * On success, applies an optimistic local update via `onUpdateProfile` @@ -116,7 +117,7 @@ const ThunderIDRoot: Component = defineComponent({ // no-op: session is resolved server-side } try { - const result: {data: {user: User}; error: string; success: boolean} = await $fetch('/api/auth/user/profile', { + const result: {data: {user: User}; error: string; success: boolean} = await $fetch(NuxtAPIRoutes.USER_PROFILE, { body: requestConfig, method: 'PATCH', }); @@ -130,13 +131,13 @@ const ThunderIDRoot: Component = defineComponent({ }; /** - * Re-fetch the full user profile (and its attribute schema) from `/api/auth/user/profile`. + * Re-fetch the full user profile (and its attribute schema) from `NuxtAPIRoutes.USER_PROFILE`. */ const revalidateProfile = async (): Promise => { try { const res: (UserProfile & {userSchema?: Record | null}) | null = await $fetch< UserProfile & {userSchema?: Record | null} - >('/api/auth/user/profile'); + >(NuxtAPIRoutes.USER_PROFILE); if (res) { const {userSchema: fetchedSchema, ...profile} = res; userProfileState.value = profile as UserProfile; @@ -148,14 +149,14 @@ const ThunderIDRoot: Component = defineComponent({ }; /** - * Fetches flow metadata via the `/api/auth/meta` Nitro route instead of `FlowMetaProvider`'s + * Fetches flow metadata via the `NuxtAPIRoutes.META` Nitro route instead of `FlowMetaProvider`'s * default direct browser-to-`baseUrl` fetch — so the browser never talks to the ThunderID * server directly and no CORS configuration is required there. Used for both the initial * fetch (when SSR seeding via `flowMetaState` didn't happen, e.g. it failed server-side) and * subsequent `switchLanguage()` calls. */ const fetchMeta = async (params: {applicationId?: string; language?: string}): Promise => - $fetch('/api/auth/meta', { + $fetch(NuxtAPIRoutes.META, { query: {...(params.language ? {language: params.language} : {})}, }); diff --git a/packages/nuxt/src/runtime/composables/useThunderID.ts b/packages/nuxt/src/runtime/composables/useThunderID.ts index 9ba6c7e3..114c8cf0 100644 --- a/packages/nuxt/src/runtime/composables/useThunderID.ts +++ b/packages/nuxt/src/runtime/composables/useThunderID.ts @@ -5,6 +5,7 @@ import {navigateTo, useState, useRuntimeConfig} from '#app'; import {EmbeddedSignInFlowStatus, EmbeddedSignUpFlowStatus, getRedirectBasedSignUpUrl} from '@thunderid/browser'; import {useThunderID as useThunderIDVue, type ThunderIDContext} from '@thunderid/vue'; import type {Ref} from 'vue'; +import NuxtAPIRoutes from '../constants/NuxtAPIRoutes'; import type {ThunderIDAuthState} from '../types'; /** @@ -53,7 +54,7 @@ export function useThunderID(): ThunderIDContext { if (isEmbedded) { const payload: Record = arg0 as Record; const request: Record = (args[1] ?? {}) as Record; - const res: {data: any; success: boolean} = await $fetch<{data: any; success: boolean}>('/api/auth/signin', { + const res: {data: any; success: boolean} = await $fetch<{data: any; success: boolean}>(NuxtAPIRoutes.SIGN_IN, { body: {payload, request}, method: 'POST', }); @@ -71,7 +72,7 @@ export function useThunderID(): ThunderIDContext { if (res.data?.afterSignInUrl) { if (import.meta.client) { try { - const session: ThunderIDAuthState = await $fetch('/api/auth/session'); + const session: ThunderIDAuthState = await $fetch(NuxtAPIRoutes.SESSION); const authState: Ref = useState('thunderid:auth'); authState.value = session; } catch { @@ -89,13 +90,15 @@ export function useThunderID(): ThunderIDContext { // Redirect flow. const options: Record | undefined = arg0 as Record | undefined; const returnTo: string | undefined = typeof options?.returnTo === 'string' ? options.returnTo : undefined; - const url: string = returnTo ? `/api/auth/signin?returnTo=${encodeURIComponent(returnTo)}` : '/api/auth/signin'; + const url: string = returnTo + ? `${NuxtAPIRoutes.SIGN_IN}?returnTo=${encodeURIComponent(returnTo)}` + : NuxtAPIRoutes.SIGN_IN; await navigateTo(url, {external: true}); return undefined; }; const signOut = async (): Promise => { - const res: {redirectUrl: string} = await $fetch<{redirectUrl: string}>('/api/auth/signout', {method: 'POST'}); + const res: {redirectUrl: string} = await $fetch<{redirectUrl: string}>(NuxtAPIRoutes.SIGN_OUT, {method: 'POST'}); await navigateTo(res.redirectUrl || '/', {external: true}); }; @@ -120,7 +123,7 @@ export function useThunderID(): ThunderIDContext { const isEmbedded: boolean = typeof payload === 'object' && payload !== null && 'flowType' in payload; if (isEmbedded) { - const res: {data: any; success: boolean} = await $fetch<{data: any; success: boolean}>('/api/auth/signup', { + const res: {data: any; success: boolean} = await $fetch<{data: any; success: boolean}>(NuxtAPIRoutes.SIGN_UP, { body: {payload}, method: 'POST', }); diff --git a/packages/nuxt/src/runtime/constants/NuxtAPIRoutes.ts b/packages/nuxt/src/runtime/constants/NuxtAPIRoutes.ts new file mode 100644 index 00000000..de08edbe --- /dev/null +++ b/packages/nuxt/src/runtime/constants/NuxtAPIRoutes.ts @@ -0,0 +1,50 @@ +// Copyright 2025 The ThunderID Authors +// SPDX-License-Identifier: Apache-2.0 + +/** + * Nitro API route paths registered by the ThunderID Nuxt SDK. + * + * Single source of truth for the `/api/auth/*` paths registered as server + * handlers in `module.ts` and consumed across `runtime/composables/useThunderID.ts`, + * `runtime/components/ThunderIDRoot.ts`, `runtime/plugins/thunderid.ts`, + * `runtime/middleware/defineThunderIDMiddleware.ts`, and + * `runtime/server/plugins/thunderid-ssr.ts`. Update a route here, not at each + * call site. + * + * @example + * ```typescript + * await $fetch(NuxtAPIRoutes.SIGN_IN, { method: 'POST' }); + * ``` + */ +const NuxtAPIRoutes: { + CALLBACK: string; + META: string; + SESSION: string; + SIGN_IN: string; + SIGN_OUT: string; + SIGN_UP: string; + TOKEN: string; + USER: string; + USER_PROFILE: string; +} = { + /** Resolves the OAuth callback and completes sign-in. */ + CALLBACK: '/api/auth/callback', + /** Serves flow metadata (design config + i18n bundle). */ + META: '/api/auth/meta', + /** Returns the current server-verified session. */ + SESSION: '/api/auth/session', + /** Starts (embedded) or redirects to (redirect flow) sign-in. */ + SIGN_IN: '/api/auth/signin', + /** Clears the session and returns a post-sign-out redirect URL. */ + SIGN_OUT: '/api/auth/signout', + /** Starts (embedded) or redirects to (redirect flow) sign-up. */ + SIGN_UP: '/api/auth/signup', + /** Returns the current access token. */ + TOKEN: '/api/auth/token', + /** Returns the current user object. */ + USER: '/api/auth/user', + /** Fetches (GET) and updates (PATCH) the user profile. */ + USER_PROFILE: '/api/auth/user/profile', +} as const; + +export default NuxtAPIRoutes; diff --git a/packages/nuxt/src/runtime/middleware/defineThunderIDMiddleware.ts b/packages/nuxt/src/runtime/middleware/defineThunderIDMiddleware.ts index 8e1b867b..be50cdda 100644 --- a/packages/nuxt/src/runtime/middleware/defineThunderIDMiddleware.ts +++ b/packages/nuxt/src/runtime/middleware/defineThunderIDMiddleware.ts @@ -4,13 +4,14 @@ import {defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig, useState} from '#app'; import type {Ref} from 'vue'; import type {RouteLocationNormalized} from 'vue-router'; +import NuxtAPIRoutes from '../constants/NuxtAPIRoutes'; import type {ThunderIDAuthState} from '../types'; import {getAuthStateKey} from '../utils/stateKeys'; export interface ThunderIDMiddlewareOptions { /** * The path to redirect unauthenticated (or unauthorised) requests to. - * Defaults to `'/api/auth/signin'`. + * Defaults to `NuxtAPIRoutes.SIGN_IN`. */ redirectTo?: string; /** @@ -25,7 +26,7 @@ export interface ThunderIDMiddlewareOptions { requireScopes?: string[]; } -const DEFAULT_REDIRECT_TO = '/api/auth/signin'; +const DEFAULT_REDIRECT_TO = NuxtAPIRoutes.SIGN_IN; /** * Typed factory for ThunderID route middleware. diff --git a/packages/nuxt/src/runtime/plugins/thunderid.ts b/packages/nuxt/src/runtime/plugins/thunderid.ts index 4c71bc58..7a4f85fd 100644 --- a/packages/nuxt/src/runtime/plugins/thunderid.ts +++ b/packages/nuxt/src/runtime/plugins/thunderid.ts @@ -9,6 +9,7 @@ import type {H3Event} from 'h3'; import {computed} from 'vue'; import type {ComputedRef, Ref} from 'vue'; import ThunderIDRoot from '../components/ThunderIDRoot'; +import NuxtAPIRoutes from '../constants/NuxtAPIRoutes'; import type {ThunderIDAuthState, ThunderIDSSRData} from '../types'; import {getAuthStateKey, getFlowMetaStateKey, getUserProfileStateKey, getUserSchemaStateKey} from '../utils/stateKeys'; import type {NuxtApp} from '#app'; @@ -154,12 +155,14 @@ export default defineNuxtPlugin((nuxtApp: NuxtApp) => { // ── 3. Action helpers (Nuxt-aware navigation) ─────────────────────────── const signIn = async (options?: Record): Promise => { const returnTo: string | undefined = typeof options?.returnTo === 'string' ? options.returnTo : undefined; - const url: string = returnTo ? `/api/auth/signin?returnTo=${encodeURIComponent(returnTo)}` : '/api/auth/signin'; + const url: string = returnTo + ? `${NuxtAPIRoutes.SIGN_IN}?returnTo=${encodeURIComponent(returnTo)}` + : NuxtAPIRoutes.SIGN_IN; await navigateTo(url, {external: true}); }; const signOut = async (): Promise => { - const res: {redirectUrl: string} = await $fetch<{redirectUrl: string}>('/api/auth/signout', {method: 'POST'}); + const res: {redirectUrl: string} = await $fetch<{redirectUrl: string}>(NuxtAPIRoutes.SIGN_OUT, {method: 'POST'}); await navigateTo(res.redirectUrl || '/', {external: true}); }; @@ -187,12 +190,12 @@ export default defineNuxtPlugin((nuxtApp: NuxtApp) => { // Last-resort fallback for unrecognised baseUrls — keeps the historical // behaviour of hitting the (POST-only) Nitro route, which will surface a // 405 in the network tab and make the misconfiguration obvious. - await navigateTo('/api/auth/signup', {external: true}); + await navigateTo(NuxtAPIRoutes.SIGN_UP, {external: true}); }; const getAccessToken = async (): Promise => { try { - const res: {accessToken: string} = await $fetch<{accessToken: string}>('/api/auth/token'); + const res: {accessToken: string} = await $fetch<{accessToken: string}>(NuxtAPIRoutes.TOKEN); return res.accessToken ?? ''; } catch { return ''; diff --git a/packages/nuxt/src/runtime/server/plugins/thunderid-ssr.ts b/packages/nuxt/src/runtime/server/plugins/thunderid-ssr.ts index 91f2b9a4..35787d24 100644 --- a/packages/nuxt/src/runtime/server/plugins/thunderid-ssr.ts +++ b/packages/nuxt/src/runtime/server/plugins/thunderid-ssr.ts @@ -10,6 +10,7 @@ import { } from '@thunderid/node'; import {getRequestURL, type H3Event} from 'h3'; import {defineNitroPlugin} from 'nitropack/runtime'; +import NuxtAPIRoutes from '../../constants/NuxtAPIRoutes'; import type {ThunderIDAuthState, ThunderIDNuxtConfig, ThunderIDSSRData} from '../../types'; import {createLogger} from '../../utils/log'; import ThunderIDNuxtClient from '../ThunderIDNuxtClient'; @@ -18,15 +19,13 @@ import {useRuntimeConfig} from '#imports'; const log: ReturnType = createLogger('thunderid-ssr'); -const CALLBACK_PATH = '/api/auth/callback'; - /** * Build the OAuth redirect_uri from the incoming request origin. * Honors X-Forwarded-* headers so it works correctly behind a reverse proxy. */ function resolveCallbackUrl(event: H3Event): string { const url: URL = getRequestURL(event, {xForwardedHost: true, xForwardedProto: true}); - return `${url.origin}${CALLBACK_PATH}`; + return `${url.origin}${NuxtAPIRoutes.CALLBACK}`; } /**