@tomei/sso
Version:
Tomei SSO Package
449 lines (415 loc) • 14.3 kB
text/typescript
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;
}
}
}