@~lyre/auth 0.0.8 → 0.1.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 CHANGED
@@ -53,6 +53,43 @@ export const handle = createAuthHandle({
53
53
  `@sveltejs/kit` is an **optional peer dependency** — only required if you import
54
54
  the `/sveltekit` entry.
55
55
 
56
+ ### `@~lyre/auth/nest` — Nest / Fastify adapter
57
+
58
+ For services on Nest + Fastify. Adds **no dependencies** — nothing here imports
59
+ `@nestjs/*` or `fastify`; you supply the guard class and DI wiring, the SDK
60
+ supplies the Accounts logic. Works from a plain Fastify app too: any request
61
+ whose `headers` is a plain object satisfies the input type.
62
+
63
+ ```ts
64
+ import { createServiceKeyResolver, createUserSessionResolver, hasScopes }
65
+ from '@~lyre/auth/nest';
66
+
67
+ const keys = createServiceKeyResolver({ config: { baseUrl: ACCOUNTS_BASE_URL } });
68
+ const sessions = createUserSessionResolver({ config: { baseUrl: ACCOUNTS_BASE_URL } });
69
+
70
+ // In a Nest guard (req is the Fastify request):
71
+ const key = await keys.resolve(req);
72
+ if (key.status === 'invalid') throw new UnauthorizedException(); // present but bad
73
+ if (key.status === 'ok') {
74
+ if (!hasScopes(key.context, ['my-app:things:read'])) throw new ForbiddenException();
75
+ // key.context.accessibleApps tells you which apps this key may act on
76
+ }
77
+
78
+ // A user session bearer resolves to who + which app, from the token alone:
79
+ const user = await sessions.resolve(req);
80
+ // → { userId, sessionId, appId, tenantId, email, name } | null
81
+ ```
82
+
83
+ Semantics match `createServiceKeyHandle`: **absent key → `absent`** (fall through to
84
+ other auth), **present-but-invalid → `invalid`** (reject rather than silently
85
+ continue). Introspection is cached by `sha256` of the credential — negative
86
+ results too, so a bad key can't hammer Accounts. Neither resolver throws: a
87
+ transport failure reads as "not authenticated", so a flaky Accounts cannot 500
88
+ the consuming service.
89
+
90
+ `resolve()` needs `GET /api/auth/me` to return `session.appId` (Accounts ≥ the
91
+ release that added it); older deployments yield `appId: null`.
92
+
56
93
  ## Publishing
57
94
 
58
95
  See [PUBLISHING.md](PUBLISHING.md). Published to npm via GitHub Actions Trusted
@@ -278,6 +278,41 @@ async function introspectServiceKey(input) {
278
278
  return null;
279
279
  }
280
280
  }
281
+ async function logoutFromAccounts(input) {
282
+ const base = normalizeOptionalString(input.config.baseUrl);
283
+ if (!base || !input.accessToken) return { success: false };
284
+ const fetchImpl = input.fetchImpl ?? fetch;
285
+ try {
286
+ const res = await fetchImpl(new URL("/api/auth/logout", base), {
287
+ method: "POST",
288
+ headers: {
289
+ "content-type": "application/json",
290
+ authorization: `Bearer ${input.accessToken}`,
291
+ accept: "application/json"
292
+ },
293
+ body: JSON.stringify({ all: input.all ?? false })
294
+ });
295
+ if (!res.ok) return { success: false };
296
+ return await res.json();
297
+ } catch {
298
+ return { success: false };
299
+ }
300
+ }
301
+ function verifyWebhookSignature(rawBody, signature, secret) {
302
+ if (!signature || !secret) return false;
303
+ const provided = (signature.startsWith("sha256=") ? signature.slice(7) : signature).toLowerCase();
304
+ if (!/^[0-9a-f]{64}$/.test(provided)) return false;
305
+ const expected = createHmac("sha256", secret).update(rawBody).digest("hex");
306
+ const a = Buffer.from(provided, "hex");
307
+ const b = Buffer.from(expected, "hex");
308
+ if (a.length !== b.length) return false;
309
+ return timingSafeEqual(a, b);
310
+ }
311
+ function normalizeAccountsWebhook(payload) {
312
+ const body = payload && typeof payload === "object" ? payload : {};
313
+ const type = String(body.event ?? body.type ?? "unknown");
314
+ return { ...body, type, data: body.data };
315
+ }
281
316
  function scopeSatisfied(granted, required) {
282
317
  if (!granted || granted.length === 0) return false;
283
318
  return granted.includes(required);
@@ -296,5 +331,8 @@ export {
296
331
  clearPlatformSessionCookie,
297
332
  serviceKeyFromRequest,
298
333
  introspectServiceKey,
334
+ logoutFromAccounts,
335
+ verifyWebhookSignature,
336
+ normalizeAccountsWebhook,
299
337
  scopeSatisfied
300
338
  };
package/dist/index.d.ts CHANGED
@@ -162,6 +162,30 @@ declare function introspectServiceKey(input: {
162
162
  key: string;
163
163
  fetchImpl?: typeof fetch;
164
164
  }): Promise<ServiceKeyContext | null>;
165
+ declare function logoutFromAccounts(input: {
166
+ config: Pick<AccountsClientConfig, 'baseUrl'>;
167
+ accessToken: string;
168
+ all?: boolean;
169
+ fetchImpl?: typeof fetch;
170
+ }): Promise<{
171
+ success: boolean;
172
+ revokedCount?: number;
173
+ }>;
174
+ declare function verifyWebhookSignature(rawBody: string, signature: string | null | undefined, secret: string): boolean;
175
+ type AccountsWebhookEvent = {
176
+ type: string;
177
+ data?: unknown;
178
+ [key: string]: unknown;
179
+ };
180
+ /**
181
+ * Normalize an Accounts webhook body into a stable `AccountsWebhookEvent`.
182
+ *
183
+ * The Accounts webhook worker sends `{ event, timestamp, data }` (the event NAME under `event`,
184
+ * the payload under `data`). Older/other senders may put the name under `type`. This collapses both
185
+ * so a consumer always reads `event.type` and `event.data`, and keeps the raw fields alongside.
186
+ * Framework-agnostic so both the SvelteKit handle and the Nest helper share one shape.
187
+ */
188
+ declare function normalizeAccountsWebhook(payload: unknown): AccountsWebhookEvent;
165
189
  declare function scopeSatisfied(granted: string[] | undefined, required: string): boolean;
166
190
 
167
- export { type AccountsClientConfig, type AccountsIdentity, type AccountsTokenResponse, type AuthenticatedPrincipal, type AuthorizationState, type CookieOptions, type LocalUser, type PermissionSet, type PlatformSession, type ServiceKeyApp, type ServiceKeyContext, type SyncAccountsUser, type SyncAccountsUserResult, type TenantAccessContext, type TenantContext, type TenantMembership, beginAccountsLoginRedirect, clearPlatformSessionCookie, createAccountsClientConfig, createPlatformAuth, exchangeAuthorizationCode, handleAccountsCallback, identityPassthroughSync, introspectServiceKey, readPlatformSessionCookie, resolveActiveTenant, scopeSatisfied, serviceKeyFromRequest, syncAccountsUser };
191
+ export { type AccountsClientConfig, type AccountsIdentity, type AccountsTokenResponse, type AccountsWebhookEvent, type AuthenticatedPrincipal, type AuthorizationState, type CookieOptions, type LocalUser, type PermissionSet, type PlatformSession, type ServiceKeyApp, type ServiceKeyContext, type SyncAccountsUser, type SyncAccountsUserResult, type TenantAccessContext, type TenantContext, type TenantMembership, beginAccountsLoginRedirect, clearPlatformSessionCookie, createAccountsClientConfig, createPlatformAuth, exchangeAuthorizationCode, handleAccountsCallback, identityPassthroughSync, introspectServiceKey, logoutFromAccounts, normalizeAccountsWebhook, readPlatformSessionCookie, resolveActiveTenant, scopeSatisfied, serviceKeyFromRequest, syncAccountsUser, verifyWebhookSignature };
package/dist/index.js CHANGED
@@ -7,12 +7,15 @@ import {
7
7
  handleAccountsCallback,
8
8
  identityPassthroughSync,
9
9
  introspectServiceKey,
10
+ logoutFromAccounts,
11
+ normalizeAccountsWebhook,
10
12
  readPlatformSessionCookie,
11
13
  resolveActiveTenant,
12
14
  scopeSatisfied,
13
15
  serviceKeyFromRequest,
14
- syncAccountsUser
15
- } from "./chunk-2FSPKJAC.js";
16
+ syncAccountsUser,
17
+ verifyWebhookSignature
18
+ } from "./chunk-TQNQLX3C.js";
16
19
  export {
17
20
  beginAccountsLoginRedirect,
18
21
  clearPlatformSessionCookie,
@@ -22,9 +25,12 @@ export {
22
25
  handleAccountsCallback,
23
26
  identityPassthroughSync,
24
27
  introspectServiceKey,
28
+ logoutFromAccounts,
29
+ normalizeAccountsWebhook,
25
30
  readPlatformSessionCookie,
26
31
  resolveActiveTenant,
27
32
  scopeSatisfied,
28
33
  serviceKeyFromRequest,
29
- syncAccountsUser
34
+ syncAccountsUser,
35
+ verifyWebhookSignature
30
36
  };
package/dist/nest.d.ts ADDED
@@ -0,0 +1,85 @@
1
+ import { AccountsWebhookEvent, ServiceKeyContext, AccountsClientConfig } from './index.js';
2
+ export { normalizeAccountsWebhook } from './index.js';
3
+
4
+ /**
5
+ * The minimum a request must expose. Fastify's `request` satisfies this as-is
6
+ * (`headers` is a plain object), as does Node's IncomingMessage.
7
+ */
8
+ type HeaderCarrier = {
9
+ headers: Record<string, string | string[] | undefined>;
10
+ };
11
+ /**
12
+ * Adapt a header-bearing request to the web `Request` that the core helpers expect.
13
+ * Only headers are needed — `serviceKeyFromRequest` reads `x-api-key` / `authorization`.
14
+ */
15
+ declare function toWebRequest(carrier: HeaderCarrier): Request;
16
+ /** Extract an `sk_` service key from a Fastify/Node request, or null when absent. */
17
+ declare function serviceKeyFromHeaders(carrier: HeaderCarrier): string | null;
18
+ type ServiceKeyResolverOptions = {
19
+ config: Pick<AccountsClientConfig, 'baseUrl'>;
20
+ fetchImpl?: typeof fetch;
21
+ /** Introspection cache TTL in ms. Default 60_000; 0 disables caching. */
22
+ cacheTtlMs?: number;
23
+ };
24
+ type ServiceKeyResolution = {
25
+ status: 'absent';
26
+ } | {
27
+ status: 'invalid';
28
+ } | {
29
+ status: 'ok';
30
+ context: ServiceKeyContext;
31
+ };
32
+ declare function createServiceKeyResolver(opts: ServiceKeyResolverOptions): {
33
+ resolve: (carrier: HeaderCarrier) => Promise<ServiceKeyResolution>;
34
+ invalidate: (key: string) => void;
35
+ };
36
+ type UserSessionContext = {
37
+ userId: string;
38
+ email?: string;
39
+ name?: string;
40
+ sessionId: string;
41
+ /** The app the session was issued for. Null for sessions created outside an app context. */
42
+ appId: string | null;
43
+ tenantId: string | null;
44
+ expiresAt?: string;
45
+ };
46
+ type UserSessionResolverOptions = {
47
+ config: Pick<AccountsClientConfig, 'baseUrl'>;
48
+ fetchImpl?: typeof fetch;
49
+ /** Cache TTL in ms. Default 60_000; 0 disables caching. */
50
+ cacheTtlMs?: number;
51
+ };
52
+ /** A session bearer is any Authorization: Bearer token that is NOT an `sk_` service key. */
53
+ declare function sessionTokenFromHeaders(carrier: HeaderCarrier): string | null;
54
+ declare function createUserSessionResolver(opts: UserSessionResolverOptions): {
55
+ resolve: (carrier: HeaderCarrier) => Promise<UserSessionContext | null>;
56
+ invalidate: (token: string) => void;
57
+ };
58
+ /**
59
+ * Whether a resolved key grants every required scope. Uses the core's `scopeSatisfied`
60
+ * (flat exact match, no wildcards) so behaviour matches Accounts' own enforcement.
61
+ */
62
+ declare function hasScopes(ctx: ServiceKeyContext, required: readonly string[]): boolean;
63
+ /** Whether a resolved key may act on an app, matched by Accounts app id OR slug. */
64
+ declare function canAccessApp(ctx: ServiceKeyContext, appIdOrSlug: string): boolean;
65
+ type AccountsWebhookResult = {
66
+ ok: true;
67
+ event: AccountsWebhookEvent;
68
+ } | {
69
+ ok: false;
70
+ reason: 'bad_signature' | 'bad_json';
71
+ };
72
+ /** Header the Accounts webhook worker sends (HMAC-SHA256 hex of the raw body). */
73
+ declare const ACCOUNTS_WEBHOOK_SIGNATURE_HEADER = "x-webhook-signature";
74
+ /**
75
+ * Verify an Accounts webhook and parse it into a normalized `{ type, data, ... }` event.
76
+ *
77
+ * Pass the RAW request body (a Nest/Fastify controller must read the raw body, not a re-serialized
78
+ * object — the signature is over the exact bytes), the signature header value
79
+ * (`request.headers['x-webhook-signature']`), and the shared secret. Returns `{ok:false}` on a bad
80
+ * signature or unparseable JSON so the controller can 401/400; `{ok:true, event}` otherwise. The
81
+ * controller then reacts to `event.type` / `event.data` and returns 200 (so Accounts doesn't retry).
82
+ */
83
+ declare function verifyAndParseAccountsWebhook(rawBody: string, signatureHeaderValue: string | string[] | undefined, secret: string): AccountsWebhookResult;
84
+
85
+ export { ACCOUNTS_WEBHOOK_SIGNATURE_HEADER, AccountsClientConfig, AccountsWebhookEvent, type AccountsWebhookResult, type HeaderCarrier, ServiceKeyContext, type ServiceKeyResolution, type ServiceKeyResolverOptions, type UserSessionContext, type UserSessionResolverOptions, canAccessApp, createServiceKeyResolver, createUserSessionResolver, hasScopes, serviceKeyFromHeaders, sessionTokenFromHeaders, toWebRequest, verifyAndParseAccountsWebhook };
package/dist/nest.js ADDED
@@ -0,0 +1,134 @@
1
+ import {
2
+ introspectServiceKey,
3
+ normalizeAccountsWebhook,
4
+ scopeSatisfied,
5
+ serviceKeyFromRequest,
6
+ verifyWebhookSignature
7
+ } from "./chunk-TQNQLX3C.js";
8
+
9
+ // src/nest.ts
10
+ import { createHash } from "crypto";
11
+ function headerValue(carrier, name) {
12
+ const v = carrier.headers[name] ?? carrier.headers[name.toLowerCase()];
13
+ return Array.isArray(v) ? v[0] : v;
14
+ }
15
+ function toWebRequest(carrier) {
16
+ const headers = new Headers();
17
+ for (const [k, v] of Object.entries(carrier.headers)) {
18
+ if (v === void 0) continue;
19
+ if (Array.isArray(v)) {
20
+ for (const item of v) headers.append(k, item);
21
+ } else {
22
+ headers.set(k, v);
23
+ }
24
+ }
25
+ return new Request("http://localhost/", { headers });
26
+ }
27
+ function serviceKeyFromHeaders(carrier) {
28
+ return serviceKeyFromRequest(toWebRequest(carrier));
29
+ }
30
+ function createServiceKeyResolver(opts) {
31
+ const ttl = opts.cacheTtlMs ?? 6e4;
32
+ const cache = /* @__PURE__ */ new Map();
33
+ async function resolve(carrier) {
34
+ const key = serviceKeyFromHeaders(carrier);
35
+ if (!key) return { status: "absent" };
36
+ const hash = createHash("sha256").update(key).digest("hex");
37
+ const now = Date.now();
38
+ let ctx;
39
+ const cached = ttl > 0 ? cache.get(hash) : void 0;
40
+ if (cached && cached.expires > now) {
41
+ ctx = cached.value;
42
+ } else {
43
+ ctx = await introspectServiceKey({ config: opts.config, key, fetchImpl: opts.fetchImpl });
44
+ if (ttl > 0) cache.set(hash, { value: ctx, expires: now + ttl });
45
+ }
46
+ return ctx ? { status: "ok", context: ctx } : { status: "invalid" };
47
+ }
48
+ function invalidate(key) {
49
+ cache.delete(createHash("sha256").update(key).digest("hex"));
50
+ }
51
+ return { resolve, invalidate };
52
+ }
53
+ function sessionTokenFromHeaders(carrier) {
54
+ const auth = headerValue(carrier, "authorization");
55
+ if (!auth || !auth.startsWith("Bearer ")) return null;
56
+ const token = auth.slice(7).trim();
57
+ if (!token || token.startsWith("sk_")) return null;
58
+ return token;
59
+ }
60
+ function createUserSessionResolver(opts) {
61
+ const ttl = opts.cacheTtlMs ?? 6e4;
62
+ const cache = /* @__PURE__ */ new Map();
63
+ const fetchImpl = opts.fetchImpl ?? fetch;
64
+ async function introspect(token) {
65
+ const base = opts.config.baseUrl?.replace(/\/$/, "");
66
+ if (!base || !token) return null;
67
+ try {
68
+ const res = await fetchImpl(new URL("/api/auth/me", base), {
69
+ headers: { authorization: `Bearer ${token}`, accept: "application/json" }
70
+ });
71
+ if (!res.ok) return null;
72
+ const data = await res.json();
73
+ const userId = data.user?.id;
74
+ const sessionId = data.session?.id;
75
+ if (!userId || !sessionId) return null;
76
+ return {
77
+ userId,
78
+ email: data.user?.emailAddress ?? data.user?.email,
79
+ name: data.user?.name,
80
+ sessionId,
81
+ appId: data.session?.appId ?? null,
82
+ tenantId: data.session?.tenantId ?? null,
83
+ expiresAt: data.session?.expiresAt
84
+ };
85
+ } catch {
86
+ return null;
87
+ }
88
+ }
89
+ async function resolve(carrier) {
90
+ const token = sessionTokenFromHeaders(carrier);
91
+ if (!token) return null;
92
+ const hash = createHash("sha256").update(token).digest("hex");
93
+ const now = Date.now();
94
+ const cached = ttl > 0 ? cache.get(hash) : void 0;
95
+ if (cached && cached.expires > now) return cached.value;
96
+ const ctx = await introspect(token);
97
+ if (ttl > 0) cache.set(hash, { value: ctx, expires: now + ttl });
98
+ return ctx;
99
+ }
100
+ function invalidate(token) {
101
+ cache.delete(createHash("sha256").update(token).digest("hex"));
102
+ }
103
+ return { resolve, invalidate };
104
+ }
105
+ function hasScopes(ctx, required) {
106
+ return required.every((scope) => scopeSatisfied(ctx.scopes, scope));
107
+ }
108
+ function canAccessApp(ctx, appIdOrSlug) {
109
+ return ctx.accessibleApps.some((a) => a.id === appIdOrSlug || a.slug === appIdOrSlug);
110
+ }
111
+ var ACCOUNTS_WEBHOOK_SIGNATURE_HEADER = "x-webhook-signature";
112
+ function verifyAndParseAccountsWebhook(rawBody, signatureHeaderValue, secret) {
113
+ const signature = Array.isArray(signatureHeaderValue) ? signatureHeaderValue[0] : signatureHeaderValue;
114
+ if (!verifyWebhookSignature(rawBody, signature ?? null, secret)) {
115
+ return { ok: false, reason: "bad_signature" };
116
+ }
117
+ try {
118
+ return { ok: true, event: normalizeAccountsWebhook(JSON.parse(rawBody)) };
119
+ } catch {
120
+ return { ok: false, reason: "bad_json" };
121
+ }
122
+ }
123
+ export {
124
+ ACCOUNTS_WEBHOOK_SIGNATURE_HEADER,
125
+ canAccessApp,
126
+ createServiceKeyResolver,
127
+ createUserSessionResolver,
128
+ hasScopes,
129
+ normalizeAccountsWebhook,
130
+ serviceKeyFromHeaders,
131
+ sessionTokenFromHeaders,
132
+ toWebRequest,
133
+ verifyAndParseAccountsWebhook
134
+ };
@@ -1,5 +1,5 @@
1
1
  import { RequestEvent, Handle } from '@sveltejs/kit';
2
- import { PlatformSession, AccountsClientConfig, ServiceKeyContext, SyncAccountsUser, CookieOptions } from './index.js';
2
+ import { AccountsWebhookEvent, PlatformSession, AccountsClientConfig, ServiceKeyContext, SyncAccountsUser, CookieOptions } from './index.js';
3
3
  export { clearPlatformSessionCookie } from './index.js';
4
4
 
5
5
  type SvelteKitAuthOptions = {
@@ -51,5 +51,18 @@ type ServiceKeyLocals = {
51
51
  serviceKey?: ServiceKeyContext | null;
52
52
  };
53
53
  declare function createServiceKeyHandle(opts: ServiceKeyHandleOptions): Handle;
54
+ type AccountsWebhookOptions = {
55
+ secret: string;
56
+ /** Route to receive webhooks on. Default '/api/auth/accounts/webhook'. */
57
+ path?: string;
58
+ /**
59
+ * Header carrying the signature. Default 'x-webhook-signature' — the header the Accounts
60
+ * webhook worker actually sends (HMAC-SHA256 hex of the raw body).
61
+ */
62
+ signatureHeader?: string;
63
+ /** React to a verified event. Throwing yields a 500 (Accounts may retry). */
64
+ onEvent: (event: AccountsWebhookEvent, raw: string, request: RequestEvent) => void | Promise<void>;
65
+ };
66
+ declare function createAccountsWebhookHandle(opts: AccountsWebhookOptions): Handle;
54
67
 
55
- export { type PlatformLocals, type ServiceKeyHandleOptions, type ServiceKeyLocals, type SvelteKitAuthOptions, createAuthHandle, createServiceKeyHandle };
68
+ export { type AccountsWebhookOptions, type PlatformLocals, type ServiceKeyHandleOptions, type ServiceKeyLocals, type SvelteKitAuthOptions, createAccountsWebhookHandle, createAuthHandle, createServiceKeyHandle };
package/dist/sveltekit.js CHANGED
@@ -4,9 +4,12 @@ import {
4
4
  handleAccountsCallback,
5
5
  identityPassthroughSync,
6
6
  introspectServiceKey,
7
+ logoutFromAccounts,
8
+ normalizeAccountsWebhook,
7
9
  readPlatformSessionCookie,
8
- serviceKeyFromRequest
9
- } from "./chunk-2FSPKJAC.js";
10
+ serviceKeyFromRequest,
11
+ verifyWebhookSignature
12
+ } from "./chunk-TQNQLX3C.js";
10
13
 
11
14
  // src/sveltekit.ts
12
15
  import { createHash } from "crypto";
@@ -55,6 +58,10 @@ function createAuthHandle(opts) {
55
58
  ]);
56
59
  }
57
60
  if (path === logoutPath) {
61
+ if (session.accessToken) {
62
+ const all = event.url.searchParams.get("all") === "1" || event.url.searchParams.get("all") === "true";
63
+ await logoutFromAccounts({ config: opts.config, accessToken: session.accessToken, all });
64
+ }
58
65
  return redirectWithCookies(postLogoutPath, [
59
66
  clearPlatformSessionCookie(cookieOptions),
60
67
  `${STATE_COOKIE}=; ${cookieOptions?.sameSite === "none" ? "Path=/; HttpOnly; SameSite=None; Secure" : "Path=/; HttpOnly; SameSite=Lax"}; Max-Age=0`
@@ -102,8 +109,35 @@ function createServiceKeyHandle(opts) {
102
109
  });
103
110
  };
104
111
  }
112
+ function jsonResponse(status, body) {
113
+ return new Response(JSON.stringify(body), { status, headers: { "content-type": "application/json" } });
114
+ }
115
+ function createAccountsWebhookHandle(opts) {
116
+ const path = opts.path ?? "/api/auth/accounts/webhook";
117
+ const sigHeader = opts.signatureHeader ?? "x-webhook-signature";
118
+ return async ({ event, resolve }) => {
119
+ if (event.url.pathname !== path || event.request.method !== "POST") return resolve(event);
120
+ const raw = await event.request.text();
121
+ if (!verifyWebhookSignature(raw, event.request.headers.get(sigHeader), opts.secret)) {
122
+ return jsonResponse(401, { ok: false, error: "Invalid webhook signature." });
123
+ }
124
+ let parsed;
125
+ try {
126
+ parsed = normalizeAccountsWebhook(JSON.parse(raw));
127
+ } catch {
128
+ return jsonResponse(400, { ok: false, error: "Invalid JSON body." });
129
+ }
130
+ try {
131
+ await opts.onEvent(parsed, raw, event);
132
+ } catch {
133
+ return jsonResponse(500, { ok: false, error: "Webhook handler failed." });
134
+ }
135
+ return jsonResponse(200, { ok: true });
136
+ };
137
+ }
105
138
  export {
106
139
  clearPlatformSessionCookie,
140
+ createAccountsWebhookHandle,
107
141
  createAuthHandle,
108
142
  createServiceKeyHandle
109
143
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@~lyre/auth",
3
- "version": "0.0.8",
3
+ "version": "0.1.1",
4
4
  "description": "Shared Axis Accounts auth SDK — framework-agnostic session/identity core (HMAC-signed cookies, accounts login/callback/logout) plus an optional turnkey SvelteKit adapter.",
5
5
  "type": "module",
6
6
  "sideEffects": false,
@@ -33,7 +33,12 @@
33
33
  "./sveltekit": {
34
34
  "types": "./dist/sveltekit.d.ts",
35
35
  "import": "./dist/sveltekit.js"
36
- }
36
+ },
37
+ "./nest": {
38
+ "types": "./dist/nest.d.ts",
39
+ "import": "./dist/nest.js"
40
+ },
41
+ "./package.json": "./package.json"
37
42
  },
38
43
  "files": [
39
44
  "dist",
@@ -41,7 +46,7 @@
41
46
  "LICENSE"
42
47
  ],
43
48
  "scripts": {
44
- "build": "tsup src/index.ts src/sveltekit.ts --format esm --dts --clean",
49
+ "build": "tsup src/index.ts src/sveltekit.ts src/nest.ts --format esm --dts --clean",
45
50
  "check": "tsc --noEmit"
46
51
  },
47
52
  "peerDependencies": {