@iqauth/sdk 2.6.3 → 2.7.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/README.md +173 -1
- package/dist/browser-session.d.mts +4 -4
- package/dist/browser-session.d.ts +4 -4
- package/dist/browser-session.js +181 -41
- package/dist/browser-session.mjs +3 -3
- package/dist/browser.d.mts +5 -5
- package/dist/browser.d.ts +5 -5
- package/dist/browser.js +271 -32
- package/dist/browser.mjs +10 -8
- package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
- package/dist/chunk-C2ZTBOAC.mjs +36 -0
- package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
- package/dist/chunk-GLXSIGVS.mjs +66 -0
- package/dist/{chunk-TKZTCPEK.mjs → chunk-GN37E64I.mjs} +32 -40
- package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
- package/dist/{chunk-W3F4JYGP.mjs → chunk-JXQI62A7.mjs} +108 -18
- package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
- package/dist/chunk-PMAFENVI.mjs +229 -0
- package/dist/chunk-RR2MGPTK.mjs +2724 -0
- package/dist/{chunk-76W5TLQQ.mjs → chunk-RTJAIBXY.mjs} +220 -20
- package/dist/{chunk-6TDJJER7.mjs → chunk-RUJXRTEW.mjs} +164 -5
- package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
- package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
- package/dist/{chunk-BVV54LPI.mjs → chunk-YVALAG3B.mjs} +10 -4
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{client-kYlJFgPv.d.mts → client-BGFnBpfc.d.mts} +47 -4
- package/dist/{client-BNQe3AgF.d.ts → client-CDQ21LvW.d.ts} +47 -4
- package/dist/{doctor-YYNHNMLD.mjs → doctor-JAFXWU3X.mjs} +2 -2
- package/dist/errors-Jl1Jtm-6.d.mts +107 -0
- package/dist/errors-Jl1Jtm-6.d.ts +107 -0
- package/dist/{express-B6_1vBYZ.d.mts → express-CVNQEkOr.d.mts} +2 -2
- package/dist/{express-CHpfa7D_.d.ts → express-Piv2WhWM.d.ts} +2 -2
- package/dist/express.d.mts +7 -6
- package/dist/express.d.ts +7 -6
- package/dist/express.js +349 -52
- package/dist/express.mjs +39 -12
- package/dist/fastify.d.mts +2 -0
- package/dist/fastify.d.ts +2 -0
- package/dist/fastify.js +332 -52
- package/dist/fastify.mjs +23 -8
- package/dist/hono.d.mts +2 -0
- package/dist/hono.d.ts +2 -0
- package/dist/hono.js +329 -52
- package/dist/hono.mjs +20 -8
- package/dist/index-5KSZEnDe.d.ts +1626 -0
- package/dist/index-CKoZHAoc.d.mts +1626 -0
- package/dist/index.d.mts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.js +565 -69
- package/dist/index.mjs +29 -9
- package/dist/{keys-NLWFAOEM.mjs → keys-6Y776TG2.mjs} +2 -2
- package/dist/locales.d.mts +1 -1
- package/dist/locales.d.ts +1 -1
- package/dist/mobile.d.mts +77 -7
- package/dist/mobile.d.ts +77 -7
- package/dist/mobile.js +276 -41
- package/dist/mobile.mjs +98 -3
- package/dist/next.d.mts +2 -1
- package/dist/next.d.ts +2 -1
- package/dist/next.js +391 -201
- package/dist/next.mjs +22 -7
- package/dist/pkce-7WKV4OIN.mjs +11 -0
- package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-CGpMRie4.d.ts} +1 -1
- package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-M5G47LWO.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.ts → publishableKey-f2kq-rKw.d.mts} +1 -1
- package/dist/{publishableKey-BaR0HoAH.d.mts → publishableKey-f2kq-rKw.d.ts} +1 -1
- package/dist/react-permissions.d.mts +52 -0
- package/dist/react-permissions.d.ts +52 -0
- package/dist/react-permissions.js +239 -0
- package/dist/react-permissions.mjs +97 -0
- package/dist/react.d.mts +9 -1624
- package/dist/react.d.ts +9 -1624
- package/dist/react.js +343 -36
- package/dist/react.mjs +59 -2611
- package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
- package/dist/server/handlers.d.mts +148 -3
- package/dist/server/handlers.d.ts +148 -3
- package/dist/server/handlers.js +410 -11
- package/dist/server/handlers.mjs +12 -3
- package/dist/server.d.mts +151 -8
- package/dist/server.d.ts +151 -8
- package/dist/server.js +406 -50
- package/dist/server.mjs +93 -11
- package/dist/service.d.mts +4 -4
- package/dist/service.d.ts +4 -4
- package/dist/service.js +181 -41
- package/dist/service.mjs +3 -3
- package/dist/{signIn-CiIBTJIh.d.mts → signIn-BLFnz8SV.d.ts} +78 -3
- package/dist/{signIn-CCY4JE5G.mjs → signIn-SHBW6Z4T.mjs} +2 -1
- package/dist/{signIn-OCr88Zf8.d.ts → signIn-T-CZ6t6r.d.mts} +78 -3
- package/dist/test.mjs +3 -3
- package/dist/{tokens-DCyzzn8L.d.mts → tokens-Bqhmqq_R.d.ts} +9 -2
- package/dist/{tokens-aHiGFr_E.d.ts → tokens-CITeoG6P.d.mts} +9 -2
- package/dist/{types-6bNdxesb.d.ts → types-BdQ2lqfT.d.mts} +1 -1
- package/dist/{types-6bNdxesb.d.mts → types-BdQ2lqfT.d.ts} +1 -1
- package/dist/{types-DZAflmmq.d.mts → types-XOV9XPVi.d.mts} +99 -10
- package/dist/{types-DZAflmmq.d.ts → types-XOV9XPVi.d.ts} +99 -10
- package/dist/webhooks.d.mts +100 -17
- package/dist/webhooks.d.ts +100 -17
- package/dist/webhooks.js +164 -15
- package/dist/webhooks.mjs +7 -1
- package/dist/ws.d.mts +2 -2
- package/dist/ws.d.ts +2 -2
- package/dist/ws.js +80 -30
- package/dist/ws.mjs +4 -4
- package/docs/error-handling.md +101 -0
- package/docs/guides/effective-permissions.md +171 -0
- package/package.json +13 -3
- package/dist/chunk-UKZLOHZG.mjs +0 -83
- package/dist/errors-CDdl24MP.d.mts +0 -52
- package/dist/errors-CDdl24MP.d.ts +0 -52
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Effective permissions: `useEffectivePermissions` + wildcard utilities
|
|
2
|
+
|
|
3
|
+
Some IQAuth apps have hundreds of permission nodes. Stuffing the full set
|
|
4
|
+
into the JWT `entitlements` claim is impractical — tokens grow past header
|
|
5
|
+
limits and rotation gets expensive. The canonical pattern is:
|
|
6
|
+
|
|
7
|
+
1. Keep the JWT small. Carry only the **roles** + a *coarse* subset of
|
|
8
|
+
`entitlements` for instant first-paint gating.
|
|
9
|
+
2. Fetch the **full** effective permission set on demand from the issuer
|
|
10
|
+
and use it for fine-grained UI gating.
|
|
11
|
+
3. Use the **same** wildcard semantics on the client and the server so the
|
|
12
|
+
two halves can never drift and produce the classic "user can see the
|
|
13
|
+
page but every API call 403s" foot-gun.
|
|
14
|
+
|
|
15
|
+
The SDK ships:
|
|
16
|
+
|
|
17
|
+
- `hasPermission(set, id)` and `expandPermissions(set)` — pure utilities
|
|
18
|
+
re-exported from `@iqauth/sdk`.
|
|
19
|
+
- `useEffectivePermissions({ appKey })` — React hook in `@iqauth/sdk/react/permissions` (separate sub-entry so the main React entry never pulls in TanStack Query unless you actually use this hook). Re-exported from
|
|
20
|
+
`@iqauth/sdk/react`.
|
|
21
|
+
|
|
22
|
+
## Wildcard semantics
|
|
23
|
+
|
|
24
|
+
Permission ids are dot-separated keys: `metrics`, `metrics.read`,
|
|
25
|
+
`billing.invoices.delete`. Two wildcard forms are supported:
|
|
26
|
+
|
|
27
|
+
| Pattern | Matches |
|
|
28
|
+
| --------------- | ------------------------------------------------------------- |
|
|
29
|
+
| `*` | Every permission id (root wildcard). |
|
|
30
|
+
| `<prefix>.*` | `<prefix>` itself **and** every descendant. |
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
|
|
34
|
+
- `metrics.*` matches `metrics`, `metrics.read`, `metrics.foo.bar`.
|
|
35
|
+
- `metrics.*` does **not** match `metric`, `metricsX`, or
|
|
36
|
+
`billing.metrics`.
|
|
37
|
+
- `*` matches everything, including any wildcard query.
|
|
38
|
+
|
|
39
|
+
Wildcards in the middle of an id (e.g. `metrics.*.read`) are treated as
|
|
40
|
+
literal strings and match nothing useful — keep wildcards at the leaf.
|
|
41
|
+
|
|
42
|
+
`expandPermissions(set)` normalizes a set: dedupes, sorts, and strips
|
|
43
|
+
entries already implied by a broader wildcard. It does **not** enumerate
|
|
44
|
+
descendants of a wildcard (that set is open-ended).
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
import { expandPermissions, hasPermission } from "@iqauth/sdk";
|
|
48
|
+
|
|
49
|
+
expandPermissions(["metrics.*", "metrics.read", "billing.read"]);
|
|
50
|
+
// → ["billing.read", "metrics.*"]
|
|
51
|
+
|
|
52
|
+
hasPermission(["metrics.*"], "metrics.foo.bar"); // true
|
|
53
|
+
hasPermission(["metrics.read"], "metrics.write"); // false
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## When to use the hook vs raw `entitlements`
|
|
57
|
+
|
|
58
|
+
- **JWT `entitlements`** — instant, sync, available the moment the user is
|
|
59
|
+
authenticated. Good for top-level navigation gating where a small,
|
|
60
|
+
hand-picked subset is enough. **Hard size budget: keep `entitlements`
|
|
61
|
+
under ~50 entries to stay well under header limits across proxies.**
|
|
62
|
+
- **`useEffectivePermissions()`** — async, network-backed, returns the
|
|
63
|
+
*complete* per-app set. Good for granular feature gating, action
|
|
64
|
+
buttons, admin-only fields. Cached for 5 minutes per
|
|
65
|
+
`(user, tenant, appKey)`.
|
|
66
|
+
|
|
67
|
+
The hook automatically falls back to `claims.entitlements` while the
|
|
68
|
+
fetch is in flight, so first paint is never blocked.
|
|
69
|
+
|
|
70
|
+
## React usage
|
|
71
|
+
|
|
72
|
+
The hook is backed by [TanStack Query](https://tanstack.com/query). The
|
|
73
|
+
SDK declares `@tanstack/react-query` as an **optional** peer dependency
|
|
74
|
+
— install it (`npm i @tanstack/react-query`) and mount a
|
|
75
|
+
`QueryClientProvider` somewhere above `<IQAuthProvider/>`:
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
79
|
+
import { IQAuthProvider } from "@iqauth/sdk/react";
|
|
80
|
+
|
|
81
|
+
const queryClient = new QueryClient();
|
|
82
|
+
|
|
83
|
+
<QueryClientProvider client={queryClient}>
|
|
84
|
+
<IQAuthProvider publishableKey={…}>
|
|
85
|
+
<App />
|
|
86
|
+
</IQAuthProvider>
|
|
87
|
+
</QueryClientProvider>
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Then anywhere inside:
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { SignedIn } from "@iqauth/sdk/react";
|
|
94
|
+
import { useEffectivePermissions } from "@iqauth/sdk/react/permissions";
|
|
95
|
+
|
|
96
|
+
function BillingPanel() {
|
|
97
|
+
const { permissions, hasPermission, isLoading, error, refetch } =
|
|
98
|
+
useEffectivePermissions({ appKey: "iqreuse" });
|
|
99
|
+
|
|
100
|
+
if (error) return <p>Couldn't load permissions: {error.message}</p>;
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<section aria-busy={isLoading}>
|
|
104
|
+
{hasPermission("billing.invoices.read") && <InvoiceList />}
|
|
105
|
+
{hasPermission("billing.invoices.write") && <NewInvoiceButton />}
|
|
106
|
+
{/* Wildcards work too — granted via `billing.*` or `*` */}
|
|
107
|
+
{hasPermission("billing.invoices.delete") && <DeleteInvoiceButton />}
|
|
108
|
+
</section>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### What the hook returns
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
{
|
|
117
|
+
permissions: string[]; // normalized set
|
|
118
|
+
hasPermission: (id: string) => boolean; // wildcard-aware
|
|
119
|
+
isLoading: boolean;
|
|
120
|
+
error: { code: string; message: string } | null;
|
|
121
|
+
refetch: () => Promise<void>; // bypass staleTime
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Behaviour
|
|
126
|
+
|
|
127
|
+
- **Caching.** Results are cached in-memory for 5 minutes per
|
|
128
|
+
`(issuer, tenantId, userId, appKey)`. Repeat mounts within the window
|
|
129
|
+
do not refetch. Override with `staleTime` (ms).
|
|
130
|
+
- **Single-flight.** Concurrent renders/mounts share one in-flight
|
|
131
|
+
request — you will not see a thundering herd of `/permissions/effective`
|
|
132
|
+
calls when many components mount at once.
|
|
133
|
+
- **No focus refetch.** The hook does **not** refetch on window focus.
|
|
134
|
+
Trigger `refetch()` after a mutation that changes permissions
|
|
135
|
+
(group assignment, role change, override write).
|
|
136
|
+
- **Platform admin short-circuit.** If the JWT carries
|
|
137
|
+
`roles: ["platform_admin", …]` the hook returns `permissions: ["*"]`
|
|
138
|
+
and `hasPermission` always returns `true` without a network round trip.
|
|
139
|
+
- **Entitlements fallback.** While the first fetch is in flight,
|
|
140
|
+
`permissions` is seeded from `claims.entitlements` so first paint isn't
|
|
141
|
+
blocked. The fetch result replaces this on success.
|
|
142
|
+
|
|
143
|
+
### Endpoint shape
|
|
144
|
+
|
|
145
|
+
The hook calls the existing issuer endpoint:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
GET /api/v1/tenants/{tenantId}/users/{userId}/permissions/effective?appKey=<appKey>
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
It expects an `ApiResponse.success(...)` body whose `data` is an array of
|
|
152
|
+
`{ scope: string; effect: "allow" | "deny"; … }` rows (the same shape
|
|
153
|
+
returned by `permissionService.resolveEffectivePermissions`). Rows with
|
|
154
|
+
`effect === "deny"` are stripped and the remaining `scope` strings are
|
|
155
|
+
fed through `expandPermissions()`.
|
|
156
|
+
|
|
157
|
+
No new server route is required.
|
|
158
|
+
|
|
159
|
+
### Server-side parity
|
|
160
|
+
|
|
161
|
+
Use the **same** utilities for server-side authorization checks so the
|
|
162
|
+
two halves cannot drift:
|
|
163
|
+
|
|
164
|
+
```ts
|
|
165
|
+
import { hasPermission } from "@iqauth/sdk";
|
|
166
|
+
|
|
167
|
+
// Inside an Express/Fastify/Hono route handler:
|
|
168
|
+
if (!hasPermission(grantedPermissionSet, "billing.invoices.delete")) {
|
|
169
|
+
return res.status(403).json({ error: { code: "PERMISSION_DENIED" } });
|
|
170
|
+
}
|
|
171
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iqauth/sdk",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.7.0",
|
|
4
4
|
"description": "TypeScript SDK for IQAuth — the canonical way for all IQ projects to integrate with IQAuthService",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
"import": "./dist/react.mjs",
|
|
30
30
|
"require": "./dist/react.js"
|
|
31
31
|
},
|
|
32
|
+
"./react/permissions": {
|
|
33
|
+
"types": "./dist/react-permissions.d.ts",
|
|
34
|
+
"import": "./dist/react-permissions.mjs",
|
|
35
|
+
"require": "./dist/react-permissions.js"
|
|
36
|
+
},
|
|
32
37
|
"./server": {
|
|
33
38
|
"types": "./dist/server.d.ts",
|
|
34
39
|
"import": "./dist/server.mjs",
|
|
@@ -96,7 +101,7 @@
|
|
|
96
101
|
"docs"
|
|
97
102
|
],
|
|
98
103
|
"scripts": {
|
|
99
|
-
"build": "tsup src/index.ts src/browser-session.ts src/browser.ts src/react.ts src/server.ts src/server/handlers.ts src/express.ts src/fastify.ts src/hono.ts src/next.ts src/mobile.ts src/service.ts src/ws.ts src/test.ts src/webhooks.ts src/locales.ts src/cli/index.ts --format cjs,esm --dts --clean --external react --external react-dom --external next/headers",
|
|
104
|
+
"build": "tsup src/index.ts src/browser-session.ts src/browser.ts src/react.ts src/react-permissions.ts src/server.ts src/server/handlers.ts src/express.ts src/fastify.ts src/hono.ts src/next.ts src/mobile.ts src/service.ts src/ws.ts src/test.ts src/webhooks.ts src/locales.ts src/cli/index.ts --format cjs,esm --dts --clean --external react --external react-dom --external next/headers --external @tanstack/react-query",
|
|
100
105
|
"test": "vitest run",
|
|
101
106
|
"test:watch": "vitest",
|
|
102
107
|
"test:coverage": "vitest run --coverage",
|
|
@@ -108,7 +113,8 @@
|
|
|
108
113
|
},
|
|
109
114
|
"peerDependencies": {
|
|
110
115
|
"react": ">=18",
|
|
111
|
-
"react-dom": ">=18"
|
|
116
|
+
"react-dom": ">=18",
|
|
117
|
+
"@tanstack/react-query": ">=4"
|
|
112
118
|
},
|
|
113
119
|
"peerDependenciesMeta": {
|
|
114
120
|
"react": {
|
|
@@ -116,9 +122,13 @@
|
|
|
116
122
|
},
|
|
117
123
|
"react-dom": {
|
|
118
124
|
"optional": true
|
|
125
|
+
},
|
|
126
|
+
"@tanstack/react-query": {
|
|
127
|
+
"optional": true
|
|
119
128
|
}
|
|
120
129
|
},
|
|
121
130
|
"devDependencies": {
|
|
131
|
+
"@tanstack/react-query": "^5.60.5",
|
|
122
132
|
"@types/jsonwebtoken": "^9.0.7",
|
|
123
133
|
"@types/node": "^20.0.0",
|
|
124
134
|
"@types/react": "^18.0.0",
|
package/dist/chunk-UKZLOHZG.mjs
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
// src/webhooks.ts
|
|
2
|
-
import crypto from "crypto";
|
|
3
|
-
var WebhookSignatureError = class extends Error {
|
|
4
|
-
constructor(code, message) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.name = "WebhookSignatureError";
|
|
7
|
-
this.code = code;
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
function toBuffer(p) {
|
|
11
|
-
if (typeof p === "string") return Buffer.from(p, "utf8");
|
|
12
|
-
if (Buffer.isBuffer(p)) return p;
|
|
13
|
-
return Buffer.from(p);
|
|
14
|
-
}
|
|
15
|
-
function parseHeader(header) {
|
|
16
|
-
let t = NaN;
|
|
17
|
-
const v1 = [];
|
|
18
|
-
for (const part of header.split(",")) {
|
|
19
|
-
const [k, v] = part.split("=", 2);
|
|
20
|
-
if (!k || v === void 0) continue;
|
|
21
|
-
const key = k.trim();
|
|
22
|
-
const value = v.trim();
|
|
23
|
-
if (key === "t") t = Number(value);
|
|
24
|
-
else if (key === "v1") v1.push(value);
|
|
25
|
-
}
|
|
26
|
-
return { t, v1 };
|
|
27
|
-
}
|
|
28
|
-
function timingSafeEqualHex(a, b) {
|
|
29
|
-
if (a.length !== b.length) return false;
|
|
30
|
-
try {
|
|
31
|
-
return crypto.timingSafeEqual(Buffer.from(a, "hex"), Buffer.from(b, "hex"));
|
|
32
|
-
} catch {
|
|
33
|
-
return false;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
function verifyWebhookSignature(opts) {
|
|
37
|
-
const headerRaw = Array.isArray(opts.header) ? opts.header[0] : opts.header;
|
|
38
|
-
if (!headerRaw || typeof headerRaw !== "string") {
|
|
39
|
-
throw new WebhookSignatureError("MISSING_HEADER", "Missing X-IQAuth-Signature header");
|
|
40
|
-
}
|
|
41
|
-
if (!opts.secret) {
|
|
42
|
-
throw new WebhookSignatureError("MISSING_SECRET", "secret is required");
|
|
43
|
-
}
|
|
44
|
-
const { t, v1 } = parseHeader(headerRaw);
|
|
45
|
-
if (!Number.isFinite(t) || v1.length === 0) {
|
|
46
|
-
throw new WebhookSignatureError("MALFORMED_HEADER", `Could not parse signature header: ${headerRaw}`);
|
|
47
|
-
}
|
|
48
|
-
const tolerance = opts.toleranceSeconds ?? 300;
|
|
49
|
-
const now = opts.nowSeconds ?? Math.floor(Date.now() / 1e3);
|
|
50
|
-
if (Math.abs(now - t) > tolerance) {
|
|
51
|
-
throw new WebhookSignatureError(
|
|
52
|
-
"TIMESTAMP_OUT_OF_TOLERANCE",
|
|
53
|
-
`Signature timestamp ${t} is outside the ${tolerance}s tolerance window (now=${now})`
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
const body = toBuffer(opts.payload);
|
|
57
|
-
const expected = crypto.createHmac("sha256", opts.secret).update(`${t}.`).update(body).digest("hex");
|
|
58
|
-
const matched = v1.some((sig) => timingSafeEqualHex(sig, expected));
|
|
59
|
-
if (!matched) {
|
|
60
|
-
throw new WebhookSignatureError("SIGNATURE_MISMATCH", "Webhook signature does not match expected value");
|
|
61
|
-
}
|
|
62
|
-
let parsed;
|
|
63
|
-
try {
|
|
64
|
-
parsed = JSON.parse(body.toString("utf8"));
|
|
65
|
-
} catch {
|
|
66
|
-
throw new WebhookSignatureError("MALFORMED_BODY", "Webhook body is not valid JSON");
|
|
67
|
-
}
|
|
68
|
-
return parsed;
|
|
69
|
-
}
|
|
70
|
-
function isValidWebhookSignature(opts) {
|
|
71
|
-
try {
|
|
72
|
-
verifyWebhookSignature(opts);
|
|
73
|
-
return true;
|
|
74
|
-
} catch {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
export {
|
|
80
|
-
WebhookSignatureError,
|
|
81
|
-
verifyWebhookSignature,
|
|
82
|
-
isValidWebhookSignature
|
|
83
|
-
};
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SOURCE REFS:
|
|
3
|
-
* - Route file: src/lib/response.ts (error envelope: { success: false, error: { code, message } })
|
|
4
|
-
* - All route files for error code extraction
|
|
5
|
-
* - Verified claims: N/A (error module)
|
|
6
|
-
* - Last verified: Phase 0 Research Summary
|
|
7
|
-
*/
|
|
8
|
-
declare class IQAuthError extends Error {
|
|
9
|
-
code: string;
|
|
10
|
-
status?: number;
|
|
11
|
-
raw?: unknown;
|
|
12
|
-
constructor(code: string, message: string, status?: number, raw?: unknown);
|
|
13
|
-
}
|
|
14
|
-
declare const ErrorCodes: {
|
|
15
|
-
readonly VALIDATION_ERROR: "VALIDATION_ERROR";
|
|
16
|
-
readonly INVALID_CREDENTIALS: "INVALID_CREDENTIALS";
|
|
17
|
-
readonly ACCOUNT_INACTIVE: "ACCOUNT_INACTIVE";
|
|
18
|
-
readonly ACCOUNT_LOCKED: "ACCOUNT_LOCKED";
|
|
19
|
-
readonly INSUFFICIENT_PERMISSIONS: "INSUFFICIENT_PERMISSIONS";
|
|
20
|
-
readonly TOKEN_INVALID: "TOKEN_INVALID";
|
|
21
|
-
readonly TOKEN_EXPIRED: "TOKEN_EXPIRED";
|
|
22
|
-
readonly TOKEN_REVOKED: "TOKEN_REVOKED";
|
|
23
|
-
readonly USER_INACTIVE: "USER_INACTIVE";
|
|
24
|
-
readonly INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
25
|
-
readonly NOT_FOUND: "NOT_FOUND";
|
|
26
|
-
readonly SESSION_INVALID: "SESSION_INVALID";
|
|
27
|
-
readonly SESSION_EXPIRED: "SESSION_EXPIRED";
|
|
28
|
-
readonly REFRESH_TOKEN_REUSED: "REFRESH_TOKEN_REUSED";
|
|
29
|
-
readonly PASSWORD_EXPIRED: "PASSWORD_EXPIRED";
|
|
30
|
-
readonly PIN_EXPIRED: "PIN_EXPIRED";
|
|
31
|
-
readonly PASSWORD_POLICY_VIOLATION: "PASSWORD_POLICY_VIOLATION";
|
|
32
|
-
readonly MFA_INVALID_CODE: "MFA_INVALID_CODE";
|
|
33
|
-
readonly MFA_METHOD_UNAVAILABLE: "MFA_METHOD_UNAVAILABLE";
|
|
34
|
-
readonly MFA_RATE_LIMITED: "MFA_RATE_LIMITED";
|
|
35
|
-
readonly MFA_ENROLLMENT_REQUIRED: "MFA_ENROLLMENT_REQUIRED";
|
|
36
|
-
readonly API_KEY_REQUIRED: "API_KEY_REQUIRED";
|
|
37
|
-
readonly API_KEY_INVALID: "API_KEY_INVALID";
|
|
38
|
-
readonly AUTH_REQUIRED: "AUTH_REQUIRED";
|
|
39
|
-
readonly ALREADY_EXISTS: "ALREADY_EXISTS";
|
|
40
|
-
readonly FORBIDDEN: "FORBIDDEN";
|
|
41
|
-
readonly OAUTH_NOT_CONFIGURED: "OAUTH_NOT_CONFIGURED";
|
|
42
|
-
readonly UPLOAD_ERROR: "UPLOAD_ERROR";
|
|
43
|
-
readonly EMAIL_SERVICE_UNAVAILABLE: "EMAIL_SERVICE_UNAVAILABLE";
|
|
44
|
-
readonly INVALID_CODE: "INVALID_CODE";
|
|
45
|
-
readonly CODE_ALREADY_USED: "CODE_ALREADY_USED";
|
|
46
|
-
readonly CODE_EXPIRED: "CODE_EXPIRED";
|
|
47
|
-
readonly CODE_IP_MISMATCH: "CODE_IP_MISMATCH";
|
|
48
|
-
readonly UNKNOWN_PAYLOAD: "UNKNOWN_PAYLOAD";
|
|
49
|
-
};
|
|
50
|
-
type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
|
|
51
|
-
|
|
52
|
-
export { ErrorCodes as E, IQAuthError as I, type ErrorCode as a };
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SOURCE REFS:
|
|
3
|
-
* - Route file: src/lib/response.ts (error envelope: { success: false, error: { code, message } })
|
|
4
|
-
* - All route files for error code extraction
|
|
5
|
-
* - Verified claims: N/A (error module)
|
|
6
|
-
* - Last verified: Phase 0 Research Summary
|
|
7
|
-
*/
|
|
8
|
-
declare class IQAuthError extends Error {
|
|
9
|
-
code: string;
|
|
10
|
-
status?: number;
|
|
11
|
-
raw?: unknown;
|
|
12
|
-
constructor(code: string, message: string, status?: number, raw?: unknown);
|
|
13
|
-
}
|
|
14
|
-
declare const ErrorCodes: {
|
|
15
|
-
readonly VALIDATION_ERROR: "VALIDATION_ERROR";
|
|
16
|
-
readonly INVALID_CREDENTIALS: "INVALID_CREDENTIALS";
|
|
17
|
-
readonly ACCOUNT_INACTIVE: "ACCOUNT_INACTIVE";
|
|
18
|
-
readonly ACCOUNT_LOCKED: "ACCOUNT_LOCKED";
|
|
19
|
-
readonly INSUFFICIENT_PERMISSIONS: "INSUFFICIENT_PERMISSIONS";
|
|
20
|
-
readonly TOKEN_INVALID: "TOKEN_INVALID";
|
|
21
|
-
readonly TOKEN_EXPIRED: "TOKEN_EXPIRED";
|
|
22
|
-
readonly TOKEN_REVOKED: "TOKEN_REVOKED";
|
|
23
|
-
readonly USER_INACTIVE: "USER_INACTIVE";
|
|
24
|
-
readonly INTERNAL_ERROR: "INTERNAL_ERROR";
|
|
25
|
-
readonly NOT_FOUND: "NOT_FOUND";
|
|
26
|
-
readonly SESSION_INVALID: "SESSION_INVALID";
|
|
27
|
-
readonly SESSION_EXPIRED: "SESSION_EXPIRED";
|
|
28
|
-
readonly REFRESH_TOKEN_REUSED: "REFRESH_TOKEN_REUSED";
|
|
29
|
-
readonly PASSWORD_EXPIRED: "PASSWORD_EXPIRED";
|
|
30
|
-
readonly PIN_EXPIRED: "PIN_EXPIRED";
|
|
31
|
-
readonly PASSWORD_POLICY_VIOLATION: "PASSWORD_POLICY_VIOLATION";
|
|
32
|
-
readonly MFA_INVALID_CODE: "MFA_INVALID_CODE";
|
|
33
|
-
readonly MFA_METHOD_UNAVAILABLE: "MFA_METHOD_UNAVAILABLE";
|
|
34
|
-
readonly MFA_RATE_LIMITED: "MFA_RATE_LIMITED";
|
|
35
|
-
readonly MFA_ENROLLMENT_REQUIRED: "MFA_ENROLLMENT_REQUIRED";
|
|
36
|
-
readonly API_KEY_REQUIRED: "API_KEY_REQUIRED";
|
|
37
|
-
readonly API_KEY_INVALID: "API_KEY_INVALID";
|
|
38
|
-
readonly AUTH_REQUIRED: "AUTH_REQUIRED";
|
|
39
|
-
readonly ALREADY_EXISTS: "ALREADY_EXISTS";
|
|
40
|
-
readonly FORBIDDEN: "FORBIDDEN";
|
|
41
|
-
readonly OAUTH_NOT_CONFIGURED: "OAUTH_NOT_CONFIGURED";
|
|
42
|
-
readonly UPLOAD_ERROR: "UPLOAD_ERROR";
|
|
43
|
-
readonly EMAIL_SERVICE_UNAVAILABLE: "EMAIL_SERVICE_UNAVAILABLE";
|
|
44
|
-
readonly INVALID_CODE: "INVALID_CODE";
|
|
45
|
-
readonly CODE_ALREADY_USED: "CODE_ALREADY_USED";
|
|
46
|
-
readonly CODE_EXPIRED: "CODE_EXPIRED";
|
|
47
|
-
readonly CODE_IP_MISMATCH: "CODE_IP_MISMATCH";
|
|
48
|
-
readonly UNKNOWN_PAYLOAD: "UNKNOWN_PAYLOAD";
|
|
49
|
-
};
|
|
50
|
-
type ErrorCode = (typeof ErrorCodes)[keyof typeof ErrorCodes];
|
|
51
|
-
|
|
52
|
-
export { ErrorCodes as E, IQAuthError as I, type ErrorCode as a };
|