ext-openid/signin.js

225 lines
4.8 KiB
JavaScript

"use strict";
const cl = global.botanLoader;
const Dragonfly = global.Dragonfly;
const crypto = require( "crypto" );
const util = require( "util" );
const EventEmitter = require( "events" ).EventEmitter;
const http = require( "http" );
const HttpRequest = cl.load( "botanss.net.HttpRequest" );
class SignInEventArgs
{
constructor({ message, redirect, data = [], success = false } = {})
{
this.success = success;
this._message = message;
this._redirect = redirect;
this.data = data
}
get message()
{
return this._message;
}
get location()
{
return this._redirect + "?" + this.data.join( "&" );
}
}
class Signin extends EventEmitter
{
/** Conf structure
* {
* "spec": process.env.OPENID_SPEC_URI
* , "authorization_endpoint_params": {
* "client_id": process.env.AZURE_AD_CLIENT_ID
* , "redirect_url": `https://${HOST_NAME}/user/login/`
* , "nonce": "{RANDSTR}"
* , "state": "{RANDSTR}"
* , "scope": "openid profile email"
* , "response_mode": "form_post"
* , "response_type": "id_token"
* }
* , "end_session_endpoint_params": {
* "post_logout_redirect_uri": `https://${HOST_NAME}/user/logout/`
* }
*
* NOTE:
* set Signin().RANDSTR = () => SecureRandStr(); to enable custom templated functions
* * Only support "^{FUNCTION_NAME}$" for now
**/
constructor( conf )
{
super();
this.conf = conf;
this.spec = null;
}
Start( action, params, handler )
{
var _self = this;
if( this.spec )
{
_self[ action ]( params, handler );
}
else
{
if( !this.conf.spec )
{
var eArgs = new SignInEventArgs({ "success": false, "data": [] });
handler( this, eArgs );
return;
}
var request = new HttpRequest( this.conf.spec );
request.addListener( "RequestComplete", function( sender, e )
{
if( e.statusCode == 200 )
{
_self.spec = JSON.parse( e.ResponseString );
_self[ action ]( params, handler );
}
} );
request.Send();
}
}
ValidateAzureAD( params, handler )
{
if( !this.spec )
throw new Error( "OpenID spec is not present" );
var _self = this;
var jwt = params[ "id_token" ];
if( !jwt )
{
handler( this, new SignInEventArgs({
"message": params[ "error_description" ]
}) );
return;
}
var [ jHeader, jPayload, jSig ] = jwt.split( "." );
var header = JSON.parse( Buffer.from( jHeader, "base64" ) );
var payload = JSON.parse( Buffer.from( jPayload, "base64" ) );
var aud = payload[ "aud" ];
var clientId = this.conf.authorization_endpoint_params.client_id;
if( aud !== clientId )
{
handler( this, new SignInEventArgs({ message: `aud mismatched: ${aud}` }) );
return;
}
var [ url, data ] = this._endPointRes( "jwks_uri" );
var request = new HttpRequest( url + "?" + data.join( "&" ) );
request.addListener( "RequestComplete", function( sender, e )
{
var eArgs;
if( e.statusCode == 200 )
{
var respJson = JSON.parse( e.ResponseString );
var keyId = header[ "kid" ];
for( let key of respJson[ "keys" ] )
{
if( key[ "kid" ] === keyId )
{
var hashFunc = crypto.createVerify({
"RS256": "RSA-SHA256"
}[ header[ "alg" ] ]);
hashFunc.write( `${jHeader}.${jPayload}` );
hashFunc.end();
var pub = crypto.createPublicKey({
"key": {
"kty": key["kty"]
, "n": key["n"]
, "e": key["e"]
}, "format": "jwk"
});
eArgs = new SignInEventArgs(
{
"success": hashFunc.verify( pub, jSig, "base64" )
, "data": payload
});
break;
}
}
eArgs = eArgs || new SignInEventArgs({
"message": `Unable to find key: ${keyId}`
});
}
else
{
eArgs = new SignInEventArgs({
"message": `Remote returned ${e.statusCode}`
});
}
handler( _self, eArgs );
});
request.Send();
}
Authorize( params, handler )
{
var [ url, data ] = this._endPointRes( "authorization_endpoint" );
var eArgs = new SignInEventArgs({ "redirect": url, "data": data });
handler( this, eArgs );
}
Logout( params, handler )
{
var [ url, data ] = this._endPointRes( "end_session_endpoint" );
var eArgs = new SignInEventArgs({ "redirect": url, "data": data });
handler( this, eArgs );
}
_endPointRes( endpoint, handler )
{
if( !this.spec )
throw new Error( "OpenID spec is not present" );
var url = this.spec[ endpoint ];
if( !url )
throw new Error( "No such endpoint" );
var params = this.conf[ endpoint + "_params" ];
var requestData = [];
if( params )
{
for( var name in params )
{
var val = params[ name ];
if( val[0] == "{" )
{
val = this[ val.replace( "{", "" ).replace( "}", "" ) ]();
}
requestData.push( name + "=" + encodeURIComponent( val ) );
}
}
return [ url, requestData ];
}
}
module.exports = Signin;