diff --git a/server/src/Apps/Account/Controller/index.js b/server/src/Apps/Account/Controller/index.js index 60e4312..da1aae9 100644 --- a/server/src/Apps/Account/Controller/index.js +++ b/server/src/Apps/Account/Controller/index.js @@ -9,25 +9,11 @@ function dummy_middleware( req, res ){ router.post('/register', dummy_middleware ); -router.post('/authorize', dummy_middleware ); - -router.get('/authorize/:session_token', dummy_middleware ); - -router.get('/check-account/:email', async( req, res ) => { - try{ - const email = req.params.email; - const data = await Application.check_account( email ); - return res.send( data ); - }catch(error){ - console.error( error ); - } -} ); - -router.post('/signup', async(req,res) => { +router.post('/authorize', async( req, res ) => { try{ const email = req.body.email; const password = req.body.password; - const data = await Application.getVerifyChecksum( email , password ); + const data = await Application.authorize_credentials( email , password ); if( data.error ){ const error = data.error; return res.status( error.code ).send( { error : error.msg } ); @@ -35,6 +21,53 @@ router.post('/signup', async(req,res) => { return res.send( data ); }catch(error){ console.error( error ); + return res.status( 500 ).send( { error } ); + } +} ); + +router.get('/authorize/:session_token', async( req, res ) => { + try{ + const session_token = req.params.session_token; + const data = await Application.authorize_token( session_token ); + if( data.error ){ + const error = data.error; + return res.status( error.code ).send( { error : error.msg } ); + } + return res.send( data ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send( { error } ); + } +} ); + +router.get('/check-account/:email', async( req, res ) => { + try{ + const email = req.params.email; + const data = await Application.check_account( email ); + if( data.error ){ + const error = data.error; + return res.status( error.code ).send( { error : error.msg } ); + } + return res.send( data ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send( { error } ); + } +} ); + +router.post('/signup', async(req,res) => { + try{ + const email = req.body.email; + const password = req.body.password; + const data = await Application.genOTPChecksum( email, password, "signup" ); + if( data.error ){ + const error = data.error; + return res.status( error.code ).send( { error : error.msg } ); + } + return res.send( data ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send( { error } ); } } ); router.patch('/signup', async(req,res) => { @@ -51,10 +84,41 @@ router.patch('/signup', async(req,res) => { return res.send( data ); }catch(error){ console.error( error ); + return res.status( 500 ).send( { error } ); } } ); -router.post('/recover', dummy_middleware ); -router.patch('/recover', dummy_middleware ); +router.post('/recover', async(req,res) => { + try{ + const email = req.body.email; + const password = req.body.password; + const data = await Application.genOTPChecksum( email, password, "recover" ); + if( data.error ){ + const error = data.error; + return res.status( error.code ).send( { error : error.msg } ); + } + return res.send( data ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send( { error } ); + } +} ); +router.patch('/recover', async(req,res) => { + try{ + const email = req.body.email; + const password = req.body.password; + const otp = req.body.otp; + const checksum = req.body.checksum; + const data = await Application.recover( email , password, otp, checksum); + if( data.error ){ + const error = data.error; + return res.status( error.code ).send( { error : error.msg } ); + } + return res.send( data ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send( { error } ); + } +} ); module.exports = router; diff --git a/server/src/Apps/Account/Domain/index.js b/server/src/Apps/Account/Domain/index.js index 42ce032..c82fdad 100644 --- a/server/src/Apps/Account/Domain/index.js +++ b/server/src/Apps/Account/Domain/index.js @@ -12,7 +12,7 @@ const { toSha256, pwdSecret, genErrorResponse } = require('../Ports/Interfaces'); -class ApplicationLogic { +class Account { constructor(){ } @@ -50,13 +50,25 @@ class ApplicationLogic { return retVal; } - async getVerifyChecksum( email , password ){ - const otp = this.genOTP( email ); - const it_exists = await Repository.getByEmail( email ); + async genOTPChecksum( email, password, reason="signup" ){ + let event_id = reason; + + if( reason === "signup" ){ + event_id = "getchecksum:signup"; - if( it_exists ){ - return genErrorResponse( "Email already exists" ); + const it_exists = await Repository.getByEmail( email ); + if( it_exists ){ + return genErrorResponse( "User already registered!" ); + } + + }else if( reason === "recover" ){ + event_id = "getchecksum:recover"; + }else{ + return genErrorResponse( "OPT Event type not defined" , 500 ); } + + const otp = this.genOTP( email ); + const content = { OTP : otp, user_name : email, email }; const checksum_entry = { @@ -66,7 +78,9 @@ class ApplicationLogic { }; const checksum = toSha256( JSON.stringify(checksum_entry)).slice(0, 32); - publishEvent( "getchecksum" , content ); + + publishEvent( event_id , content ); + return { checksum }; } @@ -93,6 +107,25 @@ class ApplicationLogic { return await this.authorize_credentials( email, password ); } + async recover( email, password, otp, checksum ){ + const user = await Repository.getByEmail( email ); + + if( !user ){ + return genErrorResponse( "Email is not registered!" ); + } + + const checksum_entry = {email, password, otp}; + const recomputed_checksum = toSha256( JSON.stringify(checksum_entry)).slice(0, 32); + + if( recomputed_checksum != checksum ){ + return genErrorResponse( "Wrong OTP" ); + } + + await Repository.updatePassword( user.id, this.genSafePassword( password ) ); + + return await this.authorize_credentials( email, password ); + } + async authorize_credentials( email, password ){ const user = await Repository.findByEmailPassword( email, this.genSafePassword( password ) ); @@ -128,6 +161,42 @@ class ApplicationLogic { }; } + async authorize_token( token ){ + const user = await Repository.findBySessionToken( token ); + + if( !user ){ + return genErrorResponse( "Invalid Session Token", 401 ); + } + + 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 ); + + await Repository.addSessionToken( user.id, session_token, session_token_exp ); + + const payload = { + iat: iat, + exp: iat + jwtTimeout * 3600, + aud: jwtOptions.audience, + iss: jwtOptions.audience, + sub: user.id, + }; + const jwt = jsonwebtoken.sign( payload , jwtSecret ); + return { + accessToken : jwt, + payload : payload, + session_token, + session_token_exp, + user : user + }; + } + }; -module.exports = new ApplicationLogic(); +module.exports = new Account(); diff --git a/server/src/Apps/Account/Repository/Objection/index.js b/server/src/Apps/Account/Repository/Objection/index.js index 4704304..18a3b0b 100644 --- a/server/src/Apps/Account/Repository/Objection/index.js +++ b/server/src/Apps/Account/Repository/Objection/index.js @@ -1,14 +1,14 @@ 'use strict'; const { getModel } = require('../../../../Shared/Models/Objection'); -const Model = getModel('users'); +const Users = getModel('users'); const Sessions = getModel('users_sessions'); const Companies = getModel('companies'); class SpecificModelRepository{ constructor(){} - async populateCompany( user ){ + async populate( user ){ if( user.company_id ){ const company = await Companies.query().findById( user.company_id ); user.company = company; @@ -19,28 +19,29 @@ class SpecificModelRepository{ } async getByEmail( email ){ - const user = await Model.query() + const user = await Users.query() .select( "*" ) .where("email","=",email).first(); + return user; } async createOne( email, safe_password ){ - const user = await Model.query().insert({ + const user = await Users.query().insert({ email, password : safe_password, "name":"No name", "last_name":"No lastname", "createdAt" : new Date().toISOString() }); - return this.populateCompany( user ); + return await this.populate( user ); } async findByEmailPassword( email, safe_password ){ - const user = await Model.query().select('*') + const user = await Users.query().select('*') .where("email","=",email) .where("password","=",safe_password) .first(); - return this.populateCompany( user ); + return await this.populate( user ); } async updateSessionToken( old_token, token, expiration ){ @@ -55,6 +56,12 @@ class SpecificModelRepository{ return null; } + async updatePassword( userId , safe_password ){ + return await Users.query() + .findById( userId ) + .patch( { password : safe_password } ); + } + async addSessionToken( userId , token, expiration ){ const entry = await Sessions.query().select('*').where("user_id",'=',userId).first(); const data = { @@ -71,6 +78,12 @@ class SpecificModelRepository{ } } + async findBySessionToken( token ){ + const session = await Sessions.query().select("*").where("token","=",token).first(); + const user = await Users.query().findById( session.user_id ); + return await this.populate( user ); + } + } module.exports = new SpecificModelRepository(); \ No newline at end of file diff --git a/server/src/SysS/EventManager/EmailEvents/index.js b/server/src/SysS/EventManager/EmailEvents/index.js index 12b1765..98af238 100644 --- a/server/src/SysS/EventManager/EmailEvents/index.js +++ b/server/src/SysS/EventManager/EmailEvents/index.js @@ -29,8 +29,8 @@ async function onContactFromWebPage( data ){ * Dictionary of event ids and handlers */ module.exports = { - "App:Account:getchecksum" : onChecksumGeneration, + "App:Account:getchecksum:signup" : onChecksumGeneration, "App:Account:signupconfirmed":onAccountConfirmed, - "App:Account:getchecksum:pwdreset":onPasswordReset, - "App:ContactEmail:getchecksum":onContactFromWebPage, + "App:Account:getchecksum:recover":onPasswordReset, + "App:ContactEmail:contact":onContactFromWebPage, }; diff --git a/src/lib/Misc.js b/src/lib/Misc.js index 25c0363..7f806eb 100644 --- a/src/lib/Misc.js +++ b/src/lib/Misc.js @@ -13,6 +13,8 @@ const s3Client = new S3Client({ }); const secret = apiConfig.authentication.jwtSecret; +const tokenSecret = apiConfig.authentication.tokenSecret; + /** * Convert string to sha256 string in hex * @param {*} text @@ -28,12 +30,14 @@ function toSha256( text ){ * @param {*} text * @returns */ -function genKey( len = 6 , key="" ){ +function genKey( len = 5 , key="" ){ if( len >= 64 ){ throw "invalid key len"; } - const complete_string = toSha256( key + new Date() + secret ); - return complete_string.substr(0 , len ); + const shacode = toSha256( key + new Date() + tokenSecret ); + const otp_hex = shacode.slice(0 , len ); + const otp_dec = Number.parseInt( otp_hex , 16 ); + return ""+otp_dec; } function getPagination( query ){