@iqauth/sdk 2.6.4 → 2.8.1
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 +212 -46
- 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 +293 -34
- package/dist/browser.mjs +5 -5
- package/dist/{chunk-BVV54LPI.mjs → chunk-25SSYDIP.mjs} +10 -4
- package/dist/{chunk-XAWYUPMO.mjs → chunk-4V7FKOTG.mjs} +242 -22
- package/dist/{chunk-6I6RM4MN.mjs → chunk-6PJRLRB4.mjs} +33 -3
- package/dist/{chunk-SL3KRS4W.mjs → chunk-CIJORODR.mjs} +23 -1
- package/dist/{chunk-LIZYFXH7.mjs → chunk-DFWHSDYQ.mjs} +1 -1
- package/dist/chunk-GLXSIGVS.mjs +66 -0
- package/dist/{chunk-DJIBN2N7.mjs → chunk-GN37E64I.mjs} +29 -7
- package/dist/{chunk-WQWBJSSS.mjs → chunk-HVHNYPDC.mjs} +6 -6
- package/dist/chunk-JRDVUWAL.mjs +46 -0
- package/dist/{chunk-UNYDG2L4.mjs → chunk-NUO2I65G.mjs} +56 -23
- package/dist/{chunk-5T7GHBX6.mjs → chunk-TLET552H.mjs} +36 -0
- package/dist/chunk-VYQ3ETCK.mjs +244 -0
- package/dist/{chunk-3JULWS6F.mjs → chunk-WCELYTJ3.mjs} +3 -3
- package/dist/chunk-WHT6WKTY.mjs +3180 -0
- package/dist/{chunk-MKKZULZR.mjs → chunk-WIFG74IK.mjs} +1 -1
- package/dist/chunk-WSH4SW7F.mjs +490 -0
- package/dist/{chunk-W3F4JYGP.mjs → chunk-ZLJPABB7.mjs} +139 -23
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/{client-BNQe3AgF.d.ts → client-D8L-PaWr.d.mts} +59 -6
- package/dist/{client-kYlJFgPv.d.mts → client-DkPL0EPZ.d.ts} +59 -6
- 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-CHpfa7D_.d.ts → express-Budysq4h.d.ts} +2 -2
- package/dist/{express-B6_1vBYZ.d.mts → express-DDTA3qV1.d.mts} +2 -2
- package/dist/express.d.mts +7 -6
- package/dist/express.d.ts +7 -6
- package/dist/express.js +563 -85
- package/dist/express.mjs +73 -34
- package/dist/fastify.d.mts +10 -0
- package/dist/fastify.d.ts +10 -0
- package/dist/fastify.js +589 -65
- package/dist/fastify.mjs +101 -11
- package/dist/hono.d.mts +10 -0
- package/dist/hono.d.ts +10 -0
- package/dist/hono.js +566 -65
- package/dist/hono.mjs +78 -11
- package/dist/index-Cko-d5po.d.mts +1848 -0
- package/dist/index-RNqwEcmY.d.ts +1848 -0
- package/dist/index.d.mts +56 -8
- package/dist/index.d.ts +56 -8
- package/dist/index.js +694 -75
- package/dist/index.mjs +30 -10
- 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/locales.js +36 -0
- package/dist/locales.mjs +1 -1
- package/dist/mobile.d.mts +77 -7
- package/dist/mobile.d.ts +77 -7
- package/dist/mobile.js +307 -46
- package/dist/mobile.mjs +98 -3
- package/dist/next.d.mts +10 -1
- package/dist/next.d.ts +10 -1
- package/dist/next.js +596 -205
- package/dist/next.mjs +83 -10
- package/dist/{provisioningBridge-88xjOS2n.d.mts → provisioningBridge-BXPMZCLe.d.ts} +30 -2
- package/dist/{provisioningBridge-DnTfzdZK.d.ts → provisioningBridge-IEycmsgb.d.mts} +30 -2
- 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 +98 -0
- package/dist/react.d.mts +9 -1624
- package/dist/react.d.ts +9 -1624
- package/dist/react.js +882 -73
- package/dist/react.mjs +71 -2631
- package/dist/{reverify-4UEJXUS6.mjs → reverify-C64QXKJO.mjs} +2 -2
- package/dist/server/handlers.d.mts +200 -4
- package/dist/server/handlers.d.ts +200 -4
- package/dist/server/handlers.js +530 -16
- package/dist/server/handlers.mjs +14 -3
- package/dist/server.d.mts +171 -8
- package/dist/server.d.ts +171 -8
- package/dist/server.js +579 -61
- package/dist/server.mjs +99 -12
- package/dist/service.d.mts +4 -4
- package/dist/service.d.ts +4 -4
- package/dist/service.js +212 -46
- package/dist/service.mjs +3 -3
- package/dist/{signIn-CiIBTJIh.d.mts → signIn-CReqfXsh.d.mts} +95 -3
- package/dist/{signIn-OCr88Zf8.d.ts → signIn-Cfa1GTpO.d.ts} +95 -3
- package/dist/{signIn-4OKLDEIH.mjs → signIn-SHBW6Z4T.mjs} +1 -1
- package/dist/test.mjs +3 -3
- package/dist/{tokens-DCyzzn8L.d.mts → tokens-9F6ETrzk.d.ts} +9 -2
- package/dist/{tokens-aHiGFr_E.d.ts → tokens-B06VtvUi.d.mts} +9 -2
- package/dist/{types-DZAflmmq.d.mts → types-Bn8O-OEd.d.mts} +164 -11
- package/dist/{types-DZAflmmq.d.ts → types-Bn8O-OEd.d.ts} +164 -11
- package/dist/{types-6bNdxesb.d.ts → types-DnU2LhXR.d.mts} +7 -1
- package/dist/{types-6bNdxesb.d.mts → types-DnU2LhXR.d.ts} +7 -1
- package/dist/webhooks.d.mts +113 -17
- package/dist/webhooks.d.ts +113 -17
- package/dist/webhooks.js +179 -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/docs/guides/invitations.md +65 -0
- package/package.json +19 -4
- package/dist/chunk-6TDJJER7.mjs +0 -217
- package/dist/chunk-UKZLOHZG.mjs +0 -83
- package/dist/errors-CDdl24MP.d.mts +0 -52
- package/dist/errors-CDdl24MP.d.ts +0 -52
|
@@ -36,7 +36,17 @@ interface IQAuthTokenClientConfig extends IQAuthClientConfigBase {
|
|
|
36
36
|
apiKey?: string;
|
|
37
37
|
accessToken?: string;
|
|
38
38
|
refreshToken?: string;
|
|
39
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Token auto-refresh strategy.
|
|
41
|
+
* - `true` (default): proactively refresh when the access token is within 60s of expiry,
|
|
42
|
+
* AND retry once on a TOKEN_EXPIRED 401 response.
|
|
43
|
+
* - `false`: never auto-refresh — caller drives `tokens.refresh()` manually.
|
|
44
|
+
* - `'app-state'` (mobile only): skip the per-request expiring-soon proactive refresh
|
|
45
|
+
* (which fights with React Native's app-suspension lifecycle) and instead refresh on
|
|
46
|
+
* AppState `active` transitions. Reactive 401 retry stays enabled. Recognized only by
|
|
47
|
+
* `createMobileClient`; passing it to other constructors falls back to `true`.
|
|
48
|
+
*/
|
|
49
|
+
autoRefresh?: boolean | "app-state";
|
|
40
50
|
onTokenRefresh?: (tokens: TokenPair) => void;
|
|
41
51
|
}
|
|
42
52
|
interface IQAuthBrowserSessionClientConfig extends IQAuthClientConfigBase {
|
|
@@ -75,7 +85,66 @@ interface JwtClaims {
|
|
|
75
85
|
email?: string;
|
|
76
86
|
name?: string;
|
|
77
87
|
};
|
|
88
|
+
picture?: string;
|
|
89
|
+
email_verified?: boolean;
|
|
90
|
+
given_name?: string;
|
|
91
|
+
family_name?: string;
|
|
92
|
+
locale?: string;
|
|
78
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Task #127 — Base claims shape (OIDC standard + IQAuth tenant/role).
|
|
96
|
+
*
|
|
97
|
+
* Required fields mirror what the IQAuth issuer always emits today; if a
|
|
98
|
+
* token is missing one of them it would fail `tokens.verify()` against the
|
|
99
|
+
* expected issuer/audience first, so this type trades runtime checks for
|
|
100
|
+
* compile-time ergonomics.
|
|
101
|
+
*/
|
|
102
|
+
interface IQAuthBaseClaims {
|
|
103
|
+
/** Subject — opaque IQAuth user id. */
|
|
104
|
+
sub: string;
|
|
105
|
+
/** OIDC issuer URL (e.g. `https://auth.dispositioniq.com`). */
|
|
106
|
+
iss: string;
|
|
107
|
+
/** Audience(s) the token was minted for. */
|
|
108
|
+
aud: string | string[];
|
|
109
|
+
/** Expiry in seconds since epoch. */
|
|
110
|
+
exp: number;
|
|
111
|
+
/** Issued-at in seconds since epoch. */
|
|
112
|
+
iat: number;
|
|
113
|
+
email?: string;
|
|
114
|
+
email_verified?: boolean;
|
|
115
|
+
name?: string;
|
|
116
|
+
picture?: string;
|
|
117
|
+
locale?: string;
|
|
118
|
+
tenantId?: string;
|
|
119
|
+
tenantName?: string;
|
|
120
|
+
tenantSlug?: string;
|
|
121
|
+
vendorId?: string | null;
|
|
122
|
+
roles?: string[];
|
|
123
|
+
entitlements?: string[];
|
|
124
|
+
sessionId?: string;
|
|
125
|
+
jti?: string;
|
|
126
|
+
scopeContext?: ScopeContext;
|
|
127
|
+
loginMethod?: string;
|
|
128
|
+
/** RFC 8693 §4.1 actor — present on impersonation tokens. */
|
|
129
|
+
purpose?: string;
|
|
130
|
+
act?: {
|
|
131
|
+
sub: string;
|
|
132
|
+
email?: string;
|
|
133
|
+
name?: string;
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Generic typed claims envelope. The type parameter `T` is structurally
|
|
138
|
+
* intersected with the base claims so app-specific fields minted via JWT
|
|
139
|
+
* templates surface with full IntelliSense:
|
|
140
|
+
*
|
|
141
|
+
* ```ts
|
|
142
|
+
* type MyClaims = { plan: "free" | "pro"; orgId: string };
|
|
143
|
+
* const claims = await client.tokens.verify<MyClaims>(token);
|
|
144
|
+
* if (claims.plan === "pro" && claims.orgId) { … } // both fields typed
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
type IQAuthClaims<T extends object = {}> = IQAuthBaseClaims & T;
|
|
79
148
|
interface UserProfile {
|
|
80
149
|
id: string;
|
|
81
150
|
email: string;
|
|
@@ -102,6 +171,19 @@ interface SessionUser {
|
|
|
102
171
|
vendorId?: string | null;
|
|
103
172
|
roles: string[];
|
|
104
173
|
entitlements: string[];
|
|
174
|
+
picture?: string;
|
|
175
|
+
emailVerified?: boolean;
|
|
176
|
+
givenName?: string;
|
|
177
|
+
familyName?: string;
|
|
178
|
+
locale?: string;
|
|
179
|
+
/**
|
|
180
|
+
* Task #171 — When the active session was minted under a source/client
|
|
181
|
+
* scope (either via a scope_hint, single-resolved scope, or post-pick),
|
|
182
|
+
* the access token carries a `scopeContext` claim and we project it here
|
|
183
|
+
* so SDK consumers (`useUser()`, framework adapters) can read the active
|
|
184
|
+
* scope without re-parsing the JWT. Absent for tenant-wide sessions.
|
|
185
|
+
*/
|
|
186
|
+
scopeContext?: ScopeContext;
|
|
105
187
|
}
|
|
106
188
|
interface Tenant {
|
|
107
189
|
tenantId: string;
|
|
@@ -127,6 +209,24 @@ interface SessionAuthenticatedLoginResult {
|
|
|
127
209
|
authMode: "session";
|
|
128
210
|
user: SessionUser;
|
|
129
211
|
}
|
|
212
|
+
/**
|
|
213
|
+
* Task #171 — A user can have multiple source/client scoped memberships in
|
|
214
|
+
* the same tenant with no tenant-wide role. When login resolves to that
|
|
215
|
+
* state the backend returns a short-lived `scopeSelectionToken` plus the
|
|
216
|
+
* list of choices; the caller redeems it via `AuthModule.selectScope`.
|
|
217
|
+
*/
|
|
218
|
+
interface ScopeChoice {
|
|
219
|
+
membershipId: string;
|
|
220
|
+
scopeType: "vendor" | "source" | "client";
|
|
221
|
+
scopeId: string;
|
|
222
|
+
scopeName: string;
|
|
223
|
+
roleName: string;
|
|
224
|
+
}
|
|
225
|
+
/** Task #171 — Optional hint forwarded with login / select-tenant / OIDC. */
|
|
226
|
+
interface ScopeHint {
|
|
227
|
+
type: "vendor" | "source" | "client";
|
|
228
|
+
id: string;
|
|
229
|
+
}
|
|
130
230
|
type LoginResult = TokenAuthenticatedLoginResult | SessionAuthenticatedLoginResult | {
|
|
131
231
|
status: "mfa_required";
|
|
132
232
|
mfaChallengeToken: string;
|
|
@@ -135,6 +235,11 @@ type LoginResult = TokenAuthenticatedLoginResult | SessionAuthenticatedLoginResu
|
|
|
135
235
|
status: "tenant_selection";
|
|
136
236
|
tenantSelectionToken: string;
|
|
137
237
|
tenants: Tenant[];
|
|
238
|
+
} | {
|
|
239
|
+
status: "scope_selection";
|
|
240
|
+
scopeSelectionToken: string;
|
|
241
|
+
tenantId: string;
|
|
242
|
+
scopes: ScopeChoice[];
|
|
138
243
|
};
|
|
139
244
|
interface Session {
|
|
140
245
|
id: string;
|
|
@@ -407,6 +512,13 @@ interface PermissionNodeManifest {
|
|
|
407
512
|
metadata?: Record<string, unknown>;
|
|
408
513
|
children?: PermissionNodeManifest[];
|
|
409
514
|
}
|
|
515
|
+
/**
|
|
516
|
+
* Task #130 — every manifest write must declare its origin environment so a
|
|
517
|
+
* dev workstation can never silently overwrite a production app's permission
|
|
518
|
+
* tree. The Admin API rejects writes whose `environment` is missing or not
|
|
519
|
+
* one of these three values with `{code: "ENVIRONMENT_REQUIRED"}`.
|
|
520
|
+
*/
|
|
521
|
+
type AppManifestEnvironment = "production" | "staging" | "development";
|
|
410
522
|
interface AppManifest {
|
|
411
523
|
key: string;
|
|
412
524
|
name: string;
|
|
@@ -415,6 +527,8 @@ interface AppManifest {
|
|
|
415
527
|
tenantId?: string | null;
|
|
416
528
|
metadata?: Record<string, unknown>;
|
|
417
529
|
permissions: PermissionNodeManifest[];
|
|
530
|
+
/** Required by `POST /api/v1/apps/sync`. See {@link AppManifestEnvironment}. */
|
|
531
|
+
environment: AppManifestEnvironment;
|
|
418
532
|
}
|
|
419
533
|
interface AppInfo {
|
|
420
534
|
id: string;
|
|
@@ -545,13 +659,17 @@ interface GroupPermission {
|
|
|
545
659
|
nodeKey?: string | null;
|
|
546
660
|
createdAt?: string;
|
|
547
661
|
}
|
|
662
|
+
/**
|
|
663
|
+
* Task #130 — `appKey` and `nodeKey` are REQUIRED on this app-scoped admin
|
|
664
|
+
* call. The legacy `product` / `scope` shape is rejected at the SDK boundary
|
|
665
|
+
* to prevent the silent-fallback failure mode where a misconfigured value
|
|
666
|
+
* led to an empty/wrong permission set without any error.
|
|
667
|
+
*/
|
|
548
668
|
interface AddGroupPermissionRequest {
|
|
549
|
-
|
|
550
|
-
|
|
669
|
+
appKey: string;
|
|
670
|
+
nodeKey: string;
|
|
551
671
|
effect: string;
|
|
552
672
|
weight?: number;
|
|
553
|
-
appKey?: string;
|
|
554
|
-
nodeKey?: string;
|
|
555
673
|
}
|
|
556
674
|
interface InheritanceRelation {
|
|
557
675
|
id: string;
|
|
@@ -569,14 +687,16 @@ interface UserPermissionOverride {
|
|
|
569
687
|
expiresAt?: string | null;
|
|
570
688
|
createdAt?: string;
|
|
571
689
|
}
|
|
690
|
+
/**
|
|
691
|
+
* Task #130 — `appKey` and `nodeKey` are REQUIRED. See `AddGroupPermissionRequest`
|
|
692
|
+
* for rationale.
|
|
693
|
+
*/
|
|
572
694
|
interface AddUserOverrideRequest {
|
|
573
|
-
|
|
574
|
-
|
|
695
|
+
appKey: string;
|
|
696
|
+
nodeKey: string;
|
|
575
697
|
effect: string;
|
|
576
698
|
weight?: number;
|
|
577
699
|
expiresAt?: string;
|
|
578
|
-
appKey?: string;
|
|
579
|
-
nodeKey?: string;
|
|
580
700
|
}
|
|
581
701
|
interface EffectivePermission {
|
|
582
702
|
scope: string;
|
|
@@ -629,13 +749,46 @@ interface Invitation {
|
|
|
629
749
|
invitedBy: string;
|
|
630
750
|
expiresAt?: string;
|
|
631
751
|
createdAt?: string;
|
|
752
|
+
/** Scope the invite grants into ("tenant" | "vendor" | "source" | "client"). */
|
|
753
|
+
scopeType?: string | null;
|
|
754
|
+
/** Scope target id (paired with `scopeType`). */
|
|
755
|
+
scopeId?: string | null;
|
|
756
|
+
/** OIDC client bound for post-accept auto-redirect (paired with `redirectUri`). */
|
|
757
|
+
clientId?: string | null;
|
|
758
|
+
/** Registered redirect URI the new invitee is sent to after account creation. */
|
|
759
|
+
redirectUri?: string | null;
|
|
760
|
+
/** Display name pre-filled on the hosted accept page. */
|
|
761
|
+
inviteeName?: string | null;
|
|
632
762
|
}
|
|
633
763
|
interface CreateInviteRequest {
|
|
634
764
|
email: string;
|
|
635
|
-
|
|
765
|
+
/**
|
|
766
|
+
* Target tenant. Optional for service (API-key) callers — the backend
|
|
767
|
+
* derives the tenant from the key and rejects a mismatching value. Platform
|
|
768
|
+
* admins may target any tenant.
|
|
769
|
+
*/
|
|
770
|
+
tenantId?: string;
|
|
636
771
|
vendorId?: string;
|
|
637
772
|
role: string;
|
|
638
773
|
products?: string[];
|
|
774
|
+
/** Scope to grant into. Must match the backend's accepted values. */
|
|
775
|
+
scopeType?: "tenant" | "vendor" | "source" | "client";
|
|
776
|
+
/** Scope target id (paired with `scopeType`). */
|
|
777
|
+
scopeId?: string;
|
|
778
|
+
/**
|
|
779
|
+
* Opt-in auto-redirect after the invitee creates their account. `clientId`
|
|
780
|
+
* and `redirectUri` are all-or-nothing — pass both or neither. The backend
|
|
781
|
+
* validates that `clientId` is an active OIDC client in the invite's tenant
|
|
782
|
+
* and that `redirectUri` is in that client's registered allowlist, then mints
|
|
783
|
+
* an OIDC authorization code and 302s the brand-new invitee to
|
|
784
|
+
* `${redirectUri}?code=…&state=…`. Point this at your app's
|
|
785
|
+
* `/api/iqauth/callback` so the framework adapter signs the user in on first
|
|
786
|
+
* paint. Existing-user accepts do NOT auto-redirect (see the integration guide).
|
|
787
|
+
*/
|
|
788
|
+
clientId?: string;
|
|
789
|
+
redirectUri?: string;
|
|
790
|
+
/** Optional display name to pre-fill on the hosted accept page. Never used for auth. */
|
|
791
|
+
inviteeName?: string;
|
|
639
792
|
}
|
|
640
793
|
interface InviteValidation {
|
|
641
794
|
valid: boolean;
|
|
@@ -903,4 +1056,4 @@ interface BackupCodeCountResult {
|
|
|
903
1056
|
remainingBackupCodes: number;
|
|
904
1057
|
}
|
|
905
1058
|
|
|
906
|
-
export type {
|
|
1059
|
+
export type { AppManifest as $, ApiSuccessResponse as A, BrandingConfig as B, CreateTenantRequest as C, ApiErrorResponse as D, ApiResponse as E, MfaMethod as F, MfaEnrollment as G, TotpEnrollmentResult as H, IQAuthBrowserSessionClientConfig as I, JwtClaims as J, MfaVerifyResult as K, LoginResult as L, MigrateUserRequest as M, PasswordPolicy as N, OidcDiscovery as O, PromoteToVendorRequest as P, MfaPolicy as Q, UserPermissions as R, SessionUser as S, TokenPair as T, UserProfile as U, ProvisionUserRequest as V, ProvisionUserResponse as W, ExpressMiddlewareOptions as X, IQAuthRetryConfig as Y, IQAuthVerifyConfig as Z, PermissionNodeManifest as _, IQAuthRequestLike as a, BackupCodesResult as a$, AppInfo as a0, PermissionNodeInfo as a1, AppSyncResult as a2, Role as a3, CreateRoleRequest as a4, UpdateRoleRequest as a5, AssignRoleRequest as a6, UserRoleAssignment as a7, UserGroupAssignment as a8, TenantUser as a9, Source as aA, CreateSourceRequest as aB, UpdateSourceRequest as aC, Client as aD, CreateClientRequest as aE, UpdateClientRequest as aF, HierarchyVendor as aG, HierarchySource as aH, HierarchyClient as aI, HierarchyLink as aJ, Membership as aK, CreateMembershipRequest as aL, UpdateMembershipRequest as aM, MembershipWithDetails as aN, AvailableScopesTree as aO, ScopeTreeClient as aP, ScopeTreeSource as aQ, ScopeTreeVendor as aR, ScopeSwitchResult as aS, GdprExportData as aT, PinStatus as aU, PinLoginResult as aV, MfaAvailableMethods as aW, TotpEnrollResult as aX, TotpVerifyResult as aY, SmsEnrollResult as aZ, EmailEnrollResult as a_, PermissionGroup as aa, GroupPermission as ab, AddGroupPermissionRequest as ac, InheritanceRelation as ad, UserPermissionOverride as ae, AddUserOverrideRequest as af, EffectivePermission as ag, PermissionCheckResult as ah, ApiKeyInfo as ai, CreateApiKeyRequest as aj, CreateApiKeyResult as ak, ApiKeyIntrospection as al, Invitation as am, CreateInviteRequest as an, InviteValidation as ao, AcceptInviteRequest as ap, WebhookEndpoint as aq, CreateWebhookRequest as ar, CreateWebhookResult as as, WebhookDelivery as at, WebhookTestResult as au, Entitlement as av, GrantEntitlementRequest as aw, Vendor as ax, CreateVendorRequest as ay, UpdateVendorRequest as az, IQAuthResponseLike as b, BackupCodeCountResult as b0, ScopeHint as b1, SignupRequest as b2, HostedClientContext as b3, IQAuthNextFunction as c, IQAuthEnvironment as d, IQAuthClientConfig as e, IQAuthTokenClientConfig as f, ScopeContext as g, IQAuthClaims as h, IQAuthBaseClaims as i, Tenant as j, TokenAuthenticatedLoginResult as k, SessionAuthenticatedLoginResult as l, Session as m, TenantInfo as n, UpdateTenantRequest as o, PromoteToVendorResult as p, InviteTenantUserRequest as q, InviteTenantUserResult as r, TenantUserRoleUpdate as s, UpdateBrandingRequest as t, BrandingAsset as u, UploadAssetRequest as v, BrandingDomainMapping as w, JwksKey as x, JwksResponse as y, OidcTokenResponse as z };
|
|
@@ -45,6 +45,7 @@ interface IQAuthLocaleBundle {
|
|
|
45
45
|
"signIn.useDifferentAccount": string;
|
|
46
46
|
"signIn.selectTenant": string;
|
|
47
47
|
"signIn.selectTenantSubtitle": string;
|
|
48
|
+
"signIn.selectScope": string;
|
|
48
49
|
"signIn.dividerOr": string;
|
|
49
50
|
"signIn.preparingExperience": string;
|
|
50
51
|
"signIn.applicationUnavailable": string;
|
|
@@ -133,6 +134,11 @@ interface IQAuthLocaleBundle {
|
|
|
133
134
|
"orgSwitcher.createNew": string;
|
|
134
135
|
"orgSwitcher.manage": string;
|
|
135
136
|
"orgSwitcher.noOrgs": string;
|
|
137
|
+
"orgSwitcher.mfaRequiredTitle": string;
|
|
138
|
+
"orgSwitcher.mfaRequiredBody": string;
|
|
139
|
+
"orgSwitcher.scopeSelectionRequiredTitle": string;
|
|
140
|
+
"orgSwitcher.scopeSelectionRequiredBody": string;
|
|
141
|
+
"orgSwitcher.continueInHostedSignIn": string;
|
|
136
142
|
"orgProfile.title": string;
|
|
137
143
|
"orgProfile.generalTab": string;
|
|
138
144
|
"orgProfile.membersTab": string;
|
|
@@ -193,4 +199,4 @@ type IQAuthLocaleKey = keyof Omit<IQAuthLocaleBundle, "locale">;
|
|
|
193
199
|
*/
|
|
194
200
|
type IQAuthLocaleOverride = Partial<IQAuthLocaleBundle>;
|
|
195
201
|
|
|
196
|
-
export type { IQAuthLocaleBundle as I,
|
|
202
|
+
export type { IQAuthLocaleBundle as I, IQAuthLocaleKey as a, IQAuthLocaleOverride as b };
|
|
@@ -45,6 +45,7 @@ interface IQAuthLocaleBundle {
|
|
|
45
45
|
"signIn.useDifferentAccount": string;
|
|
46
46
|
"signIn.selectTenant": string;
|
|
47
47
|
"signIn.selectTenantSubtitle": string;
|
|
48
|
+
"signIn.selectScope": string;
|
|
48
49
|
"signIn.dividerOr": string;
|
|
49
50
|
"signIn.preparingExperience": string;
|
|
50
51
|
"signIn.applicationUnavailable": string;
|
|
@@ -133,6 +134,11 @@ interface IQAuthLocaleBundle {
|
|
|
133
134
|
"orgSwitcher.createNew": string;
|
|
134
135
|
"orgSwitcher.manage": string;
|
|
135
136
|
"orgSwitcher.noOrgs": string;
|
|
137
|
+
"orgSwitcher.mfaRequiredTitle": string;
|
|
138
|
+
"orgSwitcher.mfaRequiredBody": string;
|
|
139
|
+
"orgSwitcher.scopeSelectionRequiredTitle": string;
|
|
140
|
+
"orgSwitcher.scopeSelectionRequiredBody": string;
|
|
141
|
+
"orgSwitcher.continueInHostedSignIn": string;
|
|
136
142
|
"orgProfile.title": string;
|
|
137
143
|
"orgProfile.generalTab": string;
|
|
138
144
|
"orgProfile.membersTab": string;
|
|
@@ -193,4 +199,4 @@ type IQAuthLocaleKey = keyof Omit<IQAuthLocaleBundle, "locale">;
|
|
|
193
199
|
*/
|
|
194
200
|
type IQAuthLocaleOverride = Partial<IQAuthLocaleBundle>;
|
|
195
201
|
|
|
196
|
-
export type { IQAuthLocaleBundle as I,
|
|
202
|
+
export type { IQAuthLocaleBundle as I, IQAuthLocaleKey as a, IQAuthLocaleOverride as b };
|
package/dist/webhooks.d.mts
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @iqauth/sdk/webhooks — webhook signature verification.
|
|
2
|
+
* @iqauth/sdk/webhooks — webhook signature verification + event parsing.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* webhook fan-out (src/services/webhook.service.ts). Constant-time compare;
|
|
7
|
-
* tolerance window in seconds (default 300).
|
|
4
|
+
* IQAuth's webhook fan-out emits a CloudEvents-style envelope:
|
|
5
|
+
* { specversion: "1.0", id, type, subject, time, data, tenantId }
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* and signs the raw request body with HMAC-SHA256, exposed via the canonical
|
|
8
|
+
* header `X-IQAuth-Signature: v1=<hex>`. Legacy header names
|
|
9
|
+
* (`X-Webhook-Signature`, `X-IQ-Auth-Signature`, `X-Signature`) are also
|
|
10
|
+
* accepted for one minor with a deprecation log.
|
|
11
|
+
*
|
|
12
|
+
* Two entry points:
|
|
13
|
+
*
|
|
14
|
+
* - `verifyWebhookSignature(opts)` — single-secret verifier kept for
|
|
15
|
+
* back-compat. Accepts both the modern `v1=<hex>` form (sig over body)
|
|
16
|
+
* AND the legacy `t=<unix>,v1=<hex>` form (sig over `${t}.${body}`).
|
|
17
|
+
*
|
|
18
|
+
* - `parseWebhookEvent(rawBody, headers, secrets, opts?)` — modern entry
|
|
19
|
+
* point. Verifies the signature against ANY secret in the array
|
|
20
|
+
* (rotation-safe, subsumes #28), enforces the CloudEvents envelope, and
|
|
21
|
+
* returns a typed `IQAuthEvent` with a stable `idempotencyKey`.
|
|
22
|
+
*
|
|
23
|
+
* Usage (Express, 5 lines):
|
|
24
|
+
*
|
|
25
|
+
* import { parseWebhookEvent } from "@iqauth/sdk/webhooks";
|
|
11
26
|
* app.post("/webhooks/iqauth", express.raw({ type: "application/json" }), (req, res) => {
|
|
12
27
|
* try {
|
|
13
|
-
* const evt =
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* });
|
|
18
|
-
* // evt is the parsed JSON event
|
|
19
|
-
* } catch (err) {
|
|
20
|
-
* return res.status(400).send(err.message);
|
|
21
|
-
* }
|
|
28
|
+
* const evt = parseWebhookEvent(req.body, req.headers, [process.env.IQAUTH_WEBHOOK_SECRET!]);
|
|
29
|
+
* handle(evt); // evt.idempotencyKey is stable across retries
|
|
30
|
+
* res.status(200).end();
|
|
31
|
+
* } catch (err) { res.status(400).send((err as Error).message); }
|
|
22
32
|
* });
|
|
23
33
|
*/
|
|
24
34
|
interface VerifyWebhookOptions {
|
|
@@ -33,6 +43,17 @@ interface VerifyWebhookOptions {
|
|
|
33
43
|
toleranceSeconds?: number;
|
|
34
44
|
/** Override the current time (seconds since epoch) — for tests. */
|
|
35
45
|
nowSeconds?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Security escape hatch (H-3): modern `v1=<hex>`-only deliveries carry no
|
|
48
|
+
* header `t=`, so freshness is derived from the CloudEvents envelope `time`
|
|
49
|
+
* field instead. By default a modern delivery that carries NO enforceable
|
|
50
|
+
* timestamp (no header `t=` and no parseable envelope `time`) is **rejected**
|
|
51
|
+
* so a captured body cannot be replayed indefinitely.
|
|
52
|
+
*
|
|
53
|
+
* Set `true` ONLY for legacy integrations that sign non-envelope bodies with
|
|
54
|
+
* the modern scheme and accept the replay risk. Defaults to `false` (secure).
|
|
55
|
+
*/
|
|
56
|
+
allowMissingTimestamp?: boolean;
|
|
36
57
|
}
|
|
37
58
|
interface IQAuthWebhookEvent {
|
|
38
59
|
id?: string;
|
|
@@ -42,13 +63,58 @@ interface IQAuthWebhookEvent {
|
|
|
42
63
|
createdAt?: number | string;
|
|
43
64
|
[key: string]: unknown;
|
|
44
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Canonical CloudEvents-style event surfaced by `parseWebhookEvent`. Includes
|
|
68
|
+
* a stable `idempotencyKey` (= `id`) so receivers can dedupe retries without
|
|
69
|
+
* inventing their own subject-extraction logic.
|
|
70
|
+
*/
|
|
71
|
+
interface IQAuthEvent<TData = Record<string, unknown>> {
|
|
72
|
+
/** CloudEvents `specversion`. Always `"1.0"` in the canonical envelope. */
|
|
73
|
+
specversion: "1.0";
|
|
74
|
+
/** Unique event id. Same value as `idempotencyKey`. */
|
|
75
|
+
id: string;
|
|
76
|
+
/** Reverse-DNS-like event type (e.g. `user.deactivated`). */
|
|
77
|
+
type: string;
|
|
78
|
+
/** Stable per-event subject (e.g. the user id, invitation id, …). */
|
|
79
|
+
subject: string;
|
|
80
|
+
/** ISO-8601 UTC timestamp the producer assigned. */
|
|
81
|
+
time: string;
|
|
82
|
+
/** Tenant the event was fired for. */
|
|
83
|
+
tenantId: string;
|
|
84
|
+
/** Event-type-specific payload. */
|
|
85
|
+
data: TData;
|
|
86
|
+
/** Stable dedupe key — receivers MUST treat repeats as no-ops. */
|
|
87
|
+
idempotencyKey: string;
|
|
88
|
+
/** Which secret index in the `secrets` array verified the signature.
|
|
89
|
+
* Useful while rotating secrets — log this to confirm callers have moved
|
|
90
|
+
* onto the new key before retiring the old one. */
|
|
91
|
+
verifiedWithSecretIndex: number;
|
|
92
|
+
}
|
|
45
93
|
declare class WebhookSignatureError extends Error {
|
|
46
94
|
code: string;
|
|
47
95
|
constructor(code: string, message: string);
|
|
48
96
|
}
|
|
97
|
+
/** Canonical signature header name. */
|
|
98
|
+
declare const IQAUTH_SIGNATURE_HEADER = "x-iqauth-signature";
|
|
99
|
+
/** Legacy header names accepted for one minor. Receivers SHOULD migrate to
|
|
100
|
+
* `X-IQAuth-Signature`; verifying via these emits a deprecation log. */
|
|
101
|
+
declare const LEGACY_SIGNATURE_HEADERS: readonly ["x-webhook-signature", "x-iq-auth-signature", "x-signature"];
|
|
49
102
|
/**
|
|
50
103
|
* Verify the signature on a webhook payload and return the parsed event.
|
|
51
104
|
* Throws `WebhookSignatureError` on any verification failure.
|
|
105
|
+
*
|
|
106
|
+
* Accepts both the modern `v1=<hex>` header (HMAC over body bytes) and the
|
|
107
|
+
* legacy `t=<unix>,v1=<hex>` header (HMAC over `${t}.${body}`). Replay-window
|
|
108
|
+
* tolerance is enforced for BOTH forms (H-3):
|
|
109
|
+
* - legacy `t=` deliveries: from the header timestamp (checked pre-verify);
|
|
110
|
+
* - modern `v1=`-only deliveries: from the CloudEvents envelope `time` field
|
|
111
|
+
* (checked post-verify). A modern delivery that carries no enforceable
|
|
112
|
+
* timestamp is rejected unless `allowMissingTimestamp` is set.
|
|
113
|
+
*
|
|
114
|
+
* **For canonical Task #129 deliveries, prefer `parseWebhookEvent`** — it
|
|
115
|
+
* validates the full CloudEvents envelope, supports multi-secret rotation, and
|
|
116
|
+
* returns a typed event with a stable `idempotencyKey`. `verifyWebhookSignature`
|
|
117
|
+
* is retained for back-compat with single-secret integrations.
|
|
52
118
|
*/
|
|
53
119
|
declare function verifyWebhookSignature(opts: VerifyWebhookOptions): IQAuthWebhookEvent;
|
|
54
120
|
/**
|
|
@@ -57,5 +123,35 @@ declare function verifyWebhookSignature(opts: VerifyWebhookOptions): IQAuthWebho
|
|
|
57
123
|
* branch on validity rather than catch.
|
|
58
124
|
*/
|
|
59
125
|
declare function isValidWebhookSignature(opts: VerifyWebhookOptions): boolean;
|
|
126
|
+
interface ParseWebhookEventOptions {
|
|
127
|
+
/** How many seconds old the envelope `time` (or legacy `t=`) may be. Default 300. */
|
|
128
|
+
toleranceSeconds?: number;
|
|
129
|
+
/** Override current time (ms since epoch) — for tests. */
|
|
130
|
+
nowMs?: number;
|
|
131
|
+
/** Sink for deprecation logs. Defaults to `console.warn`; pass `() => {}` to silence. */
|
|
132
|
+
onDeprecation?: (message: string) => void;
|
|
133
|
+
}
|
|
134
|
+
type HeadersLike = Record<string, string | string[] | undefined> | {
|
|
135
|
+
get(name: string): string | null;
|
|
136
|
+
} | Headers;
|
|
137
|
+
/**
|
|
138
|
+
* Verify and parse a webhook delivery into the canonical `IQAuthEvent`.
|
|
139
|
+
*
|
|
140
|
+
* Signature is verified against EVERY secret in `secrets` (rotation-safe).
|
|
141
|
+
* Pass `[currentSecret, previousSecret]` during a rotation window so deliveries
|
|
142
|
+
* signed with either key are accepted.
|
|
143
|
+
*
|
|
144
|
+
* Header lookup order:
|
|
145
|
+
* 1. `X-IQAuth-Signature` (canonical)
|
|
146
|
+
* 2. `X-Webhook-Signature` (legacy — emits deprecation log)
|
|
147
|
+
* 3. `X-IQ-Auth-Signature` (legacy — emits deprecation log)
|
|
148
|
+
* 4. `X-Signature` (legacy — emits deprecation log)
|
|
149
|
+
*
|
|
150
|
+
* Throws `WebhookSignatureError` (with a `.code` field) on any failure:
|
|
151
|
+
* `MISSING_HEADER` | `MISSING_SECRET` | `MALFORMED_HEADER`
|
|
152
|
+
* `TIMESTAMP_OUT_OF_TOLERANCE` | `SIGNATURE_MISMATCH`
|
|
153
|
+
* `MALFORMED_BODY` | `MALFORMED_ENVELOPE`
|
|
154
|
+
*/
|
|
155
|
+
declare function parseWebhookEvent<TData = Record<string, unknown>>(rawBody: Buffer | Uint8Array | string, headers: HeadersLike, secrets: string[], opts?: ParseWebhookEventOptions): IQAuthEvent<TData>;
|
|
60
156
|
|
|
61
|
-
export { type IQAuthWebhookEvent, type VerifyWebhookOptions, WebhookSignatureError, isValidWebhookSignature, verifyWebhookSignature };
|
|
157
|
+
export { IQAUTH_SIGNATURE_HEADER, type IQAuthEvent, type IQAuthWebhookEvent, LEGACY_SIGNATURE_HEADERS, type ParseWebhookEventOptions, type VerifyWebhookOptions, WebhookSignatureError, isValidWebhookSignature, parseWebhookEvent, verifyWebhookSignature };
|
package/dist/webhooks.d.ts
CHANGED
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @iqauth/sdk/webhooks — webhook signature verification.
|
|
2
|
+
* @iqauth/sdk/webhooks — webhook signature verification + event parsing.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* webhook fan-out (src/services/webhook.service.ts). Constant-time compare;
|
|
7
|
-
* tolerance window in seconds (default 300).
|
|
4
|
+
* IQAuth's webhook fan-out emits a CloudEvents-style envelope:
|
|
5
|
+
* { specversion: "1.0", id, type, subject, time, data, tenantId }
|
|
8
6
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
7
|
+
* and signs the raw request body with HMAC-SHA256, exposed via the canonical
|
|
8
|
+
* header `X-IQAuth-Signature: v1=<hex>`. Legacy header names
|
|
9
|
+
* (`X-Webhook-Signature`, `X-IQ-Auth-Signature`, `X-Signature`) are also
|
|
10
|
+
* accepted for one minor with a deprecation log.
|
|
11
|
+
*
|
|
12
|
+
* Two entry points:
|
|
13
|
+
*
|
|
14
|
+
* - `verifyWebhookSignature(opts)` — single-secret verifier kept for
|
|
15
|
+
* back-compat. Accepts both the modern `v1=<hex>` form (sig over body)
|
|
16
|
+
* AND the legacy `t=<unix>,v1=<hex>` form (sig over `${t}.${body}`).
|
|
17
|
+
*
|
|
18
|
+
* - `parseWebhookEvent(rawBody, headers, secrets, opts?)` — modern entry
|
|
19
|
+
* point. Verifies the signature against ANY secret in the array
|
|
20
|
+
* (rotation-safe, subsumes #28), enforces the CloudEvents envelope, and
|
|
21
|
+
* returns a typed `IQAuthEvent` with a stable `idempotencyKey`.
|
|
22
|
+
*
|
|
23
|
+
* Usage (Express, 5 lines):
|
|
24
|
+
*
|
|
25
|
+
* import { parseWebhookEvent } from "@iqauth/sdk/webhooks";
|
|
11
26
|
* app.post("/webhooks/iqauth", express.raw({ type: "application/json" }), (req, res) => {
|
|
12
27
|
* try {
|
|
13
|
-
* const evt =
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* });
|
|
18
|
-
* // evt is the parsed JSON event
|
|
19
|
-
* } catch (err) {
|
|
20
|
-
* return res.status(400).send(err.message);
|
|
21
|
-
* }
|
|
28
|
+
* const evt = parseWebhookEvent(req.body, req.headers, [process.env.IQAUTH_WEBHOOK_SECRET!]);
|
|
29
|
+
* handle(evt); // evt.idempotencyKey is stable across retries
|
|
30
|
+
* res.status(200).end();
|
|
31
|
+
* } catch (err) { res.status(400).send((err as Error).message); }
|
|
22
32
|
* });
|
|
23
33
|
*/
|
|
24
34
|
interface VerifyWebhookOptions {
|
|
@@ -33,6 +43,17 @@ interface VerifyWebhookOptions {
|
|
|
33
43
|
toleranceSeconds?: number;
|
|
34
44
|
/** Override the current time (seconds since epoch) — for tests. */
|
|
35
45
|
nowSeconds?: number;
|
|
46
|
+
/**
|
|
47
|
+
* Security escape hatch (H-3): modern `v1=<hex>`-only deliveries carry no
|
|
48
|
+
* header `t=`, so freshness is derived from the CloudEvents envelope `time`
|
|
49
|
+
* field instead. By default a modern delivery that carries NO enforceable
|
|
50
|
+
* timestamp (no header `t=` and no parseable envelope `time`) is **rejected**
|
|
51
|
+
* so a captured body cannot be replayed indefinitely.
|
|
52
|
+
*
|
|
53
|
+
* Set `true` ONLY for legacy integrations that sign non-envelope bodies with
|
|
54
|
+
* the modern scheme and accept the replay risk. Defaults to `false` (secure).
|
|
55
|
+
*/
|
|
56
|
+
allowMissingTimestamp?: boolean;
|
|
36
57
|
}
|
|
37
58
|
interface IQAuthWebhookEvent {
|
|
38
59
|
id?: string;
|
|
@@ -42,13 +63,58 @@ interface IQAuthWebhookEvent {
|
|
|
42
63
|
createdAt?: number | string;
|
|
43
64
|
[key: string]: unknown;
|
|
44
65
|
}
|
|
66
|
+
/**
|
|
67
|
+
* Canonical CloudEvents-style event surfaced by `parseWebhookEvent`. Includes
|
|
68
|
+
* a stable `idempotencyKey` (= `id`) so receivers can dedupe retries without
|
|
69
|
+
* inventing their own subject-extraction logic.
|
|
70
|
+
*/
|
|
71
|
+
interface IQAuthEvent<TData = Record<string, unknown>> {
|
|
72
|
+
/** CloudEvents `specversion`. Always `"1.0"` in the canonical envelope. */
|
|
73
|
+
specversion: "1.0";
|
|
74
|
+
/** Unique event id. Same value as `idempotencyKey`. */
|
|
75
|
+
id: string;
|
|
76
|
+
/** Reverse-DNS-like event type (e.g. `user.deactivated`). */
|
|
77
|
+
type: string;
|
|
78
|
+
/** Stable per-event subject (e.g. the user id, invitation id, …). */
|
|
79
|
+
subject: string;
|
|
80
|
+
/** ISO-8601 UTC timestamp the producer assigned. */
|
|
81
|
+
time: string;
|
|
82
|
+
/** Tenant the event was fired for. */
|
|
83
|
+
tenantId: string;
|
|
84
|
+
/** Event-type-specific payload. */
|
|
85
|
+
data: TData;
|
|
86
|
+
/** Stable dedupe key — receivers MUST treat repeats as no-ops. */
|
|
87
|
+
idempotencyKey: string;
|
|
88
|
+
/** Which secret index in the `secrets` array verified the signature.
|
|
89
|
+
* Useful while rotating secrets — log this to confirm callers have moved
|
|
90
|
+
* onto the new key before retiring the old one. */
|
|
91
|
+
verifiedWithSecretIndex: number;
|
|
92
|
+
}
|
|
45
93
|
declare class WebhookSignatureError extends Error {
|
|
46
94
|
code: string;
|
|
47
95
|
constructor(code: string, message: string);
|
|
48
96
|
}
|
|
97
|
+
/** Canonical signature header name. */
|
|
98
|
+
declare const IQAUTH_SIGNATURE_HEADER = "x-iqauth-signature";
|
|
99
|
+
/** Legacy header names accepted for one minor. Receivers SHOULD migrate to
|
|
100
|
+
* `X-IQAuth-Signature`; verifying via these emits a deprecation log. */
|
|
101
|
+
declare const LEGACY_SIGNATURE_HEADERS: readonly ["x-webhook-signature", "x-iq-auth-signature", "x-signature"];
|
|
49
102
|
/**
|
|
50
103
|
* Verify the signature on a webhook payload and return the parsed event.
|
|
51
104
|
* Throws `WebhookSignatureError` on any verification failure.
|
|
105
|
+
*
|
|
106
|
+
* Accepts both the modern `v1=<hex>` header (HMAC over body bytes) and the
|
|
107
|
+
* legacy `t=<unix>,v1=<hex>` header (HMAC over `${t}.${body}`). Replay-window
|
|
108
|
+
* tolerance is enforced for BOTH forms (H-3):
|
|
109
|
+
* - legacy `t=` deliveries: from the header timestamp (checked pre-verify);
|
|
110
|
+
* - modern `v1=`-only deliveries: from the CloudEvents envelope `time` field
|
|
111
|
+
* (checked post-verify). A modern delivery that carries no enforceable
|
|
112
|
+
* timestamp is rejected unless `allowMissingTimestamp` is set.
|
|
113
|
+
*
|
|
114
|
+
* **For canonical Task #129 deliveries, prefer `parseWebhookEvent`** — it
|
|
115
|
+
* validates the full CloudEvents envelope, supports multi-secret rotation, and
|
|
116
|
+
* returns a typed event with a stable `idempotencyKey`. `verifyWebhookSignature`
|
|
117
|
+
* is retained for back-compat with single-secret integrations.
|
|
52
118
|
*/
|
|
53
119
|
declare function verifyWebhookSignature(opts: VerifyWebhookOptions): IQAuthWebhookEvent;
|
|
54
120
|
/**
|
|
@@ -57,5 +123,35 @@ declare function verifyWebhookSignature(opts: VerifyWebhookOptions): IQAuthWebho
|
|
|
57
123
|
* branch on validity rather than catch.
|
|
58
124
|
*/
|
|
59
125
|
declare function isValidWebhookSignature(opts: VerifyWebhookOptions): boolean;
|
|
126
|
+
interface ParseWebhookEventOptions {
|
|
127
|
+
/** How many seconds old the envelope `time` (or legacy `t=`) may be. Default 300. */
|
|
128
|
+
toleranceSeconds?: number;
|
|
129
|
+
/** Override current time (ms since epoch) — for tests. */
|
|
130
|
+
nowMs?: number;
|
|
131
|
+
/** Sink for deprecation logs. Defaults to `console.warn`; pass `() => {}` to silence. */
|
|
132
|
+
onDeprecation?: (message: string) => void;
|
|
133
|
+
}
|
|
134
|
+
type HeadersLike = Record<string, string | string[] | undefined> | {
|
|
135
|
+
get(name: string): string | null;
|
|
136
|
+
} | Headers;
|
|
137
|
+
/**
|
|
138
|
+
* Verify and parse a webhook delivery into the canonical `IQAuthEvent`.
|
|
139
|
+
*
|
|
140
|
+
* Signature is verified against EVERY secret in `secrets` (rotation-safe).
|
|
141
|
+
* Pass `[currentSecret, previousSecret]` during a rotation window so deliveries
|
|
142
|
+
* signed with either key are accepted.
|
|
143
|
+
*
|
|
144
|
+
* Header lookup order:
|
|
145
|
+
* 1. `X-IQAuth-Signature` (canonical)
|
|
146
|
+
* 2. `X-Webhook-Signature` (legacy — emits deprecation log)
|
|
147
|
+
* 3. `X-IQ-Auth-Signature` (legacy — emits deprecation log)
|
|
148
|
+
* 4. `X-Signature` (legacy — emits deprecation log)
|
|
149
|
+
*
|
|
150
|
+
* Throws `WebhookSignatureError` (with a `.code` field) on any failure:
|
|
151
|
+
* `MISSING_HEADER` | `MISSING_SECRET` | `MALFORMED_HEADER`
|
|
152
|
+
* `TIMESTAMP_OUT_OF_TOLERANCE` | `SIGNATURE_MISMATCH`
|
|
153
|
+
* `MALFORMED_BODY` | `MALFORMED_ENVELOPE`
|
|
154
|
+
*/
|
|
155
|
+
declare function parseWebhookEvent<TData = Record<string, unknown>>(rawBody: Buffer | Uint8Array | string, headers: HeadersLike, secrets: string[], opts?: ParseWebhookEventOptions): IQAuthEvent<TData>;
|
|
60
156
|
|
|
61
|
-
export { type IQAuthWebhookEvent, type VerifyWebhookOptions, WebhookSignatureError, isValidWebhookSignature, verifyWebhookSignature };
|
|
157
|
+
export { IQAUTH_SIGNATURE_HEADER, type IQAuthEvent, type IQAuthWebhookEvent, LEGACY_SIGNATURE_HEADERS, type ParseWebhookEventOptions, type VerifyWebhookOptions, WebhookSignatureError, isValidWebhookSignature, parseWebhookEvent, verifyWebhookSignature };
|