@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.
- package/LICENSE +21 -0
- package/README.md +287 -0
- package/dist/browser-session.d.mts +12 -0
- package/dist/browser-session.d.ts +12 -0
- package/dist/browser-session.js +1812 -0
- package/dist/browser-session.mjs +28 -0
- package/dist/browser.d.mts +46 -0
- package/dist/browser.d.ts +46 -0
- package/dist/browser.js +768 -0
- package/dist/browser.mjs +47 -0
- package/dist/chunk-5HF3OBNO.mjs +189 -0
- package/dist/chunk-5WFR6Y33.mjs +59 -0
- package/dist/chunk-6I6RM4MN.mjs +51 -0
- package/dist/chunk-73R6BEGO.mjs +176 -0
- package/dist/chunk-E46DKOVI.mjs +632 -0
- package/dist/chunk-JQWYIIIS.mjs +1740 -0
- package/dist/chunk-X3K3WOBR.mjs +64 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +581 -0
- package/dist/cli/index.mjs +57 -0
- package/dist/client-C1DXfB8Z.d.mts +911 -0
- package/dist/client-CggvJmmm.d.ts +911 -0
- package/dist/dev-FUTJZSWN.mjs +56 -0
- package/dist/doctor-OHJRZBBT.mjs +89 -0
- package/dist/errors-CDdl24MP.d.mts +52 -0
- package/dist/errors-CDdl24MP.d.ts +52 -0
- package/dist/express-BKAXB5Nl.d.ts +61 -0
- package/dist/express-CpfyYTmw.d.mts +61 -0
- package/dist/express.d.mts +45 -0
- package/dist/express.d.ts +45 -0
- package/dist/express.js +2252 -0
- package/dist/express.mjs +122 -0
- package/dist/fastify.d.mts +23 -0
- package/dist/fastify.d.ts +23 -0
- package/dist/fastify.js +2062 -0
- package/dist/fastify.mjs +118 -0
- package/dist/hono.d.mts +22 -0
- package/dist/hono.d.ts +22 -0
- package/dist/hono.js +2051 -0
- package/dist/hono.mjs +107 -0
- package/dist/index.d.mts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +2070 -0
- package/dist/index.mjs +83 -0
- package/dist/init-LLCSQGNL.mjs +198 -0
- package/dist/keys-NLWFAOEM.mjs +63 -0
- package/dist/mobile.d.mts +11 -0
- package/dist/mobile.d.ts +11 -0
- package/dist/mobile.js +1809 -0
- package/dist/mobile.mjs +25 -0
- package/dist/next.d.mts +37 -0
- package/dist/next.d.ts +37 -0
- package/dist/next.js +2078 -0
- package/dist/next.mjs +130 -0
- package/dist/publishableKey-B5DIK81A.d.mts +24 -0
- package/dist/publishableKey-B5DIK81A.d.ts +24 -0
- package/dist/react.d.mts +196 -0
- package/dist/react.d.ts +196 -0
- package/dist/react.js +1457 -0
- package/dist/react.mjs +787 -0
- package/dist/server/handlers.d.mts +96 -0
- package/dist/server/handlers.d.ts +96 -0
- package/dist/server/handlers.js +243 -0
- package/dist/server/handlers.mjs +14 -0
- package/dist/server.d.mts +14 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.js +2195 -0
- package/dist/server.mjs +47 -0
- package/dist/service.d.mts +11 -0
- package/dist/service.d.ts +11 -0
- package/dist/service.js +1809 -0
- package/dist/service.mjs +25 -0
- package/dist/signIn-C8f6qVjD.d.mts +238 -0
- package/dist/signIn-Cy2lbEXb.d.ts +238 -0
- package/dist/types-Cxl3bQHt.d.mts +900 -0
- package/dist/types-Cxl3bQHt.d.ts +900 -0
- package/docs/APP_INTEGRATION_MATRIX.md +59 -0
- package/docs/BROWSER_SESSION_MIGRATION.md +69 -0
- package/docs/FRESH_IMPLEMENTATION_GUIDE.md +188 -0
- package/docs/TARBALL_RELEASE_WORKFLOW.md +98 -0
- package/docs/V1_TO_V2_UPGRADE_GUIDE.md +318 -0
- package/docs/guides/api-keys.md +130 -0
- package/docs/guides/app-registration.md +149 -0
- package/docs/guides/auth-flows.md +168 -0
- package/docs/guides/branding.md +160 -0
- package/docs/guides/entitlements.md +115 -0
- package/docs/guides/entity-hierarchy.md +200 -0
- package/docs/guides/error-handling.md +251 -0
- package/docs/guides/gdpr-compliance.md +123 -0
- package/docs/guides/invitations.md +143 -0
- package/docs/guides/mfa-enrollment.md +170 -0
- package/docs/guides/middleware-reference.md +205 -0
- package/docs/guides/mobile-native.md +110 -0
- package/docs/guides/roles-and-permissions.md +220 -0
- package/docs/guides/scoped-authorization.md +247 -0
- package/docs/guides/server-platform-integration.md +52 -0
- package/docs/guides/service-automation-integration.md +36 -0
- package/docs/guides/session-management.md +97 -0
- package/docs/guides/tenant-management.md +216 -0
- package/docs/guides/token-verification.md +178 -0
- package/docs/guides/user-management.md +184 -0
- package/docs/guides/webhooks.md +136 -0
- package/docs/integration-prompts/README.md +20 -0
- package/docs/integration-prompts/first-party-browser-app.md +29 -0
- package/docs/integration-prompts/install-from-tarball.md +41 -0
- package/docs/integration-prompts/migrate-from-local-packages-source.md +57 -0
- package/docs/integration-prompts/native-mobile-app.md +24 -0
- package/docs/integration-prompts/server-platform-app.md +20 -0
- package/docs/integration-prompts/service-automation-app.md +20 -0
- package/package.json +115 -0
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Token Verification
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Verify JWT access tokens using RS256 with JWKS, decode tokens without verification, and check expiry. This is the recommended approach for server-side token validation.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- `@iqauth/sdk` installed
|
|
10
|
+
- IQAuthService base URL (JWKS is fetched from `/.well-known/jwks.json`)
|
|
11
|
+
- A valid JWT access token to verify
|
|
12
|
+
|
|
13
|
+
## SDK Methods
|
|
14
|
+
|
|
15
|
+
| Module | Method | Description |
|
|
16
|
+
|--------|--------|-------------|
|
|
17
|
+
| `tokens` | `verify(token)` | Verify JWT via RS256/JWKS → `JwtClaims` |
|
|
18
|
+
| `tokens` | `decode(token)` | Decode without verification → `JwtClaims \| null` |
|
|
19
|
+
| `tokens` | `getClaims(token)` | Decode or throw → `JwtClaims` |
|
|
20
|
+
| `tokens` | `isExpired(token)` | Check `exp` claim → `boolean` |
|
|
21
|
+
| `tokens` | `clearCache()` | Clear JWKS cache (testing only) |
|
|
22
|
+
|
|
23
|
+
## Step-by-Step
|
|
24
|
+
|
|
25
|
+
### 1. Verify a Token (Server-Side)
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
const claims = await client.tokens.verify(bearerToken);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Verification performs:
|
|
32
|
+
1. Decodes the JWT header to extract the `kid` (key ID)
|
|
33
|
+
2. Fetches the public key from JWKS (cached for 1 hour)
|
|
34
|
+
3. Verifies the signature using RS256
|
|
35
|
+
4. Validates the issuer (`auth.dispositioniq.com`)
|
|
36
|
+
5. Validates the audience (`dispositioniq`, `iqcapture`, `iqreuse`, `iqvalidate`)
|
|
37
|
+
6. Checks expiration
|
|
38
|
+
|
|
39
|
+
### 2. Decode Without Verification (Client-Side)
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
const claims = client.tokens.decode(token);
|
|
43
|
+
// Returns JwtClaims | null (null if malformed)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 3. Check Expiry
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
if (client.tokens.isExpired(token)) {
|
|
50
|
+
// Refresh or re-authenticate
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### JWT Claims Structure
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
interface JwtClaims {
|
|
58
|
+
sub: string; // User ID
|
|
59
|
+
email: string; // User email
|
|
60
|
+
name: string; // Display name
|
|
61
|
+
tenantId: string; // Current tenant
|
|
62
|
+
vendorId: string | null; // Vendor ID (if vendor user)
|
|
63
|
+
roles: string[]; // Flat roles array
|
|
64
|
+
entitlements: string[]; // Product entitlements
|
|
65
|
+
sessionId: string; // Current session ID
|
|
66
|
+
jti: string; // Unique token ID
|
|
67
|
+
iss: string; // "auth.dispositioniq.com"
|
|
68
|
+
aud: string[]; // Audience array
|
|
69
|
+
exp?: number; // Expiration (Unix timestamp)
|
|
70
|
+
iat?: number; // Issued at (Unix timestamp)
|
|
71
|
+
scopeContext?: ScopeContext; // V2 scope context
|
|
72
|
+
loginMethod?: string; // "password" | "pin" | "google"
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### JWKS Caching
|
|
77
|
+
|
|
78
|
+
- Keys are cached for 1 hour (matches server's `Cache-Control: max-age=3600`)
|
|
79
|
+
- On unknown `kid`, the cache is refreshed once and retried (handles key rotation)
|
|
80
|
+
|
|
81
|
+
## Error Handling
|
|
82
|
+
|
|
83
|
+
| Error Code | Meaning | Recovery |
|
|
84
|
+
|------------|---------|----------|
|
|
85
|
+
| `TOKEN_INVALID` | Malformed, missing `kid`, or signature failure | Re-authenticate |
|
|
86
|
+
| `TOKEN_EXPIRED` | `exp` claim is in the past | Refresh token |
|
|
87
|
+
| `INTERNAL_ERROR` | Failed to fetch JWKS endpoint | Retry or check connectivity |
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
import { IQAuthError } from "@iqauth/sdk";
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const claims = await client.tokens.verify(token);
|
|
94
|
+
} catch (err) {
|
|
95
|
+
if (err instanceof IQAuthError) {
|
|
96
|
+
if (err.code === "TOKEN_EXPIRED") {
|
|
97
|
+
// Attempt refresh
|
|
98
|
+
}
|
|
99
|
+
if (err.code === "TOKEN_INVALID") {
|
|
100
|
+
// Re-authenticate
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Middleware Setup
|
|
107
|
+
|
|
108
|
+
The recommended way to verify tokens in Express is via `iqAuthMiddleware`, which wraps token verification:
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import express from "express";
|
|
112
|
+
import { createServerClient } from "@iqauth/sdk/server";
|
|
113
|
+
|
|
114
|
+
const app = express();
|
|
115
|
+
const client = createServerClient({
|
|
116
|
+
baseUrl: "https://auth.dispositioniq.com",
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
// Protect all /api routes — verifies Bearer token via JWKS
|
|
120
|
+
app.use("/api", client.middleware());
|
|
121
|
+
|
|
122
|
+
// Access verified claims via req.auth
|
|
123
|
+
app.get("/api/me", (req, res) => {
|
|
124
|
+
res.json({
|
|
125
|
+
userId: req.auth!.sub,
|
|
126
|
+
email: req.auth!.email,
|
|
127
|
+
roles: req.auth!.roles,
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
For more middleware options (optional auth, role/entitlement checks, custom error handlers), see [Middleware Reference](./middleware-reference.md).
|
|
133
|
+
|
|
134
|
+
## Complete Example
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
import express from "express";
|
|
138
|
+
import { createServerClient } from "@iqauth/sdk/server";
|
|
139
|
+
import { IQAuthError } from "@iqauth/sdk";
|
|
140
|
+
|
|
141
|
+
const client = createServerClient({
|
|
142
|
+
baseUrl: "https://auth.dispositioniq.com",
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// Option 1: Middleware (recommended for Express apps)
|
|
146
|
+
const app = express();
|
|
147
|
+
app.use("/api", client.middleware());
|
|
148
|
+
app.get("/api/me", (req, res) => res.json(req.auth));
|
|
149
|
+
|
|
150
|
+
// Option 2: Manual verification (for non-Express or custom flows)
|
|
151
|
+
async function validateRequest(authHeader: string): Promise<{
|
|
152
|
+
valid: boolean;
|
|
153
|
+
claims?: import("@iqauth/sdk").JwtClaims;
|
|
154
|
+
error?: string;
|
|
155
|
+
}> {
|
|
156
|
+
if (!authHeader?.startsWith("Bearer ")) {
|
|
157
|
+
return { valid: false, error: "Missing Bearer token" };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const token = authHeader.substring(7);
|
|
161
|
+
|
|
162
|
+
try {
|
|
163
|
+
const claims = await client.tokens.verify(token);
|
|
164
|
+
return { valid: true, claims };
|
|
165
|
+
} catch (err) {
|
|
166
|
+
if (err instanceof IQAuthError) {
|
|
167
|
+
return { valid: false, error: `${err.code}: ${err.message}` };
|
|
168
|
+
}
|
|
169
|
+
return { valid: false, error: "Unknown verification error" };
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Option 3: Client-side decode (no verification)
|
|
174
|
+
function getUserIdFromToken(token: string): string | null {
|
|
175
|
+
const claims = client.tokens.decode(token);
|
|
176
|
+
return claims?.sub ?? null;
|
|
177
|
+
}
|
|
178
|
+
```
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# User Management
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Manage user profiles, provision new users, and handle account lifecycle (deactivation, reactivation, unlock).
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- `@iqauth/sdk` installed
|
|
10
|
+
- Authenticated user with appropriate role:
|
|
11
|
+
- Any authenticated user: `getCurrent()`, `update()`, `updatePassword()`
|
|
12
|
+
- `tenant_admin`: `list()`, `deactivate()`, `reactivate()`, `unlock()`
|
|
13
|
+
- `platform_admin` or admin API key: `create()`
|
|
14
|
+
|
|
15
|
+
## Environment Note
|
|
16
|
+
|
|
17
|
+
The token examples in this guide are for trusted runtimes.
|
|
18
|
+
|
|
19
|
+
If your UI is a first-party browser app, call these operations through your backend session layer instead of storing raw tokens in browser-readable storage.
|
|
20
|
+
|
|
21
|
+
## SDK Methods
|
|
22
|
+
|
|
23
|
+
| Module | Method | Description |
|
|
24
|
+
|--------|--------|-------------|
|
|
25
|
+
| `users` | `getCurrent()` | Get current user profile |
|
|
26
|
+
| `users` | `getById(userId)` | Get user by ID |
|
|
27
|
+
| `users` | `list(params?)` | List users (filterable) |
|
|
28
|
+
| `users` | `create(tenantId, data)` | Provision a new user |
|
|
29
|
+
| `users` | `update(data)` | Update current user profile |
|
|
30
|
+
| `users` | `deactivate(userId)` | Deactivate a user |
|
|
31
|
+
| `users` | `reactivate(userId)` | Reactivate a user |
|
|
32
|
+
| `users` | `unlock(userId)` | Unlock a locked account |
|
|
33
|
+
| `users` | `getPermissions(userId, product)` | Get effective permissions |
|
|
34
|
+
| `users` | `updatePassword(current, new)` | Change password |
|
|
35
|
+
|
|
36
|
+
## Step-by-Step
|
|
37
|
+
|
|
38
|
+
### 1. Get Current User
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
const me = await client.users.getCurrent();
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Returns `UserProfile`:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
interface UserProfile {
|
|
48
|
+
id: string;
|
|
49
|
+
email: string;
|
|
50
|
+
name: string;
|
|
51
|
+
picture?: string | null;
|
|
52
|
+
isActive: boolean;
|
|
53
|
+
tenantId?: string;
|
|
54
|
+
vendorId?: string | null;
|
|
55
|
+
roles?: string[];
|
|
56
|
+
isDemoAccount?: boolean;
|
|
57
|
+
createdAt?: string;
|
|
58
|
+
updatedAt?: string;
|
|
59
|
+
mustChangePassword?: boolean;
|
|
60
|
+
passwordExpiresAt?: string | null;
|
|
61
|
+
externalId?: string | null;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 2. List Users
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const allUsers = await client.users.list();
|
|
69
|
+
const tenantUsers = await client.users.list({ tenantId: "tenant-uuid" });
|
|
70
|
+
const filtered = await client.users.list({ email: "user@example.com" });
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 3. Provision a New User
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const result = await client.users.create("tenant-uuid", {
|
|
77
|
+
email: "newuser@example.com",
|
|
78
|
+
name: "New User",
|
|
79
|
+
role: "user",
|
|
80
|
+
vendorId: "vendor-uuid",
|
|
81
|
+
isActive: true,
|
|
82
|
+
externalId: "ext-123",
|
|
83
|
+
});
|
|
84
|
+
// { userId, email, tenantId, role, vendorId }
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Note: `create()` requires a `tenantId` because user provisioning is tenant-scoped. See [DECISION-003](../../DECISIONS.md).
|
|
88
|
+
|
|
89
|
+
### 4. Update Profile
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
await client.users.update({ name: "New Name", picture: "https://example.com/avatar.jpg" });
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 5. Account Lifecycle
|
|
96
|
+
|
|
97
|
+
```typescript
|
|
98
|
+
await client.users.deactivate("user-uuid"); // Soft disable
|
|
99
|
+
await client.users.reactivate("user-uuid"); // Re-enable
|
|
100
|
+
await client.users.unlock("user-uuid"); // Unlock after lockout
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### 6. Change Password
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
await client.users.updatePassword("currentPassword", "newPassword");
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Note: There is no `users.delete()` method. Use `client.tenants.removeUser()` or `users.deactivate()`. See [DECISION-004](../../DECISIONS.md).
|
|
110
|
+
|
|
111
|
+
## Error Handling
|
|
112
|
+
|
|
113
|
+
| Error Code | Meaning | Recovery |
|
|
114
|
+
|------------|---------|----------|
|
|
115
|
+
| `NOT_FOUND` | User does not exist | Check user ID |
|
|
116
|
+
| `INSUFFICIENT_PERMISSIONS` | Caller lacks admin role | Requires `tenant_admin` |
|
|
117
|
+
| `ALREADY_EXISTS` | Email already registered | Use different email |
|
|
118
|
+
| `VALIDATION_ERROR` | Invalid request data | Check required fields |
|
|
119
|
+
| `PASSWORD_POLICY_VIOLATION` | Password doesn't meet policy | Strengthen password |
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { IQAuthError, ErrorCodes } from "@iqauth/sdk";
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
await client.users.create(tenantId, { email, name, role: "user" });
|
|
126
|
+
} catch (err) {
|
|
127
|
+
if (err instanceof IQAuthError && err.code === ErrorCodes.ALREADY_EXISTS) {
|
|
128
|
+
console.log("User already exists with this email");
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## Complete Example
|
|
134
|
+
|
|
135
|
+
```typescript
|
|
136
|
+
import { IQAuthClient, IQAuthError, ErrorCodes } from "@iqauth/sdk";
|
|
137
|
+
|
|
138
|
+
const client = new IQAuthClient({
|
|
139
|
+
baseUrl: "https://auth.dispositioniq.com",
|
|
140
|
+
// Trusted runtime example
|
|
141
|
+
accessToken: adminAccessToken,
|
|
142
|
+
refreshToken: adminRefreshToken,
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
async function provisionAndSetupUser(tenantId: string, email: string, name: string) {
|
|
146
|
+
try {
|
|
147
|
+
const result = await client.users.create(tenantId, {
|
|
148
|
+
email,
|
|
149
|
+
name,
|
|
150
|
+
role: "user",
|
|
151
|
+
isActive: true,
|
|
152
|
+
});
|
|
153
|
+
console.log("Provisioned user:", result.userId);
|
|
154
|
+
|
|
155
|
+
const user = await client.users.getById(result.userId);
|
|
156
|
+
console.log("User profile:", user.name, user.email, user.isActive);
|
|
157
|
+
|
|
158
|
+
return result;
|
|
159
|
+
} catch (err) {
|
|
160
|
+
if (err instanceof IQAuthError) {
|
|
161
|
+
if (err.code === ErrorCodes.ALREADY_EXISTS) {
|
|
162
|
+
const users = await client.users.list({ email });
|
|
163
|
+
return { userId: users[0].id, email, tenantId, role: "user", vendorId: null };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
throw err;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## API Reference
|
|
172
|
+
|
|
173
|
+
| Method | HTTP | Path |
|
|
174
|
+
|--------|------|------|
|
|
175
|
+
| `getCurrent()` | GET | `/api/v1/users/me` |
|
|
176
|
+
| `getById(id)` | GET | `/api/v1/users/:id` |
|
|
177
|
+
| `list(params?)` | GET | `/api/v1/users` |
|
|
178
|
+
| `create(tenantId, data)` | POST | `/api/v1/tenants/:tenantId/users/provision` |
|
|
179
|
+
| `update(data)` | PATCH | `/api/v1/users/me` |
|
|
180
|
+
| `deactivate(id)` | PATCH | `/api/v1/users/:id/deactivate` |
|
|
181
|
+
| `reactivate(id)` | PATCH | `/api/v1/users/:id/reactivate` |
|
|
182
|
+
| `unlock(id)` | PATCH | `/api/v1/users/:id/unlock` |
|
|
183
|
+
| `getPermissions(id, product)` | GET | `/api/v1/users/:id/permissions?product=...` |
|
|
184
|
+
| `updatePassword(current, new)` | POST | `/api/v1/auth/password/change` |
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Webhooks
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Subscribe to IQAuth events and receive HTTP callbacks. Manage webhook endpoints, test connectivity, view delivery history, and rotate signing secrets.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- `@iqauth/sdk` installed
|
|
10
|
+
- `tenant_admin` role for webhook management
|
|
11
|
+
- A publicly accessible HTTPS URL to receive webhook events
|
|
12
|
+
|
|
13
|
+
## Environment Note
|
|
14
|
+
|
|
15
|
+
The management examples in this guide assume a trusted backend, admin script, or other secure runtime.
|
|
16
|
+
|
|
17
|
+
For first-party browser apps, keep webhook administration behind your backend session model.
|
|
18
|
+
|
|
19
|
+
## SDK Methods
|
|
20
|
+
|
|
21
|
+
| Module | Method | Description |
|
|
22
|
+
|--------|--------|-------------|
|
|
23
|
+
| `webhooks` | `createEndpoint(data)` | Create endpoint → `{ endpoint, secret }` |
|
|
24
|
+
| `webhooks` | `listEndpoints()` | List endpoints → `WebhookEndpoint[]` |
|
|
25
|
+
| `webhooks` | `deleteEndpoint(id)` | Delete endpoint |
|
|
26
|
+
| `webhooks` | `getDeliveries(endpointId)` | Delivery history → `WebhookDelivery[]` |
|
|
27
|
+
| `webhooks` | `testEndpoint(id)` | Send test event → `WebhookTestResult` |
|
|
28
|
+
| `webhooks` | `rotateSecret(id)` | Rotate signing secret → `{ newSecret }` |
|
|
29
|
+
|
|
30
|
+
## Step-by-Step
|
|
31
|
+
|
|
32
|
+
### 1. Create Endpoint
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
const { endpoint, secret } = await client.webhooks.createEndpoint({
|
|
36
|
+
url: "https://myapp.com/webhooks/iqauth",
|
|
37
|
+
events: ["user.created", "user.deactivated", "session.created"],
|
|
38
|
+
});
|
|
39
|
+
// IMPORTANT: Store secret securely — used to verify webhook signatures
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Test Endpoint
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const result = await client.webhooks.testEndpoint(endpoint.id);
|
|
46
|
+
console.log("Test status:", result.statusCode); // 200
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### 3. View Deliveries
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const deliveries = await client.webhooks.getDeliveries(endpoint.id);
|
|
53
|
+
for (const d of deliveries) {
|
|
54
|
+
console.log(`${d.event} → ${d.statusCode} at ${d.deliveredAt}`);
|
|
55
|
+
}
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 4. Rotate Secret
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
const { newSecret } = await client.webhooks.rotateSecret(endpoint.id);
|
|
62
|
+
// Update your webhook handler with the new secret
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### 5. List / Delete
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
const endpoints = await client.webhooks.listEndpoints();
|
|
69
|
+
await client.webhooks.deleteEndpoint(endpoint.id);
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Error Handling
|
|
73
|
+
|
|
74
|
+
| Error Code | Meaning | Recovery |
|
|
75
|
+
|------------|---------|----------|
|
|
76
|
+
| `NOT_FOUND` | Endpoint doesn't exist | Check endpoint ID |
|
|
77
|
+
| `VALIDATION_ERROR` | Invalid URL or events | Check request format |
|
|
78
|
+
| `INSUFFICIENT_PERMISSIONS` | Caller lacks admin role | Requires `tenant_admin`+ |
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
import { IQAuthError, ErrorCodes } from "@iqauth/sdk";
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
await client.webhooks.createEndpoint({ url, events });
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (err instanceof IQAuthError && err.code === ErrorCodes.VALIDATION_ERROR) {
|
|
87
|
+
console.error("Invalid webhook URL or events:", err.message);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Complete Example
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
import { IQAuthClient } from "@iqauth/sdk";
|
|
96
|
+
|
|
97
|
+
const client = new IQAuthClient({
|
|
98
|
+
baseUrl: "https://auth.dispositioniq.com",
|
|
99
|
+
// Trusted runtime example
|
|
100
|
+
accessToken: adminToken,
|
|
101
|
+
refreshToken: adminRefreshToken,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
async function setupWebhooks(callbackUrl: string) {
|
|
105
|
+
const { endpoint, secret } = await client.webhooks.createEndpoint({
|
|
106
|
+
url: callbackUrl,
|
|
107
|
+
events: ["user.created", "user.deactivated", "session.created", "session.terminated"],
|
|
108
|
+
});
|
|
109
|
+
console.log("Webhook endpoint:", endpoint.id);
|
|
110
|
+
console.log("Secret:", secret); // Store securely!
|
|
111
|
+
|
|
112
|
+
const testResult = await client.webhooks.testEndpoint(endpoint.id);
|
|
113
|
+
if (testResult.statusCode !== 200) {
|
|
114
|
+
console.error("Webhook test failed:", testResult.statusCode);
|
|
115
|
+
await client.webhooks.deleteEndpoint(endpoint.id);
|
|
116
|
+
throw new Error("Webhook endpoint not responding correctly");
|
|
117
|
+
}
|
|
118
|
+
console.log("Webhook test passed");
|
|
119
|
+
|
|
120
|
+
const deliveries = await client.webhooks.getDeliveries(endpoint.id);
|
|
121
|
+
console.log(`${deliveries.length} deliveries logged`);
|
|
122
|
+
|
|
123
|
+
return { endpointId: endpoint.id, secret };
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## API Reference
|
|
128
|
+
|
|
129
|
+
| Method | HTTP | Path |
|
|
130
|
+
|--------|------|------|
|
|
131
|
+
| `createEndpoint(data)` | POST | `/api/v1/webhooks/endpoints` |
|
|
132
|
+
| `listEndpoints()` | GET | `/api/v1/webhooks/endpoints` |
|
|
133
|
+
| `deleteEndpoint(id)` | DELETE | `/api/v1/webhooks/endpoints/:id` |
|
|
134
|
+
| `getDeliveries(endpointId)` | GET | `/api/v1/webhooks/deliveries?endpointId=...` |
|
|
135
|
+
| `testEndpoint(id)` | POST | `/api/v1/webhooks/endpoints/:id/test` |
|
|
136
|
+
| `rotateSecret(id)` | POST | `/api/v1/webhooks/endpoints/:id/rotate-secret` |
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Integration Prompts
|
|
2
|
+
|
|
3
|
+
These prompts are intended for downstream application teams adopting `@iqauth/sdk`.
|
|
4
|
+
|
|
5
|
+
Start here if the SDK is being distributed as a `.tgz`:
|
|
6
|
+
|
|
7
|
+
- [Install from Tarball](./install-from-tarball.md)
|
|
8
|
+
- [Migrate from Local packages/ Source](./migrate-from-local-packages-source.md)
|
|
9
|
+
|
|
10
|
+
Choose the prompt that matches the application environment:
|
|
11
|
+
|
|
12
|
+
- [First-Party Browser App](./first-party-browser-app.md)
|
|
13
|
+
- [Native Mobile App](./native-mobile-app.md)
|
|
14
|
+
- [Server Platform App](./server-platform-app.md)
|
|
15
|
+
- [Service Automation App](./service-automation-app.md)
|
|
16
|
+
|
|
17
|
+
Use these prompts together with:
|
|
18
|
+
|
|
19
|
+
- [App Integration Matrix](../APP_INTEGRATION_MATRIX.md)
|
|
20
|
+
- [Fresh Implementation Guide](../FRESH_IMPLEMENTATION_GUIDE.md)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Prompt: First-Party Browser App
|
|
2
|
+
|
|
3
|
+
Use this prompt when updating a first-party web app that integrates with IQAuth.
|
|
4
|
+
|
|
5
|
+
```md
|
|
6
|
+
You are integrating a first-party browser application with IQAuth.
|
|
7
|
+
|
|
8
|
+
Requirements:
|
|
9
|
+
- Use a backend-managed cookie session, not browser-owned durable tokens.
|
|
10
|
+
- The browser must not store refresh tokens in localStorage, sessionStorage, or other durable browser-readable storage.
|
|
11
|
+
- The backend must own:
|
|
12
|
+
- POST /auth/login
|
|
13
|
+
- POST /auth/select-tenant
|
|
14
|
+
- POST /auth/mfa/verify
|
|
15
|
+
- POST /auth/mfa/verify-backup
|
|
16
|
+
- POST /auth/refresh
|
|
17
|
+
- POST /auth/logout
|
|
18
|
+
- GET /auth/me
|
|
19
|
+
- On frontend boot, restore auth state from /auth/me.
|
|
20
|
+
- Treat revoked, inactive, and expired sessions as terminal signed-out transitions.
|
|
21
|
+
- If SDK usage is needed in the browser/session-aware layer, use @iqauth/sdk/browser-session.
|
|
22
|
+
- Do not normalize token-owning browser patterns in examples or implementation.
|
|
23
|
+
|
|
24
|
+
Deliverables:
|
|
25
|
+
- backend auth proxy routes
|
|
26
|
+
- frontend auth/session bootstrap
|
|
27
|
+
- logout/session invalidation handling
|
|
28
|
+
- short verification checklist
|
|
29
|
+
```
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Prompt: Install from Tarball
|
|
2
|
+
|
|
3
|
+
Use this prompt when a downstream app needs to install or update `@iqauth/sdk` from a `.tgz` release artifact.
|
|
4
|
+
|
|
5
|
+
```md
|
|
6
|
+
You are installing @iqauth/sdk into an application from a versioned .tgz tarball.
|
|
7
|
+
|
|
8
|
+
Inputs:
|
|
9
|
+
- tarball path or filename
|
|
10
|
+
- previous installed SDK version, if any
|
|
11
|
+
- app type: first-party browser, native mobile, server platform, or service automation
|
|
12
|
+
|
|
13
|
+
Requirements:
|
|
14
|
+
- Install the tarball as a package dependency, not by copying SDK source files into the app.
|
|
15
|
+
- Preserve a reproducible dependency reference in package.json and/or the lockfile.
|
|
16
|
+
- Determine whether the installed SDK version changed.
|
|
17
|
+
|
|
18
|
+
Install step:
|
|
19
|
+
- use npm install <tarball-path>
|
|
20
|
+
|
|
21
|
+
After install:
|
|
22
|
+
- if the version changed, continue immediately into the environment-specific setup prompt
|
|
23
|
+
- if the app type is first-party browser, use the first-party browser setup prompt
|
|
24
|
+
- if the app type is native mobile, use the native mobile setup prompt
|
|
25
|
+
- if the app type is server platform, use the server platform setup prompt
|
|
26
|
+
- if the app type is service automation, use the service automation setup prompt
|
|
27
|
+
|
|
28
|
+
Do not stop at “package installed” if the version changed.
|
|
29
|
+
|
|
30
|
+
Always verify:
|
|
31
|
+
- the app is using the correct SDK entry point
|
|
32
|
+
- the app still matches the supported auth model for its environment
|
|
33
|
+
- any old browser token-owning patterns are removed if this is a first-party web app
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Next Prompt by App Type
|
|
37
|
+
|
|
38
|
+
- [First-Party Browser App](./first-party-browser-app.md)
|
|
39
|
+
- [Native Mobile App](./native-mobile-app.md)
|
|
40
|
+
- [Server Platform App](./server-platform-app.md)
|
|
41
|
+
- [Service Automation App](./service-automation-app.md)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Prompt: Migrate from Local `packages/` Source
|
|
2
|
+
|
|
3
|
+
Use this prompt when an application currently consumes `@iqauth/sdk` from a checked-in local `packages/` source folder and needs to move to the tarball-based distribution model.
|
|
4
|
+
|
|
5
|
+
```md
|
|
6
|
+
You are migrating an application away from a local checked-in `packages/` source dependency for `@iqauth/sdk`.
|
|
7
|
+
|
|
8
|
+
Current state:
|
|
9
|
+
- the app currently resolves the SDK from a local `packages/` folder or copied source tree
|
|
10
|
+
|
|
11
|
+
Target state:
|
|
12
|
+
- the app installs a versioned `.tgz` release artifact instead
|
|
13
|
+
- the tarball lives in a committed repo path such as `vendor/iq-auth-sdk-x.y.z.tgz`
|
|
14
|
+
- the dependency is reproducible in local development, CI, and deployment
|
|
15
|
+
|
|
16
|
+
Requirements:
|
|
17
|
+
- remove the local source-based dependency on `packages/`
|
|
18
|
+
- install the SDK from a `.tgz` artifact instead of copied source files
|
|
19
|
+
- preserve a clean dependency reference in `package.json` and the lockfile
|
|
20
|
+
- do not leave duplicate SDK resolution paths active
|
|
21
|
+
- identify whether the SDK version changed during the migration
|
|
22
|
+
|
|
23
|
+
Recommended target layout:
|
|
24
|
+
- `vendor/iq-auth-sdk-x.y.z.tgz`
|
|
25
|
+
|
|
26
|
+
Install step:
|
|
27
|
+
- use `npm install ./vendor/iq-auth-sdk-x.y.z.tgz`
|
|
28
|
+
|
|
29
|
+
Migration checklist:
|
|
30
|
+
1. identify how the app currently references the local `packages/` SDK
|
|
31
|
+
2. remove that reference cleanly
|
|
32
|
+
3. add the tarball to `vendor/`
|
|
33
|
+
4. install the tarball dependency
|
|
34
|
+
5. verify imports still resolve correctly
|
|
35
|
+
6. verify the app still uses the correct SDK entry point for its environment
|
|
36
|
+
|
|
37
|
+
After install:
|
|
38
|
+
- if the installed version changed, continue immediately into the matching environment-specific setup prompt
|
|
39
|
+
- if the app is first-party browser, use the first-party browser setup prompt
|
|
40
|
+
- if the app is native mobile, use the native mobile setup prompt
|
|
41
|
+
- if the app is a server platform, use the server platform setup prompt
|
|
42
|
+
- if the app is a service automation app, use the service automation setup prompt
|
|
43
|
+
|
|
44
|
+
Do not stop at “dependency swapped” if the SDK version changed.
|
|
45
|
+
|
|
46
|
+
Always verify:
|
|
47
|
+
- only one SDK source remains active
|
|
48
|
+
- the local `packages/` path is no longer part of dependency resolution
|
|
49
|
+
- the app matches the supported auth model for its environment
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Next Prompt by App Type
|
|
53
|
+
|
|
54
|
+
- [First-Party Browser App](./first-party-browser-app.md)
|
|
55
|
+
- [Native Mobile App](./native-mobile-app.md)
|
|
56
|
+
- [Server Platform App](./server-platform-app.md)
|
|
57
|
+
- [Service Automation App](./service-automation-app.md)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Prompt: Native Mobile App
|
|
2
|
+
|
|
3
|
+
Use this prompt when updating a native mobile application that integrates with IQAuth.
|
|
4
|
+
|
|
5
|
+
```md
|
|
6
|
+
You are integrating a native mobile application with IQAuth.
|
|
7
|
+
|
|
8
|
+
Requirements:
|
|
9
|
+
- Use authorization code flow with PKCE.
|
|
10
|
+
- Launch authentication in the system browser / ASWebAuthenticationSession / Custom Tabs.
|
|
11
|
+
- Use state, nonce, code_verifier, and S256 code_challenge.
|
|
12
|
+
- Exchange the authorization code securely.
|
|
13
|
+
- Store access and refresh tokens only in secure OS-backed storage such as Keychain or Keystore.
|
|
14
|
+
- If SDK usage is needed, use @iqauth/sdk/mobile.
|
|
15
|
+
- Do not use browser cookie-session assumptions as the primary mobile pattern.
|
|
16
|
+
- Do not store tokens in plaintext async storage or browser-style storage.
|
|
17
|
+
|
|
18
|
+
Deliverables:
|
|
19
|
+
- PKCE login flow
|
|
20
|
+
- secure token storage implementation
|
|
21
|
+
- refresh handling
|
|
22
|
+
- logout and token-clearing flow
|
|
23
|
+
- short verification checklist
|
|
24
|
+
```
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Prompt: Server Platform App
|
|
2
|
+
|
|
3
|
+
Use this prompt when updating a trusted backend platform or resource server that integrates with IQAuth.
|
|
4
|
+
|
|
5
|
+
```md
|
|
6
|
+
You are integrating a trusted server-side platform application with IQAuth.
|
|
7
|
+
|
|
8
|
+
Requirements:
|
|
9
|
+
- Use @iqauth/sdk/server.
|
|
10
|
+
- If the app accepts bearer tokens, use the SDK middleware by default rather than re-implementing token verification manually.
|
|
11
|
+
- Attach verified auth context to incoming requests.
|
|
12
|
+
- Enforce roles, entitlements, and route authorization on the server.
|
|
13
|
+
- Keep token handling request-scoped or service-scoped; do not share mutable multi-user token state.
|
|
14
|
+
|
|
15
|
+
Deliverables:
|
|
16
|
+
- server client creation
|
|
17
|
+
- middleware integration
|
|
18
|
+
- protected route examples
|
|
19
|
+
- verification checklist showing middleware-backed auth on protected routes
|
|
20
|
+
```
|