From c0d2707c47d389455c8366e7f36c2e81e304a8b9 Mon Sep 17 00:00:00 2001 From: Brandon Corbett Date: Wed, 27 May 2026 10:33:38 -0400 Subject: [PATCH] refactor: remove web mode --- AGENTS.md | 10 +++------- CONTRIBUTING.md | 7 +++---- README.md | 12 ++++-------- src/AuthProvider.tsx | 9 +-------- src/client/createSeamlessAuthClient.ts | 4 +--- src/fetchWithAuth.ts | 8 ++------ src/hooks/useAuthClient.ts | 5 ++--- src/index.ts | 2 -- tests/createSeamlessAuthClient.test.ts | 13 ------------- tests/fetchWithAuth.test.tsx | 12 ++++-------- tests/login.test.tsx | 3 --- tests/useAuthClient.test.tsx | 2 -- 12 files changed, 20 insertions(+), 67 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 73e4272..532bd9f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -41,15 +41,12 @@ Common usage patterns: ## Runtime Model -This package assumes cookie-based auth flows and a Seamless Auth-compatible backend. +This package assumes a Seamless Auth-compatible backend mounted under `/auth`. `createFetchWithAuth()` is the shared request helper: - it always sends `credentials: "include"` -- in `web` mode it targets `${authHost}/...` -- in `server` mode it targets `${authHost}/auth/...` - -The common deployment shape is `server` mode against a backend that mounts the Seamless Auth routes under `/auth`. +- it targets `${authHost}/auth/...` Important implication: @@ -70,7 +67,6 @@ Exports from `src/index.ts` currently include: Exported types currently include: - `AuthContextType` -- `AuthMode` - `Credential` - `CurrentUserResult` - `LoginInput` @@ -111,7 +107,7 @@ The current package is organized around a shared SDK core with optional UI layer - `src/components/*` - reusable UI pieces for those bundled screens - `src/fetchWithAuth.ts` - - mode-aware request construction + - `/auth` request construction - `src/types.ts` - shared user and credential types - `tests/*` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 7628b53..5f40914 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,9 +50,8 @@ cp .env.example .env ``` > IMPORTANT -> Change the AUTH_MODE env to "web" NOT "server". -> Change the APP_ORIGIN env to `http://localhost:5173` to match vite -> This lets you authenticate between a web app and the auth server with no need for an API. +> Change the APP_ORIGIN env to `http://localhost:5173` to match vite. +> The React SDK talks to a server adapter mounted at `/auth`, not directly to API auth cookies. ### If docker and docker compose are avaliable @@ -160,7 +159,7 @@ function ApplicationRoutes() { createRoot(document.getElementById('root')!).render( - + diff --git a/README.md b/README.md index 11dcaa8..42e6e00 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ - `useAuthClient()` - `usePasskeySupport()` - `hasScopedRole()` and `roleGrantsAccess()` -- types including `AuthMode`, `AuthContextType`, `Credential`, `User`, `OAuthProvider`, `StepUpStatus`, and the headless client input/result types +- types including `AuthContextType`, `Credential`, `User`, `OAuthProvider`, `StepUpStatus`, and the headless client input/result types ## Installation @@ -42,7 +42,7 @@ import { AuthProvider } from '@seamless-auth/react'; import { BrowserRouter } from 'react-router-dom'; - + ; @@ -101,7 +101,6 @@ You are still responsible for your app’s route protection and redirects. isAuthenticated: boolean; loading: boolean; apiHost: string; - mode: AuthMode; hasSignedInBefore: boolean; markSignedIn(): void; hasRole(role: string): boolean | undefined; @@ -221,7 +220,6 @@ import { createSeamlessAuthClient } from '@seamless-auth/react'; const authClient = createSeamlessAuthClient({ apiHost: 'https://your.api', - mode: 'server', }); const prfSupported = await authClient.isPasskeyPrfSupported(); @@ -360,7 +358,6 @@ import { createSeamlessAuthClient } from '@seamless-auth/react'; const authClient = createSeamlessAuthClient({ apiHost: 'https://your.api', - mode: 'server', }); const response = await authClient.login({ @@ -444,10 +441,9 @@ These are optional UI wrappers over the same SDK primitives the package now expo ## Backend Expectations -This package assumes a Seamless Auth-compatible backend with cookie-based auth flows. +This package assumes a Seamless Auth-compatible backend with the auth adapter mounted at `/auth`. -- In `web` mode, requests target `${apiHost}/...` -- In `server` mode, requests target `${apiHost}/auth/...` +- Requests target `${apiHost}/auth/...` - `apiHost` may be provided with or without a trailing slash - Requests are sent with `credentials: 'include'` - `AuthProvider` validates the current session by calling `/users/me` on load diff --git a/src/AuthProvider.tsx b/src/AuthProvider.tsx index 33c0ba7..63bb4c4 100644 --- a/src/AuthProvider.tsx +++ b/src/AuthProvider.tsx @@ -26,7 +26,6 @@ import React, { useState, } from 'react'; -import { AuthMode } from './fetchWithAuth'; import { usePreviousSignIn } from './hooks/usePreviousSignIn'; import { hasScopedRole as rolesGrantScopedAccess } from './scopedRoles'; @@ -41,7 +40,6 @@ export interface AuthContextType { apiHost: string; markSignedIn: () => void; hasSignedInBefore: boolean; - mode: AuthMode; credentials: Credential[]; organizations: Organization[]; activeOrganization: Organization | null; @@ -80,14 +78,12 @@ interface AuthProviderProps { children: ReactNode; apiHost: string; autoDetectPreviousSignin?: boolean; - mode?: AuthMode; } export const AuthProvider: React.FC = ({ children, apiHost, autoDetectPreviousSignin = true, - mode = 'web', }) => { const [user, setUser] = useState(null); const [credentials, setCredentials] = useState([]); @@ -97,15 +93,13 @@ export const AuthProvider: React.FC = ({ const [isAuthenticated, setIsAuthenticated] = useState(false); const [loading, setLoading] = useState(true); const { hasSignedInBefore, markSignedIn } = usePreviousSignIn(); - const authMode = mode; const authClient = useMemo( () => createSeamlessAuthClient({ - mode: authMode, apiHost, }), - [authMode, apiHost] + [apiHost] ); const login = async ( @@ -333,7 +327,6 @@ export const AuthProvider: React.FC = ({ apiHost, markSignedIn, hasSignedInBefore: autoDetectPreviousSignin ? hasSignedInBefore : false, - mode: authMode, credentials, organizations, activeOrganization, diff --git a/src/client/createSeamlessAuthClient.ts b/src/client/createSeamlessAuthClient.ts index fb23deb..f779aac 100644 --- a/src/client/createSeamlessAuthClient.ts +++ b/src/client/createSeamlessAuthClient.ts @@ -12,7 +12,7 @@ import { WebAuthnError, } from '@simplewebauthn/browser'; -import { AuthMode, createFetchWithAuth } from '../fetchWithAuth'; +import { createFetchWithAuth } from '../fetchWithAuth'; import { Credential, Organization, OrganizationMembership, User } from '../types'; import { createPrfRequestBody, @@ -27,7 +27,6 @@ import { export interface SeamlessAuthClientOptions { apiHost: string; - mode: AuthMode; } export interface LoginInput { @@ -287,7 +286,6 @@ export const createSeamlessAuthClient = ( opts: SeamlessAuthClientOptions ): SeamlessAuthClient => { const fetchWithAuth = createFetchWithAuth({ - authMode: opts.mode, authHost: opts.apiHost, }); diff --git a/src/fetchWithAuth.ts b/src/fetchWithAuth.ts index 4d6f55c..c561362 100644 --- a/src/fetchWithAuth.ts +++ b/src/fetchWithAuth.ts @@ -4,25 +4,21 @@ * See LICENSE file in the project root for full license information */ -export type AuthMode = 'web' | 'server'; - interface FetchWithAuthOptions { - authMode: AuthMode; authHost?: string; } export const createFetchWithAuth = (opts: FetchWithAuthOptions) => { - const { authMode, authHost } = opts; + const { authHost } = opts; return async function fetchWithAuth( input: string, init?: RequestInit ): Promise { const host = authHost?.replace(/\/+$/, '') ?? ''; - const base = authMode === 'server' ? '/auth' : ''; const path = input.startsWith('/') ? input : `/${input}`; - const url = `${host}${base}${path}`; + const url = `${host}/auth${path}`; const requestInit: RequestInit = { ...init, diff --git a/src/hooks/useAuthClient.ts b/src/hooks/useAuthClient.ts index 4f4935b..683bcab 100644 --- a/src/hooks/useAuthClient.ts +++ b/src/hooks/useAuthClient.ts @@ -10,14 +10,13 @@ import { useAuth } from '@/AuthProvider'; import { createSeamlessAuthClient } from '@/client/createSeamlessAuthClient'; export const useAuthClient = () => { - const { apiHost, mode } = useAuth(); + const { apiHost } = useAuth(); return useMemo( () => createSeamlessAuthClient({ apiHost, - mode, }), - [apiHost, mode] + [apiHost] ); }; diff --git a/src/index.ts b/src/index.ts index 821a6a7..eb11892 100644 --- a/src/index.ts +++ b/src/index.ts @@ -44,7 +44,6 @@ import { PasskeyPrfInput, PasskeyPrfResult, } from '@/client/webauthnPrf'; -import { AuthMode } from '@/fetchWithAuth'; import { useAuthClient } from '@/hooks/useAuthClient'; import { usePasskeySupport } from '@/hooks/usePasskeySupport'; import { hasScopedRole, roleGrantsAccess } from '@/scopedRoles'; @@ -65,7 +64,6 @@ export { }; export type { AuthContextType, - AuthMode, Credential, CreateOrganizationInput, CurrentUserResult, diff --git a/tests/createSeamlessAuthClient.test.ts b/tests/createSeamlessAuthClient.test.ts index 4912cad..3ce2e97 100644 --- a/tests/createSeamlessAuthClient.test.ts +++ b/tests/createSeamlessAuthClient.test.ts @@ -50,7 +50,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'server', }); await expect( @@ -72,7 +71,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'server', }); await expect(client.requestLoginPhoneOtp()).resolves.toBe(response); @@ -102,7 +100,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'server', }); await expect(client.requestLoginEmailOtp()).resolves.toBe(response); @@ -130,7 +127,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'server', }); await expect(client.listOrganizations()).resolves.toBe(response); @@ -180,7 +176,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'server', }); await expect(client.listOAuthProviders()).resolves.toEqual(providersResult); @@ -229,7 +224,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'web', }); await expect(client.loginWithPasskey()).resolves.toEqual({ @@ -278,7 +272,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'web', }); const result = await client.loginWithPasskey({ @@ -309,7 +302,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'web', }); await expect( @@ -343,7 +335,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'web', }); await expect( @@ -395,7 +386,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'server', }); await expect(client.getStepUpStatus()).resolves.toBe(response); @@ -426,7 +416,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'web', }); await expect(client.verifyStepUpWithPasskey()).resolves.toEqual({ @@ -453,7 +442,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'web', }); await expect(client.verifyStepUpWithPasskey()).resolves.toEqual({ @@ -515,7 +503,6 @@ describe('createSeamlessAuthClient', () => { const client = createSeamlessAuthClient({ apiHost: 'https://api.example.com', - mode: 'web', }); const result = await client.verifyStepUpWithPasskeyPrf({ salt }); diff --git a/tests/fetchWithAuth.test.tsx b/tests/fetchWithAuth.test.tsx index b84cc65..ea6c4c0 100644 --- a/tests/fetchWithAuth.test.tsx +++ b/tests/fetchWithAuth.test.tsx @@ -14,9 +14,8 @@ describe('createFetchWithAuth', () => { jest.clearAllMocks(); }); - it('builds correct URL in web mode', async () => { + it('builds correct server-adapter URL', async () => { const fetchWithAuth = createFetchWithAuth({ - authMode: 'web', authHost: 'https://auth.example.com', }); @@ -27,13 +26,12 @@ describe('createFetchWithAuth', () => { expect(mockFetch).toHaveBeenCalledTimes(1); const [url, options] = mockFetch.mock.calls[0]; - expect(url).toBe('https://auth.example.com/login/start'); + expect(url).toBe('https://auth.example.com/auth/login/start'); expect(options.credentials).toBe('include'); }); - it('builds correct URL in server mode when auth host has a trailing slash', async () => { + it('builds correct URL when auth host has a trailing slash', async () => { const fetchWithAuth = createFetchWithAuth({ - authMode: 'server', authHost: 'https://api.example.com/', }); @@ -45,9 +43,8 @@ describe('createFetchWithAuth', () => { expect(url).toBe('https://api.example.com/auth/users/me'); }); - it('builds correct URL in server mode when auth host has no trailing slash', async () => { + it('builds correct URL when auth host has no trailing slash', async () => { const fetchWithAuth = createFetchWithAuth({ - authMode: 'server', authHost: 'https://api.example.com', }); @@ -61,7 +58,6 @@ describe('createFetchWithAuth', () => { it('returns the raw response when fetch response is not ok', async () => { const fetchWithAuth = createFetchWithAuth({ - authMode: 'web', authHost: 'https://auth.example.com', }); diff --git a/tests/login.test.tsx b/tests/login.test.tsx index 732d852..26e27e6 100644 --- a/tests/login.test.tsx +++ b/tests/login.test.tsx @@ -60,7 +60,6 @@ describe('Login', () => { (useAuth as jest.Mock).mockReturnValue({ apiHost: 'http://localhost', hasSignedInBefore: true, - mode: 'web', login: jest.fn().mockResolvedValue({ ok: true }), handlePasskeyLogin: jest.fn().mockResolvedValue(false), }); @@ -117,7 +116,6 @@ describe('Login', () => { (useAuth as jest.Mock).mockReturnValue({ apiHost: 'http://localhost', hasSignedInBefore: true, - mode: 'web', login: mockLogin, handlePasskeyLogin: mockHandlePasskeyLogin, }); @@ -168,7 +166,6 @@ describe('Login', () => { (useAuth as jest.Mock).mockReturnValue({ apiHost: 'http://localhost', hasSignedInBefore: true, - mode: 'web', login: mockLogin, handlePasskeyLogin: jest.fn().mockResolvedValue(false), }); diff --git a/tests/useAuthClient.test.tsx b/tests/useAuthClient.test.tsx index 6b2dc4b..d18e1fd 100644 --- a/tests/useAuthClient.test.tsx +++ b/tests/useAuthClient.test.tsx @@ -18,7 +18,6 @@ describe('useAuthClient', () => { const client = { login: jest.fn() }; (useAuth as jest.Mock).mockReturnValue({ apiHost: 'https://api.example.com', - mode: 'server', }); (createSeamlessAuthClient as jest.Mock).mockReturnValue(client); @@ -26,7 +25,6 @@ describe('useAuthClient', () => { expect(createSeamlessAuthClient).toHaveBeenCalledWith({ apiHost: 'https://api.example.com', - mode: 'server', }); expect(result.current).toBe(client); });