Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/delete users tokens #116

Merged
merged 24 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
778612f
chore: add .yarnrc.yml file with nodeLinker configuration set to node…
timDeHof Mar 7, 2024
c6f9db6
feat(auth.service.ts): add revoke method to AuthService for revoking …
timDeHof Mar 8, 2024
3186193
chore(auth.controller.ts): add revoke endpoint to allow revoking user…
timDeHof Mar 8, 2024
51c1cf4
feat(revoke.dto.ts): add RevokeRTDTo class with userId and email prop…
timDeHof Mar 8, 2024
03e911e
feat(auth.e2e-spec.ts): add tests for revoking refresh token function…
timDeHof Mar 8, 2024
29e2187
refactor(auth.service.ts): remove console.log statement in the revoke…
timDeHof Mar 8, 2024
3984ddb
chore(revoke.dto.ts): add IsOptional decorator to userId and email pr…
timDeHof Mar 8, 2024
de71fb6
updated with ➤ YN0000: ┌ Resolution step
timDeHof Mar 8, 2024
354b2ed
feat(revoke-refresh-token.dto.ts): change filename to revoke-refresh-…
timDeHof Mar 8, 2024
e948e97
fix(auth.controller.ts): fix import statement for RevokeRTDTo to poin…
timDeHof Mar 8, 2024
56121ab
refactor(auth.service.ts): rename revoke method to revokeRefreshToken…
timDeHof Mar 8, 2024
97ff5a1
test(auth.e2e-spec.ts): add test case to check for 400 status code wh…
timDeHof Mar 8, 2024
e4f53ea
Merge branch 'dev' into feat/delete-users-tokens
timDeHof Mar 8, 2024
9eedd58
chore(CHANGELOG.md): updated changelog to include new DELETE endpoint…
timDeHof Mar 8, 2024
d15105b
Merge branch 'feat/delete-users-tokens' of github.com:chingu-x/chingu…
timDeHof Mar 8, 2024
a7b9570
fix(auth.controller.ts): fix typo in import statement and parameter n…
timDeHof Mar 10, 2024
239a9f9
fix(revoke-refresh-token.dto.ts): fix typo in class name from RevokeR…
timDeHof Mar 10, 2024
2b312b2
refactor(auth.controller.ts): remove unused RolesGuard import and usage
timDeHof Mar 10, 2024
c21cf2d
test(auth.e2e-spec.ts): add test to check if refresh token is null fo…
timDeHof Mar 10, 2024
ae6c9e5
chore(auth.controller.ts): improve the summary and description of the…
timDeHof Mar 11, 2024
c8bd005
chore(auth.service.ts): add error handling when revoking refresh toke…
timDeHof Mar 11, 2024
639358d
fix(auth.e2e-spec.ts): change revokeRTUrl endpoint from "/auth/refres…
timDeHof Mar 11, 2024
c6d6da1
Update src/auth/auth.service.ts
timDeHof Mar 13, 2024
b8eec4b
Update src/auth/auth.service.ts
timDeHof Mar 13, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .yarnrc.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
nodeLinker: node-modules
38 changes: 20 additions & 18 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,31 +6,33 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Another example [here](https://co-pilot.dev/changelog)


## [Unreleased]

### Added

- Add @ApiResponse tags to resources ([#76](https://github.com/chingu-x/chingu-dashboard-be/pull/76))
- Add @ApiResponse tags to ideations and features ([#65](https://github.com/chingu-x/chingu-dashboard-be/pull/77))
- Add refresh token functionality and global guard to protect all routes ([#78](https://github.com/chingu-x/chingu-dashboard-be/pull/78))-
- Add status to voyage table and return in /me endpoint ([#79](https://github.com/chingu-x/chingu-dashboard-be/pull/79))
- Add github action for STG ([#81](https://github.com/chingu-x/chingu-dashboard-be/pull/81))
- Add CHANGELOG.md ([#84](https://github.com/chingu-x/chingu-dashboard-be/pull/84))
- Add Role/Permission guard ([#97](https://github.com/chingu-x/chingu-dashboard-be/pull/97))
- Add e2e tests for auth controller ([#102](https://github.com/chingu-x/chingu-dashboard-be/pull/102))
- Add e2e tests for techs controller ([#103](https://github.com/chingu-x/chingu-dashboard-be/pull/103))
- Add check-in form database implementation and seed data ([#105](https://github.com/chingu-x/chingu-dashboard-be/pull/105))
- Add e2e tests for forms controller ([#107](https://github.com/chingu-x/chingu-dashboard-be/pull/107))
- Add @ApiResponse tags to resources ([#76](https://github.com/chingu-x/chingu-dashboard-be/pull/76))
- Add @ApiResponse tags to ideations and features ([#65](https://github.com/chingu-x/chingu-dashboard-be/pull/77))
- Add refresh token functionality and global guard to protect all routes ([#78](https://github.com/chingu-x/chingu-dashboard-be/pull/78))-
- Add status to voyage table and return in /me endpoint ([#79](https://github.com/chingu-x/chingu-dashboard-be/pull/79))
- Add github action for STG ([#81](https://github.com/chingu-x/chingu-dashboard-be/pull/81))
- Add CHANGELOG.md ([#84](https://github.com/chingu-x/chingu-dashboard-be/pull/84))
- Add Role/Permission guard ([#97](https://github.com/chingu-x/chingu-dashboard-be/pull/97))
- Add e2e tests for auth controller ([#102](https://github.com/chingu-x/chingu-dashboard-be/pull/102))
- Add e2e tests for techs controller ([#103](https://github.com/chingu-x/chingu-dashboard-be/pull/103))
- Add check-in form database implementation and seed data ([#105](https://github.com/chingu-x/chingu-dashboard-be/pull/105))
- Add e2e tests for forms controller ([#107](https://github.com/chingu-x/chingu-dashboard-be/pull/107))
- Add new endpoint to revoke refresh token ([#116](https://github.com/chingu-x/chingu-dashboard-be/pull/116))

### Changed
- Update docker compose and scripts in package.json to include a test database container and remove usage of .env.dev to avoid confusion ([#100](https://github.com/chingu-x/chingu-dashboard-be/pull/100))
- Restructure seed/index.ts to work with e2e tests, and add --runInBand to e2e scripts[#101](https://github.com/chingu-x/chingu-dashboard-be/pull/101)
- Update changelog ([#104](https://github.com/chingu-x/chingu-dashboard-be/pull/104))
- Update test.yml to run e2e tests on pull requests to the main branch [#105](https://github.com/chingu-x/chingu-dashboard-be/pull/105)
- Update email templates to use domain in environment variables [#110](https://github.com/chingu-x/chingu-dashboard-be/pull/110)

- Update docker compose and scripts in package.json to include a test database container and remove usage of .env.dev to avoid confusion ([#100](https://github.com/chingu-x/chingu-dashboard-be/pull/100))
- Restructure seed/index.ts to work with e2e tests, and add --runInBand to e2e scripts[#101](https://github.com/chingu-x/chingu-dashboard-be/pull/101)
- Update changelog ([#104](https://github.com/chingu-x/chingu-dashboard-be/pull/104))
- Update test.yml to run e2e tests on pull requests to the main branch [#105](https://github.com/chingu-x/chingu-dashboard-be/pull/105)
- Update email templates to use domain in environment variables [#110](https://github.com/chingu-x/chingu-dashboard-be/pull/110)

### Fixed
- Fix failed tests in app and ideation due to the change from jwt token response to http cookies ([#98](https://github.com/chingu-x/chingu-dashboard-be/pull/98))

- Fix failed tests in app and ideation due to the change from jwt token response to http cookies ([#98](https://github.com/chingu-x/chingu-dashboard-be/pull/98))

### Removed
36 changes: 36 additions & 0 deletions src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
BadRequestException,
Body,
Controller,
Delete,
HttpCode,
HttpStatus,
Post,
Expand All @@ -20,6 +21,7 @@ import {
BadRequestErrorResponse,
ForbiddenErrorResponse,
LoginUnauthorizedErrorResponse,
NotFoundErrorResponse,
UnauthorizedErrorResponse,
} from "../global/responses/errors";
import {
Expand All @@ -34,6 +36,9 @@ import { JwtAuthGuard } from "./guards/jwt-auth.guard";
import { JwtRefreshAuthGuard } from "./guards/jwt-rt-auth.guard";
import { Public } from "../global/decorators/public.decorator";
import { AT_MAX_AGE, RT_MAX_AGE } from "../global/constants";
import { RevokeRTDto } from "./dto/revoke-refresh-token.dto";
import { Roles } from "../global/decorators/roles.decorator";
import { AppRoles } from "./auth.roles";

@ApiTags("Auth")
@Controller("auth")
Expand Down Expand Up @@ -196,6 +201,37 @@ export class AuthController {
res.status(HttpStatus.OK).send({ message: "Refresh Success" });
}

@ApiOperation({
summary: "Revokes user's refresh token, with a valid user id",
description: "using the user's id, removes user's refresh token",
})
@ApiResponse({
status: HttpStatus.OK,
description: "Refresh token successfully revoked",
type: GenericSuccessResponse,
})
@ApiResponse({
status: HttpStatus.NOT_FOUND,
description: "User not found.",
type: NotFoundErrorResponse,
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: "userId and email is provided",
type: BadRequestErrorResponse,
})
@ApiResponse({
status: HttpStatus.FORBIDDEN,
description: "user doesn't have permission to preform operation",
type: ForbiddenErrorResponse,
})
@HttpCode(HttpStatus.OK)
@Roles(AppRoles.Admin)
@Delete("refresh/userId")
async revoke(@Body() body: RevokeRTDto) {
await this.authService.revokeRefreshToken(body);
}

@ApiOperation({
summary:
"When a user logs out, access and refresh tokens are cleared from cookies, refresh token is set to null in the database.",
Expand Down
35 changes: 35 additions & 0 deletions src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
ForbiddenException,
Injectable,
Logger,
NotFoundException,
UnauthorizedException,
} from "@nestjs/common";
import { UsersService } from "../users/users.service";
Expand Down Expand Up @@ -133,6 +134,40 @@ export class AuthService {
return tokens;
}

async revokeRefreshToken(body?: any): Promise<void> {
const { userId, email } = body;

if (userId && email)
throw new BadRequestException(
"Please provide either userId or email, not both",
);

if (userId) {
await this.prisma.user.update({
where: {
id: userId,
},
data: {
refreshToken: null,
},
});
} else if (email) {
await this.prisma.user.update({
where: {
email: email,
},
data: {
refreshToken: null,
},
});
} else {
throw new NotFoundException({
statusCode: 404,
message: "User not found",
});
}
}

async logout(refreshToken: string) {
try {
const payload = await this.jwtService.verifyAsync(refreshToken, {
Expand Down
20 changes: 20 additions & 0 deletions src/auth/dto/revoke-refresh-token.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { ApiProperty } from "@nestjs/swagger";
import { IsNotEmpty, IsOptional, IsString } from "class-validator";

export class RevokeRTDto {
@IsOptional()
@IsString()
@IsNotEmpty()
@ApiProperty({
example: "ebf84db9-3c8d-4900-a1eb-6a0050e983bb",
})
userId?: string;

@IsOptional()
@IsString()
@IsNotEmpty()
@ApiProperty({
example: "elza59@ethereal.email",
})
email?: "elza59@ethereal.email";
}
97 changes: 96 additions & 1 deletion test/auth.e2e-spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const resendUrl = "/auth/resend-email";
const verifyUrl = "/auth/verify-email";
const resetRequestUrl = "/auth/reset-password/request";
const resetPWUrl = "/auth/reset-password";

const revokeRTUrl = "/auth/refresh/userId";
const loginAndGetTokens = async (
email: string,
password: string,
Expand Down Expand Up @@ -421,6 +421,101 @@ describe("AuthController e2e Tests", () => {
});
});

describe("Revoke Refresh Token DELETE /auth/refresh/userId", () => {
it("should return 200 if refresh token is revoked", async () => {
await loginAndGetTokens("l.castro@outlook.com", "password", app);

const { access_token, refresh_token } = await loginAndGetTokens(
"jessica.williamson@gmail.com",
"password",
app,
);

await prisma.user.update({
where: {
email: "l.castro@outlook.com",
},
data: {
refreshToken: null,
},
});

await request(app.getHttpServer())
.delete(revokeRTUrl)
.set("Cookie", [access_token, refresh_token])
.send({ email: "l.castro@outlook.com" })
.expect(200);
timDeHof marked this conversation as resolved.
Show resolved Hide resolved
});

describe("checks if refresh token is null", () => {
it("should return null for user's refresh token", async () => {
const userEmail = "l.castro@outlook.com";
const updatedUser = await prisma.user.findUnique({
where: {
id: await getUserIdByEmail(userEmail, prisma),
},
select: {
refreshToken: true,
},
});
expect(updatedUser.refreshToken).toBeNull();
});
});

it("should return 401 if email is invalid", async () => {
await loginAndGetTokens("l.castro@outlook.com", "password", app);

const { access_token, refresh_token } = await loginAndGetTokens(
"jessica.williamson@gmail.com",
"password",
app,
);

await request(app.getHttpServer())
.delete(revokeRTUrl)
.set("Cookie", [access_token, refresh_token])
.send({})
.expect(404);
});

it("should return 403 if email and user id is provided", async () => {
await loginAndGetTokens("l.castro@outlook.com", "password", app);

const { access_token, refresh_token } = await loginAndGetTokens(
"jessica.williamson@gmail.com",
"password",
app,
);

await request(app.getHttpServer())
.delete(revokeRTUrl)
.set("Cookie", [access_token, refresh_token])
.send({
userId: "c4daf07c-8dde-4a43-bfa4-a6fd49762dd5",
email: "l.castro@outlook.com",
})
.expect(400);
});

it("should return 403 if user is not permitted", async () => {
timDeHof marked this conversation as resolved.
Show resolved Hide resolved
await loginAndGetTokens("l.castro@outlook.com", "password", app);

const { access_token, refresh_token } = await loginAndGetTokens(
"dan@random.com",
"password",
app,
);

await request(app.getHttpServer())
.delete(revokeRTUrl)
.set("Cookie", [access_token, refresh_token])
.send({
email: "l.castro@outlook.com",
})
.expect(403);
});
});

describe("Request password reset POST /auth/reset-password/request", () => {
it("should return 200 if user account exist, resetToken should be in the database", async () => {
const userEmail = "jessica.williamson@gmail.com";
Expand Down
Loading
Loading