feat(accounts): multi-tenancy support (accounts module + dynamic active-account header)#178
feat(accounts): multi-tenancy support (accounts module + dynamic active-account header)#178felixkob wants to merge 7 commits into
Conversation
…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 finished @felixkob's task —— View job Code Review for Multi-Tenancy Support PRReview Checklist
SummaryThis 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
🔴 Security Concerns
🟡 Potential Bugs and Edge Cases
⚡ Performance Considerations
📋 Test Coverage
💡 Suggestions
📚 Documentation Recommendations
✅ Approval RecommendationThis 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:
|
🚀 Package Preview Available!Install this PR's preview build with npm: npm i @base44-preview/sdk@0.8.31-pr.178.d362125Prefer not to change any import paths? Install using npm alias so your code still imports npm i "@base44/sdk@npm:@base44-preview/sdk@0.8.31-pr.178.d362125"Or add it to your {
"dependencies": {
"@base44/sdk": "npm:@base44-preview/sdk@0.8.31-pr.178.d362125"
}
}
Preview published to npm registry — try new features instantly! |
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 finished @felixkob's task —— View job Code ReviewI'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
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 PracticesStrengths:
Suggestions:
🟡 Potential Bugs & Edge Cases
⚡ Performance Considerations
🔴 Security Concerns
📋 Test CoverageGood coverage:
Missing tests:
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
📝 Minor Issues
✅ ApprovalThis 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:
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 finished @felixkob's task —— View job Code Review - Updated ImplementationI've reviewed the latest changes (commits Review Checklist
🟢 Major Architectural ImprovementThe implementation has been refactored from URL-based to localStorage-based account management: Previous approach: Read account ID from URL path ( This is a significant improvement that addresses several concerns from the previous review. 🟢 Improvements Made
🟡 Remaining Considerations
⚡ Performance Analysis
📋 Test Coverage
🔒 Security Improvements
💡 Suggestions for Further Improvement
✅ ApprovalThis refactored implementation is a significant improvement over the initial URL-based approach. The localStorage-based solution:
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 finished @felixkob's task —— View job Code Review - Multi-Tenancy Support PRI'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
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 PracticesStrengths:
Code Organization:
🟡 Potential Bugs & Edge Cases
⚡ Performance Considerations
🔒 Security Analysis
📋 Test CoverageGood coverage:
Missing tests:
💡 Suggestions for Enhancement
🐛 Minor Issues
✅ ApprovalThis 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:
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 finished @felixkob's task —— View job Code Review - Multi-Tenancy SupportI'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
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 PracticesStrengths:
🟡 Potential Bugs & Edge Cases
⚡ Performance Considerations
🔒 Security Analysis
📋 Test CoverageGood coverage:
Missing tests (noted in comments):
💡 Recommendations
✅ ApprovalThis 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 finished @felixkob's task —— View job Code Review - Multi-Tenancy Support PRI'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
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 PracticesStrengths:
🟡 Potential Bugs & Edge Cases
⚡ Performance Considerations
🔒 Security Analysis
📋 Test CoverageGood coverage:
Missing tests (noted in comments):
💡 Recommendations
Minor Observations
✅ ApprovalThis 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 finished @felixkob's task —— View job Code Review - Multi-Tenancy Support PRI'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
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 PracticesStrengths:
🟡 Potential Bugs & Edge Cases
⚡ Performance Considerations
🔒 Security Analysis
📋 Test CoverageGood coverage:
Missing tests (noted in comments):
💡 Recommendations
Minor Observations
✅ ApprovalThis 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. |
Summary
Adds first-class multi-tenancy ("Accounts") support to the SDK, paired with the platform's per-app Accounts feature.
base44.accountsmodule — manage accounts from inside an app:getActiveAccountId(),switchAccount(accountId, subPath?)listMine(),create({ name, data? }),update(accountId, { name?, data? })listMembers,invite,acceptInvite,changeMemberRole,removeMember,transferOwnershipbilling.listPlans(accountId),billing.startCheckout(accountId, { plan_id, success_url, cancel_url })/api/apps/{appId}/accounts/...runtime routes.X-Active-Account-Idheader — 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.accountsinto the client andBase44Clienttype; 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. newtests/unit/accounts.test.tsX-Active-Account-Idfollows 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
base44Client.jstemplates can drop the now-redundant staticX-Active-Account-Idheader.Made with Cursor