UNPKG

cbp-lib

Version:

Libraries for cbp

276 lines (249 loc) 11 kB
'use strict' import {httpRequest, authHeader} from '../_helpers/http' import {ArgumentError, Validation} from '../_helpers/custom-error' import {Schema, validateSchema} from '../_helpers/types' import queryString from 'query-string' import {Util} from '../_helpers/Util' import {TokenManager} from './TokenManager' import {Log} from '../_helpers/Log' import {SessionMonitor} from '../session/SessionMonitor' import {AuthenticationEvent} from './AuthenticationEvent' import uuid from 'uuid/v4' let _ = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : {}; let $ = _.document export class AuthenticationOidc { constructor(options = {}, SessionMonitorCtor = SessionMonitor) { if (typeof options !== 'object') { throw new ArgumentError('argument options must be type \'object\'') } this.options = options this._event = new AuthenticationEvent(options) this.storage = options.storage this.token_endpoint = options.token_endpoint this.authorization_server = options.authorization_server this.authentication_endpoint = options.authentication_endpoint //required this.redirect_uri = options.redirect_uri //required this.client_id = options.client_id //required this.response_type = options.response_type || 'code' this.scope = options.scope || 'openid' this.code_challenge_method = 's256' this.nonce = options.nonce this.state = options.state this.skip_login = 1 this.check_session_iframe = options.check_session_iframe || false this.prompt = options.prompt || 'none' this.token = new TokenManager({ store: this.storage }) // validate options validateSchema(Schema.authClientOptions, options) .catch(error => { Log.error("AuthenticationOidc: " + error.message) throw new ArgumentError(error.message) }) // check if monitor session is true then enable session monitoring if (this.options.monitor_session) { this._sessionMonitor = new SessionMonitorCtor(this) } } get event() { return this._event; } /** * Redirect the user to auth server authorization url * */ signInCallback() { return new Promise((resolve, reject) => { if (!Validation.isEmpty(this.authentication_endpoint)) { const util = new Util() let code_verifier = util.encryption.createCodeVerifier(uuid()) let code_challenge = util.encryption.createCodeChallenge(code_verifier) let paramString = queryString.stringify({ skip_login: this.skip_login, client_id: this.client_id, response_type: this.response_type, code_challenge, code_challenge_method: this.code_challenge_method, redirect_uri: this.redirect_uri, scope: this.scope, nonce: this.nonce }) let authorization_url = `${this.authentication_endpoint}?${paramString}` // store the code_verifier in localStorage this.storage.setItem('cvf', code_verifier) this.storage.setItem('state', this.state) if (this.options.display === 'popup') { let LeftPosition = (_.screen.width) ? (_.screen.width - 400)/2 : 0 let TopPosition = (_.screen.height) ? (_.screen.height - 500)/2 : 0 _.open(authorization_url, "Authorization Server CBP", `height=500,width=400,left=${LeftPosition},top=${TopPosition},resizable=no,scrollbars=yes,toolbar=yes,menubar=no,location=no,directories=no, status=yes`) } else { // redirect the user _.location.href = authorization_url } } else { reject(new ArgumentError('missing auth_endpoint')) } }) } /** * Catch the redirect * */ signInRedirectCallback() { return this._getToken() } /** * Internal implementation only do not use * */ _getToken() { return new Promise((resolve, reject) => { if (!Validation.isEmpty(this.token_endpoint)) { // Get data from the parameters let url = new URL($.location) let error = url.searchParams.get('error') let code = url.searchParams.get('code') let state = url.searchParams.get('state') let session_state = url.searchParams.get('session_state') let code_verifier = this.storage.getItem('cvf') if (Validation.isEmpty(error)) { if (!Validation.isEmpty(state)) { if (state !== this.storage.getItem('state')) { reject(new ArgumentError('state doesn\'t match. Possible CSRF attack!')) } } if (!Validation.isEmpty(session_state)) { this.storage.setItem('session_state', session_state) } if (!Validation.isEmpty(code)) { httpRequest('POST', this.token_endpoint, { client_id: this.client_id, grant_type: "authorization_code", code, code_verifier }) .then(token => { if (!Validation.isEmpty(token.error)) { Log.error("AuthenticatioOidc: " + JSON.stringify(token)) reject(error) } else { this.storage.setItem('token', token) this.token.decodeToken(token.access_token) .then(decode => { let newToken = { access_token: token.access_token, expires_in: decode.exp } Log.debug("AuthenticationOidc: " + decode) this._event.load(newToken) }) resolve(token) // if (this.options.display === 'popup') { // _.close() // } } }) .catch(error => { Log.error("AuthenticationOidc: Error Requesting token" + error) reject('Error Requesting token') }) } else { reject(new ArgumentError('missing code parameter in the query string')) } } else { reject(error) } } else { reject(new ArgumentError('missing token_endpoint')) } }) } querySessionState() { return new Promise((resolve, reject) => { let session_state = this.storage.getItem('session_state') if (Validation.isEmpty(session_state)) { Log.error('AuthenticationOidc: session_state is null') reject('AuthenticationOidc: session_state is null') } else { resolve(session_state) } }) } /** * Logout callback url redirect the user to auth server end session * */ logoutCallback() { return new Promise((resolve, reject) => { if (!Validation.isEmpty(this.options.end_session_endpoint) && !Validation.isEmpty(this.options.post_logout_redirect_uri)) { let idToken = this.token.getIDToken() let paramString = queryString.stringify({ id_token_hint: idToken, post_logout_redirect_uri: this.options.post_logout_redirect_uri, state: this.state || '' }) let logout_url = `${this.options.end_session_endpoint}?${paramString}` _.location.href = logout_url } else { reject(new ArgumentError('missing end_session_endpoint/id_token')) } }) } getUser() { return new Promise((resolve, reject) => { if (!Validation.isEmpty(this.options.userinfo_endpoint)) { //GET Token if (!Validation.isEmpty(this.storage)) { let token = this.storage.getItem("token") if (!Validation.isEmpty(token)) { let access_token = token.access_token if (!Validation.isEmpty(access_token)) { httpRequest("GET", this.options.userinfo_endpoint, '', { headers: { Authorization: "Bearer " + access_token } }) .then(user => { Log.debug("AuthenticationOidc.getUser: ", user) this.token.decodeToken(access_token) .then(decode => { let newToken = { access_token: token.access_token, expires_in: decode.exp } Log.debug("AuthenticationOidc.decode: " + decode) this._event.load(newToken) }) resolve(user) }) .catch(error => reject(error)) } else { reject(new Error('missing access_token')) } } else { reject(new Error('missing token in web storage.')) } } else { reject(new ArgumentError('missing storage object in option.')) } } else { reject(new ArgumentError('missing userinfo_endpoint')) } }) } }