@ai-matrx/auth 0.1.3 → 0.2.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/next.cjs CHANGED
@@ -29,8 +29,11 @@ __export(next_exports, {
29
29
  createAuthBudget: () => createAuthBudget,
30
30
  createAuthCookie: () => createAuthCookie,
31
31
  createNextSupabase: () => createNextSupabase,
32
+ createServerAuth: () => createServerAuth,
33
+ createServiceClient: () => createServiceClient,
32
34
  serializeSetCookie: () => serializeSetCookie,
33
- toResponseCookie: () => toResponseCookie
35
+ toResponseCookie: () => toResponseCookie,
36
+ verifyRequest: () => verifyRequest
34
37
  });
35
38
  module.exports = __toCommonJS(next_exports);
36
39
 
@@ -653,3 +656,132 @@ function createNextSupabase(config) {
653
656
  middlewareSession
654
657
  };
655
658
  }
659
+
660
+ // src/server-auth.ts
661
+ var import_supabase_js2 = require("@supabase/supabase-js");
662
+
663
+ // src/claims.ts
664
+ var import_supabase_js = require("@supabase/supabase-js");
665
+ async function getClaimsUser(client, jwt, options) {
666
+ const { data, error } = options ? await client.auth.getClaims(jwt, options) : await client.auth.getClaims(jwt);
667
+ if (error) {
668
+ if (isSignedOutError(error)) return { data: { user: null }, error: null };
669
+ return { data: { user: null }, error };
670
+ }
671
+ if (!data?.claims) return { data: { user: null }, error: null };
672
+ const user = userFromClaims(data.claims);
673
+ if (!user) {
674
+ return {
675
+ data: { user: null },
676
+ error: new import_supabase_js.AuthError("Verified token carries no `sub` claim", 401, "bad_jwt")
677
+ };
678
+ }
679
+ return { data: { user }, error: null };
680
+ }
681
+ var BUDGET_EXHAUSTED_STATUS = 408;
682
+ var BUDGET_EXHAUSTED_CODE = "auth_budget_exhausted";
683
+ function isAuthTransportFailure2(error) {
684
+ if (!error || typeof error !== "object") return false;
685
+ const name = String(error.name ?? "");
686
+ if (name === "AuthRetryableFetchError" || name === "TimeoutError" || name === "AbortError") return true;
687
+ const status = error.status;
688
+ if (status === 0 || status === 504 || status === 503) return true;
689
+ return status === BUDGET_EXHAUSTED_STATUS || error.code === BUDGET_EXHAUSTED_CODE;
690
+ }
691
+ function isSignedOutError(error) {
692
+ return error instanceof import_supabase_js.AuthSessionMissingError || error.name === "AuthSessionMissingError" || error.code === "session_not_found";
693
+ }
694
+ function userFromClaims(claims) {
695
+ if (!claims || typeof claims.sub !== "string") return null;
696
+ return {
697
+ ...claims,
698
+ id: claims.sub,
699
+ app_metadata: claims.app_metadata ?? {},
700
+ user_metadata: claims.user_metadata ?? {}
701
+ };
702
+ }
703
+ function accountFlagsFor(claims) {
704
+ if (!claims) return { isGuest: false, hasAccount: false };
705
+ const isGuest = claims.is_anonymous === true;
706
+ return { isGuest, hasAccount: !isGuest };
707
+ }
708
+ var PROJECT_SIGNING_KEY_AUTHORITIES = [
709
+ "db.matrxserver.com",
710
+ "brsgrqvjdzwihsvnfqkf.supabase.co"
711
+ ];
712
+ var PROJECT_SIGNING_KEYS = [
713
+ {
714
+ alg: "ES256",
715
+ crv: "P-256",
716
+ ext: true,
717
+ key_ops: ["verify"],
718
+ kid: "a00aed0b-202c-483a-91ff-fc74719a3bb2",
719
+ kty: "EC",
720
+ use: "sig",
721
+ x: "guqOsgjJqGVU6g_K0iIXKrzixlkeXKOqkPUQCconp6U",
722
+ y: "39zQtkzlfQuPf_MtIhYmMdxNQpIR2Pkl11AToywaslY"
723
+ }
724
+ ];
725
+ function pinnedSigningKeysFor(supabaseUrl) {
726
+ if (!supabaseUrl) return [];
727
+ let host;
728
+ try {
729
+ host = new URL(supabaseUrl).host.toLowerCase();
730
+ } catch {
731
+ return [];
732
+ }
733
+ return PROJECT_SIGNING_KEY_AUTHORITIES.includes(host) ? PROJECT_SIGNING_KEYS : [];
734
+ }
735
+
736
+ // src/server-auth.ts
737
+ function createServerAuth(options) {
738
+ const warn = options.warn ?? ((m) => console.warn(m));
739
+ const resolveOnce = async () => {
740
+ const client = await options.createClient();
741
+ const {
742
+ data: { user },
743
+ error
744
+ } = await getClaimsUser(client, void 0, { jwks: { keys: pinnedSigningKeysFor(options.supabaseUrl) } });
745
+ const authUnavailable = !user && error !== null && isAuthTransportFailure2(error);
746
+ if (authUnavailable) {
747
+ warn(
748
+ `[getServerAuth] identity could not be verified on this attempt \u2014 rendering without a user, NOT as signed out. Cause: ${error?.name ?? "unknown"}: ${error?.message ?? ""}`
749
+ );
750
+ }
751
+ return { isAuthenticated: !!user, user, authUnavailable, ...accountFlagsFor(user) };
752
+ };
753
+ const door = async () => {
754
+ const first = await resolveOnce();
755
+ if (!first.authUnavailable) return first;
756
+ await new Promise((resolve) => setTimeout(resolve, options.retryAfterMs ?? 250));
757
+ const second = await resolveOnce();
758
+ if (!second.authUnavailable) warn("[getServerAuth] identity verified on the retry \u2014 the first attempt's budget was spent.");
759
+ return second;
760
+ };
761
+ return options.cache ? options.cache(door) : door;
762
+ }
763
+ async function verifyRequest(request, options) {
764
+ const header = request.headers.get("Authorization") ?? request.headers.get("authorization");
765
+ if (header?.startsWith("Bearer ")) {
766
+ const token = header.slice(7);
767
+ const client2 = (0, import_supabase_js2.createClient)(options.url, options.publishableKey, {
768
+ global: { headers: { Authorization: `Bearer ${token}` } },
769
+ auth: { persistSession: false, autoRefreshToken: false }
770
+ });
771
+ const { data: data2, error: error2 } = await getClaimsUser(client2, token, {
772
+ jwks: { keys: pinnedSigningKeysFor(options.url) }
773
+ });
774
+ return { user: data2.user, error: error2 };
775
+ }
776
+ if (!options.cookieClient) return { user: null, error: null };
777
+ const client = await options.cookieClient();
778
+ const { data, error } = await getClaimsUser(client, void 0, { jwks: { keys: pinnedSigningKeysFor(options.url) } });
779
+ return { user: data.user, error };
780
+ }
781
+ function createServiceClient(options) {
782
+ const url = options.url?.trim();
783
+ const key = options.secretKey?.trim();
784
+ if (!url) throw new Error("createServiceClient: the Supabase URL is not configured");
785
+ if (!key) throw new Error("createServiceClient: the secret key (SUPABASE_SECRET_KEY) is not configured");
786
+ return (0, import_supabase_js2.createClient)(url, key, { auth: { persistSession: false, autoRefreshToken: false } });
787
+ }
package/dist/next.d.cts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { createServerClient, createBrowserClient } from '@supabase/ssr';
2
2
  import { b as AuthCookieValue, A as AuthCookie, a as AuthCookieOptions } from './auth-cookie-DWx6Q3xo.cjs';
3
3
  export { C as CreateAuthCookieArgs, D as DEFAULT_AUTH_COOKIE_NAME, c as createAuthCookie } from './auth-cookie-DWx6Q3xo.cjs';
4
+ import { SupabaseClient } from '@supabase/supabase-js';
5
+ import { a as ClaimsCapableClient, A as ApiClaimsUser } from './claims-DXdbe3cW.cjs';
4
6
 
5
7
  /**
6
8
  * @ai-matrx/data/next — the Next.js Supabase binding.
@@ -386,4 +388,78 @@ interface NextSupabase<DB extends DatabaseLike> {
386
388
  }
387
389
  declare function createNextSupabase<DB extends DatabaseLike = DatabaseLike>(config: NextSupabaseConfig): NextSupabase<DB>;
388
390
 
389
- export { AMBIGUOUS_AUTH_COOKIE_HEADER, AUTH_COOKIE_RESPONSE_HEADERS, AUTH_REFRESH_CEILING_MS, type AuthBudget, AuthCookie, AuthCookieOptions, AuthCookieValue, type CookieToSet, type DatabaseLike, type MiddlewareResponse, type MiddlewareSession, type MiddlewareSessionArgs, MissingSupabaseIdentityError, type NextBrowserClient, type NextServerClient, type NextSupabase, type NextSupabaseConfig, type RequestCookieJar, type ResponseCookie, type ResponseCookieJar, SPLIT_COOKIE_JAR_HEADER, type ServerCookieStore, createAuthBudget, createNextSupabase, serializeSetCookie, toResponseCookie };
391
+ /**
392
+ * Server identity doors (moved from the website's `utils/supabase/{getServerAuth,resolveUser,
393
+ * adminClient}.ts`, 2026-10-12). Framework-free: the app injects how it builds its cookie client,
394
+ * and (for Next) React's `cache()` so one request resolves once. Exported from `/next` and `/node`.
395
+ */
396
+
397
+ interface ServerAuthState {
398
+ /** `true` when the request carries a locally verified access token. */
399
+ isAuthenticated: boolean;
400
+ /** The verified caller (claims, `id` = `sub`), or `null` for guests and unresolvable requests. */
401
+ user: ApiClaimsUser | null;
402
+ /**
403
+ * `true` when no verdict could be REACHED (authority/JWKS unreachable, identity budget spent).
404
+ * The person is NOT signed out: never bounce to sign-in on it. Never set for a token that was
405
+ * judged bad — that is a settled answer, the same line the proxy draws.
406
+ */
407
+ authUnavailable: boolean;
408
+ /** `true` for a guest (anonymous) session. */
409
+ isGuest: boolean;
410
+ /** `isAuthenticated && !isGuest` — the flag account UI keys on. */
411
+ hasAccount: boolean;
412
+ }
413
+ interface ServerAuthOptions {
414
+ /** A FRESH cookie-bound client per attempt (the identity budget belongs to the client). */
415
+ createClient: () => Promise<ClaimsCapableClient> | ClaimsCapableClient;
416
+ /** The URL the app talks to — decides which pinned keys apply. */
417
+ supabaseUrl: string | null | undefined;
418
+ /** React's `cache` on a Next server, so the whole tree shares one resolve per request. */
419
+ cache?: <F extends (...args: never[]) => unknown>(fn: F) => F;
420
+ /** Wait before the one retry (default 250ms). */
421
+ retryAfterMs?: number;
422
+ /** Where the "could not verify" warnings go (default `console.warn`). */
423
+ warn?: (message: string) => void;
424
+ }
425
+ /**
426
+ * Build THE server identity door: claims verified locally, asked twice before it hands anyone
427
+ * "could not verify" (that is almost always one cold first load spending its budget).
428
+ *
429
+ * export const getServerAuth = createServerAuth({ createClient, supabaseUrl, cache });
430
+ */
431
+ declare function createServerAuth(options: ServerAuthOptions): () => Promise<ServerAuthState>;
432
+ /** The bit of a request this needs: its headers (a `Request`, a `NextRequest`, a Node shim). */
433
+ interface RequestLike {
434
+ headers: {
435
+ get(name: string): string | null;
436
+ };
437
+ }
438
+ interface VerifyRequestOptions {
439
+ url: string;
440
+ /** The publishable key (`sb_publishable_*`). */
441
+ publishableKey: string;
442
+ /** The app's cookie-bound client, used when the request carries no Bearer token. */
443
+ cookieClient?: () => Promise<ClaimsCapableClient> | ClaimsCapableClient;
444
+ }
445
+ /**
446
+ * Who is calling this API route: a Bearer token (public/mobile/extension/desktop clients) or the
447
+ * session cookie (browser). `user` is null when the caller is signed out OR could not be verified;
448
+ * `error` tells the two apart (null = settled signed out).
449
+ */
450
+ declare function verifyRequest(request: RequestLike, options: VerifyRequestOptions): Promise<{
451
+ user: ApiClaimsUser | null;
452
+ error: Error | null;
453
+ }>;
454
+ /**
455
+ * THE service (secret-key) client — server-only, bypasses row security. The package's browser
456
+ * clients refuse a secret key by design (`SecretKeyRefusedError`); this is the one door for it.
457
+ * Never ship it to a browser bundle: it lives in `/node` and `/next` only.
458
+ */
459
+ declare function createServiceClient<Database = unknown>(options: {
460
+ url: string | null | undefined;
461
+ /** `sb_secret_*` (the legacy service-role JWT is banned). */
462
+ secretKey: string | null | undefined;
463
+ }): SupabaseClient<Database>;
464
+
465
+ export { AMBIGUOUS_AUTH_COOKIE_HEADER, AUTH_COOKIE_RESPONSE_HEADERS, AUTH_REFRESH_CEILING_MS, type AuthBudget, AuthCookie, AuthCookieOptions, AuthCookieValue, type CookieToSet, type DatabaseLike, type MiddlewareResponse, type MiddlewareSession, type MiddlewareSessionArgs, MissingSupabaseIdentityError, type NextBrowserClient, type NextServerClient, type NextSupabase, type NextSupabaseConfig, type RequestCookieJar, type RequestLike, type ResponseCookie, type ResponseCookieJar, SPLIT_COOKIE_JAR_HEADER, type ServerAuthOptions, type ServerAuthState, type ServerCookieStore, type VerifyRequestOptions, createAuthBudget, createNextSupabase, createServerAuth, createServiceClient, serializeSetCookie, toResponseCookie, verifyRequest };
package/dist/next.d.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  import { createServerClient, createBrowserClient } from '@supabase/ssr';
2
2
  import { b as AuthCookieValue, A as AuthCookie, a as AuthCookieOptions } from './auth-cookie-DWx6Q3xo.js';
3
3
  export { C as CreateAuthCookieArgs, D as DEFAULT_AUTH_COOKIE_NAME, c as createAuthCookie } from './auth-cookie-DWx6Q3xo.js';
4
+ import { SupabaseClient } from '@supabase/supabase-js';
5
+ import { a as ClaimsCapableClient, A as ApiClaimsUser } from './claims-DXdbe3cW.js';
4
6
 
5
7
  /**
6
8
  * @ai-matrx/data/next — the Next.js Supabase binding.
@@ -386,4 +388,78 @@ interface NextSupabase<DB extends DatabaseLike> {
386
388
  }
387
389
  declare function createNextSupabase<DB extends DatabaseLike = DatabaseLike>(config: NextSupabaseConfig): NextSupabase<DB>;
388
390
 
389
- export { AMBIGUOUS_AUTH_COOKIE_HEADER, AUTH_COOKIE_RESPONSE_HEADERS, AUTH_REFRESH_CEILING_MS, type AuthBudget, AuthCookie, AuthCookieOptions, AuthCookieValue, type CookieToSet, type DatabaseLike, type MiddlewareResponse, type MiddlewareSession, type MiddlewareSessionArgs, MissingSupabaseIdentityError, type NextBrowserClient, type NextServerClient, type NextSupabase, type NextSupabaseConfig, type RequestCookieJar, type ResponseCookie, type ResponseCookieJar, SPLIT_COOKIE_JAR_HEADER, type ServerCookieStore, createAuthBudget, createNextSupabase, serializeSetCookie, toResponseCookie };
391
+ /**
392
+ * Server identity doors (moved from the website's `utils/supabase/{getServerAuth,resolveUser,
393
+ * adminClient}.ts`, 2026-10-12). Framework-free: the app injects how it builds its cookie client,
394
+ * and (for Next) React's `cache()` so one request resolves once. Exported from `/next` and `/node`.
395
+ */
396
+
397
+ interface ServerAuthState {
398
+ /** `true` when the request carries a locally verified access token. */
399
+ isAuthenticated: boolean;
400
+ /** The verified caller (claims, `id` = `sub`), or `null` for guests and unresolvable requests. */
401
+ user: ApiClaimsUser | null;
402
+ /**
403
+ * `true` when no verdict could be REACHED (authority/JWKS unreachable, identity budget spent).
404
+ * The person is NOT signed out: never bounce to sign-in on it. Never set for a token that was
405
+ * judged bad — that is a settled answer, the same line the proxy draws.
406
+ */
407
+ authUnavailable: boolean;
408
+ /** `true` for a guest (anonymous) session. */
409
+ isGuest: boolean;
410
+ /** `isAuthenticated && !isGuest` — the flag account UI keys on. */
411
+ hasAccount: boolean;
412
+ }
413
+ interface ServerAuthOptions {
414
+ /** A FRESH cookie-bound client per attempt (the identity budget belongs to the client). */
415
+ createClient: () => Promise<ClaimsCapableClient> | ClaimsCapableClient;
416
+ /** The URL the app talks to — decides which pinned keys apply. */
417
+ supabaseUrl: string | null | undefined;
418
+ /** React's `cache` on a Next server, so the whole tree shares one resolve per request. */
419
+ cache?: <F extends (...args: never[]) => unknown>(fn: F) => F;
420
+ /** Wait before the one retry (default 250ms). */
421
+ retryAfterMs?: number;
422
+ /** Where the "could not verify" warnings go (default `console.warn`). */
423
+ warn?: (message: string) => void;
424
+ }
425
+ /**
426
+ * Build THE server identity door: claims verified locally, asked twice before it hands anyone
427
+ * "could not verify" (that is almost always one cold first load spending its budget).
428
+ *
429
+ * export const getServerAuth = createServerAuth({ createClient, supabaseUrl, cache });
430
+ */
431
+ declare function createServerAuth(options: ServerAuthOptions): () => Promise<ServerAuthState>;
432
+ /** The bit of a request this needs: its headers (a `Request`, a `NextRequest`, a Node shim). */
433
+ interface RequestLike {
434
+ headers: {
435
+ get(name: string): string | null;
436
+ };
437
+ }
438
+ interface VerifyRequestOptions {
439
+ url: string;
440
+ /** The publishable key (`sb_publishable_*`). */
441
+ publishableKey: string;
442
+ /** The app's cookie-bound client, used when the request carries no Bearer token. */
443
+ cookieClient?: () => Promise<ClaimsCapableClient> | ClaimsCapableClient;
444
+ }
445
+ /**
446
+ * Who is calling this API route: a Bearer token (public/mobile/extension/desktop clients) or the
447
+ * session cookie (browser). `user` is null when the caller is signed out OR could not be verified;
448
+ * `error` tells the two apart (null = settled signed out).
449
+ */
450
+ declare function verifyRequest(request: RequestLike, options: VerifyRequestOptions): Promise<{
451
+ user: ApiClaimsUser | null;
452
+ error: Error | null;
453
+ }>;
454
+ /**
455
+ * THE service (secret-key) client — server-only, bypasses row security. The package's browser
456
+ * clients refuse a secret key by design (`SecretKeyRefusedError`); this is the one door for it.
457
+ * Never ship it to a browser bundle: it lives in `/node` and `/next` only.
458
+ */
459
+ declare function createServiceClient<Database = unknown>(options: {
460
+ url: string | null | undefined;
461
+ /** `sb_secret_*` (the legacy service-role JWT is banned). */
462
+ secretKey: string | null | undefined;
463
+ }): SupabaseClient<Database>;
464
+
465
+ export { AMBIGUOUS_AUTH_COOKIE_HEADER, AUTH_COOKIE_RESPONSE_HEADERS, AUTH_REFRESH_CEILING_MS, type AuthBudget, AuthCookie, AuthCookieOptions, AuthCookieValue, type CookieToSet, type DatabaseLike, type MiddlewareResponse, type MiddlewareSession, type MiddlewareSessionArgs, MissingSupabaseIdentityError, type NextBrowserClient, type NextServerClient, type NextSupabase, type NextSupabaseConfig, type RequestCookieJar, type RequestLike, type ResponseCookie, type ResponseCookieJar, SPLIT_COOKIE_JAR_HEADER, type ServerAuthOptions, type ServerAuthState, type ServerCookieStore, type VerifyRequestOptions, createAuthBudget, createNextSupabase, createServerAuth, createServiceClient, serializeSetCookie, toResponseCookie, verifyRequest };
package/dist/next.js CHANGED
@@ -622,6 +622,138 @@ function createNextSupabase(config) {
622
622
  middlewareSession
623
623
  };
624
624
  }
625
+
626
+ // src/server-auth.ts
627
+ import { createClient } from "@supabase/supabase-js";
628
+
629
+ // src/claims.ts
630
+ import {
631
+ AuthError,
632
+ AuthSessionMissingError
633
+ } from "@supabase/supabase-js";
634
+ async function getClaimsUser(client, jwt, options) {
635
+ const { data, error } = options ? await client.auth.getClaims(jwt, options) : await client.auth.getClaims(jwt);
636
+ if (error) {
637
+ if (isSignedOutError(error)) return { data: { user: null }, error: null };
638
+ return { data: { user: null }, error };
639
+ }
640
+ if (!data?.claims) return { data: { user: null }, error: null };
641
+ const user = userFromClaims(data.claims);
642
+ if (!user) {
643
+ return {
644
+ data: { user: null },
645
+ error: new AuthError("Verified token carries no `sub` claim", 401, "bad_jwt")
646
+ };
647
+ }
648
+ return { data: { user }, error: null };
649
+ }
650
+ var BUDGET_EXHAUSTED_STATUS = 408;
651
+ var BUDGET_EXHAUSTED_CODE = "auth_budget_exhausted";
652
+ function isAuthTransportFailure2(error) {
653
+ if (!error || typeof error !== "object") return false;
654
+ const name = String(error.name ?? "");
655
+ if (name === "AuthRetryableFetchError" || name === "TimeoutError" || name === "AbortError") return true;
656
+ const status = error.status;
657
+ if (status === 0 || status === 504 || status === 503) return true;
658
+ return status === BUDGET_EXHAUSTED_STATUS || error.code === BUDGET_EXHAUSTED_CODE;
659
+ }
660
+ function isSignedOutError(error) {
661
+ return error instanceof AuthSessionMissingError || error.name === "AuthSessionMissingError" || error.code === "session_not_found";
662
+ }
663
+ function userFromClaims(claims) {
664
+ if (!claims || typeof claims.sub !== "string") return null;
665
+ return {
666
+ ...claims,
667
+ id: claims.sub,
668
+ app_metadata: claims.app_metadata ?? {},
669
+ user_metadata: claims.user_metadata ?? {}
670
+ };
671
+ }
672
+ function accountFlagsFor(claims) {
673
+ if (!claims) return { isGuest: false, hasAccount: false };
674
+ const isGuest = claims.is_anonymous === true;
675
+ return { isGuest, hasAccount: !isGuest };
676
+ }
677
+ var PROJECT_SIGNING_KEY_AUTHORITIES = [
678
+ "db.matrxserver.com",
679
+ "brsgrqvjdzwihsvnfqkf.supabase.co"
680
+ ];
681
+ var PROJECT_SIGNING_KEYS = [
682
+ {
683
+ alg: "ES256",
684
+ crv: "P-256",
685
+ ext: true,
686
+ key_ops: ["verify"],
687
+ kid: "a00aed0b-202c-483a-91ff-fc74719a3bb2",
688
+ kty: "EC",
689
+ use: "sig",
690
+ x: "guqOsgjJqGVU6g_K0iIXKrzixlkeXKOqkPUQCconp6U",
691
+ y: "39zQtkzlfQuPf_MtIhYmMdxNQpIR2Pkl11AToywaslY"
692
+ }
693
+ ];
694
+ function pinnedSigningKeysFor(supabaseUrl) {
695
+ if (!supabaseUrl) return [];
696
+ let host;
697
+ try {
698
+ host = new URL(supabaseUrl).host.toLowerCase();
699
+ } catch {
700
+ return [];
701
+ }
702
+ return PROJECT_SIGNING_KEY_AUTHORITIES.includes(host) ? PROJECT_SIGNING_KEYS : [];
703
+ }
704
+
705
+ // src/server-auth.ts
706
+ function createServerAuth(options) {
707
+ const warn = options.warn ?? ((m) => console.warn(m));
708
+ const resolveOnce = async () => {
709
+ const client = await options.createClient();
710
+ const {
711
+ data: { user },
712
+ error
713
+ } = await getClaimsUser(client, void 0, { jwks: { keys: pinnedSigningKeysFor(options.supabaseUrl) } });
714
+ const authUnavailable = !user && error !== null && isAuthTransportFailure2(error);
715
+ if (authUnavailable) {
716
+ warn(
717
+ `[getServerAuth] identity could not be verified on this attempt \u2014 rendering without a user, NOT as signed out. Cause: ${error?.name ?? "unknown"}: ${error?.message ?? ""}`
718
+ );
719
+ }
720
+ return { isAuthenticated: !!user, user, authUnavailable, ...accountFlagsFor(user) };
721
+ };
722
+ const door = async () => {
723
+ const first = await resolveOnce();
724
+ if (!first.authUnavailable) return first;
725
+ await new Promise((resolve) => setTimeout(resolve, options.retryAfterMs ?? 250));
726
+ const second = await resolveOnce();
727
+ if (!second.authUnavailable) warn("[getServerAuth] identity verified on the retry \u2014 the first attempt's budget was spent.");
728
+ return second;
729
+ };
730
+ return options.cache ? options.cache(door) : door;
731
+ }
732
+ async function verifyRequest(request, options) {
733
+ const header = request.headers.get("Authorization") ?? request.headers.get("authorization");
734
+ if (header?.startsWith("Bearer ")) {
735
+ const token = header.slice(7);
736
+ const client2 = createClient(options.url, options.publishableKey, {
737
+ global: { headers: { Authorization: `Bearer ${token}` } },
738
+ auth: { persistSession: false, autoRefreshToken: false }
739
+ });
740
+ const { data: data2, error: error2 } = await getClaimsUser(client2, token, {
741
+ jwks: { keys: pinnedSigningKeysFor(options.url) }
742
+ });
743
+ return { user: data2.user, error: error2 };
744
+ }
745
+ if (!options.cookieClient) return { user: null, error: null };
746
+ const client = await options.cookieClient();
747
+ const { data, error } = await getClaimsUser(client, void 0, { jwks: { keys: pinnedSigningKeysFor(options.url) } });
748
+ return { user: data.user, error };
749
+ }
750
+ function createServiceClient(options) {
751
+ const url = options.url?.trim();
752
+ const key = options.secretKey?.trim();
753
+ if (!url) throw new Error("createServiceClient: the Supabase URL is not configured");
754
+ if (!key) throw new Error("createServiceClient: the secret key (SUPABASE_SECRET_KEY) is not configured");
755
+ return createClient(url, key, { auth: { persistSession: false, autoRefreshToken: false } });
756
+ }
625
757
  export {
626
758
  AMBIGUOUS_AUTH_COOKIE_HEADER,
627
759
  AUTH_COOKIE_RESPONSE_HEADERS,
@@ -632,6 +764,9 @@ export {
632
764
  createAuthBudget,
633
765
  createAuthCookie,
634
766
  createNextSupabase,
767
+ createServerAuth,
768
+ createServiceClient,
635
769
  serializeSetCookie,
636
- toResponseCookie
770
+ toResponseCookie,
771
+ verifyRequest
637
772
  };
package/dist/node.cjs CHANGED
@@ -20,9 +20,143 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/node.ts
21
21
  var node_exports = {};
22
22
  __export(node_exports, {
23
- fileStorage: () => fileStorage
23
+ createServerAuth: () => createServerAuth,
24
+ createServiceClient: () => createServiceClient,
25
+ fileStorage: () => fileStorage,
26
+ verifyRequest: () => verifyRequest
24
27
  });
25
28
  module.exports = __toCommonJS(node_exports);
29
+
30
+ // src/server-auth.ts
31
+ var import_supabase_js2 = require("@supabase/supabase-js");
32
+
33
+ // src/claims.ts
34
+ var import_supabase_js = require("@supabase/supabase-js");
35
+ async function getClaimsUser(client, jwt, options) {
36
+ const { data, error } = options ? await client.auth.getClaims(jwt, options) : await client.auth.getClaims(jwt);
37
+ if (error) {
38
+ if (isSignedOutError(error)) return { data: { user: null }, error: null };
39
+ return { data: { user: null }, error };
40
+ }
41
+ if (!data?.claims) return { data: { user: null }, error: null };
42
+ const user = userFromClaims(data.claims);
43
+ if (!user) {
44
+ return {
45
+ data: { user: null },
46
+ error: new import_supabase_js.AuthError("Verified token carries no `sub` claim", 401, "bad_jwt")
47
+ };
48
+ }
49
+ return { data: { user }, error: null };
50
+ }
51
+ var BUDGET_EXHAUSTED_STATUS = 408;
52
+ var BUDGET_EXHAUSTED_CODE = "auth_budget_exhausted";
53
+ function isAuthTransportFailure(error) {
54
+ if (!error || typeof error !== "object") return false;
55
+ const name = String(error.name ?? "");
56
+ if (name === "AuthRetryableFetchError" || name === "TimeoutError" || name === "AbortError") return true;
57
+ const status = error.status;
58
+ if (status === 0 || status === 504 || status === 503) return true;
59
+ return status === BUDGET_EXHAUSTED_STATUS || error.code === BUDGET_EXHAUSTED_CODE;
60
+ }
61
+ function isSignedOutError(error) {
62
+ return error instanceof import_supabase_js.AuthSessionMissingError || error.name === "AuthSessionMissingError" || error.code === "session_not_found";
63
+ }
64
+ function userFromClaims(claims) {
65
+ if (!claims || typeof claims.sub !== "string") return null;
66
+ return {
67
+ ...claims,
68
+ id: claims.sub,
69
+ app_metadata: claims.app_metadata ?? {},
70
+ user_metadata: claims.user_metadata ?? {}
71
+ };
72
+ }
73
+ function accountFlagsFor(claims) {
74
+ if (!claims) return { isGuest: false, hasAccount: false };
75
+ const isGuest = claims.is_anonymous === true;
76
+ return { isGuest, hasAccount: !isGuest };
77
+ }
78
+ var PROJECT_SIGNING_KEY_AUTHORITIES = [
79
+ "db.matrxserver.com",
80
+ "brsgrqvjdzwihsvnfqkf.supabase.co"
81
+ ];
82
+ var PROJECT_SIGNING_KEYS = [
83
+ {
84
+ alg: "ES256",
85
+ crv: "P-256",
86
+ ext: true,
87
+ key_ops: ["verify"],
88
+ kid: "a00aed0b-202c-483a-91ff-fc74719a3bb2",
89
+ kty: "EC",
90
+ use: "sig",
91
+ x: "guqOsgjJqGVU6g_K0iIXKrzixlkeXKOqkPUQCconp6U",
92
+ y: "39zQtkzlfQuPf_MtIhYmMdxNQpIR2Pkl11AToywaslY"
93
+ }
94
+ ];
95
+ function pinnedSigningKeysFor(supabaseUrl) {
96
+ if (!supabaseUrl) return [];
97
+ let host;
98
+ try {
99
+ host = new URL(supabaseUrl).host.toLowerCase();
100
+ } catch {
101
+ return [];
102
+ }
103
+ return PROJECT_SIGNING_KEY_AUTHORITIES.includes(host) ? PROJECT_SIGNING_KEYS : [];
104
+ }
105
+
106
+ // src/server-auth.ts
107
+ function createServerAuth(options) {
108
+ const warn = options.warn ?? ((m) => console.warn(m));
109
+ const resolveOnce = async () => {
110
+ const client = await options.createClient();
111
+ const {
112
+ data: { user },
113
+ error
114
+ } = await getClaimsUser(client, void 0, { jwks: { keys: pinnedSigningKeysFor(options.supabaseUrl) } });
115
+ const authUnavailable = !user && error !== null && isAuthTransportFailure(error);
116
+ if (authUnavailable) {
117
+ warn(
118
+ `[getServerAuth] identity could not be verified on this attempt \u2014 rendering without a user, NOT as signed out. Cause: ${error?.name ?? "unknown"}: ${error?.message ?? ""}`
119
+ );
120
+ }
121
+ return { isAuthenticated: !!user, user, authUnavailable, ...accountFlagsFor(user) };
122
+ };
123
+ const door = async () => {
124
+ const first = await resolveOnce();
125
+ if (!first.authUnavailable) return first;
126
+ await new Promise((resolve) => setTimeout(resolve, options.retryAfterMs ?? 250));
127
+ const second = await resolveOnce();
128
+ if (!second.authUnavailable) warn("[getServerAuth] identity verified on the retry \u2014 the first attempt's budget was spent.");
129
+ return second;
130
+ };
131
+ return options.cache ? options.cache(door) : door;
132
+ }
133
+ async function verifyRequest(request, options) {
134
+ const header = request.headers.get("Authorization") ?? request.headers.get("authorization");
135
+ if (header?.startsWith("Bearer ")) {
136
+ const token = header.slice(7);
137
+ const client2 = (0, import_supabase_js2.createClient)(options.url, options.publishableKey, {
138
+ global: { headers: { Authorization: `Bearer ${token}` } },
139
+ auth: { persistSession: false, autoRefreshToken: false }
140
+ });
141
+ const { data: data2, error: error2 } = await getClaimsUser(client2, token, {
142
+ jwks: { keys: pinnedSigningKeysFor(options.url) }
143
+ });
144
+ return { user: data2.user, error: error2 };
145
+ }
146
+ if (!options.cookieClient) return { user: null, error: null };
147
+ const client = await options.cookieClient();
148
+ const { data, error } = await getClaimsUser(client, void 0, { jwks: { keys: pinnedSigningKeysFor(options.url) } });
149
+ return { user: data.user, error };
150
+ }
151
+ function createServiceClient(options) {
152
+ const url = options.url?.trim();
153
+ const key = options.secretKey?.trim();
154
+ if (!url) throw new Error("createServiceClient: the Supabase URL is not configured");
155
+ if (!key) throw new Error("createServiceClient: the secret key (SUPABASE_SECRET_KEY) is not configured");
156
+ return (0, import_supabase_js2.createClient)(url, key, { auth: { persistSession: false, autoRefreshToken: false } });
157
+ }
158
+
159
+ // src/node.ts
26
160
  function builtin(name) {
27
161
  const get = globalThis.process?.getBuiltinModule;
28
162
  if (!get) throw new Error(`fileStorage needs Node >= 20.16 (process.getBuiltinModule) to reach node:${name}`);
@@ -57,5 +191,8 @@ function fileStorage(path) {
57
191
  }
58
192
  // Annotate the CommonJS export names for ESM import in node:
59
193
  0 && (module.exports = {
60
- fileStorage
194
+ createServerAuth,
195
+ createServiceClient,
196
+ fileStorage,
197
+ verifyRequest
61
198
  });