@authhero/proxy 0.4.4 → 0.5.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/dist/proxy.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { ProxyRoute, ProxyRoutesAdapter, RouteMatch, HandlerConfig } from '@authhero/adapter-interfaces';
1
+ import { ProxyRoute, ProxyRoutesAdapter, CacheAdapter, RouteMatch, HandlerConfig } from '@authhero/adapter-interfaces';
2
2
  export { HandlerConfig, ListProxyRoutesParams, ListProxyRoutesResult, ProxyRoute, ProxyRouteInsert, ProxyRouteUpdate, ProxyRoutesAdapter, RouteMatch, handlerConfigSchema, matchSchema, proxyRouteInsertSchema, proxyRouteSchema, proxyRouteUpdateSchema } from '@authhero/adapter-interfaces';
3
3
  import { MiddlewareHandler, Hono, Context } from 'hono';
4
4
  import { z } from '@hono/zod-openapi';
@@ -57,6 +57,7 @@ interface ProxyAppOptions {
57
57
  data: ProxyDataAdapter;
58
58
  cacheTtlMs?: number;
59
59
  cache?: HostCacheOptions;
60
+ resolver?: HostResolverCache;
60
61
  registry?: HandlerRegistry;
61
62
  bindings?: Record<string, unknown>;
62
63
  }
@@ -89,10 +90,53 @@ interface CacheApiHostCacheOptions {
89
90
  * Invalidation is the caller's responsibility — typically the control plane
90
91
  * issues `cache.delete(syntheticKey(...))` on every proxy_route mutation,
91
92
  * and each colo's cache expires on its own TTL otherwise.
93
+ *
94
+ * @deprecated Prefer `createCacheAdapterHostCache` with `createCloudflareCache`
95
+ * from `@authhero/cloudflare-adapter`. The adapter-based wrapper supports
96
+ * stale-while-revalidate and works with any `CacheAdapter` (Cloudflare,
97
+ * Redis, in-memory, …).
92
98
  */
93
99
  declare function createCacheApiHostCache(options: CacheApiHostCacheOptions): HostResolverCache;
94
100
  declare function buildCacheApiKey(namespace: string, host: string): Request;
95
101
 
102
+ interface CacheAdapterHostCacheOptions {
103
+ upstream: HostResolverCache;
104
+ cache: CacheAdapter;
105
+ freshTtlMs: number;
106
+ staleTtlMs?: number;
107
+ negativeTtlMs?: number;
108
+ keyPrefix?: string;
109
+ waitUntil?: (promise: Promise<unknown>) => void;
110
+ }
111
+ /**
112
+ * Wraps an upstream `HostResolverCache` with a generic `CacheAdapter` layer.
113
+ * Adds stale-while-revalidate semantics on top of any cache implementation
114
+ * — Cloudflare Cache API (via `CloudflareCache` from `@authhero/cloudflare-adapter`),
115
+ * Redis, in-memory, or anything else satisfying the `CacheAdapter` interface.
116
+ *
117
+ * Typical use on Cloudflare Workers:
118
+ *
119
+ * ```ts
120
+ * import { createCloudflareCache } from "@authhero/cloudflare-adapter";
121
+ * import { createCacheAdapterHostCache, createInMemoryHostCache } from "@authhero/proxy";
122
+ *
123
+ * const cache = createCloudflareCache({ cacheName: "authhero-proxy-hosts" });
124
+ * const resolver = createCacheAdapterHostCache({
125
+ * upstream: createInMemoryHostCache(data, { freshTtlMs: 60_000 }),
126
+ * cache,
127
+ * freshTtlMs: 60 * 60_000, // 1 hour fresh
128
+ * staleTtlMs: 23 * 60 * 60_000, // SWR for 23 more hours (24h total)
129
+ * waitUntil: (p) => ctx.waitUntil(p),
130
+ * });
131
+ * ```
132
+ *
133
+ * Invalidation is the caller's responsibility — when a host's routes change,
134
+ * call `cache.delete(buildCacheAdapterKey(keyPrefix, host))` from the control
135
+ * plane. Otherwise entries expire on their own TTL.
136
+ */
137
+ declare function createCacheAdapterHostCache(options: CacheAdapterHostCacheOptions): HostResolverCache;
138
+ declare function buildCacheAdapterKey(keyPrefix: string, host: string): string;
139
+
96
140
  declare const corsHandler: HandlerDefinition<{
97
141
  origins?: string[] | undefined;
98
142
  allow_credentials?: boolean | undefined;
@@ -232,6 +276,7 @@ interface HttpProxyAdapterOptions {
232
276
  clientId: string;
233
277
  clientSecret: string;
234
278
  audience?: string;
279
+ scope?: string;
235
280
  resolveHostPath?: string;
236
281
  tokenRefreshSkewSeconds?: number;
237
282
  timeoutMs?: number;
@@ -249,5 +294,13 @@ interface HttpProxyAdapterOptions {
249
294
  */
250
295
  declare function createHttpProxyAdapter(options: HttpProxyAdapterOptions): ProxyDataAdapter;
251
296
 
252
- export { HandlerRegistry, basicAuthHandler, buildCacheApiKey, buildMatchFilter, cacheHandler, compileHostApp, corsHandler, createCacheApiHostCache, createHttpProxyAdapter, createInMemoryHostCache, createProxyApp, createProxyDataPlaneHandler, createProxyDataPlaneRouter, createStaticProxyAdapter, defineHandler, dispatchNamespaceHandler, forwardedHeadersHandler, headersHandler, httpHandler, httpRoute, matchesAnyHost, matchesHost, redirectHandler, registerBuiltinHandlers, rewriteCookiesHandler, rewriteLocationHandler, serviceBindingHandler, sortRoutes, staticHandler };
253
- export type { CacheApiHostCacheOptions, HandlerBuildContext, HandlerDefinition, HostCacheOptions, HostResolverCache, HttpProxyAdapterOptions, ProxyAppOptions, ProxyDataAdapter, ProxyDataPlaneOptions, ResolvedHost, StaticHostConfig, StaticProxyAdapterOptions, StaticRouteInput };
297
+ /**
298
+ * Scope a control-plane bearer token must carry to call
299
+ * `GET /api/v2/proxy/control-plane/hosts/:host` or `POST /sync`. The data
300
+ * plane requests it when minting tokens; the control plane requires it when
301
+ * verifying them.
302
+ */
303
+ declare const PROXY_RESOLVE_HOST_SCOPE = "proxy:resolve_host";
304
+
305
+ export { HandlerRegistry, PROXY_RESOLVE_HOST_SCOPE, basicAuthHandler, buildCacheAdapterKey, buildCacheApiKey, buildMatchFilter, cacheHandler, compileHostApp, corsHandler, createCacheAdapterHostCache, createCacheApiHostCache, createHttpProxyAdapter, createInMemoryHostCache, createProxyApp, createProxyDataPlaneHandler, createProxyDataPlaneRouter, createStaticProxyAdapter, defineHandler, dispatchNamespaceHandler, forwardedHeadersHandler, headersHandler, httpHandler, httpRoute, matchesAnyHost, matchesHost, redirectHandler, registerBuiltinHandlers, rewriteCookiesHandler, rewriteLocationHandler, serviceBindingHandler, sortRoutes, staticHandler };
306
+ export type { CacheAdapterHostCacheOptions, CacheApiHostCacheOptions, HandlerBuildContext, HandlerDefinition, HostCacheOptions, HostResolverCache, HttpProxyAdapterOptions, ProxyAppOptions, ProxyDataAdapter, ProxyDataPlaneOptions, ResolvedHost, StaticHostConfig, StaticProxyAdapterOptions, StaticRouteInput };
package/dist/proxy.mjs CHANGED
@@ -3009,6 +3009,7 @@ function Tr(e) {
3009
3009
  data: e.data,
3010
3010
  cacheTtlMs: e.cacheTtlMs,
3011
3011
  cache: e.cache,
3012
+ resolver: e.resolver,
3012
3013
  registry: e.registry,
3013
3014
  bindings: e.bindings
3014
3015
  })), n;
@@ -3041,11 +3042,53 @@ function Or(e, t) {
3041
3042
  return Q(e, t);
3042
3043
  }
3043
3044
  //#endregion
3045
+ //#region src/data-plane/cache-adapter-cache.ts
3046
+ function kr(e) {
3047
+ let { upstream: t, cache: n, freshTtlMs: r, staleTtlMs: i = 0, negativeTtlMs: a = r, keyPrefix: o = "authhero-proxy-host", waitUntil: s } = e, c = /* @__PURE__ */ new Map();
3048
+ function l(e) {
3049
+ return `${o}:${e.toLowerCase()}`;
3050
+ }
3051
+ async function u(e) {
3052
+ let o = c.get(e);
3053
+ if (o) return o;
3054
+ let s = (async () => {
3055
+ try {
3056
+ let o = await t.resolveHost(e), s = Date.now(), c = o === null ? a : r, u = o === null ? 0 : i, d = {
3057
+ value: o,
3058
+ freshUntilMs: s + c,
3059
+ staleUntilMs: s + c + u
3060
+ }, f = Math.max(1, Math.ceil((c + u) / 1e3));
3061
+ return await n.set(l(e), d, f), o;
3062
+ } finally {
3063
+ c.delete(e);
3064
+ }
3065
+ })();
3066
+ return c.set(e, s), s;
3067
+ }
3068
+ return { async resolveHost(e) {
3069
+ let t = l(e), r = await n.get(t), i = Date.now();
3070
+ if (r && typeof r.freshUntilMs == "number") {
3071
+ if (r.freshUntilMs > i) return r.value;
3072
+ if (r.staleUntilMs > i) {
3073
+ if (!c.has(e)) {
3074
+ let t = u(e).catch(() => void 0);
3075
+ s && s(t);
3076
+ }
3077
+ return r.value;
3078
+ }
3079
+ }
3080
+ return u(e);
3081
+ } };
3082
+ }
3083
+ function Ar(e, t) {
3084
+ return `${e}:${t.toLowerCase()}`;
3085
+ }
3086
+ //#endregion
3044
3087
  //#region src/static/index.ts
3045
3088
  function $() {
3046
3089
  throw Error("Static proxy adapter is read-only; configure routes at init time");
3047
3090
  }
3048
- function kr(e, t, n, r, i, a) {
3091
+ function jr(e, t, n, r, i, a) {
3049
3092
  return {
3050
3093
  id: e.id ?? `${n}:${t}`,
3051
3094
  tenant_id: r,
@@ -3060,10 +3103,10 @@ function kr(e, t, n, r, i, a) {
3060
3103
  updated_at: e.updated_at ?? a
3061
3104
  };
3062
3105
  }
3063
- function Ar(e) {
3106
+ function Mr(e) {
3064
3107
  let t = (/* @__PURE__ */ new Date()).toISOString(), n = e.defaultTenantId ?? "static", r = /* @__PURE__ */ new Map();
3065
3108
  for (let [i, a] of Object.entries(e.hosts)) {
3066
- let e = i.toLowerCase(), o = Array.isArray(a) ? { routes: a } : a, s = o.tenant_id ?? n, c = o.custom_domain_id ?? e, l = o.routes.map((n, r) => kr(n, r, e, s, c, t)).sort((e, t) => e.priority - t.priority);
3109
+ let e = i.toLowerCase(), o = Array.isArray(a) ? { routes: a } : a, s = o.tenant_id ?? n, c = o.custom_domain_id ?? e, l = o.routes.map((n, r) => jr(n, r, e, s, c, t)).sort((e, t) => e.priority - t.priority);
3067
3110
  r.set(e, {
3068
3111
  tenant_id: s,
3069
3112
  custom_domain_id: c,
@@ -3107,7 +3150,7 @@ function Ar(e) {
3107
3150
  }
3108
3151
  };
3109
3152
  }
3110
- function jr(e, t = {}) {
3153
+ function Nr(e, t = {}) {
3111
3154
  return {
3112
3155
  priority: t.priority,
3113
3156
  match: { path: t.path ?? "/*" },
@@ -3121,8 +3164,11 @@ function jr(e, t = {}) {
3121
3164
  };
3122
3165
  }
3123
3166
  //#endregion
3167
+ //#region src/constants.ts
3168
+ var Pr = "proxy:resolve_host";
3169
+ //#endregion
3124
3170
  //#region src/http-adapter/index.ts
3125
- function Mr() {
3171
+ function Fr() {
3126
3172
  let e = () => {
3127
3173
  throw Error("HTTP proxy adapter does not expose write access to proxy_routes; mutate via the control-plane management API");
3128
3174
  };
@@ -3144,41 +3190,42 @@ function Mr() {
3144
3190
  }
3145
3191
  };
3146
3192
  }
3147
- function Nr(e) {
3148
- let t = e.fetch ?? fetch, n = e.baseUrl.replace(/\/+$/, ""), r = e.resolveHostPath ?? "/api/v2/proxy/control-plane/hosts/", i = e.audience ?? `${n}/api/v2/`, a = e.tokenRefreshSkewSeconds ?? 60, o = e.timeoutMs ?? 5e3;
3149
- function s(e) {
3150
- let t = new AbortController(), n = setTimeout(() => t.abort(), o);
3193
+ function Ir(e) {
3194
+ let t = e.fetch ?? fetch, n = e.baseUrl.replace(/\/+$/, ""), r = e.resolveHostPath ?? "/api/v2/proxy/control-plane/hosts/", i = e.audience ?? `${n}/api/v2/`, a = e.scope ?? "proxy:resolve_host", o = e.tokenRefreshSkewSeconds ?? 60, s = e.timeoutMs ?? 5e3;
3195
+ function c(e) {
3196
+ let t = new AbortController(), n = setTimeout(() => t.abort(), s);
3151
3197
  return e(t.signal).finally(() => clearTimeout(n));
3152
3198
  }
3153
- let c = null, l = null;
3154
- async function u() {
3155
- let r = await s((r) => t(`${n}/oauth/token`, {
3199
+ let l = null, u = null;
3200
+ async function d() {
3201
+ let r = await c((r) => t(`${n}/oauth/token`, {
3156
3202
  method: "POST",
3157
3203
  headers: { "content-type": "application/json" },
3158
3204
  body: JSON.stringify({
3159
3205
  grant_type: "client_credentials",
3160
3206
  client_id: e.clientId,
3161
3207
  client_secret: e.clientSecret,
3162
- audience: i
3208
+ audience: i,
3209
+ scope: a
3163
3210
  }),
3164
3211
  signal: r
3165
3212
  }));
3166
3213
  if (!r.ok) throw Error(`Proxy adapter token request failed: ${r.status} ${await r.text().catch(() => "")}`);
3167
- let o = await r.json(), l = o.expires_in ?? 3600;
3168
- return c = {
3169
- value: o.access_token,
3170
- expires_at: Date.now() + (l - a) * 1e3
3171
- }, o.access_token;
3214
+ let s = await r.json(), u = s.expires_in ?? 3600;
3215
+ return l = {
3216
+ value: s.access_token,
3217
+ expires_at: Date.now() + (u - o) * 1e3
3218
+ }, s.access_token;
3172
3219
  }
3173
- async function d() {
3174
- return c && c.expires_at > Date.now() ? c.value : l || (l = u().finally(() => {
3175
- l = null;
3176
- }), l);
3220
+ async function f() {
3221
+ return l && l.expires_at > Date.now() ? l.value : u || (u = d().finally(() => {
3222
+ u = null;
3223
+ }), u);
3177
3224
  }
3178
3225
  return {
3179
- proxyRoutes: Mr(),
3226
+ proxyRoutes: Fr(),
3180
3227
  async resolveHost(e) {
3181
- let i = await d(), a = `${n}${r}${encodeURIComponent(e.toLowerCase())}`, o = await s((e) => t(a, {
3228
+ let i = await f(), a = `${n}${r}${encodeURIComponent(e.toLowerCase())}`, o = await c((e) => t(a, {
3182
3229
  headers: { authorization: `Bearer ${i}` },
3183
3230
  signal: e
3184
3231
  }));
@@ -3189,4 +3236,4 @@ function Nr(e) {
3189
3236
  };
3190
3237
  }
3191
3238
  //#endregion
3192
- export { z as HandlerRegistry, Wn as basicAuthHandler, Or as buildCacheApiKey, yr as buildMatchFilter, Kn as cacheHandler, xr as compileHostApp, Vn as corsHandler, Dr as createCacheApiHostCache, Nr as createHttpProxyAdapter, R as createInMemoryHostCache, Tr as createProxyApp, Z as createProxyDataPlaneHandler, wr as createProxyDataPlaneRouter, Ar as createStaticProxyAdapter, B as defineHandler, fr as dispatchNamespaceHandler, qn as forwardedHeadersHandler, P as handlerConfigSchema, Gn as headersHandler, ar as httpHandler, jr as httpRoute, N as matchSchema, vr as matchesAnyHost, _r as matchesHost, F as proxyRouteInsertSchema, mn as proxyRouteSchema, hn as proxyRouteUpdateSchema, pr as redirectHandler, hr as registerBuiltinHandlers, Zn as rewriteCookiesHandler, Qn as rewriteLocationHandler, cr as serviceBindingHandler, gr as sortRoutes, mr as staticHandler };
3239
+ export { z as HandlerRegistry, Pr as PROXY_RESOLVE_HOST_SCOPE, Wn as basicAuthHandler, Ar as buildCacheAdapterKey, Or as buildCacheApiKey, yr as buildMatchFilter, Kn as cacheHandler, xr as compileHostApp, Vn as corsHandler, kr as createCacheAdapterHostCache, Dr as createCacheApiHostCache, Ir as createHttpProxyAdapter, R as createInMemoryHostCache, Tr as createProxyApp, Z as createProxyDataPlaneHandler, wr as createProxyDataPlaneRouter, Mr as createStaticProxyAdapter, B as defineHandler, fr as dispatchNamespaceHandler, qn as forwardedHeadersHandler, P as handlerConfigSchema, Gn as headersHandler, ar as httpHandler, Nr as httpRoute, N as matchSchema, vr as matchesAnyHost, _r as matchesHost, F as proxyRouteInsertSchema, mn as proxyRouteSchema, hn as proxyRouteUpdateSchema, pr as redirectHandler, hr as registerBuiltinHandlers, Zn as rewriteCookiesHandler, Qn as rewriteLocationHandler, cr as serviceBindingHandler, gr as sortRoutes, mr as staticHandler };