EN-60: Adding simplified Sign Up/Login/Renew/Recover process.
This commit is contained in:
@@ -31,12 +31,14 @@
|
||||
"express-jwt": "^8.4.1",
|
||||
"form-data": "^4.0.0",
|
||||
"helmet": "^7.0.0",
|
||||
"jsonschema": "^1.4.1",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"knex": "^2.5.1",
|
||||
"mongodb-core": "^3.2.7",
|
||||
"mongoose": "^7.5.4",
|
||||
"morgan": "^1.10.0",
|
||||
"nodemailer": "^6.9.5",
|
||||
"nodemailer-sendgrid": "^1.0.3",
|
||||
"nodemon": "^3.0.1",
|
||||
"objection": "^3.1.2",
|
||||
"uuid": "^9.0.1"
|
||||
|
||||
14
src/apps/public/account/routes.js
Normal file
14
src/apps/public/account/routes.js
Normal file
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
const router = require('express').Router();
|
||||
const services= require('./services.js');
|
||||
|
||||
router.post('/authorize', services.AuthorizeJWT);
|
||||
router.get('/authorize/:session_token', services.RenewJWT);
|
||||
|
||||
router.post('/signup', services.TryCreateAccount);
|
||||
router.patch('/signup', services.ConfirmAccount);
|
||||
|
||||
router.post('/recover', services.RecoverPwd);
|
||||
router.patch('/recover', services.ConfirmRecoverPwd);
|
||||
|
||||
module.exports = router;
|
||||
263
src/apps/public/account/services.js
Normal file
263
src/apps/public/account/services.js
Normal file
@@ -0,0 +1,263 @@
|
||||
"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};
|
||||
@@ -4,6 +4,7 @@ const { ROOT_PATH , LIB_PATH } = process.env;
|
||||
/// Router instance
|
||||
const router = require('express').Router();
|
||||
|
||||
const account = require('./account/routes.js');
|
||||
const cities = require('./cities/routes.js');
|
||||
const countries = require('./countries/routes.js');
|
||||
const metaData = require('./meta-data/routes.js');
|
||||
@@ -17,6 +18,7 @@ const publicLoadAttachments = require('./public-load-attachments/routes.js');
|
||||
const states = require('./states/routes.js');
|
||||
const test = require('./test/routes.js');
|
||||
|
||||
router.use('/account', account);
|
||||
router.use('/cities', cities);
|
||||
router.use('/countries', countries);
|
||||
router.use('/meta-data', metaData);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"authentication": {
|
||||
"pwdSecret":"Nx2g_IWo2Zt_LS$+",
|
||||
"jwtSecret":"9o3BBz0EsrwXliwEJ/SFuywZoN8=",
|
||||
"jwtTimeout":720,
|
||||
"jwtTimeout":24,
|
||||
"jwtRenewalTimeout":720,
|
||||
"tokenSecret":"9Z'jMt|(h_f(&/S+zv.K",
|
||||
"jwtOptions": {
|
||||
"header": {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{
|
||||
"authentication": {
|
||||
"pwdSecret":"Nx2g_IWo2Zt_LS$+",
|
||||
"jwtSecret":"9o3BBz0EsrwXliwEJ/SFuywZoN8=",
|
||||
"jwtTimeout":720,
|
||||
"jwtTimeout":24,
|
||||
"jwtRenewalTimeout":720,
|
||||
"tokenSecret":"9Z'jMt|(h_f(&/S+zv.K",
|
||||
"jwtOptions": {
|
||||
"header": {
|
||||
|
||||
58
src/lib/Handlers/Account/index.js
Normal file
58
src/lib/Handlers/Account/index.js
Normal file
@@ -0,0 +1,58 @@
|
||||
'user strict';
|
||||
const { ROOT_PATH, API_CONFIG, MODELS_PATH, LIB_PATH } = process.env;
|
||||
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
|
||||
const { toSha256 } = require( `${ROOT_PATH}/${LIB_PATH}/Misc.js` );
|
||||
const UserModel = require( `${ROOT_PATH}/${MODELS_PATH}/users.model.js` );
|
||||
|
||||
const pwd_secret = apiConfig.authentication.pwdSecret;
|
||||
|
||||
async function create_account( email, password, company_type ){
|
||||
let permissions;
|
||||
if( company_type === "Shipper"){
|
||||
permissions = "role_shipper";
|
||||
}else{
|
||||
permissions = "role_carrier";
|
||||
}
|
||||
let safe_password = toSha256( password + pwd_secret );
|
||||
const user = new UserModel({
|
||||
email,
|
||||
password : safe_password,
|
||||
permissions
|
||||
});
|
||||
await user.save();
|
||||
}
|
||||
|
||||
async function reset_password( email, password ){
|
||||
let safe_password = toSha256( password + pwd_secret );
|
||||
const user = await UserModel.findOne({ email });
|
||||
user.password = safe_password,
|
||||
await user.save();
|
||||
return user;
|
||||
}
|
||||
|
||||
async function already_exists( email ){
|
||||
const user = await UserModel.findOne( { email } );
|
||||
if( !user ){
|
||||
return false;
|
||||
}else{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
async function login( email , password ){
|
||||
let safe_password = toSha256( password + pwd_secret );
|
||||
const user = await UserModel.findOne({
|
||||
email , password : safe_password
|
||||
},{ password : 0 });
|
||||
return user;
|
||||
}
|
||||
|
||||
async function login_with_session_token( session_token ){
|
||||
const user = await UserModel.findOne({
|
||||
session_token,
|
||||
session_token_exp : { $gte: new Date() }
|
||||
},{ password : 0 });
|
||||
return user;
|
||||
}
|
||||
|
||||
module.exports = { create_account, already_exists, login, login_with_session_token, reset_password };
|
||||
66
src/lib/Handlers/MailClient/SendGrid.handler.js
Normal file
66
src/lib/Handlers/MailClient/SendGrid.handler.js
Normal file
@@ -0,0 +1,66 @@
|
||||
'user strict';
|
||||
const { ROOT_PATH, API_CONFIG } = process.env;
|
||||
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
|
||||
const nodemailer = require("nodemailer");
|
||||
const SendGrid = require("nodemailer-sendgrid");
|
||||
|
||||
const SiteName = "ETA Viaporte";
|
||||
|
||||
const sendgridConfig = apiConfig.sendgrid;
|
||||
|
||||
const transporter = nodemailer.createTransport(
|
||||
SendGrid({
|
||||
host: sendgridConfig.HOST,
|
||||
apiKey: sendgridConfig.API_KEY
|
||||
})
|
||||
);
|
||||
|
||||
async function sendMailTemplate( templateId, receiver, subject, content ){
|
||||
/**TODO: Remove in production */
|
||||
const default_mail = "testing@etaviaporte.com"
|
||||
if( receiver.indexOf( default_mail ) >= 0 ){
|
||||
receiver = default_mail;
|
||||
}
|
||||
return await transporter.sendMail({
|
||||
from: sendgridConfig.FROM,
|
||||
to: receiver,
|
||||
subject: subject,
|
||||
templateId: templateId,
|
||||
dynamic_template_data: content
|
||||
});
|
||||
}
|
||||
|
||||
async function AccountVerifyEmail( receiver , content ){
|
||||
const templateId = "d-e9b7966303694964a64b6e4954e9715d";
|
||||
const subject = "[ETA] Account Verification";
|
||||
const content_to_send = {
|
||||
project_name: SiteName,
|
||||
user_name: content.user_name,
|
||||
user_email: receiver,
|
||||
OTP : content.OTP
|
||||
};
|
||||
return await sendMailTemplate( templateId, receiver, subject, content_to_send );
|
||||
}
|
||||
|
||||
async function AccountConfirmed( receiver, content ){
|
||||
const templateId = "d-4daaab1b85d443ceba38826f606e9931";
|
||||
const subject = "[ETA] Welcome to ETA";
|
||||
const content_to_send = {
|
||||
user_name: content.user_name,
|
||||
};
|
||||
return await sendMailTemplate( templateId, receiver, subject, content_to_send );
|
||||
}
|
||||
|
||||
async function AccountPwdResetEmail( receiver, content ){
|
||||
const templateId = "d-e9b7966303694964a64b6e4954e9715d";
|
||||
const subject = "[ETA] Password Reset";
|
||||
const content_to_send = {
|
||||
project_name: SiteName,
|
||||
user_name: content.user_name,
|
||||
user_email: receiver,
|
||||
OTP : content.OTP
|
||||
};
|
||||
return await sendMailTemplate( templateId, receiver, subject, content_to_send );
|
||||
}
|
||||
|
||||
module.exports = { AccountVerifyEmail, AccountConfirmed, AccountPwdResetEmail };
|
||||
43
src/lib/Handlers/MailClient/index.js
Normal file
43
src/lib/Handlers/MailClient/index.js
Normal file
@@ -0,0 +1,43 @@
|
||||
'user strict';
|
||||
const { ROOT_PATH, HANDLERS_PATH, MODELS_PATH, API_CONFIG } = process.env;
|
||||
const { AccountVerifyEmail, AccountConfirmed, AccountPwdResetEmail } = require('./SendGrid.handler');
|
||||
|
||||
const EMAIL_EVENTS={
|
||||
ACCOUNT_VERIFY:1,
|
||||
ACCOUNT_CONFIRMED:2,
|
||||
ACCOUNT_PWD_RESET:3,
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email according to the event.
|
||||
* @param eventId : string
|
||||
* @param email_content : { string receiver, {*} content }
|
||||
* @returns
|
||||
*/
|
||||
async function emailEvent( eventId, receiver , content ){
|
||||
switch( eventId ){
|
||||
case EMAIL_EVENTS.ACCOUNT_VERIFY:
|
||||
{
|
||||
return await AccountVerifyEmail( receiver, content );
|
||||
}
|
||||
break;
|
||||
case EMAIL_EVENTS.ACCOUNT_CONFIRMED:
|
||||
{
|
||||
return await AccountConfirmed( receiver, content );
|
||||
}
|
||||
break;
|
||||
case EMAIL_EVENTS.ACCOUNT_PWD_RESET:
|
||||
{
|
||||
return await AccountPwdResetEmail( receiver, content );
|
||||
}
|
||||
break;
|
||||
default:
|
||||
{
|
||||
throw new Error(`Email event not defined ${eventId}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return await usersModel.findById( id , { password : 0 } );
|
||||
}
|
||||
|
||||
module.exports = { emailEvent , EMAIL_EVENTS };
|
||||
@@ -1,50 +1,30 @@
|
||||
"use strict";
|
||||
const { ROOT_PATH, API_CONFIG } = process.env;
|
||||
const apiConfig = require( `${ROOT_PATH}/${API_CONFIG}` );
|
||||
const crypto = require('crypto');
|
||||
|
||||
function getPagination( query ){
|
||||
let limit = {
|
||||
page : 0,
|
||||
elements : 10
|
||||
};
|
||||
|
||||
if( query.page ){
|
||||
limit.page = parseInt( query.page ) || 0;
|
||||
if( limit.page < 0 ){
|
||||
limit.page = 0;
|
||||
}
|
||||
}
|
||||
if( query.elements ){
|
||||
limit.elements = parseInt( query.elements ) || 10;
|
||||
/** Safe pagination limit */
|
||||
if( limit.elements > 1000 ){
|
||||
limit.elements = 1000;
|
||||
}
|
||||
else if( limit.elements < 0 ){
|
||||
limit.elements = 10;
|
||||
}
|
||||
}
|
||||
return limit;
|
||||
const secret = apiConfig.authentication.jwtSecret;
|
||||
/**
|
||||
* Convert string to sha256 string in hex
|
||||
* @param {*} text
|
||||
* @returns
|
||||
*/
|
||||
function toSha256( text ){
|
||||
return crypto.createHmac( "sha256" , "" ).update( text ).digest( 'hex' );
|
||||
}
|
||||
|
||||
async function getPage( page, elements, model, filter=null, projection=null){
|
||||
const skip = elements * page;
|
||||
const total = await model.count( filter );
|
||||
const list = await model.find( filter , projection, { skip : skip , limit : elements } );
|
||||
return {
|
||||
total : total,
|
||||
limit : elements,
|
||||
skip : skip,
|
||||
data : list
|
||||
/**
|
||||
* Generate string with fixed length with random content.
|
||||
* Length is limited to 64 characters.
|
||||
* @param {*} text
|
||||
* @returns
|
||||
*/
|
||||
function genKey( len = 6 , key="" ){
|
||||
if( len >= 64 ){
|
||||
throw "invalid key len";
|
||||
}
|
||||
const complete_string = toSha256( key + new Date() + secret );
|
||||
return complete_string.substr(0 , len );
|
||||
}
|
||||
|
||||
async function queryPage(page, elements, model, filter=null, projection=null){
|
||||
const skip = elements * page;
|
||||
const total = await model.count( filter );
|
||||
return {
|
||||
query : model.find( filter , projection, { skip : skip , limit : elements } ),
|
||||
total : total,
|
||||
skip : skip
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = { getPagination , getPage, queryPage };
|
||||
module.exports = { genKey , toSha256 };
|
||||
@@ -18,10 +18,10 @@ const schema = new Schema({
|
||||
last_name: { type: String },
|
||||
middle_name: { type: String },
|
||||
email: { type: String, unique: true, lowercase: true },
|
||||
password: { type: String },
|
||||
password: { type: String , maxLength : 256 },
|
||||
phone: { type: String },
|
||||
phone2: { type: String },
|
||||
permissions: [{ type: String, default: 'role_admin' }],
|
||||
permissions: [{ type: String, default: 'role_admin', enum : ['role_admin', 'role_shipper', 'role_carrier', 'role_driver' ] }],
|
||||
gender: { type: String },
|
||||
address: { type: String },
|
||||
dob: { type: String },
|
||||
@@ -58,6 +58,9 @@ const schema = new Schema({
|
||||
resetExpires: { type: Date }, // or a long integer
|
||||
resetAttempts: { type: Number },
|
||||
|
||||
session_token : { type : String, maxLength : 256 },
|
||||
session_token_exp : { type: Date },
|
||||
|
||||
is_hidden: { type: Boolean, default: false },
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user