191 lines
3.8 KiB
JavaScript
191 lines
3.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
|
||
|
{
|
||
|
constructor( conf )
|
||
|
{
|
||
|
super();
|
||
|
this.conf = conf;
|
||
|
this.spec = null;
|
||
|
}
|
||
|
|
||
|
Start( action, params, handler )
|
||
|
{
|
||
|
var request = new HttpRequest( this.conf.spec );
|
||
|
var _self = this;
|
||
|
|
||
|
if( this.spec )
|
||
|
{
|
||
|
_self[ action ]( params, handler );
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
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 );
|
||
|
}
|
||
|
|
||
|
_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;
|