From 26e522bab39c8c0260d855fcd067862e24712d49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=96=9F=E9=85=8C=20=E9=B5=AC=E5=85=84?= Date: Wed, 31 Aug 2022 21:59:22 +0800 Subject: [PATCH] Initial commit --- signin.js | 190 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) create mode 100644 signin.js diff --git a/signin.js b/signin.js new file mode 100644 index 0000000..2cd2a9a --- /dev/null +++ b/signin.js @@ -0,0 +1,190 @@ +"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;