Skip to content

Commit bddb526

Browse files
committed
refactor: enforce discriminated union for SSO authorization options
Apply strict typing to SSO authorization URL options, requiring exactly one of connection, organization, or provider to be specified at compile time. This prevents invalid parameter combinations that would previously fail at runtime. Breaking change for TypeScript users: The SSOAuthorizationURLOptions type now uses a discriminated union pattern.
1 parent cf9b599 commit bddb526

File tree

5 files changed

+79
-17
lines changed

5 files changed

+79
-17
lines changed

src/client/sso.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ describe('Public SSO Methods', () => {
102102

103103
it('throws error when no provider, connection, or organization specified', () => {
104104
expect(() => {
105+
// @ts-expect-error Testing runtime validation with invalid input
105106
getAuthorizationUrl({
106107
clientId: 'client_123',
107108
redirectUri: 'https://app.com/callback',

src/client/sso.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,10 @@
11
import { toQueryString } from './utils';
2+
import type { SSOAuthorizationURLOptions as BaseSSOAuthorizationURLOptions } from '../sso/interfaces';
23

3-
export interface SSOAuthorizationURLOptions {
4-
clientId: string;
5-
connection?: string;
6-
organization?: string;
7-
domainHint?: string;
8-
loginHint?: string;
9-
provider?: string;
10-
providerQueryParams?: Record<string, string | boolean | number>;
11-
providerScopes?: string[];
12-
redirectUri: string;
13-
state?: string;
14-
}
4+
// Extend the base options to include baseURL for internal use
5+
export type SSOAuthorizationURLOptions = BaseSSOAuthorizationURLOptions & {
6+
baseURL?: string;
7+
};
158

169
/**
1710
* Generates the authorization URL for SSO authentication.
@@ -22,7 +15,7 @@ export interface SSOAuthorizationURLOptions {
2215
* @throws Error if required arguments are missing
2316
*/
2417
export function getAuthorizationUrl(
25-
options: SSOAuthorizationURLOptions & { baseURL?: string },
18+
options: SSOAuthorizationURLOptions,
2619
): string {
2720
const {
2821
connection,
Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,32 @@
1-
export interface SSOAuthorizationURLOptions {
1+
interface SSOAuthorizationURLBase {
22
clientId: string;
3-
connection?: string;
4-
organization?: string;
53
domainHint?: string;
64
loginHint?: string;
7-
provider?: string;
85
providerQueryParams?: Record<string, string | boolean | number>;
96
providerScopes?: string[];
107
redirectUri: string;
118
state?: string;
129
}
10+
11+
interface SSOWithConnection extends SSOAuthorizationURLBase {
12+
connection: string;
13+
organization?: never;
14+
provider?: never;
15+
}
16+
17+
interface SSOWithOrganization extends SSOAuthorizationURLBase {
18+
organization: string;
19+
connection?: never;
20+
provider?: never;
21+
}
22+
23+
interface SSOWithProvider extends SSOAuthorizationURLBase {
24+
provider: string;
25+
connection?: never;
26+
organization?: never;
27+
}
28+
29+
export type SSOAuthorizationURLOptions =
30+
| SSOWithConnection
31+
| SSOWithOrganization
32+
| SSOWithProvider;

src/sso/sso.spec.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ describe('SSO', () => {
7070
const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
7171

7272
const urlFn = () =>
73+
// @ts-expect-error Testing runtime validation with invalid input
7374
workos.sso.getAuthorizationUrl({
7475
clientId: 'proj_123',
7576
redirectUri: 'example.com/sso/workos/callback',

src/user-management/session.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,53 @@ describe('Session', () => {
281281

282282
expect(resp.authenticated).toBe(true);
283283
});
284+
285+
it('rotates refresh tokens when refreshing session', async () => {
286+
const originalRefreshToken = 'original_refresh_token_123';
287+
const newRefreshToken = 'new_refresh_token_456';
288+
const accessToken =
289+
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJzdWIiOiAiMTIzNDU2Nzg5MCIsCiAgIm5hbWUiOiAiSm9obiBEb2UiLAogICJpYXQiOiAxNTE2MjM5MDIyLAogICJzaWQiOiAic2Vzc2lvbl8xMjMiLAogICJvcmdfaWQiOiAib3JnXzEyMyIsCiAgInJvbGUiOiAibWVtYmVyIiwKICAicGVybWlzc2lvbnMiOiBbInBvc3RzOmNyZWF0ZSIsICJwb3N0czpkZWxldGUiXQp9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
290+
291+
// Mock the API to return a new refresh token
292+
fetchOnce({
293+
user: userFixture,
294+
accessToken,
295+
refreshToken: newRefreshToken, // Different from original
296+
});
297+
298+
const cookiePassword = 'alongcookiesecretmadefortestingsessions';
299+
300+
// Create initial session with original refresh token
301+
const sessionData = await sealData(
302+
{
303+
accessToken,
304+
refreshToken: originalRefreshToken,
305+
user: {
306+
object: 'user',
307+
id: 'user_01H5JQDV7R7ATEYZDEG0W5PRYS',
308+
309+
},
310+
},
311+
{ password: cookiePassword },
312+
);
313+
314+
const session = workos.userManagement.loadSealedSession({
315+
sessionData,
316+
cookiePassword,
317+
});
318+
319+
const response = await session.refresh();
320+
321+
expect(response.authenticated).toBe(true);
322+
323+
if (!response.authenticated) {
324+
throw new Error('Expected successful response');
325+
}
326+
327+
// Verify we got a new sealed session (which proves the refresh token was rotated)
328+
expect(response.sealedSession).toBeDefined();
329+
expect(response.sealedSession).not.toBe(sessionData);
330+
});
284331
});
285332
});
286333

0 commit comments

Comments
 (0)