-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
18 changed files
with
346 additions
and
165 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { | ||
Controller, | ||
Post, | ||
Body, | ||
Req, | ||
} from '@nestjs/common'; | ||
import { Request } from 'express'; | ||
import { AdminService } from './admin.service'; | ||
import { SignUpDto, LoginDto } from './admin.dto'; | ||
import { API_VERSION } from '../version'; | ||
|
||
@Controller(`${API_VERSION}`) | ||
export class AdminController { | ||
constructor(private readonly adminService: AdminService) {} | ||
|
||
@Post("signUp") | ||
signUp(@Body() signUpData: SignUpDto) { | ||
return this.adminService.signUp(signUpData); | ||
} | ||
|
||
@Post("login") | ||
login( | ||
@Req() req, | ||
@Body() loginData: LoginDto | ||
) { | ||
return this.adminService.login(loginData); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { IsString } from 'class-validator'; | ||
import { ApiProperty } from '@nestjs/swagger'; | ||
import { PartialType } from '@nestjs/mapped-types'; | ||
|
||
export class LoginDto { | ||
@ApiProperty() | ||
@IsString() | ||
email: string; | ||
|
||
@ApiProperty() | ||
@IsString() | ||
password: string; | ||
} | ||
|
||
export class SignUpDto extends PartialType(LoginDto) { | ||
@ApiProperty() | ||
@IsString() | ||
email: string; | ||
|
||
@ApiProperty() | ||
@IsString() | ||
password: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import { Module } from '@nestjs/common'; | ||
import { AdminService } from './admin.service'; | ||
import { AdminController } from './admin.controller'; | ||
import { AuthModule } from 'src/auth/auth.module'; | ||
|
||
@Module({ | ||
imports: [AuthModule], | ||
controllers: [AdminController], | ||
providers: [AdminService], | ||
}) | ||
export class AdminModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
import { | ||
BadRequestException, | ||
HttpException, | ||
HttpStatus, | ||
Injectable, | ||
Logger, | ||
} from '@nestjs/common'; | ||
import { SignUpDto, LoginDto } from './admin.dto'; | ||
import { GraphQLClient } from "graphql-request"; | ||
import * as bcrypt from 'bcrypt'; | ||
import { v4 as uuidv4 } from 'uuid'; | ||
import { JWTService } from 'src/auth/jwt.service'; | ||
import { GRAPHQL_URL, HASURA_ADMIN_SECRET } from 'src/config.constants'; | ||
import { registerUser, getUserByEmail } from 'src/graphql-api'; | ||
|
||
@Injectable() | ||
export class AdminService { | ||
|
||
adminClient; | ||
// Initialize logger | ||
//TODO: Move this to module level | ||
logger: Logger = new Logger('UserLogger'); | ||
|
||
|
||
constructor( | ||
private jwtService: JWTService | ||
) { | ||
this.adminClient = new GraphQLClient(GRAPHQL_URL, { | ||
headers: { "x-hasura-admin-secret": HASURA_ADMIN_SECRET}, | ||
}) | ||
} | ||
|
||
async signUp(signUpDto: SignUpDto) { | ||
// We need to check if user is already registered | ||
// We skip that for the sake of time | ||
|
||
// We insert the user using a mutation | ||
// Note that we salt and hash the password using bcrypt | ||
const { email, password } = signUpDto; | ||
const id = uuidv4(); | ||
try { | ||
const { insert_User_one } = await this.adminClient.request( | ||
registerUser, | ||
{ | ||
user: { | ||
id, | ||
email, | ||
password: await bcrypt.hash(password, 10), | ||
}, | ||
}, | ||
); | ||
|
||
const { id: userId } = insert_User_one; | ||
|
||
return this.jwtService.generateHasuraJWT({ | ||
id: userId, | ||
defaultRole: 'user', | ||
allowedRoles: ['user'], | ||
otherClaims: { | ||
'X-Hasura-User-Id': userId, | ||
}, | ||
}); | ||
} catch (e) { | ||
this.logger.error(e); | ||
throw new BadRequestException('Cannot create user!'); | ||
} | ||
} | ||
|
||
async login(loginDto: LoginDto) { | ||
const { email, password } = loginDto; | ||
let foundUser = null; | ||
|
||
try { | ||
foundUser = await this.adminClient.request( | ||
getUserByEmail, | ||
{ | ||
email, | ||
}, | ||
); | ||
} catch (e) { | ||
this.logger.error(e); | ||
throw new BadRequestException('Cannot create user!'); | ||
} | ||
|
||
let { User } = foundUser; | ||
// Since we filtered on a non-primary key we got an array back | ||
User = User[0]; | ||
|
||
if (!User) { | ||
return 401; | ||
} | ||
|
||
// Check if password matches the hashed version | ||
const passwordMatch = await bcrypt.compare(password, User.password); | ||
|
||
if (passwordMatch) { | ||
return this.jwtService.generateHasuraJWT({ | ||
id: User.id, | ||
defaultRole: 'user', | ||
allowedRoles: ['user'], | ||
otherClaims: { | ||
'X-Hasura-User-Id': User.id, | ||
}, | ||
}); | ||
} else { | ||
throw new HttpException( 'Not allowed or no user found', | ||
HttpStatus.FORBIDDEN); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { Response, NextFunction } from 'express'; | ||
import * as jwt from "jsonwebtoken" | ||
import { GRAPHQL_JWT_SECRET } from './jwt.service' | ||
|
||
export function verifyJWT(token: string) { | ||
//https://stackoverflow.com/questions/43915379/i-need-to-replace-bearer-from-the-header-to-verify-the-token | ||
var parts = token.split(' '); | ||
if (parts.length === 2) { | ||
var scheme = parts[0]; | ||
var credentials = parts[1]; | ||
|
||
if (/^Bearer$/i.test(scheme)) { | ||
//verify token | ||
let jwtPayload = jwt.verify(credentials, GRAPHQL_JWT_SECRET.key); | ||
return jwtPayload; | ||
} | ||
} | ||
return null; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; | ||
import { Observable } from 'rxjs'; | ||
import { verifyJWT } from './auth.functions'; | ||
|
||
@Injectable() | ||
export class AuthGuard implements CanActivate { | ||
|
||
canActivate( | ||
context: ExecutionContext, | ||
): boolean | Promise<boolean> | Observable<boolean> { | ||
const request = context.switchToHttp().getRequest(); | ||
return this.validateRequest(request); | ||
} | ||
|
||
validateRequest(request): boolean { | ||
if (!request.headers.authorization) { | ||
// return res.status(403).json({ error: 'No credentials sent!' }); | ||
//TODO: How to send proper error code? | ||
return false; | ||
} | ||
let jwtPayload = verifyJWT(request.headers.authorization); | ||
if (!jwtPayload) { | ||
// return res.status(403).json({ error: 'No payload in jwt!' }); | ||
//TODO: How to send proper error code? | ||
return false; | ||
} | ||
return true; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common'; | ||
import { JWTService } from './jwt.service'; | ||
|
||
@Module({ | ||
providers: [ JWTService ], | ||
exports: [ JWTService ] | ||
}) | ||
export class AuthModule {} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { Injectable } from '@nestjs/common'; | ||
import * as jwt from "jsonwebtoken" | ||
import { JWT_SECRET_TYPE, JWT_SECRET_KEY } from "../config.constants" | ||
|
||
//TODO Move these to environment variables. | ||
export const GRAPHQL_JWT_SECRET = { | ||
type: JWT_SECRET_TYPE, | ||
key: JWT_SECRET_KEY, | ||
}; | ||
|
||
interface GenerateJWTParams { | ||
id: string; | ||
defaultRole: string; | ||
allowedRoles: string[]; | ||
otherClaims?: Record<string, string | string[]>; | ||
} | ||
|
||
@Injectable() | ||
export class JWTService { | ||
|
||
JWT_CONFIG: jwt.SignOptions = { | ||
algorithm: JWT_SECRET_TYPE, | ||
expiresIn: "10h", | ||
} | ||
|
||
generateHasuraJWT(params: GenerateJWTParams): string { | ||
const payload = { | ||
sub: params.id, | ||
"https://hasura.io/jwt/claims": { | ||
"x-hasura-allowed-roles": params.allowedRoles, | ||
"x-hasura-default-role": params.defaultRole, | ||
...params.otherClaims, | ||
} | ||
} | ||
return jwt.sign(payload, GRAPHQL_JWT_SECRET.key, this.JWT_CONFIG); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// These should go into the environment variables and then get imported using the nestjs config | ||
export const GRAPHQL_URL = 'http://localhost:8080/v1/graphql'; | ||
export const JWT_SECRET_TYPE = "HS256"; | ||
export const JWT_SECRET_KEY = "This-is-a-generic-hs256-secret-key-and-it-should-be-changed"; | ||
export const HASURA_ADMIN_SECRET = "myadminsecretkey"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import { gql } from 'graphql-request'; | ||
|
||
export const registerUser = gql` | ||
mutation registerUser($user: User_insert_input!) { | ||
insert_User_one(object: $user) { | ||
id | ||
} | ||
} | ||
` | ||
|
||
export const getUserByEmail = gql` | ||
query getUserByEmail($email: String!) { | ||
User(where: { email: { _eq: $email } }) { | ||
id | ||
password | ||
} | ||
} | ||
` | ||
|
||
export const getUserName = gql` | ||
query getUserName($id: String!) { | ||
User(where: { id: { _eq: $id } }) { | ||
id | ||
name | ||
} | ||
} | ||
` |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.