diff --git a/package.json b/package.json index fce19cc..5caac49 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@types/node": "^20.17.0", "@vitest/coverage-v8": "^4.0.17", - "@workos-inc/node": "^10.4.0", + "@workos-inc/node": "^10.6.0", "oxfmt": "^0.42.0", "oxlint": "^1.57.0", "typescript": "^5.9.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bd2b399..8c2eec4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -25,8 +25,8 @@ importers: specifier: ^4.0.17 version: 4.1.4(vitest@4.1.4) '@workos-inc/node': - specifier: ^10.4.0 - version: 10.4.0 + specifier: ^10.6.0 + version: 10.7.0 oxfmt: specifier: ^0.42.0 version: 0.42.0 @@ -138,56 +138,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-arm64-musl@0.42.0': resolution: {integrity: sha512-+JA0YMlSdDqmacygGi2REp57c3fN+tzARD8nwsukx9pkCHK+6DkbAA9ojS4lNKsiBjIW8WWa0pBrBWhdZEqfuw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-ppc64-gnu@0.42.0': resolution: {integrity: sha512-VfnET0j4Y5mdfCzh5gBt0NK28lgn5DKx+8WgSMLYYeSooHhohdbzwAStLki9pNuGy51y4I7IoW8bqwAaCMiJQg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-gnu@0.42.0': resolution: {integrity: sha512-gVlCbmBkB0fxBWbhBj9rcxezPydsQHf4MFKeHoTSPicOQ+8oGeTQgQ8EeesSybWeiFPVRx3bgdt4IJnH6nOjAA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-riscv64-musl@0.42.0': resolution: {integrity: sha512-zN5OfstL0avgt/IgvRu0zjQzVh/EPkcLzs33E9LMAzpqlLWiPWeMDZyMGFlSRGOdDjuNmlZBCgj0pFnK5u32TQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxfmt/binding-linux-s390x-gnu@0.42.0': resolution: {integrity: sha512-9X6+H2L0qMc2sCAgO9HS03bkGLMKvOFjmEdchaFlany3vNZOjnVui//D8k/xZAtQv2vaCs1reD5KAgPoIU4msA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-gnu@0.42.0': resolution: {integrity: sha512-BajxJ6KQvMMdpXGPWhBGyjb2Jvx4uec0w+wi6TJZ6Tv7+MzPwe0pO8g5h1U0jyFgoaF7mDl6yKPW3ykWcbUJRw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxfmt/binding-linux-x64-musl@0.42.0': resolution: {integrity: sha512-0wV284I6vc5f0AqAhgAbHU2935B4bVpncPoe5n/WzVZY/KnHgqxC8iSFGeSyLWEgstFboIcWkOPck7tqbdHkzA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxfmt/binding-openharmony-arm64@0.42.0': resolution: {integrity: sha512-p4BG6HpGnhfgHk1rzZfyR6zcWkE7iLrWxyehHfXUy4Qa5j3e0roglFOdP/Nj5cJJ58MA3isQ5dlfkW2nNEpolw==} @@ -260,56 +252,48 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-arm64-musl@1.60.0': resolution: {integrity: sha512-eDYDXZGhQAXyn6GwtwiX/qcLS0HlOLPJ/+iiIY8RYr+3P8oKBmgKxADLlniL6FtWfE7pPk7IGN9/xvDEvDvFeg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@oxlint/binding-linux-ppc64-gnu@1.60.0': resolution: {integrity: sha512-nxehly5XYBHUWI9VJX1bqCf9j/B43DaK/aS/T1fcxCpX3PA4Rm9BB54nPD1CKayT8xg6REN1ao+01hSRNgy8OA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-gnu@1.60.0': resolution: {integrity: sha512-j1qf/NaUfOWQutjeoooNG1Q0zsK0XGmSu1uDLq3cctquRF3j7t9Hxqf/76ehCc5GEUAanth2W4Fa+XT1RFg/nw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-riscv64-musl@1.60.0': resolution: {integrity: sha512-YELKPRefQ/q/h3RUmeRfPCUhh2wBvgV1RyZ/F9M9u8cDyXsQW2ojv1DeWQTt466yczDITjZnIOg/s05pk7Ve2A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - libc: [musl] '@oxlint/binding-linux-s390x-gnu@1.60.0': resolution: {integrity: sha512-JkO3C6Gki7Y6h/MiIkFKvHFOz98/YWvQ4WYbK9DLXACMP2rjULzkeGyAzorJE5S1dzLQGFgeqvN779kSFwoV1g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-gnu@1.60.0': resolution: {integrity: sha512-XjKHdFVCpZZZSWBCKyyqCq65s2AKXykMXkjLoKYODrD+f5toLhlwsMESscu8FbgnJQ4Y/dpR/zdazsahmgBJIA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@oxlint/binding-linux-x64-musl@1.60.0': resolution: {integrity: sha512-js29ZWIuPhNWzY8NC7KoffEMEeWG105vbmm+8EOJsC+T/jHBiKIJEUF78+F/IrgEWMMP9N0kRND4Pp75+xAhKg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@oxlint/binding-openharmony-arm64@1.60.0': resolution: {integrity: sha512-H+PUITKHk04stFpWj3x3Kg08Afp/bcXSBi0EhasR5a0Vw7StXHTzdl655PUI0fB4qdh2Wsu6Dsi+3ACxPoyQnA==} @@ -370,42 +354,36 @@ packages: engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-arm64-musl@1.0.0-rc.16': resolution: {integrity: sha512-3fPzdREH806oRLxpTWW1Gt4tQHs0TitZFOECB2xzCFLPKnSOy90gwA7P29cksYilFO6XVRY1kzga0cL2nRjKPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - libc: [musl] '@rolldown/binding-linux-ppc64-gnu@1.0.0-rc.16': resolution: {integrity: sha512-EKwI1tSrLs7YVw+JPJT/G2dJQ1jl9qlTTTEG0V2Ok/RdOenRfBw2PQdLPyjhIu58ocdBfP7vIRN/pvMsPxs/AQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-s390x-gnu@1.0.0-rc.16': resolution: {integrity: sha512-Uknladnb3Sxqu6SEcqBldQyJUpk8NleooZEc0MbRBJ4inEhRYWZX0NJu12vNf2mqAq7gsofAxHrGghiUYjhaLQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-gnu@1.0.0-rc.16': resolution: {integrity: sha512-FIb8+uG49sZBtLTn+zt1AJ20TqVcqWeSIyoVt0or7uAWesgKaHbiBh6OpA/k9v0LTt+PTrb1Lao133kP4uVxkg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [glibc] '@rolldown/binding-linux-x64-musl@1.0.0-rc.16': resolution: {integrity: sha512-RuERhF9/EgWxZEXYWCOaViUWHIboceK4/ivdtQ3R0T44NjLkIIlGIAVAuCddFxsZ7vnRHtNQUrt2vR2n2slB2w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - libc: [musl] '@rolldown/binding-openharmony-arm64@1.0.0-rc.16': resolution: {integrity: sha512-mXcXnvd9GpazCxeUCCnZ2+YF7nut+ZOEbE4GtaiPtyY6AkhZWbK70y1KK3j+RDhjVq5+U8FySkKRb/+w0EeUwA==} @@ -489,8 +467,8 @@ packages: '@vitest/utils@4.1.4': resolution: {integrity: sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==} - '@workos-inc/node@10.4.0': - resolution: {integrity: sha512-mrrjFYQsYGbY/rGLqUN9IIwzoCa3aIg9gXViIRpzfXkH2mozJ1NBcxSivN3SpqLgoGDuyp3Wg3Mg5UgTJU4FYQ==} + '@workos-inc/node@10.7.0': + resolution: {integrity: sha512-rffa9znZuIv4yo+9JRxGOLTTSxh0XhOT6jlsktgqEi9MuB9V0VFHRzLd3bTpkq+J9sgrPZNGpvX4aWNfMqt5cQ==} engines: {node: '>=22.11.0'} assertion-error@2.0.1: @@ -598,28 +576,24 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [glibc] lightningcss-linux-arm64-musl@1.32.0: resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] - libc: [musl] lightningcss-linux-x64-gnu@1.32.0: resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [glibc] lightningcss-linux-x64-musl@1.32.0: resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] - libc: [musl] lightningcss-win32-arm64-msvc@1.32.0: resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} @@ -1133,7 +1107,7 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 - '@workos-inc/node@10.4.0': {} + '@workos-inc/node@10.7.0': {} assertion-error@2.0.1: {} diff --git a/src/core/client/types.ts b/src/core/client/types.ts deleted file mode 100644 index aa64324..0000000 --- a/src/core/client/types.ts +++ /dev/null @@ -1,82 +0,0 @@ -import type { User, Impersonator, WorkOS } from '@workos-inc/node'; -export interface UserManagementInterface { - getAuthorizationUrl: (options: AuthorizationURLOptions) => string; - authenticateWithCode: ( - options: AuthenticateWithCodeOptions, - ) => Promise; - authenticateWithRefreshToken: ( - options: AuthenticateWithRefreshTokenOptions, - ) => Promise; - getLogoutUrl: (options: { sessionId: string; returnTo?: string }) => string; - revokeSession: (options: { sessionId: string }) => Promise; -} - -export interface WorkOSClientConstructor { - new (apiKey: string, options?: WorkOSOptions): WorkOS; -} - -export interface AppInfo { - name: string; - version: string; -} - -export interface WorkOSOptions { - apiHostname?: string; - https?: boolean; - port?: number; - config?: RequestInit; - appInfo?: AppInfo; - fetchFn?: typeof fetch; - clientId?: string; -} - -export interface AuthenticationResponse { - user: User; - organizationId?: string; - accessToken: string; - refreshToken: string; - impersonator?: Impersonator; - authenticationMethod?: string; - sealedSession?: string; -} - -export interface AuthenticateWithOptionsBase { - clientId: string; - ipAddress?: string; - userAgent?: string; - session?: { - cookiePassword?: string; - sealSession: boolean; - }; -} - -export interface AuthenticateWithRefreshTokenOptions extends AuthenticateWithOptionsBase { - refreshToken: string; - organizationId?: string; -} - -export interface AuthenticateWithCodeOptions extends AuthenticateWithOptionsBase { - codeVerifier?: string; - code: string; - invitationToken?: string; -} - -export interface AuthenticateWithEmailVerificationOptions extends AuthenticateWithOptionsBase { - code: string; - pendingAuthenticationToken: string; -} - -export interface AuthorizationURLOptions { - clientId: string; - codeChallenge?: string; - codeChallengeMethod?: 'S256'; - connectionId?: string; - context?: string; - organizationId?: string; - domainHint?: string; - loginHint?: string; - provider?: string; - redirectUri: string; - state?: string; - screenHint?: 'sign-up' | 'sign-in'; -} diff --git a/src/core/pkce/generateAuthorizationUrl.ts b/src/core/pkce/generateAuthorizationUrl.ts index cc17158..3fedb91 100644 --- a/src/core/pkce/generateAuthorizationUrl.ts +++ b/src/core/pkce/generateAuthorizationUrl.ts @@ -112,6 +112,11 @@ export async function generateAuthorizationUrl(params: { state: sealedState, codeChallenge: pkce.codeChallenge, codeChallengeMethod: pkce.codeChallengeMethod, + // Version-gated passthrough: `maxAge` only exists on the SDK option type + // (and is only serialized) in @workos-inc/node >= 10.6. The conditional + // type on GetAuthorizationUrlOptions ensures callers can only set it when + // their installed peer supports it; forwarded only when provided. + ...(options.maxAge !== undefined && { maxAge: options.maxAge }), }); return { diff --git a/src/core/pkce/pkce.spec.ts b/src/core/pkce/pkce.spec.ts index 1d90a58..c6daf4c 100644 --- a/src/core/pkce/pkce.spec.ts +++ b/src/core/pkce/pkce.spec.ts @@ -173,3 +173,40 @@ describe('PKCE payload size guards', () => { } }); }); + +describe('version-gated WorkOS passthrough params', () => { + // Capture the exact options object handed to the SDK's getAuthorizationUrl. + function capturing() { + let captured: Record | undefined; + const client = { + ...mockClient, + userManagement: { + ...mockClient.userManagement, + getAuthorizationUrl: (opts: Record) => { + captured = opts; + return mockClient.userManagement.getAuthorizationUrl(opts); + }, + }, + }; + const run = (options: Parameters[0] = {}) => + generateAuthorizationUrl({ + client: client as any, + config: config as any, + encryption: sessionEncryption, + options, + }); + return { run, opts: () => captured }; + } + + it('forwards maxAge to getAuthorizationUrl when provided', async () => { + const { run, opts } = capturing(); + await run({ maxAge: 60 }); + expect(opts()?.maxAge).toBe(60); + }); + + it('omits maxAge from the SDK call when not provided (no silent default)', async () => { + const { run, opts } = capturing(); + await run({}); + expect(opts() && 'maxAge' in opts()!).toBe(false); + }); +}); diff --git a/src/core/session/types.ts b/src/core/session/types.ts index 9b4b932..dc3b160 100644 --- a/src/core/session/types.ts +++ b/src/core/session/types.ts @@ -1,4 +1,4 @@ -import type { Impersonator, User } from '@workos-inc/node'; +import type { Impersonator, User, WorkOS } from '@workos-inc/node'; import type { JWTPayload } from 'jose'; export interface BaseTokenClaims extends JWTPayload { @@ -226,10 +226,37 @@ export interface AuthUrlOptions { state?: string; } +/** + * The options object accepted by the installed SDK's `getAuthorizationUrl`. + * + * Derived from the method authkit already calls at runtime rather than from a + * named type import, so the version gate below never depends on whether a given + * `@workos-inc/node` release exports its options type by name — it only needs + * the method to exist, which it does across the entire `^8 || ^9 || ^10` peer + * range. `keyof` is read from the consumer's installed version at *their* + * compile time, so the surface tracks the peer. + */ +type SdkAuthorizationUrlOptions = Parameters< + WorkOS['userManagement']['getAuthorizationUrl'] +>[0]; + /** * Options for `createAuthorization` / `createSignIn` / `createSignUp`, * including the `screenHint` selector used by the sign-in/sign-up variants. + * + * Kept as an `interface` so downstream consumers can still extend it via + * module-augmentation declaration merging. */ export interface GetAuthorizationUrlOptions extends AuthUrlOptions { screenHint?: 'sign-up' | 'sign-in'; + /** + * Maximum allowable elapsed time, in seconds, since the user last actively + * authenticated (OIDC `max_age`). + * + * Requires `@workos-inc/node` >= 10.6.0, where the param was added. On older + * peers (`^8`/`^9`/`<10.6`) this resolves to `never`, so the option is + * unavailable at compile time rather than advertised and silently dropped at + * runtime — no false promise, no silent no-op. + */ + maxAge?: 'maxAge' extends keyof SdkAuthorizationUrlOptions ? number : never; }