Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

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

Open
felixkob wants to merge 7 commits into
mainbase44/javascript-sdk:mainfrom
feat/accounts-multi-tenancybase44/javascript-sdk:feat/accounts-multi-tenancyCopy head branch name to clipboard
Open

feat(accounts): multi-tenancy support (accounts module + dynamic active-account header)#178
felixkob wants to merge 7 commits into
mainbase44/javascript-sdk:mainfrom
feat/accounts-multi-tenancybase44/javascript-sdk:feat/accounts-multi-tenancyCopy head branch name to clipboard

Conversation

@felixkob

@felixkob felixkob commented Jun 3, 2026

Copy link
Copy Markdown
Collaborator

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

claude Bot commented Jun 3, 2026

Copy link
Copy Markdown

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

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

🚀 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

claude Bot commented Jun 4, 2026

Copy link
Copy Markdown

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

claude Bot commented Jun 4, 2026

Copy link
Copy Markdown

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

claude Bot commented Jun 4, 2026

Copy link
Copy Markdown

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

claude Bot commented Jun 4, 2026

Copy link
Copy Markdown

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

claude Bot commented Jun 4, 2026

Copy link
Copy Markdown

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

claude Bot commented Jun 4, 2026

Copy link
Copy Markdown

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

Morty Proxy This is a proxified and sanitized view of the page, visit original site.