diff --git a/package.json b/package.json index 538abc101..ddd46b88f 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "iron-session": "~6.3.1", "jose": "~5.6.3", "leb": "^1.0.0", - "pluralize": "8.0.0" + "pluralize": "8.0.0", + "qs": "6.14.0" }, "devDependencies": { "@peculiar/webcrypto": "^1.4.5", diff --git a/src/user-management/__snapshots__/user-management.spec.ts.snap b/src/user-management/__snapshots__/user-management.spec.ts.snap index c78be1306..e7be93c21 100644 --- a/src/user-management/__snapshots__/user-management.spec.ts.snap +++ b/src/user-management/__snapshots__/user-management.spec.ts.snap @@ -12,6 +12,8 @@ exports[`UserManagement getAuthorizationUrl with a provider generates an authori exports[`UserManagement getAuthorizationUrl with a provider with providerScopes generates an authorize url that includes the specified scopes 1`] = `"https://api.workos.com/user_management/authorize?client_id=proj_123&provider=GoogleOAuth&provider_scopes=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar&provider_scopes=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fadmin.directory.group&redirect_uri=example.com%2Fauth%2Fworkos%2Fcallback&response_type=code"`; +exports[`UserManagement getAuthorizationUrl with a provider with providerScopes with providerQueryParams generates an authorize url that includes the specified query params 1`] = `"https://api.workos.com/user_management/authorize?client_id=proj_123&provider=GoogleOAuth&provider_query_params%5Bbaz%5D=123&provider_query_params%5Bbool%5D=true&provider_query_params%5Bfoo%5D=bar&redirect_uri=example.com%2Fauth%2Fworkos%2Fcallback&response_type=code"`; + exports[`UserManagement getAuthorizationUrl with a screenHint generates an authorize url with a screenHint 1`] = `"https://api.workos.com/user_management/authorize?client_id=proj_123&provider=authkit&redirect_uri=example.com%2Fauth%2Fworkos%2Fcallback&response_type=code&screen_hint=sign-up"`; exports[`UserManagement getAuthorizationUrl with an organizationId generates an authorization URL with the organization 1`] = `"https://api.workos.com/user_management/authorize?client_id=proj_123&organization_id=organization_123&redirect_uri=example.com%2Fauth%2Fworkos%2Fcallback&response_type=code"`; diff --git a/src/user-management/interfaces/authorization-url-options.interface.ts b/src/user-management/interfaces/authorization-url-options.interface.ts index 1c9ed27e1..5cec62ee7 100644 --- a/src/user-management/interfaces/authorization-url-options.interface.ts +++ b/src/user-management/interfaces/authorization-url-options.interface.ts @@ -12,6 +12,7 @@ export interface UserManagementAuthorizationURLOptions { domainHint?: string; loginHint?: string; provider?: string; + providerQueryParams?: Record; providerScopes?: string[]; prompt?: string; redirectUri: string; diff --git a/src/user-management/user-management.spec.ts b/src/user-management/user-management.spec.ts index c49549b12..ee7ba51b3 100644 --- a/src/user-management/user-management.spec.ts +++ b/src/user-management/user-management.spec.ts @@ -2021,6 +2021,24 @@ describe('UserManagement', () => { expect(url).toMatchSnapshot(); }); + + describe('with providerQueryParams', () => { + it('generates an authorize url that includes the specified query params', () => { + const workos = new WorkOS('sk_test_Sz3IQjepeSWaI4cMS4ms4sMuU'); + + const url = workos.userManagement.getAuthorizationUrl({ + provider: 'GoogleOAuth', + clientId: 'proj_123', + redirectUri: 'example.com/auth/workos/callback', + providerQueryParams: { + foo: 'bar', + baz: 123, + bool: true, + }, + }); + expect(url).toMatchSnapshot(); + }); + }); }); }); diff --git a/src/user-management/user-management.ts b/src/user-management/user-management.ts index 2639246e0..d70b7e24a 100644 --- a/src/user-management/user-management.ts +++ b/src/user-management/user-management.ts @@ -1,4 +1,5 @@ import { createRemoteJWKSet, decodeJwt, jwtVerify } from 'jose'; +import qs from 'qs'; import { OauthException } from '../common/exceptions/oauth.exception'; import { IronSessionProvider } from '../common/iron-session/iron-session-provider'; import { fetchAndDeserialize } from '../common/utils/fetch-and-deserialize'; @@ -143,26 +144,18 @@ import { serializeUpdateOrganizationMembershipOptions } from './serializers/upda import { Session } from './session'; const toQueryString = ( - options: Record, + options: Record< + string, + string | string[] | Record | undefined + >, ): string => { - const searchParams = new URLSearchParams(); - const keys = Object.keys(options).sort(); - - for (const key of keys) { - const value = options[key]; - - if (Array.isArray(value)) { - value.forEach((item) => { - searchParams.append(key, item); - }); - } - - if (typeof value === 'string') { - searchParams.append(key, value); - } - } - - return searchParams.toString(); + return qs.stringify(options, { + arrayFormat: 'repeat', + // sorts the keys alphabetically to maintain backwards compatibility + sort: (a, b) => a.localeCompare(b), + // encodes space as + instead of %20 to maintain backwards compatibility + format: 'RFC1738', + }); }; export class UserManagement { @@ -1023,6 +1016,7 @@ export class UserManagement { loginHint, organizationId, provider, + providerQueryParams, providerScopes, prompt, redirectUri, @@ -1057,6 +1051,7 @@ export class UserManagement { domain_hint: domainHint, login_hint: loginHint, provider, + provider_query_params: providerQueryParams, provider_scopes: providerScopes, prompt, client_id: clientId,