Skip to content

Commit 6839dc1

Browse files
committed
create client API for methods that don't require an API key
1 parent 369afff commit 6839dc1

File tree

10 files changed

+567
-146
lines changed

10 files changed

+567
-146
lines changed

package.json

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,15 @@
103103
"require": "./lib/cjs/index.cjs",
104104
"default": "./lib/esm/index.js"
105105
},
106+
"./public": {
107+
"types": {
108+
"require": "./lib/cjs/index.public.d.cts",
109+
"import": "./lib/esm/index.public.d.ts"
110+
},
111+
"import": "./lib/esm/index.public.js",
112+
"require": "./lib/cjs/index.public.cjs",
113+
"default": "./lib/esm/index.public.js"
114+
},
106115
"./worker": {
107116
"types": {
108117
"require": "./lib/cjs/index.worker.d.cts",

src/index.public.spec.ts

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { WorkOS } from './index.public';
2+
3+
describe('WorkOS Public API', () => {
4+
let workosPublic: WorkOS;
5+
6+
beforeEach(() => {
7+
workosPublic = new WorkOS({
8+
clientId: 'client_123',
9+
apiHostname: 'api.workos.dev',
10+
});
11+
});
12+
13+
describe('instantiation', () => {
14+
it('should instantiate without requiring an API key', () => {
15+
expect(() => new WorkOS()).not.toThrow();
16+
});
17+
18+
it('should accept public configuration options', () => {
19+
const client = new WorkOS({
20+
clientId: 'test_client',
21+
apiHostname: 'test.api.com',
22+
https: false,
23+
port: 3000,
24+
});
25+
26+
expect(client).toBeDefined();
27+
expect(client.version).toBeDefined();
28+
});
29+
});
30+
31+
describe('exposed services', () => {
32+
it('should expose webhooks service', () => {
33+
expect(workosPublic.webhooks).toBeDefined();
34+
expect(workosPublic.webhooks.verifyHeader).toBeDefined();
35+
expect(workosPublic.webhooks.computeSignature).toBeDefined();
36+
expect(workosPublic.webhooks.constructEvent).toBeDefined();
37+
});
38+
39+
it('should expose actions service', () => {
40+
expect(workosPublic.actions).toBeDefined();
41+
expect(workosPublic.actions.verifyHeader).toBeDefined();
42+
expect(workosPublic.actions.signResponse).toBeDefined();
43+
expect(workosPublic.actions.constructAction).toBeDefined();
44+
});
45+
46+
it('should expose client-safe user management methods', () => {
47+
expect(workosPublic.userManagement).toBeDefined();
48+
expect(
49+
workosPublic.userManagement.authenticateWithCodeAndVerifier,
50+
).toBeDefined();
51+
expect(workosPublic.userManagement.getAuthorizationUrl).toBeDefined();
52+
expect(workosPublic.userManagement.getLogoutUrl).toBeDefined();
53+
expect(workosPublic.userManagement.getJwksUrl).toBeDefined();
54+
});
55+
56+
it('should expose client-safe SSO methods', () => {
57+
expect(workosPublic.sso).toBeDefined();
58+
expect(workosPublic.sso.getAuthorizationUrl).toBeDefined();
59+
});
60+
61+
it('should expose version', () => {
62+
expect(workosPublic.version).toBeDefined();
63+
expect(typeof workosPublic.version).toBe('string');
64+
});
65+
});
66+
67+
describe('method behavior', () => {
68+
it('should be able to call URL generation methods', () => {
69+
const authUrl = workosPublic.userManagement.getAuthorizationUrl({
70+
clientId: 'client_123',
71+
redirectUri: 'https://example.com/callback',
72+
provider: 'GoogleOAuth',
73+
});
74+
75+
expect(authUrl).toContain('api.workos.dev');
76+
expect(authUrl).toContain('client_id=client_123');
77+
expect(authUrl).toContain('provider=GoogleOAuth');
78+
});
79+
80+
it('should be able to call JWKS URL generation', () => {
81+
const jwksUrl = workosPublic.userManagement.getJwksUrl('client_123');
82+
expect(jwksUrl).toBe('https://api.workos.dev/sso/jwks/client_123');
83+
});
84+
85+
it('should be able to call logout URL generation', () => {
86+
const logoutUrl = workosPublic.userManagement.getLogoutUrl({
87+
sessionId: 'session_123',
88+
returnTo: 'https://example.com',
89+
});
90+
91+
expect(logoutUrl).toContain('api.workos.dev');
92+
expect(logoutUrl).toContain('session_id=session_123');
93+
});
94+
95+
it('should be able to call SSO authorization URL generation', () => {
96+
const authUrl = workosPublic.sso.getAuthorizationUrl({
97+
clientId: 'client_123',
98+
redirectUri: 'https://example.com/callback',
99+
provider: 'GoogleOAuth',
100+
});
101+
102+
expect(authUrl).toContain('api.workos.dev');
103+
expect(authUrl).toContain('client_id=client_123');
104+
});
105+
});
106+
107+
describe('server-only methods should not be exposed', () => {
108+
it('should not expose getUser on userManagement', () => {
109+
expect((workosPublic.userManagement as any).getUser).toBeUndefined();
110+
});
111+
112+
it('should not expose createUser on userManagement', () => {
113+
expect((workosPublic.userManagement as any).createUser).toBeUndefined();
114+
});
115+
116+
it('should not expose listUsers on userManagement', () => {
117+
expect((workosPublic.userManagement as any).listUsers).toBeUndefined();
118+
});
119+
120+
it('should not expose updateUser on userManagement', () => {
121+
expect((workosPublic.userManagement as any).updateUser).toBeUndefined();
122+
});
123+
124+
it('should not expose server-only authentication methods', () => {
125+
expect(
126+
(workosPublic.userManagement as any).authenticateWithPassword,
127+
).toBeUndefined();
128+
expect(
129+
(workosPublic.userManagement as any).authenticateWithMagicAuth,
130+
).toBeUndefined();
131+
expect(
132+
(workosPublic.userManagement as any).authenticateWithRefreshToken,
133+
).toBeUndefined();
134+
});
135+
});
136+
137+
describe('type safety', () => {
138+
it('should provide proper TypeScript types for exposed methods', () => {
139+
// These should compile without errors
140+
const authUrl: string = workosPublic.userManagement.getAuthorizationUrl({
141+
clientId: 'test',
142+
redirectUri: 'https://example.com',
143+
provider: 'GoogleOAuth',
144+
});
145+
146+
const jwksUrl: string =
147+
workosPublic.userManagement.getJwksUrl('client_id');
148+
149+
expect(typeof authUrl).toBe('string');
150+
expect(typeof jwksUrl).toBe('string');
151+
});
152+
});
153+
});

src/index.public.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import { WorkOSBase } from './workos';
2+
import { WorkOSOptions } from './common/interfaces';
3+
4+
// Export public methods directly (new approach)
5+
export * as userManagement from './public/user-management';
6+
export * as sso from './public/sso';
7+
8+
// Re-export types for convenience
9+
export type {
10+
AuthorizationURLOptions as UserManagementAuthorizationURLOptions,
11+
LogoutURLOptions,
12+
} from './public/user-management';
13+
14+
export type { SSOAuthorizationURLOptions } from './public/sso';
15+
16+
interface WorkOSPublicOptions {
17+
clientId?: string;
18+
apiHostname?: string;
19+
https?: boolean;
20+
port?: number;
21+
}
22+
23+
/**
24+
* WorkOS public-safe SDK for browser environments.
25+
* Only exposes methods that are safe to use without API keys.
26+
* @deprecated Use the direct method imports instead:
27+
* import { userManagement, sso } from '@workos-inc/node/public';
28+
*/
29+
export class WorkOS {
30+
private _base: WorkOSBase;
31+
32+
// Client-safe services
33+
readonly userManagement: {
34+
authenticateWithCodeAndVerifier: typeof WorkOSBase.prototype.userManagement.authenticateWithCodeAndVerifier;
35+
getAuthorizationUrl: typeof WorkOSBase.prototype.userManagement.getAuthorizationUrl;
36+
getLogoutUrl: typeof WorkOSBase.prototype.userManagement.getLogoutUrl;
37+
getJwksUrl: typeof WorkOSBase.prototype.userManagement.getJwksUrl;
38+
};
39+
40+
readonly sso: {
41+
getAuthorizationUrl: typeof WorkOSBase.prototype.sso.getAuthorizationUrl;
42+
};
43+
44+
readonly webhooks: typeof WorkOSBase.prototype.webhooks;
45+
readonly actions: typeof WorkOSBase.prototype.actions;
46+
47+
constructor(options: WorkOSPublicOptions = {}) {
48+
// Convert public options to base WorkOS options format
49+
const baseOptions: WorkOSOptions = {
50+
clientId: options.clientId,
51+
apiHostname: options.apiHostname,
52+
https: options.https,
53+
port: options.port,
54+
};
55+
56+
// Create base instance without API key
57+
this._base = new WorkOSBase(undefined, baseOptions);
58+
59+
// Initialize public-safe service methods
60+
this.userManagement = {
61+
authenticateWithCodeAndVerifier:
62+
this._base.userManagement.authenticateWithCodeAndVerifier.bind(
63+
this._base.userManagement,
64+
),
65+
getAuthorizationUrl: this._base.userManagement.getAuthorizationUrl.bind(
66+
this._base.userManagement,
67+
),
68+
getLogoutUrl: this._base.userManagement.getLogoutUrl.bind(
69+
this._base.userManagement,
70+
),
71+
getJwksUrl: this._base.userManagement.getJwksUrl.bind(
72+
this._base.userManagement,
73+
),
74+
};
75+
76+
this.sso = {
77+
getAuthorizationUrl: this._base.sso.getAuthorizationUrl.bind(
78+
this._base.sso,
79+
),
80+
};
81+
82+
// These services are fully public-safe - expose entirely
83+
this.webhooks = this._base.webhooks;
84+
this.actions = this._base.actions;
85+
}
86+
87+
get version() {
88+
return this._base.version;
89+
}
90+
}

src/public-exports.spec.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { userManagement, sso } from './index.public';
2+
3+
describe('New Public Exports', () => {
4+
describe('userManagement exports', () => {
5+
it('exports getAuthorizationUrl function', () => {
6+
expect(userManagement.getAuthorizationUrl).toBeDefined();
7+
expect(typeof userManagement.getAuthorizationUrl).toBe('function');
8+
});
9+
10+
it('exports getLogoutUrl function', () => {
11+
expect(userManagement.getLogoutUrl).toBeDefined();
12+
expect(typeof userManagement.getLogoutUrl).toBe('function');
13+
});
14+
15+
it('exports getJwksUrl function', () => {
16+
expect(userManagement.getJwksUrl).toBeDefined();
17+
expect(typeof userManagement.getJwksUrl).toBe('function');
18+
});
19+
20+
it('can generate authorization URL directly', () => {
21+
const url = userManagement.getAuthorizationUrl({
22+
clientId: 'client_123',
23+
redirectUri: 'https://app.com/callback',
24+
provider: 'authkit',
25+
});
26+
27+
expect(url).toContain('https://api.workos.com/user_management/authorize');
28+
expect(url).toContain('client_id=client_123');
29+
});
30+
});
31+
32+
describe('sso exports', () => {
33+
it('exports getAuthorizationUrl function', () => {
34+
expect(sso.getAuthorizationUrl).toBeDefined();
35+
expect(typeof sso.getAuthorizationUrl).toBe('function');
36+
});
37+
38+
it('can generate SSO authorization URL directly', () => {
39+
const url = sso.getAuthorizationUrl({
40+
clientId: 'client_123',
41+
redirectUri: 'https://app.com/callback',
42+
provider: 'GoogleOAuth',
43+
});
44+
45+
expect(url).toContain('https://api.workos.com/sso/authorize');
46+
expect(url).toContain('provider=GoogleOAuth');
47+
});
48+
});
49+
});

src/public/index.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
/**
2+
* Public methods that can be safely used in without an API key.
3+
*/
4+
5+
// User Management public methods and types
6+
export * as userManagement from './user-management';
7+
8+
// SSO public methods and types
9+
export * as sso from './sso';
10+
11+
// Re-export specific types for convenience
12+
export type {
13+
AuthorizationURLOptions as UserManagementAuthorizationURLOptions,
14+
LogoutURLOptions,
15+
} from './user-management';
16+
17+
export type { SSOAuthorizationURLOptions } from './sso';

src/public/sso.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { toQueryString } from './utils';
2+
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+
}
15+
16+
/**
17+
* Generates the authorization URL for SSO authentication.
18+
* This method is safe to use in browser environments as it doesn't require an API key.
19+
*
20+
* @param options - SSO authorization URL options
21+
* @returns The authorization URL as a string
22+
* @throws Error if required arguments are missing
23+
*/
24+
export function getAuthorizationUrl(
25+
options: SSOAuthorizationURLOptions & { baseURL?: string },
26+
): string {
27+
const {
28+
connection,
29+
clientId,
30+
domainHint,
31+
loginHint,
32+
organization,
33+
provider,
34+
providerQueryParams,
35+
providerScopes,
36+
redirectUri,
37+
state,
38+
baseURL = 'https://api.workos.com',
39+
} = options;
40+
41+
if (!provider && !connection && !organization) {
42+
throw new Error(
43+
`Incomplete arguments. Need to specify either a 'connection', 'organization', or 'provider'.`,
44+
);
45+
}
46+
47+
const query = toQueryString({
48+
connection,
49+
organization,
50+
domain_hint: domainHint,
51+
login_hint: loginHint,
52+
provider,
53+
provider_query_params: providerQueryParams,
54+
provider_scopes: providerScopes,
55+
client_id: clientId,
56+
redirect_uri: redirectUri,
57+
response_type: 'code',
58+
state,
59+
});
60+
61+
return `${baseURL}/sso/authorize?${query}`;
62+
}

0 commit comments

Comments
 (0)