From c04560d02ffadde27f563ffbcb13eaf945c8681a Mon Sep 17 00:00:00 2001 From: charleswrayjr Date: Thu, 11 Sep 2025 21:34:34 -0500 Subject: [PATCH] Cleaning up the authentication.model.js file. --- src/models/authentication.model.js | 198 +++++++++++++++++++++++++++++ 1 file changed, 198 insertions(+) create mode 100644 src/models/authentication.model.js diff --git a/src/models/authentication.model.js b/src/models/authentication.model.js new file mode 100644 index 0000000..85c5dbc --- /dev/null +++ b/src/models/authentication.model.js @@ -0,0 +1,198 @@ +/** + * @file Authentication model for phase.authentication table + */ + +const { Model, NotFoundError, ValidationError, FailedToCreateError } = require( './model' ); + +/** + * @typedef {Object} Authentication + * @property {number} id - Authentication ID (primary key) + * @property {number} user_id - User ID (foreign key) + * @property {string} password - Hashed password + * @property {string} password_salt - Password salt + * @property {string|null} password_verification_token - Verification token + * @property {Date|null} password_verification_token_expiry - Token expiry + * @property {Date|null} last_password_failure_date - Last failed login attempt + * @property {number} password_failures_since_last_success - Failure count + * @property {boolean} is_deleted - Soft delete flag + * @property {number|null} deleted_by_id - ID of user who deleted this record + * @property {Date|null} deleted_at - Deletion timestamp + * @property {string|null} password_reset_token - Reset token + * @property {Date|null} password_reset_expire_date - Reset token expiry + * @property {boolean|null} is_locked - Account lock status + * @property {Date|null} locked_date - Lock timestamp + */ + +/** + * Authentication model class + * @extends Model + */ +class Authentication extends Model { + /** + * Create an Authentication instance + * @param {Partial} [props] - Authentication properties + */ + constructor( props ) { + super( props ); + this.table = 'phase.authentication'; + this.prepend = 'auth'; + this.default_columns = [ + 'id', 'user_id', 'password', 'password_salt', 'password_verification_token', + 'password_verification_token_expiry', 'last_password_failure_date', + 'password_failures_since_last_success', 'is_deleted', 'deleted_by_id', + 'deleted_at', 'password_reset_token', 'password_reset_expire_date', + 'is_locked', 'locked_date' + ]; + this.update_exclude_columns = ['id', 'user_id', 'created_at', 'is_deleted', 'deleted_at', 'deleted_by_id']; + this.base_query = ` + SELECT auth.id, + auth.user_id, + auth.password, + auth.password_salt, + auth.password_verification_token, + auth.password_verification_token_expiry, + auth.last_password_failure_date, + auth.password_failures_since_last_success, + auth.is_deleted, + auth.deleted_by_id, + auth.deleted_at, + auth.password_reset_token, + auth.password_reset_expire_date, + auth.is_locked, + auth.locked_date + FROM phase.authentication auth + WHERE auth.is_deleted = false + `; + this.base_list_query = ` + SELECT auth.id, + auth.user_id, + auth.password_verification_token, + auth.password_verification_token_expiry, + auth.last_password_failure_date, + auth.password_failures_since_last_success, + auth.password_reset_token, + auth.password_reset_expire_date, + auth.is_locked, + auth.locked_date + FROM phase.authentication auth + WHERE auth.is_deleted = false + `; + this.default_order_by = 'ORDER BY auth.user_id ASC'; + this.instance = _props => new Authentication( _props ); + }; + + /** + * Create a new authentication record + * @param {Omit} auth_data - Authentication data + * @returns {Promise} Created authentication instance + * @throws {ValidationError} If required fields are missing + * @throws {FailedToCreateError} If creation fails + */ + static async create( auth_data ) { + const { + user_id, password, password_salt, password_verification_token = null, + password_verification_token_expiry = null, last_password_failure_date = null, + password_failures_since_last_success = 0, password_reset_token = null, + password_reset_expire_date = null, is_locked = false, locked_date = null + } = auth_data; + if (!user_id || !password || !password_salt) { + throw new ValidationError( 'Missing required fields: user_id, password, password_salt' ); + } + const query_str = ` + INSERT INTO phase.authentication (user_id, password, password_salt, password_verification_token, + password_verification_token_expiry, last_password_failure_date, + password_failures_since_last_success, password_reset_token, + password_reset_expire_date, is_locked, locked_date) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) + RETURNING *; + `; + const values = [ + user_id, password, password_salt, password_verification_token, + password_verification_token_expiry, last_password_failure_date, + password_failures_since_last_success, password_reset_token, + password_reset_expire_date, is_locked, locked_date + ]; + const result = await phsdb.query( query_str, values, { plain:true } ); + if (!result) throw new FailedToCreateError( 'Failed to create authentication record' ); + return new Authentication( result ); + }; + + /** + * Find authentication record by user ID + * @param {number|string} user_id - User ID + * @param {string[]} [excludes=['password', 'password_salt']] - Fields to exclude from result + * @returns {Promise} Authentication instance or null + * @throws {NotFoundError} If record not found + * @throws {TypeError} If user_id is invalid + */ + static async find_by_user_id( user_id, excludes = ['password', 'password_salt'] ) { + return await new Authentication().find_one( { user_id }, excludes ); + }; + + /** + * Find authentication record by password reset token + * @param {string} password_reset_token - Password reset token + * @param {string[]} [excludes=['password', 'password_salt']] - Fields to exclude from result + * @returns {Promise} Authentication instance or null + * @throws {NotFoundError} If record not found + * @throws {TypeError} If password_reset_token is invalid + */ + static async find_by_reset_token( password_reset_token, excludes = ['password', 'password_salt'] ) { + return await new Authentication().find_one( { password_reset_token }, excludes ); + }; + + /** + * Soft delete authentication record + * @param {number|string} deleted_by_id - ID of user performing deletion + * @returns {Promise} Updated authentication instance + * @throws {ValidationError} If deleted_by_id is invalid + * @throws {NotFoundError} If record not found + */ + async soft_delete( deleted_by_id ) { + const deleted_by_id_int = parseInt( deleted_by_id, 10 ); + if (isNaN( deleted_by_id_int )) { + throw new ValidationError( 'deleted_by_id must be a valid integer' ); + } + return await this.update( { + is_deleted:true, + deleted_at:new Date().toISOString(), + deleted_by_id:deleted_by_id_int + } ); + }; + + /** + * Lock user account + * @returns {Promise} Updated authentication instance + * @throws {NotFoundError} If record not found + */ + async lock_account() { + return await this.update( { + is_locked:true, + locked_date:new Date().toISOString() + } ); + }; + + /** + * Unlock user account + * @returns {Promise} Updated authentication instance + * @throws {NotFoundError} If record not found + */ + async unlock_account() { + return await this.update( { + is_locked:false, + locked_date:null + } ); + }; + + // noinspection JSUnusedGlobalSymbols + /** + * Get authentication data without sensitive information + * @returns {Omit} Authentication data + */ + to_safe_json() { + const { password, password_salt, ...safe_data } = this.toJSON(); + return safe_data; + }; +} + +module.exports = Authentication; \ No newline at end of file -- 2.43.0