@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,205 @@
1
+ # Middleware Reference
2
+
3
+ ## Purpose
4
+
5
+ Protect Express routes by verifying Bearer tokens via RS256/JWKS. Supports optional auth, role checks, entitlement checks, and custom error handlers.
6
+
7
+ This is the recommended integration path for platforms using the SDK in Express-style resource servers.
8
+
9
+ ## Prerequisites
10
+
11
+ - `@iqauth/sdk` installed
12
+ - Express.js application
13
+ - IQAuthService base URL for JWKS verification
14
+
15
+ ## SDK Methods
16
+
17
+ | Export | Description |
18
+ |--------|-------------|
19
+ | `iqAuthMiddleware(client, options?)` | Creates Express middleware |
20
+
21
+ ### Options
22
+
23
+ ```typescript
24
+ interface ExpressMiddlewareOptions {
25
+ requireAuth?: boolean; // Default: true
26
+ requiredRoles?: string[]; // OR logic
27
+ requiredEntitlements?: string[]; // AND logic
28
+ onUnauthorized?: (res, reason) => void;
29
+ onForbidden?: (res, reason) => void;
30
+ }
31
+ ```
32
+
33
+ ## Step-by-Step
34
+
35
+ ### 1. Basic Protection
36
+
37
+ ```typescript
38
+ import { createServerClient } from "@iqauth/sdk/server";
39
+
40
+ const client = createServerClient({
41
+ baseUrl: "https://auth.dispositioniq.com",
42
+ });
43
+
44
+ app.use("/api", client.middleware());
45
+ ```
46
+
47
+ ### 2. Access Claims
48
+
49
+ ```typescript
50
+ app.get("/api/me", (req, res) => {
51
+ res.json({
52
+ userId: req.auth!.sub,
53
+ email: req.auth!.email,
54
+ tenantId: req.auth!.tenantId,
55
+ roles: req.auth!.roles,
56
+ });
57
+ });
58
+ ```
59
+
60
+ Claims are attached to `req.auth` (not `req.user`). See [DECISION-006](../../DECISIONS.md).
61
+
62
+ ### 3. Optional Auth
63
+
64
+ ```typescript
65
+ app.use(client.middleware({ requireAuth: false }));
66
+
67
+ app.get("/api/resource", (req, res) => {
68
+ if (req.auth) {
69
+ // Authenticated
70
+ } else {
71
+ // Anonymous
72
+ }
73
+ });
74
+ ```
75
+
76
+ ### 4. Role Checks (OR Logic)
77
+
78
+ ```typescript
79
+ app.use("/admin", client.middleware({
80
+ requiredRoles: ["tenant_admin", "platform_admin"],
81
+ }));
82
+ // User needs ANY ONE of the listed roles
83
+ ```
84
+
85
+ ### 5. Entitlement Checks (AND Logic)
86
+
87
+ ```typescript
88
+ app.use("/capture", client.middleware({
89
+ requiredEntitlements: ["iqcapture"],
90
+ }));
91
+ // User needs ALL listed entitlements
92
+ ```
93
+
94
+ ### 6. Custom Error Handlers
95
+
96
+ ```typescript
97
+ app.use(client.middleware({
98
+ onUnauthorized: (res, reason) => {
99
+ (res as any).status(401).json({ error: "Login required", detail: reason });
100
+ },
101
+ onForbidden: (res, reason) => {
102
+ (res as any).status(403).json({ error: "Access denied", detail: reason });
103
+ },
104
+ }));
105
+ ```
106
+
107
+ ### How It Works
108
+
109
+ 1. Extracts token from `Authorization: Bearer <token>` header
110
+ 2. Verifies signature using RS256 via JWKS (`/.well-known/jwks.json`)
111
+ 3. Validates issuer (`auth.dispositioniq.com`) and audience
112
+ 4. Checks expiration
113
+ 5. Attaches decoded claims to `req.auth`
114
+ 6. Checks `requiredRoles` (OR logic) if specified
115
+ 7. Checks `requiredEntitlements` (AND logic) if specified
116
+
117
+ ### TypeScript Augmentation
118
+
119
+ ```typescript
120
+ declare global {
121
+ namespace Express {
122
+ interface Request {
123
+ auth?: JwtClaims;
124
+ }
125
+ }
126
+ }
127
+ ```
128
+
129
+ ## Error Handling
130
+
131
+ ### Default 401 Response
132
+
133
+ ```json
134
+ {
135
+ "success": false,
136
+ "error": {
137
+ "code": "TOKEN_INVALID",
138
+ "message": "Missing or invalid authorization header"
139
+ }
140
+ }
141
+ ```
142
+
143
+ ### Default 403 Response
144
+
145
+ ```json
146
+ {
147
+ "success": false,
148
+ "error": {
149
+ "code": "INSUFFICIENT_PERMISSIONS",
150
+ "message": "Missing required role. Required one of: tenant_admin, platform_admin"
151
+ }
152
+ }
153
+ ```
154
+
155
+ ## Complete Example
156
+
157
+ ```typescript
158
+ import express from "express";
159
+ import { createServerClient } from "@iqauth/sdk/server";
160
+
161
+ const app = express();
162
+ const client = createServerClient({
163
+ baseUrl: "https://auth.dispositioniq.com",
164
+ });
165
+
166
+ // Public routes
167
+ app.get("/health", (req, res) => res.json({ ok: true }));
168
+
169
+ // Any authenticated user
170
+ app.use("/api", client.middleware());
171
+
172
+ app.get("/api/me", (req, res) => {
173
+ res.json({
174
+ userId: req.auth!.sub,
175
+ email: req.auth!.email,
176
+ tenantId: req.auth!.tenantId,
177
+ roles: req.auth!.roles,
178
+ entitlements: req.auth!.entitlements,
179
+ scopeContext: req.auth!.scopeContext,
180
+ });
181
+ });
182
+
183
+ // Admin only
184
+ app.use("/api/admin", client.middleware({
185
+ requiredRoles: ["tenant_admin", "platform_admin"],
186
+ }));
187
+
188
+ app.get("/api/admin/users", async (req, res) => {
189
+ const users = await client.users.list({ tenantId: req.auth!.tenantId });
190
+ res.json(users);
191
+ });
192
+
193
+ // Product-gated with custom error
194
+ app.use("/api/capture", client.middleware({
195
+ requiredEntitlements: ["iqcapture"],
196
+ onForbidden: (res, reason) => {
197
+ (res as any).status(403).json({
198
+ error: "IQCapture license required",
199
+ upgrade: "https://dispositioniq.com/pricing",
200
+ });
201
+ },
202
+ }));
203
+
204
+ app.listen(3000);
205
+ ```
@@ -0,0 +1,110 @@
1
+ # Mobile / Native Guide
2
+
3
+ Use this guide for iOS, Android, React Native, or Expo-style native applications.
4
+
5
+ ## Recommended Model
6
+
7
+ Native apps are public clients.
8
+
9
+ Use:
10
+
11
+ - authorization code flow
12
+ - PKCE
13
+ - system browser authentication
14
+ - secure OS-backed token storage
15
+
16
+ Do not embed a client secret in the app.
17
+
18
+ ## Required Security Elements
19
+
20
+ Every mobile login should generate:
21
+
22
+ - `state`
23
+ - `nonce`
24
+ - PKCE code verifier
25
+ - PKCE code challenge with `S256`
26
+
27
+ Validate `state` on return before exchanging the authorization code.
28
+
29
+ ## Redirect Handling
30
+
31
+ Supported callback patterns should be:
32
+
33
+ - deep links
34
+ - universal links
35
+ - app links
36
+
37
+ The redirect URI must be registered and validated by IQAuth.
38
+
39
+ ## Token Storage
40
+
41
+ Allowed:
42
+
43
+ - iOS Keychain
44
+ - Android Keystore
45
+ - secure-storage wrappers over those primitives
46
+
47
+ Not acceptable:
48
+
49
+ - plaintext async storage
50
+ - browser-style `localStorage` assumptions
51
+ - shipping long-lived credentials in bundled config
52
+
53
+ ## Session Lifecycle
54
+
55
+ Typical mobile flow:
56
+
57
+ 1. open the authorization URL in the system browser
58
+ 2. user authenticates
59
+ 3. app receives authorization code
60
+ 4. exchange code for tokens
61
+ 5. persist tokens in secure storage
62
+ 6. attach access token to API requests
63
+ 7. refresh when needed
64
+ 8. clear secure storage on logout or revocation
65
+
66
+ ## SDK Fit
67
+
68
+ The current SDK can be used in mobile only if:
69
+
70
+ - login uses PKCE
71
+ - tokens live in secure storage
72
+ - the app treats refresh tokens as protected credentials
73
+
74
+ Example shape:
75
+
76
+ ```typescript
77
+ const client = new IQAuthClient({
78
+ baseUrl: "https://auth.dispositioniq.com",
79
+ accessToken: await secureStore.get("accessToken"),
80
+ refreshToken: await secureStore.get("refreshToken"),
81
+ onTokenRefresh: async (tokens) => {
82
+ await secureStore.set("accessToken", tokens.accessToken);
83
+ await secureStore.set("refreshToken", tokens.refreshToken);
84
+ },
85
+ });
86
+ ```
87
+
88
+ ## Logout and Revocation
89
+
90
+ On logout:
91
+
92
+ - call the logout endpoint
93
+ - clear secure storage
94
+ - clear in-memory auth state
95
+
96
+ On reuse detection or revocation:
97
+
98
+ - force full re-authentication
99
+ - do not silently continue with cached session assumptions
100
+
101
+ ## Summary
102
+
103
+ Mobile should not reuse the browser cookie-session model.
104
+
105
+ The correct model is:
106
+
107
+ - PKCE login
108
+ - secure storage
109
+ - deep link callback
110
+ - explicit logout and revocation handling
@@ -0,0 +1,220 @@
1
+ # Roles & Permissions
2
+
3
+ ## Purpose
4
+
5
+ Check user roles and entitlements from JWT claims (client-side, no network), and manage role definitions and assignments on the server.
6
+
7
+ ## Prerequisites
8
+
9
+ - `@iqauth/sdk` installed
10
+ - For `permissions` module: authenticated user with tokens set on client
11
+ - For `roles` module: `tenant_admin` or `platform_admin` role
12
+
13
+ ## Environment Note
14
+
15
+ Direct token examples in this guide assume a trusted runtime, secure mobile storage, or server-side context.
16
+
17
+ For first-party browser apps, consume roles and permissions through your backend session model.
18
+
19
+ ## SDK Methods
20
+
21
+ ### PermissionsModule (Client-Side, No Network)
22
+
23
+ | Method | Description |
24
+ |--------|-------------|
25
+ | `getRoles()` | Get roles from JWT → `string[]` |
26
+ | `getEntitlements()` | Get entitlements from JWT → `string[]` |
27
+ | `hasRole(role)` | Check single role → `boolean` |
28
+ | `hasEntitlement(ent)` | Check single entitlement → `boolean` |
29
+ | `hasAllRoles(roles)` | All roles present (AND) → `boolean` |
30
+ | `hasAnyRole(roles)` | Any role present (OR) → `boolean` |
31
+ | `hasAllEntitlements(ents)` | All entitlements (AND) → `boolean` |
32
+ | `hasAnyEntitlement(ents)` | Any entitlement (OR) → `boolean` |
33
+
34
+ ### RolesModule (Server-Side API)
35
+
36
+ | Method | Description |
37
+ |--------|-------------|
38
+ | `list(tenantId)` | List roles → `Role[]` |
39
+ | `create(tenantId, data)` | Create role → `Role` |
40
+ | `update(tenantId, roleId, data)` | Update role → `Role` |
41
+ | `delete(tenantId, roleId)` | Delete role |
42
+ | `getUserRoles(tenantId, userId)` | Get user's roles → `Role[]` |
43
+ | `assignRole(tenantId, userId, data)` | Assign role → `UserRoleAssignment` |
44
+ | `removeRole(tenantId, userId, roleId)` | Remove role |
45
+
46
+ ## Step-by-Step
47
+
48
+ ### 1. Check Roles/Entitlements (Client-Side)
49
+
50
+ ```typescript
51
+ client.permissions.getRoles(); // ["tenant_admin", "user"]
52
+ client.permissions.getEntitlements(); // ["iqcapture", "iqreuse"]
53
+
54
+ client.permissions.hasRole("tenant_admin"); // true
55
+ client.permissions.hasEntitlement("iqcapture"); // true
56
+ client.permissions.hasAllRoles(["tenant_admin", "vendor_admin"]); // false
57
+ client.permissions.hasAnyRole(["tenant_admin", "vendor_admin"]); // true
58
+ client.permissions.hasAllEntitlements(["iqcapture", "iqreuse"]); // true
59
+ ```
60
+
61
+ These read from the current access token's claims. If no token is set, arrays return `[]` and checks return `false`.
62
+
63
+ ### 2. System Roles
64
+
65
+ | Role | Level | Scope |
66
+ |------|-------|-------|
67
+ | `platform_admin` | Global | All tenants, all operations |
68
+ | `tenant_admin` | Tenant | Full control within a tenant |
69
+ | `vendor_admin` | Vendor | Manage vendor and its sources/clients |
70
+ | `user` | User | Standard user operations |
71
+
72
+ ### 3. Create Custom Roles
73
+
74
+ ```typescript
75
+ const role = await client.roles.create(tenantId, {
76
+ name: "site_operator",
77
+ description: "Operates capture equipment at a site",
78
+ hierarchyLevel: 20,
79
+ });
80
+ ```
81
+
82
+ ### 4. Assign Roles
83
+
84
+ ```typescript
85
+ const assignment = await client.roles.assignRole(tenantId, userId, {
86
+ roleName: "site_operator",
87
+ scopeType: "source", // optional V2 scoping
88
+ scopeId: sourceId, // optional V2 scoping
89
+ });
90
+ ```
91
+
92
+ ### 5. Remove Roles
93
+
94
+ ```typescript
95
+ await client.roles.removeRole(tenantId, userId, roleId);
96
+ ```
97
+
98
+ ## Error Handling
99
+
100
+ | Error Code | Meaning | Recovery |
101
+ |------------|---------|----------|
102
+ | `INSUFFICIENT_PERMISSIONS` | Caller lacks admin role | Requires `tenant_admin` |
103
+ | `NOT_FOUND` | Role or user not found | Check IDs |
104
+ | `ALREADY_EXISTS` | Role name exists in tenant | Use different name |
105
+ | `VALIDATION_ERROR` | Invalid role data | Check required fields |
106
+
107
+ ```typescript
108
+ import { IQAuthError, ErrorCodes } from "@iqauth/sdk";
109
+
110
+ try {
111
+ await client.roles.create(tenantId, { name: "admin", hierarchyLevel: 50 });
112
+ } catch (err) {
113
+ if (err instanceof IQAuthError && err.code === ErrorCodes.ALREADY_EXISTS) {
114
+ console.log("Role already exists");
115
+ }
116
+ }
117
+ ```
118
+
119
+ ### 6. Permission Groups (V2)
120
+
121
+ Permission groups provide fine-grained access control beyond flat roles. See also [Scoped Authorization](./scoped-authorization.md).
122
+
123
+ ```typescript
124
+ // Create a group
125
+ const group = await client.permissionGroups.create(tenantId, "Operators", "Field operators");
126
+
127
+ // Add permission to group
128
+ await client.permissionGroups.addPermission(tenantId, group.id, {
129
+ appKey: "iqcapture",
130
+ nodeKey: "capture.operate",
131
+ effect: "allow",
132
+ weight: 10,
133
+ });
134
+
135
+ // Assign user to group
136
+ await client.permissionGroups.assignUserToGroup(tenantId, userId, group.id);
137
+
138
+ // Check effective permissions
139
+ const effective = await client.permissionGroups.getEffectivePermissions(
140
+ tenantId, userId, { appKey: "iqcapture" },
141
+ );
142
+ console.log("Effective permissions:", effective);
143
+
144
+ // Check a single permission
145
+ const check = await client.permissionGroups.checkPermission(
146
+ tenantId, userId, "iqcapture", "capture.operate",
147
+ );
148
+ console.log("Can operate:", check.allowed);
149
+ ```
150
+
151
+ ## Complete Example
152
+
153
+ ```typescript
154
+ import { IQAuthClient, IQAuthError, ErrorCodes } from "@iqauth/sdk";
155
+
156
+ const client = new IQAuthClient({
157
+ baseUrl: "https://auth.dispositioniq.com",
158
+ accessToken: adminToken,
159
+ refreshToken: adminRefreshToken,
160
+ });
161
+
162
+ async function setupRolesAndPermissions(tenantId: string, operatorUserId: string) {
163
+ // V1: Create and assign flat roles
164
+ const operator = await client.roles.create(tenantId, {
165
+ name: "site_operator",
166
+ description: "Operates equipment at a facility",
167
+ hierarchyLevel: 20,
168
+ });
169
+
170
+ await client.roles.assignRole(tenantId, operatorUserId, { roleName: "site_operator" });
171
+ const userRoles = await client.roles.getUserRoles(tenantId, operatorUserId);
172
+ console.log("Operator roles:", userRoles.map(r => r.name));
173
+
174
+ // V2: Fine-grained permission groups
175
+ const group = await client.permissionGroups.create(tenantId, "Capture Operators");
176
+
177
+ await client.permissionGroups.addPermission(tenantId, group.id, {
178
+ appKey: "iqcapture",
179
+ nodeKey: "capture.operate",
180
+ effect: "allow",
181
+ });
182
+
183
+ await client.permissionGroups.assignUserToGroup(tenantId, operatorUserId, group.id);
184
+
185
+ const check = await client.permissionGroups.checkPermission(
186
+ tenantId, operatorUserId, "iqcapture", "capture.operate",
187
+ );
188
+ console.log("Can operate:", check.allowed); // true
189
+
190
+ // Client-side checks (reads from JWT, no network)
191
+ const isAdmin = client.permissions.hasAnyRole(["tenant_admin", "platform_admin"]);
192
+ const hasCapture = client.permissions.hasEntitlement("iqcapture");
193
+ console.log("Is admin:", isAdmin, "Has capture:", hasCapture);
194
+ }
195
+ ```
196
+
197
+ ## API Reference
198
+
199
+ ### RolesModule
200
+
201
+ | Method | HTTP | Path |
202
+ |--------|------|------|
203
+ | `list(tenantId)` | GET | `/api/v1/tenants/:tenantId/roles` |
204
+ | `create(tenantId, data)` | POST | `/api/v1/tenants/:tenantId/roles` |
205
+ | `update(tenantId, roleId, data)` | PATCH | `/api/v1/tenants/:tenantId/roles/:roleId` |
206
+ | `delete(tenantId, roleId)` | DELETE | `/api/v1/tenants/:tenantId/roles/:roleId` |
207
+ | `getUserRoles(tenantId, userId)` | GET | `/api/v1/tenants/:tenantId/users/:userId/roles` |
208
+ | `assignRole(tenantId, userId, data)` | POST | `/api/v1/tenants/:tenantId/users/:userId/roles` |
209
+ | `removeRole(tenantId, userId, roleId)` | DELETE | `/api/v1/tenants/:tenantId/users/:userId/roles/:roleId` |
210
+
211
+ ### PermissionGroupsModule
212
+
213
+ | Method | HTTP | Path |
214
+ |--------|------|------|
215
+ | `list(tenantId)` | GET | `/api/v1/tenants/:tenantId/permission-groups` |
216
+ | `create(tenantId, name, desc?)` | POST | `/api/v1/tenants/:tenantId/permission-groups` |
217
+ | `addPermission(tenantId, groupId, data)` | POST | `.../:groupId/permissions` |
218
+ | `assignUserToGroup(tenantId, userId, groupId)` | POST | `.../users/:userId/groups` |
219
+ | `getEffectivePermissions(tenantId, userId, params)` | GET | `.../users/:userId/permissions/effective` |
220
+ | `checkPermission(tenantId, userId, appKey, nodeKey)` | POST | `.../users/:userId/permissions/check` |