implement refresh token
This commit is contained in:
parent
d81fc53819
commit
529e4a96d7
@ -13,6 +13,11 @@ export class AuthController {
|
|||||||
return this.authService.login(body);
|
return this.authService.login(body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MessagePattern('auth.loginByRefreshToken')
|
||||||
|
loginByRefreshToken({ token }: { token: string }) {
|
||||||
|
return this.authService.loginByRefreshToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
@MessagePattern('auth.verify')
|
@MessagePattern('auth.verify')
|
||||||
verify({ token }: { token: string }) {
|
verify({ token }: { token: string }) {
|
||||||
return this.authService.verifyToken(token);
|
return this.authService.verifyToken(token);
|
||||||
|
@ -17,6 +17,7 @@ import { BanService } from './services/ban.service';
|
|||||||
import { PrivilegeEntity } from './database/entities/privilege.entity';
|
import { PrivilegeEntity } from './database/entities/privilege.entity';
|
||||||
import { RoleService } from './services/role.service';
|
import { RoleService } from './services/role.service';
|
||||||
import { RoleEntity } from './database/entities/role.entity';
|
import { RoleEntity } from './database/entities/role.entity';
|
||||||
|
import { RefreshService } from './services/refresh.service';
|
||||||
|
|
||||||
const entities = [
|
const entities = [
|
||||||
UserEntity,
|
UserEntity,
|
||||||
@ -50,6 +51,7 @@ const entities = [
|
|||||||
OTPService,
|
OTPService,
|
||||||
BanService,
|
BanService,
|
||||||
RoleService,
|
RoleService,
|
||||||
|
RefreshService,
|
||||||
AuthService,
|
AuthService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
@ -2,8 +2,11 @@ import { registerAs } from '@nestjs/config';
|
|||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
|
|
||||||
export const security = registerAs('security', () => ({
|
export const security = registerAs('security', () => ({
|
||||||
algorithm: String(process.env.JWT_ALGORITHM || 'RS512'),
|
jwtAlgorithm: String(process.env.JWT_ALGORITHM || 'RS512'),
|
||||||
tokenExpiry: Number(process.env.JWT_EXPIRY) || 60 * 60,
|
jwtTokenExpiry: Number(process.env.JWT_EXPIRY) || 60 * 60,
|
||||||
|
refreshTokenExpiry: Number(process.env.REFRESH_EXPIRY) || 30 * 60 * 60,
|
||||||
privateKeyPath: resolve(String(process.env.PRIVATE_KEY_FILE)),
|
privateKeyPath: resolve(String(process.env.PRIVATE_KEY_FILE)),
|
||||||
publicKeyPath: resolve(String(process.env.PUBLIC_KEY_FILE)),
|
publicKeyPath: resolve(String(process.env.PUBLIC_KEY_FILE)),
|
||||||
|
secretKey: String(process.env.SECRET_KEY),
|
||||||
|
secretAlgorithm: String(process.env.REFRESH_ALGORITHM || 'A256CBC-HS512'),
|
||||||
}));
|
}));
|
||||||
|
@ -23,13 +23,17 @@ export class UserTokenEntity {
|
|||||||
@Column({ type: 'enum', enum: UserTokenType, nullable: false })
|
@Column({ type: 'enum', enum: UserTokenType, nullable: false })
|
||||||
type: UserTokenType;
|
type: UserTokenType;
|
||||||
|
|
||||||
@Column({ type: 'timestamp', nullable: true })
|
@Column({ type: 'timestamp', name: 'expires_at', nullable: true })
|
||||||
expires_at: Date;
|
expiresAt: Date;
|
||||||
|
|
||||||
@CreateDateColumn()
|
@CreateDateColumn({ name: 'created_at' })
|
||||||
created_at: Date;
|
createdAt: Date;
|
||||||
|
|
||||||
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE' })
|
@ManyToOne(() => UserEntity, { onDelete: 'CASCADE' })
|
||||||
@JoinColumn({ name: 'user_id' })
|
@JoinColumn({ name: 'user_id' })
|
||||||
user: UserEntity;
|
user: UserEntity;
|
||||||
|
|
||||||
|
@ManyToOne(() => UserTokenEntity, { onDelete: 'CASCADE', nullable: true })
|
||||||
|
@JoinColumn({ name: 'previous_id' })
|
||||||
|
previous: UserTokenEntity;
|
||||||
}
|
}
|
||||||
|
@ -17,15 +17,21 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
'totp',
|
'totp',
|
||||||
'public_key',
|
'public_key',
|
||||||
'recovery',
|
'recovery',
|
||||||
|
'refresh',
|
||||||
])
|
])
|
||||||
.notNullable();
|
.notNullable();
|
||||||
|
|
||||||
table.uuid('user_id').notNullable();
|
table.uuid('user_id').notNullable();
|
||||||
|
table.integer('previous_id').nullable().unsigned();
|
||||||
|
|
||||||
table.timestamp('expires_at').nullable();
|
table.timestamp('expires_at').nullable();
|
||||||
table.timestamp('created_at').notNullable().defaultTo('now()');
|
table.timestamp('created_at').notNullable().defaultTo('now()');
|
||||||
|
|
||||||
table.foreign('user_id').references('users.id').onDelete('CASCADE');
|
table.foreign('user_id').references('users.id').onDelete('CASCADE');
|
||||||
|
table
|
||||||
|
.foreign('previous_id')
|
||||||
|
.references('user_tokens.id')
|
||||||
|
.onDelete('CASCADE');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,12 @@ export const keysProviders = [
|
|||||||
.readFile(config.get('security.privateKeyPath'), 'utf-8')
|
.readFile(config.get('security.privateKeyPath'), 'utf-8')
|
||||||
.then((key) => jose.importPKCS8(key, 'RS512')),
|
.then((key) => jose.importPKCS8(key, 'RS512')),
|
||||||
},
|
},
|
||||||
|
<FactoryProvider>{
|
||||||
|
provide: 'APP_SECRET_KEY',
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: async (config: ConfigService) =>
|
||||||
|
jose.base64url.decode(config.get('security.secretKey')),
|
||||||
|
},
|
||||||
<FactoryProvider>{
|
<FactoryProvider>{
|
||||||
provide: 'APP_PUBLIC_KEY',
|
provide: 'APP_PUBLIC_KEY',
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
|
@ -16,6 +16,7 @@ import { BanService } from './ban.service';
|
|||||||
import { UserInfo } from '@freeblox/shared';
|
import { UserInfo } from '@freeblox/shared';
|
||||||
import { RoleService } from './role.service';
|
import { RoleService } from './role.service';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { RefreshService } from './refresh.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
@ -24,6 +25,7 @@ export class AuthService {
|
|||||||
private readonly otpService: OTPService,
|
private readonly otpService: OTPService,
|
||||||
private readonly banService: BanService,
|
private readonly banService: BanService,
|
||||||
private readonly roleService: RoleService,
|
private readonly roleService: RoleService,
|
||||||
|
private readonly refreshService: RefreshService,
|
||||||
@InjectRepository(UserEntity)
|
@InjectRepository(UserEntity)
|
||||||
private readonly userRepository: Repository<UserEntity>,
|
private readonly userRepository: Repository<UserEntity>,
|
||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
@ -82,25 +84,14 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for active ban
|
// Issue access token
|
||||||
const bans = await this.banService.getActiveBansForUser(userEntity);
|
const exp = this.config.get('security.jwtTokenExpiry');
|
||||||
const banned = !!bans.length;
|
const issuedToken = await this.issueToken(userEntity);
|
||||||
|
|
||||||
// Get all privileges applicable to user
|
// Issue refresh token
|
||||||
const privileges = await this.roleService.getUserPrivileges(userEntity);
|
const refreshToken = await this.refreshService.issueRefreshToken(
|
||||||
|
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
|
|
||||||
? []
|
|
||||||
: privileges.map((privilege) => privilege.privilege),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Set login time to now
|
// Set login time to now
|
||||||
await this.userRepository.update(
|
await this.userRepository.update(
|
||||||
@ -110,6 +101,28 @@ export class AuthService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
token: issuedToken,
|
token: issuedToken,
|
||||||
|
refresh: refreshToken,
|
||||||
|
expires_in: exp,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async loginByRefreshToken(token: string) {
|
||||||
|
const refreshToken = await this.refreshService.useRefreshToken(token);
|
||||||
|
const userEntity = refreshToken.user;
|
||||||
|
|
||||||
|
// Issue new access token
|
||||||
|
const exp = this.config.get('security.jwtTokenExpiry');
|
||||||
|
const issuedToken = await this.issueToken(userEntity);
|
||||||
|
|
||||||
|
// Set login time to now
|
||||||
|
await this.userRepository.update(
|
||||||
|
{ id: userEntity.id },
|
||||||
|
{ loginAt: new Date() },
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: issuedToken,
|
||||||
|
refresh: refreshToken.token,
|
||||||
expires_in: exp,
|
expires_in: exp,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -164,4 +177,25 @@ export class AuthService {
|
|||||||
const bans = await this.banService.getAllBansForUser(user);
|
const bans = await this.banService.getAllBansForUser(user);
|
||||||
return instanceToPlain(bans);
|
return instanceToPlain(bans);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async issueToken(user: UserEntity) {
|
||||||
|
// Check for active ban
|
||||||
|
const bans = await this.banService.getActiveBansForUser(user);
|
||||||
|
const banned = !!bans.length;
|
||||||
|
|
||||||
|
// Get all privileges applicable to user
|
||||||
|
const privileges = await this.roleService.getUserPrivileges(user);
|
||||||
|
|
||||||
|
// Issue new token
|
||||||
|
return this.jwtService.sign({
|
||||||
|
sub: user.id,
|
||||||
|
username: user.username,
|
||||||
|
display_name: user.displayName,
|
||||||
|
language: user.language,
|
||||||
|
banned: banned,
|
||||||
|
privileges: banned
|
||||||
|
? []
|
||||||
|
: privileges.map((privilege) => privilege.privilege),
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,22 @@
|
|||||||
import { ForbiddenException, Inject, Injectable } from '@nestjs/common';
|
import { ForbiddenException, Inject, Injectable } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { JWK, KeyLike, SignJWT, jwtVerify } from 'jose';
|
import { EncryptJWT, JWK, KeyLike, SignJWT, jwtDecrypt, jwtVerify } from 'jose';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class JWTService {
|
export class JWTService {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject('APP_PRIVATE_KEY') private readonly privateKey: KeyLike,
|
@Inject('APP_PRIVATE_KEY') private readonly privateKey: KeyLike,
|
||||||
|
@Inject('APP_SECRET_KEY') private readonly secretKey: KeyLike,
|
||||||
@Inject('APP_PUBLIC_KEY') private readonly publicKey: KeyLike,
|
@Inject('APP_PUBLIC_KEY') private readonly publicKey: KeyLike,
|
||||||
@Inject('APP_PUBLIC_KEY_JWK') private readonly publicKeyJWK: JWK,
|
@Inject('APP_PUBLIC_KEY_JWK') private readonly publicKeyJWK: JWK,
|
||||||
private readonly config: ConfigService,
|
private readonly config: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async sign(data: Record<string, unknown>, audience = 'urn:freeblox:service') {
|
async sign(data: Record<string, unknown>, audience = 'urn:freeblox:service') {
|
||||||
const alg = this.config.get('security.algorithm');
|
const alg = this.config.get('security.jwtAlgorithm');
|
||||||
const exp =
|
const exp =
|
||||||
this.config.get('security.tokenExpiry') + Math.floor(Date.now() / 1000);
|
this.config.get('security.jwtTokenExpiry') +
|
||||||
|
Math.floor(Date.now() / 1000);
|
||||||
const jwt = await new SignJWT(data)
|
const jwt = await new SignJWT(data)
|
||||||
.setProtectedHeader({ alg })
|
.setProtectedHeader({ alg })
|
||||||
.setIssuedAt()
|
.setIssuedAt()
|
||||||
@ -26,7 +28,7 @@ export class JWTService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async verify(jwt: string, audience = 'urn:freeblox:service') {
|
async verify(jwt: string, audience = 'urn:freeblox:service') {
|
||||||
const alg = this.config.get('security.algorithm');
|
const alg = this.config.get('security.jwtAlgorithm');
|
||||||
const { payload, protectedHeader } = await jwtVerify(jwt, this.publicKey, {
|
const { payload, protectedHeader } = await jwtVerify(jwt, this.publicKey, {
|
||||||
issuer: 'urn:freeblox:auth',
|
issuer: 'urn:freeblox:auth',
|
||||||
audience,
|
audience,
|
||||||
@ -35,4 +37,33 @@ export class JWTService {
|
|||||||
throw new ForbiddenException('Provided JWT contains invalid headers.');
|
throw new ForbiddenException('Provided JWT contains invalid headers.');
|
||||||
return payload;
|
return payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async encrypt(
|
||||||
|
data: Record<string, unknown>,
|
||||||
|
audience = 'urn:freeblox:service',
|
||||||
|
) {
|
||||||
|
const alg = this.config.get('security.secretAlgorithm');
|
||||||
|
const exp =
|
||||||
|
this.config.get('security.refreshTokenExpiry') +
|
||||||
|
Math.floor(Date.now() / 1000);
|
||||||
|
const jwt = await new EncryptJWT(data)
|
||||||
|
.setProtectedHeader({ alg: 'dir', enc: alg })
|
||||||
|
.setIssuedAt()
|
||||||
|
.setIssuer('urn:freeblox:auth')
|
||||||
|
.setAudience(audience)
|
||||||
|
.setExpirationTime(exp)
|
||||||
|
.encrypt(this.secretKey);
|
||||||
|
return jwt;
|
||||||
|
}
|
||||||
|
|
||||||
|
async decrypt(jwt: string, audience = 'urn:freeblox:service') {
|
||||||
|
const alg = this.config.get('security.secretAlgorithm');
|
||||||
|
const { payload, protectedHeader } = await jwtDecrypt(jwt, this.secretKey, {
|
||||||
|
issuer: 'urn:freeblox:auth',
|
||||||
|
audience,
|
||||||
|
});
|
||||||
|
if (protectedHeader.enc !== alg)
|
||||||
|
throw new ForbiddenException('Provided JWT contains invalid headers.');
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
120
apps/auth/src/services/refresh.service.ts
Normal file
120
apps/auth/src/services/refresh.service.ts
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { UserTokenEntity } from '../database/entities/user-token.entity';
|
||||||
|
import { JWTService } from './jwt.service';
|
||||||
|
import { UserEntity } from '../database/entities/user.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { UserTokenType, generateString } from '@freeblox/shared';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RefreshService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(UserTokenEntity)
|
||||||
|
private readonly userTokenRepository: Repository<UserTokenEntity>,
|
||||||
|
private readonly jwt: JWTService,
|
||||||
|
private readonly config: ConfigService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find new refresh token for old token
|
||||||
|
* @param token Old token
|
||||||
|
* @returns New Token
|
||||||
|
*/
|
||||||
|
async findNewIteration(token: UserTokenEntity) {
|
||||||
|
return this.userTokenRepository.findOne({
|
||||||
|
where: {
|
||||||
|
previous: { id: token.id },
|
||||||
|
type: UserTokenType.REFRESH,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consume a refresh token.
|
||||||
|
* @param token Refresh token
|
||||||
|
* @returns New refresh token
|
||||||
|
*/
|
||||||
|
async useRefreshToken(token: string) {
|
||||||
|
if (!token) throw new UnauthorizedException('Invalid refresh token');
|
||||||
|
const decrypted = await this.jwt.decrypt(token);
|
||||||
|
|
||||||
|
if (!decrypted.token || !decrypted.sub)
|
||||||
|
throw new UnauthorizedException('Invalid refresh token');
|
||||||
|
|
||||||
|
const tokenEntity = await this.userTokenRepository.findOneOrFail({
|
||||||
|
where: {
|
||||||
|
token: decrypted.token as string,
|
||||||
|
type: UserTokenType.REFRESH,
|
||||||
|
},
|
||||||
|
relations: ['user'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (decrypted.sub !== tokenEntity.user.id)
|
||||||
|
throw new UnauthorizedException('Invalid refresh token');
|
||||||
|
|
||||||
|
// Using an expired refresh token
|
||||||
|
if (tokenEntity.expiresAt.getTime() < Date.now()) {
|
||||||
|
const newIteration = await this.findNewIteration(tokenEntity);
|
||||||
|
// We have already issued a new refresh token, this is probably stolen
|
||||||
|
// ..so we delete the whole tree.
|
||||||
|
if (newIteration) {
|
||||||
|
await this.userTokenRepository.remove(tokenEntity);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new UnauthorizedException('Invalid refresh token');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark old token as expired
|
||||||
|
await this.userTokenRepository.update(
|
||||||
|
{ id: tokenEntity.id },
|
||||||
|
{ expiresAt: new Date() },
|
||||||
|
);
|
||||||
|
|
||||||
|
// Issue a new refresh token
|
||||||
|
const newToken = await this.issueRefreshToken(
|
||||||
|
tokenEntity.user,
|
||||||
|
tokenEntity,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: newToken,
|
||||||
|
user: tokenEntity.user,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Issue a refresh token.
|
||||||
|
* @param user User
|
||||||
|
* @param previous Previous refresh token
|
||||||
|
* @returns New Refresh token
|
||||||
|
*/
|
||||||
|
async issueRefreshToken(user: UserEntity, previous?: UserTokenEntity) {
|
||||||
|
const newRefreshToken = await this.createRefreshToken(user);
|
||||||
|
const expiry = new Date(
|
||||||
|
Date.now() + this.config.get('security.refreshTokenExpiry') * 1000,
|
||||||
|
);
|
||||||
|
await this.userTokenRepository.save({
|
||||||
|
token: newRefreshToken.token,
|
||||||
|
user,
|
||||||
|
type: UserTokenType.REFRESH,
|
||||||
|
expiresAt: expiry,
|
||||||
|
previous,
|
||||||
|
});
|
||||||
|
return newRefreshToken.encrypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate refresh token and encrypt it
|
||||||
|
* @param user User to issue the token to
|
||||||
|
* @returns Encrypted and unencrypted tokens
|
||||||
|
*/
|
||||||
|
private async createRefreshToken(user: UserEntity) {
|
||||||
|
const token = generateString(512);
|
||||||
|
const encrypted = await this.jwt.encrypt({ sub: user.id, token });
|
||||||
|
return {
|
||||||
|
encrypted,
|
||||||
|
token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -9,13 +9,20 @@ import {
|
|||||||
UseInterceptors,
|
UseInterceptors,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { ClientProxy } from '@nestjs/microservices';
|
import { ClientProxy } from '@nestjs/microservices';
|
||||||
import { ApiBearerAuth, ApiOkResponse, ApiTags } from '@nestjs/swagger';
|
import {
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiOkResponse,
|
||||||
|
ApiOperation,
|
||||||
|
ApiTags,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
import { LoginDto } from './dtos/login.dto';
|
import { LoginDto } from './dtos/login.dto';
|
||||||
import { User } from '../../decorators/user.decorator';
|
import { User } from '../../decorators/user.decorator';
|
||||||
import { UserInfo } from '@freeblox/shared';
|
import { UserInfo } from '@freeblox/shared';
|
||||||
import { lastValueFrom } from 'rxjs';
|
import { lastValueFrom } from 'rxjs';
|
||||||
import { AuthGuard } from '../../guards/auth.guard';
|
import { AuthGuard } from '../../guards/auth.guard';
|
||||||
import { UserDto } from './dtos/user.dto';
|
import { UserDto } from './dtos/user.dto';
|
||||||
|
import { LoginResponseDto } from './dtos/login-response.dto';
|
||||||
|
import { LoginByRefreshTokenDto } from './dtos/login-refresh-token.dto';
|
||||||
|
|
||||||
@Controller({
|
@Controller({
|
||||||
version: '1',
|
version: '1',
|
||||||
@ -28,11 +35,21 @@ export class AuthController {
|
|||||||
constructor(@Inject('auth') private auth: ClientProxy) {}
|
constructor(@Inject('auth') private auth: ClientProxy) {}
|
||||||
|
|
||||||
@Post('login')
|
@Post('login')
|
||||||
|
@ApiOperation({ summary: 'Login by username or email and password' })
|
||||||
|
@ApiOkResponse({ type: LoginResponseDto })
|
||||||
async login(@Body() body: LoginDto) {
|
async login(@Body() body: LoginDto) {
|
||||||
return this.auth.send('auth.login', { body });
|
return this.auth.send('auth.login', { body });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('refresh')
|
||||||
|
@ApiOperation({ summary: 'Login by refresh token' })
|
||||||
|
@ApiOkResponse({ type: LoginResponseDto })
|
||||||
|
async refresh(@Body() body: LoginByRefreshTokenDto) {
|
||||||
|
return this.auth.send('auth.loginByRefreshToken', { token: body.token });
|
||||||
|
}
|
||||||
|
|
||||||
@Get('me')
|
@Get('me')
|
||||||
|
@ApiOperation({ summary: 'Current user information' })
|
||||||
@ApiOkResponse({ type: UserDto })
|
@ApiOkResponse({ type: UserDto })
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async myInfo(@User() user: UserInfo): Promise<UserDto> {
|
async myInfo(@User() user: UserInfo): Promise<UserDto> {
|
||||||
@ -40,6 +57,7 @@ export class AuthController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('bans')
|
@Get('bans')
|
||||||
|
@ApiOperation({ summary: 'Current user ban history' })
|
||||||
@UseGuards(AuthGuard)
|
@UseGuards(AuthGuard)
|
||||||
async banInfo(@User() user: UserInfo) {
|
async banInfo(@User() user: UserInfo) {
|
||||||
return lastValueFrom(this.auth.send('auth.getUserBans', { user }));
|
return lastValueFrom(this.auth.send('auth.getUserBans', { user }));
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsString } from 'class-validator';
|
||||||
|
|
||||||
|
export class LoginByRefreshTokenDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsString()
|
||||||
|
token: string;
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class LoginResponseDto {
|
||||||
|
@ApiProperty()
|
||||||
|
token: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
refresh: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
expires_in: number;
|
||||||
|
}
|
@ -37,6 +37,7 @@ services:
|
|||||||
- POSTGRES_PASSWORD=FREEBLOXDataBaseDEV@123
|
- POSTGRES_PASSWORD=FREEBLOXDataBaseDEV@123
|
||||||
- PRIVATE_KEY_FILE=private/jwt.private.pem
|
- PRIVATE_KEY_FILE=private/jwt.private.pem
|
||||||
- PUBLIC_KEY_FILE=private/jwt.public.pem
|
- PUBLIC_KEY_FILE=private/jwt.public.pem
|
||||||
|
- SECRET_KEY=mkt9Hngcmhbd9wX4EzGbGysDWzCo793XvvswOS+wolTVM83I1K2b/j41WwsCfsv1iS901N2rTHu2hZHbsYO3RQ==
|
||||||
volumes:
|
volumes:
|
||||||
- ./apps:/usr/src/app/apps
|
- ./apps:/usr/src/app/apps
|
||||||
- ./libs:/usr/src/app/libs
|
- ./libs:/usr/src/app/libs
|
||||||
|
@ -8,4 +8,5 @@ export enum UserTokenType {
|
|||||||
TOTP = 'totp',
|
TOTP = 'totp',
|
||||||
PUBLIC_KEY = 'public_key',
|
PUBLIC_KEY = 'public_key',
|
||||||
RECOVERY = 'recovery',
|
RECOVERY = 'recovery',
|
||||||
|
REFRESH = 'refresh',
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user