diff --git a/etc/firebase-admin.api.md b/etc/firebase-admin.api.md index 39c413b7ec..1b9e7aa407 100644 --- a/etc/firebase-admin.api.md +++ b/etc/firebase-admin.api.md @@ -241,15 +241,23 @@ export namespace auth { export interface MultiFactorUpdateSettings { enrolledFactors: UpdateMultiFactorInfoRequest[] | null; } + export interface OAuthResponseType { + code?: boolean; + idToken?: boolean; + } export interface OIDCAuthProviderConfig extends AuthProviderConfig { clientId: string; + clientSecret?: string; issuer: string; + responseType?: OAuthResponseType; } export interface OIDCUpdateAuthProviderRequest { clientId?: string; + clientSecret?: string; displayName?: string; enabled?: boolean; issuer?: string; + responseType?: OAuthResponseType; } export interface PhoneIdentifier { // (undocumented) diff --git a/src/auth/auth-config.ts b/src/auth/auth-config.ts index 26db94126e..059d866574 100644 --- a/src/auth/auth-config.ts +++ b/src/auth/auth-config.ts @@ -24,6 +24,7 @@ import MultiFactorConfigState = auth.MultiFactorConfigState; import AuthFactorType = auth.AuthFactorType; import EmailSignInProviderConfig = auth.EmailSignInProviderConfig; import OIDCAuthProviderConfig = auth.OIDCAuthProviderConfig; +import OAuthResponseType = auth.OAuthResponseType; import SAMLAuthProviderConfig = auth.SAMLAuthProviderConfig; /** A maximum of 10 test phone number / code pairs can be configured. */ @@ -75,6 +76,8 @@ export interface OIDCConfigServerRequest { issuer?: string; displayName?: string; enabled?: boolean; + clientSecret?: string; + responseType?: OAuthResponseType; [key: string]: any; } @@ -87,6 +90,8 @@ export interface OIDCConfigServerResponse { issuer?: string; displayName?: string; enabled?: boolean; + clientSecret?: string; + responseType?: OAuthResponseType; } /** The server side email configuration request interface. */ @@ -650,6 +655,8 @@ export class OIDCConfig implements OIDCAuthProviderConfig { public readonly providerId: string; public readonly issuer: string; public readonly clientId: string; + public readonly clientSecret?: string; + public readonly responseType: OAuthResponseType; /** * Converts a client side request to a OIDCConfigServerRequest which is the format @@ -676,6 +683,12 @@ export class OIDCConfig implements OIDCAuthProviderConfig { request.displayName = options.displayName; request.issuer = options.issuer; request.clientId = options.clientId; + if (typeof options.clientSecret !== 'undefined') { + request.clientSecret = options.clientSecret; + } + if (typeof options.responseType !== 'undefined') { + request.responseType = options.responseType; + } return request; } @@ -715,6 +728,12 @@ export class OIDCConfig implements OIDCAuthProviderConfig { providerId: true, clientId: true, issuer: true, + clientSecret: true, + responseType: true, + }; + const validResponseTypes = { + idToken: true, + code: true, }; if (!validator.isNonNullObject(options)) { throw new FirebaseAuthError( @@ -773,6 +792,59 @@ export class OIDCConfig implements OIDCAuthProviderConfig { '"OIDCAuthProviderConfig.displayName" must be a valid string.', ); } + if (typeof options.clientSecret !== 'undefined' && + !validator.isNonEmptyString(options.clientSecret)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + '"OIDCAuthProviderConfig.clientSecret" must be a valid string.', + ); + } + if (validator.isNonNullObject(options.responseType) && typeof options.responseType !== 'undefined') { + Object.keys(options.responseType).forEach((key) => { + if (!(key in validResponseTypes)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_CONFIG, + `"${key}" is not a valid OAuthResponseType parameter.`, + ); + } + }); + + const idToken = options.responseType.idToken; + if (typeof idToken !== 'undefined' && !validator.isBoolean(idToken)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + '"OIDCAuthProviderConfig.responseType.idToken" must be a boolean.', + ); + } + + const code = options.responseType.code; + if (typeof code !== 'undefined') { + if (!validator.isBoolean(code)) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_ARGUMENT, + '"OIDCAuthProviderConfig.responseType.code" must be a boolean.', + ); + } + + // If code flow is enabled, client secret must be provided. + if (code && typeof options.clientSecret === 'undefined') { + throw new FirebaseAuthError( + AuthClientErrorCode.MISSING_OAUTH_CLIENT_SECRET, + 'The OAuth configuration client secret is required to enable OIDC code flow.', + ); + } + } + + const allKeys = Object.keys(options.responseType).length; + const enabledCount = Object.values(options.responseType).filter(Boolean).length; + // Only one of OAuth response types can be set to true. + if (allKeys > 1 && enabledCount != 1) { + throw new FirebaseAuthError( + AuthClientErrorCode.INVALID_OAUTH_RESPONSETYPE, + 'Only exactly one OAuth responseType should be set to true.', + ); + } + } } /** @@ -806,6 +878,13 @@ export class OIDCConfig implements OIDCAuthProviderConfig { // When enabled is undefined, it takes its default value of false. this.enabled = !!response.enabled; this.displayName = response.displayName; + + if (typeof response.clientSecret !== 'undefined') { + this.clientSecret = response.clientSecret; + } + if (typeof response.responseType !== 'undefined') { + this.responseType = response.responseType; + } } /** @return {OIDCAuthProviderConfig} The plain object representation of the OIDCConfig. */ @@ -816,6 +895,8 @@ export class OIDCConfig implements OIDCAuthProviderConfig { providerId: this.providerId, issuer: this.issuer, clientId: this.clientId, + clientSecret: deepCopy(this.clientSecret), + responseType: deepCopy(this.responseType), }; } } diff --git a/src/auth/index.ts b/src/auth/index.ts index b1bc47f725..83fb622d67 100644 --- a/src/auth/index.ts +++ b/src/auth/index.ts @@ -1289,6 +1289,25 @@ export namespace auth { callbackURL?: string; } + /** + * The interface representing OIDC provider's response object for OAuth + * authorization flow. + * We need either of them to be true, there are two cases: + * If set code to true, then we are doing code flow. + * If set idToken to true, then we are doing idToken flow. + */ + export interface OAuthResponseType { + /** + * Whether ID token is returned from IdP's authorization endpoint. + */ + idToken?: boolean; + + /** + * Whether authorization code is returned from IdP's authorization endpoint. + */ + code?: boolean; + } + /** * The [OIDC](https://openid.net/specs/openid-connect-core-1_0-final.html) Auth * provider configuration interface. An OIDC provider can be created via @@ -1321,6 +1340,16 @@ export namespace auth { * [spec](https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation). */ issuer: string; + + /** + * The OIDC provider's client secret to enable OIDC code flow. + */ + clientSecret?: string; + + /** + * The OIDC provider's response object for OAuth authorization flow. + */ + responseType?: OAuthResponseType; } /** @@ -1403,6 +1432,17 @@ export namespace auth { * configuration's value is not modified. */ issuer?: string; + + /** + * The OIDC provider's client secret to enable OIDC code flow. + * If not provided, the existing configuration's value is not modified. + */ + clientSecret?: string; + + /** + * The OIDC provider's response object for OAuth authorization flow. + */ + responseType?: OAuthResponseType; } /** diff --git a/src/utils/error.ts b/src/utils/error.ts index adc1b852b8..caa781e8f3 100644 --- a/src/utils/error.ts +++ b/src/utils/error.ts @@ -525,6 +525,10 @@ export class AuthClientErrorCode { code: 'invalid-provider-uid', message: 'The providerUid must be a valid provider uid string.', }; + public static INVALID_OAUTH_RESPONSETYPE = { + code: 'invalid-oauth-responsetype', + message: 'Only exactly one OAuth responseType should be set to true.', + }; public static INVALID_SESSION_COOKIE_DURATION = { code: 'invalid-session-cookie-duration', message: 'The session cookie duration must be a valid number in milliseconds ' + @@ -597,6 +601,10 @@ export class AuthClientErrorCode { code: 'missing-oauth-client-id', message: 'The OAuth/OIDC configuration client ID must not be empty.', }; + public static MISSING_OAUTH_CLIENT_SECRET = { + code: 'missing-oauth-client-secret', + message: 'The OAuth configuration client secret is required to enable OIDC code flow.', + }; public static MISSING_PROVIDER_ID = { code: 'missing-provider-id', message: 'A valid provider ID must be provided in the request.', diff --git a/test/integration/auth.spec.ts b/test/integration/auth.spec.ts index 29060b4cdd..075eacdca7 100644 --- a/test/integration/auth.spec.ts +++ b/test/integration/auth.spec.ts @@ -1334,12 +1334,21 @@ describe('admin.auth', () => { enabled: true, issuer: 'https://oidc.com/issuer1', clientId: 'CLIENT_ID1', + responseType: { + idToken: true, + code: false, + }, }; const modifiedConfigOptions = { displayName: 'OIDC_DISPLAY_NAME3', enabled: false, issuer: 'https://oidc.com/issuer3', clientId: 'CLIENT_ID3', + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + }, }; before(function() { @@ -1633,6 +1642,9 @@ describe('admin.auth', () => { enabled: true, issuer: 'https://oidc.com/issuer1', clientId: 'CLIENT_ID1', + responseType: { + idToken: true, + }, }; const authProviderConfig2 = { providerId: randomOidcProviderId(), @@ -1640,6 +1652,10 @@ describe('admin.auth', () => { enabled: true, issuer: 'https://oidc.com/issuer2', clientId: 'CLIENT_ID2', + clientSecret: 'CLIENT_SECRET', + responseType: { + code: true, + }, }; const removeTempConfigs = (): Promise => { @@ -1706,39 +1722,65 @@ describe('admin.auth', () => { }); }); - it('updateProviderConfig() successfully overwrites an OIDC config', () => { + it('updateProviderConfig() successfully partially modifies an OIDC config', () => { + const deltaChanges = { + displayName: 'OIDC_DISPLAY_NAME3', + enabled: false, + issuer: 'https://oidc.com/issuer3', + clientId: 'CLIENT_ID3', + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + }, + }; + // Only above fields should be modified. const modifiedConfigOptions = { + providerId: authProviderConfig1.providerId, displayName: 'OIDC_DISPLAY_NAME3', enabled: false, issuer: 'https://oidc.com/issuer3', clientId: 'CLIENT_ID3', + clientSecret: 'CLIENT_SECRET', + responseType: { + code: true, + }, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, modifiedConfigOptions) + return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) .then((config) => { - const modifiedConfig = deepExtend( - { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); - assertDeepEqualUnordered(modifiedConfig, config); + assertDeepEqualUnordered(modifiedConfigOptions, config); }); }); - it('updateProviderConfig() successfully partially modifies an OIDC config', () => { + it('updateProviderConfig() with invalid oauth response type should be rejected', () => { const deltaChanges = { displayName: 'OIDC_DISPLAY_NAME4', + enabled: false, issuer: 'https://oidc.com/issuer4', + clientId: 'CLIENT_ID4', + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: false, + }, }; - // Only above fields should be modified. - const modifiedConfigOptions = { - displayName: 'OIDC_DISPLAY_NAME4', + return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges). + should.eventually.be.rejected.and.have.property('code', 'auth/invalid-oauth-responsetype'); + }); + + it('updateProviderConfig() code flow with no client secret should be rejected', () => { + const deltaChanges = { + displayName: 'OIDC_DISPLAY_NAME5', enabled: false, - issuer: 'https://oidc.com/issuer4', - clientId: 'CLIENT_ID3', + issuer: 'https://oidc.com/issuer5', + clientId: 'CLIENT_ID5', + responseType: { + idToken: false, + code: true, + }, }; - return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges) - .then((config) => { - const modifiedConfig = deepExtend( - { providerId: authProviderConfig1.providerId }, modifiedConfigOptions); - assertDeepEqualUnordered(modifiedConfig, config); - }); + return admin.auth().updateProviderConfig(authProviderConfig1.providerId, deltaChanges). + should.eventually.be.rejected.and.have.property('code', 'auth/missing-oauth-client-secret'); }); it('deleteProviderConfig() successfully deletes an existing OIDC config', () => { diff --git a/test/unit/auth/auth-api-request.spec.ts b/test/unit/auth/auth-api-request.spec.ts index 0ca9bd5a85..373982a6f6 100644 --- a/test/unit/auth/auth-api-request.spec.ts +++ b/test/unit/auth/auth-api-request.spec.ts @@ -3505,6 +3505,8 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const providerId = 'oidc.provider'; const path = handler.path('v2', `/oauthIdpConfigs?oauthIdpConfigId=${providerId}`, 'project_id'); const expectedHttpMethod = 'POST'; + const clientSecret = 'CLIENT_SECRET'; + const responseType = { code: true }; const configOptions = { providerId, displayName: 'OIDC_DISPLAY_NAME', @@ -3521,6 +3523,26 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const expectedResult = utils.responseFrom(deepExtend({ name: `projects/project1/oauthIdpConfigs/${providerId}`, }, expectedRequest)); + const expectedCodeFlowOptions = { + providerId, + displayName: 'OIDC_DISPLAY_NAME', + enabled: true, + clientId: 'CLIENT_ID', + issuer: 'https://oidc.com/issuer', + clientSecret, + responseType, + }; + const expectedCodeFlowRequest = { + displayName: 'OIDC_DISPLAY_NAME', + enabled: true, + clientId: 'CLIENT_ID', + issuer: 'https://oidc.com/issuer', + clientSecret, + responseType, + }; + const expectedCodeFlowResult = utils.responseFrom(deepExtend({ + name: `projects/project1/oauthIdpConfigs/${providerId}`, + }, expectedCodeFlowRequest)); it('should be fulfilled given valid parameters', () => { const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedResult); @@ -3535,6 +3557,19 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); }); + it('should be fulfilled given valid parameters for OIDC code flow', () => { + const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedCodeFlowResult); + stubs.push(stub); + + const requestHandler = handler.init(mockApp); + return requestHandler.createOAuthIdpConfig(expectedCodeFlowOptions) + .then((response) => { + expect(response).to.deep.equal(expectedCodeFlowResult.data); + expect(stub).to.have.been.calledOnce.and.calledWith( + callParams(path, expectedHttpMethod, expectedCodeFlowRequest)); + }); + }); + it('should be rejected given invalid parameters', () => { const expectedError = new FirebaseAuthError( AuthClientErrorCode.INVALID_CONFIG, @@ -3597,6 +3632,8 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { const providerId = 'oidc.provider'; const path = handler.path('v2', `/oauthIdpConfigs/${providerId}`, 'project_id'); const expectedHttpMethod = 'PATCH'; + const clientSecret = 'CLIENT_SECRET'; + const responseType = { code: true }; const configOptions = { displayName: 'OIDC_DISPLAY_NAME', enabled: true, @@ -3620,6 +3657,26 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { clientId: 'NEW_CLIENT_ID', issuer: 'https://oidc.com/issuer2', })); + const expectedCodeFlowOptions = { + providerId, + displayName: 'OIDC_DISPLAY_NAME', + enabled: true, + clientId: 'CLIENT_ID', + issuer: 'https://oidc.com/issuer', + clientSecret, + responseType, + }; + const expectedCodeFlowRequest = { + displayName: 'OIDC_DISPLAY_NAME', + enabled: true, + clientId: 'CLIENT_ID', + issuer: 'https://oidc.com/issuer', + clientSecret, + responseType, + }; + const expectedCodeFlowResult = utils.responseFrom(deepExtend({ + name: `projects/project1/oauthIdpConfigs/${providerId}`, + }, expectedCodeFlowRequest)); it('should be fulfilled given full parameters', () => { const expectedPath = path + '?updateMask=enabled,displayName,issuer,clientId'; @@ -3635,6 +3692,20 @@ AUTH_REQUEST_HANDLER_TESTS.forEach((handler) => { }); }); + it('should be fulfilled given full parameters for OIDC code flow', () => { + const expectedPath = path + '?updateMask=enabled,displayName,issuer,clientId,clientSecret,responseType.code'; + const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedCodeFlowResult); + stubs.push(stub); + + const requestHandler = handler.init(mockApp); + return requestHandler.updateOAuthIdpConfig(providerId, expectedCodeFlowOptions) + .then((response) => { + expect(response).to.deep.equal(expectedCodeFlowResult.data); + expect(stub).to.have.been.calledOnce.and.calledWith( + callParams(expectedPath, expectedHttpMethod, expectedCodeFlowRequest)); + }); + }); + it('should be fulfilled given partial parameters', () => { const expectedPath = path + '?updateMask=enabled,clientId'; const stub = sinon.stub(HttpClient.prototype, 'send').resolves(expectedPartialResult); diff --git a/test/unit/auth/auth-config.spec.ts b/test/unit/auth/auth-config.spec.ts index ad81c5e62c..d9e6ce4abc 100644 --- a/test/unit/auth/auth-config.spec.ts +++ b/test/unit/auth/auth-config.spec.ts @@ -727,6 +727,11 @@ describe('OIDCConfig', () => { issuer: 'https://oidc.com/issuer', displayName: 'oidcProviderName', enabled: true, + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + }, }; const serverResponse: OIDCConfigServerResponse = { name: 'projects/project_id/oauthIdpConfigs/oidc.provider', @@ -734,6 +739,10 @@ describe('OIDCConfig', () => { issuer: 'https://oidc.com/issuer', displayName: 'oidcProviderName', enabled: true, + clientSecret: 'CLIENT_SECRET', + responseType: { + code: true, + }, }; const clientRequest: OIDCAuthProviderConfig = { providerId: 'oidc.provider', @@ -741,6 +750,11 @@ describe('OIDCConfig', () => { issuer: 'https://oidc.com/issuer', displayName: 'oidcProviderName', enabled: true, + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + }, }; const config = new OIDCConfig(serverResponse); @@ -769,6 +783,21 @@ describe('OIDCConfig', () => { expect(config.enabled).to.be.true; }); + it('should set readonly property clientSecret', () => { + expect(config.clientSecret).to.equal('CLIENT_SECRET'); + }); + + it('should set readonly property expected responseType', () => { + expect(config.responseType).to.deep.equal({ code: true }); + }); + + it('should not throw on no responseType and clientSecret', () => { + const testResponse = deepCopy(serverResponse); + delete testResponse.clientSecret; + delete testResponse.responseType; + expect(() => new OIDCConfig(testResponse)).not.to.throw(); + }); + it('should throw on missing issuer', () => { const invalidResponse = deepCopy(serverResponse); delete invalidResponse.issuer; @@ -831,6 +860,10 @@ describe('OIDCConfig', () => { providerId: 'oidc.provider', issuer: 'https://oidc.com/issuer', clientId: 'CLIENT_ID', + clientSecret: 'CLIENT_SECRET', + responseType: { + code: true, + }, }); }); }); @@ -844,12 +877,22 @@ describe('OIDCConfig', () => { const updateRequest: OIDCUpdateAuthProviderRequest = { clientId: 'CLIENT_ID', displayName: 'OIDC_PROVIDER_DISPLAY_NAME', + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + } }; const updateServerRequest: OIDCConfigServerRequest = { clientId: 'CLIENT_ID', displayName: 'OIDC_PROVIDER_DISPLAY_NAME', issuer: undefined, enabled: undefined, + clientSecret: 'CLIENT_SECRET', + responseType: { + idToken: false, + code: true, + } }; expect(OIDCConfig.buildServerRequest(updateRequest, true)).to.deep.equal(updateServerRequest); }); @@ -892,6 +935,62 @@ describe('OIDCConfig', () => { expect(() => OIDCConfig.validate(partialRequest, true)).not.to.throw(); }); + it('should throw on OAuth responseType contains invalid parameters', () => { + const invalidRequest = deepCopy(clientRequest) as any; + invalidRequest.responseType.unknownField = true; + expect(() => OIDCConfig.validate(invalidRequest, true)) + .to.throw('"unknownField" is not a valid OAuthResponseType parameter.'); + }); + + it('should not throw when exactly one OAuth responseType is true', () => { + const validRequest = deepCopy(clientRequest) as any; + validRequest.responseType.code = false; + validRequest.responseType.idToken = true; + expect(() => OIDCConfig.validate(validRequest, true)).not.to.throw(); + }); + + it('should not throw when only idToken responseType is set to true', () => { + const validRequest = deepCopy(clientRequest) as any; + validRequest.responseType = { idToken: true }; + expect(() => OIDCConfig.validate(validRequest, true)).not.to.throw(); + }); + + it('should not throw when only code responseType is set to true', () => { + const validRequest = deepCopy(clientRequest) as any; + const validResponseType = { code: true }; + validRequest.responseType = validResponseType; + expect(() => OIDCConfig.validate(validRequest, true)).not.to.throw(); + }); + + it('should throw on two OAuth responseTypes set to true', () => { + const invalidRequest = deepCopy(clientRequest) as any; + invalidRequest.responseType.idToken = true; + invalidRequest.responseType.code = true; + expect(() => OIDCConfig.validate(invalidRequest, true)) + .to.throw('Only exactly one OAuth responseType should be set to true.'); + }); + + it('should throw on no OAuth responseType set to true', () => { + const invalidRequest = deepCopy(clientRequest) as any; + invalidRequest.responseType.idToken = false; + invalidRequest.responseType.code = false; + expect(() => OIDCConfig.validate(invalidRequest, true)) + .to.throw('Only exactly one OAuth responseType should be set to true.'); + }); + + it('should not throw when responseType is empty', () => { + const testRequest = deepCopy(clientRequest) as any; + testRequest.responseType = {}; + expect(() => OIDCConfig.validate(testRequest, true)).not.to.throw(); + }); + + it('should throw on no client secret when OAuth responseType code flow set to true', () => { + const invalidRequest = deepCopy(clientRequest) as any; + delete invalidRequest.clientSecret; + expect(() => OIDCConfig.validate(invalidRequest, true)) + .to.throw('The OAuth configuration client secret is required to enable OIDC code flow.'); + }); + const nonObjects = [null, NaN, 0, 1, true, false, '', 'a', [], [1, 'a'], _.noop]; nonObjects.forEach((request) => { it('should throw on non-null OIDCAuthProviderConfig object:' + JSON.stringify(request), () => { @@ -957,5 +1056,35 @@ describe('OIDCConfig', () => { .to.throw('"OIDCAuthProviderConfig.displayName" must be a valid string.'); }); }); + + const invalidClientSecrets = [null, NaN, 0, 1, true, false, '', [], [1, 'a'], {}, { a: 1 }, _.noop]; + invalidClientSecrets.forEach((invalidClientSecret) => { + it('should throw on invalid clientSecret:' + JSON.stringify(invalidClientSecret), () => { + const invalidClientRequest = deepCopy(clientRequest) as any; + invalidClientRequest.clientSecret = invalidClientSecret; + expect(() => OIDCConfig.validate(invalidClientRequest)) + .to.throw('"OIDCAuthProviderConfig.clientSecret" must be a valid string.'); + }); + }); + + const invalidOAuthResponseIdTokenBooleans = [null, NaN, 0, 1, 'invalid', '', [], [1, 'a'], {}, { a: 1 }, _.noop]; + invalidOAuthResponseIdTokenBooleans.forEach((invalidOAuthResponseIdTokenBoolean) => { + it('should throw on invalid responseType.idToken:' + JSON.stringify(invalidOAuthResponseIdTokenBoolean), () => { + const invalidClientRequest = deepCopy(clientRequest) as any; + invalidClientRequest.responseType.idToken = invalidOAuthResponseIdTokenBoolean; + expect(() => OIDCConfig.validate(invalidClientRequest)) + .to.throw('"OIDCAuthProviderConfig.responseType.idToken" must be a boolean.'); + }); + }); + + const invalidOAuthResponseCodeBooleans = [null, NaN, 0, 1, 'invalid', '', [], [1, 'a'], {}, { a: 1 }, _.noop]; + invalidOAuthResponseCodeBooleans.forEach((invalidOAuthResponseCodeBoolean) => { + it('should throw on invalid responseType.code:' + JSON.stringify(invalidOAuthResponseCodeBoolean), () => { + const invalidClientRequest = deepCopy(clientRequest) as any; + invalidClientRequest.responseType.code = invalidOAuthResponseCodeBoolean; + expect(() => OIDCConfig.validate(invalidClientRequest)) + .to.throw('"OIDCAuthProviderConfig.responseType.code" must be a boolean.'); + }); + }); }); });