Энд-поинт schedule/get-group-names теперь не требует авторизации (для формы регистрации).

Энд-поинт schedule/get-group теперь не требует указания группы. Она берётся из данных пользователя.

Энд-поинт auth/sign-in теперь может возвращать группу пользователя начиная с версии 1.
This commit is contained in:
2024-10-13 17:59:48 +04:00
parent a4d7bb9cdc
commit c594039ca2
10 changed files with 98 additions and 24 deletions

View File

@@ -2,3 +2,4 @@ import { Reflector } from "@nestjs/core";
import { UserRoleDto } from "../dto/user.dto";
export const AuthRoles = Reflector.createDecorator<UserRoleDto[]>();
export const AuthUnauthorized = Reflector.createDecorator<true>();

View File

@@ -26,10 +26,13 @@ import {
ChangePasswordReqDto,
UpdateTokenReqDto,
UpdateTokenResDto,
SignInResDtoV0,
SignInResDtoV1,
} from "../dto/auth.dto";
import { ResultDto } from "../utility/validation/class-validator.interceptor";
import { ScheduleService } from "../schedule/schedule.service";
import { UserToken } from "./auth.decorator";
import { ResponseVersion } from "../version/response-version.decorator";
@Controller("api/v1/auth")
export class AuthController {
@@ -39,21 +42,30 @@ export class AuthController {
) {}
@ApiExtraModels(SignInReqDto)
@ApiExtraModels(SignInResDto)
@ApiExtraModels(SignInResDtoV0)
@ApiExtraModels(SignInResDtoV1)
@ApiOperation({ summary: "Авторизация по логину и паролю", tags: ["auth"] })
@ApiBody({ schema: refs(SignInReqDto)[0] })
@ApiOkResponse({
description: "Авторизация прошла успешно",
schema: refs(SignInResDto)[0],
schema: refs(SignInResDtoV0)[0],
})
@ApiOkResponse({
description: "Авторизация прошла успешно",
schema: refs(SignInResDtoV1)[0],
})
@ApiUnauthorizedResponse({
description: "Некорректное имя пользователя или пароль",
})
@ResultDto(SignInResDto)
@ResultDto([SignInResDtoV0, SignInResDtoV1])
@HttpCode(HttpStatus.OK)
@Post("sign-in")
signIn(@Body() signInDto: SignInReqDto) {
return this.authService.signIn(signInDto);
async signIn(
@Body() signInDto: SignInReqDto,
@ResponseVersion() responseVersion: number,
): Promise<SignInResDtoV1 | SignInResDtoV0> {
const data = await this.authService.signIn(signInDto);
return SignInResDto.stripVersion(data, responseVersion);
}
@ApiExtraModels(SignUpReqDto)

View File

@@ -9,7 +9,7 @@ import { JwtService } from "@nestjs/jwt";
import { Request } from "express";
import { UsersService } from "../users/users.service";
import { Reflector } from "@nestjs/core";
import { AuthRoles } from "../auth-role/auth-role.decorator";
import { AuthRoles, AuthUnauthorized } from "../auth-role/auth-role.decorator";
import { isJWT } from "class-validator";
@Injectable()
@@ -30,6 +30,9 @@ export class AuthGuard implements CanActivate {
}
async canActivate(context: ExecutionContext): Promise<boolean> {
if (this.reflector.get(AuthUnauthorized, context.getHandler()))
return true;
const request = context.switchToHttp().getRequest();
const token = AuthGuard.extractTokenFromRequest(request);

View File

@@ -115,7 +115,7 @@ export class AuthService {
data: { accessToken: accessToken },
});
return { id: user.id, accessToken: accessToken };
return { id: user.id, accessToken: accessToken, group: user.group };
}
async updateToken(

View File

@@ -5,8 +5,8 @@ import {
PickType,
} from "@nestjs/swagger";
import { UserDto } from "./user.dto";
import { IsString } from "class-validator";
import { Expose } from "class-transformer";
import { IsJWT, IsMongoId, IsString } from "class-validator";
import { Expose, instanceToPlain, plainToClass } from "class-transformer";
// SignIn
export class SignInReqDto extends PickType(UserDto, ["username"]) {
@@ -18,7 +18,50 @@ export class SignInReqDto extends PickType(UserDto, ["username"]) {
password: string;
}
export class SignInResDto extends PickType(UserDto, ["id", "accessToken"]) {}
export class SignInResDtoV0 {
@ApiProperty({
example: "66e1b7e255c5d5f1268cce90",
description: "Идентификатор (ObjectId)",
})
@IsMongoId()
@Expose()
id: string;
@ApiProperty({
example: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
description: "Последний токен доступа",
})
@IsJWT()
@Expose()
accessToken: string;
}
export class SignInResDtoV1 extends SignInResDtoV0 {
@ApiProperty({
example: "ИС-214/23",
description: "Группа",
})
@IsString()
@Expose()
group: string;
}
export class SignInResDto extends SignInResDtoV1 {
public static stripVersion(
instance: SignInResDto,
version: number,
): SignInResDtoV0 | SignInResDtoV1 {
switch (version) {
default:
return instance;
case 0: {
return plainToClass(SignInResDtoV0, instanceToPlain(instance), {
excludeExtraneousValues: true,
});
}
}
}
}
// SignUp
export class SignUpReqDto extends IntersectionType(
@@ -27,7 +70,10 @@ export class SignUpReqDto extends IntersectionType(
PartialType(PickType(UserDto, ["version"])),
) {}
export class SignUpResDto extends SignInResDto {}
export class SignUpResDto extends PickType(SignInResDto, [
"id",
"accessToken",
]) {}
// Update token
export class UpdateTokenReqDto extends PickType(UserDto, ["accessToken"]) {}

View File

@@ -11,7 +11,7 @@ import {
IsString,
ValidateNested,
} from "class-validator";
import { ApiProperty, OmitType, PickType } from "@nestjs/swagger";
import { ApiProperty, OmitType, PartialType, PickType } from "@nestjs/swagger";
import {
Expose,
instanceToPlain,
@@ -296,7 +296,9 @@ export class ScheduleDto {
lastChangedDays: Array<Array<number>>;
}
export class GroupScheduleRequestDto extends PickType(GroupDto, ["name"]) {}
export class GroupScheduleReqDto extends PartialType(
PickType(GroupDto, ["name"]),
) {}
export class ScheduleGroupsDto {
@ApiProperty({

View File

@@ -14,7 +14,7 @@ import {
CacheStatusV0Dto,
CacheStatusV1Dto,
GroupScheduleDto,
GroupScheduleRequestDto,
GroupScheduleReqDto,
ScheduleDto,
ScheduleGroupsDto,
SiteMainPageDto,
@@ -28,7 +28,11 @@ import {
ApiOperation,
refs,
} from "@nestjs/swagger";
import { ClientVersion } from "../version/client-version.decorator";
import { ResponseVersion } from "../version/response-version.decorator";
import { AuthRoles, AuthUnauthorized } from "../auth-role/auth-role.decorator";
import { UserDto, UserRoleDto } from "../dto/user.dto";
import { UserToken } from "../auth/auth.decorator";
import { UserFromTokenPipe } from "../auth/auth.pipe";
@Controller("api/v1/schedule")
@UseGuards(AuthGuard)
@@ -36,12 +40,16 @@ export class ScheduleController {
constructor(private readonly scheduleService: ScheduleService) {}
@ApiExtraModels(ScheduleDto)
@ApiOperation({ summary: "Получение расписания", tags: ["schedule"] })
@ApiOperation({
summary: "Получение расписания",
tags: ["schedule", "admin"],
})
@ApiOkResponse({
description: "Расписание получено успешно",
schema: refs(ScheduleDto)[0],
})
@ResultDto(ScheduleDto)
@AuthRoles([UserRoleDto.ADMIN])
@HttpCode(HttpStatus.OK)
@Get("get")
async getSchedule(): Promise<ScheduleDto> {
@@ -62,9 +70,10 @@ export class ScheduleController {
@HttpCode(HttpStatus.OK)
@Post("get-group")
async getGroupSchedule(
@Body() groupDto: GroupScheduleRequestDto,
@Body() groupDto: GroupScheduleReqDto,
@UserToken(UserFromTokenPipe) user: UserDto,
): Promise<GroupScheduleDto> {
return await this.scheduleService.getGroup(groupDto.name);
return await this.scheduleService.getGroup(groupDto.name ?? user.group);
}
@ApiExtraModels(ScheduleGroupsDto)
@@ -78,6 +87,7 @@ export class ScheduleController {
})
@ApiNotFoundResponse({ description: "Требуемая группа не найдена" })
@ResultDto(ScheduleGroupsDto)
@AuthUnauthorized()
@HttpCode(HttpStatus.OK)
@Get("get-group-names")
async getGroupNames(): Promise<ScheduleGroupsDto> {
@@ -107,7 +117,7 @@ export class ScheduleController {
@Post("update-site-main-page")
async updateSiteMainPage(
@Body() siteMainPageDto: SiteMainPageDto,
@ClientVersion() version: number,
@ResponseVersion() version: number,
): Promise<CacheStatusV0Dto> {
return CacheStatusDto.stripVersion(
await this.scheduleService.updateSiteMainPage(siteMainPageDto),
@@ -133,7 +143,7 @@ export class ScheduleController {
@HttpCode(HttpStatus.OK)
@Get("cache-status")
getCacheStatus(
@ClientVersion() version: number,
@ResponseVersion() version: number,
): CacheStatusV0Dto | CacheStatusV1Dto {
return CacheStatusDto.stripVersion(
this.scheduleService.getCacheStatus(),

View File

@@ -1,6 +1,6 @@
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const ClientVersion = createParamDecorator(
export const ResponseVersion = createParamDecorator(
(_, context: ExecutionContext) => {
const sourceVersion: string | null = context.switchToHttp().getRequest()
.headers.version;