Skip to content

Commit

Permalink
feat: add admin login auth flow (#112)
Browse files Browse the repository at this point in the history
  • Loading branch information
xixas committed Jan 17, 2024
1 parent f65ff07 commit fa1f339
Show file tree
Hide file tree
Showing 14 changed files with 217 additions and 1 deletion.
4 changes: 4 additions & 0 deletions apps/api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
SERVER_PORT=
SERVER_API_KEY= # Add your API Key

# Portal Credentials
ADMIN_USERNAME=admin
ADMIN_PASSWORD=admin@123

# Node env
NODE_ENV= # Use "development" for graphql playground to work

Expand Down
53 changes: 53 additions & 0 deletions apps/api/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"@nestjs/config": "^3.0.0",
"@nestjs/core": "^10.0.0",
"@nestjs/graphql": "^12.0.11",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.0.0",
"@nestjs/schedule": "^3.0.3",
"@nestjs/typeorm": "^10.0.0",
Expand All @@ -49,6 +50,8 @@
"mysql2": "^3.6.0",
"nest-winston": "^1.9.4",
"nodemailer": "^6.9.4",
"passport": "^0.7.0",
"passport-local": "^1.0.0",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"twilio": "^5.0.0-rc.1",
Expand Down
2 changes: 2 additions & 0 deletions apps/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DatabaseModule } from './database/database.module';
import { GraphQLModule } from '@nestjs/graphql';
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
import { join } from 'path';
import { AuthModule } from './modules/auth/auth.module';

const configService = new ConfigService();
@Module({
Expand All @@ -33,6 +34,7 @@ const configService = new ConfigService();
sortSchema: true,
playground: configService.getOrThrow('NODE_ENV') === 'development',
}),
AuthModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
13 changes: 13 additions & 0 deletions apps/api/src/modules/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule } from '@nestjs/config';
import { LocalStrategy } from './strategies/local.strategy';
import { AuthResolver } from './auth.resolver';

@Module({
imports: [PassportModule, ConfigModule],
providers: [AuthResolver, AuthService, LocalStrategy],
exports: [AuthService],
})
export class AuthModule {}
18 changes: 18 additions & 0 deletions apps/api/src/modules/auth/auth.resolver.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthResolver } from './auth.resolver';

describe('AuthResolver', () => {
let resolver: AuthResolver;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthResolver],
}).compile();

resolver = module.get<AuthResolver>(AuthResolver);
});

it('should be defined', () => {
expect(resolver).toBeDefined();
});
});
17 changes: 17 additions & 0 deletions apps/api/src/modules/auth/auth.resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { AuthService } from './auth.service';
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { LoginResponse } from './dto/login-response';
import { UseGuards } from '@nestjs/common';
import { GqlLocalAuthGuard } from './guards/gql-local.guard';
import { LoginUserInput } from './dto/login-user.input';

@Resolver()
export class AuthResolver {
constructor(private readonly authService: AuthService) {}

@Mutation(() => LoginResponse)
@UseGuards(GqlLocalAuthGuard)
async login(@Args('loginUserInput') loginUserInput: LoginUserInput): Promise<LoginResponse> {
return await this.authService.login(loginUserInput);
}
}
18 changes: 18 additions & 0 deletions apps/api/src/modules/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';

describe('AuthService', () => {
let service: AuthService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [AuthService],
}).compile();

service = module.get<AuthService>(AuthService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
27 changes: 27 additions & 0 deletions apps/api/src/modules/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { LoginResponse } from './dto/login-response';

@Injectable()
export class AuthService {
constructor(private configService: ConfigService) {}

async validateUser(username: string, password: string): Promise<string> {
const adminUsername = this.configService.getOrThrow<string>('ADMIN_USERNAME');
const adminPassword = this.configService.getOrThrow<string>('ADMIN_PASSWORD');

if (username === adminUsername && password === adminPassword) {
return username;
}

return null;
}

async login(loginUserInput): Promise<LoginResponse> {
const token = this.configService.getOrThrow<string>('SERVER_API_KEY');
return {
token,
user: loginUserInput.username,
};
}
}
10 changes: 10 additions & 0 deletions apps/api/src/modules/auth/dto/login-response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Field, ObjectType } from '@nestjs/graphql';

@ObjectType()
export class LoginResponse {
@Field()
token: string;

@Field()
user: string;
}
13 changes: 13 additions & 0 deletions apps/api/src/modules/auth/dto/login-user.input.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { InputType, Field } from '@nestjs/graphql';
import { IsNotEmpty } from 'class-validator';

@InputType()
export class LoginUserInput {
@Field()
@IsNotEmpty()
username: string;

@Field()
@IsNotEmpty()
password: string;
}
14 changes: 14 additions & 0 deletions apps/api/src/modules/auth/guards/gql-local.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Injectable, ExecutionContext } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { GqlExecutionContext } from '@nestjs/graphql';
import { Request } from 'express';

@Injectable()
export class GqlLocalAuthGuard extends AuthGuard('local') {
getRequest(context: ExecutionContext): Request {
const ctx = GqlExecutionContext.create(context);
const request = ctx.getContext().req;
request.body = ctx.getArgs().loginUserInput;
return request;
}
}
24 changes: 24 additions & 0 deletions apps/api/src/modules/auth/strategies/local.strategy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from '../auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
constructor(private authService: AuthService) {
super({
usernameField: 'username',
passwordField: 'password',
});
}

async validate(username: string, password: string): Promise<string> {
const user = await this.authService.validateUser(username, password);

if (!user) {
throw new UnauthorizedException('Invalid username or password');
}

return user;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { ApiKeyGuard } from 'src/common/guards/api-key/api-key.guard';
import { UseGuards } from '@nestjs/common';

@Resolver(() => Notification)
// @UseGuards(ApiKeyGuard)
@UseGuards(ApiKeyGuard)
export class NotificationsResolver {
constructor(private readonly notificationsService: NotificationsService) {}

Expand Down

0 comments on commit fa1f339

Please sign in to comment.