Skip to content

Commit b9b87d9

Browse files
Feat: Reset password flow (#43)
* feat: Reset password flow * Fix format * Update controllers to use * Add test * Rebase with main * fix ci * Fix comments * Fix comments * Add test * Fix comment
1 parent 57e7d33 commit b9b87d9

File tree

22 files changed

+423
-58
lines changed

22 files changed

+423
-58
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ REDIS_PASSWORD=''
2121
REDIS_PORT=6379
2222
REDIS_USERNAME=''
2323
JOBS_RETENTION_HOURS=24
24+
25+
OTP_EXPIRATION_MINUTES=15

.github/workflows/node.js.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ env:
2929
REDIS_PORT: '6379'
3030
REDIS_USERNAME: 'default'
3131
JOBS_RETENTION_HOURS: '24'
32+
OTP_EXPIRATION_MINUTES: '15'
3233

3334
jobs:
3435
build:

.woodpecker/.backend-ci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ x-common: &common
2121
- REDIS_PORT=6379
2222
- REDIS_USERNAME=default
2323
- JOBS_RETENTION_HOURS=24
24+
- OTP_EXPIRATION_MINUTES=15
2425

2526
pipeline:
2627
setup:

package-lock.json

Lines changed: 3 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"concurrently": "^8.2.0",
7171
"cors": "^2.8.5",
7272
"cross-spawn": "^7.0.3",
73+
"date-fns": "^2.30.0",
7374
"dotenv": "^16.0.0",
7475
"dotenv-cli": "^7.3.0",
7576
"express": "^4.17.1",
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
-- CreateEnum
2+
CREATE TYPE "TypeToken" AS ENUM ('RESET_PASSWORD');
3+
4+
-- CreateTable
5+
CREATE TABLE "Tokens" (
6+
"id" TEXT NOT NULL,
7+
"created_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
8+
"updated_at" TIMESTAMP(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
9+
"token" TEXT NOT NULL,
10+
"type" "TypeToken" NOT NULL,
11+
"expires_at" TIMESTAMP(3) NOT NULL,
12+
"user_id" TEXT,
13+
14+
CONSTRAINT "Tokens_pkey" PRIMARY KEY ("id")
15+
);
16+
17+
-- CreateIndex
18+
CREATE UNIQUE INDEX "Tokens_user_id_type_key" ON "Tokens"("user_id", "type");
19+
20+
-- AddForeignKey
21+
ALTER TABLE "Tokens" ADD CONSTRAINT "Tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;

prisma/schema.prisma

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ model User {
1818
email String @unique
1919
password String
2020
name String?
21+
token Tokens[]
2122
}
2223

2324
model Session {
@@ -28,3 +29,20 @@ model Session {
2829
accessToken String @unique
2930
refreshToken String @unique
3031
}
32+
33+
model Tokens {
34+
id String @id @default(uuid())
35+
createdAt DateTime @default(now()) @map("created_at") @db.Timestamp(6)
36+
updatedAt DateTime @default(now()) @updatedAt @map("updated_at") @db.Timestamp(6)
37+
token String
38+
type TypeToken
39+
expiresAt DateTime @map("expires_at")
40+
userId String? @map("user_id")
41+
user User? @relation(fields: [userId], references: [id])
42+
43+
@@unique([userId, type])
44+
}
45+
46+
enum TypeToken {
47+
RESET_PASSWORD
48+
}

src/config/config.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ const envVarsSchema = z
4747
'JOBS RETENTION HOURS must be a number',
4848
),
4949
REDIS_USERNAME: z.string(),
50+
OTP_EXPIRATION_MINUTES: z
51+
.string()
52+
.transform((val) => Number(val))
53+
.refine(
54+
(val) => !Number.isNaN(val),
55+
'OTP EXPIRATION TIME must be a number',
56+
),
5057
})
5158
.passthrough();
5259

@@ -76,4 +83,5 @@ export const config: Config = {
7683
redisPort: envVars.REDIS_PORT,
7784
redisUsername: envVars.REDIS_USERNAME,
7885
jobsRetentionHours: envVars.JOBS_RETENTION_HOURS,
86+
otpExpirationMinutes: envVars.OTP_EXPIRATION_MINUTES,
7987
};

src/config/errors.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ export const errors = {
1919
errorCode: 400_003,
2020
description: 'Invalid token',
2121
},
22+
INVALID_CODE: {
23+
httpCode: 400,
24+
errorCode: 400_004,
25+
description: 'Invalid code',
26+
},
2227
UNAUTHENTICATED: {
2328
httpCode: 401,
2429
errorCode: 401_000,
@@ -29,6 +34,11 @@ export const errors = {
2934
errorCode: 401_001,
3035
description: 'Token expired',
3136
},
37+
CODE_EXPIRED: {
38+
httpCode: 403,
39+
errorCode: 403_001,
40+
description: 'Code has expired',
41+
},
3242
NOT_FOUND: {
3343
httpCode: 404,
3444
errorCode: 404_000,

src/controllers/users.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,20 @@ import {
55
Delete,
66
Get,
77
Path,
8+
Post,
89
Put,
910
Request,
1011
Route,
1112
Security,
1213
} from 'tsoa';
1314
import { UserService } from 'services';
14-
import { ReturnUser, UpdateUserParams, AuthenticatedRequest } from 'types';
15+
import {
16+
ReturnUser,
17+
UpdateUserParams,
18+
AuthenticatedRequest,
19+
PasswordResetCodeRequest,
20+
ResetPassword,
21+
} from 'types';
1522

1623
@Route('v1/users')
1724
export class UsersControllerV1 extends Controller {
@@ -58,4 +65,21 @@ export class UsersControllerV1 extends Controller {
5865
await UserService.destroy(id);
5966
this.setStatus(httpStatus.NO_CONTENT);
6067
}
68+
69+
@Post('/requestResetPasswordCode')
70+
public async requestResetPasswordCode(
71+
@Body() requestBody: PasswordResetCodeRequest,
72+
): Promise<void> {
73+
await UserService.requestResetPasswordCode(requestBody.email);
74+
this.setStatus(httpStatus.OK);
75+
}
76+
77+
@Post('/resetPassword')
78+
public async resetPassword(
79+
@Body() requestBody: ResetPassword,
80+
): Promise<void> {
81+
const { email, code, newPassword } = requestBody;
82+
await UserService.resetPassword(email, code, newPassword);
83+
this.setStatus(httpStatus.OK);
84+
}
6185
}

0 commit comments

Comments
 (0)