@iqauth/sdk 2.0.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.
Files changed (112) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +287 -0
  3. package/dist/browser-session.d.mts +12 -0
  4. package/dist/browser-session.d.ts +12 -0
  5. package/dist/browser-session.js +1812 -0
  6. package/dist/browser-session.mjs +28 -0
  7. package/dist/browser.d.mts +46 -0
  8. package/dist/browser.d.ts +46 -0
  9. package/dist/browser.js +768 -0
  10. package/dist/browser.mjs +47 -0
  11. package/dist/chunk-5HF3OBNO.mjs +189 -0
  12. package/dist/chunk-5WFR6Y33.mjs +59 -0
  13. package/dist/chunk-6I6RM4MN.mjs +51 -0
  14. package/dist/chunk-73R6BEGO.mjs +176 -0
  15. package/dist/chunk-E46DKOVI.mjs +632 -0
  16. package/dist/chunk-JQWYIIIS.mjs +1740 -0
  17. package/dist/chunk-X3K3WOBR.mjs +64 -0
  18. package/dist/chunk-Y6FXYEAI.mjs +10 -0
  19. package/dist/cli/index.d.mts +1 -0
  20. package/dist/cli/index.d.ts +1 -0
  21. package/dist/cli/index.js +581 -0
  22. package/dist/cli/index.mjs +57 -0
  23. package/dist/client-C1DXfB8Z.d.mts +911 -0
  24. package/dist/client-CggvJmmm.d.ts +911 -0
  25. package/dist/dev-FUTJZSWN.mjs +56 -0
  26. package/dist/doctor-OHJRZBBT.mjs +89 -0
  27. package/dist/errors-CDdl24MP.d.mts +52 -0
  28. package/dist/errors-CDdl24MP.d.ts +52 -0
  29. package/dist/express-BKAXB5Nl.d.ts +61 -0
  30. package/dist/express-CpfyYTmw.d.mts +61 -0
  31. package/dist/express.d.mts +45 -0
  32. package/dist/express.d.ts +45 -0
  33. package/dist/express.js +2252 -0
  34. package/dist/express.mjs +122 -0
  35. package/dist/fastify.d.mts +23 -0
  36. package/dist/fastify.d.ts +23 -0
  37. package/dist/fastify.js +2062 -0
  38. package/dist/fastify.mjs +118 -0
  39. package/dist/hono.d.mts +22 -0
  40. package/dist/hono.d.ts +22 -0
  41. package/dist/hono.js +2051 -0
  42. package/dist/hono.mjs +107 -0
  43. package/dist/index.d.mts +6 -0
  44. package/dist/index.d.ts +6 -0
  45. package/dist/index.js +2070 -0
  46. package/dist/index.mjs +83 -0
  47. package/dist/init-LLCSQGNL.mjs +198 -0
  48. package/dist/keys-NLWFAOEM.mjs +63 -0
  49. package/dist/mobile.d.mts +11 -0
  50. package/dist/mobile.d.ts +11 -0
  51. package/dist/mobile.js +1809 -0
  52. package/dist/mobile.mjs +25 -0
  53. package/dist/next.d.mts +37 -0
  54. package/dist/next.d.ts +37 -0
  55. package/dist/next.js +2078 -0
  56. package/dist/next.mjs +130 -0
  57. package/dist/publishableKey-B5DIK81A.d.mts +24 -0
  58. package/dist/publishableKey-B5DIK81A.d.ts +24 -0
  59. package/dist/react.d.mts +196 -0
  60. package/dist/react.d.ts +196 -0
  61. package/dist/react.js +1457 -0
  62. package/dist/react.mjs +787 -0
  63. package/dist/server/handlers.d.mts +96 -0
  64. package/dist/server/handlers.d.ts +96 -0
  65. package/dist/server/handlers.js +243 -0
  66. package/dist/server/handlers.mjs +14 -0
  67. package/dist/server.d.mts +14 -0
  68. package/dist/server.d.ts +14 -0
  69. package/dist/server.js +2195 -0
  70. package/dist/server.mjs +47 -0
  71. package/dist/service.d.mts +11 -0
  72. package/dist/service.d.ts +11 -0
  73. package/dist/service.js +1809 -0
  74. package/dist/service.mjs +25 -0
  75. package/dist/signIn-C8f6qVjD.d.mts +238 -0
  76. package/dist/signIn-Cy2lbEXb.d.ts +238 -0
  77. package/dist/types-Cxl3bQHt.d.mts +900 -0
  78. package/dist/types-Cxl3bQHt.d.ts +900 -0
  79. package/docs/APP_INTEGRATION_MATRIX.md +59 -0
  80. package/docs/BROWSER_SESSION_MIGRATION.md +69 -0
  81. package/docs/FRESH_IMPLEMENTATION_GUIDE.md +188 -0
  82. package/docs/TARBALL_RELEASE_WORKFLOW.md +98 -0
  83. package/docs/V1_TO_V2_UPGRADE_GUIDE.md +318 -0
  84. package/docs/guides/api-keys.md +130 -0
  85. package/docs/guides/app-registration.md +149 -0
  86. package/docs/guides/auth-flows.md +168 -0
  87. package/docs/guides/branding.md +160 -0
  88. package/docs/guides/entitlements.md +115 -0
  89. package/docs/guides/entity-hierarchy.md +200 -0
  90. package/docs/guides/error-handling.md +251 -0
  91. package/docs/guides/gdpr-compliance.md +123 -0
  92. package/docs/guides/invitations.md +143 -0
  93. package/docs/guides/mfa-enrollment.md +170 -0
  94. package/docs/guides/middleware-reference.md +205 -0
  95. package/docs/guides/mobile-native.md +110 -0
  96. package/docs/guides/roles-and-permissions.md +220 -0
  97. package/docs/guides/scoped-authorization.md +247 -0
  98. package/docs/guides/server-platform-integration.md +52 -0
  99. package/docs/guides/service-automation-integration.md +36 -0
  100. package/docs/guides/session-management.md +97 -0
  101. package/docs/guides/tenant-management.md +216 -0
  102. package/docs/guides/token-verification.md +178 -0
  103. package/docs/guides/user-management.md +184 -0
  104. package/docs/guides/webhooks.md +136 -0
  105. package/docs/integration-prompts/README.md +20 -0
  106. package/docs/integration-prompts/first-party-browser-app.md +29 -0
  107. package/docs/integration-prompts/install-from-tarball.md +41 -0
  108. package/docs/integration-prompts/migrate-from-local-packages-source.md +57 -0
  109. package/docs/integration-prompts/native-mobile-app.md +24 -0
  110. package/docs/integration-prompts/server-platform-app.md +20 -0
  111. package/docs/integration-prompts/service-automation-app.md +20 -0
  112. package/package.json +115 -0
@@ -0,0 +1,247 @@
1
+ # Scoped Authorization (V2)
2
+
3
+ ## Purpose
4
+
5
+ Implement V2 scoped authorization using entity-scoped memberships, scope switching, and permission groups with inheritance. Extends the V1 flat role model.
6
+
7
+ ## Prerequisites
8
+
9
+ - `@iqauth/sdk` installed
10
+ - Tenant with `enableScopedAuth: true` (set via `client.tenants.update()`)
11
+ - Entity hierarchy created (vendors, sources, clients)
12
+ - `tenant_admin` or `platform_admin` role
13
+
14
+ ## Environment Note
15
+
16
+ Scope-switching token examples in this guide assume a trusted runtime or secure mobile storage.
17
+
18
+ For first-party browser apps, perform scope-sensitive operations through your backend session layer.
19
+
20
+ ## SDK Methods
21
+
22
+ ### MembershipsModule
23
+
24
+ | Method | Description |
25
+ |--------|-------------|
26
+ | `grant(data)` | Grant scoped membership → `Membership` |
27
+ | `listForUser(userId, tenantId)` | List user's memberships |
28
+ | `listForScope(type, id)` | List memberships for a scope |
29
+ | `listForTenant(params?)` | List all tenant memberships |
30
+ | `update(id, data)` | Update membership |
31
+ | `revoke(id)` | Revoke membership |
32
+
33
+ ### ScopeModule
34
+
35
+ | Method | Description |
36
+ |--------|-------------|
37
+ | `getAvailable()` | Get available scopes tree |
38
+ | `switchScope(type, id)` | Switch active scope → new access token |
39
+
40
+ ### PermissionGroupsModule
41
+
42
+ | Method | Description |
43
+ |--------|-------------|
44
+ | `list(tenantId)` | List permission groups |
45
+ | `create(tenantId, name, desc?)` | Create group |
46
+ | `update(tenantId, groupId, data)` | Update group |
47
+ | `delete(tenantId, groupId)` | Delete group |
48
+ | `getPermissions(tenantId, groupId)` | List group permissions |
49
+ | `addPermission(tenantId, groupId, data)` | Add permission to group |
50
+ | `removePermission(tenantId, groupId, permId)` | Remove permission |
51
+ | `addInheritance(tenantId, groupId, fromId)` | Add group inheritance |
52
+ | `removeInheritance(tenantId, groupId, fromId)` | Remove inheritance |
53
+ | `getUserGroups(tenantId, userId)` | Get user's groups |
54
+ | `assignUserToGroup(tenantId, userId, groupId)` | Assign user to group |
55
+ | `removeUserFromGroup(tenantId, userId, groupId)` | Remove from group |
56
+ | `getUserOverrides(tenantId, userId)` | Get user overrides |
57
+ | `addUserOverride(tenantId, userId, data)` | Add override |
58
+ | `removeUserOverride(tenantId, userId, overrideId)` | Remove override |
59
+ | `getEffectivePermissions(tenantId, userId, params)` | Resolved permissions |
60
+ | `checkPermission(tenantId, userId, appKey, nodeKey)` | Check single permission |
61
+
62
+ ## Step-by-Step
63
+
64
+ ### 1. Enable Scoped Auth
65
+
66
+ ```typescript
67
+ await client.tenants.update(tenantId, { enableScopedAuth: true });
68
+ ```
69
+
70
+ ### 2. Grant Scoped Memberships
71
+
72
+ ```typescript
73
+ const membership = await client.memberships.grant({
74
+ userId: "user-uuid",
75
+ roleName: "vendor_admin",
76
+ scopeType: "vendor",
77
+ scopeId: vendorId,
78
+ });
79
+ ```
80
+
81
+ Valid `scopeType`: `"tenant"`, `"vendor"`, `"source"`, `"client"`.
82
+
83
+ ### 3. Query Memberships
84
+
85
+ ```typescript
86
+ const userResult = await client.memberships.listForUser(userId, tenantId);
87
+ const scopeResult = await client.memberships.listForScope("vendor", vendorId);
88
+ const tenantResult = await client.memberships.listForTenant({ scopeType: "source" });
89
+ // Each result has a .memberships array
90
+ ```
91
+
92
+ ### 4. Switch Scope
93
+
94
+ ```typescript
95
+ const tree = await client.scope.getAvailable();
96
+ // tree.vendors[0].sources[0].clients[0]
97
+
98
+ const { accessToken, scopeContext } = await client.scope.switchScope("vendor", vendorId);
99
+ // Trusted runtime example: preserve the refresh token only in a server or secure mobile context
100
+ client.setTokens({ accessToken, refreshToken: client.getRefreshToken()! });
101
+ // JWT now includes scopeContext: { type, id, role, membershipId }
102
+ ```
103
+
104
+ ### 5. Set Up Permission Groups
105
+
106
+ ```typescript
107
+ const group = await client.permissionGroups.create(tenantId, "Operators", "Field operators");
108
+
109
+ await client.permissionGroups.addPermission(tenantId, group.id, {
110
+ appKey: "iqcapture",
111
+ nodeKey: "capture.operate",
112
+ effect: "allow",
113
+ weight: 10,
114
+ });
115
+
116
+ await client.permissionGroups.assignUserToGroup(tenantId, userId, group.id);
117
+ ```
118
+
119
+ ### 6. Group Inheritance
120
+
121
+ ```typescript
122
+ await client.permissionGroups.addInheritance(tenantId, adminGroupId, baseGroupId);
123
+ ```
124
+
125
+ ### 7. User Overrides
126
+
127
+ ```typescript
128
+ await client.permissionGroups.addUserOverride(tenantId, userId, {
129
+ appKey: "iqcapture",
130
+ nodeKey: "capture.delete",
131
+ effect: "deny",
132
+ weight: 100,
133
+ });
134
+ ```
135
+
136
+ ### 8. Check Effective Permissions
137
+
138
+ ```typescript
139
+ const effective = await client.permissionGroups.getEffectivePermissions(
140
+ tenantId, userId, { appKey: "iqcapture" },
141
+ );
142
+
143
+ const check = await client.permissionGroups.checkPermission(
144
+ tenantId, userId, "iqcapture", "capture.operate",
145
+ );
146
+ // { allowed: true, appKey: "iqcapture", nodeKey: "capture.operate" }
147
+ ```
148
+
149
+ ## Error Handling
150
+
151
+ | Error Code | Meaning | Recovery |
152
+ |------------|---------|----------|
153
+ | `NOT_FOUND` | Scope entity or user not found | Check IDs |
154
+ | `ALREADY_EXISTS` | Membership or group already exists | Check existing resources |
155
+ | `INSUFFICIENT_PERMISSIONS` | Caller lacks admin role | Requires `tenant_admin`+ |
156
+ | `VALIDATION_ERROR` | Invalid scope type or data | Check request format |
157
+
158
+ ```typescript
159
+ import { IQAuthError, ErrorCodes } from "@iqauth/sdk";
160
+
161
+ try {
162
+ await client.memberships.grant({
163
+ userId, roleName: "vendor_admin", scopeType: "vendor", scopeId: vendorId,
164
+ });
165
+ } catch (err) {
166
+ if (err instanceof IQAuthError && err.code === ErrorCodes.ALREADY_EXISTS) {
167
+ console.log("Membership already exists");
168
+ }
169
+ }
170
+ ```
171
+
172
+ ## Complete Example
173
+
174
+ ```typescript
175
+ import { IQAuthClient } from "@iqauth/sdk";
176
+
177
+ const client = new IQAuthClient({
178
+ baseUrl: "https://auth.dispositioniq.com",
179
+ accessToken: adminToken,
180
+ refreshToken: adminRefreshToken,
181
+ });
182
+
183
+ async function setupScopedAuth(tenantId: string, vendorId: string) {
184
+ await client.tenants.update(tenantId, { enableScopedAuth: true });
185
+
186
+ await client.memberships.grant({
187
+ userId: operatorUserId,
188
+ roleName: "user",
189
+ scopeType: "vendor",
190
+ scopeId: vendorId,
191
+ });
192
+
193
+ const group = await client.permissionGroups.create(tenantId, "Capture Operators");
194
+
195
+ await client.permissionGroups.addPermission(tenantId, group.id, {
196
+ appKey: "iqcapture",
197
+ nodeKey: "capture.operate",
198
+ effect: "allow",
199
+ });
200
+
201
+ await client.permissionGroups.assignUserToGroup(tenantId, operatorUserId, group.id);
202
+
203
+ const check = await client.permissionGroups.checkPermission(
204
+ tenantId, operatorUserId, "iqcapture", "capture.operate",
205
+ );
206
+ console.log("Can operate:", check.allowed);
207
+
208
+ const tree = await client.scope.getAvailable();
209
+ console.log("Available scopes:", JSON.stringify(tree, null, 2));
210
+ }
211
+ ```
212
+
213
+ Note: Scope routes are mounted under `/api/v1/auth`, not `/api/v1/scope`. See [DECISION-014](../../DECISIONS.md).
214
+
215
+ ## API Reference
216
+
217
+ ### MembershipsModule
218
+
219
+ | Method | HTTP | Path |
220
+ |--------|------|------|
221
+ | `grant(data)` | POST | `/api/v1/memberships` |
222
+ | `listForUser(userId, tenantId)` | GET | `/api/v1/users/:userId/memberships?tenantId=...` |
223
+ | `listForScope(type, id)` | GET | `/api/v1/memberships/scope/:type/:id` |
224
+ | `listForTenant(params?)` | GET | `/api/v1/memberships/tenant` |
225
+ | `update(id, data)` | PATCH | `/api/v1/memberships/:id` |
226
+ | `revoke(id)` | DELETE | `/api/v1/memberships/:id` |
227
+
228
+ ### ScopeModule
229
+
230
+ | Method | HTTP | Path |
231
+ |--------|------|------|
232
+ | `getAvailable()` | GET | `/api/v1/auth/available-scopes` |
233
+ | `switchScope(type, id)` | POST | `/api/v1/auth/switch-scope` |
234
+
235
+ ### PermissionGroupsModule
236
+
237
+ | Method | HTTP | Path |
238
+ |--------|------|------|
239
+ | `list(tenantId)` | GET | `/api/v1/tenants/:tenantId/permission-groups` |
240
+ | `create(tenantId, name, desc?)` | POST | `/api/v1/tenants/:tenantId/permission-groups` |
241
+ | `getPermissions(tenantId, groupId)` | GET | `.../:groupId/permissions` |
242
+ | `addPermission(tenantId, groupId, data)` | POST | `.../:groupId/permissions` |
243
+ | `removePermission(tenantId, groupId, permId)` | DELETE | `.../:groupId/permissions/:permId` |
244
+ | `addInheritance(tenantId, groupId, fromId)` | POST | `.../:groupId/inherit` |
245
+ | `assignUserToGroup(tenantId, userId, groupId)` | POST | `.../users/:userId/groups` |
246
+ | `getEffectivePermissions(tenantId, userId, params)` | GET | `.../users/:userId/permissions/effective` |
247
+ | `checkPermission(tenantId, userId, appKey, nodeKey)` | POST | `.../users/:userId/permissions/check` |
@@ -0,0 +1,52 @@
1
+ # Server Platform Integration
2
+
3
+ This guide is for trusted backend platforms that accept IQAuth bearer tokens or need to call IQAuth directly.
4
+
5
+ ## Recommended Pattern
6
+
7
+ Use the server entry point and the provided middleware:
8
+
9
+ ```ts
10
+ import express from "express";
11
+ import { createServerClient } from "@iqauth/sdk/server";
12
+
13
+ const client = createServerClient({
14
+ baseUrl: process.env.IQAUTH_BASE_URL!,
15
+ });
16
+
17
+ const app = express();
18
+
19
+ app.use("/api", client.middleware());
20
+ ```
21
+
22
+ ## Why this is the default
23
+
24
+ - it keeps token verification centralized
25
+ - it prevents each app from re-implementing JWT verification and drift-prone auth checks
26
+ - it gives the platform one canonical middleware contract
27
+
28
+ ## Required Practices
29
+
30
+ - use `client.middleware()` unless your framework cannot support Express-style middleware
31
+ - enforce role and entitlement checks on the server, not only in the UI
32
+ - avoid shared multi-user token state
33
+ - keep service tokens and user tokens separate
34
+
35
+ ## Typical Integration Shapes
36
+
37
+ ### Resource server
38
+
39
+ - accepts incoming bearer tokens
40
+ - uses middleware to verify and attach auth context
41
+ - authorizes routes from verified claims
42
+
43
+ ### Backend calling IQAuth APIs directly
44
+
45
+ - uses request-scoped or service-scoped tokens
46
+ - calls `client.users`, `client.sessions`, `client.permissions`, etc.
47
+
48
+ ## Anti-Patterns
49
+
50
+ - re-implementing token verification without a concrete reason
51
+ - trusting browser role checks without server enforcement
52
+ - sharing one mutable token cache across unrelated user sessions
@@ -0,0 +1,36 @@
1
+ # Service Automation Integration
2
+
3
+ This guide is for workers, batch jobs, schedulers, and service-to-service integrations.
4
+
5
+ ## Recommended Pattern
6
+
7
+ Prefer API keys.
8
+
9
+ ```ts
10
+ import { createServiceClient } from "@iqauth/sdk/service";
11
+
12
+ const client = createServiceClient({
13
+ baseUrl: process.env.IQAUTH_BASE_URL!,
14
+ apiKey: process.env.IQAUTH_API_KEY!,
15
+ });
16
+ ```
17
+
18
+ ## Why API keys are preferred
19
+
20
+ - no interactive user lifecycle
21
+ - simpler rotation model
22
+ - lower risk than pretending a headless service is a browser or mobile app
23
+
24
+ ## Required Practices
25
+
26
+ - scope service credentials as narrowly as possible
27
+ - document rotation and revocation ownership
28
+ - do not use interactive login flows unless a product requirement explicitly demands it
29
+
30
+ ## When not to use this model
31
+
32
+ Do not use service credentials for:
33
+
34
+ - first-party browser apps
35
+ - end-user mobile login flows
36
+ - backend routes that should be enforcing user identity
@@ -0,0 +1,97 @@
1
+ # Session Management
2
+
3
+ Session handling differs by environment.
4
+
5
+ ## Browser First-Party Apps
6
+
7
+ Use backend-managed sessions.
8
+
9
+ ### Recommended pattern
10
+
11
+ - backend stores access and refresh tokens in `httpOnly` cookies
12
+ - frontend boots with `/auth/me`
13
+ - frontend calls protected APIs with cookies
14
+ - backend performs refresh
15
+
16
+ ### Browser responsibilities
17
+
18
+ - display session state
19
+ - handle session-expired UX
20
+ - handle logout UX
21
+
22
+ ### Browser non-goals
23
+
24
+ - do not persist refresh tokens in `localStorage`
25
+ - do not make the browser responsible for durable token lifecycle
26
+
27
+ ## Server and Mobile Token-Owning Clients
28
+
29
+ If your runtime can safely own tokens, the SDK session APIs are appropriate.
30
+
31
+ ## SDK Methods
32
+
33
+ | Module | Method | Description |
34
+ |--------|--------|-------------|
35
+ | `sessions` | `list()` | List active sessions |
36
+ | `sessions` | `revoke(sessionId)` | Terminate one session |
37
+ | `sessions` | `revokeAll()` | Terminate all sessions |
38
+ | `auth` | `logout()` | End current session |
39
+ | `auth` | `logoutAll()` | End all sessions for the current user |
40
+
41
+ ## List Sessions
42
+
43
+ ```typescript
44
+ const sessions = await client.sessions.list();
45
+ ```
46
+
47
+ ## Identify Current Session
48
+
49
+ In token-owning environments, the current session ID is available from JWT claims:
50
+
51
+ ```typescript
52
+ const claims = client.tokens.getClaims(client.getAccessToken()!);
53
+ const currentSessionId = claims.sessionId;
54
+ ```
55
+
56
+ ## Revoke a Session
57
+
58
+ ```typescript
59
+ await client.sessions.revoke(sessionId);
60
+ ```
61
+
62
+ ## Revoke All Sessions
63
+
64
+ ```typescript
65
+ const { terminatedCount } = await client.sessions.revokeAll();
66
+ ```
67
+
68
+ ## Mobile Guidance
69
+
70
+ If mobile owns tokens:
71
+
72
+ - keep tokens in secure storage
73
+ - clear secure storage on logout
74
+ - treat `REFRESH_TOKEN_REUSED` or revocation as forced re-authentication
75
+
76
+ ## Browser Guidance
77
+
78
+ If the browser is using a backend cookie session:
79
+
80
+ - list/revoke session APIs can still back an "active sessions" UI
81
+ - but logout and refresh should still be backend-driven
82
+ - the browser should not need raw refresh-token access to manage session lifecycle
83
+
84
+ ## Error Handling
85
+
86
+ | Error Code | Meaning |
87
+ |--------|--------|
88
+ | `SESSION_INVALID` | Session not found |
89
+ | `SESSION_EXPIRED` | Session already expired |
90
+ | `TOKEN_INVALID` | Caller not authenticated |
91
+ | `TOKEN_REVOKED` | Session or token was revoked |
92
+
93
+ ## Summary
94
+
95
+ - browser: session cookies, backend ownership
96
+ - mobile: secure storage
97
+ - server: direct tokens allowed
@@ -0,0 +1,216 @@
1
+ # Tenant Management
2
+
3
+ ## Purpose
4
+
5
+ Create, read, update, and delete tenants. Manage tenant users, password policies, MFA policies, and public branding. Handle tenant promotion to vendor status.
6
+
7
+ ## Prerequisites
8
+
9
+ - `@iqauth/sdk` installed
10
+ - `tenant_admin` role for tenant user management and policies
11
+ - `platform_admin` role for tenant creation/deletion and promotion
12
+
13
+ ## Environment Note
14
+
15
+ Tenant-management examples are intended for trusted administrative runtimes. Keep these operations behind your backend session boundary for first-party web apps.
16
+
17
+ ## SDK Methods
18
+
19
+ | Module | Method | Description |
20
+ |--------|--------|-------------|
21
+ | `tenants` | `get(tenantId)` | Get tenant info |
22
+ | `tenants` | `getCurrent(tenantId)` | Get tenant info (alias) |
23
+ | `tenants` | `list(params?)` | List tenants |
24
+ | `tenants` | `create(data)` | Create tenant |
25
+ | `tenants` | `update(tenantId, data)` | Update tenant |
26
+ | `tenants` | `delete(tenantId)` | Delete tenant |
27
+ | `tenants` | `promoteToVendor(tenantId, data)` | Promote to vendor |
28
+ | `tenants` | `getUsers(tenantId)` | List tenant users |
29
+ | `tenants` | `inviteUser(tenantId, data)` | Invite user to tenant |
30
+ | `tenants` | `changeUserRole(tenantId, userId, role)` | Change user role |
31
+ | `tenants` | `migrateUser(tenantId, userId, data)` | Migrate user between tenants |
32
+ | `tenants` | `removeUser(tenantId, userId)` | Remove user from tenant |
33
+ | `tenants` | `getPasswordPolicy(tenantId)` | Get password policy |
34
+ | `tenants` | `updatePasswordPolicy(tenantId, data)` | Update password policy |
35
+ | `tenants` | `getMfaPolicies(tenantId)` | Get MFA policies |
36
+ | `tenants` | `updateMfaPolicy(tenantId, role, data)` | Update MFA policy for role |
37
+ | `tenants` | `getPublicBranding(params?)` | Get public branding (no auth) |
38
+ | `tenants` | `getPublicBrandingBySlug(slug)` | Get branding by vendor slug (no auth) |
39
+
40
+ ## Step-by-Step
41
+
42
+ ### 1. Get a Tenant
43
+
44
+ ```typescript
45
+ const tenant = await client.tenants.get(tenantId);
46
+ ```
47
+
48
+ Returns `TenantInfo`:
49
+
50
+ ```typescript
51
+ interface TenantInfo {
52
+ id: string;
53
+ name: string;
54
+ slug: string;
55
+ plan?: string;
56
+ isActive?: boolean;
57
+ vendorId?: string | null;
58
+ vendorName?: string | null;
59
+ allowedDomains?: string[] | null;
60
+ autoProvisionRole?: string | null;
61
+ enableScopedAuth?: boolean;
62
+ }
63
+ ```
64
+
65
+ Note: `getCurrent(tenantId)` requires explicit `tenantId`. See [DECISION-007](../../DECISIONS.md).
66
+
67
+ ### 2. Create a Tenant
68
+
69
+ ```typescript
70
+ const tenant = await client.tenants.create({
71
+ name: "Acme Corp",
72
+ slug: "acme-corp",
73
+ vendorId: "vendor-uuid",
74
+ allowedDomains: ["acme.com"],
75
+ autoProvisionRole: "user",
76
+ plan: "enterprise",
77
+ });
78
+ ```
79
+
80
+ ### 3. Update a Tenant
81
+
82
+ ```typescript
83
+ await client.tenants.update(tenantId, {
84
+ name: "Acme Corporation",
85
+ enableScopedAuth: true,
86
+ });
87
+ ```
88
+
89
+ ### 4. Manage Users
90
+
91
+ ```typescript
92
+ const users = await client.tenants.getUsers(tenantId);
93
+
94
+ await client.tenants.inviteUser(tenantId, {
95
+ email: "new@example.com",
96
+ role: "user",
97
+ products: ["iqcapture"],
98
+ });
99
+
100
+ await client.tenants.changeUserRole(tenantId, userId, "tenant_admin");
101
+
102
+ await client.tenants.migrateUser(tenantId, userId, {
103
+ targetTenantId: "new-tenant-uuid",
104
+ role: "user",
105
+ });
106
+
107
+ await client.tenants.removeUser(tenantId, userId);
108
+ ```
109
+
110
+ ### 5. Password and MFA Policies
111
+
112
+ ```typescript
113
+ await client.tenants.updatePasswordPolicy(tenantId, {
114
+ minLength: 12,
115
+ maxAgeDays: 90,
116
+ reuseCount: 5,
117
+ maxFailedAttempts: 5,
118
+ lockoutDurationMinutes: 30,
119
+ });
120
+
121
+ await client.tenants.updateMfaPolicy(tenantId, "tenant_admin", {
122
+ required: true,
123
+ methods: ["totp", "sms"],
124
+ });
125
+ ```
126
+
127
+ ### 6. Public Branding (No Auth)
128
+
129
+ ```typescript
130
+ const vendorBranding = await client.tenants.getPublicBranding({ vendor: "acme" });
131
+ const slugBranding = await client.tenants.getPublicBrandingBySlug("acme-disposal");
132
+ ```
133
+
134
+ ## Error Handling
135
+
136
+ | Error Code | Meaning | Recovery |
137
+ |------------|---------|----------|
138
+ | `NOT_FOUND` | Tenant does not exist | Check tenant ID |
139
+ | `ALREADY_EXISTS` | Tenant slug exists | Use different slug |
140
+ | `INSUFFICIENT_PERMISSIONS` | Caller lacks admin role | Requires `tenant_admin` or `platform_admin` |
141
+ | `VALIDATION_ERROR` | Invalid request data | Check required fields |
142
+
143
+ ```typescript
144
+ import { IQAuthError, ErrorCodes } from "@iqauth/sdk";
145
+
146
+ try {
147
+ await client.tenants.create({ name: "Test", slug: "test" });
148
+ } catch (err) {
149
+ if (err instanceof IQAuthError && err.code === ErrorCodes.ALREADY_EXISTS) {
150
+ console.log("Tenant slug already taken");
151
+ }
152
+ }
153
+ ```
154
+
155
+ ## Complete Example
156
+
157
+ ```typescript
158
+ import { IQAuthClient } from "@iqauth/sdk";
159
+
160
+ const client = new IQAuthClient({
161
+ baseUrl: "https://auth.dispositioniq.com",
162
+ accessToken: platformAdminToken,
163
+ refreshToken: platformAdminRefreshToken,
164
+ });
165
+
166
+ async function setupNewTenant(name: string, slug: string, adminEmail: string) {
167
+ const tenant = await client.tenants.create({
168
+ name,
169
+ slug,
170
+ plan: "standard",
171
+ allowedDomains: [slug + ".com"],
172
+ });
173
+
174
+ await client.tenants.updatePasswordPolicy(tenant.id, {
175
+ minLength: 10,
176
+ maxAgeDays: 90,
177
+ maxFailedAttempts: 5,
178
+ lockoutDurationMinutes: 15,
179
+ });
180
+
181
+ await client.tenants.updateMfaPolicy(tenant.id, "tenant_admin", {
182
+ required: true,
183
+ methods: ["totp"],
184
+ });
185
+
186
+ await client.tenants.inviteUser(tenant.id, {
187
+ email: adminEmail,
188
+ role: "tenant_admin",
189
+ });
190
+
191
+ console.log("Tenant created:", tenant.id, tenant.slug);
192
+ return tenant;
193
+ }
194
+ ```
195
+
196
+ ## API Reference
197
+
198
+ | Method | HTTP | Path |
199
+ |--------|------|------|
200
+ | `get(id)` / `getCurrent(id)` | GET | `/api/v1/tenants/:id` |
201
+ | `list(params?)` | GET | `/api/v1/tenants` |
202
+ | `create(data)` | POST | `/api/v1/tenants` |
203
+ | `update(id, data)` | PATCH | `/api/v1/tenants/:id` |
204
+ | `delete(id)` | DELETE | `/api/v1/tenants/:id` |
205
+ | `promoteToVendor(id, data)` | POST | `/api/v1/tenants/:id/promote-to-vendor` |
206
+ | `getUsers(id)` | GET | `/api/v1/tenants/:id/users` |
207
+ | `inviteUser(id, data)` | POST | `/api/v1/tenants/:id/users/invite` |
208
+ | `changeUserRole(id, userId, role)` | PATCH | `/api/v1/tenants/:id/users/:userId/role` |
209
+ | `migrateUser(id, userId, data)` | POST | `/api/v1/tenants/:id/users/:userId/migrate` |
210
+ | `removeUser(id, userId)` | DELETE | `/api/v1/tenants/:id/users/:userId` |
211
+ | `getPasswordPolicy(id)` | GET | `/api/v1/tenants/:id/password-policy` |
212
+ | `updatePasswordPolicy(id, data)` | PATCH | `/api/v1/tenants/:id/password-policy` |
213
+ | `getMfaPolicies(id)` | GET | `/api/v1/tenants/:id/mfa-policies` |
214
+ | `updateMfaPolicy(id, role, data)` | PATCH | `/api/v1/tenants/:id/mfa-policies/:role` |
215
+ | `getPublicBranding(params?)` | GET | `/api/public/branding` |
216
+ | `getPublicBrandingBySlug(slug)` | GET | `/api/public/branding/by-slug/:slug` |