]> PHS Git Server - phs-api.git/commitdiff
Fixing auth with cookies.
authorcharles <charles.wray@phasecustomsoft.com>
Thu, 18 Sep 2025 15:03:27 +0000 (15:03 +0000)
committercharles <charles.wray@phasecustomsoft.com>
Thu, 18 Sep 2025 15:03:27 +0000 (15:03 +0000)
.idea/sqldialects.xml [new file with mode: 0644]
src/controllers/auth.controller.js
src/controllers/authentication.controller.js
src/controllers/user.controller.js
src/middleware/passport.js
src/middleware/routeHelpers.js
src/models/authentication.model.js
src/models/user.model.js
src/routes/user.routes.js

diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
new file mode 100644 (file)
index 0000000..6df4889
--- /dev/null
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="SqlDialectMappings">
+    <file url="PROJECT" dialect="PostgreSQL" />
+  </component>
+</project>
\ No newline at end of file
index bac979d9979f3fea4a6b759aeb8596529ba4562a..8549cb13d5e2710c19de99ac7b4f75e916be78ed 100644 (file)
@@ -4,10 +4,25 @@
 
 const db = require( '../models' );
 const createError = require( 'http-errors' );
+const jwt = require('jsonwebtoken');
+const config = require('../config/default.json');
+const phs_env = process.env.PHS_ENV;
 
 const updateUserAndReturn = async ( validUser, res ) => {
   const token = await validUser.createToken();
-  res.status( 200 ).send( { success:true, user:validUser.to_safe_json(), token } );
+  await setCookie('phase_request_token', { user_id: validUser.id, request_token: token }, res);
+  res.status( 200 ).send( { success:true, user:validUser, token } );
+};
+
+const setCookie = (cookieKey, payload, res) => {
+  const token = jwt.sign(payload, config.keys.secret2fa, { algorithm: 'HS256', expiresIn: '1y' });
+  res.cookie(cookieKey, token, {
+    maxAge: 31536000000,
+    httpOnly: true,
+    secure: (phs_env !== 'DEV'),
+    sameSite: 'lax',
+    domain: '.phasecustomsoft.com'
+  }); // expire in 1 year: 31536000000
 };
 
 module.exports = {
index 5838010d6224639fb5a041e9c57f403d093806f0..7402fdb2a756e2cdfd5d4a855dbcbf9742faac3b 100644 (file)
@@ -21,7 +21,7 @@ module.exports = {
     try {
       const auth_data = req.body;
       const auth = await db.authentication.create( auth_data );
-      res.json( auth.to_safe_json() );
+      res.json( auth );
     } catch (error) {
       logger.error( `Create authentication error: ${ error.message }` );
       next( createError( error.status || 400, error.message ) );
@@ -40,7 +40,7 @@ module.exports = {
       const { user_id } = req.params;
       const auth = await db.authentication.find_by_user_id( parseInt( user_id ) );
       if (!auth) return next( createError( 404, 'Authentication record not found' ) );
-      res.json( auth.to_safe_json() );
+      res.json( auth );
     } catch (error) {
       logger.error( `Find authentication by user ID error: ${ error.message }` );
       next( createError( error.status || 500, error.message ) );
@@ -59,7 +59,7 @@ module.exports = {
       const { token } = req.params;
       const auth = await db.authentication.find_by_reset_token( token );
       if (!auth) return next( createError( 404, 'Authentication record not found' ) );
-      res.json( auth.to_safe_json() );
+      res.json( auth );
     } catch (error) {
       logger.error( `Find authentication by reset token error: ${ error.message }` );
       next( createError( error.status || 500, error.message ) );
@@ -78,7 +78,7 @@ module.exports = {
       const { id } = req.params;
       const auth = await db.authentication.find_one( { id:parseInt( id ) } );
       if (!auth) return next( createError( 404, 'Authentication record not found' ) );
-      res.json( auth.to_safe_json() );
+      res.json( auth );
     } catch (error) {
       logger.error( `Find authentication error: ${ error.message }` );
       next( createError( error.status || 500, error.message ) );
@@ -96,7 +96,7 @@ module.exports = {
     try {
       const { limit = '100', offset = '0', ...where } = req.query;
       const auths = await db.authentication.find_many( where, [], null, parseInt( limit ), parseInt( offset ) );
-      res.json( auths.map( auth => auth.to_safe_json() ) );
+      res.json( auths );
     } catch (error) {
       logger.error( `Find many authentication records error: ${ error.message }` );
       next( createError( error.status || 500, error.message ) );
@@ -114,7 +114,7 @@ module.exports = {
     try {
       const { id } = req.params;
       const auth = await db.authentication.lock_account( id );
-      res.json( auth.to_safe_json() );
+      res.json( auth );
     } catch (error) {
       logger.error( `Lock account error: ${ error.message }` );
       next( createError( error.status || 400, error.message ) );
@@ -132,7 +132,7 @@ module.exports = {
     try {
       const { id } = req.params;
       const auth = await db.authentication.unlock_account( id );
-      res.json( auth.to_safe_json() );
+      res.json( auth );
     } catch (error) {
       logger.error( `Unlock account error: ${ error.message }` );
       next( createError( error.status || 400, error.message ) );
@@ -151,7 +151,7 @@ module.exports = {
       const { id } = req.params;
       const { deleted_by_id } = req.body;
       const auth = await db.authentication.soft_delete( id, deleted_by_id );
-      res.json( auth.to_safe_json() );
+      res.json( auth );
     } catch (error) {
       logger.error( `Soft delete authentication error: ${ error.message }` );
       next( createError( error.status || 400, error.message ) );
index feb284c4b2bd5b39fc9511c5ca68fe716c95a247..cfa94189e60469bbf76f8adc34ca7bfb0beb04ff 100644 (file)
@@ -2,8 +2,8 @@
  * @file User controller for handling user-related API requests
  */
 
-const db = require('../models');
-const createError = require('http-errors');
+const db = require( '../models' );
+const createError = require( 'http-errors' );
 
 /**
  * User controller
@@ -17,14 +17,14 @@ module.exports = {
    * @param {Function} next - Express next middleware function
    * @returns {Promise<void>}
    */
-  async create(req, res, next) {
+  async create( req, res, next ) {
     try {
       const user_data = req.body;
-      const user = await db.user.create(user_data);
-      res.status(200).send(user.to_safe_json());
+      const user = await db.user.create( user_data );
+      res.status( 200 ).send( user );
     } catch (error) {
-      logger.error(`Create user error: ${error.message}`);
-      next(createError(error.status || 400, error.message));
+      logger.error( `Create user error: ${ error.message }` );
+      next( createError( error.status || 400, error.message ) );
     }
   },
 
@@ -35,15 +35,15 @@ module.exports = {
    * @param {Function} next - Express next middleware function
    * @returns {Promise<void>}
    */
-  async find_one(req, res, next) {
+  async find_one( req, res, next ) {
     try {
       const { id } = req.params;
-      const user = await db.user.find_one({ id: parseInt(id) });
-      if (!user) return next(createError(404, 'User not found'));
-      res.status(200).send(user.to_safe_json());
+      const user = await db.user.find_one( { id:parseInt( id ) } );
+      if (!user) return next( createError( 404, 'User not found' ) );
+      res.status( 200 ).send( user );
     } catch (error) {
-      logger.error(`Show user error: ${error.message}`);
-      next(createError(error.status || 500, error.message));
+      logger.error( `Show user error: ${ error.message }` );
+      next( createError( error.status || 500, error.message ) );
     }
   },
 
@@ -54,15 +54,15 @@ module.exports = {
    * @param {Function} next - Express next middleware function
    * @returns {Promise<void>}
    */
-  async find_many(req, res, next) {
+  async find_many( req, res, next ) {
     try {
-      const { limit = 100, offset = 0, ...where } = req.query;
-      const users = await db.user.find_many(where, [], null, parseInt(limit), parseInt(offset));
-      if (!users.length) return next(createError(404, 'No users found'));
-      res.status(200).send(users.map(u => u.to_safe_json()));
+      const { limit = '100', offset = '0', ...where } = req.query;
+      const users = await db.user.find_many( where, [], null, parseInt( limit ), parseInt( offset ) );
+      if (!users.length) return next( createError( 404, 'No users found' ) );
+      res.status( 200 ).send( users.map( u => new (require( '../models/user.model' )( u ).toJSON()) ) );
     } catch (error) {
-      logger.error(`Index users error: ${error.message}`);
-      next(createError(error.status || 500, error.message));
+      logger.error( `Index users error: ${ error.message }` );
+      next( createError( error.status || 500, error.message ) );
     }
   },
 
@@ -73,15 +73,15 @@ module.exports = {
    * @param {Function} next - Express next middleware function
    * @returns {Promise<void>}
    */
-  async find_by_email(req, res, next) {
+  async find_by_email( req, res, next ) {
     try {
       const { email } = req.params;
-      const user = await db.user.find_by_email(email);
-      if (!user) return next(createError(404, 'User not found'));
-      res.status(200).send(user.to_safe_json());
+      const user = await db.user.find_by_email( email );
+      if (!user) return next( createError( 404, 'User not found' ) );
+      res.status( 200 ).send( user );
     } catch (error) {
-      logger.error(`Find user by email error: ${error.message}`);
-      next(createError(error.status || 500, error.message));
+      logger.error( `Find user by email error: ${ error.message }` );
+      next( createError( error.status || 500, error.message ) );
     }
   },
 
@@ -92,15 +92,15 @@ module.exports = {
    * @param {Function} next - Express next middleware function
    * @returns {Promise<void>}
    */
-  async find_by_nickname(req, res, next) {
+  async find_by_nickname( req, res, next ) {
     try {
       const { nickname } = req.params;
-      const user = await db.user.find_by_nickname(nickname);
-      if (!user) return next(createError(404, 'User not found'));
-      res.status(200).send(user.to_safe_json());
+      const user = await db.user.find_by_nickname( nickname );
+      if (!user) return next( createError( 404, 'User not found' ) );
+      res.status( 200 ).send( user );
     } catch (error) {
-      logger.error(`Find user by nickname error: ${error.message}`);
-      next(createError(error.status || 500, error.message));
+      logger.error( `Find user by nickname error: ${ error.message }` );
+      next( createError( error.status || 500, error.message ) );
     }
   },
 
@@ -111,17 +111,17 @@ module.exports = {
    * @param {Function} next - Express next middleware function
    * @returns {Promise<void>}
    */
-  async update(req, res, next) {
+  async update( req, res, next ) {
     try {
       const { id } = req.params;
       const user_data = req.body;
-      const user = await db.user.instance().find_one({ id: parseInt(id) });
-      if (!user) return next(createError(404, 'User not found'));
-      const updated_user = await user.update(user_data);
-      res.status(200).send(updated_user.to_safe_json());
+      const user = await db.user.instance().find_one( { id:parseInt( id ) } );
+      if (!user) return next( createError( 404, 'User not found' ) );
+      const updated_user = await user.update( user_data );
+      res.status( 200 ).send( updated_user );
     } catch (error) {
-      logger.error(`Update user error: ${error.message}`);
-      next(createError(error.status || 400, error.message));
+      logger.error( `Update user error: ${ error.message }` );
+      next( createError( error.status || 400, error.message ) );
     }
   },
 
@@ -132,17 +132,17 @@ module.exports = {
    * @param {Function} next - Express next middleware function
    * @returns {Promise<void>}
    */
-  async deactivate(req, res, next) {
+  async deactivate( req, res, next ) {
     try {
       const { id } = req.params;
       const { deactivated_by_id } = req.body;
-      const user = await db.user.instance().find_one({ id: parseInt(id) });
-      if (!user) return next(createError(404, 'User not found'));
-      const deactivated_user = await user.deactivate(deactivated_by_id);
-      res.status(200).send(deactivated_user.to_safe_json());
+      const user = await db.user.instance().find_one( { id:parseInt( id ) } );
+      if (!user) return next( createError( 404, 'User not found' ) );
+      const deactivated_user = await user.deactivate( deactivated_by_id );
+      res.status( 200 ).send( deactivated_user );
     } catch (error) {
-      logger.error(`Deactivate user error: ${error.message}`);
-      next(createError(error.status || 400, error.message));
+      logger.error( `Deactivate user error: ${ error.message }` );
+      next( createError( error.status || 400, error.message ) );
     }
   },
 
@@ -153,16 +153,16 @@ module.exports = {
    * @param {Function} next - Express next middleware function
    * @returns {Promise<void>}
    */
-  async reactivate(req, res, next) {
+  async reactivate( req, res, next ) {
     try {
       const { id } = req.params;
-      const user = await db.user.instance().find_one({ id: parseInt(id) });
-      if (!user) return next(createError(404, 'User not found'));
+      const user = await db.user.instance().find_one( { id:parseInt( id ) } );
+      if (!user) return next( createError( 404, 'User not found' ) );
       const reactivated_user = await user.reactivate();
-      res.status(200).send(reactivated_user.to_safe_json());
+      res.status( 200 ).send( reactivated_user );
     } catch (error) {
-      logger.error(`Reactivate user error: ${error.message}`);
-      next(createError(error.status || 400, error.message));
+      logger.error( `Reactivate user error: ${ error.message }` );
+      next( createError( error.status || 400, error.message ) );
     }
   },
 
@@ -173,17 +173,29 @@ module.exports = {
    * @param {Function} next - Express next middleware function
    * @returns {Promise<void>}
    */
-  async soft_delete(req, res, next) {
+  async soft_delete( req, res, next ) {
     try {
       const { id } = req.params;
       const { deleted_by_id } = req.body;
-      const user = await db.user.instance().find_one({ id: parseInt(id) });
-      if (!user) return next(createError(404, 'User not found'));
-      const deleted_user = await user.soft_delete(deleted_by_id);
-      res.status(200).send(deleted_user.to_safe_json());
+      const user = await db.user.instance().find_one( { id:parseInt( id ) } );
+      if (!user) return next( createError( 404, 'User not found' ) );
+      const deleted_user = await user.soft_delete( deleted_by_id );
+      res.status( 200 ).send( deleted_user );
     } catch (error) {
-      logger.error(`Soft delete user error: ${error.message}`);
-      next(createError(error.status || 400, error.message));
+      logger.error( `Soft delete user error: ${ error.message }` );
+      next( createError( error.status || 400, error.message ) );
+    }
+  },
+
+  async get_current_user( req, res, next ) {
+    try {
+      const { id } = req.user;
+      const user = await db.user.instance().find_one( { id: id } );
+      if (!user) return next( createError( 404, 'User not found' ) );
+      res.status( 200 ).send( user );
+    } catch (error) {
+      logger.error( `Get current user error: ${ error.message }` );
+      next( createError( error.status || 400, error.message ) );
     }
   }
 };
\ No newline at end of file
index 2d37cff626e8b6298555b96f59c0d1a8f033cdb2..3070197cc0aa33057e475d35dec6954363c2892e 100755 (executable)
@@ -1,7 +1,33 @@
-const JWTStrategy = require( 'passport-jwt' ).Strategy,
-  ExtractJwt = require( 'passport-jwt' ).ExtractJwt;
+const JWTStrategy = require( 'passport-jwt' ).Strategy;
 const config = require( '../config/default.json' );
 const db = require( '../models' );
+const jwt = require("jsonwebtoken");
+
+const cookieExtractor = function(req) {
+    let token = null;
+    if (req?.cookies?.['phase_request_token']) {
+        const decoded = jwt.decode(req?.cookies?.['phase_request_token'].trim(), config.keys.secret);
+        token = decoded.request_token;
+    }
+    return token;
+};
+
+const authenticate = function ( JWTPayload, callback ) {
+    // return callback(null, {id: 1});
+    logger.debug( `JWT-JWTPayload: ${ JSON.stringify( JWTPayload ) }` );
+    return db.user.find_one( { email:JWTPayload.email, is_active:true, is_delete:false } )
+        .then( async user => {
+            logger.debug( 'passport: ' + user );
+            if (!user?.id) {
+                return callback( null, false );
+            } else if (user?.id) {
+                user.roles = user.get_user_roles().then( roles => roles.map( role => role.name ) );
+                return callback( null, user );
+            } else {
+                return callback( null, false );
+            }
+        } ).catch( error => callback( error, false ) );
+}
 
 // Hooks the JWT Strategy.
 function hookJWTStrategy( passport ) {
@@ -10,25 +36,10 @@ function hookJWTStrategy( passport ) {
 
   // options.secretOrKey = process.env['HASH_KEY'];
   options.secretOrKey = config.keys.secret;
-  options.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
+  options.jwtFromRequest = cookieExtractor;
   options.ignoreExpiration = true;
 
-  passport.use( 'jwt', new JWTStrategy( options, function ( JWTPayload, callback ) {
-    // return callback(null, {id: 1});
-    logger.debug( `JWT-JWTPayload: ${ JSON.stringify( JWTPayload ) }` );
-    return db.user.find_one( { email:JWTPayload.email, is_active:true, is_delete:false } )
-      .then( async user => {
-        logger.debug( 'passport: ' + user );
-        if (!user?.id) {
-          return callback( null, false );
-        } else if (user?.id) {
-          user.roles = user.get_user_roles().then( roles => roles.map( role => role.name ) );
-          return callback( null, user );
-        } else {
-          return callback( null, false );
-        }
-      } ).catch( error => callback( error, false ) );
-  } ) );
+  passport.use( 'jwt', new JWTStrategy( options, authenticate ) );
 }
 
 module.exports = hookJWTStrategy;
\ No newline at end of file
index 0963c46fd7a88e61eb0356f0035fe1f3f5cdf324..94a82939c66b03dd1c9f580601966542cb779e9f 100755 (executable)
@@ -1,7 +1,8 @@
 const validate_auth = function ( passport, context='jwt') {
   async function checkKey( req, res, next ) {
     const { authorization, apikey:apiKey } = req.headers;
-    if (authorization) return passport.authenticate( context, { session:false } )(req, res, next);
+    const { phase_request_token } = req.cookies;
+    if (authorization||phase_request_token) return passport.authenticate( context, { session:false } )(req, res, next);
     else if (!authorization && !apiKey) return passport.authenticate( context, { session: false } )(req, res, next);
     next();
   }
index c9cc1913a899dd18a8692ca374b44e4f6c67f3a5..66eb22f9026953ff3ac1e8491ecaff85dc3a5185 100644 (file)
@@ -77,6 +77,10 @@ class Authentication extends Model {
     `;
     this.default_order_by = 'ORDER BY auth.user_id ASC';
     this.instance = _props => new Authentication( _props );
+    this.toJSON = () => {
+      const { password, password_salt, password_verification_token, password_reset_token, ...safeData } = super.toJSON();
+      return safeData;
+    };
   };
 
   /**
@@ -177,11 +181,6 @@ class Authentication extends Model {
       locked_date:null
     } );
   };
-
-  to_safe_json() {
-    const { password, password_salt, password_verification_token, password_reset_token, ...safeData } = this.toJSON();
-    return safeData;
-  };
 }
 
 module.exports = Authentication;
\ No newline at end of file
index 080e3bf8e7a33eaa5de05d227628a31125c7b7e8..116af0ffa3de031f7a85f58bf36741944657a247 100644 (file)
@@ -54,12 +54,18 @@ class User extends Model {
     `;
     this.base_list_query = `
         SELECT u.id, u.email, u.first_name, u.middle_name, u.last_name, u.initials, u.nickname,
-               u.created_by_id, u.created_at, u.is_active, u.deactivated_by_id, u.deactivated_at
+               u.created_by_id, u.created_at, u.is_active, u.deactivated_by_id, u.deactivated_at, a.password, a.password_salt,
+               a.is_locked, a.locked_date
         FROM phase.users u
+                 inner join phase.authentication a on u.id = a.user_id
     `;
     this.default_order_by = 'ORDER BY u.email ASC';
     this.instance = _props => new User(_props);
-  }
+    this.toJSON = () => {
+      const { password, password_salt, ...safe_data } = super.toJSON();
+      return safe_data;
+    };
+  };
 
   static async create(user_data) {
     const {
@@ -219,11 +225,6 @@ class User extends Model {
   is_active() {
     return this.is_active === true;
   }
-
-  to_safe_json() {
-    const { password, ...safe_data } = this.toJSON();
-    return safe_data;
-  }
 }
 
 module.exports = User;
\ No newline at end of file
index 8bc2953c3941d4670e5d4e0a300d63e8d1cfc48b..a5582aac73df3ed5aaed2a7c831679eed8a31bf5 100644 (file)
@@ -5,6 +5,7 @@ const users_controller = require('../controllers/user.controller');
 
 module.exports = (passport) => {
   router.post('/create', validate_auth(passport), users_controller.create);
+  router.get( '/current', validate_auth(passport), users_controller.get_current_user);
   router.get('/email/:email', validate_auth(passport), users_controller.find_by_email);
   router.get('/:id', validate_auth(passport), users_controller.find_one);
   router.get('/', validate_auth(passport), users_controller.find_many);