import { IsBoolean, IsEmail, IsInt, IsNotEmpty, IsOptional, IsPhoneNumber, IsString, IsUrl, IsUUID } from "class-validator"; import { ChildEntity, Column, JoinTable, ManyToMany, OneToMany } from "typeorm"; import { config } from '../../config'; import { ResponsePrincipal } from '../responses/ResponsePrincipal'; import { ResponseUser } from '../responses/ResponseUser'; import { Permission } from './Permission'; import { Principal } from './Principal'; import { UserAction } from './UserAction'; import { UserGroup } from './UserGroup'; /** * Defines the User entity. * Users are the ones that can use the "admin" webui and do stuff in the backend. */ @ChildEntity() export class User extends Principal { /** * The user's uuid. * Mainly gets used as a per-user salt for the password hash. */ @Column({ unique: true }) @IsUUID(4) uuid: string; /** * The user's e-mail address. * Either username or email has to be set (otherwise the user couldn't log in). */ @Column({ nullable: false, unique: true }) @IsEmail() @IsNotEmpty() email: string; /** * The user's phone number. */ @Column({ nullable: true }) @IsOptional() @IsPhoneNumber(config.phone_validation_countrycode) phone?: string; /** * The user's username. * Either username or email has to be set (otherwise the user couldn't log in). */ @Column({ nullable: true, unique: true }) @IsString() username?: string; /** * The user's first name. */ @Column() @IsString() @IsNotEmpty() firstname: string; /** * The user's middle name. */ @Column({ nullable: true }) @IsString() @IsOptional() middlename?: string; /** * The user's last name. */ @Column() @IsString() @IsNotEmpty() lastname: string; /** * The user's password. * This is a argon2 hash salted with the user's uuid. */ @Column() @IsString() @IsNotEmpty() password: string; /** * The groups this user is a part of. * The user will inherit the groups permissions (without overwriting his own). */ @IsOptional() @ManyToMany(() => UserGroup, { nullable: true }) @JoinTable() groups: UserGroup[]; /** * Is this user enabled? */ @Column() @IsBoolean() enabled: boolean = true; /** * The user's jwt refresh token count. * Used to invalidate jwts. */ @IsInt() @Column({ default: 1 }) refreshTokenCount?: number; /** * The user's profile picture. * We haven't decided yet if this will be a bas64 encoded image or just a link to the profile picture. */ @Column({ nullable: false, unique: false }) @IsString() @IsUrl() profilePic: string; /** * The last time the user requested a password reset. * Used to prevent spamming of the password reset route. */ @Column({ nullable: true, unique: false }) @IsString() @IsOptional() resetRequestedTimestamp?: number; /** * The actions performed by this user. * For documentation purposes only, will be implemented later. */ @IsOptional() @OneToMany(() => UserAction, action => action.user, { nullable: true }) actions: UserAction[] /** * Resolves all permissions granted to this user through groups. */ public get inheritedPermissions(): Permission[] { let returnPermissions: Permission[] = new Array(); if (!this.groups) { return returnPermissions; } for (let group of this.groups) { for (let permission of group.permissions) { returnPermissions.push(permission); } } return returnPermissions; } /** * Resolves all permissions granted to this user through groups or directly to the string enum format. * Also deduplicates the array. */ public get allPermissions(): string[] { let returnPermissions: string[] = new Array(); if (!this.permissions) { return returnPermissions; } for (let permission of this.permissions) { returnPermissions.push(permission.toString()); } if (!this.groups) { return returnPermissions; } for (let group of this.groups) { for (let permission of group.permissions) { returnPermissions.push(permission.toString()); } } return Array.from(new Set(returnPermissions)); } /** * Turns this entity into it's response class. */ public toResponse(): ResponsePrincipal { return new ResponseUser(this); } }