UNPKG

@tomei/sso

Version:
449 lines (415 loc) 14.3 kB
import { ClassError, ObjectBase } from '@tomei/general'; import { APIKeyStatusEnum } from '../../enum/api-key.enum'; import { APIKeyRepository } from './api-key.repository'; import { IAPIKeyAttr } from '../../interfaces/api-key-attr.interface'; import { LoginUser } from '../login-user/login-user'; import { ApplicationConfig } from '@tomei/config'; import { randomBytes } from 'crypto'; import { ActionEnum, Activity } from '@tomei/activity-history'; import { Op } from 'sequelize'; import { System } from '../system/system'; export class APIKey extends ObjectBase { ObjectId: string; ObjectName: string; ObjectType: 'ApiKey'; TableName: 'sso_APIKey'; ApiKey: string; Name: string; SystemCode: string; Description: string; Status: APIKeyStatusEnum; ExpirationDate: Date; RevokedReason: string; _RevokedById: number; _RevokedAt: Date; _CreatedAt: Date; _CreatedById: number; get APIKeyId(): number { return parseInt(this.ObjectId); } set APIKeyId(value: number) { this.ObjectId = value.toString(); } get RevokedById(): number { return this._RevokedById; } get RevokedAt(): Date { return this._RevokedAt; } get CreatedAt(): Date { return this._CreatedAt; } get CreatedById(): number { return this._CreatedById; } protected static _Repo = new APIKeyRepository(); protected constructor(apiKeyAttr?: IAPIKeyAttr) { super(); if (apiKeyAttr) { this.APIKeyId = apiKeyAttr.APIKeyId; this.ApiKey = apiKeyAttr.ApiKey; this.Name = apiKeyAttr.Name; this.SystemCode = apiKeyAttr.SystemCode; this.Description = apiKeyAttr.Description; this.Status = apiKeyAttr.Status; this.ExpirationDate = apiKeyAttr.ExpirationDate; this._RevokedById = apiKeyAttr.RevokedById; this._RevokedAt = apiKeyAttr.RevokedAt; this._CreatedAt = apiKeyAttr.CreatedAt; this._CreatedById = apiKeyAttr.CreatedById; } } public static async init(ApiKeyId?: number, dbTransaction?: any) { try { if (ApiKeyId) { const apiKeyAttr = await this._Repo.findByPk( ApiKeyId.toString(), dbTransaction, ); if (apiKeyAttr) { return new APIKey(apiKeyAttr); } else { throw new ClassError( 'APIKey', 'APIKeyErrMsgO1', 'APIKey not found', 'init', ); } } return new APIKey(); } catch (error) { throw error; } } async generate(loginUser: LoginUser, dbTransaction?: any) { //This method generates a new API key and saves it into the database. try { // Part 2: Privilege Checking // Call loginUser.checkPrivileges() method by passing: // SystemCode: <get_from_app_config> // PrivilegeCode: "API_KEY_CREATE" const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'API_KEY_CREATE', ); if (!isPrivileged) { throw new ClassError( 'APIKey', 'APIKeyErrMsgO2', 'User does not have privilege to generate API key.', 'generate', ); } // Part 3: System Existence Check // Call System.init(dbTransaction, this.SystemCode) to verify the System exists. // If the group does not exist, throw a NotFoundError. await System.init(dbTransaction, this.SystemCode); // Part 3: Generate API Key // Populate the following attributes: // CreatedById: Set to loginUser.UserId. this._CreatedById = loginUser.UserId; // CreatedAt: Set to current date time. this._CreatedAt = new Date(); // Generate a unique API key string: // Use a secure random function or crypto module to generate a 64-character hexadecimal string. // Assign the generated string to this.ApiKey. this.ApiKey = randomBytes(64).toString('hex'); // Part 4: Save API Key to Database // Call APIKey._Repo.create() by passing: // loginUser // dbTransaction // This APIKey instance. const EntityValueAfter: any = { ApiKey: this.ApiKey, Name: this.Name, SystemCode: this.SystemCode, Status: this.Status, Description: this.Description, ExpirationDate: this.ExpirationDate, CreatedAt: this.CreatedAt, CreatedById: this.CreatedById, RevokedAt: this.RevokedAt, RevokedById: this.RevokedById, RevokedReason: this.RevokedReason, }; const data = await APIKey._Repo.create(EntityValueAfter, { transaction: dbTransaction, }); this.APIKeyId = data.APIKeyId; EntityValueAfter.ApiKeyId = data.APIKeyId; // Part 5: Record Generate API Key Activity // Initialise EntityValueBefore variable and set to empty object. const EntityValueBefore = {}; // Initialise EntityValueAfter variable and set to this APIKey instance. // Instantiate new activity from Activity class, call createId() method, then set: const activity = new Activity(); activity.ActivityId = activity.createId(); // Action: ActionEnum.Create // Description: "Generate API key." // EntityType: "APIKey" // EntityId: <this.APIKeyId> // EntityValueBefore: EntityValueBefore // EntityValueAfter: EntityValueAfter activity.Action = ActionEnum.CREATE; activity.Description = 'Generate API key.'; activity.EntityType = 'APIKey'; activity.EntityId = this.ObjectId; activity.EntityValueBefore = JSON.stringify(EntityValueBefore); activity.EntityValueAfter = JSON.stringify(EntityValueAfter); // Call new activity create method by passing: // dbTransaction // userId: loginUser.ObjectId await activity.create(loginUser.ObjectId, dbTransaction); // Part 6: Returns // Translate the created APIKey entity into an object and return the following fields: // ApiKey // Name // Status // ExpirationDate // CreatedAt // CreatedById // InvokedAt // InvokedById return { ApiKey: this.ApiKey, Name: this.Name, Status: this.Status, Description: this.Description, SystemCode: this.SystemCode, ExpirationDate: this.ExpirationDate, CreatedAt: this.CreatedAt, CreatedById: this.CreatedById, RevokedAt: this.RevokedAt, RevokedById: this.RevokedById, RevokedReason: this.RevokedReason, }; } catch (error) { throw error; } } public static async findAll( pagination: { page: number; limit: number }, loginUser: LoginUser, dbTransaction: any, whereOptions: { SystemCode?: string; Status?: APIKeyStatusEnum; ExpirationDate?: { FromDate: Date; ToDate: Date; }; CreatedById?: number; }, sortOptions?: { SortBy: string; SortOrder: 'ASC' | 'DESC'; }, ) { try { // Part 1: Privilege Checking // Call the loginUser.checkPrivileges() method by passing: // SystemCode: Retrieved from the application configuration. // PrivilegeCode: 'API_KEY_VIEW'. // Ensure the user has the necessary privileges to view API keys. const systemCode = ApplicationConfig.getComponentConfigValue('system-code'); const isPrivileged = await loginUser.checkPrivileges( systemCode, 'API_KEY_VIEW', ); if (!isPrivileged) { throw new ClassError( 'APIKey', 'APIKeyErrMsgO2', 'User does not have privilege to generate API key.', 'generate', ); } // Part 2: Prepare Filters // Ensure that whereOptions includes the necessary filters such as: // Status: Filter for specific API key statuses (e.g., "Active", "Revoked"). // ExpirationDate: Filter for expiration date ranges (e.g., { [Op.between]: [fromDate, toDate] }). // CreatedById: Filter by the user who created the API key. const where = {}; if (whereOptions) { if (whereOptions.SystemCode) { where['SystemCode'] = whereOptions.SystemCode; } if (whereOptions.Status) { where['Status'] = whereOptions.Status; } if (whereOptions.ExpirationDate) { where['ExpirationDate'] = { [Op.between]: [ whereOptions.ExpirationDate.FromDate, whereOptions.ExpirationDate.ToDate, ], }; } if (whereOptions.CreatedById) { where['CreatedById'] = whereOptions.CreatedById; } } const order = []; if (sortOptions) { // Sort the results based on the provided SortBy and SortOrder. // Ensure that the SortBy field is valid and that the SortOrder is either "ASC" or "DESC". if (sortOptions.SortBy) { order.push([sortOptions.SortBy, sortOptions.SortOrder]); } } else { order.push(['CreatedAt', 'DESC']); } let offset = 0; if (pagination) { offset = (pagination.page - 1) * pagination.limit; } // Ensure that the pagination object includes the page and limit values. // Use Sequelize's findAll() method with the provided whereOptions, sortOptions, and pagination to retrieve matching API keys. const data = await APIKey._Repo.findAllWithPagination({ where, order, offset, limit: pagination.limit, transaction: dbTransaction, distinct: true, }); // Return the list of API keys including: // ApiKeyId // ApiKey // Name // Status // ExpirationDate // CreatedAt // CreatedById // RevokedAt // RevokedById // RevokedReason // Include pagination information such as: // total: Total number of records, // page: Current page, // limit: Number of records per page. return { total: data.count, ApiKeys: data.rows.map((row) => { return { ApiKeyId: row.APIKeyId, ApiKey: row.ApiKey, Name: row.Name, SystemCode: row.SystemCode, Description: row.Description, Status: row.Status, ExpirationDate: row.ExpirationDate, CreatedAt: row.CreatedAt, CreatedById: row.CreatedById, RevokedAt: row.RevokedAt, RevokedById: row.RevokedById, RevokedReason: row.RevokedReason, }; }), page: pagination.page, limit: pagination.limit, }; } catch (error) { throw error; } } public async revoke( apiKey: string, loginUser: LoginUser, dbTransaction: any, reason?: string, ) { try { // Part 1: Prepare Required Params // Ensure apiKey, loginUser, and dbTransaction are provided. // Retrieve the existing API key record from the database using the provided apiKey. const apiKeyRecord = await APIKey._Repo.findOne({ where: { ApiKey: apiKey }, transaction: dbTransaction, }); if (!apiKeyRecord) { throw new ClassError( 'APIKey', 'APIKeyErrMsgO3', 'API key not found.', 'revoke', ); } const EntityValueBefore = { ...apiKeyRecord.get({ plain: true }), }; // Part 2: Revoke API Key // Mark the API key as revoked: // Set the Status to "Revoked". apiKeyRecord.Status = APIKeyStatusEnum.REVOKED; // Set the RevokedAt timestamp to the current date and time.\ apiKeyRecord.RevokedAt = new Date(); // Set the RevokedById to loginUser.UserId. apiKeyRecord.RevokedById = loginUser.UserId; // Optionally, set the revocation reason: // If the reason parameter is provided, store it in the RevokedReason attribute. if (reason) { apiKeyRecord.RevokedReason = reason; } // Part 3: Save API Key to Database // Call APIKey._Repo.update() by passing: // The updated APIKey instance // dbTransaction. await APIKey._Repo.update( { ...apiKeyRecord.get({ plain: true }), }, { where: { APIKeyId: apiKeyRecord.APIKeyId }, transaction: dbTransaction, }, ); // Part 4: Record Update API Key Activity // Initialise EntityValueBefore variable and set to empty object. // Initialise EntityValueAfter variable and set to this APIKey instance. const EntityValueAfter = { ...apiKeyRecord.get({ plain: true }), }; // Instantiate new activity from Activity class, call createId() method, then set: // Action: ActionEnum.Create // Description: "Revoke API key." // EntityType: "APIKey" // EntityId: <this.APIKeyId> // EntityValueBefore: EntityValueBefore // EntityValueAfter: EntityValueAfter const activity = new Activity(); activity.ActivityId = activity.createId(); activity.Action = ActionEnum.UPDATE; activity.Description = 'Revoke API key.'; activity.EntityType = 'APIKey'; activity.EntityId = apiKeyRecord.APIKeyId.toString(); activity.EntityValueBefore = JSON.stringify(EntityValueBefore); activity.EntityValueAfter = JSON.stringify(EntityValueAfter); // Call new activity create method by passing: // dbTransaction // userId: loginUser.ObjectId await activity.create(loginUser.ObjectId, dbTransaction); // Part 5: Returns // Translate the updated APIKey entity into an object and return the following fields: // ApiKey // Status: "Revoked" // RevokedAt // RevokedById // RevokedByName // RevokedReason return { ApiKey: apiKeyRecord.ApiKey, Status: apiKeyRecord.Status, RevokedAt: apiKeyRecord.RevokedAt, RevokedById: apiKeyRecord.RevokedById, RevokedReason: apiKeyRecord.RevokedReason, }; } catch (error) { throw error; } } }