Skip to content

Backend stuff done for the verification - #53

Merged
reijjo merged 6 commits into
mainfrom
front
Apr 3, 2026
Merged

Backend stuff done for the verification#53
reijjo merged 6 commits into
mainfrom
front

Conversation

@reijjo

@reijjo reijjo commented Apr 2, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Resend verification email flow (server endpoint + client UI) and client-side verify integration; verify page now server-rendered with a fallback while checking.
    • Client API: verifyAccount call and ApiResponse now includes optional status.
  • Bug Fixes

    • Expired verification tokens now return 410 Gone to enable resend flow.
    • New 429 Too Many Requests response mapping for relevant errors.
  • Style

    • Centered auth container and adjusted layout sizing/heading/button styles.

@coderabbitai

coderabbitai Bot commented Apr 2, 2026

Copy link
Copy Markdown
Contributor

Warning

Rate limit exceeded

@reijjo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 19 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 19 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: cae78420-3221-4e4a-80f3-130b4c355b0f

📥 Commits

Reviewing files that changed from the base of the PR and between f0f12a1 and 274a2ca.

📒 Files selected for processing (1)
  • client/src/app/(auth)/verify/page.tsx

Walkthrough

This PR implements an email verification token resend feature, adds Gone(String) and TooManyRequests(String) AppError variants mapped to HTTP 410/429, extends find_user_by_id to return email, introduces update_verification_token (24h expiry), adds a resend_token handler and route, and updates client verification UI/API to support resend.

Changes

Cohort / File(s) Summary
Database Query Enhancement
rust-server/src/db/queries.rs
find_user_by_id now selects id, email instead of just id, exposing user email for downstream flows.
Error Handling Extensions
rust-server/src/errors.rs
Added AppError::Gone(String) and AppError::TooManyRequests(String) plus constructors and IntoResponse mappings to HTTP 410 and 429.
Token Resend Feature (Server)
rust-server/src/features/auth/handlers.rs, rust-server/src/features/auth/queries.rs, rust-server/src/features/auth/routes.rs, rust-server/src/features/auth/types.rs
Added resend_token POST handler, new ResendTokenData type, update_verification_token query (generates new UUID token, sets expires_at = now + 24h), updated /verify route to accept POST alongside GET. Changed verify flow to return 410 for expired tokens.
Client: Verification UI & API
client/src/app/(auth)/verify/VerifyContent.tsx, client/src/app/(auth)/verify/page.tsx, client/src/lib/api/auth.ts, client/src/lib/types/apiResponse.ts, client/src/app/(auth)/layout.css
Added server-rendered Verify page and async VerifyContent component that calls new verifyAccount API. verifyAccount GETs /api/auth/verify?token=... with no-cache handling and returns ApiResponse (now includes optional status). UI shows resend button when server returns 410. Minor CSS layout adjustments.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Handler as ResendHandler
    participant DB as Database
    participant Email as EmailService

    Client->>Handler: POST /verify { token }
    Handler->>DB: find_token_by_value(token)
    alt token not found
        Handler-->>Client: 404 Not Found
    else token found
        Handler->>DB: find_user_by_id(user_id)
        alt user not found
            Handler-->>Client: 404 Not Found
        else user found
            Handler->>DB: update_verification_token(user_id)
            DB-->>Handler: new_token (24h expiry)
            Handler->>Email: send_verification_email(email, new_token)
            alt email sent
                Email-->>Handler: success
                Handler-->>Client: 200 OK (resend confirmed)
            else email failed
                Email-->>Handler: error
                Handler-->>Client: 500 / email error
            end
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is vague and generic, using non-descriptive language like 'stuff' that fails to convey meaningful information about the substantial changes across backend and frontend files. Consider a more specific title that highlights the main feature, such as 'Add token resend functionality for account verification' or 'Implement account verification email resend feature'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch front

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.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@rust-server/src/features/auth/handlers.rs`:
- Line 174: Remove the debug stderr print that exposes full user data: delete or
replace the eprint!("RESULT: {:#?}", user); in auth handlers (handlers.rs) and
instead log only non-sensitive identifiers (e.g., user.id or a redacted subset)
or use a secure logger that redacts PII in the function where `user` is printed;
ensure no full `user` struct is formatted to stderr or logs.
- Around line 167-186: The code rotates the verification token via
update_verification_token(db, user.get("id")) before attempting to send the
email, which can lock the user out if send_verification_email fails; change the
flow so you only persist the new token after the email send succeeds (call
update_verification_token after send_verification_email), or if you must
pre-create the token, capture the old token and on email send failure call a
restore_verification_token(db, user.get("id"), old_token) helper to revert to
the previous token and return the email error; update calls around
find_token_by_value, update_verification_token, and send_verification_email
accordingly.
- Around line 160-164: The resend_token handler currently accepts the token via
Query(ResendTokenData) which exposes sensitive data in URLs; change the handler
to accept the token from the request body instead (e.g., Json<ResendTokenData>
or a dedicated ResendTokenBody) by replacing Query(params) with the appropriate
Json/Extract type in the signature, update how you read token (from params.token
-> body.token), and adjust any route/tests that call resend_token to send a JSON
body; keep State(AppState) and return types the same.

In `@rust-server/src/features/auth/queries.rs`:
- Around line 87-95: The UPDATE currently always returns Ok(new_token) after
running sqlx::query(...).bind(...).execute(db).await, which can return zero rows
if no token row exists for that user_id; change this to capture the execute
result into a variable (e.g. let res =
sqlx::query(...).execute(db).await.map_err(AppError::Sql)?), check
res.rows_affected(), and if it is 0 return an appropriate error (e.g.
AppError::NotFound or a specific token-not-persisted error) instead of
Ok(new_token); only return Ok(new_token) when rows_affected() > 0.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 8559a87a-90f2-4217-b855-564768d91743

📥 Commits

Reviewing files that changed from the base of the PR and between 607cbcd and 58a0f1e.

📒 Files selected for processing (6)
  • rust-server/src/db/queries.rs
  • rust-server/src/errors.rs
  • rust-server/src/features/auth/handlers.rs
  • rust-server/src/features/auth/queries.rs
  • rust-server/src/features/auth/routes.rs
  • rust-server/src/features/auth/types.rs

Comment thread rust-server/src/features/auth/handlers.rs Outdated
Comment on lines +167 to +186
let result = find_token_by_value(db, token)
.await?
.ok_or_else(|| AppError::not_found("Token not found"));

let user = find_user_by_id(db, result?.get("user_id"))
.await?
.ok_or_else(|| AppError::not_found("User not found"))?;
eprint!("RESULT: {:#?}", user);

let new_token = update_verification_token(db, user.get("id")).await?;

if !state.config.app_env.is_test()
&& let Err(email_err) = state
.email
.send_verification_email(user.get("email"), &new_token)
.await
{
tracing::error!("Failed to send verification email: {:#?}", email_err);
return Err(email_err);
}

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.

⚠️ Potential issue | 🔴 Critical

Token rotation before email delivery can lock users out after transient mail failures.

Line 176 updates the token first; if Line 181–182 email send fails, the previously delivered token is invalidated and the user may no longer have a usable token to retry resend.

Suggested direction
-    let new_token = update_verification_token(db, user.get("id")).await?;
+    // Keep previous token data for compensation if email send fails.
+    let old_token: String = result?.get("token");
+    let old_expires_at: DateTime<Utc> = result?.get("expires_at");
+    let new_token = update_verification_token(db, user.get("id")).await?;
@@
     if !state.config.app_env.is_test()
         && let Err(email_err) = state
             .email
             .send_verification_email(user.get("email"), &new_token)
             .await
     {
+        // restore previous token state so the last delivered link remains usable
+        restore_verification_token(db, user.get("id"), &old_token, old_expires_at).await?;
         tracing::error!("Failed to send verification email: {:#?}", email_err);
         return Err(email_err);
     }

If you want, I can draft a minimal restore_verification_token(...) query helper too.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust-server/src/features/auth/handlers.rs` around lines 167 - 186, The code
rotates the verification token via update_verification_token(db, user.get("id"))
before attempting to send the email, which can lock the user out if
send_verification_email fails; change the flow so you only persist the new token
after the email send succeeds (call update_verification_token after
send_verification_email), or if you must pre-create the token, capture the old
token and on email send failure call a restore_verification_token(db,
user.get("id"), old_token) helper to revert to the previous token and return the
email error; update calls around find_token_by_value, update_verification_token,
and send_verification_email accordingly.

Comment thread rust-server/src/features/auth/handlers.rs Outdated
Comment on lines +87 to +95
sqlx::query("UPDATE tokens SET token = $1, expires_at = $2 WHERE user_id = $3")
.bind(&new_token)
.bind(new_expires_at)
.bind(user_id)
.execute(db)
.await
.map_err(AppError::Sql)?;

Ok(new_token)

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.

⚠️ Potential issue | 🟠 Major

Check rows_affected to avoid returning non-persisted tokens.

Right now, Line 87–95 returns Ok(new_token) even when no token row exists for that user_id. That can send a token that will never verify.

Suggested fix
-    sqlx::query("UPDATE tokens SET token = $1, expires_at = $2 WHERE user_id = $3")
+    let result = sqlx::query("UPDATE tokens SET token = $1, expires_at = $2 WHERE user_id = $3")
         .bind(&new_token)
         .bind(new_expires_at)
         .bind(user_id)
         .execute(db)
         .await
         .map_err(AppError::Sql)?;
 
+    if result.rows_affected() != 1 {
+        return Err(AppError::not_found("Verification token not found for user"));
+    }
+
     Ok(new_token)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
sqlx::query("UPDATE tokens SET token = $1, expires_at = $2 WHERE user_id = $3")
.bind(&new_token)
.bind(new_expires_at)
.bind(user_id)
.execute(db)
.await
.map_err(AppError::Sql)?;
Ok(new_token)
let result = sqlx::query("UPDATE tokens SET token = $1, expires_at = $2 WHERE user_id = $3")
.bind(&new_token)
.bind(new_expires_at)
.bind(user_id)
.execute(db)
.await
.map_err(AppError::Sql)?;
if result.rows_affected() != 1 {
return Err(AppError::not_found("Verification token not found for user"));
}
Ok(new_token)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust-server/src/features/auth/queries.rs` around lines 87 - 95, The UPDATE
currently always returns Ok(new_token) after running
sqlx::query(...).bind(...).execute(db).await, which can return zero rows if no
token row exists for that user_id; change this to capture the execute result
into a variable (e.g. let res =
sqlx::query(...).execute(db).await.map_err(AppError::Sql)?), check
res.rows_affected(), and if it is 0 return an appropriate error (e.g.
AppError::NotFound or a specific token-not-persisted error) instead of
Ok(new_token); only return Ok(new_token) when rows_affected() > 0.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

♻️ Duplicate comments (1)
rust-server/src/features/auth/handlers.rs (1)

178-187: ⚠️ Potential issue | 🔴 Critical

Do not invalidate the last working link before email delivery succeeds.

update_verification_token(...) runs before send_verification_email(...). If mail delivery fails here, the previously issued token is already unusable and the user is stuck without any valid verification link. Persist the new token only after a successful send, or restore the old token on failure.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rust-server/src/features/auth/handlers.rs` around lines 178 - 187, Currently
update_verification_token(db, user.get("id")) is called before sending email,
which invalidates the previous token if send_verification_email fails; change
the flow so you generate a new token but do not persist it before sending (or
alternatively read and save the old token, attempt
state.email.send_verification_email(user.get("email"), &new_token).await first,
and only call update_verification_token(...) after the send succeeds), and if
you must persist before send, ensure you restore the previous token on any send
error; refer to update_verification_token, state.email.send_verification_email,
and user.get("id")/user.get("email") to locate the relevant code to reorder or
add rollback logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/src/app/`(auth)/verify/page.tsx:
- Around line 10-21: The VerifyPage should normalize the incoming token by
trimming whitespace before treating the link as valid: in VerifyPage (the
exported async function receiving VerifyPageProps and searchParams) obtain and
trim params.token, reject if the trimmed value is empty (so links like
?token=%20%20 are treated as invalid) and pass the trimmed token into
<VerifyContent token={...}>; update any local variable names accordingly so
downstream code receives the normalized token.

In `@client/src/app/`(auth)/verify/VerifyContent.tsx:
- Around line 19-24: The "Resend verification email" Button in VerifyContent
does nothing; update the VerifyContent component to attach an onClick handler
that POSTs to the existing resend endpoint (POST /api/auth/verify) when
res.status === 410, using the same payload or token used by the initial flow,
and wire up basic UI feedback: set a loading state while the fetch/mutation
runs, disable the Button during the request, and surface success or error
messages from the response (or rethrow to global error handling). Locate the JSX
in VerifyContent.tsx where the Button is rendered and implement the async
handler, ensuring you reference the Button element and the POST /api/auth/verify
endpoint and update component state (e.g., loading, message) accordingly.

---

Duplicate comments:
In `@rust-server/src/features/auth/handlers.rs`:
- Around line 178-187: Currently update_verification_token(db, user.get("id"))
is called before sending email, which invalidates the previous token if
send_verification_email fails; change the flow so you generate a new token but
do not persist it before sending (or alternatively read and save the old token,
attempt state.email.send_verification_email(user.get("email"), &new_token).await
first, and only call update_verification_token(...) after the send succeeds),
and if you must persist before send, ensure you restore the previous token on
any send error; refer to update_verification_token,
state.email.send_verification_email, and user.get("id")/user.get("email") to
locate the relevant code to reorder or add rollback logic.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 6d9bc84e-8a0e-4d3d-87d6-c8afbb53d35e

📥 Commits

Reviewing files that changed from the base of the PR and between 58a0f1e and abadcea.

📒 Files selected for processing (6)
  • client/src/app/(auth)/layout.css
  • client/src/app/(auth)/verify/VerifyContent.tsx
  • client/src/app/(auth)/verify/page.tsx
  • client/src/lib/api/auth.ts
  • client/src/lib/types/apiResponse.ts
  • rust-server/src/features/auth/handlers.rs

Comment thread client/src/app/(auth)/verify/page.tsx Outdated
Comment thread client/src/app/(auth)/verify/VerifyContent.tsx
Comment thread rust-server/src/features/auth/handlers.rs

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/src/app/`(auth)/verify/page.tsx:
- Around line 5-7: The searchParams type and handling for token is too narrow
and can crash when Next.js supplies repeated query keys; update the type from
token?: string to token?: string | string[] and, before calling .trim() (where
token is used in the verify page logic), coerce token to a single string by
checking Array.isArray(token) and taking token = token[0] (or undefined if
empty), then safely call token = token?.trim(); adjust any downstream
null/undefined checks to use the normalized token variable so
invalid/multi-value tokens fall through to the invalid-link fallback instead of
throwing.
🪄 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: ASSERTIVE

Plan: Pro

Run ID: 18150c5b-a2b5-40ab-8eaf-cbcd0b07a060

📥 Commits

Reviewing files that changed from the base of the PR and between abadcea and f0f12a1.

📒 Files selected for processing (1)
  • client/src/app/(auth)/verify/page.tsx

Comment on lines +5 to +7
searchParams: Promise<{
token?: string;
}>;

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Next.js App Router page props, what is the exact runtime/type shape of searchParamsvalues for repeated query keys (e.g.?token=a&token=b)? Is it string | string[] | undefined?

💡 Result:

In Next.js App Router page components (server components), the searchParams prop has the runtime/type shape { [key: string]: string | string[] | undefined }. For repeated query keys like ?token=a&token=b, the value of searchParams.token is string[] containing ['a', 'b']. This matches the proposed type string | string[] | undefined for individual values (per key). In Next.js 15+, searchParams is a Promise<{ [key: string]: string | string[] | undefined }>, which must be awaited in async page components. This behavior follows standard URLSearchParams parsing where duplicate keys produce arrays, as confirmed across multiple sources including GitHub discussions, Stack Overflow answers with code examples, and Next.js tutorial type definitions.

Citations:


🏁 Script executed:

cat -n client/src/app/(auth)/verify/page.tsx

Repository: reijjo/tarpit2.0

Length of output: 191


🏁 Script executed:

cat -n "client/src/app/(auth)/verify/page.tsx"

Repository: reijjo/tarpit2.0

Length of output: 771


Handle multi-value token query params before calling .trim()—a runtime trap in Next.js.

Here's the situation: Next.js searchParams can deliver repeated query keys as arrays. When a user hits ?token=a&token=b, params.token becomes ['a', 'b']. Your current code calls .trim() directly on line 12, which will throw a TypeError at runtime because arrays don't have a .trim() method. You'll serve a 500 error instead of the invalid-link fallback.

The type definition on line 6 is too narrow. It says token?: string, but it should be token?: string | string[] to match what Next.js actually delivers at runtime.

The fix: Before trimming, detect whether token is an array and take the first element:

Suggested fix
 type VerifyPageProps = {
   searchParams: Promise<{
-    token?: string;
+    token?: string | string[];
   }>;
 };

 export default async function VerifyPage({ searchParams }: VerifyPageProps) {
   const params = await searchParams;
-  const token = params.token?.trim();
+  const rawToken = params.token;
+  const token = Array.isArray(rawToken)
+    ? rawToken[0]?.trim()
+    : rawToken?.trim();

   if (!token) {
     return <p>Invalid verification link</p>;
   }

This pattern is your insurance policy: you handle the edge case gracefully, type accurately, and keep the happy path clear.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/app/`(auth)/verify/page.tsx around lines 5 - 7, The searchParams
type and handling for token is too narrow and can crash when Next.js supplies
repeated query keys; update the type from token?: string to token?: string |
string[] and, before calling .trim() (where token is used in the verify page
logic), coerce token to a single string by checking Array.isArray(token) and
taking token = token[0] (or undefined if empty), then safely call token =
token?.trim(); adjust any downstream null/undefined checks to use the normalized
token variable so invalid/multi-value tokens fall through to the invalid-link
fallback instead of throwing.

@reijjo
reijjo merged commit 7f5ed1e into main Apr 3, 2026
4 checks passed
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.

1 participant