diff --git a/src/MetadataService.ts b/src/MetadataService.ts index 6d3dd9fb7..5a9ec22cf 100644 --- a/src/MetadataService.ts +++ b/src/MetadataService.ts @@ -48,27 +48,24 @@ export class MetadataService { this._settings.signingKeys = undefined } - getMetadata(): Promise> { + async getMetadata(): Promise> { if (this._settings.metadata) { Log.debug("MetadataService.getMetadata: Returning metadata from settings"); - return Promise.resolve(this._settings.metadata); + return this._settings.metadata; } if (!this.metadataUrl) { Log.error("MetadataService.getMetadata: No authority or metadataUrl configured on settings"); - return Promise.reject(new Error("No authority or metadataUrl configured on settings")); + throw new Error("No authority or metadataUrl configured on settings"); } Log.debug("MetadataService.getMetadata: getting metadata from", this.metadataUrl); + const metadata = await this._jsonService.getJson(this.metadataUrl); - return this._jsonService.getJson(this.metadataUrl) - .then(metadata => { - Log.debug("MetadataService.getMetadata: json received"); - - var seed = this._settings.metadataSeed || {}; - this._settings.metadata = Object.assign({}, seed, metadata) as Partial; - return this._settings.metadata; - }); + Log.debug("MetadataService.getMetadata: json received"); + var seed = this._settings.metadataSeed || {}; + this._settings.metadata = Object.assign({}, seed, metadata) as Partial; + return this._settings.metadata; } getIssuer() { @@ -103,47 +100,44 @@ export class MetadataService { return this._getMetadataProperty("jwks_uri", optional) as Promise; } - _getMetadataProperty(name: keyof OidcMetadata, optional=false) { + async _getMetadataProperty(name: keyof OidcMetadata, optional=false) { Log.debug("MetadataService.getMetadataProperty for: " + name); - return this.getMetadata().then(metadata => { - Log.debug("MetadataService.getMetadataProperty: metadata recieved"); + const metadata = await this.getMetadata(); + Log.debug("MetadataService.getMetadataProperty: metadata recieved"); - if (metadata[name] === undefined) { - if (optional === true) { - Log.warn("MetadataService.getMetadataProperty: Metadata does not contain optional property " + name); - return undefined; - } - else { - Log.error("MetadataService.getMetadataProperty: Metadata does not contain property " + name); - throw new Error("Metadata does not contain property " + name); - } + if (metadata[name] === undefined) { + if (optional === true) { + Log.warn("MetadataService.getMetadataProperty: Metadata does not contain optional property " + name); + return undefined; + } + else { + Log.error("MetadataService.getMetadataProperty: Metadata does not contain property " + name); + throw new Error("Metadata does not contain property " + name); } + } - return metadata[name]; - }); + return metadata[name]; } - getSigningKeys() { + async getSigningKeys() { if (this._settings.signingKeys) { Log.debug("MetadataService.getSigningKeys: Returning signingKeys from settings"); - return Promise.resolve(this._settings.signingKeys); + return this._settings.signingKeys; } - return this.getKeysEndpoint(false).then(jwks_uri => { - Log.debug("MetadataService.getSigningKeys: jwks_uri received", jwks_uri); + const jwks_uri = await this.getKeysEndpoint(false); + Log.debug("MetadataService.getSigningKeys: jwks_uri received", jwks_uri); - return this._jsonService.getJson(jwks_uri as string).then(keySet => { - Log.debug("MetadataService.getSigningKeys: key set received", keySet); + const keySet = await this._jsonService.getJson(jwks_uri as string); + Log.debug("MetadataService.getSigningKeys: key set received", keySet); - if (!keySet.keys) { - Log.error("MetadataService.getSigningKeys: Missing keys on keyset"); - throw new Error("Missing keys on keyset"); - } + if (!keySet.keys) { + Log.error("MetadataService.getSigningKeys: Missing keys on keyset"); + throw new Error("Missing keys on keyset"); + } - this._settings.signingKeys = keySet.keys; - return this._settings.signingKeys; - }); - }); + this._settings.signingKeys = keySet.keys; + return this._settings.signingKeys; } } diff --git a/src/OidcClient.ts b/src/OidcClient.ts index 9bd478d61..d07d09056 100644 --- a/src/OidcClient.ts +++ b/src/OidcClient.ts @@ -36,7 +36,7 @@ export class OidcClient { return this._metadataService; } - createSigninRequest({ + async createSigninRequest({ response_type, scope, redirect_uri, // data was meant to be the place a caller could indicate the data to // have round tripped, but people were getting confused, so i added state (since that matches the spec) @@ -45,7 +45,7 @@ export class OidcClient { resource, request, request_uri, response_mode, extraQueryParams, extraTokenParams, request_type, skipUserInfo }: any = {}, stateStore?: StateStore - ) { + ): Promise { Log.debug("OidcClient.createSigninRequest"); let client_id = this._settings.client_id; @@ -67,36 +67,33 @@ export class OidcClient { let authority = this._settings.authority; if (SigninRequest.isCode(response_type) && response_type !== "code") { - return Promise.reject(new Error("OpenID Connect hybrid flow is not supported")); + throw new Error("OpenID Connect hybrid flow is not supported"); } - return this._metadataService.getAuthorizationEndpoint().then((url) => { - Log.debug("OidcClient.createSigninRequest: Received authorization endpoint", url); - - let signinRequest = new SigninRequest({ - url, - client_id, - redirect_uri, - response_type, - scope, - data: data || state, - authority, - prompt, display, max_age, ui_locales, id_token_hint, login_hint, acr_values, - resource, request, request_uri, extraQueryParams, extraTokenParams, request_type, response_mode, - client_secret: this._settings.client_secret, - skipUserInfo - }); - - var signinState = signinRequest.state; - stateStore = stateStore || this._stateStore; - - return stateStore.set(signinState.id, signinState.toStorageString()).then(() => { - return signinRequest; - }); + const url = await this._metadataService.getAuthorizationEndpoint(); + Log.debug("OidcClient.createSigninRequest: Received authorization endpoint", url); + + let signinRequest = new SigninRequest({ + url, + client_id, + redirect_uri, + response_type, + scope, + data: data || state, + authority, + prompt, display, max_age, ui_locales, id_token_hint, login_hint, acr_values, + resource, request, request_uri, extraQueryParams, extraTokenParams, request_type, response_mode, + client_secret: this._settings.client_secret, + skipUserInfo }); + + var signinState = signinRequest.state; + stateStore = stateStore || this._stateStore; + await stateStore.set(signinState.id, signinState.toStorageString()); + return signinRequest; } - readSigninResponseState(url?: string, stateStore: StateStore | null = null, removeState = false) { + async readSigninResponseState(url?: string, stateStore: StateStore | null = null, removeState = false) { Log.debug("OidcClient.readSigninResponseState"); let useQuery = this._settings.response_mode === "query" || @@ -105,37 +102,34 @@ export class OidcClient { let delimiter = useQuery ? "?" : "#"; var response = new SigninResponse(url, delimiter); - if (!response.state) { Log.error("OidcClient.readSigninResponseState: No state in response"); - return Promise.reject(new Error("No state in response")); + throw new Error("No state in response"); } stateStore = stateStore || this._stateStore; var stateApi = removeState ? stateStore.remove.bind(stateStore) : stateStore.get.bind(stateStore); - return stateApi(response.state).then(storedStateString => { - if (!storedStateString) { - Log.error("OidcClient.readSigninResponseState: No matching state found in storage"); - throw new Error("No matching state found in storage"); - } + const storedStateString = await stateApi(response.state); + if (!storedStateString) { + Log.error("OidcClient.readSigninResponseState: No matching state found in storage"); + throw new Error("No matching state found in storage"); + } - let state = SigninState.fromStorageString(storedStateString); - return {state, response}; - }); + let state = SigninState.fromStorageString(storedStateString); + return {state, response}; } - processSigninResponse(url: string, stateStore: StateStore | null = null) { + async processSigninResponse(url: string, stateStore: StateStore | null = null) { Log.debug("OidcClient.processSigninResponse"); - return this.readSigninResponseState(url, stateStore, true).then(({state, response}) => { - Log.debug("OidcClient.processSigninResponse: Received state from storage; validating response"); - return this._validator.validateSigninResponse(state, response); - }); + const { state, response } = await this.readSigninResponseState(url, stateStore, true); + Log.debug("OidcClient.processSigninResponse: Received state from storage; validating response"); + return this._validator.validateSigninResponse(state, response); } - createSignoutRequest({ + async createSignoutRequest({ id_token_hint, data, state, post_logout_redirect_uri, extraQueryParams, request_type }: any = {}, stateStore: StateStore | null = null @@ -145,36 +139,35 @@ export class OidcClient { post_logout_redirect_uri = post_logout_redirect_uri || this._settings.post_logout_redirect_uri; extraQueryParams = extraQueryParams || this._settings.extraQueryParams; - return this._metadataService.getEndSessionEndpoint().then(url => { - if (!url) { - Log.error("OidcClient.createSignoutRequest: No end session endpoint url returned"); - throw new Error("no end session endpoint"); - } + const url = await this._metadataService.getEndSessionEndpoint(); + if (!url) { + Log.error("OidcClient.createSignoutRequest: No end session endpoint url returned"); + throw new Error("no end session endpoint"); + } - Log.debug("OidcClient.createSignoutRequest: Received end session endpoint", url); + Log.debug("OidcClient.createSignoutRequest: Received end session endpoint", url); - let request = new SignoutRequest({ - url, - id_token_hint, - post_logout_redirect_uri, - data: data || state, - extraQueryParams, - request_type - }); + let request = new SignoutRequest({ + url, + id_token_hint, + post_logout_redirect_uri, + data: data || state, + extraQueryParams, + request_type + }); - var signoutState = request.state; - if (signoutState) { - Log.debug("OidcClient.createSignoutRequest: Signout request has state to persist"); + var signoutState = request.state; + if (signoutState) { + Log.debug("OidcClient.createSignoutRequest: Signout request has state to persist"); - stateStore = stateStore || this._stateStore; - stateStore.set(signoutState.id, signoutState.toStorageString()); - } + stateStore = stateStore || this._stateStore; + stateStore.set(signoutState.id, signoutState.toStorageString()); + } - return request; - }); + return request; } - readSignoutResponseState(url: string, stateStore: StateStore | null = null, removeState = false) + async readSignoutResponseState(url?: string, stateStore: StateStore | null = null, removeState = false) : Promise<{ state: undefined | State, response: SignoutResponse }> { Log.debug("OidcClient.readSignoutResponseState"); @@ -184,10 +177,10 @@ export class OidcClient { if (response.error) { Log.warn("OidcClient.readSignoutResponseState: Response was error: ", response.error); - return Promise.reject(new ErrorResponse(response)); + throw new ErrorResponse(response); } - return Promise.resolve({state: undefined, response}); + return {state: undefined, response}; } var stateKey = response.state; @@ -195,31 +188,28 @@ export class OidcClient { stateStore = stateStore || this._stateStore; var stateApi = removeState ? stateStore.remove.bind(stateStore) : stateStore.get.bind(stateStore); - return stateApi(stateKey).then(storedStateString => { - if (!storedStateString) { - Log.error("OidcClient.readSignoutResponseState: No matching state found in storage"); - throw new Error("No matching state found in storage"); - } - - let state = State.fromStorageString(storedStateString); + const storedStateString = await stateApi(stateKey); + if (!storedStateString) { + Log.error("OidcClient.readSignoutResponseState: No matching state found in storage"); + throw new Error("No matching state found in storage"); + } - return {state, response}; - }); + let state = State.fromStorageString(storedStateString); + return {state, response}; } - processSignoutResponse(url: string, stateStore: StateStore | null = null) { + async processSignoutResponse(url: string, stateStore: StateStore | null = null) { Log.debug("OidcClient.processSignoutResponse"); - return this.readSignoutResponseState(url, stateStore, true).then(({state, response}) => { - if (state) { - Log.debug("OidcClient.processSignoutResponse: Received state from storage; validating response"); - return this._validator.validateSignoutResponse(state, response); - } - else { - Log.debug("OidcClient.processSignoutResponse: No state from storage; skipping validating response"); - return response; - } - }); + const {state, response} = await this.readSignoutResponseState(url, stateStore, true); + if (state) { + Log.debug("OidcClient.processSignoutResponse: Received state from storage; validating response"); + return this._validator.validateSignoutResponse(state, response); + } + else { + Log.debug("OidcClient.processSignoutResponse: No state from storage; skipping validating response"); + return response; + } } clearStaleState(stateStore: StateStore | null = null): Promise { diff --git a/src/ResponseValidator.ts b/src/ResponseValidator.ts index 0e029fa8e..50d261d2f 100644 --- a/src/ResponseValidator.ts +++ b/src/ResponseValidator.ts @@ -35,25 +35,25 @@ export class ResponseValidator { this._tokenClient = new TokenClientCtor(this._settings); } - validateSigninResponse(state: SigninState, response: SigninResponse) { + async validateSigninResponse(state: SigninState, response: SigninResponse) { Log.debug("ResponseValidator.validateSigninResponse"); - return this._processSigninParams(state, response).then(response => { - Log.debug("ResponseValidator.validateSigninResponse: state processed"); - return this._validateTokens(state, response).then(response => { - Log.debug("ResponseValidator.validateSigninResponse: tokens validated"); - return this._processClaims(state, response).then(response => { - Log.debug("ResponseValidator.validateSigninResponse: claims processed"); - return response; - }); - }); - }); + response = await this._processSigninParams(state, response); + Log.debug("ResponseValidator.validateSigninResponse: state processed"); + + response = await this._validateTokens(state, response); + Log.debug("ResponseValidator.validateSigninResponse: tokens validated"); + + response = await this._processClaims(state, response); + Log.debug("ResponseValidator.validateSigninResponse: claims processed"); + + return response; } validateSignoutResponse(state: State, response: SignoutResponse) { if (state.id !== response.state) { Log.error("ResponseValidator.validateSignoutResponse: State does not match"); - return Promise.reject(new Error("State does not match")); + throw new Error("State does not match"); } // now that we know the state matches, take the stored data @@ -64,26 +64,26 @@ export class ResponseValidator { if (response.error) { Log.warn("ResponseValidator.validateSignoutResponse: Response was error", response.error); - return Promise.reject(new ErrorResponse(response)); + throw new ErrorResponse(response); } - return Promise.resolve(response); + return response; } _processSigninParams(state: SigninState, response: SigninResponse) { if (state.id !== response.state) { Log.error("ResponseValidator._processSigninParams: State does not match"); - return Promise.reject(new Error("State does not match")); + throw new Error("State does not match"); } if (!state.client_id) { Log.error("ResponseValidator._processSigninParams: No client_id on state"); - return Promise.reject(new Error("No client_id on state")); + throw new Error("No client_id on state"); } if (!state.authority) { Log.error("ResponseValidator._processSigninParams: No authority on state"); - return Promise.reject(new Error("No authority on state")); + throw new Error("No authority on state"); } // this allows the authority to be loaded from the signin state @@ -93,7 +93,7 @@ export class ResponseValidator { // ensure we're using the correct authority if the authority is not loaded from signin state else if (this._settings.authority && this._settings.authority !== state.authority) { Log.error("ResponseValidator._processSigninParams: authority mismatch on settings vs. signin state"); - return Promise.reject(new Error("authority mismatch on settings vs. signin state")); + throw new Error("authority mismatch on settings vs. signin state"); } // this allows the client_id to be loaded from the signin state if (!this._settings.client_id) { @@ -102,7 +102,7 @@ export class ResponseValidator { // ensure we're using the correct client_id if the client_id is not loaded from signin state else if (this._settings.client_id && this._settings.client_id !== state.client_id) { Log.error("ResponseValidator._processSigninParams: client_id mismatch on settings vs. signin state"); - return Promise.reject(new Error("client_id mismatch on settings vs. signin state")); + throw new Error("client_id mismatch on settings vs. signin state"); } // now that we know the state matches, take the stored data @@ -113,27 +113,27 @@ export class ResponseValidator { if (response.error) { Log.warn("ResponseValidator._processSigninParams: Response was error", response.error); - return Promise.reject(new ErrorResponse(response)); + throw new ErrorResponse(response); } if (state.nonce && !response.id_token) { Log.error("ResponseValidator._processSigninParams: Expecting id_token in response"); - return Promise.reject(new Error("No id_token in response")); + throw new Error("No id_token in response"); } if (!state.nonce && response.id_token) { Log.error("ResponseValidator._processSigninParams: Not expecting id_token in response"); - return Promise.reject(new Error("Unexpected id_token in response")); + throw new Error("Unexpected id_token in response"); } if (state.code_verifier && !response.code) { Log.error("ResponseValidator._processSigninParams: Expecting code in response"); - return Promise.reject(new Error("No code in response")); + throw new Error("No code in response"); } if (!state.code_verifier && response.code) { Log.error("ResponseValidator._processSigninParams: Not expecting code in response"); - return Promise.reject(new Error("Unexpected code in response")); + throw new Error("Unexpected code in response"); } if (!response.scope) { @@ -141,10 +141,10 @@ export class ResponseValidator { response.scope = state.scope; } - return Promise.resolve(response); + return response; } - _processClaims(state: SigninState, response: SigninResponse): Promise { + async _processClaims(state: SigninState, response: SigninResponse): Promise { if (response.isOpenIdConnect) { Log.debug("ResponseValidator._processClaims: response is OIDC, processing claims"); @@ -153,19 +153,18 @@ export class ResponseValidator { if (state.skipUserInfo !== true && this._settings.loadUserInfo && response.access_token) { Log.debug("ResponseValidator._processClaims: loading user info"); - return this._userInfoService.getClaims(response.access_token).then(claims => { - Log.debug("ResponseValidator._processClaims: user info claims received from user info endpoint"); + const claims = await this._userInfoService.getClaims(response.access_token); + Log.debug("ResponseValidator._processClaims: user info claims received from user info endpoint"); - if (claims.sub !== response.profile.sub) { - Log.error("ResponseValidator._processClaims: sub from user info endpoint does not match sub in id_token"); - return Promise.reject(new Error("sub from user info endpoint does not match sub in id_token")); - } + if (claims.sub !== response.profile.sub) { + Log.error("ResponseValidator._processClaims: sub from user info endpoint does not match sub in id_token"); + throw new Error("sub from user info endpoint does not match sub in id_token"); + } - response.profile = this._mergeClaims(response.profile, claims); - Log.debug("ResponseValidator._processClaims: user info claims received, updated profile:", response.profile); + response.profile = this._mergeClaims(response.profile, claims); + Log.debug("ResponseValidator._processClaims: user info claims received, updated profile:", response.profile); - return response; - }); + return response; } else { Log.debug("ResponseValidator._processClaims: not loading user info"); @@ -175,7 +174,7 @@ export class ResponseValidator { Log.debug("ResponseValidator._processClaims: response is not OIDC, not processing claims"); } - return Promise.resolve(response); + return response; } _mergeClaims(claims1: any, claims2: any) { @@ -247,10 +246,10 @@ export class ResponseValidator { } Log.debug("ResponseValidator._validateTokens: No code to process or id_token to validate"); - return Promise.resolve(response); + return response; } - _processCode(state: SigninState, response: SigninResponse): Promise { + async _processCode(state: SigninState, response: SigninResponse): Promise { var request = { client_id: state.client_id, client_secret: state.client_secret, @@ -263,151 +262,140 @@ export class ResponseValidator { Object.assign(request, state.extraTokenParams); } - return this._tokenClient.exchangeCode(request).then(tokenResponse => { - // merge - response.error = tokenResponse.error || response.error; - response.error_description = tokenResponse.error_description || response.error_description; - response.error_uri = tokenResponse.error_uri || response.error_uri; + const tokenResponse = await this._tokenClient.exchangeCode(request); + // merge + response.error = tokenResponse.error || response.error; + response.error_description = tokenResponse.error_description || response.error_description; + response.error_uri = tokenResponse.error_uri || response.error_uri; - response.id_token = tokenResponse.id_token || response.id_token; - response.session_state = tokenResponse.session_state || response.session_state; - response.access_token = tokenResponse.access_token || response.access_token; - response.token_type = tokenResponse.token_type || response.token_type; - response.scope = tokenResponse.scope || response.scope; - response.expires_in = parseInt(tokenResponse.expires_in) || response.expires_in; + response.id_token = tokenResponse.id_token || response.id_token; + response.session_state = tokenResponse.session_state || response.session_state; + response.access_token = tokenResponse.access_token || response.access_token; + response.token_type = tokenResponse.token_type || response.token_type; + response.scope = tokenResponse.scope || response.scope; + response.expires_in = parseInt(tokenResponse.expires_in) || response.expires_in; - if (response.id_token) { - Log.debug("ResponseValidator._processCode: token response successful, processing id_token"); - return this._validateIdTokenAttributes(state, response); - } - else { - Log.debug("ResponseValidator._processCode: token response successful, returning response"); - } + if (response.id_token) { + Log.debug("ResponseValidator._processCode: token response successful, processing id_token"); + return this._validateIdTokenAttributes(state, response); + } + else { + Log.debug("ResponseValidator._processCode: token response successful, returning response"); + } - return response; - }); + return response; } - _validateIdTokenAttributes(state: SigninState, response: SigninResponse) { - return this._metadataService.getIssuer().then(issuer => { + async _validateIdTokenAttributes(state: SigninState, response: SigninResponse) { + const issuer = await this._metadataService.getIssuer(); - let audience = state.client_id; - let clockSkewInSeconds = this._settings.clockSkew; - Log.debug("ResponseValidator._validateIdTokenAttributes: Validaing JWT attributes; using clock skew (in seconds) of: ", clockSkewInSeconds); + let audience = state.client_id; + let clockSkewInSeconds = this._settings.clockSkew; + Log.debug("ResponseValidator._validateIdTokenAttributes: Validaing JWT attributes; using clock skew (in seconds) of: ", clockSkewInSeconds); - return this._settings.getEpochTime().then(now => { - return JoseUtil.validateJwtAttributes(response.id_token, issuer, audience, clockSkewInSeconds, now).then(payload => { - - if (state.nonce && state.nonce !== payload.nonce) { - Log.error("ResponseValidator._validateIdTokenAttributes: Invalid nonce in id_token"); - return Promise.reject(new Error("Invalid nonce in id_token")); - } + const now = await this._settings.getEpochTime(); + const payload = await JoseUtil.validateJwtAttributes(response.id_token, issuer, audience, clockSkewInSeconds, now); + if (state.nonce && state.nonce !== payload.nonce) { + Log.error("ResponseValidator._validateIdTokenAttributes: Invalid nonce in id_token"); + throw new Error("Invalid nonce in id_token"); + } - if (!payload.sub) { - Log.error("ResponseValidator._validateIdTokenAttributes: No sub present in id_token"); - return Promise.reject(new Error("No sub present in id_token")); - } + if (!payload.sub) { + Log.error("ResponseValidator._validateIdTokenAttributes: No sub present in id_token"); + throw new Error("No sub present in id_token"); + } - response.profile = payload; - return response; - }); - }); - }); + response.profile = payload; + return response; } - _validateIdTokenAndAccessToken(state: SigninState, response: SigninResponse) { - return this._validateIdToken(state, response).then(response => { - return this._validateAccessToken(response); - }); + async _validateIdTokenAndAccessToken(state: SigninState, response: SigninResponse) { + response = await this._validateIdToken(state, response); + return this._validateAccessToken(response); } - _getSigningKeyForJwt(jwt: any) { - return this._metadataService.getSigningKeys().then(keys => { - const kid = jwt.header.kid; - if (!keys) { - Log.error("ResponseValidator._validateIdToken: No signing keys from metadata"); - return Promise.reject(new Error("No signing keys from metadata")); - } + async _getSigningKeyForJwt(jwt: any) { + let keys = await this._metadataService.getSigningKeys(); + const kid = jwt.header.kid; + if (!keys) { + Log.error("ResponseValidator._validateIdToken: No signing keys from metadata"); + throw new Error("No signing keys from metadata"); + } - Log.debug("ResponseValidator._validateIdToken: Received signing keys"); - let key; - if (!kid) { - keys = this._filterByAlg(keys, jwt.header.alg); - - if (keys.length > 1) { - Log.error("ResponseValidator._validateIdToken: No kid found in id_token and more than one key found in metadata"); - return Promise.reject(new Error("No kid found in id_token and more than one key found in metadata")); - } else { - // kid is mandatory only when there are multiple keys in the referenced JWK Set document - // see http://openid.net/specs/openid-connect-core-1_0.html#Signing - key = keys[0]; - } + Log.debug("ResponseValidator._validateIdToken: Received signing keys"); + let key; + if (!kid) { + keys = this._filterByAlg(keys, jwt.header.alg); + + if (keys.length > 1) { + Log.error("ResponseValidator._validateIdToken: No kid found in id_token and more than one key found in metadata"); + throw new Error("No kid found in id_token and more than one key found in metadata"); } else { - key = keys.filter(key => { - return key.kid === kid; - })[0]; + // kid is mandatory only when there are multiple keys in the referenced JWK Set document + // see http://openid.net/specs/openid-connect-core-1_0.html#Signing + key = keys[0]; } - return Promise.resolve(key); - }); + } else { + key = keys.filter(key => { + return key.kid === kid; + })[0]; + } + return key; } - _getSigningKeyForJwtWithSingleRetry(jwt: any) { - return this._getSigningKeyForJwt(jwt).then(key => { - // Refreshing signingKeys if no suitable verification key is present for given jwt header. - if (!key) { - // set to undefined, to trigger network call to jwks_uri. - this._metadataService.resetSigningKeys(); - return this._getSigningKeyForJwt(jwt); - } else { - return Promise.resolve(key); - } - }); + async _getSigningKeyForJwtWithSingleRetry(jwt: any) { + const key = await this._getSigningKeyForJwt(jwt); + // Refreshing signingKeys if no suitable verification key is present for given jwt header. + if (!key) { + // set to undefined, to trigger network call to jwks_uri. + this._metadataService.resetSigningKeys(); + return this._getSigningKeyForJwt(jwt); + } else { + return key; + } } - _validateIdToken(state: SigninState, response: SigninResponse): Promise { + async _validateIdToken(state: SigninState, response: SigninResponse): Promise { if (!state.nonce) { Log.error("ResponseValidator._validateIdToken: No nonce on state"); - return Promise.reject(new Error("No nonce on state")); + throw new Error("No nonce on state"); } const jwt = JoseUtil.parseJwt(response.id_token); if (!jwt || !jwt.header || !jwt.payload) { Log.error("ResponseValidator._validateIdToken: Failed to parse id_token", jwt); - return Promise.reject(new Error("Failed to parse id_token")); + throw new Error("Failed to parse id_token"); } const payload: any = jwt.payload; if (state.nonce !== payload.nonce) { Log.error("ResponseValidator._validateIdToken: Invalid nonce in id_token"); - return Promise.reject(new Error("Invalid nonce in id_token")); + throw new Error("Invalid nonce in id_token"); } - return this._metadataService.getIssuer().then(issuer => { - Log.debug("ResponseValidator._validateIdToken: Received issuer"); - return this._getSigningKeyForJwtWithSingleRetry(jwt).then(key => { - if (!key) { - Log.error("ResponseValidator._validateIdToken: No key matching kid or alg found in signing keys"); - return Promise.reject(new Error("No key matching kid or alg found in signing keys")); - } + const issuer = await this._metadataService.getIssuer(); + Log.debug("ResponseValidator._validateIdToken: Received issuer"); + const key = await this._getSigningKeyForJwtWithSingleRetry(jwt); + if (!key) { + Log.error("ResponseValidator._validateIdToken: No key matching kid or alg found in signing keys"); + throw new Error("No key matching kid or alg found in signing keys"); + } - let audience = state.client_id; + let audience = state.client_id; - let clockSkewInSeconds = this._settings.clockSkew; - Log.debug("ResponseValidator._validateIdToken: Validaing JWT; using clock skew (in seconds) of: ", clockSkewInSeconds); + let clockSkewInSeconds = this._settings.clockSkew; + Log.debug("ResponseValidator._validateIdToken: Validaing JWT; using clock skew (in seconds) of: ", clockSkewInSeconds); - return JoseUtil.validateJwt(response.id_token, key, issuer, audience, clockSkewInSeconds).then(()=>{ - Log.debug("ResponseValidator._validateIdToken: JWT validation successful"); + await JoseUtil.validateJwt(response.id_token, key, issuer, audience, clockSkewInSeconds); + Log.debug("ResponseValidator._validateIdToken: JWT validation successful"); - if (!payload.sub) { - Log.error("ResponseValidator._validateIdToken: No sub present in id_token"); - return Promise.reject(new Error("No sub present in id_token")); - } + if (!payload.sub) { + Log.error("ResponseValidator._validateIdToken: No sub present in id_token"); + throw new Error("No sub present in id_token"); + } - response.profile = payload; - return response; - }); - }); - }); + response.profile = payload; + return response; } _filterByAlg(keys: any[], alg: string){ @@ -440,59 +428,58 @@ export class ResponseValidator { _validateAccessToken(response: SigninResponse) { if (!response.profile) { Log.error("ResponseValidator._validateAccessToken: No profile loaded from id_token"); - return Promise.reject(new Error("No profile loaded from id_token")); + throw new Error("No profile loaded from id_token"); } if (!response.profile.at_hash) { Log.error("ResponseValidator._validateAccessToken: No at_hash in id_token"); - return Promise.reject(new Error("No at_hash in id_token")); + throw new Error("No at_hash in id_token"); } if (!response.id_token) { Log.error("ResponseValidator._validateAccessToken: No id_token"); - return Promise.reject(new Error("No id_token")); + throw new Error("No id_token"); } const jwt = JoseUtil.parseJwt(response.id_token); if (!jwt || !jwt.header) { Log.error("ResponseValidator._validateAccessToken: Failed to parse id_token", jwt); - return Promise.reject(new Error("Failed to parse id_token")); + throw new Error("Failed to parse id_token"); } var hashAlg = jwt.header.alg; if (!hashAlg || hashAlg.length !== 5) { Log.error("ResponseValidator._validateAccessToken: Unsupported alg:", hashAlg); - return Promise.reject(new Error("Unsupported alg: " + hashAlg)); + throw new Error("Unsupported alg: " + hashAlg); } const hashBitsString = hashAlg.substr(2, 3); if (!hashBitsString) { Log.error("ResponseValidator._validateAccessToken: Unsupported alg:", hashAlg, hashBitsString); - return Promise.reject(new Error("Unsupported alg: " + hashAlg)); + throw new Error("Unsupported alg: " + hashAlg); } const hashBits = parseInt(hashBitsString); if (hashBits !== 256 && hashBits !== 384 && hashBits !== 512) { Log.error("ResponseValidator._validateAccessToken: Unsupported alg:", hashAlg, hashBits); - return Promise.reject(new Error("Unsupported alg: " + hashAlg)); + throw new Error("Unsupported alg: " + hashAlg); } let sha = "sha" + hashBits; var hash = JoseUtil.hashString(response.access_token, sha); if (!hash) { Log.error("ResponseValidator._validateAccessToken: access_token hash failed:", sha); - return Promise.reject(new Error("Failed to validate at_hash")); + throw new Error("Failed to validate at_hash"); } var left = hash.substr(0, hash.length / 2); var left_b64u = JoseUtil.hexToBase64Url(left); if (left_b64u !== response.profile.at_hash) { Log.error("ResponseValidator._validateAccessToken: Failed to validate at_hash", left_b64u, response.profile.at_hash); - return Promise.reject(new Error("Failed to validate at_hash")); + throw new Error("Failed to validate at_hash"); } Log.debug("ResponseValidator._validateAccessToken: success"); - - return Promise.resolve(response); + return response; } } diff --git a/src/SessionMonitor.ts b/src/SessionMonitor.ts index 9f064f073..a93fa65cb 100644 --- a/src/SessionMonitor.ts +++ b/src/SessionMonitor.ts @@ -26,32 +26,33 @@ export class SessionMonitor { this._userManager.events.addUserLoaded(this._start.bind(this)); this._userManager.events.addUserUnloaded(this._stop.bind(this)); - Promise.resolve(this._userManager.getUser().then(user => { - // doing this manually here since calling getUser - // doesn't trigger load event. - if (user) { - this._start(user); - } - else if (this._settings.monitorAnonymousSession) { - this._userManager.querySessionStatus().then((session: any) => { - let tmpUser = { - session_state: session.session_state, - profile: session.sub && session.sid ? { - sub: session.sub, - sid: session.sid - } : null - }; - this._start(tmpUser); - }) - .catch((err: Error) => { - // catch to suppress errors since we're in a ctor - Log.error("SessionMonitor ctor: error from querySessionStatus:", err.message); - }); - } - }).catch((err: Error) => { + Promise.resolve(this._init()) + .catch((err: Error) => { // catch to suppress errors since we're in a ctor - Log.error("SessionMonitor ctor: error from getUser:", err.message); - })); + Log.error("SessionMonitor ctor: error:", err.message); + }); + } + + private async _init() { + const user = await this._userManager.getUser(); + // doing this manually here since calling getUser + // doesn't trigger load event. + if (user) { + this._start(user); + } + else if (this._settings.monitorAnonymousSession) { + const session = await this._userManager.querySessionStatus(); + if (session) { + let tmpUser = { + session_state: session.session_state, + profile: session.sub && session.sid ? { + sub: session.sub, + sid: session.sid + } : null + }; + this._start(tmpUser); + } + } } get _settings() { @@ -70,7 +71,7 @@ export class SessionMonitor { return this._settings.stopCheckSessionOnError; } - _start(user: any) { + async _start(user: any) { let session_state = user.session_state; if (session_state) { @@ -86,7 +87,8 @@ export class SessionMonitor { } if (!this._checkSessionIFrame) { - this._metadataService.getCheckSessionIframe().then(url => { + try { + const url = await this._metadataService.getCheckSessionIframe(); if (url) { Log.debug("SessionMonitor._start: Initializing check session iframe") @@ -95,18 +97,18 @@ export class SessionMonitor { let stopOnError = this._stopCheckSessionOnError; this._checkSessionIFrame = new this._CheckSessionIFrameCtor(this._callback.bind(this), client_id, url, interval, stopOnError); - this._checkSessionIFrame.load().then(() => { - this._checkSessionIFrame && - this._checkSessionIFrame.start(session_state); - }); + await this._checkSessionIFrame.load(); + this._checkSessionIFrame && + this._checkSessionIFrame.start(session_state); } else { Log.warn("SessionMonitor._start: No check session iframe found in the metadata"); } - }).catch(err => { + } + catch (err) { // catch to suppress errors since we're in non-promise callback Log.error("SessionMonitor._start: Error from getCheckSessionIframe:", err.message); - }); + } } else { this._checkSessionIFrame.start(session_state); @@ -125,10 +127,11 @@ export class SessionMonitor { if (this._settings.monitorAnonymousSession) { // using a timer to delay re-initialization to avoid race conditions during signout - let timerHandle = this._timer.setInterval(()=>{ + let timerHandle = this._timer.setInterval(async () => { this._timer.clearInterval(timerHandle); - this._userManager.querySessionStatus().then((session: any) => { + try { + const session: any = await this._userManager.querySessionStatus(); let tmpUser = { session_state: session.session_state, profile: session.sub && session.sid ? { @@ -137,18 +140,18 @@ export class SessionMonitor { } : null }; this._start(tmpUser); - }) - .catch((err: Error) => { + } + catch(err) { // catch to suppress errors since we're in a callback Log.error("SessionMonitor: error from querySessionStatus:", err.message); - }); - + } }, 1000); } } - _callback() { - this._userManager.querySessionStatus().then((session: any) => { + async _callback() { + try { + const session: any = await this._userManager.querySessionStatus(); var raiseEvent = true; if (session && this._checkSessionIFrame) { @@ -182,11 +185,12 @@ export class SessionMonitor { this._userManager.events._raiseUserSignedIn(); } } - }).catch((err: Error) => { + } + catch(err) { if (this._sub) { Log.debug("SessionMonitor._callback: Error calling queryCurrentSigninSession; raising signed out event", err.message); this._userManager.events._raiseUserSignedOut(); } - }); + } } } diff --git a/src/SignoutResponse.ts b/src/SignoutResponse.ts index e457486f0..903bbd3a1 100644 --- a/src/SignoutResponse.ts +++ b/src/SignoutResponse.ts @@ -9,7 +9,7 @@ export class SignoutResponse { public error_uri?: string; public state?: any; - constructor(url: string) { + constructor(url?: string) { var values = UrlUtility.parseUrlFragment(url, "?"); this.error = values.error; diff --git a/src/SilentRenewService.ts b/src/SilentRenewService.ts index e2634f56e..5ba501dbd 100644 --- a/src/SilentRenewService.ts +++ b/src/SilentRenewService.ts @@ -12,18 +12,20 @@ export class SilentRenewService { this._userManager = userManager; } - start() { + async start() { if (!this._callback) { this._callback = this._tokenExpiring.bind(this); this._userManager.events.addAccessTokenExpiring(this._callback); // this will trigger loading of the user so the expiring events can be initialized - this._userManager.getUser().then(_user => { + try { + await this._userManager.getUser(); // deliberate nop - }).catch(err => { + } + catch(err) { // catch to suppress errors since we're in a ctor Log.error("SilentRenewService.start: Error from getUser:", err.message); - }); + } } } @@ -34,12 +36,14 @@ export class SilentRenewService { } } - _tokenExpiring() { - this._userManager.signinSilent().then(_user => { + async _tokenExpiring() { + try { + await this._userManager.signinSilent(); Log.debug("SilentRenewService._tokenExpiring: Silent token renewal successful"); - }, err => { + } + catch (err) { Log.error("SilentRenewService._tokenExpiring: Error from signinSilent:", err.message); this._userManager.events._raiseSilentRenewError(err); - }); + } } } diff --git a/src/State.ts b/src/State.ts index 39e29d7e7..2343589ee 100644 --- a/src/State.ts +++ b/src/State.ts @@ -53,50 +53,40 @@ export class State { return new State(JSON.parse(storageString)); } - static clearStaleState(storage: StateStore, age: number) { - + static async clearStaleState(storage: StateStore, age: number) { var cutoff = Date.now() / 1000 - age; - return storage.getAllKeys().then(async (keys) => { - Log.debug("State.clearStaleState: got keys", keys); - - var promises = []; - for (let i = 0; i < keys.length; i++) { - let key = keys[i]; - var p = storage.get(key).then(item => { - let remove = false; + const keys = await storage.getAllKeys(); + Log.debug("State.clearStaleState: got keys", keys); - if (item) { - try { - var state = State.fromStorageString(item) + for (let i = 0; i < keys.length; i++) { + let key = keys[i]; + const item = await storage.get(key); + let remove = false; - Log.debug("State.clearStaleState: got item from key: ", key, state.created); + if (item) { + try { + var state = State.fromStorageString(item) - if (state.created <= cutoff) { - remove = true; - } - } - catch (e) { - Log.error("State.clearStaleState: Error parsing state for key", key, e.message); - remove = true; - } - } - else { - Log.debug("State.clearStaleState: no item in storage for key: ", key); + Log.debug("State.clearStaleState: got item from key: ", key, state.created); + if (state.created <= cutoff) { remove = true; } - - if (remove) { - Log.debug("State.clearStaleState: removed item for key: ", key); - storage.remove(key); - } - }); - - promises.push(p); + } + catch (e) { + Log.error("State.clearStaleState: Error parsing state for key", key, e.message); + remove = true; + } + } + else { + Log.debug("State.clearStaleState: no item in storage for key: ", key); + remove = true; } - Log.debug("State.clearStaleState: waiting on promise count:", promises.length); - await Promise.all(promises); - }); + if (remove) { + Log.debug("State.clearStaleState: removed item for key: ", key); + storage.remove(key); + } + } } } diff --git a/src/TokenClient.ts b/src/TokenClient.ts index b37c0e5f2..2021a8917 100644 --- a/src/TokenClient.ts +++ b/src/TokenClient.ts @@ -22,7 +22,7 @@ export class TokenClient { this._metadataService = new MetadataServiceCtor(this._settings); } - exchangeCode(args:any = {}): Promise { + async exchangeCode(args:any = {}): Promise { args = Object.assign({}, args); args.grant_type = args.grant_type || "authorization_code"; @@ -36,23 +36,23 @@ export class TokenClient { if (!args.code) { Log.error("TokenClient.exchangeCode: No code passed"); - return Promise.reject(new Error("A code is required")); + throw new Error("A code is required"); } if (!args.redirect_uri) { Log.error("TokenClient.exchangeCode: No redirect_uri passed"); - return Promise.reject(new Error("A redirect_uri is required")); + throw new Error("A redirect_uri is required"); } if (!args.code_verifier) { Log.error("TokenClient.exchangeCode: No code_verifier passed"); - return Promise.reject(new Error("A code_verifier is required")); + throw new Error("A code_verifier is required"); } if (!args.client_id) { Log.error("TokenClient.exchangeCode: No client_id passed"); - return Promise.reject(new Error("A client_id is required")); + throw new Error("A client_id is required"); } if (!args.client_secret && client_authentication == "client_secret_basic") { Log.error("TokenClient.exchangeCode: No client_secret passed"); - return Promise.reject(new Error("A client_secret is required")); + throw new Error("A client_secret is required"); } // Sending the client credentials using the Basic Auth method @@ -63,16 +63,16 @@ export class TokenClient { delete args.client_secret; } - return this._metadataService.getTokenEndpoint(false).then(url => { - Log.debug("TokenClient.exchangeCode: Received token endpoint"); - return this._jsonService.postForm(url as string, args, basicAuth).then(response => { - Log.debug("TokenClient.exchangeCode: response received"); - return response; - }); - }); + const url = await this._metadataService.getTokenEndpoint(false); + Log.debug("TokenClient.exchangeCode: Received token endpoint"); + + const response = await this._jsonService.postForm(url as string, args, basicAuth); + Log.debug("TokenClient.exchangeCode: response received"); + + return response; } - exchangeRefreshToken(args: any = {}) { + async exchangeRefreshToken(args: any = {}) { args = Object.assign({}, args); args.grant_type = args.grant_type || "refresh_token"; @@ -85,11 +85,11 @@ export class TokenClient { if (!args.refresh_token) { Log.error("TokenClient.exchangeRefreshToken: No refresh_token passed"); - return Promise.reject(new Error("A refresh_token is required")); + throw new Error("A refresh_token is required"); } if (!args.client_id) { Log.error("TokenClient.exchangeRefreshToken: No client_id passed"); - return Promise.reject(new Error("A client_id is required")); + throw new Error("A client_id is required"); } // Sending the client credentials using the Basic Auth method @@ -100,13 +100,12 @@ export class TokenClient { delete args.client_secret; } - return this._metadataService.getTokenEndpoint(false).then(url => { - Log.debug("TokenClient.exchangeRefreshToken: Received token endpoint"); + const url = await this._metadataService.getTokenEndpoint(false); + Log.debug("TokenClient.exchangeRefreshToken: Received token endpoint"); + + const response = await this._jsonService.postForm(url as string, args, basicAuth); + Log.debug("TokenClient.exchangeRefreshToken: response received"); - return this._jsonService.postForm(url as string, args, basicAuth).then(response => { - Log.debug("TokenClient.exchangeRefreshToken: response received"); - return response; - }); - }); + return response; } } diff --git a/src/TokenRevocationClient.ts b/src/TokenRevocationClient.ts index 18ae0d78e..5a45e08c9 100644 --- a/src/TokenRevocationClient.ts +++ b/src/TokenRevocationClient.ts @@ -24,7 +24,7 @@ export class TokenRevocationClient { this._metadataService = new MetadataServiceCtor(this._settings); } - revoke(token: string, required: boolean, type = "access_token") { + async revoke(token: string, required: boolean, type = "access_token") { if (!token) { Log.error("TokenRevocationClient.revoke: No token provided"); throw new Error("No token provided."); @@ -35,22 +35,21 @@ export class TokenRevocationClient { throw new Error("Invalid token type."); } - return this._metadataService.getRevocationEndpoint().then(url => { - if (!url) { - if (required) { - Log.error("TokenRevocationClient.revoke: Revocation not supported"); - throw new Error("Revocation not supported"); - } - - // not required, so don't error and just return - return; + const url = await this._metadataService.getRevocationEndpoint(); + if (!url) { + if (required) { + Log.error("TokenRevocationClient.revoke: Revocation not supported"); + throw new Error("Revocation not supported"); } - Log.debug("TokenRevocationClient.revoke: Revoking " + type); - var client_id = this._settings.client_id; - var client_secret = this._settings.client_secret; - return this._revoke(url, client_id, client_secret, token, type); - }); + // not required, so don't error and just return + return; + } + + Log.debug("TokenRevocationClient.revoke: Revoking " + type); + var client_id = this._settings.client_id; + var client_secret = this._settings.client_secret; + return this._revoke(url, client_id, client_secret, token, type); } _revoke(url: string, client_id: string, client_secret: string | undefined, token: string, type: string) { diff --git a/src/UserInfoService.ts b/src/UserInfoService.ts index a67c49432..c9fd904b7 100644 --- a/src/UserInfoService.ts +++ b/src/UserInfoService.ts @@ -26,96 +26,91 @@ export class UserInfoService { this._metadataService = new MetadataServiceCtor(this._settings); } - getClaims(token?: string) { + async getClaims(token?: string) { if (!token) { Log.error("UserInfoService.getClaims: No token passed"); - return Promise.reject(new Error("A token is required")); + throw new Error("A token is required"); } - return this._metadataService.getUserInfoEndpoint().then(url => { - Log.debug("UserInfoService.getClaims: received userinfo url", url); + const url = await this._metadataService.getUserInfoEndpoint(); + Log.debug("UserInfoService.getClaims: received userinfo url", url); - return this._jsonService.getJson(url, token).then(claims => { - Log.debug("UserInfoService.getClaims: claims received", claims); - return claims; - }); - }); + const claims = await this._jsonService.getJson(url, token); + Log.debug("UserInfoService.getClaims: claims received", claims); + + return claims; } - _getClaimsFromJwt(req: any) { + async _getClaimsFromJwt(req: any) { try { const jwt = JoseUtil.parseJwt(req.responseText); if (!jwt || !jwt.header || !jwt.payload) { Log.error("UserInfoService._getClaimsFromJwt: Failed to parse JWT", jwt); - return Promise.reject(new Error("Failed to parse id_token")); + throw new Error("Failed to parse id_token"); } const header: any = jwt.header; const payload: any = jwt.payload; - let issuerPromise; + let issuer: string; switch (this._settings.userInfoJwtIssuer) { case 'OP': - issuerPromise = this._metadataService.getIssuer(); + issuer = await this._metadataService.getIssuer(); break; case 'ANY': - issuerPromise = Promise.resolve(payload.iss); + issuer = payload.iss; break; default: - issuerPromise = Promise.resolve(this._settings.userInfoJwtIssuer); + issuer = this._settings.userInfoJwtIssuer as string; break; } - return issuerPromise.then(issuer => { - Log.debug("UserInfoService._getClaimsFromJwt: Received issuer:" + issuer); - - return this._metadataService.getSigningKeys().then(keys => { - if (!keys) { - Log.error("UserInfoService._getClaimsFromJwt: No signing keys from metadata"); - return Promise.reject(new Error("No signing keys from metadata")); - } - - Log.debug("UserInfoService._getClaimsFromJwt: Received signing keys"); - let key; - if (!header.kid) { - keys = this._filterByAlg(keys, jwt.header.alg); - - if (keys.length > 1) { - Log.error("UserInfoService._getClaimsFromJwt: No kid found in id_token and more than one key found in metadata"); - return Promise.reject(new Error("No kid found in id_token and more than one key found in metadata")); - } - else { - // kid is mandatory only when there are multiple keys in the referenced JWK Set document - // see http://openid.net/specs/openid-connect-core-1_0.html#Signing - key = keys[0]; - } - } - else { - key = keys.filter(key => { - return key.kid === header.kid; - })[0]; - } - - if (!key) { - Log.error("UserInfoService._getClaimsFromJwt: No key matching kid or alg found in signing keys"); - return Promise.reject(new Error("No key matching kid or alg found in signing keys")); - } - - let audience = this._settings.client_id; - - let clockSkewInSeconds = this._settings.clockSkew; - Log.debug("UserInfoService._getClaimsFromJwt: Validaing JWT; using clock skew (in seconds) of: ", clockSkewInSeconds); - - return JoseUtil.validateJwt(req.responseText, key, issuer, audience, clockSkewInSeconds, undefined, true).then(() => { - Log.debug("UserInfoService._getClaimsFromJwt: JWT validation successful"); - return payload; - }); - }); - }); + Log.debug("UserInfoService._getClaimsFromJwt: Received issuer:" + issuer); + + let keys = await this._metadataService.getSigningKeys(); + if (!keys) { + Log.error("UserInfoService._getClaimsFromJwt: No signing keys from metadata"); + throw new Error("No signing keys from metadata"); + } + + Log.debug("UserInfoService._getClaimsFromJwt: Received signing keys"); + let key; + if (!header.kid) { + keys = this._filterByAlg(keys, jwt.header.alg); + + if (keys.length > 1) { + Log.error("UserInfoService._getClaimsFromJwt: No kid found in id_token and more than one key found in metadata"); + throw new Error("No kid found in id_token and more than one key found in metadata"); + } + else { + // kid is mandatory only when there are multiple keys in the referenced JWK Set document + // see http://openid.net/specs/openid-connect-core-1_0.html#Signing + key = keys[0]; + } + } + else { + key = keys.filter(key => { + return key.kid === header.kid; + })[0]; + } + + if (!key) { + Log.error("UserInfoService._getClaimsFromJwt: No key matching kid or alg found in signing keys"); + throw new Error("No key matching kid or alg found in signing keys"); + } + + let audience = this._settings.client_id; + + let clockSkewInSeconds = this._settings.clockSkew; + Log.debug("UserInfoService._getClaimsFromJwt: Validaing JWT; using clock skew (in seconds) of: ", clockSkewInSeconds); + + await JoseUtil.validateJwt(req.responseText, key, issuer, audience, clockSkewInSeconds, undefined, true); + Log.debug("UserInfoService._getClaimsFromJwt: JWT validation successful"); + return payload; } catch (e) { Log.error("UserInfoService._getClaimsFromJwt: Error parsing JWT response", e.message); - return Promise.reject(e); + throw e; } } diff --git a/src/UserManager.ts b/src/UserManager.ts index abb37ece5..585ad4ad0 100644 --- a/src/UserManager.ts +++ b/src/UserManager.ts @@ -75,85 +75,81 @@ export class UserManager extends OidcClient { return this._events; } - getUser(): Promise { - return this._loadUser().then(user => { - if (user) { - Log.info("UserManager.getUser: user loaded"); + async getUser(): Promise { + const user = await this._loadUser(); + if (user) { + Log.info("UserManager.getUser: user loaded"); - this._events.load(user, false); + this._events.load(user, false); - return user; - } - else { - Log.info("UserManager.getUser: user not found in storage"); - return null; - } - }); + return user; + } + else { + Log.info("UserManager.getUser: user not found in storage"); + return null; + } } - removeUser(): Promise { - return this.storeUser(null).then(() => { - Log.info("UserManager.removeUser: user removed from storage"); - this._events.unload(); - }); + async removeUser(): Promise { + await this.storeUser(null); + Log.info("UserManager.removeUser: user removed from storage"); + this._events.unload(); } - signinRedirect(args: any = {}): Promise { + async signinRedirect(args: any = {}): Promise { args = Object.assign({}, args); args.request_type = "si:r"; let navParams = { useReplaceToNavigate : args.useReplaceToNavigate }; - return this._signinStart(args, this._redirectNavigator, navParams).then(()=>{ - Log.info("UserManager.signinRedirect: successful"); - }); + await this._signinStart(args, this._redirectNavigator, navParams); + Log.info("UserManager.signinRedirect: successful"); } - signinRedirectCallback(url?: string): Promise { - return this._signinEnd(url || this._redirectNavigator.url).then(user => { - if (user.profile && user.profile.sub) { - Log.info("UserManager.signinRedirectCallback: successful, signed in sub: ", user.profile.sub); - } - else { - Log.info("UserManager.signinRedirectCallback: no sub"); - } + async signinRedirectCallback(url?: string): Promise { + const user = await this._signinEnd(url || this._redirectNavigator.url); + if (user.profile && user.profile.sub) { + Log.info("UserManager.signinRedirectCallback: successful, signed in sub: ", user.profile.sub); + } + else { + Log.info("UserManager.signinRedirectCallback: no sub"); + } - return user; - }); + return user; } - signinPopup(args: any = {}): Promise { + async signinPopup(args: any = {}): Promise { args = Object.assign({}, args); args.request_type = "si:p"; let url = args.redirect_uri || this.settings.popup_redirect_uri || this.settings.redirect_uri; if (!url) { Log.error("UserManager.signinPopup: No popup_redirect_uri or redirect_uri configured"); - return Promise.reject(new Error("No popup_redirect_uri or redirect_uri configured")); + throw new Error("No popup_redirect_uri or redirect_uri configured"); } args.redirect_uri = url; args.display = "popup"; - return this._signin(args, this._popupNavigator, { + const user = await this._signin(args, this._popupNavigator, { startUrl: url, popupWindowFeatures: args.popupWindowFeatures || this.settings.popupWindowFeatures, popupWindowTarget: args.popupWindowTarget || this.settings.popupWindowTarget - }).then(user => { - if (user) { - if (user.profile && user.profile.sub) { - Log.info("UserManager.signinPopup: signinPopup successful, signed in sub: ", user.profile.sub); - } - else { - Log.info("UserManager.signinPopup: no sub"); - } + }); + if (user) { + if (user.profile && user.profile.sub) { + Log.info("UserManager.signinPopup: signinPopup successful, signed in sub: ", user.profile.sub); + } + else { + Log.info("UserManager.signinPopup: no sub"); } + } - return user; - }); + return user; } - signinPopupCallback(url?: string): Promise { - return this._signinCallback(url, this._popupNavigator).then(user => { + async signinPopupCallback(url?: string): Promise { + try { + const user = await this._signinCallback(url, this._popupNavigator); if (user) { if (user.profile && user.profile.sub) { Log.info("UserManager.signinPopupCallback: successful, signed in sub: ", user.profile.sub); @@ -164,182 +160,166 @@ export class UserManager extends OidcClient { } return user; - }).catch(err=>{ + } + catch (err) { Log.error("UserManager.signinPopupCallback error: " + err && err.message); return null; - }); + } } - signinSilent(args: any = {}): Promise { + async signinSilent(args: any = {}): Promise { args = Object.assign({}, args); // first determine if we have a refresh token, or need to use iframe - return this._loadUser().then(user => { - if (user && user.refresh_token) { - args.refresh_token = user.refresh_token; - return this._useRefreshToken(args); - } - else { - args.request_type = "si:s"; - args.id_token_hint = args.id_token_hint || (this.settings.includeIdTokenInSilentRenew && user && user.id_token); - if (user && this._settings.validateSubOnSilentRenew) { - Log.debug("UserManager.signinSilent, subject prior to silent renew: ", user.profile.sub); - args.current_sub = user.profile.sub; - } - return this._signinSilentIframe(args); + const user = await this._loadUser(); + if (user && user.refresh_token) { + args.refresh_token = user.refresh_token; + return this._useRefreshToken(args); + } + else { + args.request_type = "si:s"; + args.id_token_hint = args.id_token_hint || (this.settings.includeIdTokenInSilentRenew && user && user.id_token); + if (user && this._settings.validateSubOnSilentRenew) { + Log.debug("UserManager.signinSilent, subject prior to silent renew: ", user.profile.sub); + args.current_sub = user.profile.sub; } - }); + return this._signinSilentIframe(args); + } } - _useRefreshToken(args: any = {}) { - return this._tokenClient.exchangeRefreshToken(args).then(result => { - if (!result) { - Log.error("UserManager._useRefreshToken: No response returned from token endpoint"); - return Promise.reject("No response returned from token endpoint"); - } - if (!result.access_token) { - Log.error("UserManager._useRefreshToken: No access token returned from token endpoint"); - return Promise.reject("No access token returned from token endpoint"); + async _useRefreshToken(args: any = {}) { + const result = await this._tokenClient.exchangeRefreshToken(args); + if (!result) { + Log.error("UserManager._useRefreshToken: No response returned from token endpoint"); + throw new Error("No response returned from token endpoint"); + } + if (!result.access_token) { + Log.error("UserManager._useRefreshToken: No access token returned from token endpoint"); + throw new Error("No access token returned from token endpoint"); + } + + const user = await this._loadUser(); + if (user) { + if (result.id_token) { + await this._validateIdTokenFromTokenRefreshToken(user.profile, result.id_token); } - return this._loadUser().then(user => { - if (user) { - let idTokenValidation = Promise.resolve(); - if (result.id_token) { - idTokenValidation = this._validateIdTokenFromTokenRefreshToken(user.profile, result.id_token); - } - - return idTokenValidation.then(() => { - Log.debug("UserManager._useRefreshToken: refresh token response success"); - user.id_token = result.id_token || user.id_token; - user.access_token = result.access_token || user.access_token; - user.refresh_token = /* TODO: port-TS result.refresh_token ||*/ user.refresh_token; - user.expires_in = result.expires_in; - - return this.storeUser(user).then(()=>{ - this._events.load(user); - return user; - }); - }); - } - else { - return null; - } - }); - }); + Log.debug("UserManager._useRefreshToken: refresh token response success"); + user.id_token = result.id_token || user.id_token; + user.access_token = result.access_token || user.access_token; + user.refresh_token = /* TODO: port-TS result.refresh_token ||*/ user.refresh_token; + user.expires_in = result.expires_in; + + await this.storeUser(user); + this._events.load(user); + return user; + } + else { + return null; + } } - _validateIdTokenFromTokenRefreshToken(profile: any, id_token: string) { - return this._metadataService.getIssuer().then(issuer => { - return this.settings.getEpochTime().then(now => { - return JoseUtil.validateJwtAttributes(id_token, issuer, this._settings.client_id, this._settings.clockSkew, now).then(payload => { - if (!payload) { - Log.error("UserManager._validateIdTokenFromTokenRefreshToken: Failed to validate id_token"); - return Promise.reject(new Error("Failed to validate id_token")); - } - if (payload.sub !== profile.sub) { - Log.error("UserManager._validateIdTokenFromTokenRefreshToken: sub in id_token does not match current sub"); - return Promise.reject(new Error("sub in id_token does not match current sub")); - } - if (payload.auth_time && payload.auth_time !== profile.auth_time) { - Log.error("UserManager._validateIdTokenFromTokenRefreshToken: auth_time in id_token does not match original auth_time"); - return Promise.reject(new Error("auth_time in id_token does not match original auth_time")); - } - if (payload.azp && payload.azp !== profile.azp) { - Log.error("UserManager._validateIdTokenFromTokenRefreshToken: azp in id_token does not match original azp"); - return Promise.reject(new Error("azp in id_token does not match original azp")); - } - if (!payload.azp && profile.azp) { - Log.error("UserManager._validateIdTokenFromTokenRefreshToken: azp not in id_token, but present in original id_token"); - return Promise.reject(new Error("azp not in id_token, but present in original id_token")); - } - return Promise.resolve(); - }); - }); - }); + async _validateIdTokenFromTokenRefreshToken(profile: any, id_token: string) { + const issuer = await this._metadataService.getIssuer(); + const now = await this.settings.getEpochTime(); + const payload = await JoseUtil.validateJwtAttributes(id_token, issuer, this._settings.client_id, this._settings.clockSkew, now); + if (!payload) { + Log.error("UserManager._validateIdTokenFromTokenRefreshToken: Failed to validate id_token"); + throw new Error("Failed to validate id_token"); + } + if (payload.sub !== profile.sub) { + Log.error("UserManager._validateIdTokenFromTokenRefreshToken: sub in id_token does not match current sub"); + throw new Error("sub in id_token does not match current sub"); + } + if (payload.auth_time && payload.auth_time !== profile.auth_time) { + Log.error("UserManager._validateIdTokenFromTokenRefreshToken: auth_time in id_token does not match original auth_time"); + throw new Error("auth_time in id_token does not match original auth_time"); + } + if (payload.azp && payload.azp !== profile.azp) { + Log.error("UserManager._validateIdTokenFromTokenRefreshToken: azp in id_token does not match original azp"); + throw new Error("azp in id_token does not match original azp"); + } + if (!payload.azp && profile.azp) { + Log.error("UserManager._validateIdTokenFromTokenRefreshToken: azp not in id_token, but present in original id_token"); + throw new Error("azp not in id_token, but present in original id_token"); + } } - _signinSilentIframe(args: any = {}) { + async _signinSilentIframe(args: any = {}) { let url = args.redirect_uri || this.settings.silent_redirect_uri || this.settings.redirect_uri; if (!url) { Log.error("UserManager.signinSilent: No silent_redirect_uri configured"); - return Promise.reject(new Error("No silent_redirect_uri configured")); + throw new Error("No silent_redirect_uri configured"); } args.redirect_uri = url; args.prompt = args.prompt || "none"; - return this._signin(args, this._iframeNavigator, { + const user = await this._signin(args, this._iframeNavigator, { startUrl: url, silentRequestTimeout: args.silentRequestTimeout || this.settings.silentRequestTimeout - }).then(user => { - if (user) { - if (user.profile && user.profile.sub) { - Log.info("UserManager.signinSilent: successful, signed in sub: ", user.profile.sub); - } - else { - Log.info("UserManager.signinSilent: no sub"); - } + }); + if (user) { + if (user.profile && user.profile.sub) { + Log.info("UserManager.signinSilent: successful, signed in sub: ", user.profile.sub); } + else { + Log.info("UserManager.signinSilent: no sub"); + } + } - return user; - }); + return user; } - signinSilentCallback(url?: string): Promise { - return this._signinCallback(url, this._iframeNavigator).then(user => { - if (user) { - if (user.profile && user.profile.sub) { - Log.info("UserManager.signinSilentCallback: successful, signed in sub: ", user.profile.sub); - } - else { - Log.info("UserManager.signinSilentCallback: no sub"); - } + async signinSilentCallback(url?: string): Promise { + const user = await this._signinCallback(url, this._iframeNavigator); + if (user) { + if (user.profile && user.profile.sub) { + Log.info("UserManager.signinSilentCallback: successful, signed in sub: ", user.profile.sub); + } + else { + Log.info("UserManager.signinSilentCallback: no sub"); } + } - return user; - }); + return user; } - signinCallback(url?: string): Promise { - return this.readSigninResponseState(url).then(({state}) => { - if (state.request_type === "si:r") { - return this.signinRedirectCallback(url); - } - if (state.request_type === "si:p") { - return this.signinPopupCallback(url); - } - if (state.request_type === "si:s") { - return this.signinSilentCallback(url); - } - return Promise.reject(new Error("invalid response_type in state")); - }); + async signinCallback(url?: string): Promise { + const { state } = await this.readSigninResponseState(url); + if (state.request_type === "si:r") { + return this.signinRedirectCallback(url); + } + if (state.request_type === "si:p") { + return this.signinPopupCallback(url); + } + if (state.request_type === "si:s") { + return this.signinSilentCallback(url); + } + throw new Error("invalid response_type in state"); } - signoutCallback(url?: string, keepOpen = false): Promise { - // @ts-ignore - return this.readSignoutResponseState(url).then(({state, response}) => { - if (state) { - if (state.request_type === "so:r") { - return this.signoutRedirectCallback(url); - } - if (state.request_type === "so:p") { - return this.signoutPopupCallback(url, keepOpen); - } - return Promise.reject(new Error("invalid response_type in state")); + async signoutCallback(url?: string, keepOpen = false): Promise { + const { state } = await this.readSignoutResponseState(url); + if (state) { + if (state.request_type === "so:r") { + await this.signoutRedirectCallback(url); } - return response; - }); + if (state.request_type === "so:p") { + await this.signoutPopupCallback(url, keepOpen); + } + throw new Error("invalid response_type in state"); + } } - querySessionStatus(args: any = {}): Promise { + async querySessionStatus(args: any = {}): Promise { args = Object.assign({}, args); args.request_type = "si:s"; // this acts like a signin silent let url = args.redirect_uri || this.settings.silent_redirect_uri || this.settings.redirect_uri; if (!url) { Log.error("UserManager.querySessionStatus: No silent_redirect_uri configured"); - return Promise.reject(new Error("No silent_redirect_uri configured")); + throw new Error("No silent_redirect_uri configured"); } args.redirect_uri = url; @@ -348,92 +328,88 @@ export class UserManager extends OidcClient { args.scope = args.scope || "openid"; args.skipUserInfo = true; - return this._signinStart(args, this._iframeNavigator, { + const navResponse = await this._signinStart(args, this._iframeNavigator, { startUrl: url, silentRequestTimeout: args.silentRequestTimeout || this.settings.silentRequestTimeout - }).then((navResponse: any) => { - return this.processSigninResponse(navResponse.url).then(signinResponse => { - Log.debug("UserManager.querySessionStatus: got signin response"); - - if (signinResponse.session_state && signinResponse.profile.sub) { - Log.info("UserManager.querySessionStatus: querySessionStatus success for sub: ", signinResponse.profile.sub); + }); + try { + const signinResponse = await this.processSigninResponse(navResponse.url); + Log.debug("UserManager.querySessionStatus: got signin response"); + + if (signinResponse.session_state && signinResponse.profile.sub) { + Log.info("UserManager.querySessionStatus: querySessionStatus success for sub: ", signinResponse.profile.sub); + return { + session_state: signinResponse.session_state, + sub: signinResponse.profile.sub, + sid: signinResponse.profile.sid + }; + } + else { + Log.info("querySessionStatus successful, user not authenticated"); + return null; + } + } + catch (err) { + if (err.session_state && this.settings.monitorAnonymousSession) { + if (err.message == "login_required" || + err.message == "consent_required" || + err.message == "interaction_required" || + err.message == "account_selection_required" + ) { + Log.info("UserManager.querySessionStatus: querySessionStatus success for anonymous user"); return { - session_state: signinResponse.session_state, - sub: signinResponse.profile.sub, - sid: signinResponse.profile.sid + session_state: err.session_state }; } - else { - Log.info("querySessionStatus successful, user not authenticated"); - return null; - } - }) - .catch(err => { - if (err.session_state && this.settings.monitorAnonymousSession) { - if (err.message == "login_required" || - err.message == "consent_required" || - err.message == "interaction_required" || - err.message == "account_selection_required" - ) { - Log.info("UserManager.querySessionStatus: querySessionStatus success for anonymous user"); - return { - session_state: err.session_state - }; - } - } + } - throw err; - }); - }); + throw err; + } } - _signin(args: any, navigator: INavigator, navigatorParams: any = {}): Promise { - return this._signinStart(args, navigator, navigatorParams).then((navResponse: any) => { - return this._signinEnd(navResponse.url, args); - }); + async _signin(args: any, navigator: INavigator, navigatorParams: any = {}): Promise { + const navResponse = await this._signinStart(args, navigator, navigatorParams); + return this._signinEnd(navResponse.url, args); } - _signinStart(args: any, navigator: INavigator, navigatorParams: any = {}) { - return navigator.prepare(navigatorParams).then(handle => { - Log.debug("UserManager._signinStart: got navigator window handle"); + async _signinStart(args: any, navigator: INavigator, navigatorParams: any = {}) { + const handle = await navigator.prepare(navigatorParams); + Log.debug("UserManager._signinStart: got navigator window handle"); - return this.createSigninRequest(args).then(signinRequest => { - Log.debug("UserManager._signinStart: got signin request"); + try { + const signinRequest = await this.createSigninRequest(args); + Log.debug("UserManager._signinStart: got signin request"); - navigatorParams.url = signinRequest.url; - navigatorParams.id = signinRequest.state.id; + navigatorParams.url = signinRequest.url; + navigatorParams.id = signinRequest.state.id; - return handle.navigate(navigatorParams); - }).catch(err => { - Log.debug("UserManager._signinStart: Error after preparing navigator, closing navigator window"); - handle.close(); - throw err; - }); - }); + return handle.navigate(navigatorParams); + } + catch (err) { + Log.debug("UserManager._signinStart: Error after preparing navigator, closing navigator window"); + handle.close(); + throw err; + } } - _signinEnd(url: string, args: any = {}): Promise { - return this.processSigninResponse(url).then(signinResponse => { - Log.debug("UserManager._signinEnd: got signin response"); - - let user = new User(signinResponse); + async _signinEnd(url: string, args: any = {}): Promise { + const signinResponse = await this.processSigninResponse(url); + Log.debug("UserManager._signinEnd: got signin response"); - if (args.current_sub) { - if (args.current_sub !== user.profile.sub) { - Log.debug("UserManager._signinEnd: current user does not match user returned from signin. sub from signin: ", user.profile.sub); - return Promise.reject(new Error("login_required")); - } - else { - Log.debug("UserManager._signinEnd: current user matches user returned from signin"); - } + let user = new User(signinResponse); + if (args.current_sub) { + if (args.current_sub !== user.profile.sub) { + Log.debug("UserManager._signinEnd: current user does not match user returned from signin. sub from signin: ", user.profile.sub); + throw new Error("login_required"); } + else { + Log.debug("UserManager._signinEnd: current user matches user returned from signin"); + } + } - return this.storeUser(user).then(() => { - Log.debug("UserManager._signinEnd: user stored"); - - this._events.load(user); + await this.storeUser(user); + Log.debug("UserManager._signinEnd: user stored"); + this._events.load(user); - return user; - }); - }); + return user; } _signinCallback(url: string | undefined, navigator: IFrameNavigator | PopupNavigator): Promise { Log.debug("UserManager._signinCallback"); @@ -443,7 +419,7 @@ export class UserManager extends OidcClient { return navigator.callback(url, undefined, delimiter); } - signoutRedirect(args: any = {}): Promise { + async signoutRedirect(args: any = {}): Promise { args = Object.assign({}, args); args.request_type = "so:r"; @@ -454,18 +430,16 @@ export class UserManager extends OidcClient { let navParams = { useReplaceToNavigate : args.useReplaceToNavigate }; - return this._signoutStart(args, this._redirectNavigator, navParams).then(()=>{ - Log.info("UserManager.signoutRedirect: successful"); - }); + await this._signoutStart(args, this._redirectNavigator, navParams); + Log.info("UserManager.signoutRedirect: successful"); } - signoutRedirectCallback(url?: string): Promise { - return this._signoutEnd(url || this._redirectNavigator.url).then(response=>{ - Log.info("UserManager.signoutRedirectCallback: successful"); - return response; - }); + async signoutRedirectCallback(url?: string): Promise { + const response = await this._signoutEnd(url || this._redirectNavigator.url); + Log.info("UserManager.signoutRedirectCallback: successful"); + return response; } - signoutPopup(args: any = {}): Promise { + async signoutPopup(args: any = {}): Promise { args = Object.assign({}, args); args.request_type = "so:p"; @@ -481,134 +455,124 @@ export class UserManager extends OidcClient { args.state = args.state || {}; } - return this._signout(args, this._popupNavigator, { + await this._signout(args, this._popupNavigator, { startUrl: url, popupWindowFeatures: args.popupWindowFeatures || this.settings.popupWindowFeatures, popupWindowTarget: args.popupWindowTarget || this.settings.popupWindowTarget - }).then(() => { - Log.info("UserManager.signoutPopup: successful"); }); + Log.info("UserManager.signoutPopup: successful"); } - signoutPopupCallback(url: any, keepOpen: any) { + async signoutPopupCallback(url: any, keepOpen: any) { if (typeof(keepOpen) === 'undefined' && typeof(url) === 'boolean') { keepOpen = url; url = null; } let delimiter = '?'; - return this._popupNavigator.callback(url, keepOpen, delimiter).then(() => { - Log.info("UserManager.signoutPopupCallback: successful"); - }); + await this._popupNavigator.callback(url, keepOpen, delimiter); + Log.info("UserManager.signoutPopupCallback: successful"); } - _signout(args: any, navigator: INavigator, navigatorParams: any = {}) { - return this._signoutStart(args, navigator, navigatorParams).then((navResponse: any) => { - return this._signoutEnd(navResponse.url); - }); + async _signout(args: any, navigator: INavigator, navigatorParams: any = {}) { + const navResponse = await this._signoutStart(args, navigator, navigatorParams); + return this._signoutEnd(navResponse.url); } - _signoutStart(args: any = {}, navigator: INavigator, navigatorParams: any = {}) { - return navigator.prepare(navigatorParams).then(handle => { - Log.debug("UserManager._signoutStart: got navigator window handle"); - - return this._loadUser().then(user => { - Log.debug("UserManager._signoutStart: loaded current user from storage"); - - var revokePromise = this._settings.revokeAccessTokenOnSignout ? this._revokeInternal(user) : Promise.resolve(true); - return revokePromise.then(() => { - - var id_token = args.id_token_hint || user && user.id_token; - if (id_token) { - Log.debug("UserManager._signoutStart: Setting id_token into signout request"); - args.id_token_hint = id_token; - } - - return this.removeUser().then(() => { - Log.debug("UserManager._signoutStart: user removed, creating signout request"); - - return this.createSignoutRequest(args).then(signoutRequest => { - Log.debug("UserManager._signoutStart: got signout request"); - - navigatorParams.url = signoutRequest.url; - if (signoutRequest.state) { - navigatorParams.id = signoutRequest.state.id; - } - return handle.navigate(navigatorParams); - }); - }); - }); - }).catch(err => { - Log.debug("UserManager._signoutStart: Error after preparing navigator, closing navigator window"); - handle.close(); - throw err; - }); - }); + async _signoutStart(args: any = {}, navigator: INavigator, navigatorParams: any = {}) { + const handle = await navigator.prepare(navigatorParams); + Log.debug("UserManager._signoutStart: got navigator window handle"); + + try { + const user = await this._loadUser(); + Log.debug("UserManager._signoutStart: loaded current user from storage"); + + if (this._settings.revokeAccessTokenOnSignout) { + await this._revokeInternal(user); + } + + var id_token = args.id_token_hint || user && user.id_token; + if (id_token) { + Log.debug("UserManager._signoutStart: Setting id_token into signout request"); + args.id_token_hint = id_token; + } + + await this.removeUser(); + Log.debug("UserManager._signoutStart: user removed, creating signout request"); + + const signoutRequest = await this.createSignoutRequest(args); + Log.debug("UserManager._signoutStart: got signout request"); + + navigatorParams.url = signoutRequest.url; + if (signoutRequest.state) { + navigatorParams.id = signoutRequest.state.id; + } + return handle.navigate(navigatorParams); + } + catch (err) { + Log.debug("UserManager._signoutStart: Error after preparing navigator, closing navigator window"); + handle.close(); + throw err; + } } - _signoutEnd(url: string) { - return this.processSignoutResponse(url).then(signoutResponse => { - Log.debug("UserManager._signoutEnd: got signout response"); + async _signoutEnd(url: string) { + const signoutResponse = await this.processSignoutResponse(url); + Log.debug("UserManager._signoutEnd: got signout response"); - return signoutResponse; - }); + return signoutResponse; } - revokeAccessToken(): Promise { - return this._loadUser().then(user => { - return this._revokeInternal(user, true).then(success => { - if (success && user) { - Log.debug("UserManager.revokeAccessToken: removing token properties from user and re-storing"); + async revokeAccessToken(): Promise { + const user = await this._loadUser(); + const success = await this._revokeInternal(user, true); + if (success && user) { + Log.debug("UserManager.revokeAccessToken: removing token properties from user and re-storing"); - user.access_token = ""; - user.refresh_token = ""; - user.expires_at = 0; - user.token_type = ""; + user.access_token = ""; + user.refresh_token = ""; + user.expires_at = 0; + user.token_type = ""; - this.storeUser(user).then(() => { - Log.debug("UserManager.revokeAccessToken: user stored"); - this._events.load(user); - }); - } - }); - }).then(()=>{ - Log.info("UserManager.revokeAccessToken: access token revoked successfully"); - }); + await this.storeUser(user); + Log.debug("UserManager.revokeAccessToken: user stored"); + this._events.load(user); + } + + Log.info("UserManager.revokeAccessToken: access token revoked successfully"); } - _revokeInternal(user: User | null, required = false) { + async _revokeInternal(user: User | null, required = false): Promise { if (user) { var access_token = user.access_token; var refresh_token = user.refresh_token; - return this._revokeAccessTokenInternal(access_token, required) - .then(atSuccess => { - return this._revokeRefreshTokenInternal(refresh_token, required) - .then(rtSuccess => { - if (!atSuccess && !rtSuccess) { - Log.debug("UserManager.revokeAccessToken: no need to revoke due to no token(s), or JWT format"); - } + const atSuccess = await this._revokeAccessTokenInternal(access_token, required); + const rtSuccess = await this._revokeRefreshTokenInternal(refresh_token, required); + if (!atSuccess && !rtSuccess) { + Log.debug("UserManager.revokeAccessToken: no need to revoke due to no token(s), or JWT format"); + } - return atSuccess || rtSuccess; - }); - }); + return atSuccess || rtSuccess; } - return Promise.resolve(false); + return false; } - _revokeAccessTokenInternal(access_token: string, required: boolean): Promise { + async _revokeAccessTokenInternal(access_token: string, required: boolean): Promise { // check for JWT vs. reference token if (!access_token || access_token.indexOf('.') >= 0) { - return Promise.resolve(false); + return false; } - return this._tokenRevocationClient.revoke(access_token, required).then(() => true); + await this._tokenRevocationClient.revoke(access_token, required); + return true; } - _revokeRefreshTokenInternal(refresh_token: string, required: boolean): Promise { + async _revokeRefreshTokenInternal(refresh_token: string, required: boolean): Promise { if (!refresh_token) { - return Promise.resolve(false); + return false; } - return this._tokenRevocationClient.revoke(refresh_token, required, "refresh_token").then(() => true); + await this._tokenRevocationClient.revoke(refresh_token, required, "refresh_token"); + return true; } startSilentRenew(): void { @@ -623,16 +587,15 @@ export class UserManager extends OidcClient { return `user:${this.settings.authority}:${this.settings.client_id}`; } - _loadUser() { - return this._userStore.get(this._userStoreKey).then(storageString => { - if (storageString) { - Log.debug("UserManager._loadUser: user storageString loaded"); - return User.fromStorageString(storageString); - } + async _loadUser() { + const storageString = await this._userStore.get(this._userStoreKey); + if (storageString) { + Log.debug("UserManager._loadUser: user storageString loaded"); + return User.fromStorageString(storageString); + } - Log.debug("UserManager._loadUser: no user storageString"); - return null; - }); + Log.debug("UserManager._loadUser: no user storageString"); + return null; } storeUser(user: User | null): Promise { diff --git a/src/navigators/RedirectNavigator.ts b/src/navigators/RedirectNavigator.ts index d82ca277b..049952cb1 100644 --- a/src/navigators/RedirectNavigator.ts +++ b/src/navigators/RedirectNavigator.ts @@ -14,7 +14,7 @@ export class RedirectNavigator implements INavigator, IWindow { navigate(params: any) { if (!params || !params.url) { Log.error("RedirectNavigator.navigate: No url provided"); - return Promise.reject(new Error("No url provided")); + throw new Error("No url provided"); } if (params.useReplaceToNavigate) { diff --git a/src/utils/JoseUtil.ts b/src/utils/JoseUtil.ts index 88430520c..bd17ba984 100644 --- a/src/utils/JoseUtil.ts +++ b/src/utils/JoseUtil.ts @@ -34,24 +34,24 @@ export class JoseUtil { key = X509.getPublicKeyFromCertHex(hex); } else { Log.error("JoseUtil.validateJwt: RSA key missing key material", key); - return Promise.reject(new Error("RSA key missing key material")); + throw new Error("RSA key missing key material"); } } else if (key.kty === "EC") { if (key.crv && key.x && key.y) { key = KeyUtil.getKey(key); } else { Log.error("JoseUtil.validateJwt: EC key missing key material", key); - return Promise.reject(new Error("EC key missing key material")); + throw new Error("EC key missing key material"); } } else { Log.error("JoseUtil.validateJwt: Unsupported key type", key && key.kty); - return Promise.reject(new Error("Unsupported key type: " + key && key.kty)); + throw new Error("Unsupported key type: " + key && key.kty); } return JoseUtil._validateJwt(jwt, key, issuer, audience, clockSkew, now, timeInsensitive); } catch (e) { Log.error(e && e.message || e); - return Promise.reject("JWT validation failed"); + throw e; } } @@ -66,31 +66,31 @@ export class JoseUtil { const parsedJwt = JoseUtil.parseJwt(jwt); if (!parsedJwt || !parsedJwt.payload) { - return Promise.reject(new Error("Failed to parse token")); + throw new Error("Failed to parse token"); } const payload: any = parsedJwt.payload; if (!payload.iss) { Log.error("JoseUtil._validateJwt: issuer was not provided"); - return Promise.reject(new Error("issuer was not provided")); + throw new Error("issuer was not provided"); } if (payload.iss !== issuer) { Log.error("JoseUtil._validateJwt: Invalid issuer in token", payload.iss); - return Promise.reject(new Error("Invalid issuer in token: " + payload.iss)); + throw new Error("Invalid issuer in token: " + payload.iss); } if (!payload.aud) { Log.error("JoseUtil._validateJwt: aud was not provided"); - return Promise.reject(new Error("aud was not provided")); + throw new Error("aud was not provided"); } var validAudience = payload.aud === audience || (Array.isArray(payload.aud) && payload.aud.indexOf(audience) >= 0); if (!validAudience) { Log.error("JoseUtil._validateJwt: Invalid audience in token", payload.aud); - return Promise.reject(new Error("Invalid audience in token: " + payload.aud)); + throw new Error("Invalid audience in token: " + payload.aud); } if (payload.azp && payload.azp !== audience) { Log.error("JoseUtil._validateJwt: Invalid azp in token", payload.azp); - return Promise.reject(new Error("Invalid azp in token: " + payload.azp)); + throw new Error("Invalid azp in token: " + payload.azp); } if (!timeInsensitive) { @@ -99,46 +99,48 @@ export class JoseUtil { if (!payload.iat) { Log.error("JoseUtil._validateJwt: iat was not provided"); - return Promise.reject(new Error("iat was not provided")); + throw new Error("iat was not provided"); } if (lowerNow < payload.iat) { Log.error("JoseUtil._validateJwt: iat is in the future", payload.iat); - return Promise.reject(new Error("iat is in the future: " + payload.iat)); + throw new Error("iat is in the future: " + payload.iat); } if (payload.nbf && lowerNow < payload.nbf) { Log.error("JoseUtil._validateJwt: nbf is in the future", payload.nbf); - return Promise.reject(new Error("nbf is in the future: " + payload.nbf)); + throw new Error("nbf is in the future: " + payload.nbf); } if (!payload.exp) { Log.error("JoseUtil._validateJwt: exp was not provided"); - return Promise.reject(new Error("exp was not provided")); + throw new Error("exp was not provided"); } if (payload.exp < upperNow) { Log.error("JoseUtil._validateJwt: exp is in the past", payload.exp); - return Promise.reject(new Error("exp is in the past:" + payload.exp)); + throw new Error("exp is in the past:" + payload.exp); } } - return Promise.resolve(payload); + return payload; } static _validateJwt(jwt: any, key: string, issuer: string, audience: string, clockSkew: number, now?: number, timeInsensitive = false) { + const payload = JoseUtil.validateJwtAttributes(jwt, issuer, audience, clockSkew, now, timeInsensitive); - return JoseUtil.validateJwtAttributes(jwt, issuer, audience, clockSkew, now, timeInsensitive).then(payload => { - try { - if (!KJUR.jws.JWS.verify(jwt, key, AllowedSigningAlgs)) { - Log.error("JoseUtil._validateJwt: signature validation failed"); - return Promise.reject(new Error("signature validation failed")); - } + let isValid: boolean; + try { + isValid = KJUR.jws.JWS.verify(jwt, key, AllowedSigningAlgs); + } catch (e) { + Log.error(e && e.message || e); + throw new Error("signature validation failed"); + } - return payload; - } catch (e) { - Log.error(e && e.message || e); - return Promise.reject(new Error("signature validation failed")); - } - }); + if (!isValid) { + Log.error("JoseUtil._validateJwt: signature validation failed"); + throw new Error("signature validation failed"); + } + + return payload; } static hashString(value: any, alg: string) {