UNPKG

lowcode-data-app

Version:
359 lines (299 loc) 11.2 kB
/* LOWCODE-DATA-APP / copyright 2024 by ma-ha https://github.com/ma-ha / MIT License */ const log = require( '../helper/log' ).logger const bodyParser = require( 'body-parser' ) const userDta = require( '../persistence/app-dta-user' ) const jwt = require( 'jsonwebtoken' ) // const mailer = require( '../mailer' ) exports: module.exports = { init } let gui = null let cfg = null async function init ( app, OIDCCfg ) { log.info( 'Init OpenID Login' ) cfg = OIDCCfg gui = app let oidcLoginPg = gui.addPage( 'openid-login-nonav', 'Login' ) oidcLoginPg.title = 'Login' oidcLoginPg.setPageWidth( '90%' ) oidcLoginPg.addView({ id: 'Login', title: 'Login', height: '450px', type: 'pong-form', resourceURL: 'oidc/login' }) let oidcChPwdPg = gui.addPage( 'change-password-nonav', 'ChangePassword' ) oidcChPwdPg.title = 'Change Password' oidcChPwdPg.setPageWidth( '90%' ) oidcChPwdPg.addView({ id: 'ChangePassword', title: 'Change Password', height: '450px', type: 'pong-form', resourceURL: 'oidc/password' }) svc = gui.getExpress() svc.get( '/oidc/authorize', authorize ) svc.get( '/oidc/login/pong-form', loginForm ) svc.post( '/oidc/token', bodyParser.urlencoded({ extended: false }), genToken ) svc.get( '/oidc/login/auto', autoLogin ) svc.post( '/oidc/login', bodyParser.urlencoded({ extended: false }), login ) svc.get( '/oidc/password/pong-form', passwordForm ) svc.post( '/oidc/password', bodyParser.urlencoded({ extended: false }), changePassword ) svc.get( '/oidc/logout', logout ) gui.createToken = createToken gui.getUserIdForToken = getUserIdForToken gui.getUserNameForToken = getUserNameForToken gui.deleteUserIdForToken = deleteUserIdForToken oidcSessions = await userDta.loadOidcSessions() } let oidcSessions = {} async function createToken( uid ) { // TODO: cluster enable // log.info( 'createToken', uid ) let token = gui.mkToken(32) oidcSessions[ token ] = { uid : uid } await userDta.saveOidcSessions( oidcSessions ) // log.info( 'createToken', token ) return token } async function getUserIdForToken( token ) { let session = oidcSessions[ token ] if ( ! session ) { return null } // log.info( 'getUserIdForToken', token, session ) return session.uid } async function getUserNameForToken( token ) { let session = oidcSessions[ token ] if ( ! session ) { return null } // log.info( 'getUserIdForToken', token, session ) return session.uid } async function deleteUserIdForToken( token ) { delete oidcSessions[ token ] await userDta.saveOidcSessions( oidcSessions ) } // ---------------------------------------------------------------------------- async function authorize( req, res ) { log.info( 'GET /oidc/authorize' ) try { let ctx = await getContextOrInfo( req ) if ( ctx && ctx.userId ) { // console.log( "ctx", ctx ) let redirectURL = req.query.redirect_uri let user = await userDta.getUserInfo( ctx.userId ) log.debug( "user", user ) let idToken = await oidcGetIdToken( req, user.userId, req.query.client_id ) let accessToken = await gui.createToken( user.userId ) redirectURL += ( redirectURL.indexOf( '?' ) > 0 ? '&' : '?' ) + 'id_token='+ idToken+'&access_token=' + accessToken return res.redirect( redirectURL ) } else { let loginURL = "../index.html?layout=openid-login-nonav" for ( let p in req.query ) { loginURL += `&${p}=${req.query[p]}` } log.info( 'GET /oidc/authorize append redirect ...' ) return res.redirect( loginURL ) } } catch (exc) { log.error( 'GET /oidc/authorize', exc ) let loginURL = "../index.html?layout=openid-login-nonav" for ( let p in req.query ) { loginURL += `&${p}=${req.query[p]}` } return res.redirect( loginURL ) } } async function loginForm ( req, res ) { log.info( 'GET /oidc/login/pong-form' ) // let prd = null // if ( req.query.client_id ) { // prd = await subDB.getProduct( helper.getTenant( req ), req.query.client_id ) // if ( ! prd ) { // return res.status( 200 ).send( { id: 'oIdcLoginForm', fieldGroups: [ { columns: [ { formFields: [ // { id: 'err', type: 'text', label: 'ERROR', readonly: true, defaultVal:'Client ID not valid' } // ] } ] } ] }) // } // } else { // return res.status( 200 ).send( { id: 'oIdcLoginForm', fieldGroups: [ { columns: [ { formFields: [ // { id: 'err', type: 'text', label: 'ERROR', readonly: true, defaultVal:'Client ID required' } // ] } ] } ] }) // } let frm = { id: 'oIdcLoginForm', fieldGroups: [ { columns: [ { formFields: [ { id: 'app', type: 'text', label: 'App', readonly: true, defaultVal: 'LowCode Data App' }, { id: 'client_id', type: 'text', hidden: true, value: req.query.client_id }, // refers subscription { id: 'scope', type: 'text', hidden: true, value: req.query.scope }, { id: 'audience', type: 'text', hidden: true, value: req.query.audience }, { id: 'redirect_uri', type: 'text', hidden: true, value: req.query.redirect_uri }, { id: 'response_type', type: 'text', hidden: true, value: req.query.response_type }, { id: 'email', type: 'text', label: 'Email' }, { id: 'password', type: 'password',label: 'Password' } ] } ] } ], actions: [ { id: 'oIdcLoginFormBtn', actionName: 'Login', actionURL: 'oidc/token', target: '_parent' } ] } res.send( frm ) } async function passwordForm ( req, res ) { log.info( 'GET /oidc/password/pong-form' ) let ctx = await getContextOrInfo( req ) if ( ctx && ctx.userId ) { // let user = await userDta.getUserInfo( ctx.userId ) let frm = { id: 'oidcChangePasswordForm', fieldGroups: [ { columns: [ { formFields: [ { id: 'email', type: 'text', label: 'Email', readonly: true, defaultVal: ctx.userId }, { id: 'password', type: 'password',label: 'Password' }, { id: 'newPassword', type: 'newPassword',label: 'New Password' } ] } ] } ], actions: [ { id: 'oIdcChangePasswordBtn', actionName: 'Change Password', actionURL: 'oidc/password', target: 'modal', navto: 'index.html' } ] } res.send( frm ) } else { res.send({}) } } async function genToken( req, res ) { log.info( 'POST /oidc/token' ) try { if ( req.body.client_id && req.body.redirect_uri && req.body.email && req.body.password ) { if ( await userDta.authenticate( req.body.email, req.body.password ) ) { let accessToken = await gui.createToken( req.body.email ) res.cookie( 'pong-security', accessToken, { httpOnly: true, path: gui.appRoot } ) let idToken = await oidcGetIdToken( req ) let redURL = req.body.redirect_uri redURL+= ( redURL.indexOf( '?' ) > 0 ? '&' : '?' ) + 'id_token='+ idToken+'&access_token=' + accessToken; res.send( redURL ) // } else { // res.status( 400 ).send( 'Not Authorized' ) // } } else { return res.status( 401 ).send( 'Failed' ) } } else { res.status( 400 ).send( 'Parameter Error' ) } } catch ( e ) { log.error( 'POST /oidct/token', e ) res.status( 500 ).send( [] ) } } async function autoLogin( req, res ) { log.info( 'GET /oidc/login/auto', req.query ) if ( req.query.client_id && req.query.email && req.query.t && req.query.redirect_uri ) { let accessToken = await gui.createToken( req.query.email ) res.cookie( 'pong-security', accessToken, { httpOnly: true, path: gui.appRoot } ) let idToken = await oidcGetIdToken( req, req.query.email, req.query.client_id ) let redURL = req.query.redirect_uri redURL+= ( redURL.indexOf( '?' ) > 0 ? '&' : '?' ) + 'id_token='+ idToken+'&access_token=' + accessToken; res.redirect( redURL ) } else { res.status( 400 ).send( 'Parameter Error' ) } } async function login( req, res ) { try { log.info( 'GET /oidct/login', req.body ) if ( req.body.client_id && req.body.email && req.body.password ) { if ( await userDta.authenticate( req.body.email, req.body.password ) ) { log.info( 'OIDC login', err, loginOK ) let accessToken = await gui.createToken( req.body.email ) res.cookie( 'pong-security', accessToken, { httpOnly: true, path: gui.appRoot } ) // let idToken = await oidcGetIdToken( req ) res.send( accessToken ) // } else { // res.status( 400 ).send( 'Not Authorized' ) // } } else { return res.status( 400 ).send( 'Failed' ) } } else { res.status( 400 ).send( 'Parameter Error' ) } } catch ( e ) { log.error( '/oidct/login', e ) res.status( 500 ).send( 'Error' ) } } async function changePassword( req, res ) { try { log.info( 'GET /oidct/password', req.body ) if (req.body.email && req.body.password ) { if ( await userDta.authenticate( req.body.email, req.body.password ) ) { log.info( 'changePassword authenticate ok' ) if ( req.body.newPassword.length < 8 ) { log.info( 'changePassword too short' ) return res.status( 400 ).send( 'Password length must be at least 8' ) } log.info( 'changePassword update...' ) let result = await userDta.updateUserPassword( req.body.email, req.body.newPassword ) log.info( 'changePassword done' ) return res.send( result ) } else { res.status( 400 ).send( 'Wrong Password' ) } } else { res.status( 400 ).send( 'Parameter Error' ) } } catch ( e ) { log.error( '/oidct/login', e ) res.status( 500 ).send( 'Error' ) } } function logout(req, res) { log.info( 'GET /oidc/logout' ) var token = req.cookies[ 'pong-security' ] res.redirect( req.query.return_to ) gui.deleteUserIdForToken( token ) } async function oidcGetIdToken( req, email, cltId ) { //console.log( 'oidcGetIdToken', email, cltId ) try { let uid = ( email ? email : req.body.email ) let clt = ( cltId ? cltId : req.body.client_id ) let user = await userDta.getUserInfo( uid ) // log.info( 'oidcGetIdToken', uid, user ) if ( ! user ) { return null } let userID = user.userId + '' //if ( sub.user.indexOf( userID ) >= 0 ) { TODO let idToken = jwt.sign({ iss : 'EkoSys', aud : clt, email : uid, name : userID, // user.name, sub : userID, exp : Math.round( Date.now() / 1000 + cfg.userSessionExpireMin * 60 ) //TODO? }, cfg.OPENID_SEC_KEY ); // log.info( 'token', idToken ) return idToken } catch ( exc ) { log.error( 'oidcGetIdToken', exc ) return null } } async function getContextOrInfo( req ) { let result = { tenantDN : '', userId : null } result.userId = await gui.getUserIdFromReq( req ) return result }