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