cbp-lib
Version:
Libraries for cbp
276 lines (249 loc) • 11 kB
JavaScript
'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'))
}
})
}
}