Skip to content

feat(accounts): multi-tenancy support (accounts module + dynamic active-account header)#178

Open
felixkob wants to merge 7 commits into
mainfrom
feat/accounts-multi-tenancy
Open

feat(accounts): multi-tenancy support (accounts module + dynamic active-account header)#178
felixkob wants to merge 7 commits into
mainfrom
feat/accounts-multi-tenancy

Conversation

@felixkob
Copy link
Copy Markdown
Collaborator

@felixkob felixkob commented Jun 3, 2026

Summary

Adds first-class multi-tenancy ("Accounts") support to the SDK, paired with the platform's per-app Accounts feature.

  • base44.accounts module — manage accounts from inside an app:
    • getActiveAccountId(), switchAccount(accountId, subPath?)
    • listMine(), create({ name, data? }), update(accountId, { name?, data? })
    • Members: listMembers, invite, acceptInvite, changeMemberRole, removeMember, transferOwnership
    • billing.listPlans(accountId), billing.startCheckout(accountId, { plan_id, success_url, cancel_url })
    • Maps to the backend /api/apps/{appId}/accounts/... runtime routes.
  • Dynamic X-Active-Account-Id header — set per request from the URL path (/<account_id>/...), the canonical account source. Overrides any header frozen at module load, so account-scoped reads/writes stay isolated to the current tenant even after a client-side (Link/useNavigate) account switch. No-op for single-tenant apps and in Node.
  • Wires accounts into the client and Base44Client type; re-exports the account types from the package root.

Why

Apps served under /<account_id>/... need every request to carry the active account and a typed surface for account/membership/billing operations. Previously the active-account header was computed once at client construction and went stale on client-side navigation.

Test plan

  • npx tsc --noEmit (clean)
  • npm run test:types (clean)
  • npx vitest run — full suite green (171 tests), incl. new tests/unit/accounts.test.ts
  • Browser smoke: confirm X-Active-Account-Id follows the URL after a client-side account switch (browser-only path; not unit-tested since the suite runs in Node and jsdom isn't a dep)

Notes

  • Follow-up in the platform monorepo: the generated base44Client.js templates can drop the now-redundant static X-Active-Account-Id header.
  • No version bump included — left to the maintainers' release flow.

Made with Cursor

…ive-account header

- Add `base44.accounts` module: getActiveAccountId, switchAccount, listMine,
  create, update, listMembers, invite, acceptInvite, changeMemberRole,
  removeMember, transferOwnership, and billing.{listPlans, startCheckout}
  mapping to the backend /api/apps/{appId}/accounts/... routes.
- Send X-Active-Account-Id per request, read from the URL path (the canonical
  account source) so account-scoped reads/writes stay isolated to the current
  tenant even after a client-side (Link/useNavigate) account switch. No-op for
  single-tenant apps / Node.
- Wire accounts into the client + Base44Client type; re-export account types.
- Add node-safe unit tests for the module's HTTP surface.

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link
Copy Markdown

claude Bot commented Jun 3, 2026

Claude finished @felixkob's task —— View job


Code Review for Multi-Tenancy Support PR

Review Checklist

  • Read and analyze all changed files
  • Review code quality and best practices
  • Check for potential bugs or edge cases
  • Assess performance implications
  • Evaluate security concerns
  • Review test coverage

Summary

This PR adds comprehensive multi-tenancy support to the SDK with a well-designed accounts module and dynamic header management. The implementation is clean, type-safe, and follows established patterns. However, there are some security considerations and edge cases that should be addressed.

🟢 Strengths

  1. Architecture and Design

    • Clean separation of concerns with dedicated accounts module
    • Type-safe implementation with comprehensive TypeScript types
    • Follows existing SDK patterns for module creation
    • Good JSDoc documentation with clear examples
  2. Dynamic Header Management

    • Clever solution using request interceptor to update X-Active-Account-Id based on URL path
    • Properly handles client-side navigation without module reload
    • Gracefully degrades for non-browser environments
  3. API Surface

    • Complete CRUD operations for accounts
    • Comprehensive membership management (invite, accept, role changes)
    • Billing integration support
    • Intuitive method naming

🔴 Security Concerns

  1. URL-based Account ID Validation (src/utils/common.ts:8-16)

    const ACCOUNT_ID_RE = /^[a-f0-9]{24}$/;

    The regex only validates format, not ownership. Consider:

    • What prevents a user from manually changing the URL to another account's ID?
    • Should the SDK verify account membership client-side?
    • Document that server-side validation is critical
  2. Account Switching (src/modules/accounts.ts:34-38)

    switchAccount(accountId: string, subPath = ""): void {
      if (typeof window === "undefined") return;
      const clean = subPath.replace(/^\/+/, "");
      window.location.assign(`/${accountId}${clean ? `/${clean}` : "/"}`);
    }
    • No validation that the user has access to the target account
    • subPath parameter could be exploited if not properly sanitized server-side
    • Consider validating against listMine() results before switching

🟡 Potential Bugs and Edge Cases

  1. Email Encoding (src/modules/accounts.ts:79,88)

    `${base}/${accountId}/members/${enc(email)}/role`

    Good use of encodeURIComponent, but consider edge cases:

    • What if email contains % already encoded?
    • Test with international email addresses
  2. Error Handling

    • No explicit error handling in the module
    • Relies on axios interceptor, but consider module-specific errors
    • What happens if switchAccount is called with invalid ID?
  3. Race Conditions

    • If user rapidly switches accounts, could the header update race with pending requests?
    • Consider request queuing or cancellation

⚡ Performance Considerations

  1. Path Parsing on Every Request

    const activeAccountId = getActiveAccountIdFromPath();
    if (activeAccountId) {
      config.headers.set("X-Active-Account-Id", activeAccountId);
    }
    • Parsing URL on every request is acceptable, but could be optimized with memoization
    • Consider caching with URL change listener
  2. No Pagination

    • listMembers() and listMine() don't support pagination
    • Could be problematic for organizations with many members/accounts

📋 Test Coverage

  1. Good Coverage

    • API endpoint integration tests
    • URL encoding edge cases
    • Proper mocking with nock
  2. Missing Tests

    • Browser-specific behavior (noted in comment at line 54)
    • Account switching functionality
    • Dynamic header injection
    • Error scenarios
    • Edge cases (invalid account IDs, network failures)

💡 Suggestions

  1. Add Account Validation Helper

    async canAccessAccount(accountId: string): Promise<boolean> {
      const { accounts } = await this.listMine();
      return accounts.some(acc => acc.id === accountId);
    }
  2. Add Pagination Support

    listMembers(accountId: string, options?: { limit?: number; offset?: number })
  3. Consider Account Context Hook (for React apps)

    // Future enhancement
    const { activeAccount, switchAccount } = useAccount();
  4. Add Rate Limiting Awareness

    • Document rate limits for account operations
    • Consider adding retry logic for 429 responses

📚 Documentation Recommendations

  1. Add a dedicated accounts/multi-tenancy guide covering:

    • Security model and server-side validation requirements
    • URL structure and routing implications
    • Best practices for account switching
    • Migration guide for single to multi-tenant
  2. Add more examples for common scenarios:

    • Checking if user can access an account before switching
    • Handling account switching errors
    • Building an account switcher UI

✅ Approval Recommendation

This is a well-implemented feature that follows SDK conventions and provides a clean API. The security concerns mentioned should be addressed primarily through documentation and server-side validation, as the SDK correctly treats the URL as the source of truth.

I recommend approval with the following action items:

  1. Document security considerations for implementers
  2. Add browser-based tests for the dynamic header behavior
  3. Consider adding pagination to list endpoints
  4. Add examples for secure account switching patterns

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 3, 2026

🚀 Package Preview Available!


Install this PR's preview build with npm:

npm i @base44-preview/sdk@0.8.31-pr.178.d362125

Prefer not to change any import paths? Install using npm alias so your code still imports @base44/sdk:

npm i "@base44/sdk@npm:@base44-preview/sdk@0.8.31-pr.178.d362125"

Or add it to your package.json dependencies:

{
  "dependencies": {
    "@base44/sdk": "npm:@base44-preview/sdk@0.8.31-pr.178.d362125"
  }
}

Preview published to npm registry — try new features instantly!

@github-actions github-actions Bot added the docs-draft PR has auto-drafted documentation suggestions label Jun 3, 2026
In the sandbox/preview the app is served under /<appId>/ (the dev-server base
path), so getActiveAccountIdFromPath returned the app id and the request
interceptor sent it as X-Active-Account-Id — the server then failed the
membership lookup and account-scoped reads got no active account (the app hung
on load). Pass the appId through createClient -> createAxiosClient and skip a
leading app-id segment, reading the account from the next segment instead.
Single-tenant and production multi-tenant (served under /<accountId>/) are
unaffected.

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link
Copy Markdown

claude Bot commented Jun 4, 2026

Claude finished @felixkob's task —— View job


Code Review

I've completed my review of this PR that adds multi-tenancy support to the SDK. This is a well-implemented feature that follows established patterns and provides a clean API for account management.

Review Checklist

  • Read and analyze all changed files
  • Review code quality and best practices
  • Check for potential bugs or edge cases
  • Assess performance implications
  • Evaluate security concerns
  • Review test coverage

Summary

Overall Assessment: This is a high-quality implementation that's ready for approval. The code is clean, type-safe, and follows SDK conventions. While there are some security considerations to document and minor improvements suggested, none are blocking issues.

🟢 Code Quality & Best Practices

Strengths:

  1. Excellent TypeScript usage - Comprehensive type definitions with clear JSDoc documentation
  2. Clean module architecture - Follows established patterns with createAccountsModule factory
  3. Consistent API design - Methods are intuitive and well-named
  4. Good separation of concerns - Account ID extraction logic properly isolated in utils
  5. Proper URL encoding - Uses encodeURIComponent for email parameters (src/modules/accounts.ts:79,88)

Suggestions:

  • Consider adding return type annotations to improve readability:
    getActiveAccountId(): string | undefined  // already has this ✓
    switchAccount(accountId: string, subPath = ""): void  // already has this ✓

🟡 Potential Bugs & Edge Cases

  1. Account switching race conditions (src/modules/accounts.ts:34-38)

    • If user rapidly switches accounts, pending requests might use outdated headers
    • Consider debouncing or request cancellation
  2. Missing error handling

    • No try-catch blocks or error boundaries in the module
    • Relies on axios interceptor, but module-specific errors might be helpful
  3. Email encoding edge cases (src/modules/accounts.ts:79,88)

    • Good use of encodeURIComponent, but test with international/special characters
    • Consider validation before encoding
  4. Browser-only functionality (src/modules/accounts.ts:35)

    • switchAccount silently returns in Node environments
    • Consider throwing or warning for better developer experience

⚡ Performance Considerations

  1. URL parsing on every request (src/utils/axios-client.ts:187)

    const activeAccountId = getActiveAccountIdFromPath(appId);
    • Acceptable overhead, but could cache with URL change listener
    • Current implementation is simple and correct
  2. No pagination support

    • listMembers() and listMine() could be problematic for large organizations
    • Consider adding optional pagination parameters
  3. Regex performance (src/utils/common.ts:8)

    const ACCOUNT_ID_RE = /^[a-f0-9]{24}$/;
    • Simple regex, minimal performance impact
    • Compiled once at module load

🔴 Security Concerns

  1. URL-based account ID trust (src/utils/common.ts:18-23)

    • The SDK trusts the URL path as the source of truth for account ID
    • Critical: Server MUST validate user has access to the account
    • Consider documenting this security model prominently
  2. No client-side validation (src/modules/accounts.ts:34-38)

    switchAccount(accountId: string, subPath = ""): void {
      if (typeof window === "undefined") return;
      const clean = subPath.replace(/^\/+/, "");
      window.location.assign(`/${accountId}${clean ? `/${clean}` : "/"}`);
    }
    • No check if user has access before switching
    • Could validate against listMine() results first
    • subPath parameter could contain malicious values if not sanitized server-side
  3. Service token exposure

    • Good separation between user and service tokens
    • Headers properly managed to prevent token leakage

📋 Test Coverage

Good coverage:

  • API endpoint integration tests ✓
  • URL encoding edge cases tested ✓
  • Proper mocking with nock ✓

Missing tests:

  1. Browser-specific behavior (noted in test file comment)
  2. Dynamic header injection
  3. Error scenarios
  4. Account switching functionality
  5. Race conditions
  6. Invalid account ID formats

Test improvements needed:

// Add tests for:
- getActiveAccountIdFromPath with various URL patterns
- Request interceptor header injection
- Error handling for failed account operations
- Browser vs Node environment differences

💡 Recommendations

  1. Add validation helper:

    async canAccessAccount(accountId: string): Promise<boolean> {
      const { accounts } = await this.listMine();
      return accounts.some(acc => acc.id === accountId);
    }
  2. Add pagination support:

    listMembers(accountId: string, options?: { limit?: number; offset?: number })
  3. Document security model - Add prominent documentation about:

    • Server-side validation requirements
    • URL as source of truth implications
    • Best practices for secure account switching
  4. Consider request queuing - To handle rapid account switches gracefully

📝 Minor Issues

  1. Import path consistency - All imports correctly use .js extensions ✓
  2. No unused imports
  3. Proper error class usage - Inherits from Base44Error ✓

✅ Approval

This is a well-implemented feature that provides essential multi-tenancy support. The code quality is high, and the architecture is sound. The security concerns I've raised are primarily about documentation and server-side validation, not fundamental flaws in the implementation.

I recommend approval with the understanding that:

  1. Server-side validation is critical and must be properly implemented
  2. The suggested improvements (pagination, validation helpers) can be added in follow-up PRs
  3. Additional browser-based tests should be added when feasible

Great work on this implementation! The dynamic header approach is elegant and the API design is clean and intuitive.

… URL path

Replaces getActiveAccountIdFromPath (which read the first URL segment and broke
in the sandbox where the app is served under a non-account base path) with
localStorage-backed state keyed per app (base44:active_account:<appId>). The
request interceptor forwards X-Active-Account-Id from storage (no header when
unset → server defaults to the user's sole account). accounts.switchAccount(id)
persists + reloads; adds clearActiveAccount(); drops the now-unused subPath.

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link
Copy Markdown

claude Bot commented Jun 4, 2026

Claude finished @felixkob's task —— View job


Code Review - Updated Implementation

I've reviewed the latest changes (commits fb1867d and f30dbed) that significantly refactor the multi-tenancy implementation. Here's my analysis:

Review Checklist

  • Read and analyze the new commits and changes
  • Review code quality and best practices
  • Check for potential bugs or issues
  • Assess performance implications
  • Evaluate security concerns
  • Review test coverage
  • Check if previous review concerns were addressed

🟢 Major Architectural Improvement

The implementation has been refactored from URL-based to localStorage-based account management:

Previous approach: Read account ID from URL path (/<account_id>/...)
New approach: Store account ID in localStorage (base44:active_account:<appId>)

This is a significant improvement that addresses several concerns from the previous review.

🟢 Improvements Made

  1. Better Security Model

    • Account ID no longer exposed in URL, reducing risk of accidental sharing
    • Account switching now requires explicit API call + page reload
    • Invalid account IDs are validated before storage (24-hex format check)
  2. Cleaner API Design

    • Removed confusing subPath parameter from switchAccount()
    • Added clearActiveAccount() method for explicit account removal
    • Account switching is now atomic: store + reload
  3. Better Error Handling

    • Try-catch blocks around localStorage operations
    • Graceful fallback when storage is unavailable
    • Validation of account ID format before storage
  4. App Isolation

    • Each app has its own localStorage key (base44:active_account:<appId>)
    • Prevents cross-app account contamination
    • Works correctly in sandbox/preview environments

🟡 Remaining Considerations

  1. Browser Storage Limitations

    • localStorage can be cleared by users/browsers
    • No fallback if storage is disabled (returns undefined)
    • Consider documenting this limitation
  2. Account Validation

    • Still no client-side validation that user has access to the account
    • switchAccount() will store any valid-format ID without checking membership
    • Consider adding a helper like:
    async canAccessAccount(accountId: string): Promise<boolean> {
      const { accounts } = await this.listMine();
      return accounts.some(acc => acc.id === accountId);
    }
  3. Race Conditions

    • Page reload after switchAccount() helps avoid race conditions
    • However, any pending requests will be cancelled
    • Consider documenting this behavior

⚡ Performance Analysis

  1. Minimal Overhead

    • localStorage read on each request is very fast
    • No regex parsing of URL paths anymore
    • Account ID validation happens only on write
  2. Page Reload Impact

    • switchAccount() now triggers full page reload
    • This ensures clean state but impacts UX
    • Trade-off is reasonable for security/correctness

📋 Test Coverage

  • Tests updated to reflect new implementation
  • Comment acknowledges browser-only testing limitation
  • Consider adding mock localStorage tests for edge cases

🔒 Security Improvements

  1. No URL Manipulation

    • Account ID no longer in URL prevents tampering via browser address bar
    • Reduces social engineering attack surface
  2. Explicit State Management

    • Clear separation between stored state and request headers
    • Backend still must validate access (as noted in comments)
  3. App-Scoped Storage

    • Each app's accounts are isolated via appId in storage key
    • Prevents cross-app account leakage

💡 Suggestions for Further Improvement

  1. Add Migration Guide

    • Document migration from URL-based to localStorage-based approach
    • Explain implications for existing implementations
  2. Consider Session Storage Option

    • For temporary account switches, sessionStorage might be preferred
    • Could be a configuration option
  3. Add Debug Logging

    • Log account switches and storage operations in development mode
    • Help developers troubleshoot issues

✅ Approval

This refactored implementation is a significant improvement over the initial URL-based approach. The localStorage-based solution:

  • Provides better security through URL opacity
  • Simplifies the API surface
  • Handles edge cases more gracefully
  • Maintains proper app isolation

The changes successfully address most of the concerns from my initial review. While some considerations remain (like client-side validation), these can be addressed in follow-up work.

I recommend approval of this PR. The implementation is solid, secure, and provides a clean API for multi-tenancy support.


…ount

Generated multi-tenant apps kept hitting two runtime errors (seen in the
builder preview's Issues panel):
- "base44.accounts.billing.getSubscription is not a function" — add it. It
  returns { account_id, plan_id, billing_status, plan } derived from listMine()
  + listPlans() (no new backend endpoint).
- "Account not found" 404 — billing/member reads were called with an account id
  derived from getActiveAccountId(), which is null for single-account users, so
  the path became /accounts/undefined/... Make accountId OPTIONAL on the
  read-style calls (listMembers, billing.listPlans/getSubscription/startCheckout)
  and resolve it via stored selection → server default (listMine().active_account_id),
  throwing a clear error only when truly none exists.

startCheckout now accepts either (params) or (accountId, params).

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link
Copy Markdown

claude Bot commented Jun 4, 2026

Claude finished @felixkob's task —— View job


Code Review - Multi-Tenancy Support PR

I've completed my review of this PR that adds multi-tenancy support to the SDK. This is a well-architected implementation that follows established patterns and provides a clean API for account management.

Review Checklist

  • Read and analyze all changed files
  • Review code quality and best practices
  • Check for potential bugs or edge cases
  • Assess performance implications
  • Evaluate security concerns
  • Review test coverage
  • Provide comprehensive feedback

Summary

Overall Assessment: This is a high-quality implementation that's ready for approval. The code is clean, type-safe, and follows SDK conventions. The evolution from URL-based to localStorage-based account management shows thoughtful design iteration.

🟢 Code Quality & Best Practices

Strengths:

  1. Excellent TypeScript usage - Comprehensive type definitions with clear JSDoc documentation (src/modules/accounts.types.ts)
  2. Clean module architecture - Follows established factory pattern with createAccountsModule (src/modules/accounts.ts:27-30)
  3. Consistent API design - Methods are intuitive, well-named, and properly documented
  4. Good separation of concerns - Account ID storage logic properly isolated in utils (src/utils/common.ts:21-53)
  5. Proper URL encoding - Uses encodeURIComponent for email parameters (src/modules/accounts.ts:105,114)
  6. App isolation - Each app has its own localStorage key preventing cross-app contamination

Code Organization:

  • Clean imports and proper use of .js extensions
  • No unused imports or variables
  • Proper error class inheritance from Base44Error

🟡 Potential Bugs & Edge Cases

  1. Browser Storage Limitations (src/utils/common.ts:23-29)

    • localStorage can be cleared by users/browsers
    • No fallback if storage is disabled (gracefully returns undefined)
    • Consider documenting this limitation in user-facing docs
  2. Account Validation (src/modules/accounts.ts:55-59)

    • switchAccount() accepts any valid-format ID without checking membership
    • Consider adding a helper method to validate access:
    async canAccessAccount(accountId: string): Promise<boolean> {
      const { accounts } = await this.listMine();
      return accounts.some(acc => acc.id === accountId);
    }
  3. Page Reload on Switch (src/modules/accounts.ts:58)

    • Full page reload ensures clean state but may cancel pending operations
    • This is a reasonable trade-off but should be documented
  4. Error Handling (src/utils/common.ts:26-28, 50-52)

    • Good use of try-catch around localStorage operations
    • Silent failures could make debugging difficult in some cases

⚡ Performance Considerations

  1. Minimal Overhead

    • localStorage read on each request is very fast (src/utils/axios-client.ts:185-191)
    • Account ID validation regex is simple and compiled once (src/utils/common.ts:9)
    • No performance concerns identified
  2. Smart Resolution Strategy (src/modules/accounts.ts:39-48)

    • resolveAccountId efficiently cascades: explicit → stored → server default
    • Minimizes API calls by checking local state first
  3. No Pagination Support

    • listMembers() and listMine() could be problematic for large organizations
    • Consider adding optional pagination parameters in a future enhancement

🔒 Security Analysis

  1. Improved Security Model

    • Account ID no longer exposed in URL, reducing accidental sharing risk
    • localStorage provides better isolation than URL-based approach
    • Proper validation of account ID format before storage (24-hex regex)
  2. App-Scoped Storage (src/utils/common.ts:11-12)

    • Each app's accounts isolated via base44:active_account:<appId> key
    • Prevents cross-app account leakage
    • Works correctly in sandbox/preview environments
  3. Server-Side Validation Required

    • The SDK correctly treats client state as a hint
    • Critical: Server MUST validate user has access to the account
    • Good comments documenting this requirement (src/modules/accounts.types.ts:7-8)

📋 Test Coverage

Good coverage:

  • API endpoint integration tests with proper mocking ✓
  • URL encoding edge cases tested ✓
  • Account resolution logic tested ✓
  • Error scenarios tested ✓

Missing tests:

  1. Browser-specific behavior (noted in test comment at line 113-116)

    • localStorage operations
    • switchAccount() page reload behavior
    • clearActiveAccount() functionality
  2. Consider adding tests for:

    • Invalid account ID formats
    • localStorage unavailable scenarios
    • Rapid account switches

💡 Suggestions for Enhancement

  1. Add Validation Helper

    async validateAccountAccess(accountId: string): Promise<boolean> {
      try {
        const { accounts } = await this.listMine();
        return accounts.some(acc => acc.id === accountId);
      } catch {
        return false;
      }
    }
  2. Add Debug Mode

    • Log account switches in development for easier debugging
    • Could help developers understand state changes
  3. Migration Guide

    • Document how to migrate from any URL-based implementations
    • Explain the benefits of the localStorage approach
  4. Future Enhancements

    • Pagination for list operations
    • Session storage option for temporary switches
    • Account switch event emitter for UI updates

🐛 Minor Issues

  1. Consistent Error Messages (src/modules/accounts.ts:45-47, 140-143)

    • Both error messages are clear and helpful
    • Good consistency in error handling
  2. Type Safety

    • All methods properly typed with TypeScript
    • Good use of optional parameters and overloads

✅ Approval

This is a well-implemented feature that provides essential multi-tenancy support. The evolution from URL-based to localStorage-based architecture shows careful consideration of real-world usage patterns and security implications.

I recommend approval of this PR.

Key strengths:

  • Clean, idiomatic TypeScript code
  • Thoughtful API design with good defaults
  • Proper security considerations
  • Excellent documentation
  • Follows SDK patterns consistently

The minor suggestions I've provided can be addressed in follow-up PRs and don't block the current implementation.

Great work on this feature! The localStorage-based approach is more secure and provides better isolation than URL-based solutions.

…point

Replace the listMine()-derived shim with a dedicated call to
GET /accounts/{id}/billing/subscription, returning richer detail:
billing_provider, current_period_end (renewal), cancel_at_period_end,
canceled_at, started_at — alongside plan_id/billing_status/plan. The id still
auto-resolves to the active account when omitted.

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link
Copy Markdown

claude Bot commented Jun 4, 2026

Claude finished @felixkob's task —— View job


Code Review - Multi-Tenancy Support

I've completed a thorough review of this PR that adds multi-tenancy support to the Base44 SDK. This is a well-implemented feature that provides comprehensive account management capabilities.

Review Checklist

  • Read and understand repo guidelines (CLAUDE.md)
  • Read and analyze all changed files
  • Review code quality and best practices
  • Check for potential bugs or edge cases
  • Assess performance implications
  • Evaluate security concerns
  • Review test coverage
  • Provide comprehensive feedback

Summary

Overall Assessment: This is a high-quality implementation that's ready for approval. The code is clean, well-documented, and follows SDK conventions. The localStorage-based approach for storing active account state is a solid architectural choice.

🟢 Code Quality & Best Practices

Strengths:

  1. Excellent TypeScript usage - Comprehensive type definitions with clear JSDoc documentation in src/modules/accounts.types.ts:1-168
  2. Clean module architecture - Follows established patterns with createAccountsModule factory pattern
  3. Consistent API design - Methods are intuitive and follow RESTful conventions
  4. Good error handling - Clear error messages when no active account is found (src/modules/accounts.ts:45-47)
  5. Proper URL encoding - Uses encodeURIComponent for email parameters (src/modules/accounts.ts:105,114)

🟡 Potential Bugs & Edge Cases

  1. Page reload on account switch (src/modules/accounts.ts:58)

    • Forces full page reload which cancels pending requests
    • Consider documenting this behavior clearly for developers
  2. Storage access failures (src/utils/common.ts:26-27,50-52)

    • Silently returns undefined or ignores errors
    • Good defensive programming, but consider logging in development mode
  3. No client-side validation (src/modules/accounts.ts:55-59)

    • switchAccount() accepts any string without checking if user has access
    • Consider adding optional validation against listMine() results
  4. Email encoding edge cases

    • Good use of encodeURIComponent, but test with international/special characters
    • Current tests cover basic cases like "a+x@b.com"

⚡ Performance Considerations

  1. LocalStorage reads on every request (src/utils/axios-client.ts:185-187)

    • Minimal performance impact - localStorage is synchronous and fast
    • Good trade-off for maintaining correct account context
  2. No pagination support

    • listMembers() and listMine() could be problematic for large organizations
    • Consider adding optional pagination parameters in future iteration
  3. Account resolution strategy (src/modules/accounts.ts:39-48)

    • Smart fallback chain: explicit ID → stored → server default
    • Only makes network request when necessary

🔒 Security Analysis

  1. Account isolation via localStorage (src/utils/common.ts:11-12)

    • Each app has isolated storage key: base44:active_account:<appId>
    • Prevents cross-app account leakage
    • Account ID not exposed in URL (better than initial implementation)
  2. Format validation (src/utils/common.ts:9)

    const ACCOUNT_ID_RE = /^[a-f0-9]{24}$/;
    • Validates 24-character hex format before storage
    • Invalid IDs are rejected, preventing injection attempts
  3. Server-side validation required

    • Client correctly trusts server for authorization
    • Headers are set per-request, allowing server to validate access
    • Good separation of concerns

📋 Test Coverage

Good coverage:

  • API endpoint integration tests ✓
  • URL encoding for emails tested ✓ (tests/unit/accounts.test.ts:36-41)
  • Account resolution logic tested ✓
  • Error cases covered ✓

Missing tests (noted in comments):

  • Browser-specific localStorage behavior (tests/unit/accounts.test.ts:124-126)
  • Dynamic header injection
  • Account switching with page reload
  • Storage access failures

💡 Recommendations

  1. Add validation helper for safer account switching:

    async canAccessAccount(accountId: string): Promise<boolean> {
      const { accounts } = await this.listMine();
      return accounts.some(acc => acc.id === accountId);
    }
  2. Consider adding pagination:

    listMembers(accountId?: string, options?: { 
      limit?: number; 
      offset?: number 
    }): Promise<AccountMembership[]>
  3. Document the page reload behavior in switchAccount() JSDoc

  4. Add development-mode logging for storage access failures

✅ Approval

This implementation successfully adds comprehensive multi-tenancy support to the SDK. The localStorage-based approach is secure, performant, and maintains proper account isolation. The API is clean and follows SDK conventions.

Minor improvements suggested above can be addressed in follow-up PRs. The core functionality is solid and ready for use.

Great work on this feature! The architecture is well-thought-out and the implementation is production-ready.

…iveAccount, joinAccount)

- getPublicAccount(slug): unauthenticated GET /accounts/public/by-slug/{slug}
  for the per-account landing page (returns id/name/slug + public fields).
- setActiveAccount(accountId): persist the active account WITHOUT reloading, so
  the landing can select the account before redirecting to login.
- joinAccount(slug): self-join an account from its landing page.
- PublicAccount type + unit tests.

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link
Copy Markdown

claude Bot commented Jun 4, 2026

Claude finished @felixkob's task —— View job


Code Review - Multi-Tenancy Support PR

I've completed my review of this PR that adds multi-tenancy support to the Base44 JavaScript SDK. This is a well-architected implementation that provides comprehensive account management capabilities.

Review Checklist

  • Read repository guidelines (CLAUDE.md)
  • Read and analyze all changed files
  • Review code quality and best practices
  • Check for potential bugs or edge cases
  • Assess performance implications
  • Evaluate security concerns
  • Review test coverage
  • Provide comprehensive feedback

Summary

Overall Assessment: This is a high-quality implementation that's ready for approval. The code is clean, well-documented, and follows SDK conventions. The localStorage-based approach for storing active account state is a solid architectural choice.

🟢 Code Quality & Best Practices

Strengths:

  1. Excellent TypeScript usage - Comprehensive type definitions with clear JSDoc documentation (src/modules/accounts.types.ts)
  2. Clean module architecture - Follows established factory pattern with createAccountsModule
  3. Consistent API design - Methods are intuitive and follow RESTful conventions
  4. Good error handling - Clear error messages when no active account is found (src/modules/accounts.ts:45-47)
  5. Proper URL encoding - Uses encodeURIComponent for email parameters (src/modules/accounts.ts:118,127)
  6. App isolation - Each app has isolated storage key preventing cross-app contamination

🟡 Potential Bugs & Edge Cases

  1. Page reload on account switch (src/modules/accounts.ts:59)

    • Forces full page reload which cancels pending requests
    • Consider documenting this behavior clearly for developers
  2. Storage access failures (src/utils/common.ts:26-27,50-52)

    • Silently returns undefined or ignores errors
    • Good defensive programming, but consider logging in development mode
  3. No client-side validation (src/modules/accounts.ts:56-59)

    switchAccount(accountId: string): void {
      setStoredActiveAccountId(appId, accountId);
      if (typeof window === "undefined") return;
      window.location.reload();
    }
    • Accepts any string without checking if user has access
    • Consider adding optional validation against listMine() results
  4. Email encoding edge cases

    • Good use of encodeURIComponent, but test with international/special characters
    • Current tests cover basic cases like "a+x@b.com"

⚡ Performance Considerations

  1. LocalStorage reads on every request (src/utils/axios-client.ts:185-187)

    • Minimal performance impact - localStorage is synchronous and fast
    • Good trade-off for maintaining correct account context
  2. No pagination support

    • listMembers() and listMine() could be problematic for large organizations
    • Consider adding optional pagination parameters in future iteration
  3. Account resolution strategy (src/modules/accounts.ts:40-49)

    • Smart fallback chain: explicit ID → stored → server default
    • Only makes network request when necessary

🔒 Security Analysis

  1. Account isolation via localStorage (src/utils/common.ts:11-12)

    const activeAccountStorageKey = (appId: string): string =>
      `base44:active_account:${appId}`;
    • Each app has isolated storage key
    • Prevents cross-app account leakage
    • Account ID not exposed in URL (better than initial implementation)
  2. Format validation (src/utils/common.ts:9)

    const ACCOUNT_ID_RE = /^[a-f0-9]{24}$/;
    • Validates 24-character hex format before storage
    • Invalid IDs are rejected, preventing injection attempts
  3. Server-side validation required

    • Client correctly trusts server for authorization
    • Headers are set per-request, allowing server to validate access
    • Good separation of concerns

📋 Test Coverage

Good coverage:

  • API endpoint integration tests ✓
  • URL encoding for emails tested ✓ (tests/unit/accounts.test.ts:36-41)
  • Account resolution logic tested ✓
  • Error cases covered ✓

Missing tests (noted in comments):

  • Browser-specific localStorage behavior (tests/unit/accounts.test.ts:141-144)
  • Dynamic header injection
  • Account switching with page reload
  • Storage access failures

💡 Recommendations

  1. Add validation helper for safer account switching:

    async canAccessAccount(accountId: string): Promise<boolean> {
      const { accounts } = await this.listMine();
      return accounts.some(acc => acc.id === accountId);
    }
  2. Consider adding pagination:

    listMembers(accountId?: string, options?: { 
      limit?: number; 
      offset?: number 
    }): Promise<AccountMembership[]>
  3. Document the page reload behavior in switchAccount() JSDoc

  4. Add development-mode logging for storage access failures

Minor Observations

  • Good use of setActiveAccount() vs switchAccount() - clear distinction between setting state and switching with reload
  • Smart decision to use localStorage over sessionStorage for persistence across tabs
  • The clearActiveAccount() method provides a clean way to reset state
  • Billing module integration is well-structured with proper method overloading

✅ Approval

This implementation successfully adds comprehensive multi-tenancy support to the SDK. The localStorage-based approach is secure, performant, and maintains proper account isolation. The API is clean and follows SDK conventions.

Minor improvements suggested above can be addressed in follow-up PRs. The core functionality is solid and ready for use.

Great work on this feature! The architecture is well-thought-out and the implementation is production-ready.

Forward an optional landing-page `slug` on accounts.create and accounts.update
so apps can set/change the public landing-page URL (e.g. from a registration
form). Backend validates + enforces per-app uniqueness.

Co-authored-by: Cursor <cursoragent@cursor.com>
@claude
Copy link
Copy Markdown

claude Bot commented Jun 4, 2026

Claude finished @felixkob's task —— View job


Code Review - Multi-Tenancy Support PR

I've completed my review of this PR that adds multi-tenancy support to the Base44 JavaScript SDK. This is a well-architected implementation that provides comprehensive account management capabilities.

Review Checklist

  • Read repository guidelines (CLAUDE.md)
  • Read and analyze all changed files
  • Review code quality and best practices
  • Check for potential bugs or edge cases
  • Assess performance implications
  • Evaluate security concerns
  • Review test coverage
  • Provide comprehensive feedback

Summary

Overall Assessment: This is a high-quality implementation that's ready for approval. The code is clean, well-documented, and follows SDK conventions. The localStorage-based approach for storing active account state is a solid architectural choice.

🟢 Code Quality & Best Practices

Strengths:

  1. Excellent TypeScript usage - Comprehensive type definitions with clear JSDoc documentation (src/modules/accounts.types.ts:1-204)
  2. Clean module architecture - Follows established factory pattern with createAccountsModule (src/modules/accounts.ts:28-30)
  3. Consistent API design - Methods are intuitive and follow RESTful conventions
  4. Good error handling - Clear error messages when no active account is found (src/modules/accounts.ts:46-48)
  5. Proper URL encoding - Uses encodeURIComponent for email parameters (src/modules/accounts.ts:119,128)

🟡 Potential Bugs & Edge Cases

  1. Page reload on account switch (src/modules/accounts.ts:59)

    • Forces full page reload which cancels pending requests
    • Consider documenting this behavior clearly for developers
  2. Storage access failures (src/utils/common.ts:26-27,50-52)

    • Silently returns undefined or ignores errors
    • Good defensive programming, but consider logging in development mode
  3. No client-side validation (src/modules/accounts.ts:56-59)

    switchAccount(accountId: string): void {
      setStoredActiveAccountId(appId, accountId);
      if (typeof window === "undefined") return;
      window.location.reload();
    }
    • Accepts any string without checking if user has access
    • Consider adding optional validation against listMine() results
  4. Email encoding edge cases

    • Good use of encodeURIComponent, but test with international/special characters
    • Current tests cover basic cases like "a+x@b.com"

⚡ Performance Considerations

  1. LocalStorage reads on every request (src/utils/axios-client.ts:185-187)

    • Minimal performance impact - localStorage is synchronous and fast
    • Good trade-off for maintaining correct account context
  2. No pagination support

    • listMembers() and listMine() could be problematic for large organizations
    • Consider adding optional pagination parameters in future iteration
  3. Account resolution strategy (src/modules/accounts.ts:40-49)

    • Smart fallback chain: explicit ID → stored → server default
    • Only makes network request when necessary

🔒 Security Analysis

  1. Account isolation via localStorage (src/utils/common.ts:11-12)

    const activeAccountStorageKey = (appId: string): string =>
      `base44:active_account:${appId}`;
    • Each app has isolated storage key
    • Prevents cross-app account leakage
    • Account ID not exposed in URL (better than initial implementation)
  2. Format validation (src/utils/common.ts:9)

    const ACCOUNT_ID_RE = /^[a-f0-9]{24}$/;
    • Validates 24-character hex format before storage
    • Invalid IDs are rejected, preventing injection attempts
  3. Server-side validation required

    • Client correctly trusts server for authorization
    • Headers are set per-request, allowing server to validate access
    • Good separation of concerns

📋 Test Coverage

Good coverage:

  • API endpoint integration tests ✓
  • URL encoding for emails tested ✓ (tests/unit/accounts.test.ts:36-41)
  • Account resolution logic tested ✓
  • Error cases covered ✓

Missing tests (noted in comments):

  • Browser-specific localStorage behavior (tests/unit/accounts.test.ts:141-144)
  • Dynamic header injection
  • Account switching with page reload
  • Storage access failures

💡 Recommendations

  1. Add validation helper for safer account switching:

    async canAccessAccount(accountId: string): Promise<boolean> {
      const { accounts } = await this.listMine();
      return accounts.some(acc => acc.id === accountId);
    }
  2. Consider adding pagination:

    listMembers(accountId?: string, options?: { 
      limit?: number; 
      offset?: number 
    }): Promise<AccountMembership[]>
  3. Document the page reload behavior in switchAccount() JSDoc

  4. Add development-mode logging for storage access failures

Minor Observations

  • Good use of setActiveAccount() vs switchAccount() - clear distinction between setting state and switching with reload
  • Smart decision to use localStorage over sessionStorage for persistence across tabs
  • The clearActiveAccount() method provides a clean way to reset state
  • Billing module integration is well-structured with proper method overloading

✅ Approval

This implementation successfully adds comprehensive multi-tenancy support to the SDK. The localStorage-based approach is secure, performant, and maintains proper account isolation. The API is clean and follows SDK conventions.

Minor improvements suggested above can be addressed in follow-up PRs. The core functionality is solid and ready for use.

Great work on this feature! The architecture is well-thought-out and the implementation is production-ready.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs-draft PR has auto-drafted documentation suggestions

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant