Ability to opt out of sending profile scope (#2436)

* Default should be true, opt-in via JacksonOption

* Add new env to example file

* Omit profile scope if opted out

* Type update

* Service env processing

* Sync package locks

* Add unit tests
This commit is contained in:
Aswin V 2024-03-13 20:17:15 +05:30 committed by GitHub
parent 3e37a69bf9
commit 92d4f864ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 91 additions and 1847 deletions

View File

@ -105,3 +105,6 @@ SETUP_LINK_EXPIRY_DAYS=3
# Ory integration. You need BOXYHQ_LICENSE_KEY to be set to use this.
ENTERPRISE_ORY_SDK_TOKEN=
ENTERPRISE_ORY_PROJECT_ID=
# Uncomment below if you wish to opt-out of sending `profile` scope in OIDC Provider Authorization Request
#OIDC_REQUEST_PROFILE_SCOPE=false

File diff suppressed because it is too large Load Diff

View File

@ -71,6 +71,7 @@ const jacksonOptions: JacksonOption = {
private: process.env.OPENID_RSA_PRIVATE_KEY || '',
public: process.env.OPENID_RSA_PUBLIC_KEY || '',
},
requestProfileScope: process.env.OIDC_REQUEST_PROFILE_SCOPE === 'false' ? false : true,
},
certs: {
publicKey: process.env.PUBLIC_KEY || '',

View File

@ -407,8 +407,11 @@ export class OAuthController implements IOAuthController {
oidcCodeVerifier = generators.codeVerifier();
const code_challenge = generators.codeChallenge(oidcCodeVerifier);
oidcNonce = generators.nonce();
const standardScopes = this.opts.openid?.requestProfileScope
? ['openid', 'email', 'profile']
: ['openid', 'email'];
ssoUrl = oidcClient.authorizationUrl({
scope: [...requestedScopes, 'openid', 'email', 'profile']
scope: [...requestedScopes, ...standardScopes]
.filter((value, index, self) => self.indexOf(value) === index) // filter out duplicates
.join(' '),
code_challenge,

View File

@ -51,6 +51,7 @@ const defaultOpts = (opts: JacksonOption): JacksonOption => {
newOpts.openid = newOpts.openid || {};
newOpts.openid.jwsAlg = newOpts.openid.jwsAlg || 'RS256';
newOpts.openid.requestProfileScope = newOpts.openid?.requestProfileScope ?? true;
newOpts.boxyhqLicenseKey = newOpts.boxyhqLicenseKey || undefined;

View File

@ -437,6 +437,7 @@ export interface JacksonOption {
private: string;
public: string;
};
requestProfileScope?: boolean; // defaults to true
};
certs?: {
publicKey: string;

View File

@ -45,6 +45,38 @@ tap.test('[OIDCProvider]', async (t) => {
context.state = params.get('state');
});
t.test('[authorize] Should omit profile scope if openid.requestProfileScope is set to false', async (t) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
oauthController.opts.openid.requestProfileScope = false;
const response = (await oauthController.authorize(<OAuthReq>authz_request_oidc_provider)) as {
redirect_url: string;
};
const params = new URLSearchParams(new URL(response.redirect_url!).search);
t.ok('redirect_url' in response, 'got the Idp authorize URL');
t.ok(params.has('state'), 'state present');
t.match(params.get('scope')?.includes('profile'), false, 'profile scope should be absent');
});
t.test(
'[authorize] Should include profile scope if openid.requestProfileScope is set to false but request contains scope param',
async (t) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
oauthController.opts.openid.requestProfileScope = false;
const response = (await oauthController.authorize(<OAuthReq>{
...authz_request_oidc_provider,
scope: 'openid email profile',
})) as {
redirect_url: string;
};
const params = new URLSearchParams(new URL(response.redirect_url!).search);
t.ok('redirect_url' in response, 'got the Idp authorize URL');
t.ok(params.has('state'), 'state present');
t.match(params.get('scope')?.includes('profile'), true, 'profile scope should be absent');
}
);
t.test('[authorize] Should return error if `oidcPath` is not set', async (t) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore

30
package-lock.json generated
View File

@ -85,7 +85,7 @@
"@types/node": "20.11.26",
"@types/react": "18.2.65",
"@typescript-eslint/eslint-plugin": "7.2.0",
"@typescript-eslint/parser": "7.1.1",
"@typescript-eslint/parser": "7.2.0",
"@vitejs/plugin-react": "4.2.1",
"eslint": "8.57.0",
"eslint-plugin-react-hooks": "4.6.0",
@ -178,6 +178,34 @@
}
}
},
"internal-ui/node_modules/@typescript-eslint/parser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
"integrity": "sha512-5FKsVcHTk6TafQKQbuIVkXq58Fnbkd2wDL4LB7AURN7RUOu1utVP+G8+6u3ZhEroW3DF6hyo3ZEXxgKgp4KeCg==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.2.0",
"@typescript-eslint/types": "7.2.0",
"@typescript-eslint/typescript-estree": "7.2.0",
"@typescript-eslint/visitor-keys": "7.2.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^16.0.0 || >=18.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"internal-ui/node_modules/typescript": {
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",