@tenantscale/sdk 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- export { type Tenant, type TenantUser, type TenantScaleOptions, type TenantResolver, type AuditEvent, type FeatureCheck, type ImpersonationSession, type PlanTier, type TenantSettings, TenantScaleError, PlanLimitError, UnauthorizedError, } from './types.js';
1
+ export { type Tenant, type TenantUser, type TenantScaleOptions, type TenantScaleContext, type TenantResolver, type AuditEvent, type FeatureCheck, type ImpersonationSession, type PlanTier, type TenantSettings, type UserTenantContext, TenantScaleError, PlanLimitError, UnauthorizedError, } from './types.js';
2
+ export { TenantScale } from './tenant-scale.js';
2
3
  export { TenantClient } from './tenant.js';
3
4
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACL,KAAK,MAAM,EACX,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,KAAK,oBAAoB,EACzB,KAAK,QAAQ,EACb,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,gBAAgB,EAChB,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
package/dist/index.js CHANGED
@@ -2,5 +2,6 @@
2
2
  // @tenantscale/sdk — Barrel exports
3
3
  // ──────────────────────────────────────────────────────
4
4
  export { TenantScaleError, PlanLimitError, UnauthorizedError, } from './types.js';
5
+ export { TenantScale } from './tenant-scale.js';
5
6
  export { TenantClient } from './tenant.js';
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,oCAAoC;AACpC,yDAAyD;AAEzD,OAAO,EAUL,gBAAgB,EAChB,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,oCAAoC;AACpC,yDAAyD;AAEzD,OAAO,EAYL,gBAAgB,EAChB,cAAc,EACd,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA"}
@@ -0,0 +1,6 @@
1
+ export { tenantMiddleware, TenantClient } from './hono.js';
2
+ export { expressMiddleware } from './express.js';
3
+ export { createTenantSafeClient } from './query-guard.js';
4
+ export { createUserTenantMiddleware, requireUserRole } from './user-auth.js';
5
+ export type { UserTenantMiddlewareOptions } from './user-auth.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAC5E,YAAY,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,8 @@
1
+ // ──────────────────────────────────────────────────────
2
+ // Middleware barrel — import via '@tenantscale/sdk/middleware'
3
+ // ──────────────────────────────────────────────────────
4
+ export { tenantMiddleware, TenantClient } from './hono.js';
5
+ export { expressMiddleware } from './express.js';
6
+ export { createTenantSafeClient } from './query-guard.js';
7
+ export { createUserTenantMiddleware, requireUserRole } from './user-auth.js';
8
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,+DAA+D;AAC/D,yDAAyD;AAEzD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAC1D,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAA;AACzD,OAAO,EAAE,0BAA0B,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,31 @@
1
+ export type GuardMode = 'strict' | 'warn';
2
+ export interface GuardOptions {
3
+ /** The tenant ID to enforce on all queries */
4
+ tenantId: string;
5
+ /**
6
+ * strict — throw an error (default, prevents data leaks)
7
+ * warn — log a warning and proceed (use during migration)
8
+ */
9
+ mode?: GuardMode;
10
+ }
11
+ /**
12
+ * Wrap a Supabase client so every query on every table must include
13
+ * `.eq('tenant_id', <tenantId>)` or explicitly call `.bypassIsolation()`.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const supabase = createClient(url, key)
18
+ * const db = createTenantSafeClient(supabase, { tenantId: 'tenant-123' })
19
+ *
20
+ * // ✅ Works:
21
+ * await db.from('projects').select('*').eq('tenant_id', 'tenant-123')
22
+ *
23
+ * // ❌ Throws:
24
+ * await db.from('projects').select('*')
25
+ *
26
+ * // ✅ Admin bypass:
27
+ * await db.from('plans').select('*').bypassIsolation()
28
+ * ```
29
+ */
30
+ export declare function createTenantSafeClient(supabase: any, options: GuardOptions): any;
31
+ //# sourceMappingURL=query-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-guard.d.ts","sourceRoot":"","sources":["../../src/middleware/query-guard.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,MAAM,CAAA;AAEzC,MAAM,WAAW,YAAY;IAC3B,8CAA8C;IAC9C,QAAQ,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,IAAI,CAAC,EAAE,SAAS,CAAA;CACjB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,GAAG,EACb,OAAO,EAAE,YAAY,GACpB,GAAG,CAiBL"}
@@ -0,0 +1,137 @@
1
+ // ──────────────────────────────────────────────────────
2
+ // Query Guard — runtime tenant isolation enforcement
3
+ // Wraps a Supabase client and rejects queries that don't
4
+ // filter by tenant_id. Catches the #1 multi-tenant bug
5
+ // at dev time instead of during a security audit.
6
+ // ──────────────────────────────────────────────────────
7
+ /**
8
+ * Wrap a Supabase client so every query on every table must include
9
+ * `.eq('tenant_id', <tenantId>)` or explicitly call `.bypassIsolation()`.
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const supabase = createClient(url, key)
14
+ * const db = createTenantSafeClient(supabase, { tenantId: 'tenant-123' })
15
+ *
16
+ * // ✅ Works:
17
+ * await db.from('projects').select('*').eq('tenant_id', 'tenant-123')
18
+ *
19
+ * // ❌ Throws:
20
+ * await db.from('projects').select('*')
21
+ *
22
+ * // ✅ Admin bypass:
23
+ * await db.from('plans').select('*').bypassIsolation()
24
+ * ```
25
+ */
26
+ export function createTenantSafeClient(supabase, options) {
27
+ const { tenantId, mode = 'strict' } = options;
28
+ const strict = mode === 'strict';
29
+ return new Proxy(supabase, {
30
+ get(target, prop, receiver) {
31
+ if (prop === 'from') {
32
+ return (table) => {
33
+ const rawQb = target.from(table);
34
+ return wrapBuilder(rawQb, tenantId, strict, table);
35
+ };
36
+ }
37
+ const value = Reflect.get(target, prop, receiver);
38
+ if (typeof value === 'function')
39
+ return value.bind(target);
40
+ return value;
41
+ },
42
+ });
43
+ }
44
+ // ── Methods that return a new query builder (must wrap results) ──
45
+ const CHAIN_METHODS = new Set([
46
+ 'select', 'insert', 'update', 'delete', 'upsert',
47
+ 'eq', 'neq', 'gt', 'gte', 'lt', 'lte',
48
+ 'like', 'ilike', 'is', 'isNot', 'in', 'not',
49
+ 'or', 'and', 'filter', 'match',
50
+ 'contains', 'containedBy', 'overlaps',
51
+ 'textSearch', 'csv',
52
+ 'order', 'limit', 'range',
53
+ ]);
54
+ function wrapBuilder(rootQb, tenantId, strict, table) {
55
+ let hasTenantFilter = false;
56
+ let bypassed = false;
57
+ function guardCheck() {
58
+ if (bypassed)
59
+ return;
60
+ if (hasTenantFilter)
61
+ return;
62
+ const msg = `[TenantScale] Query on "${table}" is missing a tenant_id filter. ` +
63
+ `Add .eq('tenant_id', '${tenantId}') to scope this query to the current tenant, ` +
64
+ `or use .bypassIsolation() for admin-only cross-tenant queries.`;
65
+ if (strict)
66
+ throw new TypeError(msg);
67
+ console.warn(msg);
68
+ }
69
+ // Build the proxy once, then reuse it for wrapped results.
70
+ // Chain methods return a new proxy on the same state.
71
+ function makeProxy(target) {
72
+ return new Proxy(target, proxyHandler);
73
+ }
74
+ // `.single()` and `.maybeSingle()` modify internal state and return `this`.
75
+ // We intercept them to return our proxy-wrapped `this`.
76
+ const TERMINAL_THIS = new Set(['single', 'maybeSingle']);
77
+ const proxyHandler = {
78
+ get(target, prop) {
79
+ // ── Track tenant_id filter ──
80
+ if (prop === 'eq') {
81
+ return (column, value) => {
82
+ if (column === 'tenant_id')
83
+ hasTenantFilter = true;
84
+ const nextQb = target.eq(column, value);
85
+ return makeProxy(nextQb);
86
+ };
87
+ }
88
+ // ── Bypass for admin routes ──
89
+ if (prop === 'bypassIsolation') {
90
+ return () => {
91
+ bypassed = true;
92
+ // Return a transparent pass-through proxy
93
+ return new Proxy(target, {
94
+ get(t, p) {
95
+ if (p === 'bypassIsolation')
96
+ return () => t;
97
+ const v = Reflect.get(t, p);
98
+ if (typeof v === 'function')
99
+ return v.bind(t);
100
+ return v;
101
+ },
102
+ });
103
+ };
104
+ }
105
+ // ── Intercept .then() — the HTTP request ──
106
+ if (prop === 'then') {
107
+ return (resolve, reject) => {
108
+ guardCheck();
109
+ return target.then(resolve, reject);
110
+ };
111
+ }
112
+ // ── Chain methods — wrap their return values ──
113
+ if (typeof prop === 'string' && CHAIN_METHODS.has(prop)) {
114
+ return (...args) => {
115
+ const nextQb = target[prop](...args);
116
+ return makeProxy(nextQb);
117
+ };
118
+ }
119
+ // ── Terminal-this methods (.single(), .maybeSingle()) ──
120
+ if (typeof prop === 'string' && TERMINAL_THIS.has(prop)) {
121
+ return (...args) => {
122
+ const result = target[prop](...args);
123
+ // single() returns `this` (the PostgrestFilterBuilder)
124
+ // Wrap it so further chaining still works
125
+ return makeProxy(result);
126
+ };
127
+ }
128
+ // ── Everything else passes through ──
129
+ const value = Reflect.get(target, prop, target);
130
+ if (typeof value === 'function')
131
+ return value.bind(target);
132
+ return value;
133
+ },
134
+ };
135
+ return makeProxy(rootQb);
136
+ }
137
+ //# sourceMappingURL=query-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query-guard.js","sourceRoot":"","sources":["../../src/middleware/query-guard.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,qDAAqD;AACrD,yDAAyD;AACzD,uDAAuD;AACvD,kDAAkD;AAClD,yDAAyD;AAczD;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAAa,EACb,OAAqB;IAErB,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,QAAQ,EAAE,GAAG,OAAO,CAAA;IAC7C,MAAM,MAAM,GAAG,IAAI,KAAK,QAAQ,CAAA;IAEhC,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE;QACzB,GAAG,CAAC,MAAM,EAAE,IAAqB,EAAE,QAAQ;YACzC,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;gBACpB,OAAO,CAAC,KAAa,EAAE,EAAE;oBACvB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;oBAChC,OAAO,WAAW,CAAC,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,CAAC,CAAA;gBACpD,CAAC,CAAA;YACH,CAAC;YACD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAA;YACjD,IAAI,OAAO,KAAK,KAAK,UAAU;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YAC1D,OAAO,KAAK,CAAA;QACd,CAAC;KACF,CAAC,CAAA;AACJ,CAAC;AAED,oEAAoE;AAEpE,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC;IAC5B,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ;IAChD,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK;IACrC,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK;IAC3C,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO;IAC9B,UAAU,EAAE,aAAa,EAAE,UAAU;IACrC,YAAY,EAAE,KAAK;IACnB,OAAO,EAAE,OAAO,EAAE,OAAO;CAC1B,CAAC,CAAA;AAEF,SAAS,WAAW,CAClB,MAAW,EACX,QAAgB,EAChB,MAAe,EACf,KAAa;IAEb,IAAI,eAAe,GAAG,KAAK,CAAA;IAC3B,IAAI,QAAQ,GAAG,KAAK,CAAA;IAEpB,SAAS,UAAU;QACjB,IAAI,QAAQ;YAAE,OAAM;QACpB,IAAI,eAAe;YAAE,OAAM;QAE3B,MAAM,GAAG,GACP,2BAA2B,KAAK,mCAAmC;YACnE,yBAAyB,QAAQ,gDAAgD;YACjF,gEAAgE,CAAA;QAElE,IAAI,MAAM;YAAE,MAAM,IAAI,SAAS,CAAC,GAAG,CAAC,CAAA;QACpC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;IACnB,CAAC;IAED,2DAA2D;IAC3D,sDAAsD;IACtD,SAAS,SAAS,CAAC,MAAW;QAC5B,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE,YAAY,CAAC,CAAA;IACxC,CAAC;IAED,4EAA4E;IAC5E,wDAAwD;IACxD,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAA;IAExD,MAAM,YAAY,GAAG;QACnB,GAAG,CAAC,MAAW,EAAE,IAAqB;YACpC,+BAA+B;YAC/B,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;gBAClB,OAAO,CAAC,MAAc,EAAE,KAAc,EAAE,EAAE;oBACxC,IAAI,MAAM,KAAK,WAAW;wBAAE,eAAe,GAAG,IAAI,CAAA;oBAClD,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,CAAA;oBACvC,OAAO,SAAS,CAAC,MAAM,CAAC,CAAA;gBAC1B,CAAC,CAAA;YACH,CAAC;YAED,gCAAgC;YAChC,IAAI,IAAI,KAAK,iBAAiB,EAAE,CAAC;gBAC/B,OAAO,GAAG,EAAE;oBACV,QAAQ,GAAG,IAAI,CAAA;oBACf,0CAA0C;oBAC1C,OAAO,IAAI,KAAK,CAAC,MAAM,EAAE;wBACvB,GAAG,CAAC,CAAC,EAAE,CAAkB;4BACvB,IAAI,CAAC,KAAK,iBAAiB;gCAAE,OAAO,GAAG,EAAE,CAAC,CAAC,CAAA;4BAC3C,MAAM,CAAC,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;4BAC3B,IAAI,OAAO,CAAC,KAAK,UAAU;gCAAE,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;4BAC7C,OAAO,CAAC,CAAA;wBACV,CAAC;qBACF,CAAC,CAAA;gBACJ,CAAC,CAAA;YACH,CAAC;YAED,6CAA6C;YAC7C,IAAI,IAAI,KAAK,MAAM,EAAE,CAAC;gBACpB,OAAO,CACL,OAA6B,EAC7B,MAA6B,EAC7B,EAAE;oBACF,UAAU,EAAE,CAAA;oBACZ,OAAO,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;gBACrC,CAAC,CAAA;YACH,CAAC;YAED,iDAAiD;YACjD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxD,OAAO,CAAC,GAAG,IAAW,EAAE,EAAE;oBACxB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;oBACpC,OAAO,SAAS,CAAC,MAAM,CAAC,CAAA;gBAC1B,CAAC,CAAA;YACH,CAAC;YAED,0DAA0D;YAC1D,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;gBACxD,OAAO,CAAC,GAAG,IAAW,EAAE,EAAE;oBACxB,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;oBACpC,uDAAuD;oBACvD,0CAA0C;oBAC1C,OAAO,SAAS,CAAC,MAAM,CAAC,CAAA;gBAC1B,CAAC,CAAA;YACH,CAAC;YAED,uCAAuC;YACvC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,CAAA;YAC/C,IAAI,OAAO,KAAK,KAAK,UAAU;gBAAE,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAA;YAC1D,OAAO,KAAK,CAAA;QACd,CAAC;KACF,CAAA;IAED,OAAO,SAAS,CAAC,MAAM,CAAC,CAAA;AAC1B,CAAC"}
@@ -0,0 +1,53 @@
1
+ import { type Context, type MiddlewareHandler } from 'hono';
2
+ import { type Tenant, type TenantScaleOptions, type UserTenantContext } from '../types.js';
3
+ declare module 'hono' {
4
+ interface ContextVariableMap {
5
+ userTenant: UserTenantContext;
6
+ tenant: Tenant;
7
+ }
8
+ }
9
+ /** Options for the user tenant middleware */
10
+ export interface UserTenantMiddlewareOptions extends TenantScaleOptions {
11
+ /**
12
+ * Function that extracts the authenticated user's ID from the request.
13
+ * Return null/undefined if the user is not authenticated.
14
+ */
15
+ getUser: (c: Context) => string | null | undefined | Promise<string | null | undefined>;
16
+ /**
17
+ * What to do when no authenticated user is found.
18
+ * - 'reject': Return 401 Unauthorized (default)
19
+ * - 'skip': Call next() without setting userTenant — useful when some routes are public
20
+ */
21
+ onUnauthenticated?: 'reject' | 'skip';
22
+ /**
23
+ * Optional: How to determine which tenant to use when a user belongs to multiple.
24
+ * - 'first': Use the first tenant (default)
25
+ * - 'header': Read tenant from a custom header (specify via tenantHeader)
26
+ * - 'domain': Resolve from the request's host/domain
27
+ */
28
+ multiTenantStrategy?: 'first' | 'header' | 'domain';
29
+ /** Header name to use when multiTenantStrategy is 'header' (default: 'X-Tenant-ID') */
30
+ tenantHeader?: string;
31
+ /**
32
+ * Role values that count as "admin" for the `isAdmin` flag.
33
+ * Default: ['owner', 'admin']
34
+ * Set this to match your own role model.
35
+ */
36
+ adminRoles?: string[];
37
+ }
38
+ /**
39
+ * Creates Hono middleware that resolves a user's tenant context
40
+ * from your existing auth system. BYOA — Bring Your Own Auth.
41
+ *
42
+ * The developer provides `getUser(c)` which extracts the user ID
43
+ * from their auth provider. TenantScale looks up the tenant membership
44
+ * and attaches the full context to the request.
45
+ */
46
+ export declare function createUserTenantMiddleware(options: UserTenantMiddlewareOptions): MiddlewareHandler;
47
+ /**
48
+ * Role guard middleware — restricts routes based on user role.
49
+ * Must be used after `createUserTenantMiddleware()`.
50
+ */
51
+ export declare function requireUserRole(...roles: string[]): MiddlewareHandler;
52
+ export { type UserTenantContext };
53
+ //# sourceMappingURL=user-auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-auth.d.ts","sourceRoot":"","sources":["../../src/middleware/user-auth.ts"],"names":[],"mappings":"AAoCA,OAAO,EAAE,KAAK,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAE3D,OAAO,EACL,KAAK,MAAM,EACX,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EAGvB,MAAM,aAAa,CAAA;AAGpB,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,kBAAkB;QAC1B,UAAU,EAAE,iBAAiB,CAAA;QAC7B,MAAM,EAAE,MAAM,CAAA;KACf;CACF;AAED,6CAA6C;AAC7C,MAAM,WAAW,2BAA4B,SAAQ,kBAAkB;IACrE;;;OAGG;IACH,OAAO,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC,CAAA;IAEvF;;;;OAIG;IACH,iBAAiB,CAAC,EAAE,QAAQ,GAAG,MAAM,CAAA;IAErC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAA;IAEnD,uFAAuF;IACvF,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;CACtB;AAED;;;;;;;GAOG;AACH,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,2BAA2B,GACnC,iBAAiB,CA0HnB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,GAAG,KAAK,EAAE,MAAM,EAAE,GAAG,iBAAiB,CAarE;AAED,OAAO,EAAE,KAAK,iBAAiB,EAAE,CAAA"}
@@ -0,0 +1,154 @@
1
+ // ──────────────────────────────────────────────────────
2
+ // User Auth Middleware — Bring Your Own Auth (BYOA)
3
+ // ──────────────────────────────────────────────────────
4
+ // Developers keep their auth provider (Clerk, Auth0, Supabase Auth,
5
+ // Firebase, raw JWT, session cookies). TenantScale resolves the
6
+ // tenant context from the authenticated user.
7
+ //
8
+ // Usage:
9
+ // ```ts
10
+ // import { Hono } from 'hono'
11
+ // import { createUserTenantMiddleware } from '@tenantscale/sdk/user'
12
+ //
13
+ // const app = new Hono()
14
+ //
15
+ // // Developer's auth middleware (their own provider)
16
+ // app.use('*', async (c, next) => {
17
+ // const session = await getMyAuthSession(c) // Clerk, Auth0, etc.
18
+ // c.set('userId', session?.userId)
19
+ // await next()
20
+ // })
21
+ //
22
+ // // TenantScale resolves tenant from the user
23
+ // app.use('/api/*', createUserTenantMiddleware({
24
+ // apiKey: process.env.TENANTSCALE_API_KEY,
25
+ // baseUrl: 'https://api.tenantscale.com',
26
+ // getUser: (c) => c.get('userId'),
27
+ // onUnauthenticated: 'reject', // or 'skip'
28
+ // }))
29
+ //
30
+ // app.get('/api/projects', (c) => {
31
+ // const ctx = c.var.userTenant
32
+ // // ctx.userId, ctx.tenantId, ctx.role, ctx.tenant
33
+ // })
34
+ // ```
35
+ // ──────────────────────────────────────────────────────
36
+ import { TenantClient } from '../tenant.js';
37
+ import { UnauthorizedError, TenantScaleError, } from '../types.js';
38
+ /**
39
+ * Creates Hono middleware that resolves a user's tenant context
40
+ * from your existing auth system. BYOA — Bring Your Own Auth.
41
+ *
42
+ * The developer provides `getUser(c)` which extracts the user ID
43
+ * from their auth provider. TenantScale looks up the tenant membership
44
+ * and attaches the full context to the request.
45
+ */
46
+ export function createUserTenantMiddleware(options) {
47
+ const { getUser, onUnauthenticated = 'reject', multiTenantStrategy = 'first', tenantHeader = 'X-Tenant-ID', adminRoles = ['owner', 'admin'], ...tsOptions } = options;
48
+ const client = new TenantClient(tsOptions);
49
+ return async (c, next) => {
50
+ // 1. Get the authenticated user from the developer's auth
51
+ const userId = await getUser(c);
52
+ if (!userId) {
53
+ if (onUnauthenticated === 'skip') {
54
+ // Let the request pass through — public routes handled downstream
55
+ await next();
56
+ return;
57
+ }
58
+ return c.json({ error: 'Authentication required' }, 401);
59
+ }
60
+ try {
61
+ // 2. Resolve tenant membership from TenantScale API
62
+ const baseUrl = tsOptions.baseUrl ?? 'https://api.tenantscale.com';
63
+ const apiKey = tsOptions.apiKey;
64
+ // Fetch user's tenant memberships
65
+ const res = await fetch(`${baseUrl}/v1/users/${userId}/tenants`, {
66
+ headers: {
67
+ Authorization: `Bearer ${apiKey}`,
68
+ 'Content-Type': 'application/json',
69
+ },
70
+ });
71
+ if (!res.ok) {
72
+ throw new TenantScaleError(`Failed to resolve user tenants: ${res.statusText}`, 'USER_TENANT_RESOLVE_FAILED', res.status);
73
+ }
74
+ const body = await res.json();
75
+ if (!body.tenants || body.tenants.length === 0) {
76
+ return c.json({ error: 'User is not associated with any tenant' }, 403);
77
+ }
78
+ // 3. Determine which tenant to use
79
+ let selectedTenant;
80
+ if (body.tenants.length === 1 || multiTenantStrategy === 'first') {
81
+ selectedTenant = body.tenants[0];
82
+ }
83
+ else if (multiTenantStrategy === 'header') {
84
+ const requestedTenantId = c.req.header(tenantHeader);
85
+ const match = body.tenants.find(t => t.tenantId === requestedTenantId || t.tenantSlug === requestedTenantId);
86
+ if (!match) {
87
+ return c.json({
88
+ error: `Tenant not found or access denied`,
89
+ available_tenants: body.tenants.map(t => ({
90
+ id: t.tenantId,
91
+ name: t.tenantName,
92
+ slug: t.tenantSlug,
93
+ role: t.role,
94
+ })),
95
+ }, 404);
96
+ }
97
+ selectedTenant = match;
98
+ }
99
+ else {
100
+ // 'domain' strategy — resolve from host
101
+ const host = c.req.header('host') ?? '';
102
+ const match = body.tenants.find(t => t.tenantSlug && host.startsWith(t.tenantSlug));
103
+ selectedTenant = match ?? body.tenants[0];
104
+ }
105
+ // 4. Build the user tenant context
106
+ const ctx = {
107
+ userId,
108
+ tenantId: selectedTenant.tenantId,
109
+ role: selectedTenant.role,
110
+ tenant: selectedTenant.tenant,
111
+ isAdmin: adminRoles.includes(selectedTenant.role),
112
+ tenants: body.tenants.map(t => ({
113
+ tenantId: t.tenantId,
114
+ tenantName: t.tenantName,
115
+ tenantSlug: t.tenantSlug,
116
+ role: t.role,
117
+ })),
118
+ };
119
+ // 5. Attach to Hono context
120
+ c.set('userTenant', ctx);
121
+ c.set('tenant', selectedTenant.tenant);
122
+ await next();
123
+ }
124
+ catch (err) {
125
+ if (err instanceof UnauthorizedError) {
126
+ return c.json({ error: err.message }, 401);
127
+ }
128
+ if (err instanceof TenantScaleError) {
129
+ return c.json({ error: err.message, code: err.code }, err.status);
130
+ }
131
+ console.error('[TenantScale] User auth error:', err);
132
+ return c.json({ error: 'Failed to resolve user tenant context' }, 500);
133
+ }
134
+ };
135
+ }
136
+ /**
137
+ * Role guard middleware — restricts routes based on user role.
138
+ * Must be used after `createUserTenantMiddleware()`.
139
+ */
140
+ export function requireUserRole(...roles) {
141
+ return async (c, next) => {
142
+ const ctx = c.get('userTenant');
143
+ if (!ctx) {
144
+ return c.json({ error: 'Authentication required' }, 401);
145
+ }
146
+ if (!roles.includes(ctx.role)) {
147
+ return c.json({
148
+ error: `This endpoint requires one of these roles: ${roles.join(', ')}`,
149
+ }, 403);
150
+ }
151
+ await next();
152
+ };
153
+ }
154
+ //# sourceMappingURL=user-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"user-auth.js","sourceRoot":"","sources":["../../src/middleware/user-auth.ts"],"names":[],"mappings":"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"}
@@ -13,7 +13,7 @@ export const TenantContext = createContext({
13
13
  });
14
14
  export function useTenant() {
15
15
  const ctx = useContext(TenantContext);
16
- if (!ctx) {
16
+ if (!ctx.tenant && !ctx.loading) {
17
17
  throw new Error('useTenant must be used within a TenantProvider');
18
18
  }
19
19
  return ctx;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,4DAA4D;AAC5D,yDAAyD;AAEzD,YAAY,CAAA;AAEZ,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAejD,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAqB;IAC7D,MAAM,EAAE,IAAI;IACZ,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,IAAI;IACb,YAAY,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;IAC5B,UAAU,EAAE,GAAG,EAAE,CAAC,KAAK;IACvB,MAAM,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;CACvB,CAAC,CAAA;AAEF,MAAM,UAAU,SAAS;IACvB,MAAM,GAAG,GAAG,UAAU,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAA;IACnE,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/react/index.ts"],"names":[],"mappings":"AAAA,yDAAyD;AACzD,4DAA4D;AAC5D,yDAAyD;AAEzD,YAAY,CAAA;AAEZ,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAA;AAejD,MAAM,CAAC,MAAM,aAAa,GAAG,aAAa,CAAqB;IAC7D,MAAM,EAAE,IAAI;IACZ,IAAI,EAAE,IAAI;IACV,OAAO,EAAE,IAAI;IACb,YAAY,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;IAC5B,UAAU,EAAE,GAAG,EAAE,CAAC,KAAK;IACvB,MAAM,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;CACvB,CAAC,CAAA;AAEF,MAAM,UAAU,SAAS;IACvB,MAAM,GAAG,GAAG,UAAU,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,gDAAgD,CAAC,CAAA;IACnE,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -0,0 +1,129 @@
1
+ import { TenantClient } from './tenant.js';
2
+ import { type Tenant, type TenantScaleOptions, type AuditEvent } from './types.js';
3
+ import type { Context, MiddlewareHandler } from 'hono';
4
+ declare module 'hono' {
5
+ interface ContextVariableMap {
6
+ tenant: Tenant;
7
+ tenantClient: TenantClient;
8
+ }
9
+ }
10
+ export declare class TenantScale {
11
+ private client;
12
+ private apiKey;
13
+ constructor(options: TenantScaleOptions & {
14
+ apiKey?: string;
15
+ });
16
+ /**
17
+ * Create a new instance with a different API key.
18
+ * Useful when you need to switch tenants.
19
+ */
20
+ withApiKey(apiKey: string): TenantScale;
21
+ /**
22
+ * Enforces tenant isolation — extracts the API key from the
23
+ * Authorization header and attaches tenant context to the request.
24
+ *
25
+ * Usage:
26
+ * app.use('/api/*', ts.protect())
27
+ */
28
+ protect(): MiddlewareHandler;
29
+ /**
30
+ * Logs every request to the audit trail.
31
+ * Place after protect() so tenant context is available.
32
+ *
33
+ * Usage:
34
+ * app.use('/api/*', ts.audit())
35
+ */
36
+ audit(): MiddlewareHandler;
37
+ /**
38
+ * Requires an admin API key. Use on admin routes.
39
+ *
40
+ * Usage:
41
+ * app.use('/admin/*', ts.requireAdmin())
42
+ */
43
+ requireAdmin(): MiddlewareHandler;
44
+ /**
45
+ * Get the current tenant context from a Hono context.
46
+ * Works after protect() middleware has run.
47
+ */
48
+ getTenant(c: Context): Tenant | undefined;
49
+ /**
50
+ * Get the current user context.
51
+ * (Placeholder — user resolution depends on your auth setup.)
52
+ */
53
+ getUser(c: Context): {
54
+ id: string;
55
+ email?: string;
56
+ role?: string;
57
+ } | undefined;
58
+ /**
59
+ * Log a custom audit event from within a route handler.
60
+ *
61
+ * Usage:
62
+ * app.post('/api/orders', async (c) => {
63
+ * await ts.logAudit(c, { action: 'order.created', resource: 'order:123' })
64
+ * })
65
+ */
66
+ logAudit(c: Context, event: {
67
+ action: string;
68
+ resource: string;
69
+ details?: Record<string, unknown>;
70
+ }): Promise<void>;
71
+ /**
72
+ * List all tenants (admin only).
73
+ */
74
+ listTenants(): Promise<Tenant[]>;
75
+ /**
76
+ * Get paginated audit log (admin only).
77
+ */
78
+ getAuditLog(c: Context, opts: {
79
+ tenantId?: string;
80
+ limit?: number;
81
+ }): Promise<AuditEvent[]>;
82
+ /**
83
+ * Get a specific plan limit value for the current tenant.
84
+ * Returns null if the limit is not defined.
85
+ *
86
+ * Usage:
87
+ * const maxUsers = ts.getPlanLimit(c, 'max_users')
88
+ * // → 100 (or null if unlimited / not set)
89
+ */
90
+ getPlanLimit(c: Context, key: string): boolean | number | string | null;
91
+ /**
92
+ * Get all plan limits for the current tenant.
93
+ * Returns the merged feature map (plan defaults + tenant overrides).
94
+ *
95
+ * Usage:
96
+ * const limits = ts.getPlanLimits(c)
97
+ * // → { max_users: 100, audit_log_retention_days: 30, sso: true, ... }
98
+ */
99
+ getPlanLimits(c: Context): Record<string, boolean | number | string>;
100
+ /**
101
+ * Check if a usage value exceeds the plan limit.
102
+ * Returns true if the limit is exceeded (or is 0).
103
+ * Returns false if the limit is null (unlimited).
104
+ *
105
+ * Usage:
106
+ * if (ts.planLimitExceeded(c, 'max_users', currentUserCount)) {
107
+ * return c.json({ error: 'User limit reached' }, 403)
108
+ * }
109
+ */
110
+ planLimitExceeded(c: Context, key: string, currentValue: number): boolean;
111
+ /**
112
+ * Fetch the full plan definition (metadata + limits) from the API.
113
+ * Use this when you need plan pricing, name, description, etc.
114
+ *
115
+ * Usage:
116
+ * const plan = await ts.fetchPlan(c)
117
+ * // → { id: 'pro', name: 'Pro', price_monthly: 39900, features: {...}, ... }
118
+ */
119
+ fetchPlan(c: Context): Promise<Record<string, unknown> | null>;
120
+ /**
121
+ * Fetch all available plans from the API.
122
+ *
123
+ * Usage:
124
+ * const plans = await ts.fetchPlans()
125
+ * // → [{ id: 'free', name: 'Free', ... }, { id: 'pro', ... }]
126
+ */
127
+ fetchPlans(): Promise<Record<string, unknown>[]>;
128
+ }
129
+ //# sourceMappingURL=tenant-scale.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tenant-scale.d.ts","sourceRoot":"","sources":["../src/tenant-scale.ts"],"names":[],"mappings":"AAWA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAC1C,OAAO,EAAE,KAAK,MAAM,EAAE,KAAK,kBAAkB,EAAE,KAAK,UAAU,EAAkB,MAAM,YAAY,CAAA;AAClG,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAGtD,OAAO,QAAQ,MAAM,CAAC;IACpB,UAAU,kBAAkB;QAC1B,MAAM,EAAE,MAAM,CAAA;QACd,YAAY,EAAE,YAAY,CAAA;KAC3B;CACF;AAKD,qBAAa,WAAW;IACtB,OAAO,CAAC,MAAM,CAAc;IAC5B,OAAO,CAAC,MAAM,CAAQ;gBAEV,OAAO,EAAE,kBAAkB,GAAG;QAAE,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE;IAK7D;;;OAGG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW;IAMvC;;;;;;OAMG;IACH,OAAO,IAAI,iBAAiB;IAwB5B;;;;;;OAMG;IACH,KAAK,IAAI,iBAAiB;IAsB1B;;;;;OAKG;IACH,YAAY,IAAI,iBAAiB;IAuBjC;;;OAGG;IACH,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS;IAIzC;;;OAGG;IACH,OAAO,CAAC,CAAC,EAAE,OAAO,GAAG;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,SAAS;IAO9E;;;;;;;OAOG;IACG,QAAQ,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAYzH;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAKtC;;OAEG;IACG,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAMjG;;;;;;;OAOG;IACH,YAAY,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI;IAMvE;;;;;;;OAOG;IACH,aAAa,CAAC,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;IAKpE;;;;;;;;;OASG;IACH,iBAAiB,CAAC,CAAC,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;IAYzE;;;;;;;OAOG;IACG,SAAS,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAqBpE;;;;;;OAMG;IACG,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;CAiBvD"}