[ST-P3-008] feat: Add missing auth entities from erp-core

- tenant.entity.ts: Multi-tenant support
- device.entity.ts: User devices tracking
- verification-code.entity.ts: MFA verification codes
- trusted-device.entity.ts: Trusted device management
- mfa-audit-log.entity.ts: MFA audit logging
- oauth-provider.entity.ts: OAuth provider configuration
- oauth-state.entity.ts: OAuth state management
- oauth-user-link.entity.ts: OAuth user linking
- user-profile.entity.ts: User profile definitions
- profile-tool.entity.ts: Profile tool permissions
- profile-module.entity.ts: Profile module access
- user-profile-assignment.entity.ts: Profile assignments

Total: 12 new entities synchronized from erp-core.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Adrian Flores Cortes 2026-02-03 07:48:30 -06:00
parent 7f32ee65b6
commit 728f8ae7fd
13 changed files with 912 additions and 4 deletions

View File

@ -0,0 +1,64 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
CreateDateColumn,
Index,
} from 'typeorm';
import { Tenant } from './tenant.entity';
import { User } from './user.entity';
@Entity({ schema: 'auth', name: 'devices' })
@Index('idx_devices_tenant_id', ['tenantId'])
@Index('idx_devices_user_id', ['userId'])
@Index('idx_devices_device_id', ['deviceId'])
export class Device {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: false, name: 'tenant_id' })
tenantId: string;
@Column({ type: 'uuid', nullable: false, name: 'user_id' })
userId: string;
@Column({ type: 'varchar', length: 255, nullable: false, name: 'device_id' })
deviceId: string;
@Column({ type: 'varchar', length: 255, nullable: true, name: 'device_name' })
deviceName: string;
@Column({ type: 'varchar', length: 50, nullable: false, name: 'device_type' })
deviceType: string;
@Column({ type: 'varchar', length: 50, nullable: true })
platform: string;
@Column({ type: 'varchar', length: 50, nullable: true, name: 'os_version' })
osVersion: string;
@Column({ type: 'varchar', length: 20, nullable: true, name: 'app_version' })
appVersion: string;
@Column({ type: 'text', nullable: true, name: 'push_token' })
pushToken: string;
@Column({ name: 'is_trusted', default: false })
isTrusted: boolean;
@Column({ type: 'timestamptz', nullable: true, name: 'last_active_at' })
lastActiveAt: Date;
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
createdAt: Date;
@ManyToOne(() => Tenant, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'tenant_id' })
tenant: Tenant;
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_id' })
user: User;
}

View File

@ -1,14 +1,38 @@
/** /**
* Auth Entities - Export * Auth Entities - Export
* Updated: 2026-02-03 (ST-P3-008)
*/ */
export { RefreshToken } from './refresh-token.entity'; // Core entities
export { Tenant, TenantStatus } from './tenant.entity';
export { User, UserStatus } from './user.entity';
export { Role } from './role.entity'; export { Role } from './role.entity';
export { Permission } from './permission.entity'; export { Permission } from './permission.entity';
export { UserRole } from './user-role.entity'; export { UserRole } from './user-role.entity';
export { User, UserStatus } from './user.entity';
export { Session, SessionStatus } from './session.entity'; export { Session, SessionStatus } from './session.entity';
export { ApiKey } from './api-key.entity';
export { PasswordReset } from './password-reset.entity';
export { Company } from './company.entity'; export { Company } from './company.entity';
export { Group } from './group.entity'; export { Group } from './group.entity';
// Authentication
export { RefreshToken } from './refresh-token.entity';
export { ApiKey } from './api-key.entity';
export { PasswordReset } from './password-reset.entity';
export { VerificationCode, CodeType } from './verification-code.entity';
// Devices
export { Device } from './device.entity';
export { TrustedDevice, TrustLevel } from './trusted-device.entity';
// MFA
export { MfaAuditLog, MfaEventType } from './mfa-audit-log.entity';
// OAuth
export { OAuthProvider } from './oauth-provider.entity';
export { OAuthState } from './oauth-state.entity';
export { OAuthUserLink } from './oauth-user-link.entity';
// User Profiles
export { UserProfile } from './user-profile.entity';
export { ProfileTool } from './profile-tool.entity';
export { ProfileModule } from './profile-module.entity';
export { UserProfileAssignment } from './user-profile-assignment.entity';

View File

@ -0,0 +1,80 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
Index,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { User } from './user.entity';
export enum MfaEventType {
MFA_SETUP_INITIATED = 'mfa_setup_initiated',
MFA_SETUP_COMPLETED = 'mfa_setup_completed',
MFA_DISABLED = 'mfa_disabled',
TOTP_VERIFIED = 'totp_verified',
TOTP_FAILED = 'totp_failed',
BACKUP_CODE_USED = 'backup_code_used',
BACKUP_CODES_REGENERATED = 'backup_codes_regenerated',
DEVICE_TRUSTED = 'device_trusted',
DEVICE_REVOKED = 'device_revoked',
ANOMALY_DETECTED = 'anomaly_detected',
ACCOUNT_LOCKED = 'account_locked',
ACCOUNT_UNLOCKED = 'account_unlocked',
}
@Entity({ schema: 'auth', name: 'mfa_audit_log' })
@Index('idx_mfa_audit_user', ['userId', 'createdAt'])
@Index('idx_mfa_audit_event', ['eventType', 'createdAt'])
@Index('idx_mfa_audit_failures', ['userId', 'createdAt'], {
where: 'success = FALSE',
})
export class MfaAuditLog {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: false, name: 'user_id' })
userId: string;
@Column({
type: 'enum',
enum: MfaEventType,
nullable: false,
name: 'event_type',
})
eventType: MfaEventType;
@Column({ type: 'boolean', nullable: false })
success: boolean;
@Column({ type: 'varchar', length: 128, nullable: true, name: 'failure_reason' })
failureReason: string | null;
@Column({ type: 'inet', nullable: true, name: 'ip_address' })
ipAddress: string | null;
@Column({ type: 'text', nullable: true, name: 'user_agent' })
userAgent: string | null;
@Column({
type: 'varchar',
length: 128,
nullable: true,
name: 'device_fingerprint',
})
deviceFingerprint: string | null;
@Column({ type: 'jsonb', nullable: true })
location: Record<string, unknown> | null;
@Column({ type: 'jsonb', default: {}, nullable: true })
metadata: Record<string, unknown>;
@ManyToOne(() => User)
@JoinColumn({ name: 'user_id' })
user: User;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt: Date;
}

View File

@ -0,0 +1,181 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Index,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { Tenant } from './tenant.entity';
import { User } from './user.entity';
import { Role } from './role.entity';
@Entity({ schema: 'auth', name: 'oauth_providers' })
@Index('idx_oauth_providers_enabled', ['isEnabled'])
@Index('idx_oauth_providers_tenant', ['tenantId'])
@Index('idx_oauth_providers_code', ['code'])
export class OAuthProvider {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: true, name: 'tenant_id' })
tenantId: string | null;
@Column({ type: 'varchar', length: 50, nullable: false, unique: true })
code: string;
@Column({ type: 'varchar', length: 100, nullable: false })
name: string;
@Column({ type: 'varchar', length: 255, nullable: false, name: 'client_id' })
clientId: string;
@Column({ type: 'varchar', length: 500, nullable: true, name: 'client_secret' })
clientSecret: string | null;
@Column({
type: 'varchar',
length: 500,
nullable: false,
name: 'authorization_endpoint',
})
authorizationEndpoint: string;
@Column({
type: 'varchar',
length: 500,
nullable: false,
name: 'token_endpoint',
})
tokenEndpoint: string;
@Column({
type: 'varchar',
length: 500,
nullable: false,
name: 'userinfo_endpoint',
})
userinfoEndpoint: string;
@Column({ type: 'varchar', length: 500, nullable: true, name: 'jwks_uri' })
jwksUri: string | null;
@Column({
type: 'varchar',
length: 500,
default: 'openid profile email',
nullable: false,
})
scope: string;
@Column({
type: 'varchar',
length: 50,
default: 'code',
nullable: false,
name: 'response_type',
})
responseType: string;
@Column({
type: 'boolean',
default: true,
nullable: false,
name: 'pkce_enabled',
})
pkceEnabled: boolean;
@Column({
type: 'varchar',
length: 10,
default: 'S256',
nullable: true,
name: 'code_challenge_method',
})
codeChallengeMethod: string | null;
@Column({
type: 'jsonb',
nullable: false,
name: 'claim_mapping',
default: {
sub: 'oauth_uid',
email: 'email',
name: 'name',
picture: 'avatar_url',
},
})
claimMapping: Record<string, unknown>;
@Column({ type: 'varchar', length: 100, nullable: true, name: 'icon_class' })
iconClass: string | null;
@Column({ type: 'varchar', length: 100, nullable: true, name: 'button_text' })
buttonText: string | null;
@Column({ type: 'varchar', length: 20, nullable: true, name: 'button_color' })
buttonColor: string | null;
@Column({
type: 'integer',
default: 10,
nullable: false,
name: 'display_order',
})
displayOrder: number;
@Column({ type: 'boolean', default: false, nullable: false, name: 'is_enabled' })
isEnabled: boolean;
@Column({ type: 'boolean', default: true, nullable: false, name: 'is_visible' })
isVisible: boolean;
@Column({
type: 'text',
array: true,
nullable: true,
name: 'allowed_domains',
})
allowedDomains: string[] | null;
@Column({
type: 'boolean',
default: false,
nullable: false,
name: 'auto_create_users',
})
autoCreateUsers: boolean;
@Column({ type: 'uuid', nullable: true, name: 'default_role_id' })
defaultRoleId: string | null;
@ManyToOne(() => Tenant, { onDelete: 'CASCADE', nullable: true })
@JoinColumn({ name: 'tenant_id' })
tenant: Tenant | null;
@ManyToOne(() => Role, { nullable: true })
@JoinColumn({ name: 'default_role_id' })
defaultRole: Role | null;
@ManyToOne(() => User, { nullable: true })
@JoinColumn({ name: 'created_by' })
createdByUser: User | null;
@ManyToOne(() => User, { nullable: true })
@JoinColumn({ name: 'updated_by' })
updatedByUser: User | null;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt: Date;
@Column({ type: 'uuid', nullable: true, name: 'created_by' })
createdBy: string | null;
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
updatedAt: Date;
@Column({ type: 'uuid', nullable: true, name: 'updated_by' })
updatedBy: string | null;
}

View File

@ -0,0 +1,60 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
Index,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { OAuthProvider } from './oauth-provider.entity';
import { User } from './user.entity';
@Entity({ schema: 'auth', name: 'oauth_states' })
@Index('idx_oauth_states_state', ['state'])
@Index('idx_oauth_states_expires', ['expiresAt'])
export class OAuthState {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 64, nullable: false, unique: true })
state: string;
@Column({ type: 'varchar', length: 128, nullable: true, name: 'code_verifier' })
codeVerifier: string | null;
@Column({ type: 'uuid', nullable: false, name: 'provider_id' })
providerId: string;
@Column({ type: 'varchar', length: 500, nullable: false, name: 'redirect_uri' })
redirectUri: string;
@Column({ type: 'varchar', length: 500, nullable: true, name: 'return_url' })
returnUrl: string | null;
@Column({ type: 'uuid', nullable: true, name: 'link_user_id' })
linkUserId: string | null;
@Column({ type: 'inet', nullable: true, name: 'ip_address' })
ipAddress: string | null;
@Column({ type: 'text', nullable: true, name: 'user_agent' })
userAgent: string | null;
@ManyToOne(() => OAuthProvider)
@JoinColumn({ name: 'provider_id' })
provider: OAuthProvider;
@ManyToOne(() => User, { nullable: true })
@JoinColumn({ name: 'link_user_id' })
linkUser: User | null;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt: Date;
@Column({ type: 'timestamptz', nullable: false, name: 'expires_at' })
expiresAt: Date;
@Column({ type: 'timestamptz', nullable: true, name: 'used_at' })
usedAt: Date | null;
}

View File

@ -0,0 +1,68 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Index,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { User } from './user.entity';
import { OAuthProvider } from './oauth-provider.entity';
@Entity({ schema: 'auth', name: 'oauth_user_links' })
@Index('idx_oauth_links_user', ['userId'])
@Index('idx_oauth_links_provider', ['providerId'])
@Index('idx_oauth_links_oauth_uid', ['oauthUid'])
export class OAuthUserLink {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: false, name: 'user_id' })
userId: string;
@Column({ type: 'uuid', nullable: false, name: 'provider_id' })
providerId: string;
@Column({ type: 'varchar', length: 255, nullable: false, name: 'oauth_uid' })
oauthUid: string;
@Column({ type: 'varchar', length: 255, nullable: true, name: 'oauth_email' })
oauthEmail: string | null;
@Column({ type: 'text', nullable: true, name: 'access_token' })
accessToken: string | null;
@Column({ type: 'text', nullable: true, name: 'refresh_token' })
refreshToken: string | null;
@Column({ type: 'text', nullable: true, name: 'id_token' })
idToken: string | null;
@Column({ type: 'timestamptz', nullable: true, name: 'token_expires_at' })
tokenExpiresAt: Date | null;
@Column({ type: 'jsonb', nullable: true, name: 'raw_userinfo' })
rawUserinfo: Record<string, unknown> | null;
@Column({ type: 'timestamptz', nullable: true, name: 'last_login_at' })
lastLoginAt: Date | null;
@Column({ type: 'integer', default: 0, nullable: false, name: 'login_count' })
loginCount: number;
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_id' })
user: User;
@ManyToOne(() => OAuthProvider, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'provider_id' })
provider: OAuthProvider;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at', type: 'timestamptz' })
updatedAt: Date;
}

View File

@ -0,0 +1,27 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { UserProfile } from './user-profile.entity';
@Entity({ schema: 'auth', name: 'profile_modules' })
export class ProfileModule {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: false, name: 'profile_id' })
profileId: string;
@Column({ type: 'varchar', length: 50, nullable: false, name: 'module_code' })
moduleCode: string;
@Column({ name: 'is_enabled', default: true })
isEnabled: boolean;
@ManyToOne(() => UserProfile, (p) => p.modules, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'profile_id' })
profile: UserProfile;
}

View File

@ -0,0 +1,36 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { UserProfile } from './user-profile.entity';
@Entity({ schema: 'auth', name: 'profile_tools' })
export class ProfileTool {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: false, name: 'profile_id' })
profileId: string;
@Column({ type: 'varchar', length: 50, nullable: false, name: 'tool_code' })
toolCode: string;
@Column({ name: 'can_view', default: false })
canView: boolean;
@Column({ name: 'can_create', default: false })
canCreate: boolean;
@Column({ name: 'can_edit', default: false })
canEdit: boolean;
@Column({ name: 'can_delete', default: false })
canDelete: boolean;
@ManyToOne(() => UserProfile, (p) => p.tools, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'profile_id' })
profile: UserProfile;
}

View File

@ -0,0 +1,91 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
UpdateDateColumn,
Index,
OneToMany,
} from 'typeorm';
import { Company } from './company.entity';
import { User } from './user.entity';
import { Role } from './role.entity';
export enum TenantStatus {
ACTIVE = 'active',
SUSPENDED = 'suspended',
TRIAL = 'trial',
CANCELLED = 'cancelled',
}
@Entity({ schema: 'auth', name: 'tenants' })
@Index('idx_tenants_subdomain', ['subdomain'])
@Index('idx_tenants_status', ['status'], { where: 'deleted_at IS NULL' })
@Index('idx_tenants_created_at', ['createdAt'])
export class Tenant {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'varchar', length: 255, nullable: false })
name: string;
@Column({ type: 'varchar', length: 100, unique: true, nullable: false })
subdomain: string;
@Column({
type: 'varchar',
length: 100,
unique: true,
nullable: false,
name: 'schema_name',
})
schemaName: string;
@Column({
type: 'enum',
enum: TenantStatus,
default: TenantStatus.ACTIVE,
nullable: false,
})
status: TenantStatus;
@Column({ type: 'jsonb', default: {} })
settings: Record<string, unknown>;
@Column({ type: 'varchar', length: 50, default: 'basic', nullable: true })
plan: string;
@Column({ type: 'integer', default: 10, name: 'max_users' })
maxUsers: number;
@OneToMany(() => Company, (company) => company.tenant)
companies: Company[];
@OneToMany(() => User, (user) => user.tenant)
users: User[];
@OneToMany(() => Role, (role) => role.tenant)
roles: Role[];
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
createdAt: Date;
@Column({ type: 'uuid', nullable: true, name: 'created_by' })
createdBy: string | null;
@UpdateDateColumn({
name: 'updated_at',
type: 'timestamp',
nullable: true,
})
updatedAt: Date | null;
@Column({ type: 'uuid', nullable: true, name: 'updated_by' })
updatedBy: string | null;
@Column({ type: 'timestamp', nullable: true, name: 'deleted_at' })
deletedAt: Date | null;
@Column({ type: 'uuid', nullable: true, name: 'deleted_by' })
deletedBy: string | null;
}

View File

@ -0,0 +1,107 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
Index,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { User } from './user.entity';
export enum TrustLevel {
STANDARD = 'standard',
HIGH = 'high',
TEMPORARY = 'temporary',
}
@Entity({ schema: 'auth', name: 'trusted_devices' })
@Index('idx_trusted_devices_user', ['userId'], { where: 'is_active' })
@Index('idx_trusted_devices_fingerprint', ['deviceFingerprint'])
@Index('idx_trusted_devices_expires', ['trustExpiresAt'], {
where: 'trust_expires_at IS NOT NULL AND is_active',
})
export class TrustedDevice {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: false, name: 'user_id' })
userId: string;
@Column({
type: 'varchar',
length: 128,
nullable: false,
name: 'device_fingerprint',
})
deviceFingerprint: string;
@Column({ type: 'varchar', length: 128, nullable: true, name: 'device_name' })
deviceName: string | null;
@Column({ type: 'varchar', length: 32, nullable: true, name: 'device_type' })
deviceType: string | null;
@Column({ type: 'text', nullable: true, name: 'user_agent' })
userAgent: string | null;
@Column({ type: 'varchar', length: 64, nullable: true, name: 'browser_name' })
browserName: string | null;
@Column({
type: 'varchar',
length: 32,
nullable: true,
name: 'browser_version',
})
browserVersion: string | null;
@Column({ type: 'varchar', length: 64, nullable: true, name: 'os_name' })
osName: string | null;
@Column({ type: 'varchar', length: 32, nullable: true, name: 'os_version' })
osVersion: string | null;
@Column({ type: 'inet', nullable: false, name: 'registered_ip' })
registeredIp: string;
@Column({ type: 'jsonb', nullable: true, name: 'registered_location' })
registeredLocation: Record<string, unknown> | null;
@Column({ type: 'boolean', default: true, nullable: false, name: 'is_active' })
isActive: boolean;
@Column({
type: 'enum',
enum: TrustLevel,
default: TrustLevel.STANDARD,
nullable: false,
name: 'trust_level',
})
trustLevel: TrustLevel;
@Column({ type: 'timestamptz', nullable: true, name: 'trust_expires_at' })
trustExpiresAt: Date | null;
@Column({ type: 'timestamptz', nullable: false, name: 'last_used_at' })
lastUsedAt: Date;
@Column({ type: 'inet', nullable: true, name: 'last_used_ip' })
lastUsedIp: string | null;
@Column({ type: 'integer', default: 1, nullable: false, name: 'use_count' })
useCount: number;
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_id' })
user: User;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt: Date;
@Column({ type: 'timestamptz', nullable: true, name: 'revoked_at' })
revokedAt: Date | null;
@Column({ type: 'varchar', length: 128, nullable: true, name: 'revoked_reason' })
revokedReason: string | null;
}

View File

@ -0,0 +1,36 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
JoinColumn,
CreateDateColumn,
} from 'typeorm';
import { User } from './user.entity';
import { UserProfile } from './user-profile.entity';
@Entity({ schema: 'auth', name: 'user_profile_assignments' })
export class UserProfileAssignment {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: false, name: 'user_id' })
userId: string;
@Column({ type: 'uuid', nullable: false, name: 'profile_id' })
profileId: string;
@Column({ name: 'is_default', default: false })
isDefault: boolean;
@CreateDateColumn({ name: 'assigned_at', type: 'timestamp' })
assignedAt: Date;
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_id' })
user: User;
@ManyToOne(() => UserProfile, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'profile_id' })
profile: UserProfile;
}

View File

@ -0,0 +1,52 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
ManyToOne,
OneToMany,
JoinColumn,
CreateDateColumn,
UpdateDateColumn,
Index,
} from 'typeorm';
import { Tenant } from './tenant.entity';
import { ProfileTool } from './profile-tool.entity';
import { ProfileModule } from './profile-module.entity';
@Entity({ schema: 'auth', name: 'user_profiles' })
@Index('idx_user_profiles_tenant_id', ['tenantId'])
export class UserProfile {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: false, name: 'tenant_id' })
tenantId: string;
@Column({ type: 'varchar', length: 10, nullable: false })
code: string;
@Column({ type: 'varchar', length: 100, nullable: false })
name: string;
@Column({ type: 'text', nullable: true })
description: string;
@Column({ name: 'is_active', default: true })
isActive: boolean;
@CreateDateColumn({ name: 'created_at', type: 'timestamp' })
createdAt: Date;
@UpdateDateColumn({ name: 'updated_at', type: 'timestamp', nullable: true })
updatedAt: Date;
@ManyToOne(() => Tenant, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'tenant_id' })
tenant: Tenant;
@OneToMany(() => ProfileTool, (pt) => pt.profile)
tools: ProfileTool[];
@OneToMany(() => ProfileModule, (pm) => pm.profile)
modules: ProfileModule[];
}

View File

@ -0,0 +1,82 @@
import {
Entity,
PrimaryGeneratedColumn,
Column,
CreateDateColumn,
Index,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { User } from './user.entity';
import { Session } from './session.entity';
export enum CodeType {
TOTP_SETUP = 'totp_setup',
SMS = 'sms',
EMAIL = 'email',
BACKUP = 'backup',
}
@Entity({ schema: 'auth', name: 'verification_codes' })
@Index('idx_verification_codes_user', ['userId', 'codeType'], {
where: 'used_at IS NULL',
})
@Index('idx_verification_codes_expires', ['expiresAt'], {
where: 'used_at IS NULL',
})
export class VerificationCode {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column({ type: 'uuid', nullable: false, name: 'user_id' })
userId: string;
@Column({ type: 'uuid', nullable: true, name: 'session_id' })
sessionId: string | null;
@Column({
type: 'enum',
enum: CodeType,
nullable: false,
name: 'code_type',
})
codeType: CodeType;
@Column({ type: 'varchar', length: 64, nullable: false, name: 'code_hash' })
codeHash: string;
@Column({ type: 'integer', default: 6, nullable: false, name: 'code_length' })
codeLength: number;
@Column({ type: 'varchar', length: 256, nullable: true })
destination: string | null;
@Column({ type: 'integer', default: 0, nullable: false })
attempts: number;
@Column({ type: 'integer', default: 5, nullable: false, name: 'max_attempts' })
maxAttempts: number;
@CreateDateColumn({ name: 'created_at', type: 'timestamptz' })
createdAt: Date;
@Column({ type: 'timestamptz', nullable: false, name: 'expires_at' })
expiresAt: Date;
@Column({ type: 'timestamptz', nullable: true, name: 'used_at' })
usedAt: Date | null;
@Column({ type: 'inet', nullable: true, name: 'ip_address' })
ipAddress: string | null;
@Column({ type: 'text', nullable: true, name: 'user_agent' })
userAgent: string | null;
@ManyToOne(() => User, { onDelete: 'CASCADE' })
@JoinColumn({ name: 'user_id' })
user: User;
@ManyToOne(() => Session, { onDelete: 'CASCADE', nullable: true })
@JoinColumn({ name: 'session_id' })
session: Session | null;
}