Implement complete authentication system with role-based access control#1
Conversation
…ponents for enhanced form handling
…sign in, and sign up
…d password reset functionality
… in DefaultErrorBoundary
|
Warning Review limit reached
More reviews will be available in 47 minutes and 48 seconds. Learn how PR review limits work. Your organization has run out of usage credits. Purchase more credits in the billing tab to continue. ⌛ How to resolve this issue?After more reviews become available, a review can be triggered using the 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 include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available. Please see our Fair Usage Limits Policy for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughAdds full auth stack: Prisma models and env schema, reusable form and shared UI components, Zod validation, sign-in/sign-up/forgot/reset UIs and handlers, email templates and SMTP sender, and a session API endpoint. ChangesAuthentication System Implementation
Sequence DiagramssequenceDiagram
participant User
participant SignInForm as Sign-In Form
participant AuthClient as authClient
participant Toast as Toast Notifications
participant Router as Router Navigation
User->>SignInForm: submit credentials or click social
SignInForm->>AuthClient: signIn.email / signIn.social
alt success
AuthClient-->>SignInForm: success
SignInForm->>Toast: show success
SignInForm->>Router: navigate to redirect or /
else error
AuthClient-->>SignInForm: error
SignInForm->>Toast: show error
end
sequenceDiagram
participant User
participant ForgotForm as Forgot Password Form
participant AuthClient as authClient
participant Email as sendAuthEmail
participant ResetForm as Reset Password Form
participant Router as Router
User->>ForgotForm: submit email
ForgotForm->>AuthClient: requestPasswordReset
AuthClient->>Email: send password-reset email
User->>ResetForm: open link with token
ResetForm->>AuthClient: resetPassword(token, newPassword)
alt valid token
AuthClient-->>ResetForm: success
ResetForm->>Router: navigate to /
else invalid token
ResetForm-->>User: show invalid reset link
end
sequenceDiagram
participant App as Application
participant EmailSender as sendAuthEmail
participant Template as React Email Template
participant Nodemailer as Nodemailer
participant SMTP as SMTP Server
App->>EmailSender: send auth email payload
EmailSender->>Template: render HTML
Template-->>EmailSender: HTML + plain text
EmailSender->>Nodemailer: send(mail options)
Nodemailer->>SMTP: deliver message
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly Related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
|
React Doctor found 5 issues in 5 files · 1 error & 4 warnings · score 73 / 100 (Needs work) · vs Errors
4 warnings
Reviewed by React Doctor for commit |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 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 `@prisma/schema.prisma`:
- Line 181: The required createdBy relations (createdById / createdBy) in the
StockMovement, StockTransfer, Order and PurchaseOrder models conflict with hard
user deletion (user.deleteUser.enabled: true); either disable hard-delete in the
auth config or make these relations nullable with an explicit onDelete action.
Fix by choosing one policy: A) disable hard delete by setting deleteUser.enabled
to false in the auth config, or B) make the Prisma relations nullable and add
onDelete: SetNull — change createdById to optional (String?) and the createdBy
relation to optional with `@relation`(fields: [createdById], references: [id],
onDelete: SetNull) for the StockMovement, StockTransfer, Order and PurchaseOrder
models, then run a migration to apply the schema change.
In `@src/env/server.ts`:
- Line 7: The NODE_ENV enum in the server environment schema rejects the "test"
value which breaks CI/test imports; update the z.enum for NODE_ENV (the schema
field named NODE_ENV in your server env schema) to include "test" alongside
"development" and "production" so NODE_ENV="test" is accepted (keep the default
as appropriate), then run/type-check to ensure imports that read this schema
(e.g., serverEnv) accept test environments.
In `@src/features/auth/components/forgot-password-form.tsx`:
- Line 40: The validators binding in the ForgotPasswordForm component uses the
misspelled identifier forgotPasswordShema; update the validators property
(validators: { onSubmit: ... }) to reference the correctly imported symbol
forgotPasswordSchema so the onSubmit validation uses the proper schema, ensuring
the import at the top (forgotPasswordSchema) and the validators assignment
match.
In `@src/features/auth/schema/index.ts`:
- Around line 16-23: The password schema currently uses .regex(...) to require a
special character and .refine(...) only to ban leading/trailing spaces, allowing
internal whitespace; replace the .refine(...) call with a regex that rejects any
whitespace in the entire value (e.g. use .regex(/^\S+$/u, "Password must not
contain whitespace")) and keep/update the special-character check (the existing
.regex(/[^A-Za-z0-9]/u, "Password must contain at least one special character")
can remain or be tightened to a Unicode-aware class like /[^\p{L}\p{N}]/u), so
update the .refine usage to .regex(/^\S+$/u, "Password must not contain
whitespace") in the same password schema chain.
In `@src/features/email/functions/index.tsx`:
- Around line 37-87: sendAuthEmail currently embeds payload.url directly into
rendered EmailVerificationEmail and PasswordResetEmail HTML/text; validate that
payload.url is a well-formed HTTPS URL and that its hostname matches the
expected host(s) before rendering/sending. Inside sendAuthEmail, parse
payload.url (e.g., new URL(payload.url)), check url.protocol === "https:" and
allowedHosts.includes(url.hostname); if validation fails, log and throw or
return an error instead of calling transporter.sendMail and avoid passing the
raw URL into PasswordResetEmail or EmailVerificationEmail. Also ensure both
branches (password-reset and verification) perform this same validation before
calling render or transporter.sendMail.
- Around line 23-31: The Nodemailer transporter created by createTransport
(variable transporter) lacks explicit SMTP timeouts causing potential request
hangs; update the createTransport options used in
src/features/email/functions/index.tsx to include connectionTimeout,
greetingTimeout, and socketTimeout (e.g., set to sensible millisecond values
like 10_000) so slow/broken SMTP handshakes fail fast instead of hanging the
request.
🪄 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 Plus
Run ID: 9aee0766-26d1-41d5-a92f-b5a3a14ab7d5
⛔ Files ignored due to path filters (26)
bun.lockis excluded by!**/*.locksrc/generated/prisma/browser.tsis excluded by!**/generated/**src/generated/prisma/client.tsis excluded by!**/generated/**src/generated/prisma/commonInputTypes.tsis excluded by!**/generated/**src/generated/prisma/enums.tsis excluded by!**/generated/**src/generated/prisma/internal/class.tsis excluded by!**/generated/**src/generated/prisma/internal/prismaNamespace.tsis excluded by!**/generated/**src/generated/prisma/internal/prismaNamespaceBrowser.tsis excluded by!**/generated/**src/generated/prisma/models.tsis excluded by!**/generated/**src/generated/prisma/models/Account.tsis excluded by!**/generated/**src/generated/prisma/models/AuditLog.tsis excluded by!**/generated/**src/generated/prisma/models/Category.tsis excluded by!**/generated/**src/generated/prisma/models/InventoryItem.tsis excluded by!**/generated/**src/generated/prisma/models/Order.tsis excluded by!**/generated/**src/generated/prisma/models/OrderItem.tsis excluded by!**/generated/**src/generated/prisma/models/Product.tsis excluded by!**/generated/**src/generated/prisma/models/PurchaseOrder.tsis excluded by!**/generated/**src/generated/prisma/models/PurchaseOrderItem.tsis excluded by!**/generated/**src/generated/prisma/models/Session.tsis excluded by!**/generated/**src/generated/prisma/models/StockMovement.tsis excluded by!**/generated/**src/generated/prisma/models/StockTransfer.tsis excluded by!**/generated/**src/generated/prisma/models/StockTransferItem.tsis excluded by!**/generated/**src/generated/prisma/models/Supplier.tsis excluded by!**/generated/**src/generated/prisma/models/User.tsis excluded by!**/generated/**src/generated/prisma/models/Verification.tsis excluded by!**/generated/**src/generated/prisma/models/Warehouse.tsis excluded by!**/generated/**
📒 Files selected for processing (36)
biome.jsoncpackage.jsonprisma/schema.prismasrc/components/form/form-checkbox.tsxsrc/components/form/form-input.tsxsrc/components/form/form-password.tsxsrc/components/form/submit-button.tsxsrc/components/shared/default-error-boundary.tsxsrc/components/shared/default-not-found.tsxsrc/components/shared/password-input.tsxsrc/env/server.tssrc/features/auth/components/forgot-password-form.tsxsrc/features/auth/components/reset-password-form.tsxsrc/features/auth/components/sign-in-form.tsxsrc/features/auth/components/sign-up-form.tsxsrc/features/auth/functions/index.tssrc/features/auth/schema/index.tssrc/features/email/components/email-layout.tsxsrc/features/email/components/email-verification-email.tsxsrc/features/email/components/password-reset-email.tsxsrc/features/email/functions/index.tsxsrc/hooks/use-form-context.tssrc/hooks/use-form.tssrc/lib/auth-client.tssrc/lib/auth.tssrc/lib/permissions.tssrc/routeTree.gen.tssrc/router.tsxsrc/routes/_app/index.tsxsrc/routes/_app/route.tsxsrc/routes/_auth/forgot-password.tsxsrc/routes/_auth/reset-password.tsxsrc/routes/_auth/route.tsxsrc/routes/_auth/sign-in.tsxsrc/routes/_auth/sign-up.tsxsrc/routes/index.tsx
📜 Review details
🔇 Additional comments (17)
src/features/email/components/email-layout.tsx (1)
1-64: LGTM!src/features/email/components/email-verification-email.tsx (1)
1-58: LGTM!src/features/email/components/password-reset-email.tsx (1)
1-58: LGTM!biome.jsonc (1)
8-9: LGTM!package.json (1)
54-54: LGTM!src/features/auth/functions/index.ts (1)
1-12: LGTM!src/features/auth/components/sign-in-form.tsx (1)
23-174: LGTM!src/features/auth/components/sign-up-form.tsx (1)
22-161: LGTM!src/features/auth/components/reset-password-form.tsx (1)
17-109: LGTM!src/components/form/form-checkbox.tsx (1)
9-23: LGTM!src/components/form/form-input.tsx (1)
12-36: LGTM!src/components/form/form-password.tsx (1)
12-48: LGTM!src/components/form/submit-button.tsx (1)
11-39: LGTM!src/components/shared/password-input.tsx (1)
12-37: LGTM!src/components/shared/default-error-boundary.tsx (1)
12-135: LGTM!src/components/shared/default-not-found.tsx (1)
5-64: LGTM!src/features/auth/schema/index.ts (1)
25-58: LGTM!
…s and fields; add Customer model
…ing regex for special characters
…ter configuration
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/features/email/functions/index.tsx (1)
40-90:⚠️ Potential issue | 🟠 Major | ⚡ Quick winAdd error handling and logging to prevent silent email failures.
sendAuthEmailhas no try-catch aroundrender()ortransporter.sendMail(), so failures (network issues, SMTP auth errors, template rendering errors) silently propagate. Because callers usevoid sendAuthEmail(...)(fire-and-forget), errors are swallowed and users attempting password resets or email verification receive no feedback. At minimum, wrap the function body in try-catch and log failures for observability.🛡️ Suggested error handling with logging
export async function sendAuthEmail(payload: AuthEmailPayload) { + try { const from = getFromAddress(); if (payload.kind === "password-reset") { const html = await render( <PasswordResetEmail expiresInMinutes={payload.expiresInMinutes ?? 30} name={payload.name ?? undefined} resetUrl={payload.url} /> ); await transporter.sendMail({ from, html, subject: "InvenEase password reset", text: [ `Hi ${payload.name ?? "there"},`, "", `Reset your password here: ${payload.url}`, "", `This link expires in ${payload.expiresInMinutes ?? 30} minutes.`, ].join("\n"), to: payload.to, }); return; } const html = await render( <EmailVerificationEmail expiresInMinutes={payload.expiresInMinutes ?? 60} name={payload.name ?? undefined} verificationUrl={payload.url} /> ); await transporter.sendMail({ from, html, subject: "InvenEase email verification", text: [ `Hi ${payload.name ?? "there"},`, "", `Verify your email here: ${payload.url}`, "", `This link expires in ${payload.expiresInMinutes ?? 60} minutes.`, ].join("\n"), to: payload.to, }); + } catch (error) { + console.error("Failed to send auth email:", { + kind: payload.kind, + to: payload.to, + error: error instanceof Error ? error.message : String(error), + }); + // Consider: send to error monitoring service, store in DB for retry + } }🤖 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 `@src/features/email/functions/index.tsx` around lines 40 - 90, Wrap the entire body of sendAuthEmail in a try-catch: catch any errors thrown by render(...) or transporter.sendMail(...) and call your application logger (or processLogger) to record a clear error including the email payload.kind, payload.to, and the error stack/message; ensure the function does not rethrow so fire-and-forget callers aren’t disrupted, but returns after logging (or returns a rejected Promise if your call sites expect it), and keep references to render and transporter.sendMail to locate where to add the try-catch.src/components/shared/default-error-boundary.tsx (1)
115-121:⚠️ Potential issue | 🟡 MinorMake “Reload page” trigger a real document reload
router.navigate({ to: "." })is client-side navigation (doesn’t force a browser-level reload), so the button label and the “reset after crash” intent don’t match—force reload viareloadDocument: true.Suggested change
<Button - onClick={() => router.navigate({ to: "." })} + onClick={() => router.navigate({ to: ".", reloadDocument: true })} size="sm" variant="outline" >🤖 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 `@src/components/shared/default-error-boundary.tsx` around lines 115 - 121, The "Reload page" Button currently calls router.navigate({ to: "." }) which performs client-side navigation; change the call in the Button's onClick handler to invoke router.navigate with reloadDocument: true (e.g., router.navigate({ to: ".", reloadDocument: true })) so the action triggers a full browser/document reload and matches the "reset after crash" intent; update the onClick where Button is defined to use this option.
♻️ Duplicate comments (1)
src/features/email/functions/index.tsx (1)
40-90:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidate
payload.urlbefore embedding in email content.As flagged in the previous review,
payload.urlis embedded directly into email templates without validation. Although Better Auth generates these URLs, validating the protocol and origin provides defense-in-depth against potential library vulnerabilities or configuration errors.🤖 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 `@src/features/email/functions/index.tsx` around lines 40 - 90, The sendAuthEmail function embeds payload.url into email bodies without validation; validate payload.url before rendering/sending. In sendAuthEmail, before calling render(...) or transporter.sendMail(...), parse and validate payload.url (e.g., with URL constructor) to ensure it is a well-formed absolute URL with an allowed protocol (https) and, if applicable, an allowed origin/hostname; if validation fails, throw or return an error to avoid embedding unsafe values. Apply this check for both branches (PasswordResetEmail and EmailVerificationEmail) referencing payload.url, render, and transporter.sendMail so no unvalidated URL reaches the templates.
🤖 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 `@prisma/schema.prisma`:
- Line 138: Remove the redundant table-level index declaration @@index([sku]) in
the Product model because sku is already marked `@unique`; open the Product model
in schema.prisma, locate the duplicate @@index([sku]) entry and delete that
line, leaving the existing sku `@unique` declaration intact so Prisma/Postgres
only creates the single backing unique index.
---
Outside diff comments:
In `@src/components/shared/default-error-boundary.tsx`:
- Around line 115-121: The "Reload page" Button currently calls
router.navigate({ to: "." }) which performs client-side navigation; change the
call in the Button's onClick handler to invoke router.navigate with
reloadDocument: true (e.g., router.navigate({ to: ".", reloadDocument: true }))
so the action triggers a full browser/document reload and matches the "reset
after crash" intent; update the onClick where Button is defined to use this
option.
In `@src/features/email/functions/index.tsx`:
- Around line 40-90: Wrap the entire body of sendAuthEmail in a try-catch: catch
any errors thrown by render(...) or transporter.sendMail(...) and call your
application logger (or processLogger) to record a clear error including the
email payload.kind, payload.to, and the error stack/message; ensure the function
does not rethrow so fire-and-forget callers aren’t disrupted, but returns after
logging (or returns a rejected Promise if your call sites expect it), and keep
references to render and transporter.sendMail to locate where to add the
try-catch.
---
Duplicate comments:
In `@src/features/email/functions/index.tsx`:
- Around line 40-90: The sendAuthEmail function embeds payload.url into email
bodies without validation; validate payload.url before rendering/sending. In
sendAuthEmail, before calling render(...) or transporter.sendMail(...), parse
and validate payload.url (e.g., with URL constructor) to ensure it is a
well-formed absolute URL with an allowed protocol (https) and, if applicable, an
allowed origin/hostname; if validation fails, throw or return an error to avoid
embedding unsafe values. Apply this check for both branches (PasswordResetEmail
and EmailVerificationEmail) referencing payload.url, render, and
transporter.sendMail so no unvalidated URL reaches the templates.
🪄 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 Plus
Run ID: 07a8d3fc-bbcd-434e-9eb9-b03e3b62c17e
📒 Files selected for processing (6)
prisma/schema.prismasrc/components/shared/default-error-boundary.tsxsrc/features/auth/components/forgot-password-form.tsxsrc/features/auth/schema/index.tssrc/features/email/functions/index.tsxsrc/lib/permissions.ts
📜 Review details
🔇 Additional comments (1)
src/features/email/functions/index.tsx (1)
23-34: LGTM!
| </CardHeader> | ||
|
|
||
| <CardContent> | ||
| <form |
There was a problem hiding this comment.
React Doctor · react-doctor/no-prevent-default (warning)
Your users can't submit this
without JavaScript because onSubmit calls preventDefault(), so use a server action like<form action={serverAction}> to make it work either way.
Fix → Use <form action> where your framework supports it (it works without JS), or use a <button> instead of an <a> with preventDefault.
| const handleRetry = async () => { | ||
| setIsRetrying(true); | ||
|
|
||
| try { |
There was a problem hiding this comment.
React Doctor · react-hooks-js/todo (error)
This component misses React Compiler's automatic memoization & re-renders more than it should. Rewrite the flagged code so the compiler can optimize it.
Fix → Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause
| </CardHeader> | ||
|
|
||
| <CardContent> | ||
| <form |
There was a problem hiding this comment.
React Doctor · react-doctor/no-prevent-default (warning)
Your users can't submit this
without JavaScript because onSubmit calls preventDefault(), so use a server action like<form action={serverAction}> to make it work either way.
Fix → Use <form action> where your framework supports it (it works without JS), or use a <button> instead of an <a> with preventDefault.
| </CardHeader> | ||
|
|
||
| <CardContent> | ||
| <form |
There was a problem hiding this comment.
React Doctor · react-doctor/no-prevent-default (warning)
Your users can't submit this
without JavaScript because onSubmit calls preventDefault(), so use a server action like<form action={serverAction}> to make it work either way.
Fix → Use <form action> where your framework supports it (it works without JS), or use a <button> instead of an <a> with preventDefault.
| </CardHeader> | ||
|
|
||
| <CardContent> | ||
| <form |
There was a problem hiding this comment.
React Doctor · react-doctor/no-prevent-default (warning)
Your users can't submit this
without JavaScript because onSubmit calls preventDefault(), so use a server action like<form action={serverAction}> to make it work either way.
Fix → Use <form action> where your framework supports it (it works without JS), or use a <button> instead of an <a> with preventDefault.
This PR introduces a comprehensive authentication and authorization system
built with Better Auth, covering the full auth lifecycle from sign-up to
password recovery, alongside role-based access control.
Features
components with validation and error handling
with a shared email layout
redirect support
route guards
FormCheckbox, SubmitButton, PasswordInput)
reset, and forgot-password flows
getUserSessionutility and<AppLayout>withOutlet-based nested routingDefaultErrorBoundaryandDefaultNotFoundpagescreatedByrelation onStockTransferand User model adjustmentssendAuthEmailutility for transactional emails
Fixes
forgotPasswordSchemaimport/declarationonCheckedChangehandler clarity