feat(auth): add user management endpoint and update guards
Introduce new "me" endpoints for fetching, updating, and deleting current user data. Update guards to properly inject dependent services using @Inject decorator for better DI handling.
This commit is contained in:
parent
5d24f36133
commit
27f79d4e46
@ -1,11 +1,21 @@
|
|||||||
import { Body, Controller, HttpCode, HttpStatus, Post } from "@nestjs/common";
|
import {
|
||||||
import { SignInDto, SignUpDto } from "src/auth/auth.dto";
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus, Patch,
|
||||||
|
Post,
|
||||||
|
UnauthorizedException,
|
||||||
|
UseGuards
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { SignInDto, SignUpDto, UpdateUserDto } from "src/auth/auth.dto";
|
||||||
import { AuthService } from "src/auth/auth.service";
|
import { AuthService } from "src/auth/auth.service";
|
||||||
import * as console from "node:console";
|
import { UserGuard } from "./auth.guard";
|
||||||
|
|
||||||
@Controller("auth")
|
@Controller("auth")
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(private readonly authService: AuthService) {}
|
constructor(private readonly authService: AuthService) { }
|
||||||
|
|
||||||
//POST signup
|
//POST signup
|
||||||
@HttpCode(HttpStatus.CREATED)
|
@HttpCode(HttpStatus.CREATED)
|
||||||
@ -23,6 +33,42 @@ export class AuthController {
|
|||||||
return this.authService.doLogin(dto);
|
return this.authService.doLogin(dto);
|
||||||
}
|
}
|
||||||
//GET me -- Get current user data via jwt
|
//GET me -- Get current user data via jwt
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Get("me")
|
||||||
|
@UseGuards(UserGuard)
|
||||||
|
async getMe(@Body() body: object) {
|
||||||
|
// @ts-ignore
|
||||||
|
const targetId = body.sourceUserId
|
||||||
|
const userData = await this.authService.fetchUserById(targetId)
|
||||||
|
if (!userData) {
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
return userData;
|
||||||
|
}
|
||||||
//DELETE me
|
//DELETE me
|
||||||
|
@HttpCode(HttpStatus.FOUND)
|
||||||
|
@Delete("me")
|
||||||
|
@UseGuards(UserGuard)
|
||||||
|
async deleteMe(@Body() body: object) {
|
||||||
|
// @ts-ignore
|
||||||
|
const targetId = body.sourceUserId
|
||||||
|
try {
|
||||||
|
await this.authService.deleteUser(targetId)
|
||||||
|
} catch (err) {
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//PATCH me
|
//PATCH me
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Patch("me")
|
||||||
|
@UseGuards(UserGuard)
|
||||||
|
async patchMe(@Body() body: UpdateUserDto) {
|
||||||
|
console.log(body);
|
||||||
|
// @ts-ignore
|
||||||
|
const targetId = body.sourceUserId;
|
||||||
|
await this.authService.updateUser(targetId, body);
|
||||||
|
return await this.authService.fetchUserById(targetId)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,3 +48,17 @@ export class SignInDto {
|
|||||||
})
|
})
|
||||||
password: string;
|
password: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class UpdateUserDto {
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(24)
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
firstName: string;
|
||||||
|
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(24)
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
lastName: string;
|
||||||
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException } from '@nestjs/common';
|
import { Injectable, CanActivate, ExecutionContext, UnauthorizedException, Inject } from "@nestjs/common";
|
||||||
import { Request } from 'express';
|
import { Request } from "express";
|
||||||
import { CredentialsService } from "src/credentials/credentials.service";
|
import { CredentialsService } from "src/credentials/credentials.service";
|
||||||
import { DrizzleService } from "src/drizzle/drizzle.service";
|
import { DrizzleService } from "src/drizzle/drizzle.service";
|
||||||
import { UsersTable } from "src/schema";
|
import { UsersTable } from "src/schema";
|
||||||
@ -8,37 +8,35 @@ import { Reflector } from "@nestjs/core";
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserGuard implements CanActivate {
|
export class UserGuard implements CanActivate {
|
||||||
private readonly credentialService: CredentialsService;
|
|
||||||
private readonly databaseService: DrizzleService;
|
|
||||||
|
|
||||||
constructor() {
|
constructor(
|
||||||
const reflector = new Reflector();
|
@Inject(CredentialsService) private readonly credentialService: CredentialsService,
|
||||||
this.credentialService = reflector.get<CredentialsService>('CredentialsService', UserGuard);
|
@Inject(DrizzleService) private readonly databaseService: DrizzleService,
|
||||||
this.databaseService = reflector.get<DrizzleService>('DrizzleService', UserGuard);
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async canActivate(
|
async canActivate(
|
||||||
context: ExecutionContext,
|
context: ExecutionContext
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const request: Request = context.switchToHttp().getRequest();
|
const request: Request = context.switchToHttp().getRequest();
|
||||||
const authHeader = request.headers.authorization;
|
const authHeader = request.headers.authorization;
|
||||||
|
|
||||||
if (!authHeader)
|
if (!authHeader)
|
||||||
throw new UnauthorizedException('No authorization header found.');
|
throw new UnauthorizedException("No authorization header found.");
|
||||||
|
|
||||||
const token = authHeader.split(' ')[1];
|
const token = authHeader.split(" ")[1];
|
||||||
const vToken = await this.credentialService.verifyAuthToken(token)
|
const vToken = await this.credentialService.verifyAuthToken(token);
|
||||||
|
|
||||||
const user = await this.databaseService.use()
|
const user = await this.databaseService.use()
|
||||||
.select()
|
.select()
|
||||||
.from(UsersTable)
|
.from(UsersTable)
|
||||||
.where(eq(UsersTable.uuid, vToken.payload.sub))
|
.where(eq(UsersTable.uuid, vToken.payload.sub));
|
||||||
|
|
||||||
if (user.length !== 1)
|
if (user.length !== 1)
|
||||||
throw new UnauthorizedException('No such user found.');
|
throw new UnauthorizedException("No such user found.");
|
||||||
|
|
||||||
if (user[0].emailCode)
|
if (user[0].emailCode)
|
||||||
throw new UnauthorizedException('Email not verified.');
|
throw new UnauthorizedException("Email not verified.");
|
||||||
|
|
||||||
// Inject user ID into request body
|
// Inject user ID into request body
|
||||||
request.body.sourceUserId = vToken.payload.sub;
|
request.body.sourceUserId = vToken.payload.sub;
|
||||||
@ -49,33 +47,34 @@ export class UserGuard implements CanActivate {
|
|||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AdminGuard implements CanActivate {
|
export class AdminGuard implements CanActivate {
|
||||||
constructor(
|
|
||||||
private readonly credentialService: CredentialsService,
|
|
||||||
private readonly databaseService: DrizzleService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@Inject(CredentialsService) private readonly credentialService: CredentialsService,
|
||||||
|
@Inject(DrizzleService) private readonly databaseService: DrizzleService,
|
||||||
|
) {}
|
||||||
async canActivate(
|
async canActivate(
|
||||||
context: ExecutionContext,
|
context: ExecutionContext
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const request: Request = context.switchToHttp().getRequest();
|
const request: Request = context.switchToHttp().getRequest();
|
||||||
|
|
||||||
const authHeader = request.headers.authorization;
|
const authHeader = request.headers.authorization;
|
||||||
|
if (!authHeader) {
|
||||||
|
throw new UnauthorizedException("No authorization header found.");
|
||||||
|
|
||||||
if (!authHeader)
|
}
|
||||||
throw new UnauthorizedException('No authorization header found.');
|
const token = authHeader.split(" ")[1];
|
||||||
|
const vToken = await this.credentialService.verifyAuthToken(token);
|
||||||
const token = authHeader.split(' ')[1];
|
|
||||||
const vToken = await this.credentialService.verifyAuthToken(token)
|
|
||||||
|
|
||||||
const user = await this.databaseService.use()
|
const user = await this.databaseService.use()
|
||||||
.select()
|
.select()
|
||||||
.from(UsersTable)
|
.from(UsersTable)
|
||||||
.where(eq(UsersTable.uuid, vToken.payload.sub))
|
.where(eq(UsersTable.uuid, vToken.payload.sub));
|
||||||
|
|
||||||
if (user.length !== 1)
|
if (user.length !== 1)
|
||||||
throw new UnauthorizedException('No such user found.');
|
throw new UnauthorizedException("No such user found.");
|
||||||
|
|
||||||
if (!user[0].isAdmin) {
|
if (!user[0].isAdmin) {
|
||||||
throw new UnauthorizedException('Administrator only..');
|
throw new UnauthorizedException("Administrator only..");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject user ID into request body
|
// Inject user ID into request body
|
||||||
|
@ -3,11 +3,12 @@ import { CredentialsModule } from "src/credentials/credentials.module";
|
|||||||
import { DrizzleModule } from "src/drizzle/drizzle.module";
|
import { DrizzleModule } from "src/drizzle/drizzle.module";
|
||||||
import { AuthController } from "./auth.controller";
|
import { AuthController } from "./auth.controller";
|
||||||
import { AuthService } from "./auth.service";
|
import { AuthService } from "./auth.service";
|
||||||
import { AdminGuard } from "src/auth/auth.guard";
|
import { AdminGuard, UserGuard } from "src/auth/auth.guard";
|
||||||
|
import { CredentialsService } from "src/credentials/credentials.service";
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [DrizzleModule, CredentialsModule],
|
imports: [DrizzleModule, CredentialsModule],
|
||||||
providers: [AuthService, AdminGuard],
|
providers: [AuthService, CredentialsService, AdminGuard, UserGuard],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController],
|
||||||
})
|
})
|
||||||
export class AuthModule {}
|
export class AuthModule {}
|
||||||
|
@ -75,7 +75,23 @@ export class AuthService implements OnModuleInit {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async fetchUser() {
|
async fetchUserById(userId: string) {
|
||||||
|
const user = await this.db
|
||||||
|
.use()
|
||||||
|
.select()
|
||||||
|
.from(UsersTable)
|
||||||
|
.where(eq(UsersTable.uuid, userId))
|
||||||
|
.prepare("userById")
|
||||||
|
.execute();
|
||||||
|
if (user.length !== 1) {
|
||||||
|
throw new UnauthorizedException("User not found");
|
||||||
|
}
|
||||||
|
delete user[0].hash;
|
||||||
|
delete user[0].emailCode;
|
||||||
|
return user[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
async fetchUsers() {
|
||||||
//TODO Pagination
|
//TODO Pagination
|
||||||
const usersInDb = await this.db.use().select().from(UsersTable);
|
const usersInDb = await this.db.use().select().from(UsersTable);
|
||||||
const result = {
|
const result = {
|
||||||
@ -134,7 +150,7 @@ export class AuthService implements OnModuleInit {
|
|||||||
|
|
||||||
async onModuleInit() {
|
async onModuleInit() {
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.fetchUser();
|
this.fetchUsers();
|
||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user