more auth stuff
This commit is contained in:
parent
1904fe94ed
commit
d81fc53819
@ -2,6 +2,7 @@ import { Controller } from '@nestjs/common';
|
||||
import { AuthService } from './services/auth.service';
|
||||
import { MessagePattern } from '@nestjs/microservices';
|
||||
import { LoginRequest } from './interfaces/auth.interface';
|
||||
import { UserInfo } from '@freeblox/shared';
|
||||
|
||||
@Controller()
|
||||
export class AuthController {
|
||||
@ -14,6 +15,16 @@ export class AuthController {
|
||||
|
||||
@MessagePattern('auth.verify')
|
||||
verify({ token }: { token: string }) {
|
||||
return this.authService.getUserFromToken(token);
|
||||
return this.authService.verifyToken(token);
|
||||
}
|
||||
|
||||
@MessagePattern('auth.getUserById')
|
||||
getUserById({ id }: { id: string }) {
|
||||
return this.authService.getUserById(id);
|
||||
}
|
||||
|
||||
@MessagePattern('auth.getUserBans')
|
||||
getUserBans({ user }: { user: UserInfo }) {
|
||||
return this.authService.getUserBans(user);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,17 @@ import { UserTokenEntity } from './database/entities/user-token.entity';
|
||||
import { OTPService } from './services/otp.service';
|
||||
import { BanEntity } from './database/entities/ban.entity';
|
||||
import { BanService } from './services/ban.service';
|
||||
import { PrivilegeEntity } from './database/entities/privilege.entity';
|
||||
import { RoleService } from './services/role.service';
|
||||
import { RoleEntity } from './database/entities/role.entity';
|
||||
|
||||
const entities = [
|
||||
UserEntity,
|
||||
UserTokenEntity,
|
||||
BanEntity,
|
||||
PrivilegeEntity,
|
||||
RoleEntity,
|
||||
];
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -26,9 +37,10 @@ import { BanService } from './services/ban.service';
|
||||
inject: [ConfigService],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
...config.get('typeorm'),
|
||||
entities,
|
||||
}),
|
||||
}),
|
||||
TypeOrmModule.forFeature([UserEntity, UserTokenEntity, BanEntity]),
|
||||
TypeOrmModule.forFeature(entities),
|
||||
ClientsModule.register([natsClient('auth')]),
|
||||
],
|
||||
controllers: [AuthController],
|
||||
@ -36,8 +48,9 @@ import { BanService } from './services/ban.service';
|
||||
...keysProviders,
|
||||
JWTService,
|
||||
OTPService,
|
||||
AuthService,
|
||||
BanService,
|
||||
RoleService,
|
||||
AuthService,
|
||||
],
|
||||
})
|
||||
export class AuthModule implements OnModuleInit {
|
||||
|
@ -3,6 +3,7 @@ import { resolve } from 'path';
|
||||
|
||||
export const security = registerAs('security', () => ({
|
||||
algorithm: String(process.env.JWT_ALGORITHM || 'RS512'),
|
||||
tokenExpiry: Number(process.env.JWT_EXPIRY) || 60 * 60,
|
||||
privateKeyPath: resolve(String(process.env.PRIVATE_KEY_FILE)),
|
||||
publicKeyPath: resolve(String(process.env.PUBLIC_KEY_FILE)),
|
||||
}));
|
||||
|
18
apps/auth/src/database/entities/privilege.entity.ts
Normal file
18
apps/auth/src/database/entities/privilege.entity.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { UserMetaEntity } from '@freeblox/shared';
|
||||
import { Exclude, Expose } from 'class-transformer';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
|
||||
@Entity('privileges')
|
||||
@Exclude()
|
||||
export class PrivilegeEntity extends UserMetaEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
@Expose()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
@Expose()
|
||||
privilege: string;
|
||||
|
||||
@Column({ default: false })
|
||||
automatic: boolean;
|
||||
}
|
54
apps/auth/src/database/entities/role.entity.ts
Normal file
54
apps/auth/src/database/entities/role.entity.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import { UserMetaEntity } from '@freeblox/shared';
|
||||
import { Exclude, Expose } from 'class-transformer';
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
Tree,
|
||||
TreeChildren,
|
||||
TreeParent,
|
||||
} from 'typeorm';
|
||||
import { PrivilegeEntity } from './privilege.entity';
|
||||
|
||||
@Entity('roles')
|
||||
@Exclude()
|
||||
@Tree('materialized-path')
|
||||
export class RoleEntity extends UserMetaEntity {
|
||||
@PrimaryGeneratedColumn()
|
||||
@Expose()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
@Expose()
|
||||
role: string;
|
||||
|
||||
@Column({ name: 'parent_id' })
|
||||
@Expose()
|
||||
parentId: number;
|
||||
|
||||
@TreeParent()
|
||||
@JoinColumn({ name: 'parent_id' })
|
||||
parent: RoleEntity;
|
||||
|
||||
@TreeChildren()
|
||||
children: RoleEntity[];
|
||||
|
||||
@Column({ default: false })
|
||||
automatic: boolean;
|
||||
|
||||
@ManyToMany(() => PrivilegeEntity, { eager: true })
|
||||
@Expose()
|
||||
@JoinTable({
|
||||
name: 'role_privilege',
|
||||
joinColumn: {
|
||||
name: 'role_id',
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: 'privilege_id',
|
||||
},
|
||||
})
|
||||
privileges: PrivilegeEntity[];
|
||||
}
|
@ -5,6 +5,7 @@ import {
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
} from 'typeorm';
|
||||
import { UserEntity } from './user.entity';
|
||||
|
||||
@ -29,5 +30,6 @@ export class UserTokenEntity {
|
||||
created_at: Date;
|
||||
|
||||
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE' })
|
||||
@JoinColumn({ name: 'user_id' })
|
||||
user: UserEntity;
|
||||
}
|
||||
|
@ -1,10 +1,18 @@
|
||||
import { MetaEntity } from '@freeblox/shared';
|
||||
import { Exclude, Expose } from 'class-transformer';
|
||||
import { IsOptional, IsString } from 'class-validator';
|
||||
import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
|
||||
import {
|
||||
Column,
|
||||
Entity,
|
||||
JoinTable,
|
||||
ManyToMany,
|
||||
PrimaryGeneratedColumn,
|
||||
} from 'typeorm';
|
||||
import { PrivilegeEntity } from './privilege.entity';
|
||||
import { RoleEntity } from './role.entity';
|
||||
|
||||
@Entity('user_entity')
|
||||
@Expose()
|
||||
@Entity('users')
|
||||
export class UserEntity extends MetaEntity {
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
@IsString()
|
||||
@ -55,4 +63,30 @@ export class UserEntity extends MetaEntity {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
loginAt: Date;
|
||||
|
||||
@ManyToMany(() => PrivilegeEntity)
|
||||
@JoinTable({
|
||||
name: 'user_privilege',
|
||||
joinColumn: {
|
||||
name: 'user_id',
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: 'privilege_id',
|
||||
},
|
||||
})
|
||||
@Exclude()
|
||||
privileges: PrivilegeEntity[];
|
||||
|
||||
@ManyToMany(() => RoleEntity)
|
||||
@JoinTable({
|
||||
name: 'user_role',
|
||||
joinColumn: {
|
||||
name: 'user_id',
|
||||
},
|
||||
inverseJoinColumn: {
|
||||
name: 'role_id',
|
||||
},
|
||||
})
|
||||
@Exclude()
|
||||
roles: RoleEntity[];
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ export async function up(knex: Knex): Promise<void> {
|
||||
table.text('reason').notNullable();
|
||||
|
||||
table.string('ip', 255).nullable();
|
||||
table.integer('cidr', 2).nullable().unsigned();
|
||||
table.integer('cidr', 2).nullable().defaultTo(32).unsigned();
|
||||
|
||||
table.uuid('user_id').nullable();
|
||||
table.uuid('admin_id').nullable();
|
||||
|
@ -2,6 +2,7 @@ import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
return knex.schema.createTable('user_privilege', (table) => {
|
||||
table.increments('id').primary();
|
||||
table.integer('privilege_id').nullable().unsigned();
|
||||
table.uuid('user_id').nullable();
|
||||
table
|
||||
|
@ -4,6 +4,7 @@ export async function up(knex: Knex): Promise<void> {
|
||||
return knex.schema.createTable('roles', (table) => {
|
||||
table.increments('id').primary();
|
||||
table.text('role').notNullable();
|
||||
table.string('mpath').nullable().defaultTo('');
|
||||
table.integer('parent_id').nullable().unsigned();
|
||||
table.boolean('automatic').defaultTo(false);
|
||||
|
||||
|
@ -2,6 +2,7 @@ import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
return knex.schema.createTable('user_role', (table) => {
|
||||
table.increments('id').primary();
|
||||
table.integer('role_id').nullable().unsigned();
|
||||
table.uuid('user_id').nullable();
|
||||
table.foreign('role_id').references('roles.id').onDelete('CASCADE');
|
||||
|
@ -2,6 +2,7 @@ import { Knex } from 'knex';
|
||||
|
||||
export async function up(knex: Knex): Promise<void> {
|
||||
return knex.schema.createTable('role_privilege', (table) => {
|
||||
table.increments('id').primary();
|
||||
table.integer('role_id').nullable().unsigned();
|
||||
table.integer('privilege_id').nullable().unsigned();
|
||||
table.foreign('role_id').references('roles.id').onDelete('CASCADE');
|
||||
|
@ -6,25 +6,30 @@ export async function seed(knex: Knex): Promise<void> {
|
||||
name: 'player',
|
||||
automatic: true,
|
||||
id: 0,
|
||||
path: '',
|
||||
},
|
||||
{
|
||||
name: 'member',
|
||||
id: 0,
|
||||
parent: 'player',
|
||||
path: '',
|
||||
},
|
||||
{
|
||||
name: 'moderator',
|
||||
id: 0,
|
||||
parent: 'member',
|
||||
path: '',
|
||||
},
|
||||
{
|
||||
name: 'admin',
|
||||
id: 0,
|
||||
parent: 'moderator',
|
||||
path: '',
|
||||
},
|
||||
{
|
||||
name: 'reduced',
|
||||
id: 0,
|
||||
path: '',
|
||||
},
|
||||
];
|
||||
|
||||
@ -105,7 +110,10 @@ export async function seed(knex: Knex): Promise<void> {
|
||||
const findRole = initialRoles.find(
|
||||
(parent) => parent.name === role.parent,
|
||||
);
|
||||
parentId = findRole?.id;
|
||||
if (findRole) {
|
||||
parentId = findRole.id;
|
||||
role.path += findRole.path;
|
||||
}
|
||||
}
|
||||
|
||||
const [created] = await knex('roles')
|
||||
@ -113,12 +121,17 @@ export async function seed(knex: Knex): Promise<void> {
|
||||
{
|
||||
role: role.name,
|
||||
automatic: role?.automatic,
|
||||
mpath: role.path,
|
||||
parent_id: parentId,
|
||||
created_at: new Date(),
|
||||
},
|
||||
])
|
||||
.returning(['id']);
|
||||
|
||||
role.id = created.id;
|
||||
role.path += `${role.path ? '.' : ''}${role.id}`;
|
||||
|
||||
await knex('roles').where({ id: role.id }).update({ mpath: role.path });
|
||||
}
|
||||
|
||||
for (const privilege of initialPrivileges) {
|
||||
|
4
apps/auth/src/interfaces/token-response.interface.ts
Normal file
4
apps/auth/src/interfaces/token-response.interface.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export interface TokenResponse {
|
||||
token: string;
|
||||
expires_in: number;
|
||||
}
|
@ -14,6 +14,8 @@ import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { OTPService } from './otp.service';
|
||||
import { BanService } from './ban.service';
|
||||
import { UserInfo } from '@freeblox/shared';
|
||||
import { RoleService } from './role.service';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
@ -21,8 +23,10 @@ export class AuthService {
|
||||
private readonly jwtService: JWTService,
|
||||
private readonly otpService: OTPService,
|
||||
private readonly banService: BanService,
|
||||
private readonly roleService: RoleService,
|
||||
@InjectRepository(UserEntity)
|
||||
private readonly userRepository: Repository<UserEntity>,
|
||||
private readonly config: ConfigService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@ -78,17 +82,24 @@ export class AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
// Check for active ban
|
||||
const bans = await this.banService.getActiveBansForUser(userEntity);
|
||||
const banned = !!bans.length;
|
||||
|
||||
// Get all privileges applicable to user
|
||||
const privileges = await this.roleService.getUserPrivileges(userEntity);
|
||||
|
||||
// Issue token
|
||||
const exp = this.config.get('security.tokenExpiry');
|
||||
const issuedToken = await this.jwtService.sign({
|
||||
sub: userEntity.id,
|
||||
username: userEntity.username,
|
||||
display_name: userEntity.displayName,
|
||||
language: userEntity.language,
|
||||
banned: banned,
|
||||
privileges: banned ? [] : ['freeblox'],
|
||||
privileges: banned
|
||||
? []
|
||||
: privileges.map((privilege) => privilege.privilege),
|
||||
});
|
||||
|
||||
// Set login time to now
|
||||
@ -97,16 +108,19 @@ export class AuthService {
|
||||
{ loginAt: new Date() },
|
||||
);
|
||||
|
||||
return issuedToken;
|
||||
return {
|
||||
token: issuedToken,
|
||||
expires_in: exp,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate user token
|
||||
* Get user from token
|
||||
* @param token JWT Token
|
||||
* @returns User entity
|
||||
*/
|
||||
async getUserFromToken(token: string) {
|
||||
const tokenInfo = await this.jwtService.verify(token);
|
||||
const tokenInfo = await this.verifyToken(token);
|
||||
const user = await this.userRepository.findOneByOrFail({
|
||||
id: tokenInfo.sub,
|
||||
activated: true,
|
||||
@ -114,6 +128,30 @@ export class AuthService {
|
||||
return instanceToPlain(user);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify user token
|
||||
* @param token JWT token
|
||||
* @returns User token info
|
||||
*/
|
||||
async verifyToken(token: string) {
|
||||
try {
|
||||
return await this.jwtService.verify(token);
|
||||
} catch (e) {
|
||||
console.error(token, e);
|
||||
throw new ForbiddenException('Invalid token');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user entity by ID
|
||||
* @param id User ID
|
||||
* @returns User entity
|
||||
*/
|
||||
async getUserById(id: string) {
|
||||
if (!id) throw new BadRequestException('ID is required');
|
||||
return instanceToPlain(await this.userRepository.findOneByOrFail({ id }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user bans
|
||||
* @param tokeninfo
|
||||
|
@ -13,12 +13,14 @@ export class JWTService {
|
||||
|
||||
async sign(data: Record<string, unknown>, audience = 'urn:freeblox:service') {
|
||||
const alg = this.config.get('security.algorithm');
|
||||
const exp =
|
||||
this.config.get('security.tokenExpiry') + Math.floor(Date.now() / 1000);
|
||||
const jwt = await new SignJWT(data)
|
||||
.setProtectedHeader({ alg })
|
||||
.setIssuedAt()
|
||||
.setIssuer('urn:freeblox:auth')
|
||||
.setAudience(audience)
|
||||
.setExpirationTime('8d')
|
||||
.setExpirationTime(exp)
|
||||
.sign(this.privateKey);
|
||||
return jwt;
|
||||
}
|
||||
|
78
apps/auth/src/services/role.service.ts
Normal file
78
apps/auth/src/services/role.service.ts
Normal file
@ -0,0 +1,78 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectEntityManager, InjectRepository } from '@nestjs/typeorm';
|
||||
import { EntityManager, Repository } from 'typeorm';
|
||||
import { UserEntity } from '../database/entities/user.entity';
|
||||
import { RoleEntity } from '../database/entities/role.entity';
|
||||
import { PrivilegeEntity } from '../database/entities/privilege.entity';
|
||||
|
||||
@Injectable()
|
||||
export class RoleService {
|
||||
constructor(
|
||||
@InjectEntityManager() private manager: EntityManager,
|
||||
@InjectRepository(UserEntity)
|
||||
private readonly userRepository: Repository<UserEntity>,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Get all applicable privileges for roles
|
||||
* @param roles Roles to look for privileges for
|
||||
* @param startingPrivileges Privileges already included
|
||||
* @returns Unique list of privileges
|
||||
*/
|
||||
async getApplicablePrivileges(
|
||||
roles: RoleEntity[],
|
||||
startingPrivileges: PrivilegeEntity[] = [],
|
||||
) {
|
||||
const roleTree = await this.manager
|
||||
.getTreeRepository(RoleEntity)
|
||||
.findTrees({ relations: ['privileges'] });
|
||||
|
||||
const privileges: PrivilegeEntity[] = [...startingPrivileges];
|
||||
for (const { id } of roles) {
|
||||
for (const role of roleTree) {
|
||||
const [result, list] = this.accumulatePrivilegesFromTree(role, id);
|
||||
if (!result) continue;
|
||||
privileges.push(...list);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return privileges.filter(
|
||||
(value, index, array) =>
|
||||
array.findIndex((entry) => entry.id === value.id) === index,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all applicable privileges for user.
|
||||
* @param user User
|
||||
* @returns Unique privilege list
|
||||
*/
|
||||
async getUserPrivileges(user: UserEntity) {
|
||||
if (!user.privileges || !user.roles) {
|
||||
user = await this.userRepository.findOne({
|
||||
where: { id: user.id },
|
||||
relations: ['privileges', 'roles'],
|
||||
});
|
||||
}
|
||||
|
||||
return this.getApplicablePrivileges(user.roles, user.privileges);
|
||||
}
|
||||
|
||||
/**
|
||||
* Accumulate privileges from a tree structure of roles.
|
||||
* @param root Node to start from
|
||||
* @param target Target role ID
|
||||
* @returns `[<Found role entity>, <Privilege list>]`
|
||||
*/
|
||||
private accumulatePrivilegesFromTree(root: RoleEntity, target: number) {
|
||||
const privileges: PrivilegeEntity[] = [...root.privileges];
|
||||
if (root.id === target) return [root, privileges];
|
||||
for (const child of root.children || []) {
|
||||
const [found, list] = this.accumulatePrivilegesFromTree(child, target);
|
||||
if (found) return [found, [...privileges, ...list]];
|
||||
continue;
|
||||
}
|
||||
return [null, []];
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
|
||||
|
||||
export const User = createParamDecorator(
|
||||
(data: unknown, ctx: ExecutionContext) => {
|
||||
const response = ctx.switchToHttp().getResponse();
|
||||
return response.locals.user;
|
||||
},
|
||||
);
|
30
apps/freeblox-web-service/src/guards/auth.guard.ts
Normal file
30
apps/freeblox-web-service/src/guards/auth.guard.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Inject,
|
||||
Injectable,
|
||||
} from '@nestjs/common';
|
||||
import { ClientProxy } from '@nestjs/microservices';
|
||||
import { Request, Response } from 'express';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
|
||||
@Injectable()
|
||||
export class AuthGuard implements CanActivate {
|
||||
constructor(@Inject('auth') private authClient: ClientProxy) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest() as Request;
|
||||
const response = context.switchToHttp().getResponse() as Response;
|
||||
if (!request.headers.authorization) return false;
|
||||
|
||||
// Verify token by auth microservice
|
||||
const [, token] = request.headers.authorization.split(' ');
|
||||
const user = await lastValueFrom(
|
||||
this.authClient.send('auth.verify', { token }),
|
||||
);
|
||||
|
||||
// Add token contents to locals
|
||||
response.locals.user = user;
|
||||
return true;
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ async function bootstrap() {
|
||||
.setTitle('Freeblox Web Service')
|
||||
.setDescription('Freeblox Web Service API gateway')
|
||||
.setVersion('1.0')
|
||||
.addBearerAuth()
|
||||
.build();
|
||||
const document = SwaggerModule.createDocument(app, config);
|
||||
SwaggerModule.setup('api', app, document);
|
||||
|
@ -2,18 +2,26 @@ import {
|
||||
Body,
|
||||
ClassSerializerInterceptor,
|
||||
Controller,
|
||||
Get,
|
||||
Inject,
|
||||
Post,
|
||||
UseGuards,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { ClientProxy } from '@nestjs/microservices';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { ApiBearerAuth, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { LoginDto } from './dtos/login.dto';
|
||||
import { User } from '../../decorators/user.decorator';
|
||||
import { UserInfo } from '@freeblox/shared';
|
||||
import { lastValueFrom } from 'rxjs';
|
||||
import { AuthGuard } from '../../guards/auth.guard';
|
||||
import { UserDto } from './dtos/user.dto';
|
||||
|
||||
@Controller({
|
||||
version: '1',
|
||||
path: 'auth',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@ApiTags('Auth')
|
||||
@UseInterceptors(ClassSerializerInterceptor)
|
||||
export class AuthController {
|
||||
@ -23,4 +31,17 @@ export class AuthController {
|
||||
async login(@Body() body: LoginDto) {
|
||||
return this.auth.send('auth.login', { body });
|
||||
}
|
||||
|
||||
@Get('me')
|
||||
@ApiOkResponse({ type: UserDto })
|
||||
@UseGuards(AuthGuard)
|
||||
async myInfo(@User() user: UserInfo): Promise<UserDto> {
|
||||
return lastValueFrom(this.auth.send('auth.getUserById', { id: user.sub }));
|
||||
}
|
||||
|
||||
@Get('bans')
|
||||
@UseGuards(AuthGuard)
|
||||
async banInfo(@User() user: UserInfo) {
|
||||
return lastValueFrom(this.auth.send('auth.getUserBans', { user }));
|
||||
}
|
||||
}
|
||||
|
33
apps/freeblox-web-service/src/services/auth/dtos/user.dto.ts
Normal file
33
apps/freeblox-web-service/src/services/auth/dtos/user.dto.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class UserDto {
|
||||
@ApiProperty()
|
||||
id: string;
|
||||
|
||||
@ApiProperty()
|
||||
username: string;
|
||||
|
||||
@ApiProperty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
phone: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
country: string;
|
||||
|
||||
@ApiProperty()
|
||||
language: string;
|
||||
|
||||
@ApiProperty({ nullable: true })
|
||||
displayName: string;
|
||||
|
||||
@ApiProperty()
|
||||
verified: boolean;
|
||||
|
||||
@ApiProperty()
|
||||
activated: boolean;
|
||||
|
||||
@ApiProperty({ type: Date })
|
||||
loginAt: string;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { Exclude } from 'class-transformer';
|
||||
import { CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
import { Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
export class MetaEntity {
|
||||
@CreateDateColumn({ name: 'created_at' })
|
||||
@ -10,3 +10,13 @@ export class MetaEntity {
|
||||
@Exclude()
|
||||
updatedAt: Date;
|
||||
}
|
||||
|
||||
export class UserMetaEntity extends MetaEntity {
|
||||
@Column({ name: 'created_by' })
|
||||
@Exclude()
|
||||
createdBy: string;
|
||||
|
||||
@Column({ name: 'updated_by' })
|
||||
@Exclude()
|
||||
updatedBy: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user