@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,251 @@
1
+ # Error Handling
2
+
3
+ ## Purpose
4
+
5
+ Handle all SDK errors using `IQAuthError` and `ErrorCodes`. Understand error codes, HTTP status mapping, and implement recovery patterns.
6
+
7
+ ## Prerequisites
8
+
9
+ - `@iqauth/sdk` installed
10
+
11
+ ## Environment Note
12
+
13
+ Refresh-and-retry examples in this guide apply to token-owning runtimes such as servers and native mobile apps with secure storage.
14
+
15
+ For first-party browser apps, the backend should own refresh and session recovery.
16
+
17
+ ## SDK Methods
18
+
19
+ | Export | Description |
20
+ |--------|-------------|
21
+ | `IQAuthError` | Error class thrown by all SDK API calls |
22
+ | `ErrorCodes` | Constant object with all error code strings |
23
+ | `ErrorCode` | TypeScript type for error code values |
24
+
25
+ ### IQAuthError Properties
26
+
27
+ | Property | Type | Description |
28
+ |----------|------|-------------|
29
+ | `code` | `string` | Machine-readable error code |
30
+ | `message` | `string` | Human-readable description |
31
+ | `status` | `number \| undefined` | HTTP status code |
32
+ | `raw` | `unknown` | Full raw response body |
33
+ | `name` | `string` | Always `"IQAuthError"` |
34
+
35
+ ## Step-by-Step
36
+
37
+ ### 1. Basic Error Handling
38
+
39
+ ```typescript
40
+ import { IQAuthError, ErrorCodes } from "@iqauth/sdk";
41
+
42
+ try {
43
+ await client.auth.login(email, password);
44
+ } catch (err) {
45
+ if (err instanceof IQAuthError) {
46
+ console.error(`[${err.code}] ${err.message} (HTTP ${err.status})`);
47
+ }
48
+ }
49
+ ```
50
+
51
+ ### 2. Switch on Error Codes
52
+
53
+ ```typescript
54
+ try {
55
+ await client.auth.login(email, password);
56
+ } catch (err) {
57
+ if (err instanceof IQAuthError) {
58
+ switch (err.code) {
59
+ case ErrorCodes.INVALID_CREDENTIALS:
60
+ showError("Wrong email or password");
61
+ break;
62
+ case ErrorCodes.ACCOUNT_LOCKED:
63
+ showError("Account locked — try again later");
64
+ break;
65
+ case ErrorCodes.ACCOUNT_INACTIVE:
66
+ showError("Account deactivated");
67
+ break;
68
+ case ErrorCodes.PASSWORD_EXPIRED:
69
+ redirectToPasswordChange();
70
+ break;
71
+ default:
72
+ showError("An error occurred");
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ ### 3. Token Error Recovery
79
+
80
+ ```typescript
81
+ try {
82
+ await client.users.getCurrent();
83
+ } catch (err) {
84
+ if (err instanceof IQAuthError) {
85
+ if (err.code === ErrorCodes.TOKEN_EXPIRED) {
86
+ const newTokens = await client.auth.refreshTokens(client.getRefreshToken()!);
87
+ client.setTokens(newTokens);
88
+ return client.users.getCurrent(); // Retry
89
+ }
90
+ if (err.code === ErrorCodes.REFRESH_TOKEN_REUSED) {
91
+ // Security: possible token theft — force re-login
92
+ forceReLogin();
93
+ }
94
+ }
95
+ }
96
+ ```
97
+
98
+ ## Error Handling
99
+
100
+ All SDK API calls throw `IQAuthError` on failure. The error object carries a machine-readable `code`, human-readable `message`, HTTP `status`, and the `raw` response body. Use `ErrorCodes` constants for reliable comparisons.
101
+
102
+ ## Error Codes Reference
103
+
104
+ ### Authentication
105
+
106
+ | Code | HTTP | Description |
107
+ |------|------|-------------|
108
+ | `INVALID_CREDENTIALS` | 401 | Wrong email or password |
109
+ | `ACCOUNT_INACTIVE` | 403 | User account is deactivated |
110
+ | `ACCOUNT_LOCKED` | 403 | Account locked (too many failed attempts) |
111
+ | `AUTH_REQUIRED` | 401 | No authentication provided |
112
+ | `PASSWORD_EXPIRED` | 403 | Password must be changed |
113
+ | `PASSWORD_POLICY_VIOLATION` | 400 | New password doesn't meet policy |
114
+
115
+ ### Tokens
116
+
117
+ | Code | HTTP | Description |
118
+ |------|------|-------------|
119
+ | `TOKEN_INVALID` | 401 | Malformed or fails verification |
120
+ | `TOKEN_EXPIRED` | 401 | `exp` claim is in the past |
121
+ | `TOKEN_REVOKED` | 401 | Token revoked server-side |
122
+ | `REFRESH_TOKEN_REUSED` | 401 | Refresh token reuse (possible theft) |
123
+
124
+ ### Sessions
125
+
126
+ | Code | HTTP | Description |
127
+ |------|------|-------------|
128
+ | `SESSION_INVALID` | 401 | Session does not exist |
129
+ | `SESSION_EXPIRED` | 401 | Session has expired |
130
+
131
+ ### MFA
132
+
133
+ | Code | HTTP | Description |
134
+ |------|------|-------------|
135
+ | `MFA_INVALID_CODE` | 401 | Wrong MFA code |
136
+ | `MFA_METHOD_UNAVAILABLE` | 400 | Method not available |
137
+ | `MFA_RATE_LIMITED` | 429 | Too many attempts |
138
+ | `MFA_ENROLLMENT_REQUIRED` | 403 | MFA must be enrolled |
139
+
140
+ ### Authorization
141
+
142
+ | Code | HTTP | Description |
143
+ |------|------|-------------|
144
+ | `INSUFFICIENT_PERMISSIONS` | 403 | Lacks required role/entitlement |
145
+ | `FORBIDDEN` | 403 | Action not allowed |
146
+ | `USER_INACTIVE` | 403 | User inactive in tenant |
147
+
148
+ ### API Keys
149
+
150
+ | Code | HTTP | Description |
151
+ |------|------|-------------|
152
+ | `API_KEY_REQUIRED` | 401 | Endpoint requires API key |
153
+ | `API_KEY_INVALID` | 401 | Invalid or expired API key |
154
+
155
+ ### Resources
156
+
157
+ | Code | HTTP | Description |
158
+ |------|------|-------------|
159
+ | `NOT_FOUND` | 404 | Resource doesn't exist |
160
+ | `ALREADY_EXISTS` | 409 | Duplicate identifier |
161
+ | `VALIDATION_ERROR` | 400 | Failed request validation |
162
+
163
+ ### PIN
164
+
165
+ | Code | HTTP | Description |
166
+ |------|------|-------------|
167
+ | `PIN_EXPIRED` | 401 | PIN has expired |
168
+
169
+ ### Invitations
170
+
171
+ | Code | HTTP | Description |
172
+ |------|------|-------------|
173
+ | `INVALID_CODE` | 400 | Invalid invite code |
174
+ | `CODE_ALREADY_USED` | 400 | Code already accepted |
175
+ | `CODE_EXPIRED` | 400 | Code has expired |
176
+ | `CODE_IP_MISMATCH` | 403 | IP mismatch |
177
+
178
+ ### External Services
179
+
180
+ | Code | HTTP | Description |
181
+ |------|------|-------------|
182
+ | `OAUTH_NOT_CONFIGURED` | 500 | OAuth not configured |
183
+ | `EMAIL_SERVICE_UNAVAILABLE` | 503 | Email service down |
184
+ | `UPLOAD_ERROR` | 500 | Upload failed |
185
+
186
+ ### Other
187
+
188
+ | Code | HTTP | Description |
189
+ |------|------|-------------|
190
+ | `INTERNAL_ERROR` | 500 | Server-side error |
191
+ | `UNKNOWN_PAYLOAD` | 500 | Unexpected response |
192
+
193
+ ## Complete Example
194
+
195
+ ```typescript
196
+ import { IQAuthClient, IQAuthError, ErrorCodes } from "@iqauth/sdk";
197
+
198
+ const client = new IQAuthClient({
199
+ baseUrl: "https://auth.dispositioniq.com",
200
+ // Trusted runtime example
201
+ accessToken: storedToken,
202
+ refreshToken: storedRefreshToken,
203
+ });
204
+
205
+ async function safeApiCall<T>(fn: () => Promise<T>): Promise<T> {
206
+ try {
207
+ return await fn();
208
+ } catch (err) {
209
+ if (!(err instanceof IQAuthError)) throw err;
210
+
211
+ switch (err.code) {
212
+ case ErrorCodes.TOKEN_EXPIRED: {
213
+ const refreshToken = client.getRefreshToken();
214
+ if (!refreshToken) throw new Error("Session expired — please log in again");
215
+ const newTokens = await client.auth.refreshTokens(refreshToken);
216
+ client.setTokens(newTokens);
217
+ return fn(); // Retry
218
+ }
219
+
220
+ case ErrorCodes.REFRESH_TOKEN_REUSED:
221
+ throw new Error("Session compromised — log in again");
222
+
223
+ case ErrorCodes.INSUFFICIENT_PERMISSIONS:
224
+ throw new Error("You don't have permission for this action");
225
+
226
+ case ErrorCodes.NOT_FOUND:
227
+ throw new Error("The requested resource was not found");
228
+
229
+ default:
230
+ throw err;
231
+ }
232
+ }
233
+ }
234
+
235
+ // Usage
236
+ const user = await safeApiCall(() => client.users.getCurrent());
237
+ ```
238
+
239
+ ## HTTP Response Envelope
240
+
241
+ The server wraps all responses:
242
+
243
+ ```json
244
+ // Success
245
+ { "success": true, "data": { ... } }
246
+
247
+ // Error
248
+ { "success": false, "error": { "code": "...", "message": "..." } }
249
+ ```
250
+
251
+ The SDK automatically unwraps success responses and throws `IQAuthError` for errors.
@@ -0,0 +1,123 @@
1
+ # GDPR Compliance
2
+
3
+ ## Purpose
4
+
5
+ Implement GDPR data subject rights: personal data export (right of access) and account deletion (right to erasure).
6
+
7
+ ## Prerequisites
8
+
9
+ - `@iqauth/sdk` installed
10
+ - Authenticated user (access token set on client)
11
+
12
+ ## Environment Note
13
+
14
+ This guide's token examples assume a trusted runtime or secure mobile storage, not browser-local token ownership for first-party web apps.
15
+
16
+ ## SDK Methods
17
+
18
+ | Module | Method | Description |
19
+ |--------|--------|-------------|
20
+ | `gdpr` | `exportData()` | Export all personal data → `GdprExportData` |
21
+ | `gdpr` | `deleteAccount(confirmEmail)` | Permanently delete account |
22
+
23
+ ## Step-by-Step
24
+
25
+ ### 1. Export Personal Data
26
+
27
+ ```typescript
28
+ const exportData = await client.gdpr.exportData();
29
+ ```
30
+
31
+ Returns `GdprExportData`:
32
+
33
+ ```typescript
34
+ interface GdprExportData {
35
+ exportedAt: string;
36
+ user: Record<string, unknown>;
37
+ memberships: Record<string, unknown>[];
38
+ sessions: Record<string, unknown>[];
39
+ linkedAccounts: Record<string, unknown>[];
40
+ mfaEnrollments: Record<string, unknown>[];
41
+ auditLog: Record<string, unknown>[];
42
+ }
43
+ ```
44
+
45
+ The export includes: user profile, tenant memberships, session history, linked OAuth accounts, MFA enrollments, and audit log entries.
46
+
47
+ ### 2. Delete Account
48
+
49
+ ```typescript
50
+ await client.gdpr.deleteAccount("user@example.com");
51
+ ```
52
+
53
+ The `confirmEmail` must match the authenticated user's email as a confirmation step. This action is **irreversible** — all user data is permanently deleted.
54
+
55
+ ## Error Handling
56
+
57
+ | Error Code | Meaning | Recovery |
58
+ |------------|---------|----------|
59
+ | `TOKEN_INVALID` | Not authenticated | Re-authenticate |
60
+ | `VALIDATION_ERROR` | Email doesn't match | Pass correct email |
61
+ | `INTERNAL_ERROR` | Server error during export/delete | Retry |
62
+
63
+ ```typescript
64
+ import { IQAuthError, ErrorCodes } from "@iqauth/sdk";
65
+
66
+ try {
67
+ await client.gdpr.deleteAccount(email);
68
+ } catch (err) {
69
+ if (err instanceof IQAuthError && err.code === ErrorCodes.VALIDATION_ERROR) {
70
+ console.error("Email confirmation doesn't match your account");
71
+ }
72
+ }
73
+ ```
74
+
75
+ ## Complete Example
76
+
77
+ ```typescript
78
+ import { IQAuthClient, IQAuthError } from "@iqauth/sdk";
79
+
80
+ const client = new IQAuthClient({
81
+ baseUrl: "https://auth.dispositioniq.com",
82
+ accessToken: userToken,
83
+ refreshToken: userRefreshToken,
84
+ });
85
+
86
+ async function handleDataExportRequest() {
87
+ const data = await client.gdpr.exportData();
88
+ console.log("Export generated at:", data.exportedAt);
89
+ console.log("Sessions:", data.sessions.length);
90
+ console.log("Memberships:", data.memberships.length);
91
+ console.log("MFA enrollments:", data.mfaEnrollments.length);
92
+
93
+ // Provide as downloadable JSON
94
+ return JSON.stringify(data, null, 2);
95
+ }
96
+
97
+ async function handleAccountDeletionRequest(userEmail: string) {
98
+ // Confirm with user first (this is irreversible)
99
+ // promptUserConfirmation() is your app's UI function — show a confirmation dialog
100
+ const confirmed = await promptUserConfirmation(
101
+ "This will permanently delete your account and all data. Are you sure?",
102
+ );
103
+ if (!confirmed) return;
104
+
105
+ try {
106
+ await client.gdpr.deleteAccount(userEmail);
107
+ console.log("Account deleted successfully");
108
+ // Redirect to goodbye page
109
+ } catch (err) {
110
+ if (err instanceof IQAuthError) {
111
+ console.error("Deletion failed:", err.message);
112
+ }
113
+ throw err;
114
+ }
115
+ }
116
+ ```
117
+
118
+ ## API Reference
119
+
120
+ | Method | HTTP | Path |
121
+ |--------|------|------|
122
+ | `exportData()` | GET | `/api/v1/gdpr/export` |
123
+ | `deleteAccount(confirmEmail)` | POST | `/api/v1/gdpr/delete` |
@@ -0,0 +1,143 @@
1
+ # Invitations
2
+
3
+ ## Purpose
4
+
5
+ Invite users to join a tenant. Supports both new users (who create an account) and existing users (who join the tenant directly).
6
+
7
+ ## Prerequisites
8
+
9
+ - `@iqauth/sdk` installed
10
+ - `tenant_admin` role for creating invitations
11
+ - No auth required for `validate()` and `accept()`
12
+
13
+ ## Environment Note
14
+
15
+ These administrative examples assume a trusted runtime. First-party browser apps should execute invitation management through a backend cookie session.
16
+
17
+ ## SDK Methods
18
+
19
+ | Module | Method | Description |
20
+ |--------|--------|-------------|
21
+ | `invites` | `create(data)` | Create invitation → `{ invitation, inviteToken?, warning? }` |
22
+ | `invites` | `validate(token)` | Validate invite token → `InviteValidation` |
23
+ | `invites` | `accept(token, data)` | Accept invitation |
24
+
25
+ ## Step-by-Step
26
+
27
+ ### 1. Create an Invitation
28
+
29
+ ```typescript
30
+ const result = await client.invites.create({
31
+ email: "newuser@example.com",
32
+ tenantId: "tenant-uuid",
33
+ role: "user",
34
+ vendorId: "vendor-uuid", // optional
35
+ products: ["iqcapture"], // optional product access
36
+ });
37
+ // result.inviteToken — for programmatic flows
38
+ // In production, user receives email with invite link
39
+ ```
40
+
41
+ ### 2. Validate Token
42
+
43
+ ```typescript
44
+ const validation = await client.invites.validate(inviteToken);
45
+ // { valid: true, email: "user@example.com", role: "user" }
46
+ ```
47
+
48
+ ### 3. Accept Invitation
49
+
50
+ ```typescript
51
+ // New user
52
+ await client.invites.accept(inviteToken, {
53
+ name: "New User",
54
+ password: "securePassword123",
55
+ });
56
+
57
+ // Existing Google user
58
+ await client.invites.accept(inviteToken, {
59
+ googleId: "google-uid-123",
60
+ });
61
+ ```
62
+
63
+ ### Alternative: Tenant-Level Invite
64
+
65
+ ```typescript
66
+ await client.tenants.inviteUser(tenantId, {
67
+ email: "user@example.com",
68
+ role: "user",
69
+ products: ["iqcapture"],
70
+ });
71
+ ```
72
+
73
+ ## Error Handling
74
+
75
+ | Error Code | Meaning | Recovery |
76
+ |------------|---------|----------|
77
+ | `INVALID_CODE` | Invite code is invalid | Check token |
78
+ | `CODE_ALREADY_USED` | Invite already accepted | Create new invitation |
79
+ | `CODE_EXPIRED` | Invite has expired | Create new invitation |
80
+ | `CODE_IP_MISMATCH` | IP doesn't match invite | Security restriction |
81
+ | `ALREADY_EXISTS` | User already in tenant | No action needed |
82
+
83
+ ```typescript
84
+ import { IQAuthError, ErrorCodes } from "@iqauth/sdk";
85
+
86
+ try {
87
+ await client.invites.accept(token, { name: "User", password: "pass" });
88
+ } catch (err) {
89
+ if (err instanceof IQAuthError) {
90
+ if (err.code === ErrorCodes.CODE_EXPIRED) {
91
+ console.error("Invitation has expired — request a new one");
92
+ }
93
+ if (err.code === ErrorCodes.CODE_ALREADY_USED) {
94
+ console.error("This invitation was already used");
95
+ }
96
+ }
97
+ }
98
+ ```
99
+
100
+ ## Complete Example
101
+
102
+ ```typescript
103
+ import { IQAuthClient, IQAuthError, ErrorCodes } from "@iqauth/sdk";
104
+
105
+ const adminClient = new IQAuthClient({
106
+ baseUrl: "https://auth.dispositioniq.com",
107
+ // Trusted runtime example
108
+ accessToken: adminToken,
109
+ refreshToken: adminRefreshToken,
110
+ });
111
+
112
+ async function inviteAndAccept(tenantId: string, email: string) {
113
+ const { invitation, inviteToken } = await adminClient.invites.create({
114
+ email,
115
+ tenantId,
116
+ role: "user",
117
+ products: ["iqcapture"],
118
+ });
119
+ console.log("Invitation created:", invitation.id);
120
+
121
+ // In a real app, the user clicks an email link containing inviteToken
122
+ // For programmatic flows:
123
+
124
+ const validation = await adminClient.invites.validate(inviteToken!);
125
+ if (!validation.valid) {
126
+ throw new Error("Invalid invitation");
127
+ }
128
+
129
+ await adminClient.invites.accept(inviteToken!, {
130
+ name: "New User",
131
+ password: "SecurePass123!",
132
+ });
133
+ console.log("Invitation accepted");
134
+ }
135
+ ```
136
+
137
+ ## API Reference
138
+
139
+ | Method | HTTP | Path |
140
+ |--------|------|------|
141
+ | `create(data)` | POST | `/api/v1/invites` |
142
+ | `validate(token)` | GET | `/api/v1/invites/:token/validate` |
143
+ | `accept(token, data)` | POST | `/api/v1/invites/:token/accept` |
@@ -0,0 +1,170 @@
1
+ # MFA Enrollment
2
+
3
+ ## Purpose
4
+
5
+ Manage multi-factor authentication enrollment for the current user. Supports TOTP (authenticator app), SMS, and email methods, with backup code management.
6
+
7
+ ## Prerequisites
8
+
9
+ - `@iqauth/sdk` installed
10
+ - Authenticated user (access token set on client)
11
+ - For SMS enrollment: a valid phone number
12
+
13
+ ## Environment Note
14
+
15
+ The token-owning examples in this guide fit trusted runtimes or native mobile secure storage. First-party browser apps should drive MFA through backend-managed session endpoints.
16
+
17
+ ## SDK Methods
18
+
19
+ | Module | Method | Description |
20
+ |--------|--------|-------------|
21
+ | `mfa` | `getAvailableMethods()` | Available MFA methods |
22
+ | `mfa` | `enrollTotp()` | Start TOTP enrollment → QR code + secret |
23
+ | `mfa` | `verifyTotpEnrollment(code, secret)` | Verify TOTP → enrollment + backup codes |
24
+ | `mfa` | `enrollSms(phone)` | Start SMS enrollment |
25
+ | `mfa` | `verifySmsEnrollment(code, phone)` | Verify SMS enrollment |
26
+ | `mfa` | `startEmailEnrollment()` | Start email enrollment |
27
+ | `mfa` | `verifyEmailEnrollment(code)` | Verify email enrollment |
28
+ | `mfa` | `listEnrollments()` | List enrollments → `MfaEnrollment[]` |
29
+ | `mfa` | `setPrimaryEnrollment(id)` | Set primary method |
30
+ | `mfa` | `deactivateEnrollment(id)` | Remove enrollment |
31
+ | `mfa` | `regenerateBackupCodes()` | Regenerate backup codes |
32
+ | `mfa` | `getBackupCodeCount()` | Get remaining count |
33
+
34
+ ## Step-by-Step
35
+
36
+ ### 1. Check Available Methods
37
+
38
+ ```typescript
39
+ const { available } = await client.mfa.getAvailableMethods();
40
+ // ["totp", "sms", "email"]
41
+ ```
42
+
43
+ ### 2. TOTP Enrollment
44
+
45
+ ```typescript
46
+ const { secret, qrCodeUri, qrCodeDataUrl } = await client.mfa.enrollTotp();
47
+ // Display qrCodeDataUrl as <img src="..."> for the user to scan
48
+
49
+ const { enrollment, backupCodes, backupCodesWarning } = await client.mfa.verifyTotpEnrollment(
50
+ userEnteredCode,
51
+ secret,
52
+ );
53
+ // Show backupCodes to user — they won't be shown again
54
+ ```
55
+
56
+ ### 3. SMS Enrollment
57
+
58
+ ```typescript
59
+ const { sent } = await client.mfa.enrollSms("+15551234567");
60
+ const { enrollment } = await client.mfa.verifySmsEnrollment(smsCode, "+15551234567");
61
+ ```
62
+
63
+ ### 4. Email Enrollment
64
+
65
+ ```typescript
66
+ const { sent } = await client.mfa.startEmailEnrollment();
67
+ const { enrollment } = await client.mfa.verifyEmailEnrollment(emailCode);
68
+ ```
69
+
70
+ ### 5. Manage Enrollments
71
+
72
+ ```typescript
73
+ const enrollments = await client.mfa.listEnrollments();
74
+ await client.mfa.setPrimaryEnrollment(enrollmentId);
75
+ await client.mfa.deactivateEnrollment(enrollmentId);
76
+ ```
77
+
78
+ ### 6. Backup Codes
79
+
80
+ ```typescript
81
+ const { backupCodes, warning } = await client.mfa.regenerateBackupCodes();
82
+ const { remainingBackupCodes } = await client.mfa.getBackupCodeCount();
83
+ ```
84
+
85
+ ## Error Handling
86
+
87
+ | Error Code | Meaning | Recovery |
88
+ |------------|---------|----------|
89
+ | `MFA_INVALID_CODE` | Wrong verification code | Let user retry |
90
+ | `MFA_METHOD_UNAVAILABLE` | Method not supported | Check available methods |
91
+ | `MFA_RATE_LIMITED` | Too many attempts | Wait and retry |
92
+ | `MFA_ENROLLMENT_REQUIRED` | MFA must be enrolled | Redirect to enrollment |
93
+
94
+ ```typescript
95
+ import { IQAuthError, ErrorCodes } from "@iqauth/sdk";
96
+
97
+ try {
98
+ await client.mfa.verifyTotpEnrollment(code, secret);
99
+ } catch (err) {
100
+ if (err instanceof IQAuthError && err.code === ErrorCodes.MFA_INVALID_CODE) {
101
+ console.error("Wrong code — please try again");
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Complete Example
107
+
108
+ ```typescript
109
+ import { IQAuthClient, IQAuthError, ErrorCodes } from "@iqauth/sdk";
110
+
111
+ const client = new IQAuthClient({
112
+ baseUrl: "https://auth.dispositioniq.com",
113
+ // Trusted runtime example
114
+ accessToken: userToken,
115
+ refreshToken: userRefreshToken,
116
+ });
117
+
118
+ async function setupTotp() {
119
+ const { available } = await client.mfa.getAvailableMethods();
120
+ if (!available.includes("totp")) {
121
+ throw new Error("TOTP not available");
122
+ }
123
+
124
+ const { secret, qrCodeDataUrl } = await client.mfa.enrollTotp();
125
+ console.log("Show this QR code to user:", qrCodeDataUrl);
126
+
127
+ // User scans QR and enters the 6-digit code from their authenticator app
128
+ // promptUserForCode() is your app's UI function — show an input field and return the value
129
+ const code = await promptUserForCode();
130
+
131
+ try {
132
+ const { enrollment, backupCodes } = await client.mfa.verifyTotpEnrollment(code, secret);
133
+ console.log("TOTP enrolled:", enrollment.id);
134
+ console.log("Backup codes (save these!):", backupCodes);
135
+
136
+ await client.mfa.setPrimaryEnrollment(enrollment.id);
137
+ } catch (err) {
138
+ if (err instanceof IQAuthError && err.code === ErrorCodes.MFA_INVALID_CODE) {
139
+ console.error("Invalid code — scan QR again and retry");
140
+ }
141
+ throw err;
142
+ }
143
+ }
144
+
145
+ async function checkBackupCodes() {
146
+ const { remainingBackupCodes } = await client.mfa.getBackupCodeCount();
147
+ if (remainingBackupCodes < 3) {
148
+ console.warn("Low backup codes — consider regenerating");
149
+ const { backupCodes } = await client.mfa.regenerateBackupCodes();
150
+ console.log("New backup codes:", backupCodes);
151
+ }
152
+ }
153
+ ```
154
+
155
+ ## API Reference
156
+
157
+ | Method | HTTP | Path |
158
+ |--------|------|------|
159
+ | `getAvailableMethods()` | GET | `/api/v1/mfa/methods` |
160
+ | `enrollTotp()` | POST | `/api/v1/mfa/enroll/totp` |
161
+ | `verifyTotpEnrollment(code, secret)` | POST | `/api/v1/mfa/enroll/totp/verify` |
162
+ | `enrollSms(phone)` | POST | `/api/v1/mfa/enroll/sms` |
163
+ | `verifySmsEnrollment(code, phone)` | POST | `/api/v1/mfa/enroll/sms/verify` |
164
+ | `startEmailEnrollment()` | POST | `/api/v1/mfa/enroll/email/start` |
165
+ | `verifyEmailEnrollment(code)` | POST | `/api/v1/mfa/enroll/email/verify` |
166
+ | `listEnrollments()` | GET | `/api/v1/mfa/enrollments` |
167
+ | `setPrimaryEnrollment(id)` | PATCH | `/api/v1/mfa/enrollments/:id/primary` |
168
+ | `deactivateEnrollment(id)` | DELETE | `/api/v1/mfa/enrollments/:id` |
169
+ | `regenerateBackupCodes()` | POST | `/api/v1/mfa/backup-codes/regenerate` |
170
+ | `getBackupCodeCount()` | GET | `/api/v1/mfa/backup-codes/count` |