parent
24885340d9
commit
dab8b2e84b
@ -0,0 +1,122 @@
|
||||
import { BadRequestException, ConflictException, Injectable, UnauthorizedException } from "@nestjs/common";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { UserService } from "../../user/service/user.service";
|
||||
import { DeviceService } from "../../device/service/device.service";
|
||||
import { PrismaService } from "../../Prisma/service/prisma.service";
|
||||
import { PrismaPromise } from "@prisma/client";
|
||||
import { SHA256, UUIDv4 } from "../../const/regex";
|
||||
|
||||
@Injectable()
|
||||
export class TokenService {
|
||||
constructor(private readonly jwt: JwtService,
|
||||
private readonly user: UserService,
|
||||
private readonly device: DeviceService,
|
||||
private readonly prisma: PrismaService) {
|
||||
}
|
||||
|
||||
public async CheckRefresh(params: { refresh_token: string }) {
|
||||
try {
|
||||
await this.CheckRefreshTokenAssert(params);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async GenerateAccess(params: { refresh_token: string }) {
|
||||
const data = await this.CheckRefreshTokenAssert(params);
|
||||
return await this.jwt.signAsync({
|
||||
type: "access",
|
||||
user_uuid: data.user_uuid,
|
||||
device_fingerprint: data.device_fingerprint
|
||||
}, { expiresIn: 600 });
|
||||
}
|
||||
|
||||
public async GenerateRefresh(params: { user_uuid: string, device_fingerprint: string, device_name?: string | null }) {
|
||||
const { is_user_blocked, is_device_blocked } = await this.GenerateRefreshBlocksChecker(params);
|
||||
if (is_user_blocked) throw new ConflictException("The user is blocked");
|
||||
if (is_device_blocked) throw new ConflictException("The device is blocked");
|
||||
return await this.jwt.signAsync({
|
||||
type: "refresh",
|
||||
user_uuid: params.user_uuid,
|
||||
device_fingerprint: params.device_fingerprint
|
||||
}, {
|
||||
expiresIn: "7d"
|
||||
});
|
||||
}
|
||||
|
||||
private async JwtVerifyGuard<T extends object = any>(params: { jwt_token: string }) {
|
||||
try {
|
||||
return await this.jwt.verifyAsync<T>(params.jwt_token);
|
||||
} catch (e) {
|
||||
throw new UnauthorizedException("Invalid token");
|
||||
}
|
||||
}
|
||||
|
||||
private async IsUserAndDeviceBlockedAssert(params: { user_uuid: string, device_fingerprint: string }) {
|
||||
const res = await this.prisma.$transaction([
|
||||
this.user.isBlocked({ uuid: params.user_uuid }),
|
||||
this.device.isBlocked({ fingerprint: params.device_fingerprint }),
|
||||
this.device.getUserUuidAsPrismaPromise({ fingerprint: params.device_fingerprint })
|
||||
]);
|
||||
if (res[0] === null) throw new ConflictException("user with this uuid does not exist");
|
||||
if (res[1] === null || res[2] === null) throw new ConflictException("device with this fingerprint does not exist");
|
||||
if (res[0].isBlocked) throw new ConflictException("The user is blocked");
|
||||
if (res[1].isBlocked) throw new ConflictException("The device is blocked");
|
||||
if (res[2].userUuid !== params.user_uuid) throw new ConflictException("The device is connected to another user");
|
||||
}
|
||||
|
||||
private async CheckRefreshTokenAssert(params: { refresh_token: string }) {
|
||||
const data = await this.JwtVerifyGuard<{ user_uuid: string, device_fingerprint: string, type: string }>({ jwt_token: params.refresh_token });
|
||||
if (typeof data !== "object") throw new BadRequestException("JWT payload does not object");
|
||||
if (typeof data.user_uuid !== "string") throw new BadRequestException("user_uuid field does not string");
|
||||
if (!UUIDv4.test(data.user_uuid)) throw new BadRequestException("user_uuid field does not uuid");
|
||||
if (typeof data.device_fingerprint !== "string") throw new BadRequestException("device_fingerprint field does not string");
|
||||
if (!SHA256.test(data.device_fingerprint)) throw new BadRequestException("device_fingerprint field does not sha256");
|
||||
if (typeof data.type !== "string") throw new BadRequestException("type field does not string");
|
||||
if (data.type !== "refresh") throw new BadRequestException("type field does not equal \"refresh\" value");
|
||||
await this.IsUserAndDeviceBlockedAssert(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
private async GenerateRefreshBlocksChecker(params: { user_uuid: string, device_fingerprint: string, device_name?: string | null }) {
|
||||
const transaction: PrismaPromise<any>[] = [];
|
||||
const user_exists = await this.user.isExists({ uuid: params.user_uuid });
|
||||
const device_exists = await this.device.isExists({ fingerprint: params.device_fingerprint });
|
||||
if (user_exists) {
|
||||
if (device_exists) {
|
||||
if ((await this.device.getUserUuidAsPrismaPromise({ fingerprint: params.device_fingerprint }))?.userUuid !== params.user_uuid)
|
||||
throw new ConflictException("The device is connected to another user");
|
||||
} else {
|
||||
if (params.device_name === null || params.device_name === undefined)
|
||||
throw new ConflictException("The device does not exist, but the new device name is not specified");
|
||||
transaction.push(
|
||||
this.device.register({
|
||||
userUuid: params.user_uuid,
|
||||
fingerprint: params.device_fingerprint,
|
||||
name: params.device_name
|
||||
}));
|
||||
}
|
||||
} else {
|
||||
if (device_exists)
|
||||
throw new ConflictException("The device is connected to another user");
|
||||
if (params.device_name === null || params.device_name === undefined)
|
||||
throw new ConflictException("The device does not exist, but the new device name is not specified");
|
||||
transaction.push(
|
||||
this.user.register({ uuid: params.user_uuid }),
|
||||
this.device.register({
|
||||
userUuid: params.user_uuid,
|
||||
fingerprint: params.device_fingerprint,
|
||||
name: params.device_name
|
||||
}));
|
||||
}
|
||||
transaction.push(
|
||||
this.user.isBlocked({ uuid: params.user_uuid }),
|
||||
this.device.isBlocked({ fingerprint: params.device_fingerprint })
|
||||
);
|
||||
const transaction_res = await this.prisma.$transaction(transaction);
|
||||
const is_user_blocked = transaction_res[transaction_res.length - 2]?.isBlocked as boolean | undefined;
|
||||
const is_device_blocked = transaction_res[transaction_res.length - 1]?.isBlocked as boolean | undefined;
|
||||
return { is_user_blocked, is_device_blocked };
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
import { Body, Controller, Get, HttpCode, Post, ValidationPipe } from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import { TokenService } from "./service/token.service";
|
||||
import { GenerateRefreshDto } from "./dto/generate-refresh.dto";
|
||||
import { RefreshTokenDto } from "./dto/refresh-token.dto";
|
||||
|
||||
@Controller("token")
|
||||
export class TokenController {
|
||||
constructor(private config: ConfigService<{ port: number, private_key: string, public_key: string }>,
|
||||
private token: TokenService) {
|
||||
}
|
||||
|
||||
@Post("generate/refresh")
|
||||
@HttpCode(200)
|
||||
GenerateRefresh(@Body(new ValidationPipe()) params: GenerateRefreshDto) {
|
||||
return this.token.GenerateRefresh(params);
|
||||
}
|
||||
|
||||
@Post("generate/access")
|
||||
@HttpCode(200)
|
||||
GenerateAccess(@Body(new ValidationPipe()) params: RefreshTokenDto) {
|
||||
return this.token.GenerateAccess(params);
|
||||
}
|
||||
|
||||
@Post("is-valid/refresh")
|
||||
@HttpCode(200)
|
||||
CheckRefresh(@Body(new ValidationPipe()) params: RefreshTokenDto) {
|
||||
return this.token.CheckRefresh(params);
|
||||
}
|
||||
|
||||
@Get("public-key")
|
||||
@HttpCode(200)
|
||||
async GetAccessPublicKey() {
|
||||
return this.config.get<string>("public_key");
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { TokenController } from "./token.controller";
|
||||
import { JwtModule } from "@nestjs/jwt";
|
||||
import { ConfigModule, ConfigService } from "@nestjs/config";
|
||||
import { TokenService } from "./service/token.service";
|
||||
import { UserModule } from "../user/user.module";
|
||||
import { DeviceModule } from "../device/device.module";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
UserModule,
|
||||
DeviceModule,
|
||||
JwtModule.registerAsync({
|
||||
imports: [ConfigModule],
|
||||
inject: [ConfigService],
|
||||
useFactory: (configService: ConfigService<{ port: number, private_key: string, public_key: string }>) => ({
|
||||
publicKey: configService.get<string>("public_key"),
|
||||
privateKey: configService.get<string>("private_key"),
|
||||
signOptions: { algorithm: "RS512", issuer: "sc-user-authentication" }
|
||||
})
|
||||
})],
|
||||
controllers: [TokenController],
|
||||
providers: [TokenService]
|
||||
})
|
||||
export class TokenModule {
|
||||
}
|
Loading…
Reference in new issue