Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd avoid adding the new package qs here if it's possible to use the native URLSearchParams (to avoid extra packages installed in the SDK).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the native URLSearchParams doesn't have good support for array or object parameters, like provider_scopes and now provider_query_params. writing our own logic for array params was ok, but I was worried about missing edge cases if we tried to write our own logic to handle objects so I thought this was worth the added dependency.

},
"devDependencies": {
"@peculiar/webcrypto": "^1.4.5",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export interface UserManagementAuthorizationURLOptions {
domainHint?: string;
loginHint?: string;
provider?: string;
providerQueryParams?: Record<string, string | boolean | number>;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider adding JSDoc comment explaining the purpose and expected format of providerQueryParams, similar to the context param above

providerScopes?: string[];
prompt?: string;
redirectUri: string;
Expand Down
18 changes: 18 additions & 0 deletions src/user-management/user-management.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});
});

Expand Down
33 changes: 14 additions & 19 deletions src/user-management/user-management.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -143,26 +144,18 @@ import { serializeUpdateOrganizationMembershipOptions } from './serializers/upda
import { Session } from './session';

const toQueryString = (
options: Record<string, string | string[] | undefined>,
options: Record<
string,
string | string[] | Record<string, string | boolean | number> | 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 {
Expand Down Expand Up @@ -1023,6 +1016,7 @@ export class UserManagement {
loginHint,
organizationId,
provider,
providerQueryParams,
providerScopes,
prompt,
redirectUri,
Expand Down Expand Up @@ -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,
Expand Down