Skip to content

Commit

Permalink
feat(iam): forgot-password http controller + specs
Browse files Browse the repository at this point in the history
  • Loading branch information
Gi-jutsu committed Oct 11, 2024
1 parent dc087e2 commit 3865a86
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 34 deletions.
16 changes: 16 additions & 0 deletions src/identity-and-access/identity-and-access.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { JwtServiceToken } from "@identity-and-access/infrastructure/services/jwt.service.js";
import { Module } from "@nestjs/common";
import { AccountRepositoryToken } from "./domain/account/repository.js";
import { PasswordResetRequestRepositoryToken } from "./domain/password-reset-request/repository.js";
import { DrizzleAccountRepository } from "./infrastructure/repositories/drizzle-account.repository.js";
import { InMemoryPasswordResetRequestRepository } from "./infrastructure/repositories/in-memory-password-reset-request.repository.js";
import { ForgotPasswordHttpController } from "./use-cases/forgot-password/http.controller.js";
import { ForgotPasswordUseCase } from "./use-cases/forgot-password/use-case.js";
import { SignInWithCredentialsHttpController } from "./use-cases/sign-in-with-credentials/http.controller.js";
import { SignInWithCredentialsUseCase } from "./use-cases/sign-in-with-credentials/use-case.js";
import { SignUpWithCredentialsHttpController } from "./use-cases/sign-up-with-credentials/http.controller.js";
import { SignUpWithCredentialsUseCase } from "./use-cases/sign-up-with-credentials/use-case.js";

@Module({
controllers: [
ForgotPasswordHttpController,
SignInWithCredentialsHttpController,
SignUpWithCredentialsHttpController,
],
Expand All @@ -18,6 +23,10 @@ import { SignUpWithCredentialsUseCase } from "./use-cases/sign-up-with-credentia
provide: AccountRepositoryToken,
useClass: DrizzleAccountRepository,
},
{
provide: PasswordResetRequestRepositoryToken,
useClass: InMemoryPasswordResetRequestRepository,
},

/** Services */
{
Expand All @@ -26,6 +35,13 @@ import { SignUpWithCredentialsUseCase } from "./use-cases/sign-up-with-credentia
},

/** Use cases */
{
provide: ForgotPasswordUseCase,
useFactory: (
...args: ConstructorParameters<typeof ForgotPasswordUseCase>
) => new ForgotPasswordUseCase(...args),
inject: [AccountRepositoryToken, PasswordResetRequestRepositoryToken],
},
{
provide: SignInWithCredentialsUseCase,
useFactory: (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { Server } from "http";
import supertest from "supertest";
import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { bootstrap } from "../../../main.js";

describe("ForgotPasswordHttpController", () => {
let server: Server;

beforeAll(async () => {
server = await bootstrap();
});

afterAll(async () => {
server.close();
});

describe("POST /auth/forgot-password", () => {
it("should return 204 when the password reset process has been initiated", async () => {
const response = await supertest(server)
.post("/auth/forgot-password")
.send({ email: "[email protected]" });

expect(response.status).toEqual(204);
});

it("should return 404 when attempting to reset password for an unregistered email address", async () => {
const response = await supertest(server)
.post("/auth/forgot-password")
.send({ email: "[email protected]" });

expect(response.status).toEqual(404);
expect(response.body).toEqual({
code: "resource-not-found",
detail: "The Account you are trying to access does not exist.",
pointer: "/data/attributes/email",
resource: "Account",
searchedByFieldName: "email",
searchedByValue: "[email protected]",
status: 404,
timestamp: expect.any(String),
title: "Resource Not Found",
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Body, Controller, HttpCode, HttpStatus, Post } from "@nestjs/common";
import { ForgotPasswordHttpRequestBody } from "./http.request.js";
import { ForgotPasswordUseCase } from "./use-case.js";

@Controller()
export class ForgotPasswordHttpController {
constructor(private readonly useCase: ForgotPasswordUseCase) {}

@HttpCode(HttpStatus.NO_CONTENT)
@Post("/auth/forgot-password")
async handle(@Body() body: ForgotPasswordHttpRequestBody) {
return await this.useCase.execute(body);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { IsEmail } from "class-validator";

export class ForgotPasswordHttpRequestBody {
@IsEmail()
email: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ describe("SignInWithCredentialsHttpController", () => {
server.close();
});

describe("POST /authentication/sign-in", () => {
describe("POST /auth/sign-in", () => {
it("should return 200 and set the access token in a secure, HTTP-only, SameSite=Strict cookie when the credentials are valid", async () => {
const response = await supertest(server)
.post("/authentication/sign-in")
.send({
email: "[email protected]",
password: "password",
});
const response = await supertest(server).post("/auth/sign-in").send({
email: "[email protected]",
password: "password",
});

expect(response.status).toEqual(200);
expect(response.headers["set-cookie"][0]).toMatch(
Expand All @@ -32,12 +30,10 @@ describe("SignInWithCredentialsHttpController", () => {
});

it("should return 404 when the account does not exist", async () => {
const response = await supertest(server)
.post("/authentication/sign-in")
.send({
email: "[email protected]",
password: "password",
});
const response = await supertest(server).post("/auth/sign-in").send({
email: "[email protected]",
password: "password",
});

expect(response.status).toEqual(404);
expect(response.body).toEqual({
Expand All @@ -54,12 +50,10 @@ describe("SignInWithCredentialsHttpController", () => {
});

it("should return 401 when the password is invalid", async () => {
const response = await supertest(server)
.post("/authentication/sign-in")
.send({
email: "[email protected]",
password: "wrong-password",
});
const response = await supertest(server).post("/auth/sign-in").send({
email: "[email protected]",
password: "wrong-password",
});

expect(response.status).toEqual(401);
expect(response.body).toEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { SignInWithCredentialsUseCase } from "./use-case.js";
export class SignInWithCredentialsHttpController {
constructor(private readonly useCase: SignInWithCredentialsUseCase) {}

@Post("/authentication/sign-in")
@Post("/auth/sign-in")
@HttpCode(HttpStatus.OK)
async handle(
@Body() body: SignInWithCredentialsHttpRequestBody,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,12 @@ describe("SignInWithCredentialsHttpController", () => {
server.close();
});

describe("POST /authentication/sign-up", () => {
describe("POST /auth/sign-up", () => {
it("should return 201 when the account is created", async () => {
const response = await supertest(server)
.post("/authentication/sign-up")
.send({
email: "[email protected]",
password: "password",
});
const response = await supertest(server).post("/auth/sign-up").send({
email: "[email protected]",
password: "password",
});

expect(response.status).toEqual(201);
expect(response.body).toEqual({
Expand All @@ -32,12 +30,10 @@ describe("SignInWithCredentialsHttpController", () => {
});

it("should return 409 when the email is already taken", async () => {
const response = await supertest(server)
.post("/authentication/sign-up")
.send({
email: "[email protected]",
password: "password",
});
const response = await supertest(server).post("/auth/sign-up").send({
email: "[email protected]",
password: "password",
});

expect(response.status).toEqual(409);
expect(response.body).toEqual({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { SignUpWithCredentialsUseCase } from "./use-case.js";
export class SignUpWithCredentialsHttpController {
constructor(private readonly useCase: SignUpWithCredentialsUseCase) {}

@Post("/authentication/sign-up")
@Post("/auth/sign-up")
@HttpCode(HttpStatus.CREATED)
async handle(@Body() body: SignUpWithCredentialsHttpRequestBody) {
return await this.useCase.execute({
Expand Down

0 comments on commit 3865a86

Please sign in to comment.