mirror of
https://github.com/n08i40k/schedule-parser-next.git
synced 2025-12-06 09:47:46 +03:00
Пред-деплой
This commit is contained in:
@@ -23,11 +23,13 @@ import {
|
||||
SignInResDto,
|
||||
SignUpReqDto,
|
||||
SignUpResDto,
|
||||
UpdateTokenDto,
|
||||
UpdateTokenResultDto,
|
||||
ChangePasswordReqDto,
|
||||
UpdateTokenReqDto,
|
||||
UpdateTokenResDto,
|
||||
} from "../dto/auth.dto";
|
||||
import { ResultDto } from "../utility/validation/class-validator.interceptor";
|
||||
import { ScheduleService } from "../schedule/schedule.service";
|
||||
import { UserToken } from "./auth.decorator";
|
||||
|
||||
@Controller("api/v1/auth")
|
||||
export class AuthController {
|
||||
@@ -82,26 +84,52 @@ export class AuthController {
|
||||
return this.authService.signUp(signUpDto);
|
||||
}
|
||||
|
||||
@ApiExtraModels(UpdateTokenDto)
|
||||
@ApiExtraModels(UpdateTokenResultDto)
|
||||
@ApiExtraModels(UpdateTokenReqDto)
|
||||
@ApiExtraModels(UpdateTokenResDto)
|
||||
@ApiOperation({
|
||||
summary: "Обновление просроченного токена",
|
||||
tags: ["auth"],
|
||||
tags: ["auth", "accessToken"],
|
||||
})
|
||||
@ApiBody({ schema: refs(UpdateTokenDto)[0] })
|
||||
@ApiBody({ schema: refs(UpdateTokenReqDto)[0] })
|
||||
@ApiOkResponse({
|
||||
description: "Токен обновлён успешно",
|
||||
schema: refs(UpdateTokenResultDto)[0],
|
||||
schema: refs(UpdateTokenResDto)[0],
|
||||
})
|
||||
@ApiNotFoundResponse({
|
||||
description: "Передан несуществующий или недействительный токен",
|
||||
})
|
||||
@ResultDto(UpdateTokenResultDto)
|
||||
@ResultDto(UpdateTokenResDto)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("updateToken")
|
||||
updateToken(
|
||||
@Body() updateTokenDto: UpdateTokenDto,
|
||||
): Promise<UpdateTokenResultDto> {
|
||||
@Body() updateTokenDto: UpdateTokenReqDto,
|
||||
): Promise<UpdateTokenResDto> {
|
||||
return this.authService.updateToken(updateTokenDto);
|
||||
}
|
||||
|
||||
@ApiExtraModels(ChangePasswordReqDto)
|
||||
@ApiOperation({
|
||||
summary: "Обновление пароля",
|
||||
tags: ["auth", "password"],
|
||||
})
|
||||
@ApiBody({ schema: refs(ChangePasswordReqDto)[0] })
|
||||
@ApiOkResponse({ description: "Пароль обновлён успешно" })
|
||||
@ApiConflictResponse({ description: "Пароли идентичны" })
|
||||
@ApiUnauthorizedResponse({
|
||||
description:
|
||||
"Передан неверный текущий пароль или запрос был послан без токена",
|
||||
})
|
||||
@ResultDto(null)
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Post("changePassword")
|
||||
async changePassword(
|
||||
@Body() changePasswordReqDto: ChangePasswordReqDto,
|
||||
@UserToken() userToken: string,
|
||||
): Promise<void> {
|
||||
await this.authService
|
||||
.decodeUserToken(userToken)
|
||||
.then((user) =>
|
||||
this.authService.changePassword(user, changePasswordReqDto),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ import { AuthGuard } from "./auth.guard";
|
||||
|
||||
// TODO: Найти применение этой функции
|
||||
// noinspection JSUnusedGlobalSymbols
|
||||
export const UserToken = createParamDecorator((_, context: ExecutionContext) => {
|
||||
return AuthGuard.extractTokenFromRequest(
|
||||
context.switchToHttp().getRequest(),
|
||||
);
|
||||
});
|
||||
export const UserToken = createParamDecorator(
|
||||
(_, context: ExecutionContext) => {
|
||||
return AuthGuard.extractTokenFromRequest(
|
||||
context.switchToHttp().getRequest(),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -15,28 +15,31 @@ export class AuthGuard implements CanActivate {
|
||||
private readonly jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
public static extractTokenFromRequest(req: Request): string | null {
|
||||
public static extractTokenFromRequest(req: Request): string {
|
||||
const [type, token] = req.headers.authorization?.split(" ") ?? [];
|
||||
return type === "Bearer" ? token : null;
|
||||
|
||||
if (type !== "Bearer" || !token || token.length === 0)
|
||||
throw new UnauthorizedException("Не указан токен!");
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const token = AuthGuard.extractTokenFromRequest(request);
|
||||
|
||||
if (!token) throw new UnauthorizedException("Не указан токен!");
|
||||
|
||||
try {
|
||||
if (
|
||||
!(await this.jwtService.verifyAsync(token)) ||
|
||||
!(await this.usersService.contains({ accessToken: token }))
|
||||
) {
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
throw new Error();
|
||||
if (!token)
|
||||
try {
|
||||
if (
|
||||
!(await this.jwtService.verifyAsync(token)) ||
|
||||
!(await this.usersService.contains({ accessToken: token }))
|
||||
) {
|
||||
// noinspection ExceptionCaughtLocallyJS
|
||||
throw new Error();
|
||||
}
|
||||
} catch {
|
||||
throw new UnauthorizedException("Указан неверный токен!");
|
||||
}
|
||||
} catch {
|
||||
throw new UnauthorizedException("Указан неверный токен!");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ import {
|
||||
SignInResDto,
|
||||
SignUpReqDto,
|
||||
SignUpResDto,
|
||||
UpdateTokenDto,
|
||||
UpdateTokenResultDto,
|
||||
ChangePasswordReqDto,
|
||||
UpdateTokenReqDto,
|
||||
UpdateTokenResDto,
|
||||
} from "../dto/auth.dto";
|
||||
import { UsersService } from "../users/users.service";
|
||||
import { genSalt, hash } from "bcrypt";
|
||||
@@ -27,13 +28,30 @@ export class AuthService {
|
||||
private readonly jwtService: JwtService,
|
||||
) {}
|
||||
|
||||
async decodeUserToken(token: string): Promise<UserDto | null> {
|
||||
const jwtUser: { id: string } =
|
||||
async decodeUserToken(token: string): Promise<UserDto> {
|
||||
const jwtUser: { id: string } | null =
|
||||
await this.jwtService.verifyAsync(token);
|
||||
|
||||
return this.usersService
|
||||
if (jwtUser === null) {
|
||||
throw new UnauthorizedException(
|
||||
"Некорректный или недействительный токен",
|
||||
);
|
||||
}
|
||||
|
||||
const user = await this.usersService
|
||||
.findUnique({ id: jwtUser.id })
|
||||
.then((user) => user as UserDto | null);
|
||||
|
||||
if (!user)
|
||||
throw new UnauthorizedException("Не удалось найти пользователя!");
|
||||
|
||||
if (user.accessToken !== token) {
|
||||
throw new UnauthorizedException(
|
||||
"Некорректный или недействительный токен",
|
||||
);
|
||||
}
|
||||
|
||||
return user as UserDto;
|
||||
}
|
||||
|
||||
async signUp(signUpDto: SignUpReqDto): Promise<SignUpResDto> {
|
||||
@@ -99,8 +117,8 @@ export class AuthService {
|
||||
}
|
||||
|
||||
async updateToken(
|
||||
updateTokenDto: UpdateTokenDto,
|
||||
): Promise<UpdateTokenResultDto> {
|
||||
updateTokenDto: UpdateTokenReqDto,
|
||||
): Promise<UpdateTokenResDto> {
|
||||
if (
|
||||
!(await this.jwtService.verifyAsync(updateTokenDto.accessToken, {
|
||||
ignoreExpiration: true,
|
||||
@@ -131,4 +149,24 @@ export class AuthService {
|
||||
|
||||
return { accessToken: accessToken };
|
||||
}
|
||||
|
||||
async changePassword(
|
||||
user: UserDto,
|
||||
changePasswordReqDto: ChangePasswordReqDto,
|
||||
): Promise<void> {
|
||||
const { oldPassword, newPassword } = changePasswordReqDto;
|
||||
|
||||
if (oldPassword == newPassword)
|
||||
throw new ConflictException("Пароли идентичны");
|
||||
|
||||
if (user.password !== (await hash(oldPassword, user.salt)))
|
||||
throw new UnauthorizedException("Передан неверный исходный пароль");
|
||||
|
||||
await this.usersService.update({
|
||||
where: { id: user.id },
|
||||
data: {
|
||||
password: await hash(newPassword, user.salt),
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { ApiProperty, IntersectionType, PickType } from "@nestjs/swagger";
|
||||
import { UserDto } from "./user.dto";
|
||||
import { IsString } from "class-validator";
|
||||
import { Expose } from "class-transformer";
|
||||
|
||||
// SignIn
|
||||
export class SignInReqDto extends PickType(UserDto, ["username"]) {
|
||||
@@ -20,6 +21,25 @@ export class SignUpReqDto extends IntersectionType(
|
||||
export class SignUpResDto extends SignInResDto {}
|
||||
|
||||
// Update token
|
||||
export class UpdateTokenDto extends PickType(UserDto, ["accessToken"]) {}
|
||||
export class UpdateTokenReqDto extends PickType(UserDto, ["accessToken"]) {}
|
||||
|
||||
export class UpdateTokenResultDto extends UpdateTokenDto {}
|
||||
export class UpdateTokenResDto extends UpdateTokenReqDto {}
|
||||
|
||||
// Update password
|
||||
export class ChangePasswordReqDto {
|
||||
@ApiProperty({
|
||||
example: "my-old-password",
|
||||
description: "Старый пароль",
|
||||
})
|
||||
@IsString()
|
||||
@Expose()
|
||||
oldPassword: string;
|
||||
|
||||
@ApiProperty({
|
||||
example: "my-new-password",
|
||||
description: "Новый пароль",
|
||||
})
|
||||
@IsString()
|
||||
@Expose()
|
||||
newPassword: string;
|
||||
}
|
||||
|
||||
@@ -56,11 +56,18 @@ export enum LessonTypeDto {
|
||||
export class LessonDto {
|
||||
@ApiProperty({
|
||||
example: LessonTypeDto.DEFAULT,
|
||||
description: "Тип занятия.",
|
||||
description: "Тип занятия",
|
||||
})
|
||||
@IsEnum(LessonTypeDto)
|
||||
type: LessonTypeDto;
|
||||
|
||||
@ApiProperty({
|
||||
example: 1,
|
||||
description: "Индекс пары, если присутствует",
|
||||
})
|
||||
@IsNumber()
|
||||
defaultIndex: number;
|
||||
|
||||
@ApiProperty({
|
||||
example: "Элементы высшей математики",
|
||||
description: "Название занятия",
|
||||
@@ -95,14 +102,16 @@ export class LessonDto {
|
||||
|
||||
constructor(
|
||||
type: LessonTypeDto,
|
||||
defaultIndex: number,
|
||||
time: LessonTimeDto,
|
||||
name: string,
|
||||
cabinets: Array<string>,
|
||||
teacherNames: Array<string>,
|
||||
) {
|
||||
this.type = type;
|
||||
this.name = name;
|
||||
this.defaultIndex = defaultIndex;
|
||||
this.time = time;
|
||||
this.name = name;
|
||||
this.cabinets = cabinets;
|
||||
this.teacherNames = teacherNames;
|
||||
}
|
||||
|
||||
@@ -25,10 +25,7 @@ export class ScheduleParseResult {
|
||||
export class ScheduleParser {
|
||||
private lastResult: ScheduleParseResult | null = null;
|
||||
|
||||
public constructor(
|
||||
private readonly xlsDownloader: XlsDownloaderBase,
|
||||
private readonly group: string,
|
||||
) {}
|
||||
public constructor(private readonly xlsDownloader: XlsDownloaderBase) {}
|
||||
|
||||
private static getCellName(
|
||||
worksheet: XLSX.Sheet,
|
||||
@@ -162,14 +159,14 @@ export class ScheduleParser {
|
||||
row < daySkeleton.row + rowDistance;
|
||||
++row
|
||||
) {
|
||||
const time = ScheduleParser.getCellName(
|
||||
const time: string | null = ScheduleParser.getCellName(
|
||||
workSheet,
|
||||
row,
|
||||
lessonTimeColumn,
|
||||
)?.replaceAll(" ", "");
|
||||
if (!time || typeof time !== "string") continue;
|
||||
|
||||
const rawName = ScheduleParser.getCellName(
|
||||
const rawName: string | null = ScheduleParser.getCellName(
|
||||
workSheet,
|
||||
row,
|
||||
groupSkeleton.column,
|
||||
@@ -216,6 +213,9 @@ export class ScheduleParser {
|
||||
day.lessons.push(
|
||||
new LessonDto(
|
||||
type,
|
||||
type === LessonTypeDto.DEFAULT
|
||||
? Number.parseInt(time[0])
|
||||
: -1,
|
||||
LessonTimeDto.fromString(
|
||||
type === LessonTypeDto.DEFAULT
|
||||
? time.substring(5)
|
||||
|
||||
@@ -3,7 +3,8 @@ import {
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus, Post,
|
||||
HttpStatus,
|
||||
Post,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { AuthGuard } from "../auth/auth.guard";
|
||||
|
||||
@@ -22,7 +22,6 @@ export class ScheduleService {
|
||||
"https://politehnikum-eng.ru/index/raspisanie_zanjatij/0-409",
|
||||
XlsDownloaderCacheMode.SOFT,
|
||||
),
|
||||
"ИС-214/23",
|
||||
);
|
||||
|
||||
private lastCacheUpdate: Date = new Date(0);
|
||||
@@ -73,7 +72,7 @@ export class ScheduleService {
|
||||
|
||||
async getGroup(group: string): Promise<GroupScheduleDto> {
|
||||
const schedule = await this.getSourceSchedule();
|
||||
console.log(schedule);
|
||||
|
||||
if ((schedule.groups as object)[group] === undefined) {
|
||||
throw new NotFoundException(
|
||||
"Группы с таким названием не существует!",
|
||||
|
||||
@@ -3,7 +3,6 @@ import {
|
||||
Get,
|
||||
HttpCode,
|
||||
HttpStatus,
|
||||
NotFoundException,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { AuthGuard } from "../auth/auth.guard";
|
||||
@@ -22,8 +21,6 @@ export class UsersController {
|
||||
@Get("me")
|
||||
async getMe(@UserToken() token: string): Promise<ClientUserDto> {
|
||||
const userDto = await this.authService.decodeUserToken(token);
|
||||
if (!userDto)
|
||||
throw new NotFoundException("Не удалось найти пользователя!");
|
||||
|
||||
return ClientUserDto.fromUserDto(userDto);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ export class UsersService {
|
||||
async update(params: {
|
||||
where: Prisma.UserWhereUniqueInput;
|
||||
data: Prisma.UserUpdateInput;
|
||||
}): Promise<UserDto | null> {
|
||||
}): Promise<UserDto> {
|
||||
return this.prismaService.user
|
||||
.update(params)
|
||||
.then(UsersService.convertToDto);
|
||||
|
||||
@@ -32,6 +32,8 @@ export class ClassValidatorInterceptor implements NestInterceptor {
|
||||
handler.name,
|
||||
);
|
||||
|
||||
if (classDto === null) return returnValue;
|
||||
|
||||
if (classDto === undefined) {
|
||||
console.warn(
|
||||
`Undefined DTO type for function \"${cls.name}::${handler.name}\"!`,
|
||||
|
||||
Reference in New Issue
Block a user