Skip to content

feat: add createdAt timestamp to email verification and password reset entities#1821

Merged
Artuomka merged 2 commits into
mainfrom
backend_fixes
Jun 3, 2026
Merged

feat: add createdAt timestamp to email verification and password reset entities#1821
Artuomka merged 2 commits into
mainfrom
backend_fixes

Conversation

@Artuomka

@Artuomka Artuomka commented Jun 2, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Bug Fixes
    • Email verification codes now expire after 24 hours
    • Password reset codes now expire after 24 hours

Copilot AI review requested due to automatic review settings June 2, 2026 15:22
@Artuomka Artuomka enabled auto-merge June 2, 2026 15:22
@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds persistent creation timestamps to email verification and password reset records, enforcing a one-day validity window in repository lookups. Entity schemas are updated with createdAt columns, repository queries filter by this timestamp, and a database migration applies the schema changes to both tables.

Changes

Email Verification and Password Reset Timestamp Expiry

Layer / File(s) Summary
Entity createdAt Timestamp Columns
backend/src/entities/email/email-verification.entity.ts, backend/src/entities/user/user-password/password-reset.entity.ts
EmailVerificationEntity and PasswordResetEntity each define a new createdAt: Date column stored as timestamp with CURRENT_TIMESTAMP default.
Repository Query Constraints with Validity Window
backend/src/entities/email/repository/email-verification-custom-repository-extension.ts, backend/src/entities/user/user-password/repository/user-password-custom-repository-extension.ts
Both repository extensions import Constants and add createdAt >= Constants.ONE_DAY_AGO() predicates to findVerificationWithVerificationString and findPasswordResetWithVerificationString queries, restricting results to records created within the last day.
Database Migration
backend/src/migrations/1780411916141-AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities.ts
TypeORM migration class alters email_verification and password_reset tables to add non-null createdAt timestamp columns with now() default on up(), and drops both columns on down().

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐰 A timestamp here, a timestamp there,
One day to verify with utmost care!
Emails and passwords now know their age,
Fresh within hours—that's the stage!

🚥 Pre-merge checks | ✅ 6
✅ Passed checks (6 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding createdAt timestamp columns to both email verification and password reset entities across the codebase.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Check ✅ Passed Parameterized queries prevent SQL injection, 1-day token expiration enforced with NOT NULL DEFAULT constraints, tokens hashed with HMAC-SHA256, proper authorization and error handling implemented.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch backend_fixes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a createdAt timestamp to the email_verification and password_reset persistence layer and uses it to enforce a 24-hour validity window when looking up verification/reset tokens.

Changes:

  • Adds a DB migration to introduce createdAt columns (defaulting to current time) on email_verification and password_reset.
  • Updates EmailVerificationEntity and PasswordResetEntity to map the new createdAt timestamp columns.
  • Updates the custom repository lookups to only return tokens created within the last day (Constants.ONE_DAY_AGO()).

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
backend/src/migrations/1780411916141-AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities.ts Adds createdAt columns to both tables with NOT NULL + default timestamp; includes down migration.
backend/src/entities/user/user-password/repository/user-password-custom-repository-extension.ts Filters password reset lookup to last 24 hours (adds createdAt constraint) and imports Constants.
backend/src/entities/user/user-password/password-reset.entity.ts Adds createdAt column mapping to PasswordResetEntity.
backend/src/entities/email/repository/email-verification-custom-repository-extension.ts Filters email verification lookup to last 24 hours (adds createdAt constraint) and imports Constants.
backend/src/entities/email/email-verification.entity.ts Adds createdAt column mapping to EmailVerificationEntity.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +12 to 15
})
.andWhere('email_verification.createdAt >= :valid_after', {
valid_after: Constants.ONE_DAY_AGO(),
});
Comment on lines 10 to 15
.where('password_reset.verification_string = :ver_string', {
ver_string: verificationString,
})
.andWhere('password_reset.createdAt >= :valid_after', {
valid_after: Constants.ONE_DAY_AGO(),
});

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
backend/src/entities/user/user-password/password-reset.entity.ts (1)

12-13: ⚡ Quick win

Same as email-verification.entity.ts: prefer @CreateDateColumn() and consider timestamptz.

The identical idiomatic-TypeORM and timezone considerations apply to this createdAt column, since findPasswordResetWidthVerificationString compares it against a JS Date from Constants.ONE_DAY_AGO().

♻️ Proposed change
-	`@Column`({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
-	createdAt: Date;
+	`@CreateDateColumn`({ type: 'timestamptz' })
+	createdAt: Date;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/entities/user/user-password/password-reset.entity.ts` around
lines 12 - 13, Replace the manual `@Column` createdAt declaration with TypeORM's
`@CreateDateColumn` using a timezone-aware type (e.g., 'timestamptz' or 'timestamp
with time zone') and ensure CreateDateColumn is imported so createdAt is
auto-populated; this keeps behavior consistent with email-verification.entity
and prevents timezone mismatch when findPasswordResetWidthVerificationString
compares createdAt against the JS Date from Constants.ONE_DAY_AGO().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/src/entities/email/email-verification.entity.ts`:
- Around line 12-13: Replace the manual createdAt column declaration with
TypeORM's `@CreateDateColumn` to let the ORM populate insert time while keeping
the DB type aligned: in the EmailVerification entity
(email_verification.createdAt / createdAt property) swap the `@Column`({ type:
'timestamp', default: () => 'CURRENT_TIMESTAMP' }) declaration to
`@CreateDateColumn`({ type: 'timestamp' }) so the runtime uses insert-time
population and retains the existing TIMESTAMP semantics used by your ONE_DAY_AGO
cutoff; do not change to 'timestamptz' here — only migrate the column type after
adding an explicit DB migration if you later decide to use timestamptz.

---

Nitpick comments:
In `@backend/src/entities/user/user-password/password-reset.entity.ts`:
- Around line 12-13: Replace the manual `@Column` createdAt declaration with
TypeORM's `@CreateDateColumn` using a timezone-aware type (e.g., 'timestamptz' or
'timestamp with time zone') and ensure CreateDateColumn is imported so createdAt
is auto-populated; this keeps behavior consistent with email-verification.entity
and prevents timezone mismatch when findPasswordResetWidthVerificationString
compares createdAt against the JS Date from Constants.ONE_DAY_AGO().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 17782bfb-073a-489d-9a0c-7c620f275c2d

📥 Commits

Reviewing files that changed from the base of the PR and between f92662f and 1462b19.

📒 Files selected for processing (5)
  • backend/src/entities/email/email-verification.entity.ts
  • backend/src/entities/email/repository/email-verification-custom-repository-extension.ts
  • backend/src/entities/user/user-password/password-reset.entity.ts
  • backend/src/entities/user/user-password/repository/user-password-custom-repository-extension.ts
  • backend/src/migrations/1780411916141-AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities.ts

Comment on lines +12 to +13
@Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
createdAt: Date;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does TypeORM @CreateDateColumnsupport the type 'timestamptz' option, and how does it compare to@column with default CURRENT_TIMESTAMP?

💡 Result:

Yes, TypeORM's @CreateDateColumn decorator supports the type option, which allows you to specify 'timestamptz' (or 'timestamp with time zone') to ensure the database column is created with timezone awareness [1][2][3]. When you define @CreateDateColumn({ type: 'timestamptz' }), TypeORM configures the underlying database schema to use the appropriate timezone-aware timestamp type [1][2]. This is often preferred over the default behavior, which may create a naive 'timestamp' column that lacks timezone information, potentially leading to inconsistencies when storing and retrieving dates across different server or application timezones [4][2]. Comparison: @CreateDateColumn vs. @Column with default CURRENT_TIMESTAMP 1. Functional Purpose @CreateDateColumn is a specialized decorator designed specifically for tracking entity creation [5]. It automatically sets the field to the current date and time upon insertion without requiring manual intervention [5]. In contrast, @Column is a general-purpose decorator [6]. When combined with default: => 'CURRENT_TIMESTAMP', it functions similarly to @CreateDateColumn but requires you to manually define the column attributes [7][8][9]. 2. Configuration and Automation @CreateDateColumn handles metadata and behavior automatically (such as ensuring the value is set on insert) [5]. While you can override the type (e.g., to 'timestamptz'), it is intended to be used as-is [5]. Using @Column requires more verbose configuration [7]. You must explicitly provide the type, the default value (often using a SQL expression like => 'CURRENT_TIMESTAMP'), and potentially manage other behaviors manually [7][8][9]. 3. Recommendation If you need a standard "created at" field, @CreateDateColumn is the recommended approach for its simplicity and specialized integration [5]. However, if you require specific database-level configuration (such as custom default expressions, specific timezone handling beyond the default, or database-specific column types that @CreateDateColumn does not cleanly expose), using @Column with a custom default is a viable and flexible alternative [1][7]. If you encounter issues with time offsets or timezone mismatches, explicitly setting type: 'timestamptz' in either decorator is the standard fix to ensure the database preserves timezone information correctly [4][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and print the target entity file with line numbers
FILE="backend/src/entities/email/email-verification.entity.ts"
if [ -f "$FILE" ]; then
  echo "=== $FILE (head) ==="
  nl -ba "$FILE" | sed -n '1,200p'
else
  echo "File not found: $FILE" >&2
fi

# Search for findVerificationWithVerificationString usage and its definition
echo "=== Searching for findVerificationWithVerificationString ==="
rg -n "findVerificationWithVerificationString" backend || true

# If found, print surrounding implementation
# (Limit output via --max-count per match)
matches=$(rg -n "findVerificationWithVerificationString" backend -l || true)
if [ -n "${matches}" ]; then
  echo "=== Files with findVerificationWithVerificationString ==="
  echo "$matches"
  for f in $matches; do
    echo "=== $f (relevant section) ==="
    rg -n "findVerificationWithVerificationString" "$f" -n -C 5 || true
  done
fi

# Check existing TypeORM imports to see if CreateDateColumn is already imported/used elsewhere
echo "=== Searching TypeORM imports in the entity file ==="
if [ -f "$FILE" ]; then
  rg -n "from 'typeorm'" "$FILE" && rg -n "CreateDateColumn|timestamptz|timestamp" "$FILE" || true
fi

Repository: rocket-admin/rocketadmin

Length of output: 200


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate and print the target entity file with line numbers
FILE="backend/src/entities/email/email-verification.entity.ts"
if [ -f "$FILE" ]; then
  echo "=== $FILE (head) ==="
  nl -ba "$FILE" | sed -n '1,200p'
else
  echo "File not found: $FILE" >&2
fi

# Search for findVerificationWithVerificationString usage and its definition
echo "=== Searching for findVerificationWithVerificationString ==="
rg -n "findVerificationWithVerificationString" backend || true

# Print surrounding implementation if it exists
for f in $(rg -n "findVerificationWithVerificationString" backend -l || true); do
  echo "=== $f (relevant section) ==="
  rg -n "findVerificationWithVerificationString" "$f" -C 5 || true
done

# Check existing TypeORM imports and timestamp usage in the entity file
echo "=== TypeORM imports / timestamp usage in $FILE ==="
if [ -f "$FILE" ]; then
  rg -n "from 'typeorm'" "$FILE" || true
  rg -n "createdAt|CreateDateColumn|timestamptz|timestamp" "$FILE" || true
fi

Repository: rocket-admin/rocketadmin

Length of output: 200


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="backend/src/entities/email/email-verification.entity.ts"
if [ -f "$FILE" ]; then
  echo "=== $FILE (first 200 lines with numbers) ==="
  awk '{printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,200p'
else
  echo "File not found: $FILE" >&2
fi

echo "=== Search: findVerificationWithVerificationString ==="
# Show matches and the surrounding implementation for each defining/using file
rg -n "findVerificationWithVerificationString" backend || true
for f in $(rg -n "findVerificationWithVerificationString" backend -l || true); do
  echo "=== $f (context) ==="
  rg -n "findVerificationWithVerificationString" "$f" -C 5 || true
done

echo "=== TypeORM import / timestamp usage in $FILE ==="
if [ -f "$FILE" ]; then
  rg -n "from 'typeorm'" "$FILE" || true
  rg -n "createdAt|CreateDateColumn|timestamptz|timestamp" "$FILE" || true
fi

Repository: rocket-admin/rocketadmin

Length of output: 3699


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Show email-verification-custom-repository-extension.ts (relevant parts) ==="
FILE="backend/src/entities/email/repository/email-verification-custom-repository-extension.ts"
if [ -f "$FILE" ]; then
  awk '{printf "%5d\t%s\n", NR, $0}' "$FILE" | sed -n '1,220p'
else
  echo "File not found: $FILE" >&2
fi

echo "=== Search for ONE_DAY_AGO and createdAt window usage ==="
rg -n "ONE_DAY_AGO" backend || true
rg -n "createdAt" backend/src/entities/email backend/src/entities/user backend || true
rg -n "timestamptz|timestamp'|CURRENT_TIMESTAMP" backend/src/entities/email backend || true

Repository: rocket-admin/rocketadmin

Length of output: 50380


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print ONE_DAY_AGO implementation
FILE="backend/src/helpers/constants/constants.ts"
if [ -f "$FILE" ]; then
  echo "=== ONE_DAY_AGO in $FILE ==="
  rg -n "ONE_DAY_AGO" "$FILE"
  # Show nearby lines around ONE_DAY_AGO (limit output)
  rg -n -C 5 "ONE_DAY_AGO" "$FILE"
else
  echo "File not found: $FILE" >&2
fi

# Inspect migration that adds createdAt to email_verification/password_reset
MIG="backend/src/migrations/1780411916141-AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities.ts"
if [ -f "$MIG" ]; then
  echo "=== Migration $MIG ==="
  awk '{printf "%5d\t%s\n", NR, $0}' "$MIG" | sed -n '1,120p'
else
  echo "File not found: $MIG" >&2
fi

Repository: rocket-admin/rocketadmin

Length of output: 1505


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print ONE_DAY_AGO implementation
FILE="backend/src/helpers/constants/constants.ts"
if [ -f "$FILE" ]; then
  echo "=== ONE_DAY_AGO in $FILE ==="
  rg -n "ONE_DAY_AGO" "$FILE"
  rg -n -C 5 "ONE_DAY_AGO" "$FILE"
else
  echo "File not found: $FILE" >&2
fi

# Inspect migration that adds createdAt to email_verification/password_reset
MIG="backend/src/migrations/1780411916141-AddCreatedAtColumnsIntoPasswordResetAndEmailVerificationEntities.ts"
if [ -f "$MIG" ]; then
  echo "=== Migration $MIG ==="
  awk '{printf "%5d\t%s\n", NR, $0}' "$MIG" | sed -n '1,120p'
else
  echo "File not found: $MIG" >&2
fi

Repository: rocket-admin/rocketadmin

Length of output: 1505


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "synchronize\\s*:\\s*(true|false)" backend || true
rg -n "typeorm.*synchronize|synchronize\\s*:" backend || true
rg -n "dataSource|DataSourceOptions|ormconfig" backend -S || true
# List likely typeorm config files
fd -t f "ormconfig*.{js,ts}" backend || true
fd -t f "*data-source*.{js,ts}" backend || true
fd -t f "*typeorm*.{js,ts}" backend || true

Repository: rocket-admin/rocketadmin

Length of output: 13261


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "synchronize\\s*:\\s*(true|false)" backend || true
rg -n "typeorm.*synchronize|synchronize\\s*:" backend || true
rg -n "dataSource|DataSourceOptions|ormconfig" backend -S || true
fd -t f "ormconfig*.{js,ts}" backend || true
fd -t f "*data-source*.{js,ts}" backend || true
fd -t f "*typeorm*.{js,ts}" backend || true

Repository: rocket-admin/rocketadmin

Length of output: 13261


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "type:\\s*['\"](postgres|mysql|mariadb|mssql|sqlite|cockroachdb)['\"]" backend/src/shared/config/app-config.ts backend/src/shared/config/config.service.ts backend/src/shared/database -S || true
rg -n "postgres|mysql|mariadb" backend/src/shared/config/app-config.ts backend/src/shared/config/config.service.ts -S || true
# show the getTypeOrmConfig block around baseConfig creation (limit)
FILE="backend/src/shared/config/app-config.ts"
if [ -f "$FILE" ]; then
  echo "=== app-config.ts around getTypeOrmConfig ==="
  rg -n "getTypeOrmConfig" "$FILE"
  # print lines around 296-340
  awk 'NR>=240 && NR<=360 {print NR"\t"$0}' "$FILE" | sed -n '1,140p'
fi

Repository: rocket-admin/rocketadmin

Length of output: 5371


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "type:\\s*['\"](postgres|mysql|mariadb|mssql|sqlite|cockroachdb)['\"]" backend/src/shared/config/app-config.ts backend/src/shared/config/config.service.ts backend/src/shared/database -S || true
rg -n "postgres|mysql|mariadb" backend/src/shared/config/app-config.ts backend/src/shared/config/config.service.ts -S || true

FILE="backend/src/shared/config/app-config.ts"
if [ -f "$FILE" ]; then
  echo "=== app-config.ts around getTypeOrmConfig ==="
  awk 'NR>=240 && NR<=360 {print NR"\t"$0}' "$FILE" | sed -n '1,140p'
fi

Repository: rocket-admin/rocketadmin

Length of output: 5319


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n "timestamptz" backend || true
rg -n "`@CreateDateColumn`\\(\\{[^}]*type:\\s*'timestamp'" backend/src/entities || true

Repository: rocket-admin/rocketadmin

Length of output: 1256


Prefer @CreateDateColumn() for createdAt (keep DB type aligned).

  • Replace the manual @Column(... DEFAULT CURRENT_TIMESTAMP) with @CreateDateColumn() for idiomatic insert-time population.
  • email_verification.createdAt is currently TIMESTAMP ... DEFAULT now() (and is used for the 1-day cutoff via createdAt >= Constants.ONE_DAY_AGO()), so switch to type: 'timestamp' to match the existing schema; only use type: 'timestamptz' after adding a migration to alter the column type.
♻️ Proposed change
-import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, Relation } from 'typeorm';
+import { Column, CreateDateColumn, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn, Relation } from 'typeorm';

...

-	`@Column`({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
-	createdAt: Date;
+	`@CreateDateColumn`({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
+	createdAt: Date;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/entities/email/email-verification.entity.ts` around lines 12 -
13, Replace the manual createdAt column declaration with TypeORM's
`@CreateDateColumn` to let the ORM populate insert time while keeping the DB type
aligned: in the EmailVerification entity (email_verification.createdAt /
createdAt property) swap the `@Column`({ type: 'timestamp', default: () =>
'CURRENT_TIMESTAMP' }) declaration to `@CreateDateColumn`({ type: 'timestamp' })
so the runtime uses insert-time population and retains the existing TIMESTAMP
semantics used by your ONE_DAY_AGO cutoff; do not change to 'timestamptz' here —
only migrate the column type after adding an explicit DB migration if you later
decide to use timestamptz.

@Artuomka Artuomka merged commit 083a198 into main Jun 3, 2026
16 of 17 checks passed
@Artuomka Artuomka deleted the backend_fixes branch June 3, 2026 06:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants