From 428293078745619b14bce932d30b9b73e5cce08c Mon Sep 17 00:00:00 2001 From: Gearonix Date: Tue, 17 Oct 2023 15:39:26 +0300 Subject: [PATCH] refactor: moved logic to auth icroservice --- .docker/compose/docker-compose.prod.yml | 14 +++++ apps/docs/introduction/about-project.md | 8 +-- apps/server/gateway/src/app.module.ts | 4 +- apps/server/gateway/src/auth/auth.module.ts | 26 --------- apps/server/gateway/src/auth/auth.resolver.ts | 36 ------------- apps/server/gateway/src/auth/auth.service.ts | 54 ------------------- apps/server/gateway/src/auth/index.ts | 1 - .../gateway/src/auth/responses/index.ts | 2 - .../src/auth/strategies/jwt.strategy.ts | 24 --------- .../gateway/src/core/auth/auth.module.ts | 13 +++++ .../gateway/src/core/auth/auth.resolver.ts | 49 +++++++++++++++++ .../code-executor.controller.ts | 4 +- .../code-executor.module.ts | 0 .../index.ts | 0 apps/server/gateway/src/core/users/index.ts | 3 -- .../gateway/src/core/users/users.module.ts | 12 ----- apps/server/service-auth/src/auth.consumer.ts | 22 ++++++-- apps/server/service-auth/src/auth.module.ts | 41 +++++++++----- apps/server/service-auth/src/auth.service.ts | 54 +++++++++++++++++++ .../auth => service-auth/src/lib}/types.ts | 0 apps/server/service-auth/src/main.ts | 18 +++---- .../src/queries/handlers/index.ts | 1 - .../service-auth/src/queries/impl/index.ts | 1 - .../src/repository/accounts.repository.ts} | 4 +- .../src/strategies/jwt.strategy.ts | 23 ++++++++ .../src}/strategies/local.strategy.ts | 7 ++- .../src/code-executor.consumer.ts | 3 +- .../api/common/src/consts/microservices.ts | 3 +- .../src/modules/env/conf/kafka.config.ts | 6 ++- packages/api/common/src/modules/index.ts | 1 + packages/api/common/src/modules/jwt/index.ts | 1 + .../api/common/src/modules/jwt/jwt.module.ts | 17 ++++++ packages/api/contracts/src/index.ts | 2 + packages/api/contracts/src/inputs/index.ts | 1 + .../contracts/src}/inputs/sign-in.input.ts | 0 packages/api/contracts/src/responses/index.ts | 2 + .../src}/responses/token.response.ts | 4 +- .../contracts/src}/responses/user.response.ts | 4 +- packages/api/contracts/src/rpc/auth.rpc.ts | 3 ++ packages/api/contracts/src/rpc/index.ts | 1 + 40 files changed, 261 insertions(+), 208 deletions(-) delete mode 100644 apps/server/gateway/src/auth/auth.module.ts delete mode 100644 apps/server/gateway/src/auth/auth.resolver.ts delete mode 100644 apps/server/gateway/src/auth/auth.service.ts delete mode 100644 apps/server/gateway/src/auth/index.ts delete mode 100644 apps/server/gateway/src/auth/responses/index.ts delete mode 100644 apps/server/gateway/src/auth/strategies/jwt.strategy.ts create mode 100644 apps/server/gateway/src/core/auth/auth.module.ts create mode 100644 apps/server/gateway/src/core/auth/auth.resolver.ts rename apps/server/gateway/src/core/{code-executor-api => code-executor}/code-executor.controller.ts (92%) rename apps/server/gateway/src/core/{code-executor-api => code-executor}/code-executor.module.ts (100%) rename apps/server/gateway/src/core/{code-executor-api => code-executor}/index.ts (100%) delete mode 100644 apps/server/gateway/src/core/users/index.ts delete mode 100644 apps/server/gateway/src/core/users/users.module.ts create mode 100644 apps/server/service-auth/src/auth.service.ts rename apps/server/{gateway/src/auth => service-auth/src/lib}/types.ts (100%) delete mode 100644 apps/server/service-auth/src/queries/handlers/index.ts delete mode 100644 apps/server/service-auth/src/queries/impl/index.ts rename apps/server/{gateway/src/core/users/users.repository.ts => service-auth/src/repository/accounts.repository.ts} (83%) create mode 100644 apps/server/service-auth/src/strategies/jwt.strategy.ts rename apps/server/{gateway/src/auth => service-auth/src}/strategies/local.strategy.ts (83%) create mode 100644 packages/api/common/src/modules/jwt/index.ts create mode 100644 packages/api/common/src/modules/jwt/jwt.module.ts create mode 100644 packages/api/contracts/src/inputs/index.ts rename {apps/server/gateway/src/auth => packages/api/contracts/src}/inputs/sign-in.input.ts (100%) create mode 100644 packages/api/contracts/src/responses/index.ts rename {apps/server/gateway/src/auth => packages/api/contracts/src}/responses/token.response.ts (69%) rename {apps/server/gateway/src/auth => packages/api/contracts/src}/responses/user.response.ts (75%) create mode 100644 packages/api/contracts/src/rpc/auth.rpc.ts diff --git a/.docker/compose/docker-compose.prod.yml b/.docker/compose/docker-compose.prod.yml index c7d6ef75..24fedf9d 100644 --- a/.docker/compose/docker-compose.prod.yml +++ b/.docker/compose/docker-compose.prod.yml @@ -45,3 +45,17 @@ services: env_file: - .env - .docker/.override.env + + service-auth: + build: + dockerfile: apps/server/service-auth/Dockerfile + context: . + container_name: service-auth + depends_on: + - kafka + networks: + - cgnet + restart: always + env_file: + - .env + - .docker/.override.env diff --git a/apps/docs/introduction/about-project.md b/apps/docs/introduction/about-project.md index 63305b90..9330fb38 100644 --- a/apps/docs/introduction/about-project.md +++ b/apps/docs/introduction/about-project.md @@ -10,7 +10,7 @@ could conduct online interviews and edit code in real time with other people. --- -Log preview +preview ## Customization @@ -26,7 +26,7 @@ You can customize the color, background, font size, tab size (default is 4 space You can *find*, *replace* text, create *tabs*, and also *run your code* (`javascript` and `python` are currently supported). -Log preview +preview ::: tip NOTE @@ -38,7 +38,7 @@ All your changes are saved to `localStorage` automatically, so you don't have to You can also log in and send a link to another person so that he can come in and edit the code with you in *real time*. -Log preview +preview ## What is the essence of this project? @@ -74,4 +74,4 @@ Something like that! 🚀 💫 --- -Log preview +preview diff --git a/apps/server/gateway/src/app.module.ts b/apps/server/gateway/src/app.module.ts index 005bb2c2..87e8cd27 100644 --- a/apps/server/gateway/src/app.module.ts +++ b/apps/server/gateway/src/app.module.ts @@ -2,12 +2,11 @@ import { join } from 'path' import { ApolloDriver } from '@nestjs/apollo' import { ApolloDriverConfig } from '@nestjs/apollo' import { Module } from '@nestjs/common' -import { ConfigModule } from '@nestjs/config' import { GraphQLModule } from '@nestjs/graphql' import { AuthModule } from '@/auth' -import { CodeExecutorModule } from './core/code-executor-api' +import { CodeExecutorModule } from './core/code-executor' import { EnvModule } from '@code-gear/api/common' import { ListenerModule } from '@code-gear/api/common' @@ -15,7 +14,6 @@ import { ListenerModule } from '@code-gear/api/common' imports: [ CodeExecutorModule, EnvModule, - AuthModule, ListenerModule.forRoot({ isMicroservice: false }), diff --git a/apps/server/gateway/src/auth/auth.module.ts b/apps/server/gateway/src/auth/auth.module.ts deleted file mode 100644 index eabe79b6..00000000 --- a/apps/server/gateway/src/auth/auth.module.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { jwtSecret } from '@code-gear/config' -import { Module } from '@nestjs/common' -import { JwtModule } from '@nestjs/jwt' - -import { UsersModule } from '@/core/users' - -import { AuthResolver } from './auth.resolver' -import { AuthService } from './auth.service' -import { JwtStrategy } from './strategies/jwt.strategy' -import { LocalStrategy } from './strategies/local.strategy' - -@Module({ - controllers: [], - providers: [AuthResolver, JwtStrategy, LocalStrategy, AuthService], - imports: [ - UsersModule, - JwtModule.register({ - secret: jwtSecret, - signOptions: { - expiresIn: '24h' - } - }) - ], - exports: [] -}) -export class AuthModule {} diff --git a/apps/server/gateway/src/auth/auth.resolver.ts b/apps/server/gateway/src/auth/auth.resolver.ts deleted file mode 100644 index 5a79ede9..00000000 --- a/apps/server/gateway/src/auth/auth.resolver.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { GqlAuthGuard } from '@code-gear/api/common' -import { GqlLocalAuthGuard } from '@code-gear/api/common' -import { JwtAuthGuard } from '@code-gear/api/common' -import { User } from '@code-gear/api/common' -import { WithUser } from '@code-gear/api/common' -import { graphqlArg } from '@code-gear/config' -import { UseGuards } from '@nestjs/common' -import { Args } from '@nestjs/graphql' -import { Mutation } from '@nestjs/graphql' -import { Query } from '@nestjs/graphql' -import { Resolver } from '@nestjs/graphql' - -import { AuthService } from './auth.service' -import { SignIn } from './inputs/sign-in.input' -import { AccessToken } from './responses' -import { UserResponse } from './responses' - -@Resolver(() => UserResponse) -export class AuthResolver { - constructor(private authService: AuthService) {} - - @Mutation(() => AccessToken) - @UseGuards(GqlAuthGuard, GqlLocalAuthGuard) - async signIn( - @Args(graphqlArg) payload: SignIn, - @WithUser() user: User - ): Promise { - return this.authService.generateToken(user.username) - } - - @Query(() => UserResponse) - @UseGuards(JwtAuthGuard) - async getProfile(@WithUser() user): Promise { - return user - } -} diff --git a/apps/server/gateway/src/auth/auth.service.ts b/apps/server/gateway/src/auth/auth.service.ts deleted file mode 100644 index e580f466..00000000 --- a/apps/server/gateway/src/auth/auth.service.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { Injectable } from '@nestjs/common' -import { JwtService } from '@nestjs/jwt' -import * as bcrypt from 'bcryptjs' - -import { UsersRepository } from '@/core/users' - -import { SignIn } from './inputs/sign-in.input' -import { AccessToken } from './responses' -import { UserResponse } from './responses' -import { JwtTokenPayload } from './types' - -@Injectable() -export class AuthService { - constructor( - private readonly jwtService: JwtService, - private readonly usersService: UsersRepository - ) {} - - public async validate(payload: SignIn): Promise { - const user = await this.usersService.getUserByUsername(payload.username) - - if (!user) { - return this.registerUser(payload) - } - - const isPasswordEquals = await bcrypt.compare( - payload.password, - user.password - ) - - if (!isPasswordEquals) { - return null - } - - return user - } - - public async registerUser(user: SignIn): Promise { - const hashPassword = await bcrypt.hash(user.password, 5) - - return this.usersService.createUser({ - ...user, - password: hashPassword - }) - } - - public async generateToken(username: string): Promise { - const accessToken = this.jwtService.sign({ - username - } satisfies JwtTokenPayload) - - return { accessToken } - } -} diff --git a/apps/server/gateway/src/auth/index.ts b/apps/server/gateway/src/auth/index.ts deleted file mode 100644 index c4707a8c..00000000 --- a/apps/server/gateway/src/auth/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { AuthModule } from './auth.module' diff --git a/apps/server/gateway/src/auth/responses/index.ts b/apps/server/gateway/src/auth/responses/index.ts deleted file mode 100644 index 893fd5c8..00000000 --- a/apps/server/gateway/src/auth/responses/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { AccessToken } from './token.response' -export { UserResponse } from './user.response' diff --git a/apps/server/gateway/src/auth/strategies/jwt.strategy.ts b/apps/server/gateway/src/auth/strategies/jwt.strategy.ts deleted file mode 100644 index 8d31ed4c..00000000 --- a/apps/server/gateway/src/auth/strategies/jwt.strategy.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { jwtSecret } from '@code-gear/config' -import { Injectable } from '@nestjs/common' -import { PassportStrategy } from '@nestjs/passport' -import { ExtractJwt } from 'passport-jwt' -import { Strategy } from 'passport-jwt' - -import { UsersRepository } from '@/core/users' - -import { JwtTokenPayload } from '../types' - -@Injectable() -export class JwtStrategy extends PassportStrategy(Strategy) { - constructor(private readonly usersService: UsersRepository) { - super({ - jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), - ignoreExpiration: false, - secretOrKey: jwtSecret - }) - } - - validate(payload: JwtTokenPayload) { - return this.usersService.getUserByUsername(payload.username) - } -} diff --git a/apps/server/gateway/src/core/auth/auth.module.ts b/apps/server/gateway/src/core/auth/auth.module.ts new file mode 100644 index 00000000..1786b853 --- /dev/null +++ b/apps/server/gateway/src/core/auth/auth.module.ts @@ -0,0 +1,13 @@ +import { Module } from '@nestjs/common' + +import { AuthResolver } from './auth.resolver' +import { KafkaModule } from '@code-gear/api/common' +import { Microservice } from '@code-gear/api/common' + +@Module({ + controllers: [], + providers: [AuthResolver], + imports: [KafkaModule.forRoot(Microservice.AUTH)], + exports: [] +}) +export class AuthModule {} diff --git a/apps/server/gateway/src/core/auth/auth.resolver.ts b/apps/server/gateway/src/core/auth/auth.resolver.ts new file mode 100644 index 00000000..d8b78ac7 --- /dev/null +++ b/apps/server/gateway/src/core/auth/auth.resolver.ts @@ -0,0 +1,49 @@ +import { GqlAuthGuard } from '@code-gear/api/common' +import { Microservice } from '@code-gear/api/common' +import { GqlLocalAuthGuard } from '@code-gear/api/common' +import { JwtAuthGuard } from '@code-gear/api/common' +import { User } from '@code-gear/api/common' +import { WithUser } from '@code-gear/api/common' +import { graphqlArg } from '@code-gear/config' +import { Inject } from '@nestjs/common' +import { OnModuleInit } from '@nestjs/common' +import { UseGuards } from '@nestjs/common' +import { Args } from '@nestjs/graphql' +import { Mutation } from '@nestjs/graphql' +import { Query } from '@nestjs/graphql' +import { Resolver } from '@nestjs/graphql' + +import { AccessTokenResponse } from '@code-gear/api/contracts' +import { AuthTopic } from '@code-gear/api/contracts' +import { SignIn } from '@code-gear/api/contracts' +import { UserResponse } from '@code-gear/api/contracts' +import { ClientKafka } from '@nestjs/microservices' +import { Observable } from 'rxjs' + +@Resolver(() => UserResponse) +export class AuthResolver implements OnModuleInit { + constructor( + @Inject(Microservice.AUTH) private readonly authClient: ClientKafka + ) {} + + async onModuleInit() { + this.authClient.subscribeToResponseOf(AuthTopic.SIGN_IN) + this.authClient.subscribeToResponseOf(AuthTopic.GET_PROFILE) + await this.authClient.connect() + } + + @Mutation(() => AccessTokenResponse) + @UseGuards(GqlAuthGuard, GqlLocalAuthGuard) + async signIn( + @Args(graphqlArg) payload: SignIn, + @WithUser() user: User + ): Promise> { + return this.authClient.send(AuthTopic.SIGN_IN, user) + } + + @Query(() => UserResponse) + @UseGuards(JwtAuthGuard) + async getProfile(@WithUser() user): Promise { + return user + } +} diff --git a/apps/server/gateway/src/core/code-executor-api/code-executor.controller.ts b/apps/server/gateway/src/core/code-executor/code-executor.controller.ts similarity index 92% rename from apps/server/gateway/src/core/code-executor-api/code-executor.controller.ts rename to apps/server/gateway/src/core/code-executor/code-executor.controller.ts index 03729264..a1d32e9c 100644 --- a/apps/server/gateway/src/core/code-executor-api/code-executor.controller.ts +++ b/apps/server/gateway/src/core/code-executor/code-executor.controller.ts @@ -1,5 +1,5 @@ import { EndPoints } from '@code-gear/config' -import { Body } from '@nestjs/common' +import {Body, OnModuleInit} from '@nestjs/common' import { Controller } from '@nestjs/common' import { Inject } from '@nestjs/common' import { Post } from '@nestjs/common' @@ -15,7 +15,7 @@ import { ExecutorLanguagesValues } from '@code-gear/api/contracts' @ApiTags('Code executor API') @Controller(EndPoints.CODE_EXECUTOR_API) -export class CodeExecutorController { +export class CodeExecutorController implements OnModuleInit { constructor( @Inject(Microservice.CODE_EXECUTOR) private executorClient: ClientKafka ) {} diff --git a/apps/server/gateway/src/core/code-executor-api/code-executor.module.ts b/apps/server/gateway/src/core/code-executor/code-executor.module.ts similarity index 100% rename from apps/server/gateway/src/core/code-executor-api/code-executor.module.ts rename to apps/server/gateway/src/core/code-executor/code-executor.module.ts diff --git a/apps/server/gateway/src/core/code-executor-api/index.ts b/apps/server/gateway/src/core/code-executor/index.ts similarity index 100% rename from apps/server/gateway/src/core/code-executor-api/index.ts rename to apps/server/gateway/src/core/code-executor/index.ts diff --git a/apps/server/gateway/src/core/users/index.ts b/apps/server/gateway/src/core/users/index.ts deleted file mode 100644 index 6824f882..00000000 --- a/apps/server/gateway/src/core/users/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export { UsersModule } from './users.module' -export { UsersRepository } from './users.repository' -// diff --git a/apps/server/gateway/src/core/users/users.module.ts b/apps/server/gateway/src/core/users/users.module.ts deleted file mode 100644 index ff56bae9..00000000 --- a/apps/server/gateway/src/core/users/users.module.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Module } from '@nestjs/common' - -import { UsersRepository } from './users.repository' -import { DatabaseModule } from '@code-gear/api/common' - -@Module({ - controllers: [], - providers: [UsersRepository], - imports: [DatabaseModule], - exports: [UsersRepository] -}) -export class UsersModule {} diff --git a/apps/server/service-auth/src/auth.consumer.ts b/apps/server/service-auth/src/auth.consumer.ts index 9ca4d09e..250250e2 100644 --- a/apps/server/service-auth/src/auth.consumer.ts +++ b/apps/server/service-auth/src/auth.consumer.ts @@ -1,7 +1,21 @@ -import { Controller } from '@nestjs/common' -import { QueryBus } from '@nestjs/cqrs' +import { Controller } from '@nestjs/common' +import { QueryBus } from '@nestjs/cqrs' +import { User } from '@code-gear/api/common' +import { MessagePattern } from '@nestjs/microservices' +import { Payload } from '@nestjs/microservices' +import { AccessTokenResponse } from '@code-gear/api/contracts' +import { AuthTopic } from '@code-gear/api/contracts' +import { AuthService } from './auth.service' @Controller() -export class TemplateConsumer { - constructor(private readonly query: QueryBus) {} +export class AuthConsumer { + constructor( + private readonly query: QueryBus, + private readonly authService: AuthService + ) {} + + @MessagePattern(AuthTopic.SIGN_IN) + async signIn(@Payload() user: User): Promise { + return this.authService.generateToken(user.username) + } } diff --git a/apps/server/service-auth/src/auth.module.ts b/apps/server/service-auth/src/auth.module.ts index a31468f4..303dfc54 100644 --- a/apps/server/service-auth/src/auth.module.ts +++ b/apps/server/service-auth/src/auth.module.ts @@ -1,24 +1,37 @@ -import { HttpModule } from '@nestjs/axios' -import { Module } from '@nestjs/common' +import { HttpModule } from '@nestjs/axios' +import { Module } from '@nestjs/common' -import { TemplateConsumer } from './auth.consumer' -import { EnvModule } from '@code-gear/api/common' -import { ListenerModule } from '@code-gear/api/common' -import { KafkaService } from '@code-gear/api/common' -import { QueryHandlers } from '@/queries/handlers' -import { CqrsModule } from '@nestjs/cqrs' +import { AuthConsumer } from './auth.consumer' +import { DatabaseModule } from '@code-gear/api/common' +import { EnvModule } from '@code-gear/api/common' +import { JwtModule } from '@code-gear/api/common' +import { KafkaService } from '@code-gear/api/common' +import { ListenerModule } from '@code-gear/api/common' +import { CqrsModule } from '@nestjs/cqrs' +import { AuthService } from './auth.service' +import { AccountsRepository } from './repository/accounts.repository' +import { JwtStrategy } from '@/strategies/jwt.strategy' +import { LocalStrategy } from '@/strategies/local.strategy' @Module({ imports: [ HttpModule, EnvModule, CqrsModule, + JwtModule, + DatabaseModule, ListenerModule.forRoot({ - isMicroservice: true, - }), + isMicroservice: true + }) ], - providers: [KafkaService, ...QueryHandlers], - controllers: [TemplateConsumer], - exports: [], + providers: [ + KafkaService, + AuthService, + AccountsRepository, + JwtStrategy, + LocalStrategy + ], + controllers: [AuthConsumer], + exports: [] }) -export class TemplateModule {} +export class AuthModule {} diff --git a/apps/server/service-auth/src/auth.service.ts b/apps/server/service-auth/src/auth.service.ts new file mode 100644 index 00000000..f83f997f --- /dev/null +++ b/apps/server/service-auth/src/auth.service.ts @@ -0,0 +1,54 @@ +import { Injectable } from '@nestjs/common' +import { JwtService } from '@nestjs/jwt' +import * as bcrypt from 'bcryptjs' + +import { AccessTokenResponse } from '@code-gear/api/contracts' +import { SignIn } from '@code-gear/api/contracts' +import { UserResponse } from '@code-gear/api/contracts' +import { Nullable } from '@grnx-utils/types' +import { AccountsRepository } from '@/repository/accounts.repository' +import { JwtTokenPayload } from '@/lib/types' + +@Injectable() +export class AuthService { + constructor( + private readonly jwtService: JwtService, + private readonly accountsService: AccountsRepository + ) {} + + public async validate(payload: SignIn): Promise> { + const user = await this.accountsService.getUserByUsername(payload.username) + + if (!user) { + return this.registerUser(payload) + } + + const isPasswordEquals = await bcrypt.compare( + payload.password, + user.password + ) + + if (!isPasswordEquals) { + return null + } + + return user + } + + public async registerUser(user: SignIn): Promise { + const hashPassword = await bcrypt.hash(user.password, 5) + + return this.accountsService.createUser({ + ...user, + password: hashPassword + }) + } + + public async generateToken(username: string): Promise { + const accessToken = this.jwtService.sign({ + username + } satisfies JwtTokenPayload) + + return { accessToken } + } +} diff --git a/apps/server/gateway/src/auth/types.ts b/apps/server/service-auth/src/lib/types.ts similarity index 100% rename from apps/server/gateway/src/auth/types.ts rename to apps/server/service-auth/src/lib/types.ts diff --git a/apps/server/service-auth/src/main.ts b/apps/server/service-auth/src/main.ts index 8d16785d..c1edc5e3 100644 --- a/apps/server/service-auth/src/main.ts +++ b/apps/server/service-auth/src/main.ts @@ -1,19 +1,19 @@ import { MicroserviceOptions } from '@nestjs/microservices' -import { NestFactory } from '@nestjs/core' -import { KafkaService } from '@code-gear/api/common' -import { ListenerService } from '@code-gear/api/common' -import { Microservice } from '@code-gear/api/common' -import { RpcExceptionFilter } from '@code-gear/api/common' -import { ValidationPipe } from '@code-gear/api/common' -import { TemplateModule } from './auth.module' +import { NestFactory } from '@nestjs/core' +import { KafkaService } from '@code-gear/api/common' +import { ListenerService } from '@code-gear/api/common' +import { Microservice } from '@code-gear/api/common' +import { RpcExceptionFilter } from '@code-gear/api/common' +import { ValidationPipe } from '@code-gear/api/common' +import { AuthModule } from './auth.module' const bootstrap = async () => { - const app = await NestFactory.create(TemplateModule) + const app = await NestFactory.create(AuthModule) const kafkaService = app.get(KafkaService) app.connectMicroservice( - kafkaService.getKafkaOptions(Microservice.TEMPLATE), + kafkaService.getKafkaOptions(Microservice.AUTH) ) app.useGlobalFilters(new RpcExceptionFilter()) diff --git a/apps/server/service-auth/src/queries/handlers/index.ts b/apps/server/service-auth/src/queries/handlers/index.ts deleted file mode 100644 index af49aac2..00000000 --- a/apps/server/service-auth/src/queries/handlers/index.ts +++ /dev/null @@ -1 +0,0 @@ -export const QueryHandlers = [] diff --git a/apps/server/service-auth/src/queries/impl/index.ts b/apps/server/service-auth/src/queries/impl/index.ts deleted file mode 100644 index f51c76d0..00000000 --- a/apps/server/service-auth/src/queries/impl/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './execute-code.query' diff --git a/apps/server/gateway/src/core/users/users.repository.ts b/apps/server/service-auth/src/repository/accounts.repository.ts similarity index 83% rename from apps/server/gateway/src/core/users/users.repository.ts rename to apps/server/service-auth/src/repository/accounts.repository.ts index 807f6ab4..95046833 100644 --- a/apps/server/gateway/src/core/users/users.repository.ts +++ b/apps/server/service-auth/src/repository/accounts.repository.ts @@ -1,11 +1,11 @@ import { Injectable } from '@nestjs/common' -import { SignIn } from '@/auth/inputs/sign-in.input' import { DatabaseService } from '@code-gear/api/common' import { User } from '@code-gear/api/common' +import { SignIn } from '@code-gear/api/contracts' @Injectable() -export class UsersRepository { +export class AccountsRepository { constructor(private prisma: DatabaseService) {} public getUserByUsername(username: string): User { diff --git a/apps/server/service-auth/src/strategies/jwt.strategy.ts b/apps/server/service-auth/src/strategies/jwt.strategy.ts new file mode 100644 index 00000000..41ba5ef3 --- /dev/null +++ b/apps/server/service-auth/src/strategies/jwt.strategy.ts @@ -0,0 +1,23 @@ +import { jwtSecret } from '@code-gear/config' +import { Injectable } from '@nestjs/common' +import { PassportStrategy } from '@nestjs/passport' +import { ExtractJwt } from 'passport-jwt' +import { Strategy } from 'passport-jwt' + +import { JwtTokenPayload } from '../lib/types' +import { AccountsRepository } from '@/repository/accounts.repository' + +@Injectable() +export class JwtStrategy extends PassportStrategy(Strategy) { + constructor(private readonly usersService: AccountsRepository) { + super({ + jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), + ignoreExpiration: false, + secretOrKey: jwtSecret + }) + } + + validate(payload: JwtTokenPayload) { + return this.usersService.getUserByUsername(payload.username) + } +} diff --git a/apps/server/gateway/src/auth/strategies/local.strategy.ts b/apps/server/service-auth/src/strategies/local.strategy.ts similarity index 83% rename from apps/server/gateway/src/auth/strategies/local.strategy.ts rename to apps/server/service-auth/src/strategies/local.strategy.ts index 43362606..91bc9d7d 100644 --- a/apps/server/gateway/src/auth/strategies/local.strategy.ts +++ b/apps/server/service-auth/src/strategies/local.strategy.ts @@ -5,7 +5,7 @@ import { PassportStrategy } from '@nestjs/passport' import { Strategy } from 'passport-local' import { AuthService } from '../auth.service' -import { UserResponse } from '../responses' +import { AccountsRepository } from '@/repository/accounts.repository' @Injectable() export class LocalStrategy extends PassportStrategy(Strategy) { @@ -13,7 +13,10 @@ export class LocalStrategy extends PassportStrategy(Strategy) { super({ usernameField: 'username', passwordField: 'password' }) } - async validate(username: string, password: string): Promise { + async validate( + username: string, + password: string + ): Promise { try { const user = await this.authService.validate({ username, diff --git a/apps/server/service-code-executor/src/code-executor.consumer.ts b/apps/server/service-code-executor/src/code-executor.consumer.ts index ecd6d554..d08be76e 100644 --- a/apps/server/service-code-executor/src/code-executor.consumer.ts +++ b/apps/server/service-code-executor/src/code-executor.consumer.ts @@ -16,7 +16,6 @@ export class CodeExecutorConsumer { async executeCode( @Payload() payload: ExecuteCodeApiDTO ): Promise> { - const command = new ExecuteCodeQuery(payload) - return this.query.execute(command) + return this.query.execute(new ExecuteCodeQuery(payload)) } } diff --git a/packages/api/common/src/consts/microservices.ts b/packages/api/common/src/consts/microservices.ts index b058c76c..b583a88e 100644 --- a/packages/api/common/src/consts/microservices.ts +++ b/packages/api/common/src/consts/microservices.ts @@ -1,3 +1,4 @@ export enum Microservice { - CODE_EXECUTOR = 'codeExecutor' + CODE_EXECUTOR = 'codeExecutor', + AUTH = 'auth' } diff --git a/packages/api/common/src/modules/env/conf/kafka.config.ts b/packages/api/common/src/modules/env/conf/kafka.config.ts index 2e6c7393..c1ae5ec2 100644 --- a/packages/api/common/src/modules/env/conf/kafka.config.ts +++ b/packages/api/common/src/modules/env/conf/kafka.config.ts @@ -12,6 +12,9 @@ class KafkaValidator { @Env() SERVER_KAFKA_MICROSERVICE_CODE_EXECUTOR: string + @Env() + SERVER_KAFKA_MICROSERVICE_AUTH: string + @IsNumberString() @IsNotEmpty() SERVER_KAFKA_SESSION_TIMEOUT: string @@ -27,7 +30,8 @@ export const kafka = registerAs('kafka', (): KafkaConfig => { return { brokers: conf.SERVER_KAFKA_BROKERS.split(','), microservices: { - codeExecutor: conf.SERVER_KAFKA_MICROSERVICE_CODE_EXECUTOR + codeExecutor: conf.SERVER_KAFKA_MICROSERVICE_CODE_EXECUTOR, + auth: conf.SERVER_KAFKA_MICROSERVICE_AUTH }, sessionTimeout: Number(conf.SERVER_KAFKA_SESSION_TIMEOUT), heartbeatInterval: Number(conf.SERVER_KAFKA_HEARTBEAT_INTERVAL) diff --git a/packages/api/common/src/modules/index.ts b/packages/api/common/src/modules/index.ts index 9ee98a88..e5dbae52 100644 --- a/packages/api/common/src/modules/index.ts +++ b/packages/api/common/src/modules/index.ts @@ -2,3 +2,4 @@ export * from './kafka' export * from './env' export * from './database' export * from './listener' +export * from './jwt' diff --git a/packages/api/common/src/modules/jwt/index.ts b/packages/api/common/src/modules/jwt/index.ts new file mode 100644 index 00000000..336e962e --- /dev/null +++ b/packages/api/common/src/modules/jwt/index.ts @@ -0,0 +1 @@ +export { JwtModule } from './jwt.module' diff --git a/packages/api/common/src/modules/jwt/jwt.module.ts b/packages/api/common/src/modules/jwt/jwt.module.ts new file mode 100644 index 00000000..723850a0 --- /dev/null +++ b/packages/api/common/src/modules/jwt/jwt.module.ts @@ -0,0 +1,17 @@ +import { Module } from '@nestjs/common' +import { JwtModule as NestJwtModule } from '@nestjs/jwt' +import { JwtService } from '@nestjs/jwt' +import { jwtSecret } from '@code-gear/config' + +@Module({ + imports: [ + NestJwtModule.register({ + secret: jwtSecret, + signOptions: { + expiresIn: '24h' + } + }) + ], + exports: [NestJwtModule] +}) +export class JwtModule {} diff --git a/packages/api/contracts/src/index.ts b/packages/api/contracts/src/index.ts index 41ee37c5..93ba81b2 100644 --- a/packages/api/contracts/src/index.ts +++ b/packages/api/contracts/src/index.ts @@ -1,3 +1,5 @@ export * from './rpc' export * from './interfaces' export * from './dto' +export * from './inputs' +export * from './responses' diff --git a/packages/api/contracts/src/inputs/index.ts b/packages/api/contracts/src/inputs/index.ts new file mode 100644 index 00000000..3cc6c34b --- /dev/null +++ b/packages/api/contracts/src/inputs/index.ts @@ -0,0 +1 @@ +export * from './sign-in.input' diff --git a/apps/server/gateway/src/auth/inputs/sign-in.input.ts b/packages/api/contracts/src/inputs/sign-in.input.ts similarity index 100% rename from apps/server/gateway/src/auth/inputs/sign-in.input.ts rename to packages/api/contracts/src/inputs/sign-in.input.ts diff --git a/packages/api/contracts/src/responses/index.ts b/packages/api/contracts/src/responses/index.ts new file mode 100644 index 00000000..cc7545b1 --- /dev/null +++ b/packages/api/contracts/src/responses/index.ts @@ -0,0 +1,2 @@ +export { AccessTokenResponse } from './token.response' +export { UserResponse } from './user.response' diff --git a/apps/server/gateway/src/auth/responses/token.response.ts b/packages/api/contracts/src/responses/token.response.ts similarity index 69% rename from apps/server/gateway/src/auth/responses/token.response.ts rename to packages/api/contracts/src/responses/token.response.ts index 0d6ecbcb..1b1af65e 100644 --- a/apps/server/gateway/src/auth/responses/token.response.ts +++ b/packages/api/contracts/src/responses/token.response.ts @@ -2,10 +2,10 @@ import { Field } from '@nestjs/graphql' import { ObjectType } from '@nestjs/graphql' import { ApiProperty } from '@nestjs/swagger' -import { AccessToken as CommonAccessToken } from '$/nest-common' +import { AccessToken as CommonAccessToken } from '@code-gear/api/common' @ObjectType() -export class AccessToken implements CommonAccessToken { +export class AccessTokenResponse implements CommonAccessToken { @Field() @ApiProperty({ description: 'Authorization token (jwt)' }) accessToken: string diff --git a/apps/server/gateway/src/auth/responses/user.response.ts b/packages/api/contracts/src/responses/user.response.ts similarity index 75% rename from apps/server/gateway/src/auth/responses/user.response.ts rename to packages/api/contracts/src/responses/user.response.ts index 54acb4a7..354422cd 100644 --- a/apps/server/gateway/src/auth/responses/user.response.ts +++ b/packages/api/contracts/src/responses/user.response.ts @@ -2,10 +2,10 @@ import { Field } from '@nestjs/graphql' import { ObjectType } from '@nestjs/graphql' import { ApiProperty } from '@nestjs/swagger' -import { UserEntity } from '$/nest-common' +import { User } from '@code-gear/api/common' @ObjectType() -export class UserResponse implements UserEntity { +export class UserResponse implements Omit { @Field() @ApiProperty({ description: 'Username (used as userId)' }) username: string diff --git a/packages/api/contracts/src/rpc/auth.rpc.ts b/packages/api/contracts/src/rpc/auth.rpc.ts new file mode 100644 index 00000000..32f7ed5a --- /dev/null +++ b/packages/api/contracts/src/rpc/auth.rpc.ts @@ -0,0 +1,3 @@ +export const SIGN_IN = 'auth.sign-in' as const + +export const GET_PROFILE = 'auth.get-profile' as const diff --git a/packages/api/contracts/src/rpc/index.ts b/packages/api/contracts/src/rpc/index.ts index b5e1c6c4..74908938 100644 --- a/packages/api/contracts/src/rpc/index.ts +++ b/packages/api/contracts/src/rpc/index.ts @@ -1 +1,2 @@ export * as CodeExecutorTopic from './code-executor.rpc' +export * as AuthTopic from './auth.rpc'