From 1b5917f651eba3c07478e181ed1b32ec427fb7ea Mon Sep 17 00:00:00 2001 From: N08I40K Date: Sun, 26 Jan 2025 01:14:46 +0400 Subject: [PATCH] Add VKID OAuth integration and constants - Add VKIDModule - Define vkIdConstants in constants.ts - Create OAuthRequestDto for VKID - Create OAuthResponseDto for VKID --- src/app.module.ts | 2 ++ src/contants.ts | 5 ++++ src/vkid/dto/oauth.request.dto.ts | 12 +++++++++ src/vkid/dto/oauth.response.dto.ts | 11 ++++++++ src/vkid/vkid.controller.spec.ts | 18 +++++++++++++ src/vkid/vkid.controller.ts | 43 ++++++++++++++++++++++++++++++ src/vkid/vkid.module.ts | 11 ++++++++ src/vkid/vkid.service.spec.ts | 18 +++++++++++++ src/vkid/vkid.service.ts | 36 +++++++++++++++++++++++++ 9 files changed, 156 insertions(+) create mode 100644 src/vkid/dto/oauth.request.dto.ts create mode 100644 src/vkid/dto/oauth.response.dto.ts create mode 100644 src/vkid/vkid.controller.spec.ts create mode 100644 src/vkid/vkid.controller.ts create mode 100644 src/vkid/vkid.module.ts create mode 100644 src/vkid/vkid.service.spec.ts create mode 100644 src/vkid/vkid.service.ts diff --git a/src/app.module.ts b/src/app.module.ts index b0e9eec..33511c8 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -4,6 +4,7 @@ import { UsersModule } from "./users/users.module"; import { ScheduleModule } from "./schedule/schedule.module"; import { CacheModule } from "@nestjs/cache-manager"; import { FirebaseAdminModule } from "./firebase-admin/firebase-admin.module"; +import { VKIDModule } from "./vkid/vkid.module"; @Module({ imports: [ @@ -12,6 +13,7 @@ import { FirebaseAdminModule } from "./firebase-admin/firebase-admin.module"; ScheduleModule, CacheModule.register({ ttl: 5 * 60 * 1000, isGlobal: true }), FirebaseAdminModule, + VKIDModule, ], providers: [], }) diff --git a/src/contants.ts b/src/contants.ts index bab9a0b..d4b14db 100644 --- a/src/contants.ts +++ b/src/contants.ts @@ -3,6 +3,11 @@ import * as process from "node:process"; configDotenv(); +export const vkIdConstants = { + clientId: +process.env.VKID_CLIENT_ID, + redirectUri: process.env.VKID_REDIRECT_URI, +}; + export const jwtConstants = { secret: process.env.JWT_SECRET, }; diff --git a/src/vkid/dto/oauth.request.dto.ts b/src/vkid/dto/oauth.request.dto.ts new file mode 100644 index 0000000..4a9a1ad --- /dev/null +++ b/src/vkid/dto/oauth.request.dto.ts @@ -0,0 +1,12 @@ +import { IsString } from "class-validator"; + +export default class OAuthRequestDto { + @IsString() + code: string; + + @IsString() + codeVerifier: string; + + @IsString() + deviceId: string; +} diff --git a/src/vkid/dto/oauth.response.dto.ts b/src/vkid/dto/oauth.response.dto.ts new file mode 100644 index 0000000..b657dee --- /dev/null +++ b/src/vkid/dto/oauth.response.dto.ts @@ -0,0 +1,11 @@ +import { IsString } from "class-validator"; +import { + ClassTransformerCtor, + Ctor, +} from "../../utility/class-trasformer/class-transformer-ctor"; + +@ClassTransformerCtor() +export default class OAuthResponseDto extends Ctor { + @IsString() + accessToken: string; +} diff --git a/src/vkid/vkid.controller.spec.ts b/src/vkid/vkid.controller.spec.ts new file mode 100644 index 0000000..904f609 --- /dev/null +++ b/src/vkid/vkid.controller.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VKIDController } from './vkid.controller'; + +describe('VkidController', () => { + let controller: VKIDController; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [VKIDController], + }).compile(); + + controller = module.get(VKIDController); + }); + + it('should be defined', () => { + expect(controller).toBeDefined(); + }); +}); diff --git a/src/vkid/vkid.controller.ts b/src/vkid/vkid.controller.ts new file mode 100644 index 0000000..4ce4ad1 --- /dev/null +++ b/src/vkid/vkid.controller.ts @@ -0,0 +1,43 @@ +import { + Body, + Controller, + HttpStatus, + NotAcceptableException, + Post, +} from "@nestjs/common"; +import { ApiBody, ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger"; +import { VKIDService } from "./vkid.service"; +import OAuthRequestDto from "./dto/oauth.request.dto"; +import OAuthResponseDto from "./dto/oauth.response.dto"; +import { ResultDto } from "../utility/validation/class-validator.interceptor"; + +@ApiTags("v1/vkid") +@Controller({ path: "vkid", version: "1" }) +export class VKIDController { + constructor(private readonly vkidService: VKIDService) {} + + @ApiOperation({ + summary: "OAuth аутентификация", + description: "Аутентификация пользователя с использованием OAuth", + }) + @ApiBody({ type: OAuthRequestDto, description: "Данные для OAuth запроса" }) + @ApiResponse({ + status: HttpStatus.OK, + type: OAuthResponseDto, + description: "OAuth аутентификация прошла успешно", + }) + @ApiResponse({ + status: HttpStatus.NOT_ACCEPTABLE, + description: "Ошибка в процессе OAuth", + }) + @ResultDto(OAuthResponseDto) + @Post("oauth") + async oauth( + @Body() oAuthRequestDto: OAuthRequestDto, + ): Promise { + const result = await this.vkidService.oauth(oAuthRequestDto); + if (!result) throw new NotAcceptableException("OAuth process failed"); + + return result; + } +} diff --git a/src/vkid/vkid.module.ts b/src/vkid/vkid.module.ts new file mode 100644 index 0000000..3937548 --- /dev/null +++ b/src/vkid/vkid.module.ts @@ -0,0 +1,11 @@ +import { Module } from "@nestjs/common"; +import { VKIDService } from "./vkid.service"; +import { VKIDController } from "./vkid.controller"; +import { UsersModule } from "../users/users.module"; + +@Module({ + imports: [UsersModule], + providers: [VKIDService], + controllers: [VKIDController], +}) +export class VKIDModule {} diff --git a/src/vkid/vkid.service.spec.ts b/src/vkid/vkid.service.spec.ts new file mode 100644 index 0000000..152a3fc --- /dev/null +++ b/src/vkid/vkid.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { VKIDService } from './vkid.service'; + +describe('VkidService', () => { + let service: VKIDService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [VKIDService], + }).compile(); + + service = module.get(VKIDService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/src/vkid/vkid.service.ts b/src/vkid/vkid.service.ts new file mode 100644 index 0000000..06cc44d --- /dev/null +++ b/src/vkid/vkid.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from "@nestjs/common"; +import OAuthRequestDto from "./dto/oauth.request.dto"; +import OAuthResponseDto from "./dto/oauth.response.dto"; +import axios from "axios"; +import { vkIdConstants } from "../contants"; +import { v4 as uuidv4 } from "uuid"; + +@Injectable() +export class VKIDService { + async oauth(oauthRequestDto: OAuthRequestDto): Promise { + const state = uuidv4(); + + const response = await axios.post( + "https://id.vk.com/oauth2/auth", + "grant_type=authorization_code&" + + `client_id=${vkIdConstants.clientId}&` + + `state=${state}&` + + `code_verifier=${oauthRequestDto.codeVerifier}&` + + `code=${oauthRequestDto.code}&` + + `device_id=${oauthRequestDto.deviceId}&` + + `redirect_uri=${vkIdConstants.redirectUri}`, + ); + + const accessToken: string = (response.data as { access_token: string }) + .access_token; + + if (!accessToken) { + console.error(response.data); + return null; + } + + return new OAuthResponseDto({ + accessToken: accessToken, + }); + } +}