feat(backend): forgot password (#250)

* feat(backend): forgot password

* feat: apply feedback from code review

* chore(auth): validate the minimum length of 'newPassword'

* chore(auth): make token has an expiry of 1 hour

* chore: rename all occurrences of 'code' to 'token'

* chore(backend): provide value on nanoIdGen method
This commit is contained in:
Reinaldy Rafli
2024-09-17 21:52:47 +07:00
committed by GitHub
parent 6a3a7721be
commit f34812653e
12 changed files with 306 additions and 16 deletions
@@ -0,0 +1,27 @@
import { sql, Kysely } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await db.schema
.createTable('user_tokens')
.addColumn('id', 'uuid', (col) =>
col.primaryKey().defaultTo(sql`gen_uuid_v7()`),
)
.addColumn('token', 'varchar', (col) => col.notNull())
.addColumn('user_id', 'uuid', (col) =>
col.notNull().references('users.id').onDelete('cascade'),
)
.addColumn('workspace_id', 'uuid', (col) =>
col.references('workspaces.id').onDelete('cascade'),
)
.addColumn('type', 'varchar', (col) => col.notNull())
.addColumn('expires_at', 'timestamptz')
.addColumn('used_at', 'timestamptz', (col) => col)
.addColumn('created_at', 'timestamptz', (col) =>
col.notNull().defaultTo(sql`now()`),
)
.execute();
}
export async function down(db: Kysely<any>): Promise<void> {
await db.schema.dropTable('user_tokens').execute();
}
@@ -0,0 +1,89 @@
import {
InsertableUserToken,
UpdatableUserToken,
} from '@docmost/db/types/entity.types';
import { KyselyDB, KyselyTransaction } from '@docmost/db/types/kysely.types';
import { dbOrTx } from '@docmost/db/utils';
import { Injectable } from '@nestjs/common';
import { InjectKysely } from 'nestjs-kysely';
@Injectable()
export class UserTokensRepo {
constructor(@InjectKysely() private readonly db: KyselyDB) {}
async insertUserToken(
insertableUserToken: InsertableUserToken,
trx?: KyselyTransaction,
) {
const db = dbOrTx(this.db, trx);
return db
.insertInto('userTokens')
.values(insertableUserToken)
.returningAll()
.executeTakeFirst();
}
async findByUserId(
userId: string,
workspaceId: string,
tokenType: string,
trx?: KyselyTransaction,
) {
const db = dbOrTx(this.db, trx);
return db
.selectFrom('userTokens')
.select([
'id',
'token',
'user_id',
'workspace_id',
'type',
'expires_at',
'used_at',
'created_at',
])
.where('user_id', '=', userId)
.where('workspace_id', '=', workspaceId)
.where('type', '=', tokenType)
.orderBy('expires_at desc')
.execute();
}
async updateUserToken(
updatableUserToken: UpdatableUserToken,
userTokenId: string,
trx?: KyselyTransaction,
) {
const db = dbOrTx(this.db, trx);
return db
.updateTable('userTokens')
.set({ ...updatableUserToken })
.where('id', '=', userTokenId)
.execute();
}
async deleteUserToken(
userId: string,
workspaceId: string,
tokenType: string,
trx?: KyselyTransaction,
) {
const db = dbOrTx(this.db, trx);
return db
.deleteFrom('userTokens')
.where('user_id', '=', userId)
.where('workspace_id', '=', workspaceId)
.where('type', '=', tokenType)
.execute();
}
async deleteExpiredUserTokens(
trx?: KyselyTransaction,
) {
const db = dbOrTx(this.db, trx);
return db
.deleteFrom('userTokens')
.where('expires_at', '<', new Date())
.execute();
}
}
+22 -5
View File
@@ -1,10 +1,15 @@
import type { ColumnType } from "kysely";
import type { ColumnType } from 'kysely';
export type Generated<T> = T extends ColumnType<infer S, infer I, infer U>
? ColumnType<S, I | undefined, U>
: ColumnType<T, T | undefined, T>;
export type Generated<T> =
T extends ColumnType<infer S, infer I, infer U>
? ColumnType<S, I | undefined, U>
: ColumnType<T, T | undefined, T>;
export type Int8 = ColumnType<string, bigint | number | string, bigint | number | string>;
export type Int8 = ColumnType<
string,
bigint | number | string,
bigint | number | string
>;
export type Json = JsonValue;
@@ -185,6 +190,17 @@ export interface Workspaces {
updatedAt: Generated<Timestamp>;
}
export interface UserTokens {
id: Generated<string>;
token: string;
user_id: string;
workspace_id: string;
type: string;
expires_at: Timestamp | null;
used_at: Timestamp | null;
created_at: Generated<Timestamp>;
}
export interface DB {
attachments: Attachments;
comments: Comments;
@@ -197,4 +213,5 @@ export interface DB {
users: Users;
workspaceInvitations: WorkspaceInvitations;
workspaces: Workspaces;
userTokens: UserTokens;
}
@@ -11,6 +11,7 @@ import {
GroupUsers,
SpaceMembers,
WorkspaceInvitations,
UserTokens,
} from './db';
// Workspace
@@ -71,3 +72,8 @@ export type UpdatableComment = Updateable<Omit<Comments, 'id'>>;
export type Attachment = Selectable<Attachments>;
export type InsertableAttachment = Insertable<Attachments>;
export type UpdatableAttachment = Updateable<Omit<Attachments, 'id'>>;
// User Tokens
export type UserToken = Selectable<UserTokens>;
export type InsertableUserToken = Insertable<UserTokens>;
export type UpdatableUserToken = Updateable<Omit<UserTokens, 'id'>>;