Skip to content

feat: implement social sign-in for Google, GitHub, and Microsoft with configuration support#138

Merged
JoachimLK merged 8 commits intomainfrom
feat/authentication-methods
Apr 13, 2026
Merged

feat: implement social sign-in for Google, GitHub, and Microsoft with configuration support#138
JoachimLK merged 8 commits intomainfrom
feat/authentication-methods

Conversation

@JoachimLK
Copy link
Copy Markdown
Contributor

@JoachimLK JoachimLK commented Apr 13, 2026

Summary

  • What does this PR change?
  • Why is this needed?

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Docs
  • Chore

Validation

  • I tested locally
  • I added/updated relevant documentation
  • I verified multi-tenant scoping and auth behavior for affected API paths

DCO

  • All commits in this PR are signed off (Signed-off-by) via git commit -s

Summary by CodeRabbit

  • New Features

    • Added social sign-in (Google, GitHub, Microsoft) so users can authenticate with those providers.
    • Sign-in and sign-up pages show provider-specific “Continue with …” buttons when configured.
    • Buttons show per-provider “Redirecting…” state and are disabled during processing to prevent duplicate actions.
  • Documentation

    • Example environment file updated with optional social sign-in configuration and OAuth redirect guidance.

@railway-app
Copy link
Copy Markdown

railway-app Bot commented Apr 13, 2026

🚅 Deployed to the reqcore-pr-138 environment in applirank

Service Status Web Updated (UTC)
applirank ✅ Success (View Logs) Web Apr 13, 2026 at 11:19 am

@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-138 April 13, 2026 08:30 Destroyed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds optional social OAuth (Google, GitHub, Microsoft): env docs and schema, provider discovery API, Better Auth provider wiring with token encryption/rate-limit schema, frontend sign-in/sign-up social buttons with loading/error handling, email senders for verification/password reset, and related tests.

Changes

Cohort / File(s) Summary
Env & Config
.env.example, server/utils/env.ts, nuxt.config.ts
Documented new AUTH_<PROVIDER>_CLIENT_ID/SECRET and AUTH_MICROSOFT_TENANT_ID in example and schema; added public runtime flags authGoogleEnabled, authGithubEnabled, authMicrosoftEnabled.
Auth Core & Database
server/utils/auth.ts, server/database/schema/auth.ts, server/utils/rateLimit.ts
Better Auth init: conditional social provider registrations (Google/GitHub/Microsoft), session policy, password rules, DB hooks to encrypt tokens using BETTER_AUTH_SECRET, DB-backed rate_limit table added, in-memory rate limiter doc/warn updated.
API Endpoint
server/api/auth/providers.get.ts
New GET endpoint returning enabled provider booleans (google, github, microsoft, oidc) and oidcProviderName.
Email sending utilities
server/utils/email.ts
Added sendVerificationEmail and sendPasswordResetEmail with Resend integration, logging fallback when Resend unavailable, and HTML/text template builders.
Frontend auth pages
app/pages/auth/sign-in.vue, app/pages/auth/sign-up.vue
Fetch /api/auth/providers, compute socialProviders, add socialLoading state and handlers (handleSocialSignIn/handleSocialSignUp), render dynamic social buttons with per-provider loading and error handling; minor button class tweaks.
Env-driven behavior tests
tests/unit/auth-security-hardening.test.ts
New Vitest suite asserting password policy, session expiry, encryption-at-rest properties, rate-limit config intent, and OIDC PKCE/state intent.

Sequence Diagram

sequenceDiagram
    participant User
    participant Frontend as Frontend (Vue)
    participant API as Backend API (/api/auth/providers)
    participant BetterAuth as Better Auth
    participant OAuth as OAuth Provider

    User->>Frontend: Clicks "Continue with Provider"
    Frontend->>API: (optional) fetch provider flags
    Frontend->>Frontend: set socialLoading, compute callbackURL
    Frontend->>BetterAuth: authClient.signIn.social(providerId)
    BetterAuth->>OAuth: Redirect user to provider consent
    OAuth->>User: Consent screen
    User->>OAuth: Approve
    OAuth->>BetterAuth: Redirect with code
    BetterAuth->>BetterAuth: Exchange code → tokens
    BetterAuth->>Backend: Persist session / encrypted tokens
    BetterAuth->>Frontend: Redirect to callbackURL
    Frontend->>User: Finalize redirect / show signed-in state
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~40 minutes

Possibly related PRs

  • Add OIDC SSO support #136 — Overlaps changes to auth sign-in/sign-up provider gating and replacing runtimeConfig OIDC flags with a runtime provider discovery approach.

Poem

🐰 I hopped to add buttons three,

Google, GitHub, Microsoft — whee!
Tokens tucked and emails sent,
Providers found with one event,
Hop, redirect, sign-in glee! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The pull request description is incomplete. The summary section is blank and critical validation items remain unchecked, including local testing, documentation updates, and multi-tenant verification. Complete the summary to explain what changes were made and why. Check relevant validation boxes and ensure all commits are signed off with DCO (Signed-off-by).
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: implementing social sign-in for Google, GitHub, and Microsoft with configuration support. This is the primary feature introduced across the changeset.

✏️ 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 feat/authentication-methods

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.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

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 (2)
app/pages/auth/sign-in.vue (1)

30-36: Extract shared social-provider metadata/UI to avoid drift with sign-up page.

The provider list and icon/button rendering here are duplicated in app/pages/auth/sign-up.vue (Line 29-Line 35 and Line 162-Line 197). A shared constant/composable/component would reduce maintenance risk.

♻️ Suggested direction
+// app/composables/useSocialProviders.ts
+export const SOCIAL_PROVIDERS = [
+  { id: "google", name: "Google" },
+  { id: "github", name: "GitHub" },
+  { id: "microsoft", name: "Microsoft" },
+] as const;
+
+export type SocialProviderId = (typeof SOCIAL_PROVIDERS)[number]["id"];
-const socialProviders = computed(() => {
-    const providers: { id: string; name: string }[] = [];
-    if (config.public.authGoogleEnabled) providers.push({ id: "google", name: "Google" });
-    if (config.public.authGithubEnabled) providers.push({ id: "github", name: "GitHub" });
-    if (config.public.authMicrosoftEnabled) providers.push({ id: "microsoft", name: "Microsoft" });
-    return providers;
-});
+const socialProviders = computed(() =>
+  SOCIAL_PROVIDERS.filter((p) =>
+    p.id === "google" ? config.public.authGoogleEnabled :
+    p.id === "github" ? config.public.authGithubEnabled :
+    config.public.authMicrosoftEnabled
+  )
+);

Also applies to: 217-252

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

In `@app/pages/auth/sign-in.vue` around lines 30 - 36, The social provider list is
duplicated between sign-in and sign-up; extract the logic and UI into a shared
module and replace both copies: create a small exported composable or constant
(e.g., useSocialProviders or SOCIAL_PROVIDERS) that returns the providers array
and a reusable presentational component (e.g., SocialAuthButtons) that renders
the icon/button markup, then import and use those in app/pages/auth/sign-in.vue
(replace socialProviders and its rendering) and app/pages/auth/sign-up.vue
(replace the duplicated arrays and render blocks at the indicated ranges) so
both pages consume the single source of truth.
server/utils/env.ts (1)

111-124: Add all-or-none validation per social provider to fail fast on partial config.

Right now, setting only one of AUTH_<PROVIDER>_CLIENT_ID/SECRET silently disables that provider downstream. Consider validating pair completeness in superRefine (similar to OIDC) to surface misconfiguration early.

🧩 Proposed validation addition
   .superRefine((data, ctx) => {
+    const socialPairs = [
+      ["AUTH_GOOGLE_CLIENT_ID", data.AUTH_GOOGLE_CLIENT_ID, "AUTH_GOOGLE_CLIENT_SECRET", data.AUTH_GOOGLE_CLIENT_SECRET],
+      ["AUTH_GITHUB_CLIENT_ID", data.AUTH_GITHUB_CLIENT_ID, "AUTH_GITHUB_CLIENT_SECRET", data.AUTH_GITHUB_CLIENT_SECRET],
+      ["AUTH_MICROSOFT_CLIENT_ID", data.AUTH_MICROSOFT_CLIENT_ID, "AUTH_MICROSOFT_CLIENT_SECRET", data.AUTH_MICROSOFT_CLIENT_SECRET],
+    ] as const;
+
+    for (const [idName, idVal, secretName, secretVal] of socialPairs) {
+      if ((idVal && !secretVal) || (!idVal && secretVal)) {
+        ctx.addIssue({
+          code: z.ZodIssueCode.custom,
+          path: [!idVal ? idName : secretName],
+          message: `${idName} and ${secretName} must both be set or both be unset.`,
+        });
+      }
+    }
+
     // BETTER_AUTH_URL can be derived at runtime from RAILWAY_PUBLIC_DOMAIN,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/env.ts` around lines 111 - 124, Add an all-or-none validation
for each social provider inside the schema's superRefine: for each pair
AUTH_GOOGLE_CLIENT_ID/AUTH_GOOGLE_CLIENT_SECRET,
AUTH_GITHUB_CLIENT_ID/AUTH_GITHUB_CLIENT_SECRET, and
AUTH_MICROSOFT_CLIENT_ID/AUTH_MICROSOFT_CLIENT_SECRET, detect if one is present
and the other is missing and call ctx.addIssue with a custom error for the
missing key(s) so partial configs fail fast; keep the existing
AUTH_MICROSOFT_TENANT_ID handling as-is but ensure the Microsoft client pair is
validated the same way, referencing those exact symbol names in the superRefine
implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@nuxt.config.ts`:
- Around line 225-239: The public auth feature flags (authGoogleEnabled,
authGithubEnabled, authMicrosoftEnabled) in nuxt.config.ts are being computed
from process.env at build time and frozen into runtimeConfig.public, causing
UI/backend mismatches versus the runtime evaluation in server/utils/auth.ts's
getAuth(); change those public flags to read from NUXT_PUBLIC_AUTH_* environment
variables (NUXT_PUBLIC_AUTH_GOOGLE_CLIENT_ID / SECRET etc.) so they can be
overridden at runtime, or alternatively move the flag computation out of
nuxt.config.ts into a runtime-evaluated utility used by both the UI and server
(keeping getAuth() as the single source of truth) to ensure consistent runtime
behavior.

---

Nitpick comments:
In `@app/pages/auth/sign-in.vue`:
- Around line 30-36: The social provider list is duplicated between sign-in and
sign-up; extract the logic and UI into a shared module and replace both copies:
create a small exported composable or constant (e.g., useSocialProviders or
SOCIAL_PROVIDERS) that returns the providers array and a reusable presentational
component (e.g., SocialAuthButtons) that renders the icon/button markup, then
import and use those in app/pages/auth/sign-in.vue (replace socialProviders and
its rendering) and app/pages/auth/sign-up.vue (replace the duplicated arrays and
render blocks at the indicated ranges) so both pages consume the single source
of truth.

In `@server/utils/env.ts`:
- Around line 111-124: Add an all-or-none validation for each social provider
inside the schema's superRefine: for each pair
AUTH_GOOGLE_CLIENT_ID/AUTH_GOOGLE_CLIENT_SECRET,
AUTH_GITHUB_CLIENT_ID/AUTH_GITHUB_CLIENT_SECRET, and
AUTH_MICROSOFT_CLIENT_ID/AUTH_MICROSOFT_CLIENT_SECRET, detect if one is present
and the other is missing and call ctx.addIssue with a custom error for the
missing key(s) so partial configs fail fast; keep the existing
AUTH_MICROSOFT_TENANT_ID handling as-is but ensure the Microsoft client pair is
validated the same way, referencing those exact symbol names in the superRefine
implementation.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 6fb1dc4d-67f4-4088-a27d-b9a03cf17041

📥 Commits

Reviewing files that changed from the base of the PR and between ee34d86 and 0e4d4bd.

📒 Files selected for processing (6)
  • .env.example
  • app/pages/auth/sign-in.vue
  • app/pages/auth/sign-up.vue
  • nuxt.config.ts
  • server/utils/auth.ts
  • server/utils/env.ts

Comment thread nuxt.config.ts Outdated
Comment on lines +225 to +239
/** Whether Google social sign-in is enabled */
authGoogleEnabled: !!(
process.env.AUTH_GOOGLE_CLIENT_ID &&
process.env.AUTH_GOOGLE_CLIENT_SECRET
),
/** Whether GitHub social sign-in is enabled */
authGithubEnabled: !!(
process.env.AUTH_GITHUB_CLIENT_ID &&
process.env.AUTH_GITHUB_CLIENT_SECRET
),
/** Whether Microsoft social sign-in is enabled */
authMicrosoftEnabled: !!(
process.env.AUTH_MICROSOFT_CLIENT_ID &&
process.env.AUTH_MICROSOFT_CLIENT_SECRET
),
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 | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In Nuxt 4, if runtimeConfig.public values are computed from process.env.AUTH_* inside nuxt.config.ts, are those values fixed at config/build evaluation unless overridden by NUXT_PUBLIC_* runtime env vars?

💡 Result:

Yes, in Nuxt 4, if runtimeConfig.public values are computed from process.env.AUTH_* (e.g., process.env.AUTH_SECRET) inside nuxt.config.ts, those values are fixed at build/config evaluation time. They can only be overridden at runtime by the corresponding NUXT_PUBLIC_* environment variables (e.g., NUXT_PUBLIC_AUTH_SECRET for public.runtimeConfig.authSecret). The official Nuxt documentation explicitly states: "Setting the default of runtimeConfig values to differently named environment variables (for example setting myVar to process.env.OTHER_VARIABLE) will only work during build-time and will break on runtime." Runtime overrides only work with matching NUXT_ prefixed variables that follow the runtimeConfig structure.

Citations:


🏁 Script executed:

cat -n nuxt.config.ts | sed -n '220,245p'

Repository: reqcore-inc/reqcore

Length of output: 1072


🏁 Script executed:

find . -name "auth.ts" -type f | head -20

Repository: reqcore-inc/reqcore

Length of output: 143


🏁 Script executed:

cat -n server/utils/auth.ts

Repository: reqcore-inc/reqcore

Length of output: 13558


Public social flags are fixed at build time while server-side providers are evaluated at runtime, causing UI/backend mismatch in runtime-only deployments.

Lines 225-239 compute authGoogleEnabled, authGithubEnabled, and authMicrosoftEnabled from process.env.AUTH_* inside nuxt.config.ts. In Nuxt 4, these build-time values are frozen into runtimeConfig.public and cannot be changed at runtime unless overridden by corresponding NUXT_PUBLIC_* environment variables.

However, server/utils/auth.ts (lines 167–193) evaluates the same AUTH_* variables at runtime when getAuth() is first called. This creates a critical mismatch:

  • Scenario A: If AUTH_* vars are not set at build time but injected at runtime (common in Railway and similar platforms), the UI flags remain false while the server can enable providers — users won't see the buttons despite functional backends.
  • Scenario B: If AUTH_* vars are set at build time but removed at runtime, the UI shows buttons for disabled providers — users encounter auth failures.

Fix: Use NUXT_PUBLIC_AUTH_* environment variables for the runtimeConfig.public flags, or move the flag computation logic to a runtime-evaluated utility that reads directly from env at request time.

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

In `@nuxt.config.ts` around lines 225 - 239, The public auth feature flags
(authGoogleEnabled, authGithubEnabled, authMicrosoftEnabled) in nuxt.config.ts
are being computed from process.env at build time and frozen into
runtimeConfig.public, causing UI/backend mismatches versus the runtime
evaluation in server/utils/auth.ts's getAuth(); change those public flags to
read from NUXT_PUBLIC_AUTH_* environment variables
(NUXT_PUBLIC_AUTH_GOOGLE_CLIENT_ID / SECRET etc.) so they can be overridden at
runtime, or alternatively move the flag computation out of nuxt.config.ts into a
runtime-evaluated utility used by both the UI and server (keeping getAuth() as
the single source of truth) to ensure consistent runtime behavior.

@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-138 April 13, 2026 08:51 Destroyed
@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-138 April 13, 2026 09:01 Destroyed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
server/plugins/auth-flags.ts (1)

16-40: Consider a tiny helper to reduce repeated env checks.

This is optional, but it lowers copy/paste risk when adding more providers/flags later.

♻️ Suggested cleanup
 export default defineNitroPlugin(() => {
   const cfg = useRuntimeConfig();
+  const hasEnv = (...keys: string[]) => keys.every((k) => Boolean(process.env[k]));

-  cfg.public.authGoogleEnabled = !!(
-    process.env.AUTH_GOOGLE_CLIENT_ID &&
-    process.env.AUTH_GOOGLE_CLIENT_SECRET
-  );
+  cfg.public.authGoogleEnabled = hasEnv(
+    "AUTH_GOOGLE_CLIENT_ID",
+    "AUTH_GOOGLE_CLIENT_SECRET",
+  );

-  cfg.public.authGithubEnabled = !!(
-    process.env.AUTH_GITHUB_CLIENT_ID &&
-    process.env.AUTH_GITHUB_CLIENT_SECRET
-  );
+  cfg.public.authGithubEnabled = hasEnv(
+    "AUTH_GITHUB_CLIENT_ID",
+    "AUTH_GITHUB_CLIENT_SECRET",
+  );

-  cfg.public.authMicrosoftEnabled = !!(
-    process.env.AUTH_MICROSOFT_CLIENT_ID &&
-    process.env.AUTH_MICROSOFT_CLIENT_SECRET
-  );
+  cfg.public.authMicrosoftEnabled = hasEnv(
+    "AUTH_MICROSOFT_CLIENT_ID",
+    "AUTH_MICROSOFT_CLIENT_SECRET",
+  );

-  cfg.public.oidcEnabled = !!(
-    process.env.OIDC_CLIENT_ID &&
-    process.env.OIDC_CLIENT_SECRET &&
-    process.env.OIDC_DISCOVERY_URL
-  );
+  cfg.public.oidcEnabled = hasEnv(
+    "OIDC_CLIENT_ID",
+    "OIDC_CLIENT_SECRET",
+    "OIDC_DISCOVERY_URL",
+  );

-  cfg.public.feedbackEnabled = !!(
-    process.env.GITHUB_FEEDBACK_TOKEN &&
-    process.env.GITHUB_FEEDBACK_REPO
-  );
+  cfg.public.feedbackEnabled = hasEnv(
+    "GITHUB_FEEDBACK_TOKEN",
+    "GITHUB_FEEDBACK_REPO",
+  );
 });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/plugins/auth-flags.ts` around lines 16 - 40, Extract the repeated
environment checks into a small helper like hasEnvVars(varNames: string[]) and
replace each boolean assignment (cfg.public.authGoogleEnabled,
cfg.public.authGithubEnabled, cfg.public.authMicrosoftEnabled,
cfg.public.oidcEnabled, cfg.public.feedbackEnabled) to call that helper with the
relevant env variable names; the helper should return !!varNames.every(name =>
!!process.env[name]) so future flags only need to list required env keys and you
avoid copy/paste and inconsistent checks.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@server/plugins/auth-flags.ts`:
- Around line 16-40: Extract the repeated environment checks into a small helper
like hasEnvVars(varNames: string[]) and replace each boolean assignment
(cfg.public.authGoogleEnabled, cfg.public.authGithubEnabled,
cfg.public.authMicrosoftEnabled, cfg.public.oidcEnabled,
cfg.public.feedbackEnabled) to call that helper with the relevant env variable
names; the helper should return !!varNames.every(name => !!process.env[name]) so
future flags only need to list required env keys and you avoid copy/paste and
inconsistent checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d91acc23-9523-46d3-b818-0ba2d9c048dd

📥 Commits

Reviewing files that changed from the base of the PR and between d8d0e6e and 6b7b699.

📒 Files selected for processing (1)
  • server/plugins/auth-flags.ts

@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-138 April 13, 2026 09:13 Destroyed
@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-138 April 13, 2026 09:53 Destroyed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
server/api/auth/providers.get.ts (1)

11-27: Reduce drift risk by centralizing provider-enable checks.

This logic is duplicated from server/utils/auth.ts. Consider extracting a shared helper (e.g., server/utils/authProviderFlags.ts) and reusing it here and in auth initialization so UI flags and actual provider registration can’t diverge.

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

In `@server/api/auth/providers.get.ts` around lines 11 - 27, The provider-enable
boolean checks (google, github, microsoft, oidc) are duplicated here and in
server/utils/auth.ts; extract them into a single helper (e.g., export a function
or object from server/utils/authProviderFlags.ts) that performs the environment
checks for AUTH_GOOGLE_CLIENT_ID/SECRET, AUTH_GITHUB_CLIENT_ID/SECRET,
AUTH_MICROSOFT_CLIENT_ID/SECRET, and OIDC_CLIENT_ID/SECRET/DISCOVERY_URL, then
import and use that helper in providers.get.ts (replacing the inline boolean
expressions) and in server/utils/auth.ts so both the UI flags and provider
registration derive from the same source.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@server/api/auth/providers.get.ts`:
- Around line 11-27: The provider-enable boolean checks (google, github,
microsoft, oidc) are duplicated here and in server/utils/auth.ts; extract them
into a single helper (e.g., export a function or object from
server/utils/authProviderFlags.ts) that performs the environment checks for
AUTH_GOOGLE_CLIENT_ID/SECRET, AUTH_GITHUB_CLIENT_ID/SECRET,
AUTH_MICROSOFT_CLIENT_ID/SECRET, and OIDC_CLIENT_ID/SECRET/DISCOVERY_URL, then
import and use that helper in providers.get.ts (replacing the inline boolean
expressions) and in server/utils/auth.ts so both the UI flags and provider
registration derive from the same source.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 31ebd5d8-84f3-4a0e-bd6f-e583de734e39

📥 Commits

Reviewing files that changed from the base of the PR and between 6b7b699 and ad91cc9.

📒 Files selected for processing (3)
  • app/pages/auth/sign-in.vue
  • app/pages/auth/sign-up.vue
  • server/api/auth/providers.get.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/pages/auth/sign-in.vue
  • app/pages/auth/sign-up.vue

@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-138 April 13, 2026 10:33 Destroyed
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

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

Inline comments:
In `@server/database/schema/auth.ts`:
- Around line 116-123: Create a new migration that creates the rate_limit table
to match the schema defined in server/database/schema/auth.ts: columns id (text
primary key), key (text not null), count (integer not null), last_request
(bigint not null) and an index on key (rate_limit_key_idx); include a down
migration that drops the rate_limit table so rollbacks work; name the migration
consistently with the project’s migration naming convention and export it so
your migration runner picks it up before deploying since server/utils/auth.ts
sets rateLimit.storage = "database".

In `@server/utils/auth.ts`:
- Around line 171-181: The auth config currently wires sendResetPassword under
emailAndPassword but never registers the new sendVerificationEmail or enables
email verification; add an emailVerification block that calls
sendVerificationEmail (importing it from server/utils/email.ts) and add
requireEmailVerification: true under the existing emailAndPassword config so
Better Auth will trigger verification; ensure you reference the existing
sendResetPassword handler and the emailAndPassword object when adding the
emailVerification handler and importing sendVerificationEmail.
- Around line 183-215: The custom databaseHooks on account (create/update) that
call encrypt(...) with env.BETTER_AUTH_SECRET should be removed and replaced by
enabling Better Auth's built-in transparent encryption: set the account option
encryptOAuthTokens: true (remove the manual databaseHooks for
account.create.before and account.update.before and any direct encrypt(...)
usage), so Better Auth will handle encryption on write and automatic decryption
on read; ensure no leftover references to env.BETTER_AUTH_SECRET or encrypt(...)
remain for account tokens.

In `@server/utils/email.ts`:
- Around line 43-57: The Resend send call can throw (network/SDK exceptions)
which aren't caught—wrap the call inside sendVerificationEmail in a try/catch so
thrown exceptions are logged as well as the returned { error } handled; call
resend.emails.send inside try, if it returns an { error } call
logError('email.verification_send_failed', { provider: 'resend', error_message:
error.message }), and in catch logError('email.verification_send_failed', {
provider: 'resend', error_message: err.message, stack: err.stack }); repeat the
same try/catch + returned-error handling pattern for the other similar helper
(the block around lines 84-98).
- Around line 33-38: The fallback logging is leaking bearer-token URLs and
recipient emails (see the console.info guarded by the resend flag referencing
data.user.email and data.url and the similar block at lines 74-79); change these
logs to avoid printing the full email or URL — either remove the URL and email
entirely or redact them (e.g., log "verification email sent" with a
non-sensitive identifier like a hashed email or just the domain), and ensure any
remaining log text does not include data.url or data.user.email; update both the
first console.info block and the similar block later so no bearer token or full
address is emitted.

In `@tests/unit/auth-security-hardening.test.ts`:
- Around line 14-56: Replace the local magic constants in the tests with the
real exported auth config so the assertions validate actual runtime values:
remove the local MIN_LENGTH, MAX_LENGTH, SESSION_EXPIRES_IN, and
SESSION_UPDATE_AGE declarations and instead import the matching exports from the
auth config module (e.g., the constants exported by server/utils/auth.ts or your
config builder), then update the specs to assert against those imported symbols
(MIN_LENGTH, MAX_LENGTH, SESSION_EXPIRES_IN, SESSION_UPDATE_AGE) so changes in
the implementation will fail the tests.
- Around line 104-131: The test duplicates and diverges from production logic;
replace the local rateLimitConfig and the `'CI' in process.env` recompute by
importing the actual exported rate-limit configuration or factory from
server/utils/auth.ts (e.g., rateLimitConfig or getRateLimitConfig) and assert
against that object, and when simulating CI checks use the same boolean
expression the production code uses (!process.env.CI &&
!process.env.GITHUB_ACTIONS) to determine enabled/disabled so the test fails if
the real config drifts.
- Around line 136-157: The test currently asserts on hand-written mocks
(oidcConfig and defaultOAuthState) instead of exercising real code; update the
test to import and assert against the actual exported genericOAuth config from
auth.ts (verify pkce and requireIssuerValidation there) and call the
repository's real OAuth state builder/helper (e.g., buildOAuthState or
createOAuthState) to generate state and assert it includes codeVerifier; if no
shared state builder exists yet, remove the defaultOAuthState test until one is
available.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7334bf57-c1f9-45e5-9ec1-ac9f547c8624

📥 Commits

Reviewing files that changed from the base of the PR and between ad91cc9 and aaae17f.

📒 Files selected for processing (5)
  • server/database/schema/auth.ts
  • server/utils/auth.ts
  • server/utils/email.ts
  • server/utils/rateLimit.ts
  • tests/unit/auth-security-hardening.test.ts
✅ Files skipped from review due to trivial changes (1)
  • server/utils/rateLimit.ts

Comment thread server/database/schema/auth.ts
Comment thread server/utils/auth.ts
Comment on lines 171 to +181
emailAndPassword: {
enabled: true,
// Server-side password policy — prevents bypass via direct API calls.
// Client-side validation (sign-up.vue) is UX only; this is the enforcement.
minPasswordLength: 8,
maxPasswordLength: 128,
// Password reset via email.
async sendResetPassword({ user, url, token }, request) {
void sendPasswordResetEmail({ user, url, token });
},
},
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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Expect a real auth config path to wire the verification sender.
rg -n --hidden --glob '!**/node_modules/**' --glob '!**/.git/**' '\bsendVerificationEmail\b|\bemailVerification\b|\brequireEmailVerification\b'

Repository: reqcore-inc/reqcore

Length of output: 231


🏁 Script executed:

cat -n server/utils/auth.ts

Repository: reqcore-inc/reqcore

Length of output: 16934


🏁 Script executed:

find . -type f \( -name '*.ts' -o -name '*.js' -o -name '*.tsx' -o -name '*.jsx' \) ! -path '*/node_modules/*' ! -path '*/.git/*' -exec grep -l "emailVerification\|requireEmailVerification\|sendVerificationEmail" {} \;

Repository: reqcore-inc/reqcore

Length of output: 86


🏁 Script executed:

sed -n '20,35p' server/utils/email.ts

Repository: reqcore-inc/reqcore

Length of output: 595


The new verification-email flow still looks unwired.

This file hooks up sendResetPassword, but the new sendVerificationEmail function from server/utils/email.ts is never imported or referenced here. There is no emailVerification block and no requireEmailVerification setting in the auth config. Better Auth requires both to activate email verification—the function needs to be wired through a dedicated emailVerification config block while requireEmailVerification belongs under emailAndPassword. (better-auth.com)

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

In `@server/utils/auth.ts` around lines 171 - 181, The auth config currently wires
sendResetPassword under emailAndPassword but never registers the new
sendVerificationEmail or enables email verification; add an emailVerification
block that calls sendVerificationEmail (importing it from server/utils/email.ts)
and add requireEmailVerification: true under the existing emailAndPassword
config so Better Auth will trigger verification; ensure you reference the
existing sendResetPassword handler and the emailAndPassword object when adding
the emailVerification handler and importing sendVerificationEmail.

Comment thread server/utils/auth.ts
Comment thread server/utils/email.ts
Comment thread server/utils/email.ts Outdated
Comment on lines +14 to +56
describe('Server-side password policy', () => {
const MIN_LENGTH = 8
const MAX_LENGTH = 128

it('rejects passwords shorter than minimum', () => {
const password = 'short'
expect(password.length).toBeLessThan(MIN_LENGTH)
})

it('accepts passwords at minimum length', () => {
const password = 'a'.repeat(MIN_LENGTH)
expect(password.length).toBeGreaterThanOrEqual(MIN_LENGTH)
})

it('rejects passwords exceeding maximum length', () => {
const password = 'a'.repeat(MAX_LENGTH + 1)
expect(password.length).toBeGreaterThan(MAX_LENGTH)
})

it('minimum length is at least 8 characters', () => {
expect(MIN_LENGTH).toBeGreaterThanOrEqual(8)
})
})

// ── Issue #6: Explicit session expiry ───────────────────────────────

describe('Session expiry configuration', () => {
const SESSION_EXPIRES_IN = 60 * 60 * 24 // 24 hours (seconds)
const SESSION_UPDATE_AGE = 60 * 60 // 1 hour (seconds)

it('session expiry is explicitly set (not default 7 days)', () => {
const DEFAULT_BETTER_AUTH_EXPIRY = 60 * 60 * 24 * 7 // 7 days
expect(SESSION_EXPIRES_IN).toBeLessThan(DEFAULT_BETTER_AUTH_EXPIRY)
})

it('session expiry is at most 24 hours', () => {
expect(SESSION_EXPIRES_IN).toBeLessThanOrEqual(60 * 60 * 24)
})

it('session update age is shorter than expiry', () => {
expect(SESSION_UPDATE_AGE).toBeLessThan(SESSION_EXPIRES_IN)
})
})
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 | 🟠 Major

These assertions don't protect the real password/session config.

Everything here is derived from local constants, so server/utils/auth.ts can change and this block will still stay green. Please extract the auth constants into a shared module or a pure config builder and assert against the imported values instead.

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

In `@tests/unit/auth-security-hardening.test.ts` around lines 14 - 56, Replace the
local magic constants in the tests with the real exported auth config so the
assertions validate actual runtime values: remove the local MIN_LENGTH,
MAX_LENGTH, SESSION_EXPIRES_IN, and SESSION_UPDATE_AGE declarations and instead
import the matching exports from the auth config module (e.g., the constants
exported by server/utils/auth.ts or your config builder), then update the specs
to assert against those imported symbols (MIN_LENGTH, MAX_LENGTH,
SESSION_EXPIRES_IN, SESSION_UPDATE_AGE) so changes in the implementation will
fail the tests.

Comment thread tests/unit/auth-security-hardening.test.ts
Comment thread tests/unit/auth-security-hardening.test.ts Outdated
@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-138 April 13, 2026 10:43 Destroyed
…social sign-in options and enhancing OAuth token encryption
@railway-app railway-app Bot temporarily deployed to applirank / reqcore-pr-138 April 13, 2026 11:14 Destroyed
@JoachimLK JoachimLK merged commit d4ceaf8 into main Apr 13, 2026
5 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