@~lyre/auth 0.0.7 → 0.0.8

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.
@@ -242,6 +242,46 @@ function readCookie(cookieHeader, name) {
242
242
  }
243
243
  return null;
244
244
  }
245
+ function serviceKeyFromRequest(request) {
246
+ const explicit = request.headers.get("x-api-key");
247
+ if (explicit && explicit.trim()) return explicit.trim();
248
+ const auth = request.headers.get("authorization");
249
+ if (auth && auth.startsWith("Bearer ")) {
250
+ const token = auth.slice(7).trim();
251
+ if (token.startsWith("sk_")) return token;
252
+ }
253
+ return null;
254
+ }
255
+ async function introspectServiceKey(input) {
256
+ const base = normalizeOptionalString(input.config.baseUrl);
257
+ if (!input.key || !base) return null;
258
+ const fetchImpl = input.fetchImpl ?? fetch;
259
+ try {
260
+ const res = await fetchImpl(new URL("/api/auth/service-keys/introspect", base), {
261
+ method: "POST",
262
+ headers: { "x-api-key": input.key, accept: "application/json" }
263
+ });
264
+ if (!res.ok) return null;
265
+ const data = await res.json();
266
+ if (!data.valid || !data.keyId) return null;
267
+ const accessibleApps = (data.accessibleApps ?? []).filter(
268
+ (a) => Boolean(a) && typeof a.id === "string" && typeof a.slug === "string"
269
+ ).map((a) => ({ id: a.id, slug: a.slug, name: a.name }));
270
+ return {
271
+ keyId: data.keyId,
272
+ tenantId: data.tenantId ?? null,
273
+ scopes: Array.isArray(data.scopes) ? data.scopes : [],
274
+ accessibleApps,
275
+ expiresAt: data.expiresAt ?? null
276
+ };
277
+ } catch {
278
+ return null;
279
+ }
280
+ }
281
+ function scopeSatisfied(granted, required) {
282
+ if (!granted || granted.length === 0) return false;
283
+ return granted.includes(required);
284
+ }
245
285
 
246
286
  export {
247
287
  createAccountsClientConfig,
@@ -253,5 +293,8 @@ export {
253
293
  identityPassthroughSync,
254
294
  resolveActiveTenant,
255
295
  readPlatformSessionCookie,
256
- clearPlatformSessionCookie
296
+ clearPlatformSessionCookie,
297
+ serviceKeyFromRequest,
298
+ introspectServiceKey,
299
+ scopeSatisfied
257
300
  };
package/dist/index.d.ts CHANGED
@@ -142,5 +142,26 @@ type CookieOptions = {
142
142
  secure?: boolean;
143
143
  };
144
144
  declare function clearPlatformSessionCookie(opts?: CookieOptions): string;
145
+ type ServiceKeyApp = {
146
+ id: string;
147
+ slug: string;
148
+ name?: string;
149
+ };
150
+ type ServiceKeyContext = {
151
+ keyId: string;
152
+ tenantId: string | null;
153
+ /** Scopes granted to this key (opaque strings owned by the consuming app). */
154
+ scopes: string[];
155
+ /** Apps this key may act on (auth resolves these from the key's app grant). */
156
+ accessibleApps: ServiceKeyApp[];
157
+ expiresAt: string | null;
158
+ };
159
+ declare function serviceKeyFromRequest(request: Request): string | null;
160
+ declare function introspectServiceKey(input: {
161
+ config: Pick<AccountsClientConfig, 'baseUrl'>;
162
+ key: string;
163
+ fetchImpl?: typeof fetch;
164
+ }): Promise<ServiceKeyContext | null>;
165
+ declare function scopeSatisfied(granted: string[] | undefined, required: string): boolean;
145
166
 
146
- export { type AccountsClientConfig, type AccountsIdentity, type AccountsTokenResponse, type AuthenticatedPrincipal, type AuthorizationState, type CookieOptions, type LocalUser, type PermissionSet, type PlatformSession, type SyncAccountsUser, type SyncAccountsUserResult, type TenantAccessContext, type TenantContext, type TenantMembership, beginAccountsLoginRedirect, clearPlatformSessionCookie, createAccountsClientConfig, createPlatformAuth, exchangeAuthorizationCode, handleAccountsCallback, identityPassthroughSync, readPlatformSessionCookie, resolveActiveTenant, syncAccountsUser };
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 };
package/dist/index.js CHANGED
@@ -6,10 +6,13 @@ import {
6
6
  exchangeAuthorizationCode,
7
7
  handleAccountsCallback,
8
8
  identityPassthroughSync,
9
+ introspectServiceKey,
9
10
  readPlatformSessionCookie,
10
11
  resolveActiveTenant,
12
+ scopeSatisfied,
13
+ serviceKeyFromRequest,
11
14
  syncAccountsUser
12
- } from "./chunk-RADVGFYQ.js";
15
+ } from "./chunk-2FSPKJAC.js";
13
16
  export {
14
17
  beginAccountsLoginRedirect,
15
18
  clearPlatformSessionCookie,
@@ -18,7 +21,10 @@ export {
18
21
  exchangeAuthorizationCode,
19
22
  handleAccountsCallback,
20
23
  identityPassthroughSync,
24
+ introspectServiceKey,
21
25
  readPlatformSessionCookie,
22
26
  resolveActiveTenant,
27
+ scopeSatisfied,
28
+ serviceKeyFromRequest,
23
29
  syncAccountsUser
24
30
  };
@@ -1,5 +1,5 @@
1
1
  import { RequestEvent, Handle } from '@sveltejs/kit';
2
- import { PlatformSession, AccountsClientConfig, SyncAccountsUser, CookieOptions } from './index.js';
2
+ import { PlatformSession, AccountsClientConfig, ServiceKeyContext, SyncAccountsUser, CookieOptions } from './index.js';
3
3
  export { clearPlatformSessionCookie } from './index.js';
4
4
 
5
5
  type SvelteKitAuthOptions = {
@@ -35,5 +35,21 @@ type PlatformLocals = {
35
35
  principal: PlatformSession['principal'];
36
36
  };
37
37
  declare function createAuthHandle(opts: SvelteKitAuthOptions): Handle;
38
+ type ServiceKeyHandleOptions = {
39
+ config: AccountsClientConfig;
40
+ fetchImpl?: typeof fetch;
41
+ /** Which requests to attempt key auth on. Default: paths starting with `/api`. */
42
+ match?: (event: RequestEvent) => boolean;
43
+ /** Introspection cache TTL in ms. Default 60_000; set 0 to disable caching. */
44
+ cacheTtlMs?: number;
45
+ /** Map the resolved key grant onto app locals. Called only for a valid key. */
46
+ onResolved?: (event: RequestEvent, ctx: ServiceKeyContext) => void;
47
+ /** Observe a present-but-invalid key (e.g. logging) before the 401 is returned. */
48
+ onInvalid?: (event: RequestEvent) => void;
49
+ };
50
+ type ServiceKeyLocals = {
51
+ serviceKey?: ServiceKeyContext | null;
52
+ };
53
+ declare function createServiceKeyHandle(opts: ServiceKeyHandleOptions): Handle;
38
54
 
39
- export { type PlatformLocals, type SvelteKitAuthOptions, createAuthHandle };
55
+ export { type PlatformLocals, type ServiceKeyHandleOptions, type ServiceKeyLocals, type SvelteKitAuthOptions, createAuthHandle, createServiceKeyHandle };
package/dist/sveltekit.js CHANGED
@@ -3,10 +3,13 @@ import {
3
3
  clearPlatformSessionCookie,
4
4
  handleAccountsCallback,
5
5
  identityPassthroughSync,
6
- readPlatformSessionCookie
7
- } from "./chunk-RADVGFYQ.js";
6
+ introspectServiceKey,
7
+ readPlatformSessionCookie,
8
+ serviceKeyFromRequest
9
+ } from "./chunk-2FSPKJAC.js";
8
10
 
9
11
  // src/sveltekit.ts
12
+ import { createHash } from "crypto";
10
13
  var STATE_COOKIE = "accounts_auth_state";
11
14
  function redirectWithCookies(location, cookies) {
12
15
  const headers = new Headers({ location });
@@ -69,7 +72,38 @@ function createAuthHandle(opts) {
69
72
  return resolve(event);
70
73
  };
71
74
  }
75
+ function createServiceKeyHandle(opts) {
76
+ const match = opts.match ?? ((event) => event.url.pathname.startsWith("/api"));
77
+ const ttl = opts.cacheTtlMs ?? 6e4;
78
+ const cache = /* @__PURE__ */ new Map();
79
+ return async ({ event, resolve }) => {
80
+ if (!match(event)) return resolve(event);
81
+ const key = serviceKeyFromRequest(event.request);
82
+ if (!key) return resolve(event);
83
+ const hash = createHash("sha256").update(key).digest("hex");
84
+ const now = Date.now();
85
+ let ctx;
86
+ const cached = ttl > 0 ? cache.get(hash) : void 0;
87
+ if (cached && cached.expires > now) {
88
+ ctx = cached.value;
89
+ } else {
90
+ ctx = await introspectServiceKey({ config: opts.config, key, fetchImpl: opts.fetchImpl });
91
+ if (ttl > 0) cache.set(hash, { value: ctx, expires: now + ttl });
92
+ }
93
+ if (ctx) {
94
+ event.locals.serviceKey = ctx;
95
+ opts.onResolved?.(event, ctx);
96
+ return resolve(event);
97
+ }
98
+ opts.onInvalid?.(event);
99
+ return new Response(JSON.stringify({ message: "Invalid or expired API key." }), {
100
+ status: 401,
101
+ headers: { "content-type": "application/json" }
102
+ });
103
+ };
104
+ }
72
105
  export {
73
106
  clearPlatformSessionCookie,
74
- createAuthHandle
107
+ createAuthHandle,
108
+ createServiceKeyHandle
75
109
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@~lyre/auth",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
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,