Skip to content

All done except the email sending for registration - #49

Merged
reijjo merged 2 commits into
mainfrom
back
Mar 30, 2026
Merged

All done except the email sending for registration#49
reijjo merged 2 commits into
mainfrom
back

Conversation

@reijjo

@reijjo reijjo commented Mar 30, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • New Features

    • Added email verification for user registration with automatic token generation
    • Integrated email sending capability for verification workflows
  • Chores

    • Added dependencies for UUID generation and email handling

@coderabbitai

coderabbitai Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Walkthrough

This PR introduces an email verification system for user registration by adding a service layer that orchestrates user creation and verification token generation within a transaction, refactoring database queries to use generic executors, and adding email utility support.

Changes

Cohort / File(s) Summary
Dependencies
rust-server/Cargo.toml
Added uuid (with serde and v4 features) and resend-rs v0.21.1 dependencies for verification token generation and email sending.
Auth Service Layer
rust-server/src/features/auth/service.rs
New public async function new_user that orchestrates user creation and token generation within a transaction, returning both user_id and token string.
Auth Handler & Routing
rust-server/src/features/auth/handlers.rs, rust-server/src/features/auth/routes.rs
Handler renamed from create_user to register_user; updated to call service layer (returning tuple), log token, and conditionally send verification email; routing updated to wire renamed handler.
Auth Data Layer
rust-server/src/features/auth/queries.rs
Refactored database functions to use generic Executor<'e, Database = Postgres> instead of &PgPool; register_user renamed to create_user (returning Uuid); new create_verification_token function for token persistence.
Auth Types & Module Exports
rust-server/src/features/auth/types.rs, rust-server/src/features/auth/mod.rs
Added Serialize derive to Token struct; exported new service module from auth module.
Email Utilities
rust-server/src/utils/email.rs, rust-server/src/utils/mod.rs
New async utility send_verification_email (currently logs to stderr); added email module export to utils.
Documentation
rust-server/documentation.md
Updated architecture documentation to reflect new service layer, refactored queries with generic executor pattern, and token creation workflow with 24-hour expiration.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Handler as register_user Handler
    participant Service as Service Layer
    participant Database as Database
    participant Email as Email Utility

    Client->>Handler: POST /auth/register (email, username, password)
    Handler->>Service: new_user(pool, email, username, password)
    Service->>Database: BEGIN TRANSACTION
    Service->>Database: create_user (insert into users, RETURNING id)
    Database-->>Service: user_id (Uuid)
    Service->>Service: Generate token (UUID string)
    Service->>Service: Calculate expiry (now + 24h)
    Service->>Database: create_verification_token (insert into tokens)
    Database-->>Service: confirmation
    Service->>Database: COMMIT
    Database-->>Service: success
    Service-->>Handler: (user_id, token)
    Handler->>Email: send_verification_email(email, token)
    Email-->>Handler: Ok(())
    Handler-->>Client: 201 Created
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

The changes span multiple coordinated layers with new logic around transaction handling, generic executor patterns, and token generation—but they follow a clear, cohesive architectural pattern that's easy to trace.

Possibly Related PRs

  • PR #42: Foundational database integration with sqlx and PgPool initialization—the service and query layers in this PR directly build on that infrastructure.
  • PR #47: Modifies the same auth registration flow and modules (handlers, queries, types), likely introducing complementary features or refinements to user creation.
  • PR #46: Adds input validation and AppError::Validation variant to the auth pipeline, complementing the registration flow improvements introduced here.

🎓 Professional Dev Insight

You're organizing your auth flow beautifully here, mentor! Notice how you've separated concerns nicely:

  • Handlers: HTTP orchestration only (request → service → response)
  • Service: Business logic (transaction coordination, token generation)
  • Queries: Pure data persistence (generic over executor type)

This generic Executor pattern you're using is chef's kiss—it lets you pass transactions, connections, or pools without changing your query code. That's advanced and makes testing a breeze. The service layer managing the transaction boundary is exactly where it belongs, not sprinkled through handlers. Well done! 🚀

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 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: implementing user registration workflow (user creation, token generation, email placeholder) except actual email sending.

✏️ 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 back

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: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
rust-server/src/features/auth/types.rs (1)

28-34: 🧹 Nitpick | 🔵 Trivial

Consider: The Token struct isn't used in the current flow.

I notice you've added Serialize to Token, which is good forward-thinking. However, looking at your service.rs, you're returning (Uuid, String) tuple instead of using this struct.

A pro tip for cleaner code: when you have a well-defined struct like Token, consider using it as your return type instead of anonymous tuples. It makes your code self-documenting:

// Instead of: Result<(Uuid, String), AppError>
// Consider: Result<Token, AppError> or a dedicated NewUserResult struct

This isn't blocking—the #[allow(dead_code)] shows you're aware—but keep it in mind as you flesh out the feature.

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

In `@rust-server/src/features/auth/types.rs` around lines 28 - 34, The Token
struct is defined and serialized but not used: update the service function
currently returning Result<(Uuid, String), AppError> to return Result<Token,
AppError> (or a NewUserResult wrapper) — construct a Token (populate token,
expires_at, user_id) where you currently build the tuple, update the function
signature and any callers in service.rs to accept Token, and remove
#[allow(dead_code)] from Token once it's used; ensure the serialization remains
by keeping #[derive(Serialize, Debug)] on Token.
rust-server/src/features/auth/handlers.rs (1)

35-52: ⚠️ Potential issue | 🟠 Major

Race condition: uniqueness checks happen outside the transaction.

Here's a subtle but important bug pattern to learn. Your flow is:

  1. Check if email exists (line 35) — outside transaction
  2. Check if username exists (line 39) — outside transaction
  3. Call new_user() which starts transaction and inserts (line 46)

The problem: Between steps 1-2 and step 3, another concurrent request could insert the same email/username. Your database constraints will catch it, but the error bubbles up as a raw SQL constraint violation instead of your friendly "Email already in use" message.

Professional fix: Either move the checks inside the transaction in service.rs, or handle the unique constraint violation in your error mapping to return the appropriate user-friendly message.

🛠️ Suggested approach: Handle constraint violations gracefully

In your error handling (or in the handler), catch the specific unique constraint violation:

// In the match/error handling for new_user result:
match new_user(db, &cleaned_data.email, &cleaned_data.username, &hashed_password).await {
    Ok((user_id, token)) => { /* success path */ },
    Err(AppError::Sql(sqlx::Error::Database(db_err))) 
        if db_err.is_unique_violation() => {
        // Parse which constraint failed and return appropriate message
        return Err(AppError::conflict("Email or username already in use"));
    },
    Err(e) => return Err(e),
}

Alternatively, move the existence checks into new_user() so they run within the transaction.

🤖 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 35 - 52, The current
uniqueness checks (find_user_by_email and find_user_by_username) run outside the
transaction and can race with concurrent inserts; update the handler to either
move those checks inside the transaction in new_user (so do existence checks in
new_user in service.rs) or catch and map SQL unique-constraint errors from
new_user: match the Err(AppError::Sql(sqlx::Error::Database(db_err))) case, use
db_err.is_unique_violation() and inspect db_err.constraint() (or message) to
return AppError::conflict("Email already in use") or
AppError::conflict("Username already in use") as appropriate, otherwise rethrow
the error.
rust-server/src/features/auth/queries.rs (1)

18-23: ⚠️ Potential issue | 🔴 Critical

Use the correct DB column name for stored passwords (password_hash).

Line [18] inserts into password, but the users table schema defines password_hash. This will fail during registration with a SQL column error.

Proposed fix
 pub async fn create_user<'e, E>(
     db: E,
     email: &str,
     username: &str,
-    password: &str,
+    password_hash: &str,
 ) -> Result<Uuid, AppError>
 where
     E: Executor<'e, Database = Postgres>,
 {
     let row = sqlx::query_scalar::<_, Uuid>(
-        "INSERT INTO users (email, username, password) VALUES ($1, $2, $3) RETURNING id",
+        "INSERT INTO users (email, username, password_hash) VALUES ($1, $2, $3) RETURNING id",
     )
     .bind(email)
     .bind(username)
-    .bind(password)
+    .bind(password_hash)
     .fetch_one(db)
🤖 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 18 - 23, The INSERT
uses the wrong column name for stored passwords; update the SQL in the user
creation query to use password_hash instead of password and ensure the bound
value matches (replace "INSERT INTO users (email, username, password) ..." with
"INSERT INTO users (email, username, password_hash) ..." in the query used in
this file and keep the .bind(...) that supplies the hashed password (e.g.,
.bind(password) or rename that variable to password_hash if you have one) so the
bound parameter matches the password_hash column; locate the query in
rust-server/src/features/auth/queries.rs where
.bind(email).bind(username).bind(password).fetch_one(db) is used (the user
creation function) and make the column name change there.
🤖 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/Cargo.toml`:
- Around line 23-24: Add a short TODO comment above the resend-rs dependency in
Cargo.toml to indicate it's intentionally added for future email integration;
update the file so the line near "resend-rs = \"0.21.1\"" is preceded by a
comment like "# TODO: Integrate resend-rs email functionality" to make the
intent explicit for future maintainers (references: dependency names "resend-rs"
and "uuid").

In `@rust-server/documentation.md`:
- Around line 324-326: The eprintln in send_verification_email is logging
sensitive PII and secrets (the full to_email and token); remove that direct
logging and instead log only non-sensitive context such as the email domain. In
the send_verification_email function replace the eprintln that prints
"{to_email} with token: {token}" with logic that extracts the domain (e.g.,
split to_email on '@' and fallback to "unknown") and log only the domain and a
safe message; do not log the token or full address anywhere. Also audit any
similar eprintln/println in authentication/account recovery helpers and update
them to the same domain-only pattern.

In `@rust-server/src/features/auth/handlers.rs`:
- Around line 54-55: Replace the two eprintln! debug statements that print
sensitive data in handlers.rs with non-production-safe logging: remove
eprintln!("USER ID: {:#?}", user_id) and eprintln!("VERIFICATION TOKEN: {:#?}",
token) and instead use tracing::debug! with a redacted token; e.g., emit
tracing::debug! for the user_id only if needed and log the token as a
masked/hashed value (e.g., show first 4 chars and length or a hash) so the full
verification token is never printed; update the same pattern as in email.rs to
ensure no raw tokens are logged.

In `@rust-server/src/features/auth/service.rs`:
- Around line 18-19: The expiry for generated tokens is hardcoded when creating
expires_at in service.rs; make it configurable by adding a token_expiry_hours
(u32) field to your config (e.g., in config.rs) and replace the literal 24 with
config.token_expiry_hours cast to i64 when computing chrono::Duration::hours;
update any callers/constructors that build the service to pass the config and
add a default of 24 for existing behavior (so tests can override to shorter
values).

In `@rust-server/src/utils/email.rs`:
- Around line 3-7: Remove the eprintln! in send_verification_email and stop
printing PII or tokens; instead use tracing::debug! (to match handlers.rs) for
non-sensitive diagnostics and log only non-secret metadata (e.g., that an email
was queued/sent or the recipient domain or a truncated/hashed identifier, never
the full address or token). Update the function
send_verification_email(to_email: &str, token: &str) -> Result<(), AppError> to
replace direct stderr output with a tracing::debug! call that omits the raw
token and full email, and ensure any error paths return or log AppError via
tracing without exposing secrets.

---

Outside diff comments:
In `@rust-server/src/features/auth/handlers.rs`:
- Around line 35-52: The current uniqueness checks (find_user_by_email and
find_user_by_username) run outside the transaction and can race with concurrent
inserts; update the handler to either move those checks inside the transaction
in new_user (so do existence checks in new_user in service.rs) or catch and map
SQL unique-constraint errors from new_user: match the
Err(AppError::Sql(sqlx::Error::Database(db_err))) case, use
db_err.is_unique_violation() and inspect db_err.constraint() (or message) to
return AppError::conflict("Email already in use") or
AppError::conflict("Username already in use") as appropriate, otherwise rethrow
the error.

In `@rust-server/src/features/auth/queries.rs`:
- Around line 18-23: The INSERT uses the wrong column name for stored passwords;
update the SQL in the user creation query to use password_hash instead of
password and ensure the bound value matches (replace "INSERT INTO users (email,
username, password) ..." with "INSERT INTO users (email, username,
password_hash) ..." in the query used in this file and keep the .bind(...) that
supplies the hashed password (e.g., .bind(password) or rename that variable to
password_hash if you have one) so the bound parameter matches the password_hash
column; locate the query in rust-server/src/features/auth/queries.rs where
.bind(email).bind(username).bind(password).fetch_one(db) is used (the user
creation function) and make the column name change there.

In `@rust-server/src/features/auth/types.rs`:
- Around line 28-34: The Token struct is defined and serialized but not used:
update the service function currently returning Result<(Uuid, String), AppError>
to return Result<Token, AppError> (or a NewUserResult wrapper) — construct a
Token (populate token, expires_at, user_id) where you currently build the tuple,
update the function signature and any callers in service.rs to accept Token, and
remove #[allow(dead_code)] from Token once it's used; ensure the serialization
remains by keeping #[derive(Serialize, Debug)] on Token.
🪄 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: 3c20d76e-c3a0-4d50-9dd6-87fd6868e192

📥 Commits

Reviewing files that changed from the base of the PR and between acbac5d and 6a98874.

⛔ Files ignored due to path filters (1)
  • rust-server/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (10)
  • rust-server/Cargo.toml
  • rust-server/documentation.md
  • rust-server/src/features/auth/handlers.rs
  • rust-server/src/features/auth/mod.rs
  • rust-server/src/features/auth/queries.rs
  • rust-server/src/features/auth/routes.rs
  • rust-server/src/features/auth/service.rs
  • rust-server/src/features/auth/types.rs
  • rust-server/src/utils/email.rs
  • rust-server/src/utils/mod.rs

Comment thread rust-server/Cargo.toml
Comment thread rust-server/documentation.md
Comment thread rust-server/src/features/auth/handlers.rs
Comment thread rust-server/src/features/auth/service.rs
Comment thread rust-server/src/utils/email.rs
@reijjo
reijjo merged commit 0886c75 into main Mar 30, 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