diff --git a/v1/README.md b/v1/README.md index 58ca037..8c43612 100644 --- a/v1/README.md +++ b/v1/README.md @@ -482,3 +482,17 @@ __This endpoint is only valid for carriers.__ - `PATCH /:id` : Updates data from specific element. - `DELETE /:id` : Delete element from company. Returns the element data. - `GET /:id` : Get data from specific element. + +## Observers endpoints + +### observers/public/account/ + +The full endpoint is `observers/public/account/`, and serves for the account credentials management, the same way it works for the common users at the endpoint `/public/account`, by the process of creating an OTP when requesting a new user or recovering the account password. + + - `POST /client/authorize` or `POST /warehouse/authorize`: Authorize the access to the user identified by email:password. + - `GET /client/authorize/:session_token` or `GET /warehouse/authorize/:session_token`: Authorize the access to the user identified by the session token. + - `POST /client/signup` or `POST /warehouse/signup`: Requests an OTP to create a new account. + - `PATCH /client/signup` or `PATCH /warehouse/signup`: Confirms the account OTP. + - `POST /client/recover` or `POST /warehouse/recover`: Requests an OTP recover the account password. + - `PATCH /client/recover` or `PATCH /warehouse/recover`: Confirms the new password OTP. + diff --git a/v1/src/apps/index.js b/v1/src/apps/index.js index 874f7ad..6804277 100644 --- a/v1/src/apps/index.js +++ b/v1/src/apps/index.js @@ -3,7 +3,9 @@ const app = express(); const private = require('./private'); const public = require('./public'); +const observers = require('./observers'); +app.use( '/api/v1/observers/', observers ); app.use( '/api/v1/', public ); app.use( '/api/v1/', private ); diff --git a/v1/src/apps/observers/index.js b/v1/src/apps/observers/index.js new file mode 100644 index 0000000..b0af384 --- /dev/null +++ b/v1/src/apps/observers/index.js @@ -0,0 +1,10 @@ +const express = require('express'); +const app = express(); + +const public = require('./public'); +const private = require('./private'); + +app.use( public ); +app.use( private ); + +module.exports = app; diff --git a/v1/src/apps/observers/private/index.js b/v1/src/apps/observers/private/index.js new file mode 100644 index 0000000..04a4d8d --- /dev/null +++ b/v1/src/apps/observers/private/index.js @@ -0,0 +1,22 @@ +'use strict'; +const router = require('express').Router(); + +const jwtValidator = require( '../../../lib/jwtValidator.js' ); +const context = require( './lib/context' ); + +// const loads = require('./loads/routes.js'); + +router.use( jwtValidator.middleware ); +router.use( context.middleware ); + +router.use( (req,res) => { + console.log( req.JWT ); + console.log( req.context ); + return res.status(500).send( { + error: "Not implemented yet" + } ); +} ) + +// router.use('/loads', loads); + +module.exports = router; diff --git a/v1/src/apps/observers/private/lib/context/index.js b/v1/src/apps/observers/private/lib/context/index.js new file mode 100644 index 0000000..1a80206 --- /dev/null +++ b/v1/src/apps/observers/private/lib/context/index.js @@ -0,0 +1,38 @@ +'use strict'; +const { getModel } = require( '../../../../../lib/Models' ); +const ObserverClient = getModel('observers.client'); +const ObserverWarehouse = getModel('observers.warehouse'); + +function get_model_from_type( user_type ){ + let observer_model; + + if( user_type == "client" ){ + observer_model = ObserverClient; + }else if( user_type == "warehouse" ){ + observer_model = ObserverWarehouse; + }else{ + observer_model = null; + } + return observer_model; +} + +async function middleware( req, res, next ){ + if( ! req.JWT?.isValid ){ + return res.status(401).send({error:"Unauthorized",code:401}); + } + const user_model = get_model_from_type( req.JWT.payload.user_type ); + const userID = req.JWT.payload.sub; + + req.context = { + user : await user_model.findById( userID , { password : 0 , session_token : 0 , session_token_exp : 0 } ) + } + + req.context.userId = req.context.user.id; + req.context.email = req.context.user.email; + req.context.user_type = req.JWT.payload.user_type; + next(); +} + +module.exports = { + middleware +}; diff --git a/v1/src/apps/observers/private/loads/routes.js b/v1/src/apps/observers/private/loads/routes.js new file mode 100644 index 0000000..038099a --- /dev/null +++ b/v1/src/apps/observers/private/loads/routes.js @@ -0,0 +1,13 @@ +'use strict'; +const router = require('express').Router(); +const services= require('./services.js'); + +router.get('/find', services.findList); +router.get('/calendar', services.findCalendarList); +router.post('/new', services.postLoad); + +router.patch('/:id', services.patchLoad); +router.delete('/:id', services.deleteLoad); +router.get('/:id', services.getById); + +module.exports = router; diff --git a/v1/src/apps/observers/private/loads/services.js b/v1/src/apps/observers/private/loads/services.js new file mode 100644 index 0000000..809bce3 --- /dev/null +++ b/v1/src/apps/observers/private/loads/services.js @@ -0,0 +1,335 @@ +"use strict"; +const { getModel } = require( '../../../lib/Models' ); +const { getPagination, genKey } = require( '../../../lib/Misc.js' ); +const { GenericHandler } = require( '../../../lib/Handlers/Generic.handler.js' ); +const Model = getModel('loads'); +const CompanyModel = getModel('companies'); +const ProposalsModel = getModel('proposals'); +const branchesModel = getModel('branches'); + +const carrier_projection = [ + 'company_name', + 'company_code', + 'createdAt', + 'rfc' +]; + +const vehicle_projection = [ + 'vehicle_code', + 'truck_type', + 'driver', + 'categories', + 'circulation_serial_number', + 'trailer_plate_1', + 'trailer_plate_2', + 'city', +]; +const user_projection = ['first_name','last_name','middle_name']; +const populate_list = [ + 'product', + 'company', + 'categories', + 'shipper_warehouse', + {path:'carrier',select: carrier_projection }, + {path:'vehicle',select: vehicle_projection }, + {path:'driver',select: user_projection }, + {path:'bidder',select: user_projection }, +]; +const generic = new GenericHandler( Model, null, populate_list ); + +function getAndFilterList( query ){ + const filter_list = []; + const { + company, + carrier, + vehicle, + driver, + status, + posted_by, + posted_by_name, + load_status, + published_date, + loaded_date, + transit_date, + categories, + product, + shipment_code, + shipper_warehouse, + est_loading_date, + alert_list, + } = query; + + if( company ){ filter_list.push( { company } ); } + if( carrier ){ filter_list.push( { carrier } ); } + if( vehicle ){ filter_list.push( { vehicle } ); } + if( driver ){ filter_list.push( { driver } ); } + if( status ){ filter_list.push( { status } ); } + if( posted_by ) { filter_list.push({ posted_by }); } + if( posted_by_name ) { filter_list.push({ posted_by_name }); } + if( load_status ) { filter_list.push({ load_status }); } + if( published_date ) { filter_list.push({ published_date }); } + if( loaded_date ) { filter_list.push({ loaded_date }); } + if( transit_date ) { filter_list.push({ transit_date }); } + if( categories ) { filter_list.push({ categories }); } + if( product ) { filter_list.push({ product }); } + if( shipment_code ) { filter_list.push({ shipment_code }); } + if( shipper_warehouse ) { filter_list.push({ shipper_warehouse }); } + if( alert_list ) { filter_list.push({ alert_list }); } + if( est_loading_date ) { + if( (est_loading_date.gte == undefined) || (est_loading_date.gte == null) ){ + throw "est_loading_date[gte] is required"; + } + if( (est_loading_date.lte == undefined) || (est_loading_date.lte == null) ){ + throw "est_loading_date[lte] is required"; + } + filter_list.push({ + "est_loading_date" : { + $gte : new Date( est_loading_date["gte"] ), + $lte : new Date( est_loading_date["lte"] ) + } + }); + } + + if( filter_list.length == 0 ){ + return null; + } + return filter_list; +} + +async function findLoads( query ){ + const { $sort, company_name } = query; + const { page, elements } = getPagination( query ); + const andFilterList = getAndFilterList( query ) || []; + + let filter; + + if( company_name ){ + /* Populate list of company ids with match on the company_name */ + const company_list = await CompanyModel.find( { company_name }, [ "id" ] ); + const or_company_list = [] + company_list.forEach( (item) =>{ + or_company_list.push({"company" : item.id}); + }) + andFilterList.push({ + $or : or_company_list + }); + } + + if( andFilterList.length > 0 ){ + filter = { $and : andFilterList }; + }else{ + filter = null; + } + + const { total , limit, skip, data } = await generic.getList( page , elements, filter, null, $sort ); + + const load_list = data; + + for(let i=0; i { + try{ + const query = req.query || {}; + const companyId = req.context.companyId; + const userId = req.context.userId; + const retVal = await findCalendarLoads( userId, companyId, query ); + res.send( retVal ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +} + +const findList = async(req, res) => { + try{ + const query = req.query || {}; + const retVal = await findLoads( query ); + res.send( retVal ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +}; + +const getById = async(req, res) => { + try{ + const elementId = req.params.id; + res.send( await findElementById( elementId ) ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +}; + +const patchLoad = async(req, res) => { + try{ + const elementId = req.params.id; + const permissions = req.context.permissions; + const data = req.body; + const load = await findElementById( elementId ); + if( !load ){ + throw "You can't modify this load"; + } + if( !data ){ + throw "load data not sent"; + } + await Model.findByIdAndUpdate( elementId , data ); + return res.send( await Model.findById( elementId ) ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +}; + +const postLoad = async(req, res) => { + try{ + const companyId = req.context.companyId; + const userId = req.context.userId; + const user_name = req.context.user.first_name; + const permissions = req.context.permissions; + const data = req.body; + if( !data ){ + throw "Load data not sent"; + } + if(permissions !== "role_shipper" ){ + throw "You can't create loads"; + } + data.company = companyId; + data.posted_by = userId; + data.name = user_name; + const load = new Model( data ); + await load.save(); + + const id = "" + load._id; + const shipment_code = "ETA-" + genKey( 6, id ); + await Model.findByIdAndUpdate( id , { + shipment_code + }); + load.shipment_code = shipment_code; + return res.send( load ); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +}; + +const deleteLoad = async(req, res) => { + try{ + const companyId = req.context.companyId; + const elementId = req.params.id; + const permissions = req.context.permissions; + const load = await findElementById( elementId , companyId ); + if(!load){ + throw "You can't delete this load"; + } + if(permissions !== "role_shipper" ){ + throw "You can't delete loads"; + } + await Model.findByIdAndDelete( elementId ); + return res.send(load); + }catch(error){ + console.error( error ); + return res.status( 500 ).send({ error }); + } +}; + +module.exports = { findCalendarList, findList, getById, patchLoad, postLoad, deleteLoad }; diff --git a/v1/src/apps/observers/public/account/routes.js b/v1/src/apps/observers/public/account/routes.js new file mode 100644 index 0000000..69acd6f --- /dev/null +++ b/v1/src/apps/observers/public/account/routes.js @@ -0,0 +1,23 @@ +'use strict'; +const router = require('express').Router(); +const services= require('./services.js'); + +router.post('/client/authorize', services.Client_AuthorizeJWT); +router.post('/warehouse/authorize', services.Warehouse_AuthorizeJWT); + +router.get('/client/authorize/:session_token', services.Client_RenewJWT); +router.get('/warehouse/authorize/:session_token', services.Warehouse_RenewJWT); + +router.post('/client/signup', services.Client_TryCreateAccount); +router.post('/warehouse/signup', services.Warehouse_TryCreateAccount); + +router.patch('/client/signup', services.Client_ConfirmAccount); +router.patch('/warehouse/signup', services.Warehouse_ConfirmAccount); + +router.post('/client/recover', services.Client_RecoverPwd); +router.post('/warehouse/recover', services.Warehouse_RecoverPwd); + +router.patch('/client/recover', services.Client_ConfirmRecoverPwd); +router.patch('/warehouse/recover', services.Warehouse_ConfirmRecoverPwd); + +module.exports = router; diff --git a/v1/src/apps/observers/public/account/services.js b/v1/src/apps/observers/public/account/services.js new file mode 100644 index 0000000..1230556 --- /dev/null +++ b/v1/src/apps/observers/public/account/services.js @@ -0,0 +1,534 @@ +"use strict"; +const jsonwebtoken = require('jsonwebtoken'); + +const apiConfig = require('../../../../config/apiConfig.json' ); + +const { genKey, toSha256 } = require( '../../../../lib/Misc' ); +const { emailEvent , EMAIL_EVENTS } = require( '../../../../lib/Handlers/MailClient' ); +const { create_account, already_exists, login, login_with_session_token, reset_password } = require( '../../../../lib/Handlers/Observers' ); +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 }, + 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', '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 +}; + +async function AuthorizeJWT_email_pwd( user_type, email , password ){ + const user = await login( user_type, email, password ); + if( !user ){ + return null; + } + + 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, + user_type: user_type, + sub: user.id, + }; + const jwt = jsonwebtoken.sign( payload , jwtSecret ); + return { + accessToken : jwt, + payload : payload, + session_token, + session_token_exp, + user : user + }; +} + +function valid_user_type( user_type ){ + return ( (user_type == "client") || (user_type == "warehouse") ); +} + +async function AuthorizeJWT( user_type, body ){ + if( !valid_user_type( user_type ) ){ + throw "Invalid user_type"; + } + + if( validator.validate( body, login_account_schema ).valid ){ + const { email, password } = body; + const retVal = await AuthorizeJWT_email_pwd( user_type, email , password ); + if( !retVal ){ + return { + status : 401, + payload:{ + error: "Invalid credentials" + } + } + }else{ + return { + status : null, + payload : retVal + } + } + }else{ + return { + status : 400, + payload:{ + error: "Invalid request" + } + } + } + +} + +async function RenewJWT( user_type, params ){ + if( !valid_user_type( user_type ) ){ + throw "Invalid user_type"; + } + + const login_session_token = params.session_token; + const user = await login_with_session_token( user_type, login_session_token ); + if( !user ){ + return { + status : 401, + payload:{ + 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(); + + delete user.session_token; + delete user.session_token_exp; + + const payload = { + iat: iat, + exp: iat + jwtTimeout * 3600, + aud: jwtOptions.audience, + iss: jwtOptions.audience, + user_type: user_type, + sub: user.id, + }; + const jwt = jsonwebtoken.sign( payload , jwtSecret ); + + return { + status: null, + payload: { + accessToken : jwt, + payload : payload, + session_token, + session_token_exp, + user : user + } + } +} + +async function TryCreateAccount( user_type, body ){ + if( !valid_user_type( user_type ) ){ + throw "Invalid user_type"; + } + + if( validator.validate( body , create_account_schema ).valid ){ + const otp = genKey(); + const { email : receiver , password } = body; + const email = receiver; + + const it_exists = await already_exists( user_type, email ); + if( it_exists ){ + return { + status: 400, + payload: { + 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); + + /** + * TODO: Send an confirmation email with client/warehouse templates! + */ + await emailEvent( EMAIL_EVENTS.ACCOUNT_VERIFY , receiver , content ); + console.log( + content + ); + return { + status: null, + payload: { + checksum + } + } + }else{ + return { + status: 400, + payload: { + error : "Invalid request" + } + } + } +}; + +async function ConfirmAccount( user_type, body ){ + if( !valid_user_type( user_type ) ){ + throw "Invalid user_type"; + } + + if( validator.validate( body , confirm_account_schema ).valid ){ + const { email, password, otp, checksum } = body; + + const it_exists = await already_exists( user_type, email ); + if( it_exists ){ + return { + status : 400, + payload : { 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 { + status : 400, + payload : { error : "Wrong OTP" } + } + } + + await create_account( user_type, email, password ); + + const content = { user_name : email }; + const receiver = email; + /** + * TODO: Send an confirmation email with client/warehouse templates! + */ + await emailEvent( EMAIL_EVENTS.ACCOUNT_CONFIRMED , receiver , content ); + const retVal = await AuthorizeJWT_email_pwd( user_type, email , password ); + + return { + status : null, + payload: retVal + } + }else{ + return { + status: 400, + payload: { error : "Invalid request" } + } + } +} + +async function RecoverPwd( user_type, body ){ + if( !valid_user_type( user_type ) ){ + throw "Invalid user_type"; + } + + if( validator.validate( body , password_recover_schema ).valid ){ + const otp = genKey(); + const { email : receiver , password } = body; + const email = receiver; + + const it_exists = await already_exists( user_type, email ); + if( !it_exists ){ + return { + status: 400, + payload: { 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); + + /** + * TODO: Send an confirmation email with client/warehouse templates! + */ + await emailEvent( EMAIL_EVENTS.ACCOUNT_PWD_RESET , receiver , content ); + console.log( + content + ); + + return { + status: null, + payload: { checksum } + } + }else{ + return { + status: 400, + payload: { error: "Invalid request" } + } + } +} + +async function ConfirmRecoverPwd( user_type, body ){ + if( !valid_user_type( user_type ) ){ + throw "Invalid user_type"; + } + + if( validator.validate( body , confirm_password_recover_schema ).valid ){ + const { email, password, otp, checksum } = body; + + const it_exists = await already_exists( user_type, email ); + if( !it_exists ){ + return { + status: 400, + payload: { 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 { + status: 400, + payload: { error : "Wrong OTP" } + } + } + + await reset_password( user_type, email, password ); + + return { + status: null, + payload: { msg : "Password is reset!" } + } + }else{ + return { + status: 400, + payload: { error : "Invalid request" } + } + } +} + +const Client_AuthorizeJWT = async(req, res) => { + try{ + const result = await AuthorizeJWT( 'client', req.body ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Login: Internal error" }); + } +}; +const Warehouse_AuthorizeJWT = async(req, res) => { + try{ + const result = await AuthorizeJWT( 'warehouse', req.body ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Login: Internal error" }); + } +}; + +const Client_RenewJWT = async(req, res) => { + try{ + const result = await RenewJWT( 'client', req.params ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Renew: Internal error" }); + } +}; +const Warehouse_RenewJWT = async(req, res) => { + try{ + const result = await RenewJWT( 'warehouse', req.params ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Renew: Internal error" }); + } +}; + +const Client_TryCreateAccount = async(req, res) => { + try{ + const result = await TryCreateAccount( 'client', req.body ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Account creation: Internal error" }); + } +}; +const Warehouse_TryCreateAccount = async(req, res) => { + try{ + const result = await TryCreateAccount( 'warehouse', req.body ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Account creation: Internal error" }); + } +}; + +const Client_ConfirmAccount = async(req, res) => { + try{ + const result = await ConfirmAccount( 'client', req.body ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Account creation: Internal error" }); + } +}; +const Warehouse_ConfirmAccount = async(req, res) => { + try{ + const result = await ConfirmAccount( 'warehouse', req.body ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Account creation: Internal error" }); + } +}; + +const Client_RecoverPwd = async(req, res) => { + try{ + const result = await RecoverPwd( 'client', req.body ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Password Recover: Internal error" }); + } +}; +const Warehouse_RecoverPwd = async(req, res) => { + try{ + const result = await RecoverPwd( 'warehouse', req.body ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Password Recover: Internal error" }); + } +}; + +const Client_ConfirmRecoverPwd = async(req, res) => { + try{ + const result = await ConfirmRecoverPwd( 'client', req.body ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Password Recover: Internal error" }); + } +}; +const Warehouse_ConfirmRecoverPwd = async(req, res) => { + try{ + const result = await ConfirmRecoverPwd( 'warehouse', req.body ); + if( !result.status ){ + return res.send( result.payload ); + }else{ + return res.status(result.status).send( result.payload ); + } + }catch( err ){ + console.error( err ); + return res.status(500).send({ error : "Password Recover: Internal error" }); + } +}; + +module.exports = { + Client_AuthorizeJWT, + Warehouse_AuthorizeJWT, + Client_RenewJWT, + Warehouse_RenewJWT, + Client_TryCreateAccount, + Warehouse_TryCreateAccount, + Client_ConfirmAccount, + Warehouse_ConfirmAccount, + Client_RecoverPwd, + Warehouse_RecoverPwd, + Client_ConfirmRecoverPwd, + Warehouse_ConfirmRecoverPwd +}; diff --git a/v1/src/apps/observers/public/index.js b/v1/src/apps/observers/public/index.js new file mode 100644 index 0000000..098bc1e --- /dev/null +++ b/v1/src/apps/observers/public/index.js @@ -0,0 +1,8 @@ +'use strict'; +const router = require('express').Router(); + +const account = require('./account/routes.js'); + +router.use('/account', account); + +module.exports = router; diff --git a/v1/src/apps/public/account/services.js b/v1/src/apps/public/account/services.js index f2f8f91..7b9c2b5 100644 --- a/v1/src/apps/public/account/services.js +++ b/v1/src/apps/public/account/services.js @@ -116,6 +116,9 @@ const RenewJWT = async(req, res) => { user.session_token_exp = session_token_exp; await user.save(); + delete user.session_token; + delete user.session_token_exp; + const payload = { iat: iat, exp: iat + jwtTimeout * 3600, diff --git a/v1/src/index.js b/v1/src/index.js index 397cd31..b90a418 100644 --- a/v1/src/index.js +++ b/v1/src/index.js @@ -11,7 +11,7 @@ const morgan = require('morgan'); const helmet = require('helmet'); const bodyParser = require('body-parser'); const fileUpload = require('express-fileupload'); -const middlewares = require( `${ROOT_PATH}/${LIB_PATH}/Middlewares.js` ); +const middlewares = require( './lib//Middlewares.js' ); const mongoose = require('mongoose'); mongoose.connect( diff --git a/v1/src/lib/Handlers/Observers/index.js b/v1/src/lib/Handlers/Observers/index.js new file mode 100644 index 0000000..1309bee --- /dev/null +++ b/v1/src/lib/Handlers/Observers/index.js @@ -0,0 +1,95 @@ +'user strict'; +const { getModel } = require( '../../Models' ); +const apiConfig = require( '../../../config/apiConfig.json' ); +const { toSha256 } = require( '../../Misc' ); +const ObserverClient = getModel('observers.client'); +const ObserverWarehouse = getModel('observers.warehouse'); + +const pwd_secret = apiConfig.authentication.pwdSecret; + +function get_model_from_type( user_type ){ + let observer_model; + + if( user_type == "client" ){ + observer_model = ObserverClient; + }else if( user_type == "warehouse" ){ + observer_model = ObserverWarehouse; + }else{ + observer_model = null; + } + return observer_model; +} + +async function create_account( user_type, email, password ){ + let safe_password = toSha256( password + pwd_secret ); + let user_model = get_model_from_type( user_type ); + if( user_model == null ){ return null; } + + const user = new user_model({ + email, + password : safe_password, + }); + + await user.save(); + return user; +} + +async function reset_password( user_type, email, password ){ + let safe_password = toSha256( password + pwd_secret ); + let user_model = get_model_from_type( user_type ); + if( user_model == null ){ return null; } + + const user = await user_model.findOne({ email }); + + if( user ){ + user.password = safe_password; + await user.save(); + return user; + }else{ + return null; + } +} + +async function already_exists( user_type, email ){ + let user_model = get_model_from_type( user_type ); + if( user_model == null ){ return null; } + + const user = await user_model.findOne( { email } ); + if( !user ){ + return false; + }else{ + return true; + } +} + +async function login( user_type, email , password ){ + let user_model = get_model_from_type( user_type ); + if( user_model == null ){ return null; } + + let safe_password = toSha256( password + pwd_secret ); + const user = await user_model.findOne({ + email , password : safe_password + },{ password : 0 , session_token : 0 , session_token_exp : 0 }); + + if( !user ){ + return null; + } + return user; +} + +async function login_with_session_token( user_type, session_token ){ + let user_model = get_model_from_type( user_type ); + if( user_model == null ){ return null; } + + const user = await user_model.findOne({ + session_token, + session_token_exp : { $gte: new Date() } + },{ password : 0 , session_token : 0 , session_token_exp : 0 }); + + if( !user ){ + return null; + } + return user; +} + +module.exports = { create_account, already_exists, login, login_with_session_token, reset_password }; diff --git a/v1/src/lib/Models/Observers/index.js b/v1/src/lib/Models/Observers/index.js new file mode 100644 index 0000000..30e95e3 --- /dev/null +++ b/v1/src/lib/Models/Observers/index.js @@ -0,0 +1,18 @@ +const mongoose = require('mongoose'); +const { Schema } = mongoose; + +const schema = new Schema({ + email: { type: String, unique: true, lowercase: true }, + password: { type: String , maxLength : 256 }, + session_token : { type : String, maxLength : 256 }, + session_token_exp : { type: Date }, + createdAt: { type : Date, required : true, default : () => { return Date.now(); } } +}); + +const warehouse = mongoose.model( "observer_warehouse", schema ) +const client = mongoose.model( "observer_client", schema ) + +module.exports = { + warehouse, + client +}; \ No newline at end of file diff --git a/v1/src/lib/Models/index.js b/v1/src/lib/Models/index.js index 9b9df07..5feef98 100644 --- a/v1/src/lib/Models/index.js +++ b/v1/src/lib/Models/index.js @@ -22,6 +22,8 @@ const trackings = require('./trackings.model.js'); const users = require('./users.model.js'); const vehicles = require('./vehicles.model.js'); +const observers = require('./Observers') + function getModel( name ){ switch( name ){ case 'branches': @@ -66,6 +68,10 @@ function getModel( name ){ return users; case 'vehicles': return vehicles; + case 'observers.client': + return observers.client; + case 'observers.warehouse': + return observers.warehouse; default: return null; }