Skip to content

Commit

Permalink
Support Organization Name (#1291)
Browse files Browse the repository at this point in the history
  • Loading branch information
frederikprijck committed Jul 14, 2023
1 parent e7e2c0d commit 0f5b884
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 15 deletions.
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -347,7 +347,7 @@ export interface NextConfig extends Pick<BaseConfig, 'identityClaimFilter'> {
* Log users in to a specific organization.
*
* This will specify an `organization` parameter in your user's login request and will add a step to validate
* the `org_id` claim in your user's ID token.
* the `org_id` or `org_name` claim in your user's ID token.
*
* If your app supports multiple organizations, you should take a look at {@link AuthorizationParams.organization}.
*/
Expand Down
24 changes: 17 additions & 7 deletions src/handlers/callback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,23 @@ const idTokenValidator =
(afterCallback?: AfterCallback, organization?: string): AfterCallback =>
(req, res, session, state) => {
if (organization) {
assert(session.user.org_id, 'Organization Id (org_id) claim must be a string present in the ID token');
assert.equal(
session.user.org_id,
organization,
`Organization Id (org_id) claim value mismatch in the ID token; ` +
`expected "${organization}", found "${session.user.org_id}"`
);
if (organization.startsWith('org_')) {
assert(session.user.org_id, 'Organization Id (org_id) claim must be a string present in the ID token');
assert.equal(
session.user.org_id,
organization,
`Organization Id (org_id) claim value mismatch in the ID token; ` +
`expected "${organization}", found "${session.user.org_id}"`
);
} else {
assert(session.user.org_name, 'Organization Name (org_name) claim must be a string present in the ID token');
assert.equal(
session.user.org_name.toLowerCase(),
organization.toLowerCase(),
`Organization Name (org_name) claim value mismatch in the ID token; ` +
`expected "${organization}", found "${session.user.org_name}"`
);
}
}
if (afterCallback) {
return afterCallback(req, res, session, state);
Expand Down
2 changes: 1 addition & 1 deletion src/handlers/login.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ export interface AuthorizationParams extends Partial<AuthorizationParameters> {
* ```
*
* Your invite url can then take the format:
* `https://example.com/api/invite?invitation=invitation_id&organization=org_id`.
* `https://example.com/api/invite?invitation=invitation_id&organization=org_id_or_name`.
*/
invitation?: string;

Expand Down
84 changes: 78 additions & 6 deletions tests/handlers/callback.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ describe('callback handler', () => {
});

test('throws for missing org_id claim', async () => {
const baseUrl = await setup({ ...withApi, organization: 'foo' });
const baseUrl = await setup({ ...withApi, organization: 'org_foo' });
const state = encodeState({ returnTo: baseUrl });
const cookieJar = await toSignedCookieJar(
{
Expand All @@ -334,8 +334,77 @@ describe('callback handler', () => {
).rejects.toThrow('Organization Id (org_id) claim must be a string present in the ID token');
});

test('throws for missing org_name claim', async () => {
const baseUrl = await setup({ ...withApi, organization: 'foo' });
const state = encodeState({ returnTo: baseUrl });
const cookieJar = await toSignedCookieJar(
{
state,
nonce: '__test_nonce__'
},
baseUrl
);
await expect(
callback(
baseUrl,
{
state,
code: 'code'
},
cookieJar
)
).rejects.toThrow('Organization Name (org_name) claim must be a string present in the ID token');
});

test('throws for org_id claim mismatch', async () => {
const baseUrl = await setup({ ...withApi, organization: 'foo' }, { idTokenClaims: { org_id: 'bar' } });
const baseUrl = await setup({ ...withApi, organization: 'org_foo' }, { idTokenClaims: { org_id: 'org_bar' } });
const state = encodeState({ returnTo: baseUrl });
const cookieJar = await toSignedCookieJar(
{
state,
nonce: '__test_nonce__'
},
baseUrl
);
await expect(
callback(
baseUrl,
{
state,
code: 'code'
},
cookieJar
)
).rejects.toThrow('Organization Id (org_id) claim value mismatch in the ID token; expected "org_foo", found "org_bar"');
});

test('throws for org_name claim mismatch', async () => {
const baseUrl = await setup({ ...withApi, organization: 'foo' }, { idTokenClaims: { org_name: 'bar' } });
const state = encodeState({ returnTo: baseUrl });
const cookieJar = await toSignedCookieJar(
{
state,
nonce: '__test_nonce__'
},
baseUrl
);
await expect(
callback(
baseUrl,
{
state,
code: 'code'
},
cookieJar
)
).rejects.toThrow('Organization Name (org_name) claim value mismatch in the ID token; expected "foo", found "bar"');
});

test('accepts a valid organization id', async () => {
const baseUrl = await setup(withApi, {
idTokenClaims: { org_id: 'org_foo' },
callbackOptions: { organization: 'org_foo' }
});
const state = encodeState({ returnTo: baseUrl });
const cookieJar = await toSignedCookieJar(
{
Expand All @@ -353,12 +422,15 @@ describe('callback handler', () => {
},
cookieJar
)
).rejects.toThrow('Organization Id (org_id) claim value mismatch in the ID token; expected "foo", found "bar"');
).resolves.not.toThrow();
const session = await get(baseUrl, '/api/session', { cookieJar });

expect(session.user.org_id).toEqual('org_foo');
});

test('accepts a valid organization', async () => {
test('accepts a valid organization name', async () => {
const baseUrl = await setup(withApi, {
idTokenClaims: { org_id: 'foo' },
idTokenClaims: { org_name: 'foo' },
callbackOptions: { organization: 'foo' }
});
const state = encodeState({ returnTo: baseUrl });
Expand All @@ -381,7 +453,7 @@ describe('callback handler', () => {
).resolves.not.toThrow();
const session = await get(baseUrl, '/api/session', { cookieJar });

expect(session.user.org_id).toEqual('foo');
expect(session.user.org_name).toEqual('foo');
});

test('should pass custom params to the token exchange', async () => {
Expand Down

0 comments on commit 0f5b884

Please sign in to comment.