"use strict"; const jsonwebtoken = require('jsonwebtoken'); const { API_CONFIG, ROOT_PATH, LIB_PATH, HANDLERS_PATH } = process.env; const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` ); const { genKey, toSha256 } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` ); const { emailEvent , EMAIL_EVENTS } = require( `${ROOT_PATH}/${HANDLERS_PATH}/MailClient` ); const { create_account, already_exists, login, login_with_session_token, reset_password } = require( `${ROOT_PATH}/${HANDLERS_PATH}/Account` ); const { Validator } = require( "jsonschema" ); const jwtSecret = apiConfig.authentication.jwtSecret; const jwtTimeout = apiConfig.authentication.jwtTimeout;//Timeout in hours const jwtRenewalTimeout = apiConfig.authentication.jwtRenewalTimeout;//Timeout in hours const jwtOptions = apiConfig.authentication.jwtOptions; const validator = new Validator(); const create_account_schema = { type : 'object', properties : { email : { type : 'string' , maxLength : 256 }, password : { type : 'string', maxLength : 256}, otp : { type : 'string', maxLength : 6 }, company_type : { type : 'string', enum : ["Shipper" , "Carrier"] }, checksum : { type : 'string', maxLength : 32 } }, required : [ 'email', 'password' ] }; const confirm_account_schema = { type : 'object', properties : create_account_schema.properties,//Same properties required : [ 'email', 'password', 'otp', 'company_type', 'checksum' ]//Different requirements }; const login_account_schema = { type : 'object', properties : create_account_schema.properties,//Same properties required : [ 'email', 'password' ]//Different requirements }; const password_recover_schema = create_account_schema; const confirm_password_recover_schema = { type : 'object', properties : create_account_schema.properties,//Same properties required : [ 'email', 'password', 'otp', 'checksum' ]//Different requirements }; const AuthorizeJWT = async(req, res) => { try{ if( validator.validate( req.body , login_account_schema ).valid ){ const { email, password } = req.body; const user = await login( email, password ); if( !user ){ return res.status(401).send( { error : "Invalid credentials" } ); } const current_date = new Date(); const iat = Math.floor( (current_date.getTime())/1000 ); const renewal_exp = ( iat + 3600*jwtRenewalTimeout ) * 1000; /** * Renew session token on every login event. * Previous session token is lost */ const session_token = toSha256( `${new Date()}` ); const session_token_exp = new Date( renewal_exp ); user.session_token = session_token; user.session_token_exp = session_token_exp; await user.save(); const payload = { iat: iat, exp: iat + jwtTimeout * 3600, aud: jwtOptions.audience, iss: jwtOptions.audience, sub: user.id, }; const jwt = jsonwebtoken.sign( payload , jwtSecret ); return res.status(200).send( { accessToken : jwt, payload : payload, session_token, session_token_exp, user : user } ); }else{ return res.status(400).send( { error : "Invalid request" } ); } }catch( err ){ console.error( err ); res.status(500).send({ error : "Login: Internal error" }); } }; const RenewJWT = async(req, res) => { try{ const login_session_token = req.params.session_token; const user = await login_with_session_token( login_session_token ); if( !user ){ return res.status(401).send( { error : "Invalid or Expired Session Token" } ); } const current_date = new Date(); const iat = Math.floor( (current_date.getTime())/1000 ); const renewal_exp = ( iat + 3600*jwtRenewalTimeout ) * 1000; /** * Renew session token on every login event. * Previous session token is lost */ const session_token = toSha256( `${new Date()}` ); const session_token_exp = new Date( renewal_exp ); user.session_token = session_token; user.session_token_exp = session_token_exp; await user.save(); const payload = { iat: iat, exp: iat + jwtTimeout * 3600, aud: jwtOptions.audience, iss: jwtOptions.audience, sub: user.id, }; const jwt = jsonwebtoken.sign( payload , jwtSecret ); return res.status(200).send( { accessToken : jwt, payload : payload, session_token, session_token_exp, user : user } ); }catch( err ){ console.error( err ); res.status(500).send({ error : "Renew: Internal error" }); } }; const TryCreateAccount = async(req, res) => { try{ if( validator.validate( req.body , create_account_schema ).valid ){ const otp = genKey(); const { email : receiver , password } = req.body; const email = receiver; const it_exists = await already_exists( email ); if( it_exists ){ return res.status(400).send({ error : "Email already exists" }); } const content = { OTP : otp, user_name : email }; const checksum_entry = { email : receiver, password, otp }; const checksum = toSha256( JSON.stringify(checksum_entry)).substr(0, 32); await emailEvent( EMAIL_EVENTS.ACCOUNT_VERIFY , receiver , content ); console.log( content ); res.status(200).send( { checksum } ); }else{ res.status(400).send( { error : "Invalid request" } ); } }catch( err ){ console.error( err ); res.status(500).send({ error : "Account creation: Internal error" }); } }; const ConfirmAccount = async(req, res) => { try{ if( validator.validate( req.body , confirm_account_schema ).valid ){ const { email, password, otp, company_type, checksum } = req.body; const it_exists = await already_exists( email ); if( it_exists ){ return res.status(400).send({ error : "User already registered!" }); } const checksum_entry = {email, password, otp}; const recomputed_checksum = toSha256( JSON.stringify(checksum_entry)).substr(0, 32); if( recomputed_checksum != checksum ){ return res.status(400).send({ error : "Wrong OTP" }); } await create_account( email, password, company_type ); const content = { user_name : email }; const receiver = email; // await emailEvent( EMAIL_EVENTS.ACCOUNT_CONFIRMED , receiver , content ); console.log( content ); return res.status(200).send( { msg : "User created successfully!" } ); }else{ return res.status(400).send( { error : "Invalid request" } ); } }catch( err ){ console.error( err ); return res.status(500).send({ error : "Account creation: Internal error" }); } }; const RecoverPwd = async(req, res) => { try{ if( validator.validate( req.body , password_recover_schema ).valid ){ const otp = genKey(); const { email : receiver , password } = req.body; const email = receiver; const it_exists = await already_exists( email ); if( !it_exists ){ return res.status(400).send({ error : "Email is not registered!" }); } const content = { OTP : otp, user_name : email }; const checksum_entry = { email : receiver, password, otp }; const checksum = toSha256( JSON.stringify(checksum_entry)).substr(0, 32); await emailEvent( EMAIL_EVENTS.ACCOUNT_VERIFY , receiver , content ); console.log( content ); res.status(200).send( { checksum } ); }else{ res.status(400).send( { error : "Invalid request" } ); } }catch( err ){ console.error( err ); res.status(500).send({ error : "Password Recover: Internal error" }); } }; const ConfirmRecoverPwd = async(req, res) => { try{ if( validator.validate( req.body , confirm_password_recover_schema ).valid ){ const { email, password, otp, checksum } = req.body; const it_exists = await already_exists( email ); if( !it_exists ){ return res.status(400).send({ error : "Email is not registered!" }); } const checksum_entry = {email, password, otp}; const recomputed_checksum = toSha256( JSON.stringify(checksum_entry)).substr(0, 32); if( recomputed_checksum != checksum ){ return res.status(400).send({ error : "Wrong OTP" }); } await reset_password( email, password ); return res.status(200).send( { msg : "Password is reset!" } ); }else{ return res.status(400).send( { error : "Invalid request" } ); } }catch( err ){ console.error( err ); return res.status(500).send({ error : "Password Recover Confirmation: Internal error" }); } }; module.exports = { AuthorizeJWT, RenewJWT, TryCreateAccount, ConfirmAccount, RecoverPwd, ConfirmRecoverPwd};