diff --git a/src/user-management/user-management.spec.ts b/src/user-management/user-management.spec.ts index e71cf89bb..41e658ccf 100644 --- a/src/user-management/user-management.spec.ts +++ b/src/user-management/user-management.spec.ts @@ -69,6 +69,17 @@ describe('UserManagement', () => { locale: 'en-US', }); }); + + it('encodes the userId so it cannot escape the route template', async () => { + fetchOnce(userFixture); + + await workos.userManagement.getUser('../../organizations/org_01TARGET?'); + + const url = new URL(fetchURL() as string); + expect(url.pathname).toBe( + '/user_management/users/..%2F..%2Forganizations%2Forg_01TARGET%3F', + ); + }); }); describe('getUserByExternalId', () => { @@ -2022,6 +2033,20 @@ describe('UserManagement', () => { }); }); + it('encodes the userId so it cannot escape the route template', async () => { + fetchOnce(userFixture); + + await workos.userManagement.updateUser({ + userId: '../../organizations/org_01TARGET?', + firstName: 'Dane', + }); + + const url = new URL(fetchURL() as string); + expect(url.pathname).toBe( + '/user_management/users/..%2F..%2Forganizations%2Forg_01TARGET%3F', + ); + }); + describe('when only one property is provided', () => { it('sends a updateUser request', async () => { fetchOnce(userFixture); @@ -2130,6 +2155,19 @@ describe('UserManagement', () => { expect(fetchURL()).toContain(`/user_management/users/${userId}`); expect(resp).toBeUndefined(); }); + + it('encodes the userId so it cannot escape the route template', async () => { + fetchOnce(); + + await workos.userManagement.deleteUser( + '../../organizations/org_01TARGET?', + ); + + const url = new URL(fetchURL() as string); + expect(url.pathname).toBe( + '/user_management/users/..%2F..%2Forganizations%2Forg_01TARGET%3F', + ); + }); }); describe('listUserApiKeys', () => { @@ -2184,6 +2222,19 @@ describe('UserManagement', () => { organization_id: 'org_01EHZNVPK3SFK441A1RGBFSHRT', }); }); + + it('encodes the userId so it cannot escape the route template', async () => { + fetchOnce(listUserApiKeysFixture); + + await workos.userManagement.listUserApiKeys( + '../../organizations/org_01TARGET/api_keys?', + ); + + const url = new URL(fetchURL() as string); + expect(url.pathname).toBe( + '/user_management/users/..%2F..%2Forganizations%2Forg_01TARGET%2Fapi_keys%3F/api_keys', + ); + }); }); describe('createUserApiKey', () => { @@ -2241,6 +2292,23 @@ describe('UserManagement', () => { 'Idempotency-Key': 'the-idempotency-key', }); }); + + it('encodes the userId so it cannot escape the route template', async () => { + fetchOnce(createUserApiKeyFixture, { status: 201 }); + + await workos.userManagement.createUserApiKey( + '../../../organizations/org_01TARGET/api_keys?', + { + name: 'attacker-key', + organizationId: 'org_01EHZNVPK3SFK441A1RGBFSHRT', + }, + ); + + const url = new URL(fetchURL() as string); + expect(url.pathname).toBe( + '/user_management/users/..%2F..%2F..%2Forganizations%2Forg_01TARGET%2Fapi_keys%3F/api_keys', + ); + }); }); describe('getUserIdentities', () => { diff --git a/src/user-management/user-management.ts b/src/user-management/user-management.ts index fd02fe8ea..c836bb292 100644 --- a/src/user-management/user-management.ts +++ b/src/user-management/user-management.ts @@ -253,7 +253,7 @@ export class UserManagement { */ async getUser(userId: string): Promise { const { data } = await this.workos.get( - `/user_management/users/${userId}`, + `/user_management/users/${encodeURIComponent(userId)}`, ); return deserializeUser(data); @@ -860,7 +860,7 @@ export class UserManagement { userId, }: SendVerificationEmailOptions): Promise<{ user: User }> { const { data } = await this.workos.post<{ user: UserResponse }>( - `/user_management/users/${userId}/email_verification/send`, + `/user_management/users/${encodeURIComponent(userId)}/email_verification/send`, {}, ); @@ -931,9 +931,12 @@ export class UserManagement { const { data } = await this.workos.post< { user: UserResponse }, SerializedVerifyEmailOptions - >(`/user_management/users/${userId}/email_verification/confirm`, { - code, - }); + >( + `/user_management/users/${encodeURIComponent(userId)}/email_verification/confirm`, + { + code, + }, + ); return { user: deserializeUser(data.user) }; } @@ -1005,7 +1008,7 @@ export class UserManagement { */ async updateUser(payload: UpdateUserOptions): Promise { const { data } = await this.workos.put( - `/user_management/users/${payload.userId}`, + `/user_management/users/${encodeURIComponent(payload.userId)}`, serializeUpdateUserOptions(payload), ); @@ -1028,14 +1031,14 @@ export class UserManagement { return new AutoPaginatable( await fetchAndDeserialize( this.workos, - `/user_management/users/${userId}/sessions`, + `/user_management/users/${encodeURIComponent(userId)}/sessions`, deserializeSession, options ? serializeListSessionsOptions(options) : undefined, ), (params) => fetchAndDeserialize( this.workos, - `/user_management/users/${userId}/sessions`, + `/user_management/users/${encodeURIComponent(userId)}/sessions`, deserializeSession, params, ), @@ -1051,7 +1054,9 @@ export class UserManagement { * @throws {NotFoundException} 404 */ async deleteUser(userId: string) { - await this.workos.delete(`/user_management/users/${userId}`); + await this.workos.delete( + `/user_management/users/${encodeURIComponent(userId)}`, + ); } /** @@ -1074,14 +1079,14 @@ export class UserManagement { return new AutoPaginatable( await fetchAndDeserialize( this.workos, - `/user_management/users/${userId}/api_keys`, + `/user_management/users/${encodeURIComponent(userId)}/api_keys`, deserializeUserApiKey, serializedOptions, ), (params) => fetchAndDeserialize( this.workos, - `/user_management/users/${userId}/api_keys`, + `/user_management/users/${encodeURIComponent(userId)}/api_keys`, deserializeUserApiKey, params, ), @@ -1109,7 +1114,7 @@ export class UserManagement { SerializedUserApiKeyWithValue, ReturnType >( - `/user_management/users/${userId}/api_keys`, + `/user_management/users/${encodeURIComponent(userId)}/api_keys`, serializeCreateUserApiKeyOptions(options), requestOptions, ); @@ -1130,7 +1135,7 @@ export class UserManagement { } const { data } = await this.workos.get( - `/user_management/users/${userId}/identities`, + `/user_management/users/${encodeURIComponent(userId)}/identities`, ); return deserializeIdentities(data);