Skip to content

Tenant scoping, encrypted tenant secrets, Redis rate limiting, and audit logging#1

Merged
AutomatosAI merged 1 commit intomainfrom
codex/perform-full-review-of-saas-platform
Jan 13, 2026
Merged

Tenant scoping, encrypted tenant secrets, Redis rate limiting, and audit logging#1
AutomatosAI merged 1 commit intomainfrom
codex/perform-full-review-of-saas-platform

Conversation

@AutomatosAI
Copy link
Copy Markdown
Owner

@AutomatosAI AutomatosAI commented Jan 13, 2026

Motivation

  • Prevent tenant header spoofing and centralize tenant context so downstream code can rely on a single request-scoped tenant identifier.
  • Eliminate the unsafe fallback to the "first active tenant" to avoid accidental cross-tenant access.
  • Protect tenant API credentials by storing them encrypted and avoiding exposure in the UI.
  • Move rate limiting to a Redis-backed store for production reliability and add audit instrumentation for settings changes.

Description

  • Add request-scoped tenant context using AsyncLocalStorage (lib/tenant-context.ts) and set it from tenant resolution in lib/tenant.ts and getTenantFromRequest so the tenant id is available per request.
  • Enforce tenant scoping in Prisma via middleware added to lib/db.ts, which injects tenantId for create/upsert and scopes where clauses for tenant-scoped models and actions.
  • Harden middleware.ts to remove incoming x-tenant-* headers, set derived x-tenant-slug|subdomain|custom-domain on proxied requests, and avoid trusting client-supplied tenant headers.
  • Encrypt tenant Dr. Green credentials on save and decrypt on use via changes to app/api/tenant-admin/settings/route.ts, lib/tenant-config.ts, and lib/encryption.ts, mask keys in the settings UI, and record a SETTINGS_UPDATED audit entry when settings are changed.
  • Replace the in-memory rate limiter with a Redis-backed implementation in lib/rate-limit.ts and update callers to await checkRateLimit(...) across affected API routes, and add a toggle for Next.js image optimization in next.config.js.

Testing

  • No automated tests or CI jobs were executed for these changes (no test results available).

Codex Task

Summary by CodeRabbit

Release Notes

  • New Features

    • Added audit logging for tenant settings updates, capturing user, tenant, and request metadata for compliance tracking.
    • Improved multi-tenant configuration with tenant-specific credentials and dynamic context propagation.
  • Bug Fixes

    • Fixed rate limiting to properly handle asynchronous operations.
    • Corrected encryption key resolution to use the appropriate environment configuration.
  • Improvements

    • Enhanced API key security with improved encryption and field masking.
    • Upgraded rate limiting to a more resilient, scalable backend infrastructure.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

This change introduces infrastructure for multi-tenant isolation and API request rate limiting. Adds Redis-backed rate limiting with async evaluation, tenant-scoped Prisma operations via middleware, Dr. Green API key encryption and decryption, AsyncLocalStorage-based tenant context propagation, and audit logging for settings updates.

Changes

Cohort / File(s) Summary
Rate Limiting Async Migration
nextjs_space/app/api/super-admin/tenants/bulk/route.ts, nextjs_space/app/api/super-admin/tenants/route.ts, nextjs_space/app/api/tenant-admin/analytics/route.ts, nextjs_space/app/api/tenant-admin/orders/bulk/route.ts, nextjs_space/app/api/tenant-admin/products/bulk/route.ts, nextjs_space/app/api/tenant-admin/products/reorder/route.ts
Adds await to checkRateLimit() calls to handle async resolution before evaluating success status.
Rate Limiting Implementation
nextjs_space/lib/rate-limit.ts
Replaces in-memory sliding window with Redis-backed fixed-window rate limiter; both checkRateLimit and getRateLimitStatus become async; uses Redis INCR with TTL-driven windowing; defaults to allowing requests on Redis errors.
Tenant Context Infrastructure
nextjs_space/lib/tenant-context.ts
New file: Introduces AsyncLocalStorage-based tenant context with TenantContext type, setTenantContext, getTenantContext, and runWithTenantContext for propagating tenant ID through async call chains.
Tenant Scoping & Identification
nextjs_space/lib/tenant.ts
Adds setTenantContext calls in getCurrentTenant and getTenantContext; enhances path-based routing to recognize /store/{slug}; ensures context is cleared on error/missing tenant.
Prisma Tenant Scoping
nextjs_space/lib/db.ts
Adds Prisma middleware with tenant-aware query filtering; injects tenantId into where clauses and data payloads; maps findUnique to findFirst for tenant filtering; respects models that allow null tenantId.
Dr. Green API Key Encryption
nextjs_space/app/api/shop/register/route.ts
Adds getTenantDrGreenConfig import; switches from tenant-embedded fallback to two-step credential resolution: initialize with platform env keys, then attempt to fetch and override with tenant-specific credentials.
Settings Audit & Encryption
nextjs_space/app/api/tenant-admin/settings/route.ts
Adds audit logging imports; encrypts drGreenApiKey when provided; logs successful updates with user, tenant, IP, user agent, and updated field metadata; masks sensitive fields in logging output.
Client-Side API Key Masking
nextjs_space/app/tenant-admin/settings/page.tsx, nextjs_space/app/tenant-admin/settings/settings-form.tsx
Masks drGreenApiKey before sending to client (page.tsx); updates form UI to show API key verified state via hasApiKey flag and contextual helper text (settings-form.tsx).
API Key Decryption
nextjs_space/lib/tenant-config.ts
Decrypts drGreenApiKey; tightens error handling to validate both API key and secret key are decrypted successfully.
Encryption Key Sourcing
nextjs_space/lib/encryption.ts
Removes fallback to NEXT_PUBLIC_ENCRYPTION_KEY; requires ENCRYPTION_KEY env var (errors if missing).
Middleware Header Propagation
nextjs_space/middleware.ts
Removes tenant-related headers from incoming request; forwards sanitized headers via NextResponse.next({ request: { headers } }) across all routes; sets x-tenant-slug, x-tenant-subdomain, or x-tenant-custom-domain on request headers based on routing context.
Image Optimization Configuration
nextjs_space/next.config.js
Makes images.unoptimized dynamic: conditionally set to true if NEXT_IMAGE_UNOPTIMIZED env var equals 'true'.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Poem

🐰 A rabbit hops through Redis queues so grand,
Tenant contexts flow through async land,
Green keys encrypted, audit logs refine,
Middleware headers now align—
Multi-tenant dreams come to life,
With scoping and security, no more strife!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.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 clearly and concisely summarizes the four main objectives of the changeset: tenant scoping, encrypted tenant secrets, Redis rate limiting, and audit logging.

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

✨ Finishing touches
  • 📝 Generate docstrings

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

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 98306132ab

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread nextjs_space/lib/db.ts
Comment on lines +134 to +136
if (params.args?.where) {
params.args.where = applyTenantScope(params.args.where, tenantId, allowNull);
} else if (params.action !== 'createMany') {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Avoid injecting tenantId into update/delete unique filters

The middleware unconditionally rewrites params.args.where for all actions, including update, delete, and upsert, by merging in tenantId. Those actions use Prisma WhereUniqueInput, which only accepts unique fields (e.g., id), so adding tenantId will throw a runtime validation error for models without a composite unique on tenantId (e.g., orders, users). This means routes that set tenant context (like /app/api/orders/route.ts, which calls getTenantFromRequest and then prisma.orders.update({ where: { id } })) will now fail once the context is set. Consider scoping by converting to updateMany/deleteMany or handling scoping in a way that preserves WhereUniqueInput requirements.

Useful? React with 👍 / 👎.

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

Caution

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

⚠️ Outside diff range comments (1)
nextjs_space/app/api/shop/register/route.ts (1)

35-53: Incomplete handling for missing credentials allows silent failure.

The code logs an error at line 49 when credentials are missing but continues to createClient, which will throw. This defers the failure rather than providing a clear early response.

Consider returning a proper error response when credentials are unavailable:

Suggested improvement
     if (!config.apiKey || !config.secretKey) {
       console.error('Missing Dr. Green API credentials for registration');
-      // Continue anyway? The createClient might fail or we should block.
-      // createClient throws 'MISSING_CREDENTIALS` if keys are missing.
-      // We'll let it proceed and fail inside if needed, or handle it here.
+      return NextResponse.json(
+        { error: 'Dr. Green API is not configured for this store. Please contact support.' },
+        { status: 503 }
+      );
     }
🤖 Fix all issues with AI agents
In @nextjs_space/lib/db.ts:
- Around line 107-109: The middleware converts params.action from 'findUnique'
to 'findFirst' (params.action) to inject tenant scoping, which masks a schema
issue: orders.orderNumber and users.email are single-field uniques and could
leak across tenants if middleware is bypassed; fix by adding compound unique
constraints in the schema (e.g., @@unique([tenantId, orderNumber]) on the orders
model and a composite unique that includes tenantId for users' email) or
alternatively ensure all code paths always include tenantId in unique lookups
(update model definitions for orders and users and any unique-lookup usage to
include tenantId).

In @nextjs_space/lib/tenant.ts:
- Around line 165-168: The catch block in the function that fetches tenant from
a request currently logs the error and returns null but does not clear any
previously set tenant context; update the catch handler in getTenantFromRequest
(or the function that calls setTenantContext(tenant.id)) to call
setTenantContext(null) before returning null so you don't leave a stale tenant
in context — alternatively move the context cleanup into a finally block that
always calls setTenantContext(null) if appropriate.
🧹 Nitpick comments (7)
nextjs_space/lib/encryption.ts (1)

41-47: Fallback to returning original text may mask encryption issues.

When the format is invalid, returning the original text could silently expose unencrypted sensitive data if a caller assumes the value is decrypted. Consider throwing an error or returning a distinct sentinel value to make the failure explicit.

♻️ Suggested improvement
     if (parts.length !== 3) {
-        // Fallback: If it's not in our format, return as is (maybe it wasn't encrypted yet)
-        // Or throw error. For migration safety, we can return null or error.
-        console.warn('Invalid encrypted format, returning original');
-        return text;
+        // Invalid format - throw to make failure explicit
+        throw new Error('Invalid encrypted format');
     }

If migration compatibility is required, consider a dedicated migration utility instead.

nextjs_space/app/api/tenant-admin/settings/route.ts (1)

8-14: Consider adding rate limiting to this settings endpoint.

Other admin endpoints in this PR use rate limiting, but this settings update endpoint does not. Settings changes are sensitive operations that could benefit from rate limiting to prevent abuse.

nextjs_space/lib/tenant-context.ts (1)

9-11: enterWith can leak context across unrelated async operations.

enterWith() modifies the current execution context permanently rather than scoping it to a callback. If called outside an existing async context (e.g., at the top level of a request handler before any async work begins), it can cause the tenant context to leak to subsequent unrelated requests sharing the same async resource.

Consider using runWithTenantContext consistently instead, or ensure setTenantContext is only called within an already-scoped async context established by run().

♻️ Alternative: Remove setTenantContext and use runWithTenantContext exclusively

If setTenantContext must remain for ergonomic reasons, document its constraints clearly:

+/**
+ * Sets the tenant context for the current async execution.
+ * WARNING: Must only be called within an existing AsyncLocalStorage context
+ * (e.g., inside a runWithTenantContext callback or Next.js request handler).
+ * Calling this at module scope or outside async context can leak state.
+ */
 export function setTenantContext(tenantId: string | null) {
   tenantContextStorage.enterWith({ tenantId });
 }
nextjs_space/lib/db.ts (1)

125-132: Setting tenantId on upsert's update path may not behave as expected.

Line 130 sets tenantId on the update payload, but if the tenant-scoped where clause (applied later) doesn't match an existing record (e.g., record exists but belongs to a different tenant), Prisma will execute the create path instead. This is likely the intended security behavior, but it could silently duplicate records across tenants if the unique constraint doesn't include tenantId.

Consider removing the tenantId assignment from the update path since the where clause already scopes to the correct tenant, and changing tenant ownership via update should probably be an explicit operation.

♻️ Suggested change
     if (params.action === 'upsert') {
       if (params.args?.create) {
         params.args.create.tenantId = params.args.create.tenantId ?? tenantId;
       }
-      if (params.args?.update) {
-        params.args.update.tenantId = params.args.update.tenantId ?? tenantId;
-      }
     }
nextjs_space/lib/tenant.ts (1)

160-163: Consider clearing context when subdomain tenant is not found.

If the subdomain path is taken but no tenant is found (line 159), the function falls through to line 170 returning null without clearing context. This is fine if no context was set earlier, but for defensive consistency, consider adding setTenantContext(null) before the final return.

♻️ Suggested improvement
   }
 
+  setTenantContext(null);
   return null;
 }
nextjs_space/app/api/shop/register/route.ts (1)

40-46: Fallback warning message may be misleading when platform credentials are empty.

When getTenantDrGreenConfig throws, the warning states "Using platform Dr. Green credentials fallback" but doesn't verify platform credentials actually exist. Consider clarifying the log message.

Optional clarity improvement
     if (tenant?.id) {
       try {
         config = await getTenantDrGreenConfig(tenant.id);
       } catch (error) {
-        console.warn('Using platform Dr. Green credentials fallback:', error);
+        console.warn('Tenant-specific Dr. Green config unavailable, attempting platform fallback:', error);
       }
     }
nextjs_space/app/tenant-admin/settings/settings-form.tsx (1)

162-173: Consider consistent pattern for Secret Key handling.

The drGreenSecretKey section uses tenant.drGreenSecretKey directly in the conditional (line 169, 172), while the API Key section uses a derived hasApiKey variable. For consistency and readability, consider adding a hasSecretKey variable:

 const hasApiKey = Boolean(tenant.drGreenApiKey);
+const hasSecretKey = Boolean(tenant.drGreenSecretKey);

Then use hasSecretKey in lines 169 and 172. This is a minor stylistic nit.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 643c77d and 9830613.

📒 Files selected for processing (18)
  • nextjs_space/app/api/shop/register/route.ts
  • nextjs_space/app/api/super-admin/tenants/bulk/route.ts
  • nextjs_space/app/api/super-admin/tenants/route.ts
  • nextjs_space/app/api/tenant-admin/analytics/route.ts
  • nextjs_space/app/api/tenant-admin/orders/bulk/route.ts
  • nextjs_space/app/api/tenant-admin/products/bulk/route.ts
  • nextjs_space/app/api/tenant-admin/products/reorder/route.ts
  • nextjs_space/app/api/tenant-admin/settings/route.ts
  • nextjs_space/app/tenant-admin/settings/page.tsx
  • nextjs_space/app/tenant-admin/settings/settings-form.tsx
  • nextjs_space/lib/db.ts
  • nextjs_space/lib/encryption.ts
  • nextjs_space/lib/rate-limit.ts
  • nextjs_space/lib/tenant-config.ts
  • nextjs_space/lib/tenant-context.ts
  • nextjs_space/lib/tenant.ts
  • nextjs_space/middleware.ts
  • nextjs_space/next.config.js
🧰 Additional context used
🧬 Code graph analysis (11)
nextjs_space/app/api/tenant-admin/products/bulk/route.ts (2)
nextjs_space/lib/rate-limit.ts (1)
  • checkRateLimit (50-95)
nextjs_space/lib/auth.ts (1)
  • session (56-63)
nextjs_space/app/api/tenant-admin/orders/bulk/route.ts (2)
nextjs_space/lib/rate-limit.ts (1)
  • checkRateLimit (50-95)
nextjs_space/lib/auth.ts (1)
  • session (56-63)
nextjs_space/app/api/super-admin/tenants/bulk/route.ts (2)
nextjs_space/lib/rate-limit.ts (1)
  • checkRateLimit (50-95)
nextjs_space/lib/auth.ts (1)
  • session (56-63)
nextjs_space/lib/db.ts (1)
nextjs_space/lib/tenant-context.ts (1)
  • getTenantContext (13-15)
nextjs_space/lib/tenant-config.ts (1)
nextjs_space/lib/encryption.ts (1)
  • decrypt (38-67)
nextjs_space/app/api/super-admin/tenants/route.ts (2)
nextjs_space/lib/rate-limit.ts (1)
  • checkRateLimit (50-95)
nextjs_space/lib/auth.ts (1)
  • session (56-63)
nextjs_space/lib/rate-limit.ts (1)
nextjs_space/middleware.ts (1)
  • config (4-16)
nextjs_space/app/api/tenant-admin/products/reorder/route.ts (2)
nextjs_space/lib/rate-limit.ts (1)
  • checkRateLimit (50-95)
nextjs_space/lib/auth.ts (1)
  • session (56-63)
nextjs_space/app/api/tenant-admin/analytics/route.ts (2)
nextjs_space/lib/rate-limit.ts (1)
  • checkRateLimit (50-95)
nextjs_space/lib/auth.ts (1)
  • session (56-63)
nextjs_space/lib/tenant.ts (2)
nextjs_space/lib/tenant-context.ts (1)
  • setTenantContext (9-11)
nextjs_space/lib/db.ts (1)
  • prisma (31-37)
nextjs_space/app/api/shop/register/route.ts (2)
nextjs_space/middleware.ts (1)
  • config (4-16)
nextjs_space/lib/tenant-config.ts (1)
  • getTenantDrGreenConfig (10-38)
🔇 Additional comments (25)
nextjs_space/app/api/tenant-admin/analytics/route.ts (1)

17-21: LGTM!

The rate-limiting check is correctly awaited, aligning with the new async Redis-backed implementation.

nextjs_space/next.config.js (1)

17-17: LGTM!

Environment-driven configuration for image optimization is a good practice. Note that the default behavior changes: without NEXT_IMAGE_UNOPTIMIZED=true, images will now be optimized (previously hardcoded to unoptimized).

nextjs_space/lib/encryption.ts (1)

5-5: LGTM - Removing public key fallback is the correct security posture.

Encryption keys should never be exposed via NEXT_PUBLIC_* environment variables.

nextjs_space/lib/tenant-config.ts (1)

27-36: LGTM!

The implementation correctly decrypts both API key and secret key, with proper validation before returning.

nextjs_space/app/api/tenant-admin/settings/route.ts (3)

82-89: LGTM!

API key encryption follows the same established pattern as the secret key encryption, maintaining consistency.


91-95: Good security practice masking sensitive fields in logs.

The masked logging correctly hides encrypted credential values.


103-117: Audit logging is well-structured.

The implementation correctly captures user context, tenant ID, and client info for the audit trail.

nextjs_space/lib/rate-limit.ts (2)

90-92: Fail-open behavior is intentional but worth documenting.

On Redis errors, requests are allowed through. This prioritizes availability over strict rate limiting. Ensure this trade-off is acceptable for your security requirements and consider adding metrics/alerting for Redis failures.


14-17: maxRetriesPerRequest: null does not cause infinite retries on failures—it affects pending command flushing during reconnection.

The technical premise needs correction. maxRetriesPerRequest: null does not retry indefinitely on command failures. Instead, it disables per-request flushing; reconnection behavior is governed by retryStrategy/reconnectOnError options (neither configured here).

The lazyConnect concern is partially valid: the first request may incur a connection delay (bounded by connectTimeout), but this is not due to infinite retries. Commands queue by default until the connection succeeds. The code already handles Redis errors gracefully with try-catch, allowing requests through if Redis is unavailable.

If first-request latency is a concern, consider explicitly setting connectTimeout or disabling lazyConnect, but the current configuration with graceful error handling is defensible for a rate limiter.

Likely an incorrect or invalid review comment.

nextjs_space/app/api/tenant-admin/products/bulk/route.ts (1)

30-34: LGTM!

The rate-limiting check is correctly awaited, consistent with the other API routes updated in this PR.

nextjs_space/app/api/tenant-admin/products/reorder/route.ts (1)

19-23: LGTM!

The rate limiting check is now correctly awaited to work with the async Redis-backed implementation. The pattern is consistent with other routes in the PR.

nextjs_space/app/api/tenant-admin/orders/bulk/route.ts (1)

32-36: LGTM!

The rate limiting check is correctly awaited, consistent with the async Redis-backed rate limiter migration across the codebase.

nextjs_space/lib/tenant-context.ts (1)

13-18: LGTM!

getTenantContext and runWithTenantContext are well-implemented. The nullish coalescing handles missing store gracefully, and run() properly scopes the context to the callback's async lifetime.

nextjs_space/lib/db.ts (2)

80-96: LGTM!

The applyTenantScope function correctly handles both strict tenant scoping and the allowNull case for shared resources (like email templates). Using AND with OR for null-access models is the right approach.


98-104: LGTM on the guard clause and compatibility check.

The '$use' in prisma check correctly handles the mock client case during builds. The early return for missing context or non-scoped models/actions is efficient.

nextjs_space/lib/tenant.ts (2)

131-145: LGTM on path-based tenant resolution.

The /store/{slug} pattern extraction and tenant lookup correctly implement path-based routing. Setting the tenant context on successful resolution ensures downstream code has access to the tenant ID.


20-23: LGTM on tenant context propagation in getCurrentTenant.

Good defensive pattern: setting context to null when no identifiers are present, setting to tenant?.id ?? null on resolution, and clearing on error. This ensures the context is always in a known state.

Also applies to: 52-58

nextjs_space/middleware.ts (2)

22-27: Good security hardening: stripping client-supplied tenant headers.

Removing incoming x-tenant-* headers prevents header spoofing attacks where a malicious client could inject arbitrary tenant context. The middleware now exclusively controls tenant header values based on trusted routing logic.


41-49: Consistent header forwarding pattern across all routes.

All code paths now correctly forward the sanitized requestHeaders via NextResponse.next({ request: { headers: requestHeaders } }), ensuring tenant context is propagated only when derived from trusted sources (path, subdomain, or custom domain).

Also applies to: 62-63, 75-76, 89-93

nextjs_space/app/api/super-admin/tenants/bulk/route.ts (1)

30-34: Correct async rate limit integration.

The await on checkRateLimit aligns with the Redis-backed implementation that returns a Promise. The existing success check and response handling remain valid.

nextjs_space/app/api/super-admin/tenants/route.ts (2)

24-28: GET handler correctly awaits async rate limit check.

Consistent with the Redis-backed checkRateLimit implementation.


124-128: POST handler correctly awaits async rate limit check.

Same pattern applied consistently across both handlers.

nextjs_space/app/tenant-admin/settings/page.tsx (1)

22-28: Good: Both API key and secret key are masked before client exposure.

Consistent masking pattern prevents credential leakage to the browser. The '********' placeholder correctly indicates the presence of a configured key without revealing its value.

nextjs_space/app/tenant-admin/settings/settings-form.tsx (2)

29-34: Security improvement: API key no longer pre-populated in form.

Initializing drGreenApiKey as an empty string rather than tenant.drGreenApiKey correctly prevents potentially masked or encrypted values from being sent back to the server on save. This aligns with the PR objective of avoiding exposure of credentials in the UI.


156-160: LGTM - Clear UX for masked credential handling.

The conditional placeholder and helper text accurately communicate to users whether an API key exists and that leaving the field empty preserves the existing key.

Comment thread nextjs_space/lib/db.ts
Comment thread nextjs_space/lib/rate-limit.ts
Comment thread nextjs_space/lib/tenant.ts
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