Skip to content

Commit a1ec146

Browse files
authored
Add session-management endpoints (#974)
## Description Adds types/endpoints for session management. ## Documentation Does this require changes to the WorkOS Docs? E.g. the [API Reference](https://workos.com/docs/reference) or code snippets need updates. ``` [X] Yes ``` If yes, link a related docs PR and add a docs maintainer as a reviewer. Their approval is required.
1 parent 8c9db68 commit a1ec146

9 files changed

+184
-2
lines changed
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import {
2+
AuthenticateWithOptionsBase,
3+
SerializedAuthenticateWithOptionsBase,
4+
} from './authenticate-with-options-base.interface';
5+
6+
export interface AuthenticateWithRefreshTokenOptions
7+
extends AuthenticateWithOptionsBase {
8+
refreshToken: string;
9+
}
10+
11+
export interface AuthenticateUserWithRefreshTokenCredentials {
12+
clientSecret: string | undefined;
13+
}
14+
15+
export interface SerializedAuthenticateWithRefreshTokenOptions
16+
extends SerializedAuthenticateWithOptionsBase {
17+
grant_type: 'refresh_token';
18+
refresh_token: string;
19+
}

src/user-management/interfaces/authentication-response.interface.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,23 @@ import { User, UserResponse } from './user.interface';
33
export interface AuthenticationResponse {
44
user: User;
55
organizationId?: string;
6+
accessToken?: string;
7+
refreshToken?: string;
68
}
79

810
export interface AuthenticationResponseResponse {
911
user: UserResponse;
1012
organization_id?: string;
13+
access_token?: string;
14+
refresh_token?: string;
15+
}
16+
17+
export interface RefreshAuthenticationResponse {
18+
accessToken: string;
19+
refreshToken: string;
20+
}
21+
22+
export interface RefreshAuthenticationResponseResponse {
23+
access_token: string;
24+
refresh_token: string;
1125
}

src/user-management/interfaces/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './authenticate-with-magic-auth-options.interface';
22
export * from './authenticate-with-password-options.interface';
33
export * from './authenticate-with-code-options.interface';
4+
export * from './authenticate-with-refresh-token-options.interface';
45
export * from './authenticate-with-totp-options.interface';
56
export * from './authentication-response.interface';
67
export * from './reset-password-options.interface';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export interface RevokeSessionOptions {
2+
sessionId: string;
3+
}
4+
5+
export interface SerializedRevokeSessionOptions {
6+
session_id: string;
7+
}
8+
9+
export const serializeRevokeSessionOptions = (
10+
options: RevokeSessionOptions,
11+
): SerializedRevokeSessionOptions => ({
12+
session_id: options.sessionId,
13+
});
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import {
2+
AuthenticateUserWithCodeCredentials,
3+
AuthenticateWithRefreshTokenOptions,
4+
SerializedAuthenticateWithRefreshTokenOptions,
5+
} from '../interfaces';
6+
7+
export const serializeAuthenticateWithRefreshTokenOptions = (
8+
options: AuthenticateWithRefreshTokenOptions &
9+
AuthenticateUserWithCodeCredentials,
10+
): SerializedAuthenticateWithRefreshTokenOptions => ({
11+
grant_type: 'refresh_token',
12+
client_id: options.clientId,
13+
client_secret: options.clientSecret,
14+
refresh_token: options.refreshToken,
15+
ip_address: options.ipAddress,
16+
user_agent: options.userAgent,
17+
});
Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,34 @@
11
import {
22
AuthenticationResponse,
33
AuthenticationResponseResponse,
4+
RefreshAuthenticationResponse,
5+
RefreshAuthenticationResponseResponse,
46
} from '../interfaces';
57
import { deserializeUser } from './user.serializer';
68

79
export const deserializeAuthenticationResponse = (
810
authenticationResponse: AuthenticationResponseResponse,
911
): AuthenticationResponse => {
10-
const { user, organization_id, ...rest } = authenticationResponse;
12+
const { user, organization_id, access_token, refresh_token, ...rest } =
13+
authenticationResponse;
1114
return {
1215
user: deserializeUser(user),
1316
organizationId: organization_id,
17+
accessToken: access_token,
18+
refreshToken: refresh_token,
19+
...rest,
20+
};
21+
};
22+
23+
export const deserializeRefreshAuthenticationResponse = (
24+
refreshAuthenticationResponse: RefreshAuthenticationResponseResponse,
25+
): RefreshAuthenticationResponse => {
26+
const { access_token, refresh_token, ...rest } =
27+
refreshAuthenticationResponse;
28+
29+
return {
30+
accessToken: access_token,
31+
refreshToken: refresh_token,
1432
...rest,
1533
};
1634
};

src/user-management/serializers/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './authenticate-with-code-options.serializer';
22
export * from './authenticate-with-magic-auth-options.serializer';
33
export * from './authenticate-with-password-options.serializer';
4+
export * from './authenticate-with-refresh-token.options.serializer';
45
export * from './authenticate-with-totp-options.serializer';
56
export * from './authentication-response.serializer';
67
export * from './enroll-auth-factor-options.serializer';

src/user-management/user-management.spec.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,32 @@ describe('UserManagement', () => {
164164
});
165165
});
166166

167+
describe('authenticateWithRefreshToken', () => {
168+
it('sends a refresh_token authentication request', async () => {
169+
fetchOnce({
170+
access_token: 'access_token',
171+
refresh_token: 'refreshToken2',
172+
});
173+
const resp = await workos.userManagement.authenticateWithRefreshToken({
174+
clientId: 'proj_whatever',
175+
refreshToken: 'refresh_token1',
176+
});
177+
178+
expect(fetchURL()).toContain('/user_management/authenticate');
179+
expect(fetchBody()).toEqual({
180+
client_id: 'proj_whatever',
181+
client_secret: 'sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU',
182+
refresh_token: 'refresh_token1',
183+
grant_type: 'refresh_token',
184+
});
185+
186+
expect(resp).toMatchObject({
187+
accessToken: 'access_token',
188+
refreshToken: 'refreshToken2',
189+
});
190+
});
191+
});
192+
167193
describe('authenticateUserWithTotp', () => {
168194
it('sends a token authentication request', async () => {
169195
fetchOnce({ user: userFixture });
@@ -710,6 +736,22 @@ describe('UserManagement', () => {
710736
});
711737
});
712738

739+
describe('revokeSession', () => {
740+
it('sends a Revoke Session request', async () => {
741+
const sessionId = 'session_12345';
742+
fetchOnce({});
743+
744+
await workos.userManagement.revokeSession({
745+
sessionId,
746+
});
747+
748+
expect(fetchURL()).toContain('/user_management/sessions/revoke');
749+
expect(fetchBody()).toEqual({
750+
session_id: 'session_12345',
751+
});
752+
});
753+
});
754+
713755
describe('getAuthorizationUrl', () => {
714756
describe('with no custom api hostname', () => {
715757
it('generates an authorize url with the default api hostname', () => {
@@ -848,4 +890,18 @@ describe('UserManagement', () => {
848890
});
849891
});
850892
});
893+
894+
describe('getLogoutUrl', () => {
895+
it('returns a logout url', () => {
896+
const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU');
897+
898+
const url = workos.userManagement.getLogoutUrl({
899+
sessionId: '123456',
900+
});
901+
902+
expect(url).toBe(
903+
'https://api.workos.com/user_management/sessions/logout?session_id=123456',
904+
);
905+
});
906+
});
851907
});

src/user-management/user-management.ts

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ import {
2828
User,
2929
UserResponse,
3030
VerifyEmailOptions,
31+
AuthenticateWithRefreshTokenOptions,
32+
SerializedAuthenticateWithRefreshTokenOptions,
33+
RefreshAuthenticationResponseResponse,
34+
RefreshAuthenticationResponse,
3135
} from './interfaces';
3236
import {
3337
deserializeAuthenticationResponse,
@@ -43,6 +47,8 @@ import {
4347
serializeCreateUserOptions,
4448
serializeSendMagicAuthCodeOptions,
4549
serializeUpdateUserOptions,
50+
deserializeRefreshAuthenticationResponse,
51+
serializeAuthenticateWithRefreshTokenOptions,
4652
} from './serializers';
4753
import { fetchAndDeserialize } from '../common/utils/fetch-and-deserialize';
4854
import { Challenge, ChallengeResponse } from '../mfa/interfaces';
@@ -90,6 +96,11 @@ import {
9096
FactorWithSecretsResponse,
9197
} from './interfaces/factor.interface';
9298
import { deserializeFactor } from './serializers/factor.serializer';
99+
import {
100+
RevokeSessionOptions,
101+
SerializedRevokeSessionOptions,
102+
serializeRevokeSessionOptions,
103+
} from './interfaces/revoke-session-options.interface';
93104

94105
const toQueryString = (options: Record<string, string | undefined>): string => {
95106
const searchParams = new URLSearchParams();
@@ -196,6 +207,23 @@ export class UserManagement {
196207
return deserializeAuthenticationResponse(data);
197208
}
198209

210+
async authenticateWithRefreshToken(
211+
payload: AuthenticateWithRefreshTokenOptions,
212+
): Promise<RefreshAuthenticationResponse> {
213+
const { data } = await this.workos.post<
214+
RefreshAuthenticationResponseResponse,
215+
SerializedAuthenticateWithRefreshTokenOptions
216+
>(
217+
'/user_management/authenticate',
218+
serializeAuthenticateWithRefreshTokenOptions({
219+
...payload,
220+
clientSecret: this.workos.key,
221+
}),
222+
);
223+
224+
return deserializeRefreshAuthenticationResponse(data);
225+
}
226+
199227
async authenticateWithTotp(
200228
payload: AuthenticateWithTotpOptions,
201229
): Promise<AuthenticationResponse> {
@@ -471,6 +499,13 @@ export class UserManagement {
471499
return deserializeInvitation(data);
472500
}
473501

502+
async revokeSession(payload: RevokeSessionOptions): Promise<void> {
503+
await this.workos.post<void, SerializedRevokeSessionOptions>(
504+
'/user_management/sessions/revoke',
505+
serializeRevokeSessionOptions(payload),
506+
);
507+
}
508+
474509
getAuthorizationUrl({
475510
connectionId,
476511
clientId,
@@ -482,7 +517,7 @@ export class UserManagement {
482517
state,
483518
}: AuthorizationURLOptions): string {
484519
if (!provider && !connectionId && !organizationId) {
485-
throw new Error(
520+
throw new TypeError(
486521
`Incomplete arguments. Need to specify either a 'connectionId', 'organizationId', or 'provider'.`,
487522
);
488523
}
@@ -501,4 +536,12 @@ export class UserManagement {
501536

502537
return `${this.workos.baseURL}/user_management/authorize?${query}`;
503538
}
539+
540+
getLogoutUrl({ sessionId }: { sessionId: string }): string {
541+
if (!sessionId) {
542+
throw new TypeError(`Incomplete arguments. Need to specify 'sessionId'.`);
543+
}
544+
545+
return `${this.workos.baseURL}/user_management/sessions/logout?session_id=${sessionId}`;
546+
}
504547
}

0 commit comments

Comments
 (0)