import { DateTime } from 'luxon'; import { withAuthFinder } from '@adonisjs/auth/mixins/lucid'; import { column, manyToMany, hasMany, SnakeCaseNamingStrategy } from '@adonisjs/lucid/orm'; import hash from '@adonisjs/core/services/hash'; import Role from './role.js'; import db from '@adonisjs/lucid/services/db'; import config from '@adonisjs/core/services/config'; import Dataset from './dataset.js'; import BaseModel from './base_model.js'; // import Encryption from '@ioc:Adonis/Core/Encryption'; import encryption from '@adonisjs/core/services/encryption'; import { TotpState } from '#contracts/enums'; import type { ManyToMany } from '@adonisjs/lucid/types/relations'; import type { HasMany } from '@adonisjs/lucid/types/relations'; import { compose } from '@adonisjs/core/helpers'; import BackupCode from './backup_code.js'; const AuthFinder = withAuthFinder(() => hash.use('laravel'), { uids: ['email'], passwordColumnName: 'password', }); // import TotpSecret from './TotpSecret'; // export default interface IUser { // id: number; // login: string; // email: string; // // password: string; // // createdAt: DateTime; // // updatedAt: DateTime; // // async (user): Promise; // } // const permissionTable = config.get('rolePermission.permission_table', 'permissions'); // const rolePermissionTable = config.get('rolePermission.role_permission_table', 'role_has_permissions'); // const roleTable = config.get('rolePermission.role_table', 'roles'); // const userRoleTable = config.get('rolePermission.user_role_table', 'link_accounts_roles'); export default class User extends compose(BaseModel, AuthFinder) { // export default class User extends BaseModel { public static namingStrategy = new SnakeCaseNamingStrategy(); public static table = 'accounts'; @column({ isPrimary: true }) public id: number; @column() public login: string; @column() public firstName: string; @column() public lastName: string; @column() public email: string; @column({ serializeAs: null }) public password: string; @column.dateTime({ autoCreate: true }) public createdAt: DateTime; @column.dateTime({ autoCreate: true, autoUpdate: true }) public updatedAt: DateTime; // serializeAs: null removes the model properties from the serialized output. @column({ serializeAs: null, consume: (value: string) => (value ? JSON.parse(encryption.decrypt(value) ?? '{}') : null), prepare: (value: string) => encryption.encrypt(JSON.stringify(value)), }) public twoFactorSecret?: string | null; // serializeAs: null removes the model properties from the serialized output. @column({ serializeAs: null, consume: (value: string) => (value ? JSON.parse(encryption.decrypt(value) ?? '[]') : []), prepare: (value: string[]) => encryption.encrypt(JSON.stringify(value)), }) public twoFactorRecoveryCodes?: string[] | null; @column({}) public state: number; // @hasOne(() => TotpSecret, { // foreignKey: 'user_id', // }) // public totp_secret: HasOne; // @beforeSave() // public static async hashPassword(user: User) { // if (user.$dirty.password) { // user.password = await hash.use('laravel').make(user.password); // } // } public get isTwoFactorEnabled(): boolean { return Boolean(this?.twoFactorSecret && this.state == TotpState.STATE_ENABLED); // return Boolean(this.totp_secret?.twoFactorSecret); } @manyToMany(() => Role, { pivotForeignKey: 'account_id', pivotRelatedForeignKey: 'role_id', pivotTable: 'link_accounts_roles', }) public roles: ManyToMany; @hasMany(() => Dataset, { foreignKey: 'account_id', }) public datasets: HasMany; @hasMany(() => BackupCode, { foreignKey: 'user_id', }) public backupcodes: HasMany; public async getBackupCodes(this: User): Promise { const test = await this.related('backupcodes').query(); // return test.map((role) => role.code); return test; } // https://github.com/adonisjs/core/discussions/1872#discussioncomment-132289 public async getRoles(this: User): Promise { const test = await this.related('roles').query(); return test.map((role) => role.name); } public async can(permissionNames: Array): Promise { // const permissions = await this.getPermissions() // return Acl.check(expression, operand => _.includes(permissions, operand)) const hasPermission = await this.checkHasPermissions(this, permissionNames); return hasPermission; } private async checkHasPermissions(user: User, permissionNames: Array): Promise { const permissionTable = config.get('rolePermission.permission_table', 'permissions'); const rolePermissionTable = config.get('rolePermission.role_permission_table', 'role_has_permissions'); const roleTable = config.get('rolePermission.role_table', 'roles'); const userRoleTable = config.get('rolePermission.user_role_table', 'link_accounts_roles'); let permissionPlaceHolder = '('; let placeholders = new Array(permissionNames.length).fill('?'); permissionPlaceHolder += placeholders.join(','); permissionPlaceHolder += ')'; let { rows: { 0: { permissioncount }, }, } = await db.rawQuery( 'SELECT count("p"."name") as permissionCount FROM ' + roleTable + ' r INNER JOIN ' + userRoleTable + ' ur ON ur.role_id=r.id AND "ur"."account_id"=? ' + ' INNER JOIN ' + rolePermissionTable + ' rp ON rp.role_id=r.id ' + ' INNER JOIN ' + permissionTable + ' p ON rp.permission_id=p.id AND "p"."name" in ' + permissionPlaceHolder + ' LIMIT 1', [user.id, ...permissionNames], ); return permissioncount > 0; } } // export default User;