aui-auth 0.0.4 → 0.0.5

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
@@ -2,4 +2,4 @@
2
2
 
3
3
  Server-side sessions for web apps that sign in through assistant-ui accounts: OpenID Connect with PKCE, tokens kept in the app's own database, single-flight refresh, and an encrypted cache cookie. Internal to assistant-ui; the manual is `docs/first-party-auth.md` and the design is `packages/aui-auth/ARCHITECTURE.md` in the assistant-internal repository.
4
4
 
5
- Entry points: `aui-auth`, `aui-auth/accounts`, `aui-auth/next`, `aui-auth/database`, `aui-auth/database/pg`.
5
+ Entry points: `aui-auth`, `aui-auth/accounts`, `aui-auth/next`, `aui-auth/database`, `aui-auth/database/pg`, `aui-auth/database/redis`, `aui-auth/testing`.
@@ -1,4 +1,5 @@
1
- import { m as SignInInput, p as RevalidateInput } from "./types-BcXM3ZEI.js";
1
+ import { _ as SignInInput, h as RevalidateInput } from "./types-C5wNLE3i.js";
2
+ import { t as AccountsFetchOptions } from "./oidc-D-qX1b25.js";
2
3
  //#region src/accounts/index.d.ts
3
4
  type AccountsOrganization = {
4
5
  id: string;
@@ -11,15 +12,10 @@ type AccountsOrganization = {
11
12
  type OrganizationSessionData = {
12
13
  activeOrganization: AccountsOrganization | null;
13
14
  };
14
- type OrganizationHooksOptions = {
15
- issuer: string;
16
- fetch?: typeof fetch;
15
+ type OrganizationHooksOptions = AccountsFetchOptions & {
17
16
  preferredSlug?: (redirectPath: string) => string | null;
18
17
  };
19
- declare function fetchOrganizations(options: {
20
- issuer: string;
21
- fetch?: typeof fetch;
22
- }, accessToken: string): Promise<AccountsOrganization[]>;
18
+ declare function fetchOrganizations(options: AccountsFetchOptions, accessToken: string): Promise<AccountsOrganization[]>;
23
19
  declare function selectOrganization(organizations: AccountsOrganization[], preferredSlug?: string | null): AccountsOrganization | null;
24
20
  declare function preferredOrganizationSlug(redirectPath: string): string | null;
25
21
  declare function organizationHooks(options: OrganizationHooksOptions): {
package/dist/accounts.js CHANGED
@@ -1 +1 @@
1
- import{a as e,m as t}from"./cache-BWpH93Oi.js";async function n(n,r){let i=await(n.fetch??globalThis.fetch.bind(globalThis))(e(n.issuer,`/api/oidc/organizations`),{headers:{Authorization:`Bearer ${r}`},cache:`no-store`});if(!i.ok)throw new t(`Failed to load organizations: ${i.status}`,i.status);return await i.json()}function r(e,t){if(t){let n=e.find(e=>e.slug===t);if(n)return n}return e[0]??null}function i(e){let[,t]=new URL(e,`http://localhost`).pathname.split(`/`);return t&&t!==`org`?t:null}function a(e){let a=e.preferredSlug??i;async function o(t){return{activeOrganization:r(await n(e,t.accessToken),a(t.redirectPath))}}async function s(i){let a;try{a=await n(e,i.accessToken)}catch(e){if(e instanceof t&&e.status>=400&&e.status<500)return null;throw e}let o=i.session.data.activeOrganization;if(!o)return{activeOrganization:r(a)};let s=a.find(e=>e.id===o.id)??null;return s?{activeOrganization:s}:null}return{onSignIn:o,onRevalidate:s}}export{n as fetchOrganizations,a as organizationHooks,i as preferredOrganizationSlug,r as selectOrganization};
1
+ import{f as e,h as t,n}from"./oidc-Dmqfk0L9.js";async function r(t,r){let i=await n(t,`/api/oidc/organizations`,{headers:{Authorization:`Bearer ${r}`}});if(!i.ok)throw new e(`Failed to load organizations: ${i.status}`,i.status);return await i.json()}function i(e,t){if(t){let n=e.find(e=>e.slug===t);if(n)return n}return e[0]??null}function a(e){let[,t]=new URL(e,`http://localhost`).pathname.split(`/`);return t&&t!==`org`?t:null}function o(e){let n=e.preferredSlug??a;async function o(t){return{activeOrganization:i(await r(e,t.accessToken),n(t.redirectPath))}}async function s(n){let a;try{a=await r(e,n.accessToken)}catch(e){if(t(e))return null;throw e}let o=n.session.data.activeOrganization;if(!o)return{activeOrganization:i(a)};let s=a.find(e=>e.id===o.id)??null;return s?{activeOrganization:s}:null}return{onSignIn:o,onRevalidate:s}}export{r as fetchOrganizations,o as organizationHooks,a as preferredOrganizationSlug,i as selectOrganization};
@@ -0,0 +1 @@
1
+ import{g as e,u as t}from"./oidc-Dmqfk0L9.js";function n(e){if(!e)return;let t=e.split(`,`);return t[t.length-1].trim()||void 0}function r(e){let t=new URL(e.url);return`${n(e.headers.get(`x-forwarded-proto`))||t.protocol.replace(`:`,``)}://${n(e.headers.get(`x-forwarded-host`))||e.headers.get(`host`)||t.host}`}function i(e){return new URL(e).protocol===`https:`}function a(e,t){return t?`__Secure-${e}`:e}function o(e){let t=new Map;if(!e)return t;for(let n of e.split(`;`)){let e=n.indexOf(`=`);if(e===-1)continue;let r=n.slice(0,e).trim();!r||t.has(r)||t.set(r,n.slice(e+1).trim())}return t}function s(e,t){let n=o(e.headers.get(`cookie`));return n.get(a(t,!0))||n.get(a(t,!1))||null}function c(e,t,n,r){let i=[`${e}=${t}`,`Path=/`,`HttpOnly`,`SameSite=Lax`,`Max-Age=${r}`];return n&&i.push(`Secure`),i.join(`; `)}function l(e,t,n,r,o){let s=i(r);e.append(`set-cookie`,c(a(t,s),n,s,Math.floor(o/1e3)))}function u(e,t){let n=i(t);return[n,!n].map(t=>c(a(e,t),``,t,0))}function d(e,t,n){for(let r of u(t,n))e.append(`set-cookie`,r)}function f(e,t=307){return new Response(null,{status:t,headers:{location:e.toString()}})}function p(e,t){return`${e}_flow_${t}`}function m(e){return btoa(JSON.stringify(e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/g,``)}function h(e){if(!e)return null;try{let t=e.replace(/-/g,`+`).replace(/_/g,`/`),n=JSON.parse(atob(t));return typeof n.verifier==`string`?{verifier:n.verifier,redirectPath:typeof n.redirectPath==`string`?n.redirectPath:`/`,silent:n.silent===!0}:null}catch{return null}}function g(e,t){if(!e)return`/`;let n;try{n=new URL(e,t)}catch{return`/`}return n.origin!==t||n.pathname.startsWith(`//`)?`/`:`${n.pathname}${n.search}${n.hash}`}const _=4096;function v(e){return`${e}_cache`}function y(e,t,n){if(!e.cache)return 0;let{lifetimes:r}=e,i=Math.min(t.lastValidatedAt+r.revalidateIntervalMs,t.tokens.accessTokenExpiresAt-r.refreshBeforeExpiryMs);return Math.min(e.cache.maxAgeMs,(i>n?i:n+r.retryIntervalMs)-n,t.createdAt+r.absoluteMaxAgeMs-n,t.lastValidatedAt+r.idleMaxAgeMs-n)}async function b(e,t,n){if(!e.cache)return null;let r=y(e,t,n);if(r<1e3)return null;let i={v:1,id:t.id,u:t.user,d:t.data,at:t.tokens.accessToken,ate:t.tokens.accessTokenExpiresAt,ca:t.createdAt,lv:t.lastValidatedAt,exp:n+r};return{value:await(await e.cache.codec()).encrypt(JSON.stringify(i)),maxAgeMs:r}}async function x(e,n,r,i){if(!e.cache||!n||!r)return null;let a;try{let t=await e.cache.codec();a=JSON.parse(await t.decrypt(n))}catch{return null}return a.v!==1||typeof a.exp!=`number`||a.exp<=i||a.id!==await t(r)?null:{id:a.id,user:a.u,tokens:{accessToken:a.at,refreshToken:null,idToken:null,accessTokenExpiresAt:a.ate},data:a.d,createdAt:a.ca,lastValidatedAt:a.lv,refreshLeaseUntil:null}}async function S(t,n,r,o){try{let e=await b(t,n,o);if(!e)return null;let s=i(r),l=c(a(v(t.cookieName),s),e.value,s,Math.floor(e.maxAgeMs/1e3));if(l.length>_)throw Error(`Cache cookie is ${l.length} bytes and browsers drop cookies over ${_}; the session data is too large to cache`);return l}catch(n){return e(t,`cache_cookie`,n),null}}function C(e,t){return e.cache?u(v(e.cookieName),t):[]}export{l as _,d as a,h as c,o as d,s as f,c as g,g as h,x as i,m as l,r as m,v as n,u as o,f as p,S as r,a as s,C as t,p as u};
@@ -0,0 +1,8 @@
1
+ //#region src/core/codec.d.ts
2
+ type SessionCodec = {
3
+ encrypt(value: string): Promise<string>;
4
+ decrypt(value: string): Promise<string>;
5
+ };
6
+ declare function createAesGcmCodec(hexKey: string): SessionCodec;
7
+ //#endregion
8
+ export { createAesGcmCodec as n, SessionCodec as t };
@@ -0,0 +1 @@
1
+ function e(e){let t=new Uint8Array(e.length/2);for(let n=0;n<e.length;n+=2)t[n/2]=Number.parseInt(e.slice(n,n+2),16);return t}function t(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/g,``)}function n(e){let t=e.replace(/-/g,`+`).replace(/_/g,`/`),n=t+`=`.repeat((4-t.length%4)%4),r;try{r=atob(n)}catch{throw Error(`Malformed encrypted value`)}let i=new Uint8Array(r.length);for(let e=0;e<r.length;e++)i[e]=r.charCodeAt(e);return i}function r(e){let r=null,i=()=>r??=crypto.subtle.importKey(`raw`,e,{name:`AES-GCM`},!1,[`encrypt`,`decrypt`]);return{async encrypt(e){let n=crypto.getRandomValues(new Uint8Array(12)),r=new Uint8Array(await crypto.subtle.encrypt({name:`AES-GCM`,iv:n},await i(),new TextEncoder().encode(e))),a=new Uint8Array(12+r.length);return a.set(n),a.set(r,12),t(a)},async decrypt(e){let t=n(e);if(t.length<28)throw Error(`Malformed encrypted value`);let r=await crypto.subtle.decrypt({name:`AES-GCM`,iv:t.subarray(0,12)},await i(),t.subarray(12));return new TextDecoder().decode(r)}}}function i(t){if(!/^[0-9a-fA-F]{64}$/.test(t))throw Error(`AES-GCM codec key must be 64 hex characters (32 bytes)`);return r(e(t))}function a(e){let t=null;return()=>t??=crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(e)).then(e=>r(new Uint8Array(e)))}export{a as n,i as t};
@@ -1,2 +1,3 @@
1
- import { c as AccountsSessionStore, g as createAesGcmCodec, h as SessionCodec, l as AccountsTokens, o as AccountsSession, s as AccountsSessionPatch, u as AccountsUser } from "../types-BcXM3ZEI.js";
2
- export { type AccountsSession, type AccountsSessionPatch, type AccountsSessionStore, type AccountsTokens, type AccountsUser, type SessionCodec, createAesGcmCodec };
1
+ import { n as createAesGcmCodec, t as SessionCodec } from "../codec-BPnk_EfF.js";
2
+ import { c as AccountsSession, d as AccountsTokens, f as AccountsUser, g as SessionSweepCutoffs, l as AccountsSessionPatch, u as AccountsSessionStore } from "../types-C5wNLE3i.js";
3
+ export { type AccountsSession, type AccountsSessionPatch, type AccountsSessionStore, type AccountsTokens, type AccountsUser, type SessionCodec, type SessionSweepCutoffs, createAesGcmCodec };
@@ -1 +1 @@
1
- import{t as e}from"../codec-BcgaGRoP.js";export{e as createAesGcmCodec};
1
+ import{t as e}from"../codec-D-31dtWO.js";export{e as createAesGcmCodec};
@@ -1,4 +1,5 @@
1
- import { c as AccountsSessionStore, h as SessionCodec } from "../types-BcXM3ZEI.js";
1
+ import { t as SessionCodec } from "../codec-BPnk_EfF.js";
2
+ import { u as AccountsSessionStore } from "../types-C5wNLE3i.js";
2
3
  //#region src/database/pg.d.ts
3
4
  type PgQuery = (text: string, values?: unknown[]) => Promise<{
4
5
  rows: Record<string, unknown>[];
@@ -11,4 +11,4 @@ const e=`CREATE TABLE "accounts_sessions" (
11
11
  "last_validated_at" timestamp with time zone NOT NULL,
12
12
  "refresh_lease_until" timestamp with time zone
13
13
  );
14
- CREATE INDEX "accounts_sessions_user_id_idx" ON "accounts_sessions" ("user_id");`,t={encrypt:async e=>e,decrypt:async e=>e};function n(e){if(e instanceof Date)return e.getTime();if(typeof e==`string`)return new Date(e).getTime();if(typeof e==`number`)return e;throw Error(`Unexpected timestamp value in accounts session row`)}function r(e){return new Date(e)}function i(e){let i=e.table??`accounts_sessions`;if(!/^[a-z_][a-z0-9_]*$/.test(i))throw Error(`Invalid accounts session table name: ${i}`);let a=`"${i}"`,o=e.codec??t;async function s(e){return{accessToken:await o.encrypt(e.accessToken),refreshToken:e.refreshToken?await o.encrypt(e.refreshToken):null,idToken:e.idToken?await o.encrypt(e.idToken):null}}async function c(e){return{id:e.id,user:e.user,tokens:{accessToken:await o.decrypt(e.access_token),refreshToken:e.refresh_token?await o.decrypt(e.refresh_token):null,idToken:e.id_token?await o.decrypt(e.id_token):null,accessTokenExpiresAt:n(e.access_token_expires_at)},data:e.data,createdAt:n(e.created_at),lastValidatedAt:n(e.last_validated_at),refreshLeaseUntil:e.refresh_lease_until===null||e.refresh_lease_until===void 0?null:n(e.refresh_lease_until)}}return{async create(t){let n=await s(t.tokens);await e.query(`INSERT INTO ${a} ("id", "user_id", "user", "access_token", "refresh_token", "id_token", "access_token_expires_at", "data", "created_at", "last_validated_at", "refresh_lease_until") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NULL)`,[t.id,t.user.id,t.user,n.accessToken,n.refreshToken,n.idToken,r(t.tokens.accessTokenExpiresAt),t.data,r(t.createdAt),r(t.lastValidatedAt)])},async read(t){let{rows:n}=await e.query(`SELECT * FROM ${a} WHERE "id" = $1`,[t]);return n[0]?c(n[0]):null},async update(t,n,i){let o=[],l=[t],u=(e,t)=>{l.push(t),o.push(`"${e}" = $${l.length}`)};if(n.user!==void 0&&(u(`user_id`,n.user.id),u(`user`,n.user)),n.tokens!==void 0){let e=await s(n.tokens);u(`access_token`,e.accessToken),u(`refresh_token`,e.refreshToken),u(`id_token`,e.idToken),u(`access_token_expires_at`,r(n.tokens.accessTokenExpiresAt))}if(n.data!==void 0&&u(`data`,n.data),n.lastValidatedAt!==void 0&&u(`last_validated_at`,r(n.lastValidatedAt)),n.refreshLeaseUntil!==void 0&&u(`refresh_lease_until`,n.refreshLeaseUntil===null?null:r(n.refreshLeaseUntil)),o.length===0)return this.read(t);let d=`"id" = $1`;i!==void 0&&(l.push(r(i)),d+=` AND "refresh_lease_until" = $${l.length}`);let{rows:f}=await e.query(`UPDATE ${a} SET ${o.join(`, `)} WHERE ${d} RETURNING *`,l);return f[0]?c(f[0]):null},async claimRefreshLease(t,n,i){let{rows:o}=await e.query(`UPDATE ${a} SET "refresh_lease_until" = $2 WHERE "id" = $1 AND ("refresh_lease_until" IS NULL OR "refresh_lease_until" <= $3) RETURNING *`,[t,r(n),r(i)]);return o[0]?c(o[0]):null},async delete(t,n){if(n===void 0){let{rows:n}=await e.query(`DELETE FROM ${a} WHERE "id" = $1 RETURNING "id"`,[t]);return n.length>0}let{rows:i}=await e.query(`DELETE FROM ${a} WHERE "id" = $1 AND "refresh_lease_until" = $2 RETURNING "id"`,[t,r(n)]);return i.length>0}}}export{e as PG_SESSION_TABLE_SQL,i as createPgSessionStore};
14
+ CREATE INDEX "accounts_sessions_user_id_idx" ON "accounts_sessions" ("user_id");`,t={encrypt:async e=>e,decrypt:async e=>e};function n(e){if(e instanceof Date)return e.getTime();if(typeof e==`string`)return new Date(e).getTime();if(typeof e==`number`)return e;throw Error(`Unexpected timestamp value in accounts session row`)}function r(e){return new Date(e)}function i(e){let i=e.table??`accounts_sessions`;if(!/^[a-z_][a-z0-9_]*$/.test(i))throw Error(`Invalid accounts session table name: ${i}`);let a=`"${i}"`,o=e.codec??t;async function s(e){return{accessToken:await o.encrypt(e.accessToken),refreshToken:e.refreshToken?await o.encrypt(e.refreshToken):null,idToken:e.idToken?await o.encrypt(e.idToken):null}}async function c(e){return{id:e.id,user:e.user,tokens:{accessToken:await o.decrypt(e.access_token),refreshToken:e.refresh_token?await o.decrypt(e.refresh_token):null,idToken:e.id_token?await o.decrypt(e.id_token):null,accessTokenExpiresAt:n(e.access_token_expires_at)},data:e.data,createdAt:n(e.created_at),lastValidatedAt:n(e.last_validated_at),refreshLeaseUntil:e.refresh_lease_until===null||e.refresh_lease_until===void 0?null:n(e.refresh_lease_until)}}return{async create(t){let n=await s(t.tokens);await e.query(`INSERT INTO ${a} ("id", "user_id", "user", "access_token", "refresh_token", "id_token", "access_token_expires_at", "data", "created_at", "last_validated_at", "refresh_lease_until") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, NULL)`,[t.id,t.user.id,t.user,n.accessToken,n.refreshToken,n.idToken,r(t.tokens.accessTokenExpiresAt),t.data,r(t.createdAt),r(t.lastValidatedAt)])},async read(t){let{rows:n}=await e.query(`SELECT * FROM ${a} WHERE "id" = $1`,[t]);return n[0]?c(n[0]):null},async update(t,n,i){let o=[],l=[t],u=(e,t)=>{l.push(t),o.push(`"${e}" = $${l.length}`)};if(n.user!==void 0&&(u(`user_id`,n.user.id),u(`user`,n.user)),n.tokens!==void 0){let e=await s(n.tokens);u(`access_token`,e.accessToken),u(`refresh_token`,e.refreshToken),u(`id_token`,e.idToken),u(`access_token_expires_at`,r(n.tokens.accessTokenExpiresAt))}if(n.data!==void 0&&u(`data`,n.data),n.lastValidatedAt!==void 0&&u(`last_validated_at`,r(n.lastValidatedAt)),n.refreshLeaseUntil!==void 0&&u(`refresh_lease_until`,n.refreshLeaseUntil===null?null:r(n.refreshLeaseUntil)),o.length===0){if(i===void 0)return this.read(t);let n=await e.query(`SELECT * FROM ${a} WHERE "id" = $1 AND "refresh_lease_until" = $2`,[t,r(i)]);return n.rows[0]?c(n.rows[0]):null}let d=`"id" = $1`;i!==void 0&&(l.push(r(i)),d+=` AND "refresh_lease_until" = $${l.length}`);let{rows:f}=await e.query(`UPDATE ${a} SET ${o.join(`, `)} WHERE ${d} RETURNING *`,l);return f[0]?c(f[0]):null},async claimRefreshLease(t,n,i){let{rows:o}=await e.query(`UPDATE ${a} SET "refresh_lease_until" = $2 WHERE "id" = $1 AND ("refresh_lease_until" IS NULL OR "refresh_lease_until" <= $3) RETURNING *`,[t,r(n),r(i)]);return o[0]?c(o[0]):null},async delete(t,n){if(n===void 0){let{rows:n}=await e.query(`DELETE FROM ${a} WHERE "id" = $1 RETURNING "id"`,[t]);return n.length>0}let{rows:i}=await e.query(`DELETE FROM ${a} WHERE "id" = $1 AND "refresh_lease_until" = $2 RETURNING "id"`,[t,r(n)]);return i.length>0},async sweep(t){let{rows:n}=await e.query(`WITH "removed" AS (DELETE FROM ${a} WHERE "created_at" < $1 OR "last_validated_at" < $2 RETURNING 1) SELECT count(*)::int AS "count" FROM "removed"`,[r(t.createdBefore),r(t.validatedBefore)]);return Number(n[0]?.count??0)}}}export{e as PG_SESSION_TABLE_SQL,i as createPgSessionStore};
@@ -1,4 +1,5 @@
1
- import { c as AccountsSessionStore, h as SessionCodec } from "../types-BcXM3ZEI.js";
1
+ import { t as SessionCodec } from "../codec-BPnk_EfF.js";
2
+ import { u as AccountsSessionStore } from "../types-C5wNLE3i.js";
2
3
  //#region src/database/redis.d.ts
3
4
  /**
4
5
  * The slice of a Redis client this store uses, so the package takes no Redis
@@ -1,20 +1,14 @@
1
- import { c as AccountsSessionStore, f as ResolvedAccountsAuthConfig, l as AccountsTokens, o as AccountsSession, t as AccountsAuthConfig, u as AccountsUser } from "./types-BcXM3ZEI.js";
1
+ import { c as AccountsSession, t as AccountsAuthConfig, u as AccountsSessionStore } from "./types-C5wNLE3i.js";
2
2
  //#region src/core/cookies.d.ts
3
3
  declare function requestOrigin(request: Request): string;
4
4
  declare function sanitizeRedirectPath(rawRedirect: string | null | undefined, origin: string): string;
5
5
  //#endregion
6
- //#region src/core/oidc.d.ts
7
- declare function accountsEndpoint(issuer: string, path: string): string;
8
- declare function fetchUserInfo(options: {
9
- issuer: string;
10
- fetch?: typeof fetch;
11
- }, accessToken: string): Promise<AccountsUser>;
12
- //#endregion
13
6
  //#region src/core/cache.d.ts
14
7
  declare function cacheCookieBase(sessionCookie: string): string;
15
8
  //#endregion
16
9
  //#region src/core/guard.d.ts
17
10
  type GuardOptions = {
11
+ /** Paths that need no session, on top of the auth routes and the error page. */
18
12
  publicPaths?: string[];
19
13
  apiPaths?: string[];
20
14
  redirectPath?: (request: Request) => string;
@@ -49,21 +43,7 @@ type SessionCookies = {
49
43
  cache: string | null;
50
44
  };
51
45
  declare function createAccountsAuth<Data>(input: AccountsAuthConfig<Data>): {
52
- config: ResolvedAccountsAuthConfig<Data>;
53
- sessions: {
54
- load: (cookieValue: string | null) => Promise<AccountsSession<Data> | null>;
55
- start: (input: {
56
- user: AccountsUser;
57
- tokens: AccountsTokens;
58
- data: Data;
59
- }) => Promise<{
60
- session: AccountsSession<Data>;
61
- cookieValue: string;
62
- }>;
63
- ensureFreshTokens: (session: AccountsSession<Data>) => Promise<AccountsSession<Data> | null>;
64
- revalidate: (session: AccountsSession<Data>) => Promise<AccountsSession<Data> | null>;
65
- end: (session: AccountsSession<Data>) => Promise<void>;
66
- };
46
+ cookieName: string;
67
47
  handlers: {
68
48
  login: (request: Request) => Promise<Response>;
69
49
  callback: (request: Request) => Promise<Response>;
@@ -71,14 +51,13 @@ declare function createAccountsAuth<Data>(input: AccountsAuthConfig<Data>): {
71
51
  };
72
52
  guard: (request: Request, options?: GuardOptions) => Promise<GuardResult<Data>>;
73
53
  resolveSession: (request: Request) => Promise<SessionResolution<Data>>;
74
- sessionFromCookie: (cookieValue: string | null) => Promise<AccountsSession<Data> | null>;
75
54
  sessionFromCookies: (cookies: SessionCookies) => Promise<AccountsSession<Data> | null>;
76
55
  sessionFromRequest: (request: Request) => Promise<AccountsSession<Data> | null>;
77
- accessTokenFromCookie: (cookieValue: string | null) => Promise<string | null>;
78
56
  accessTokenFromCookies: (cookies: SessionCookies) => Promise<string | null>;
79
57
  accessTokenFromRequest: (request: Request) => Promise<string | null>;
80
- updateSessionData: (cookieValue: string | null, update: (data: Data) => Data) => Promise<AccountsSession<Data> | null>;
58
+ updateSessionData: (cookies: SessionCookies, update: (data: Data) => Data) => Promise<AccountsSession<Data> | null>;
59
+ sweepSessions: () => Promise<number>;
81
60
  };
82
61
  type AccountsAuth<Data> = ReturnType<typeof createAccountsAuth<Data>>;
83
62
  //#endregion
84
- export { SessionResolution as a, cacheCookieBase as c, requestOrigin as d, sanitizeRedirectPath as f, createMemorySessionStore as i, accountsEndpoint as l, SessionCookies as n, GuardOptions as o, createAccountsAuth as r, GuardResult as s, AccountsAuth as t, fetchUserInfo as u };
63
+ export { SessionResolution as a, cacheCookieBase as c, createMemorySessionStore as i, requestOrigin as l, SessionCookies as n, GuardOptions as o, createAccountsAuth as r, GuardResult as s, AccountsAuth as t, sanitizeRedirectPath as u };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
- import { a as AccountsRequestError, c as AccountsSessionStore, d as OAuthTokenError, i as AccountsPaths, l as AccountsTokens, m as SignInInput, n as AccountsCacheConfig, o as AccountsSession, p as RevalidateInput, r as AccountsLifetimes, s as AccountsSessionPatch, t as AccountsAuthConfig, u as AccountsUser } from "./types-BcXM3ZEI.js";
2
- import { a as SessionResolution, c as cacheCookieBase, d as requestOrigin, f as sanitizeRedirectPath, i as createMemorySessionStore, l as accountsEndpoint, n as SessionCookies, o as GuardOptions, r as createAccountsAuth, s as GuardResult, t as AccountsAuth, u as fetchUserInfo } from "./index-CZZ3DRlf.js";
3
- export { AccountsAuth, type AccountsAuthConfig, type AccountsCacheConfig, type AccountsLifetimes, type AccountsPaths, AccountsRequestError, type AccountsSession, type AccountsSessionPatch, type AccountsSessionStore, type AccountsTokens, type AccountsUser, type GuardOptions, type GuardResult, OAuthTokenError, type RevalidateInput, SessionCookies, type SessionResolution, type SignInInput, accountsEndpoint, cacheCookieBase, createAccountsAuth, createMemorySessionStore, fetchUserInfo, requestOrigin, sanitizeRedirectPath };
1
+ import { _ as SignInInput, a as AccountsLifetimes, c as AccountsSession, d as AccountsTokens, f as AccountsUser, g as SessionSweepCutoffs, h as RevalidateInput, i as AccountsErrorPhase, l as AccountsSessionPatch, m as OAuthTokenError, n as AccountsCacheConfig, o as AccountsPaths, p as DEFAULT_LIFETIMES, r as AccountsErrorEvent, s as AccountsRequestError, t as AccountsAuthConfig, u as AccountsSessionStore, v as isAccessDenied } from "./types-C5wNLE3i.js";
2
+ import { n as accountsEndpoint, r as fetchUserInfo, t as AccountsFetchOptions } from "./oidc-D-qX1b25.js";
3
+ import { a as SessionResolution, c as cacheCookieBase, i as createMemorySessionStore, l as requestOrigin, n as SessionCookies, o as GuardOptions, r as createAccountsAuth, s as GuardResult, t as AccountsAuth, u as sanitizeRedirectPath } from "./index-oUN8Ug-7.js";
4
+ export { AccountsAuth, type AccountsAuthConfig, type AccountsCacheConfig, type AccountsErrorEvent, type AccountsErrorPhase, type AccountsFetchOptions, type AccountsLifetimes, type AccountsPaths, AccountsRequestError, type AccountsSession, type AccountsSessionPatch, type AccountsSessionStore, type AccountsTokens, type AccountsUser, DEFAULT_LIFETIMES, type GuardOptions, type GuardResult, OAuthTokenError, type RevalidateInput, SessionCookies, type SessionResolution, type SessionSweepCutoffs, type SignInInput, accountsEndpoint, cacheCookieBase, createAccountsAuth, createMemorySessionStore, fetchUserInfo, isAccessDenied, requestOrigin, sanitizeRedirectPath };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{C as e,D as t,E as n,S as r,T as i,_ as a,a as o,b as s,c,d as l,f as u,g as d,h as f,i as p,l as m,m as h,n as g,o as _,p as v,r as y,s as b,t as x,u as S,v as C,w,x as T}from"./cache-BWpH93Oi.js";function E(e,t){return[...C(e.cookieName,t),...x(e,t)]}function D(t,n){return async function(r){let a=i(r),o=e(r,t.cookieName),s=e(r,g(t.cookieName)),c=await p(t,s,o,t.now());if(c)return{session:c,cookies:[]};let l={session:null,cookies:o!==null||s!==null?E(t,a):[]},u=await n.load(o);if(!u)return l;let d=u;try{let e=await n.revalidate(u);if(!e)return l;d=e}catch{return{session:d,cookies:[]}}if(d.tokens.accessTokenExpiresAt<=t.now()){let e=await n.ensureFreshTokens(d).catch(()=>d);if(!e)return l;d=e}let f=await y(t,d,a,t.now());return{session:d,cookies:f?[f]:[]}}}function O(e,t){let n=D(e,t);return async function(t,i={}){let a=new URL(t.url);if((i.publicPaths??[e.paths.login,e.paths.callback,e.paths.error]).some(e=>a.pathname.startsWith(e)))return{kind:`ok`,session:null};let{session:o,cookies:s}=await n(t);return o?s.length>0?{kind:`ok`,session:o,cookies:s}:{kind:`ok`,session:o}:r(t,i,s)};function r(e,t,n){let r=n.length>0,i=new URL(e.url),o=(t.apiPaths??[`/api`,`/trpc`]).some(e=>i.pathname.startsWith(e))?Response.json({error:`Unauthorized`},{status:401}):w(a(e,t,r));for(let e of n)o.headers.append(`set-cookie`,e);return{kind:`response`,response:o}}function a(t,n,r){let a=new URL(t.url),o=new URL(e.paths.login,i(t));return o.searchParams.set(`redirect`,n.redirectPath?.(t)??`${a.pathname}${a.search}`),r&&o.searchParams.set(`silent`,`1`),o}}const k=/^[A-Za-z0-9_-]{1,128}$/,A=new Set([`login_required`,`consent_required`,`interaction_required`,`account_selection_required`]);function j(o,l){function u(e,t){let n=new URL(o.paths.login,e);return n.searchParams.set(`redirect`,t),n}function d(e,t,n){let r=new URL(o.paths.error,e);return r.searchParams.set(`reason`,t),n&&r.searchParams.set(`description`,n),r}async function p(e){let a=i(e),s=new URL(e.url).searchParams,c=n(s.get(`redirect`),a),l=s.get(`silent`)===`1`,{url:u,state:d,verifier:f}=await _(o,{origin:a,silent:l}),p=w(u);return t(p.headers,r(o.cookieName,d),T({verifier:f,redirectPath:c,silent:l}),a,o.lifetimes.flowMaxAgeMs),p}async function h(p){let h=i(p),g=new URL(p.url).searchParams,_=g.get(`state`),b=_&&k.test(_)?r(o.cookieName,_):null,S=b?s(e(p,b)):null,C=e=>(b&&a(e.headers,b,h),e);if(!S)return C(w(d(h,`invalid_state`,null)));let T=n(S.redirectPath,h),E=g.get(`error`);if(E)return S.silent&&A.has(E)?C(w(u(h,T))):C(w(d(h,E,g.get(`error_description`))));let D=g.get(`code`);if(!D)return C(w(d(h,`invalid_request`,null)));let O,j,M;try{let e=await c(o,{code:D,verifier:S.verifier,redirectUri:new URL(o.paths.callback,h).toString()});O=v(e,null,o.now()),j=await m(o,O.accessToken),M=await o.onSignIn({user:j,accessToken:O.accessToken,redirectPath:T,previous:null})}catch(e){return e instanceof f?C(w(d(h,e.code,e.description||null))):C(w(d(h,`accounts_unavailable`,null)))}if(M===null){let t=w(d(h,`access_denied`,null)),n=e(p,o.cookieName),r=await l.load(n);r&&await l.end(r),n!==null&&a(t.headers,o.cookieName,h);for(let e of x(o,h))t.headers.append(`set-cookie`,e);return C(t)}let N,P;try{({cookieValue:N,session:P}=await l.start({user:j,tokens:O,data:M}))}catch{return C(w(d(h,`session_store_error`,null)))}let F=w(new URL(T,h));t(F.headers,o.cookieName,N,h,o.lifetimes.absoluteMaxAgeMs);let I=await y(o,P,h,o.now()).catch(()=>null);return I&&F.headers.append(`set-cookie`,I),C(F)}async function g(t){let n=i(t),r=await l.load(e(t,o.cookieName));r&&await l.end(r);let a=r?.tokens.idToken?b(o,{origin:n,idToken:r.tokens.idToken}):new URL(o.paths.logoutComplete,n).toString(),s=w(a,303);for(let e of E(o,n))s.headers.append(`set-cookie`,e);return s}return{login:p,callback:h,logout:g}}const M=e=>new Promise(t=>setTimeout(t,e));function N(e){let{store:t,lifetimes:n}=e;function r(e,t){return t-e.createdAt>=n.absoluteMaxAgeMs||t-e.lastValidatedAt>=n.idleMaxAgeMs}function i(e,t){return e.accessTokenExpiresAt-t<=n.refreshBeforeExpiryMs}async function a(n){if(!n)return null;let i=await t.read(await u(n));return i?r(i,e.now())?(await t.delete(i.id),null):i:null}async function o(n){let r=S(32),i=e.now(),a={id:await u(r),user:n.user,tokens:n.tokens,data:n.data,createdAt:i,lastValidatedAt:i,refreshLeaseUntil:null};return await t.create(a),{session:a,cookieValue:r}}async function s(r){let a=e.now();if(!i(r.tokens,a))return r;if(!r.tokens.refreshToken)return await t.delete(r.id),null;let o=await t.claimRefreshLease(r.id,a+n.refreshLeaseMs,a);if(!o)return c(r.id,a);let s=o.refreshLeaseUntil??void 0;if(!i(o.tokens,a))return await t.update(o.id,{refreshLeaseUntil:null},s)??t.read(o.id);try{let r=await l(e,o.tokens.refreshToken,Math.max(n.refreshLeaseMs-1e3,n.refreshLeaseMs/2));return await t.update(o.id,{tokens:v(r,o.tokens,e.now()),refreshLeaseUntil:null},s)??t.read(o.id)}catch(n){if(n instanceof f&&n.code===`invalid_grant`)return await t.delete(o.id,s)?null:await t.read(o.id)??null;let r=await t.update(o.id,{refreshLeaseUntil:null},s);if(o.tokens.accessTokenExpiresAt>e.now())return r??o;throw n}}async function c(r,a){for(;;){await M(100);let o=await t.read(r);if(!o)return null;let c=e.now();if(!i(o.tokens,c))return o;if(o.refreshLeaseUntil===null||o.refreshLeaseUntil<=c)return s(o);if(c-a>=n.leaseWaitMs){if(o.tokens.accessTokenExpiresAt>c)return o;throw new h(`Timed out waiting for the session refresh lease`,0)}}}async function d(r){if(e.now()-r.lastValidatedAt<n.revalidateIntervalMs)return r;let i=await s(r);if(!i)return null;let a=await e.onRevalidate({session:i,accessToken:i.tokens.accessToken});return a===null?(await t.delete(i.id),null):t.update(i.id,{data:a,lastValidatedAt:e.now()})}async function p(e){await t.delete(e.id)}return{load:a,start:o,ensureFreshTokens:s,revalidate:d,end:p}}function P(){let e=new Map;return{rows:e,async create(t){e.set(t.id,structuredClone(t))},async read(t){let n=e.get(t);return n?structuredClone(n):null},async update(t,n,r){let i=e.get(t);if(!i||r!==void 0&&i.refreshLeaseUntil!==r)return null;let a=structuredClone({...i,...n});return e.set(t,a),structuredClone(a)},async claimRefreshLease(t,n,r){let i=e.get(t);if(!i||i.refreshLeaseUntil!==null&&i.refreshLeaseUntil>r)return null;let a={...i,refreshLeaseUntil:n};return e.set(t,a),structuredClone(a)},async delete(t,n){let r=e.get(t);return!r||n!==void 0&&r.refreshLeaseUntil!==n?!1:(e.delete(t),!0)}}}function F(t){let n=d(t),r=N(n),i=j(n,r),a=O(n,r),o=D(n,r);function s(t){return{session:e(t,n.cookieName),cache:e(t,g(n.cookieName))}}async function c(e){return r.load(e)}async function l(e){return await p(n,e.cache,e.session,n.now())??r.load(e.session)}async function u(e){return l(s(e))}async function f(e){let t=await r.load(e);return t?(await r.ensureFreshTokens(t))?.tokens.accessToken??null:null}async function m(e){let t=await p(n,e.cache,e.session,n.now());return t&&t.tokens.accessTokenExpiresAt-n.now()>n.lifetimes.refreshBeforeExpiryMs?t.tokens.accessToken:f(e.session)}async function h(e){return m(s(e))}async function _(e,t){let i=await r.load(e);return i?n.store.update(i.id,{data:t(i.data)}):null}return{config:n,sessions:r,handlers:i,guard:a,resolveSession:o,sessionFromCookie:c,sessionFromCookies:l,sessionFromRequest:u,accessTokenFromCookie:f,accessTokenFromCookies:m,accessTokenFromRequest:h,updateSessionData:_}}export{h as AccountsRequestError,f as OAuthTokenError,o as accountsEndpoint,g as cacheCookieBase,F as createAccountsAuth,P as createMemorySessionStore,m as fetchUserInfo,i as requestOrigin,n as sanitizeRedirectPath};
1
+ import{_ as e,a as t,c as n,d as r,f as i,g as a,h as o,i as s,l as c,m as l,n as u,o as d,p as f,r as p,t as m,u as h}from"./cache-BlNb0qE2.js";import{_ as g,a as _,c as v,d as y,f as b,g as x,h as S,i as C,l as w,m as T,o as E,p as D,r as O,s as k,t as A,u as j}from"./oidc-Dmqfk0L9.js";function M(e,t){return[...d(e.cookieName,t),...m(e,t)]}function N(e,t){return async function(n){let r=l(n),a=i(n,e.cookieName),o=i(n,u(e.cookieName)),c=await s(e,o,a,e.now());if(c)return{session:c,cookies:[]};let d={session:null,cookies:a!==null||o!==null?M(e,r):[]},f=await t.load(a);if(!f)return d;let m=f;try{let e=await t.revalidate(f);if(!e)return d;m=e}catch(t){x(e,`revalidate`,t)}let h=await p(e,m,r,e.now());return{session:m,cookies:h?[h]:[]}}}function P(e,t){let n=N(e,t);return async function(t,i={}){let a=new URL(t.url);if([e.paths.login,e.paths.callback,e.paths.logout,e.paths.logoutComplete,e.paths.error,...i.publicPaths??[]].some(e=>a.pathname.startsWith(e)))return{kind:`ok`,session:null};let{session:o,cookies:s}=await n(t);return o?s.length>0?{kind:`ok`,session:o,cookies:s}:{kind:`ok`,session:o}:r(t,i,s)};function r(e,t,n){let r=n.length>0,a=new URL(e.url),o=(t.apiPaths??[`/api`,`/trpc`]).some(e=>a.pathname.startsWith(e))?Response.json({error:`Unauthorized`},{status:401}):f(i(e,t,r));for(let e of n)o.headers.append(`set-cookie`,e);return{kind:`response`,response:o}}function i(t,n,r){let i=new URL(t.url),a=new URL(e.paths.login,l(t));return a.searchParams.set(`redirect`,n.redirectPath?.(t)??`${i.pathname}${i.search}`),r&&a.searchParams.set(`silent`,`1`),a}}const F=/^[A-Za-z0-9_-]{1,128}$/,I=new Set([`login_required`,`consent_required`,`interaction_required`,`account_selection_required`]);function L(s,u){function d(e,t){let n=new URL(s.paths.login,e);return n.searchParams.set(`redirect`,t),n}function g(e,t,n){let r=new URL(s.paths.error,e);return r.searchParams.set(`reason`,t),n&&r.searchParams.set(`description`,n),r}function v(e,t){let n=h(s.cookieName,``),i=[...r(e.headers.get(`cookie`)).keys()].filter(e=>e.replace(/^__Secure-/,``).startsWith(n)),o=i.length-4;for(let e of i.slice(0,Math.max(0,o)))t.headers.append(`set-cookie`,a(e,``,e.startsWith(`__Secure-`),0))}async function b(e){try{let t=await u.load(e);t&&await u.end(t)}catch(e){x(s,`session_store`,e)}}async function S(t){let n=l(t),r=new URL(t.url).searchParams,i=o(r.get(`redirect`),n),a=r.get(`silent`)===`1`,{url:u,state:d,verifier:p}=await O(s,{origin:n,silent:a}),m=f(u);return v(t,m),e(m.headers,h(s.cookieName,d),c({verifier:p,redirectPath:i,silent:a}),n,s.lifetimes.flowMaxAgeMs),m}async function w(r){let a=l(r),c=new URL(r.url).searchParams,v=c.get(`state`),S=v&&F.test(v)?h(s.cookieName,v):null,C=S?n(i(r,S)):null,w=e=>(S&&t(e.headers,S,a),e);if(!C)return w(f(g(a,`invalid_state`,null)));let D=o(C.redirectPath,a),O=c.get(`error`);if(O)return C.silent&&I.has(O)?w(f(d(a,D))):w(f(g(a,O,c.get(`error_description`))));let A=c.get(`code`);if(!A)return w(f(g(a,`invalid_request`,null)));let j,M,N;try{let e=await _(s,{code:A,verifier:C.verifier,redirectUri:new URL(s.paths.callback,a).toString()});j=y(e,null,s.now()),M=await k(E(s),j.accessToken),N=await s.onSignIn({user:M,accessToken:j.accessToken,redirectPath:D,previous:null})}catch(e){return x(s,`sign_in`,e),e instanceof T?w(f(g(a,e.code,e.description||null))):w(f(g(a,`accounts_unavailable`,null)))}let P=i(r,s.cookieName);if(N===null){let e=f(g(a,`access_denied`,null));P!==null&&(await b(P),t(e.headers,s.cookieName,a));for(let t of m(s,a))e.headers.append(`set-cookie`,t);return w(e)}let L,R;try{({cookieValue:L,session:R}=await u.start({user:M,tokens:j,data:N}))}catch(e){return x(s,`session_store`,e),w(f(g(a,`session_store_error`,null)))}P!==null&&await b(P);let z=f(new URL(D,a));e(z.headers,s.cookieName,L,a,s.lifetimes.absoluteMaxAgeMs);let B=await p(s,R,a,s.now());return B&&z.headers.append(`set-cookie`,B),w(z)}async function D(e){let t=l(e),n=await u.load(i(e,s.cookieName));n&&await u.end(n);let r=n?.tokens.idToken?C(s,{origin:t,idToken:n.tokens.idToken}):new URL(s.paths.logoutComplete,t).toString(),a=f(r,303);for(let e of M(s,t))a.headers.append(`set-cookie`,e);return a}return{login:S,callback:w,logout:D}}const R=e=>new Promise(t=>setTimeout(t,e));function z(e){let{store:t,lifetimes:n}=e;function r(e,t){return t-e.createdAt>=n.absoluteMaxAgeMs||t-e.lastValidatedAt>=n.idleMaxAgeMs}function i(e,t){return e.accessTokenExpiresAt-t<=n.refreshBeforeExpiryMs}async function a(n){if(!n)return null;let i=await t.read(await j(n));return i?r(i,e.now())?(await t.delete(i.id),null):i:null}async function o(n){let r=v(32),i=e.now(),a={id:await j(r),user:n.user,tokens:n.tokens,data:n.data,createdAt:i,lastValidatedAt:i,refreshLeaseUntil:null};return await t.create(a),{session:a,cookieValue:r}}async function s(r){let a=e.now();if(!i(r.tokens,a))return r;if(!r.tokens.refreshToken)return await t.delete(r.id),null;let o=await t.claimRefreshLease(r.id,a+n.refreshLeaseMs,a);if(!o)return c(r.id,a);let s=o.refreshLeaseUntil??void 0;if(!i(o.tokens,a))return await t.update(o.id,{refreshLeaseUntil:null},s)??t.read(o.id);try{let r=await w(e,o.tokens.refreshToken,Math.max(n.refreshLeaseMs-1e3,n.refreshLeaseMs/2));return await t.update(o.id,{tokens:y(r,o.tokens,e.now()),refreshLeaseUntil:null},s)??t.read(o.id)}catch(n){if(n instanceof T&&n.code===`invalid_grant`)return await t.delete(o.id,s)?null:await t.read(o.id)??null;let r=await t.update(o.id,{refreshLeaseUntil:null},s);if(o.tokens.accessTokenExpiresAt>e.now())return x(e,`refresh`,n),r??o;throw n}}async function c(r,a){for(;;){await R(100);let o=await t.read(r);if(!o)return null;let c=e.now();if(!i(o.tokens,c))return o;if(o.refreshLeaseUntil===null||o.refreshLeaseUntil<=c)return s(o);if(c-a>=n.leaseWaitMs){if(o.tokens.accessTokenExpiresAt>c)return o;throw new b(`Timed out waiting for the session refresh lease`,0)}}}async function l(r){let i;try{i=await s(r)}catch(t){x(e,`refresh`,t),i=r}if(!i)return null;if(e.now()-i.lastValidatedAt<n.revalidateIntervalMs||!u(i.tokens))return i;let a=e.revalidateUser?await f(i):i.user;if(!a)return d(i);if(!u(i.tokens))return i;let o=await e.onRevalidate({session:i,accessToken:i.tokens.accessToken,user:a});if(o===null)return d(i);let c={data:o,lastValidatedAt:e.now()};return a!==i.user&&(c.user=a),t.update(i.id,c)}function u(t){return t.accessTokenExpiresAt-e.now()>n.requestTimeoutMs}async function d(n){return n.tokens.accessTokenExpiresAt<=e.now()?n:(await t.delete(n.id),null)}async function f(t){try{return await k(E(e),t.tokens.accessToken)}catch(n){return S(n)?null:(x(e,`revalidate`,n),t.user)}}async function p(e){await t.delete(e.id)}async function m(){if(!t.sweep)return 0;let r=e.now();return t.sweep({createdBefore:r-n.absoluteMaxAgeMs,validatedBefore:r-n.idleMaxAgeMs})}return{load:a,start:o,ensureFreshTokens:s,revalidate:l,end:p,sweep:m}}function B(){let e=new Map;return{rows:e,async create(t){e.set(t.id,structuredClone(t))},async read(t){let n=e.get(t);return n?structuredClone(n):null},async update(t,n,r){let i=e.get(t);if(!i||r!==void 0&&i.refreshLeaseUntil!==r)return null;let a=structuredClone({...i,...n});return e.set(t,a),structuredClone(a)},async claimRefreshLease(t,n,r){let i=e.get(t);if(!i||i.refreshLeaseUntil!==null&&i.refreshLeaseUntil>r)return null;let a={...i,refreshLeaseUntil:n};return e.set(t,a),structuredClone(a)},async delete(t,n){let r=e.get(t);return!r||n!==void 0&&r.refreshLeaseUntil!==n?!1:(e.delete(t),!0)},async sweep(t){let n=0;for(let[r,i]of e)(i.createdAt<t.createdBefore||i.lastValidatedAt<t.validatedBefore)&&(e.delete(r),n+=1);return n}}}function V(e){let t=g(e),n=z(t),r=L(t,n),a=P(t,n),o=N(t,n);function c(e){return{session:i(e,t.cookieName),cache:i(e,u(t.cookieName))}}async function l(e){return await s(t,e.cache,e.session,t.now())??n.load(e.session)}async function d(e){return l(c(e))}async function f(e){let r=await s(t,e.cache,e.session,t.now());if(r&&r.tokens.accessTokenExpiresAt>t.now())return r.tokens.accessToken;let i=await n.load(e.session);return i?(await n.ensureFreshTokens(i))?.tokens.accessToken??null:null}async function p(e){return f(c(e))}async function m(e,r){let i=await n.load(e.session);return i?t.store.update(i.id,{data:r(i.data)}):null}function h(){return n.sweep()}return{cookieName:t.cookieName,handlers:r,guard:a,resolveSession:o,sessionFromCookies:l,sessionFromRequest:d,accessTokenFromCookies:f,accessTokenFromRequest:p,updateSessionData:m,sweepSessions:h}}export{b as AccountsRequestError,D as DEFAULT_LIFETIMES,T as OAuthTokenError,A as accountsEndpoint,u as cacheCookieBase,V as createAccountsAuth,B as createMemorySessionStore,k as fetchUserInfo,S as isAccessDenied,l as requestOrigin,o as sanitizeRedirectPath};
package/dist/next.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { o as AccountsSession } from "./types-BcXM3ZEI.js";
2
- import { t as AccountsAuth } from "./index-CZZ3DRlf.js";
1
+ import { c as AccountsSession } from "./types-C5wNLE3i.js";
2
+ import { t as AccountsAuth } from "./index-oUN8Ug-7.js";
3
3
  //#region src/next/index.d.ts
4
4
  declare function withNextRequestScope<Data>(auth: AccountsAuth<Data>): {
5
5
  getSession(): Promise<AccountsSession<Data> | null>;
package/dist/next.js CHANGED
@@ -1 +1 @@
1
- import{n as e,y as t}from"./cache-BWpH93Oi.js";import{cookies as n}from"next/headers";import{connection as r}from"next/server";async function i(i){await r();let a=await n(),o=e=>a.get(t(e,!0))?.value||a.get(t(e,!1))?.value||null;return{session:o(i),cache:o(e(i))}}async function a(r){let i=await n(),a=e(r);for(let e of[t(a,!0),t(a,!1)])try{i.delete(e)}catch{}}function o(e){let t=e.config.cookieName;return{async getSession(){return e.sessionFromCookies(await i(t))},async getAccessToken(){return e.accessTokenFromCookies(await i(t))},async updateSessionData(n){let r=await e.updateSessionData((await i(t)).session,n);return r&&await a(t),r}}}export{o as withNextRequestScope};
1
+ import{n as e,s as t}from"./cache-BlNb0qE2.js";import{cookies as n}from"next/headers";import{connection as r}from"next/server";async function i(i){await r();let a=await n(),o=e=>a.get(t(e,!0))?.value||a.get(t(e,!1))?.value||null;return{session:o(i),cache:o(e(i))}}async function a(r){let i=await n(),a=e(r);for(let e of[t(a,!0),t(a,!1)])try{i.delete(e)}catch{}}function o(e){let t=e.cookieName;return{async getSession(){return e.sessionFromCookies(await i(t))},async getAccessToken(){return e.accessTokenFromCookies(await i(t))},async updateSessionData(n){let r=await e.updateSessionData(await i(t),n);return r&&await a(t),r}}}export{o as withNextRequestScope};
@@ -0,0 +1,11 @@
1
+ import { f as AccountsUser } from "./types-C5wNLE3i.js";
2
+ //#region src/core/oidc.d.ts
3
+ type AccountsFetchOptions = {
4
+ issuer: string;
5
+ fetch?: typeof fetch;
6
+ timeoutMs?: number;
7
+ };
8
+ declare function accountsEndpoint(issuer: string, path: string): string;
9
+ declare function fetchUserInfo(options: AccountsFetchOptions, accessToken: string): Promise<AccountsUser>;
10
+ //#endregion
11
+ export { accountsEndpoint as n, fetchUserInfo as r, AccountsFetchOptions as t };
@@ -0,0 +1 @@
1
+ import{n as e}from"./codec-D-31dtWO.js";const t={login:`/api/auth/login`,callback:`/api/auth/callback`,logout:`/api/auth/logout`,logoutComplete:`/api/auth/logout-complete`,error:`/auth/error`},n={absoluteMaxAgeMs:2592e6,idleMaxAgeMs:6048e5,revalidateIntervalMs:12e4,refreshBeforeExpiryMs:3e5,flowMaxAgeMs:6e5,refreshLeaseMs:2e4,leaseWaitMs:5e3,requestTimeoutMs:1e4,retryIntervalMs:1e4},r=Object.freeze({});function i({phase:e,error:t}){console.error(`[aui-auth] ${e}`,t)}function a(e,t,n){try{e.onError({phase:t,error:n})}catch{}}function o(a){return{...a,scope:a.scope??`openid profile email offline_access`,onSignIn:a.onSignIn??(async()=>r),onRevalidate:a.onRevalidate??(async()=>r),onError:a.onError??i,revalidateUser:a.revalidateUser??!1,paths:{...t,...a.paths},lifetimes:{...n,...a.lifetimes},fetch:a.fetch??globalThis.fetch.bind(globalThis),now:a.now??Date.now,cache:a.cache?{codec:e(a.cache.secret),maxAgeMs:a.cache.maxAgeMs??6e4}:null,audience:a.audience===!1?null:a.audience??new URL(`/api/auth`,a.issuer).toString()}}var s=class extends Error{status;constructor(e,t){super(e),this.name=`AccountsRequestError`,this.status=t}},c=class extends s{code;description;constructor(e,t,n){super(`Token request failed: ${e} ${t} ${n}`,e),this.name=`OAuthTokenError`,this.code=t,this.description=n}};const l=new Set([408,425,429]);function u(e){return e instanceof s&&e.status>=400&&e.status<500&&!l.has(e.status)}function d(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/g,``)}function f(e){return d(crypto.getRandomValues(new Uint8Array(e)))}async function p(e){let t=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(e));return d(new Uint8Array(t))}function m(e,t){return new URL(t,e).toString()}function h(e){return{issuer:e.issuer,fetch:e.fetch,timeoutMs:e.lifetimes.requestTimeoutMs}}async function g(e,t,r={},i){let a=e.fetch??globalThis.fetch.bind(globalThis),o=e.timeoutMs??n.requestTimeoutMs,c=i===void 0?o:Math.min(o,i-Date.now());if(c<=0)throw new s(`Accounts request deadline passed`,0);return a(m(e.issuer,t),{...r,cache:`no-store`,signal:AbortSignal.timeout(c)})}async function _(e,t){let n=f(24),r=f(32),i=new URL(m(e.issuer,`/api/auth/oauth2/authorize`));return i.searchParams.set(`client_id`,e.clientId),i.searchParams.set(`redirect_uri`,new URL(e.paths.callback,t.origin).toString()),i.searchParams.set(`response_type`,`code`),i.searchParams.set(`scope`,e.scope),i.searchParams.set(`state`,n),i.searchParams.set(`code_challenge`,await p(r)),i.searchParams.set(`code_challenge_method`,`S256`),t.silent&&i.searchParams.set(`prompt`,`none`),{url:i.toString(),state:n,verifier:r}}function v(e,t){let n=new URL(m(e.issuer,`/api/auth/oauth2/end-session`));return n.searchParams.set(`id_token_hint`,t.idToken),n.searchParams.set(`client_id`,e.clientId),n.searchParams.set(`post_logout_redirect_uri`,new URL(e.paths.logoutComplete,t.origin).toString()),n.toString()}async function y(e,t,n,r=!0){let i=new URLSearchParams({client_id:e.clientId,...t});e.clientSecret&&i.set(`client_secret`,e.clientSecret),e.audience&&r&&i.set(`resource`,e.audience);let a=await g(h(e),`/api/auth/oauth2/token`,{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:i},n);if(!a.ok){let i=await a.text().catch(()=>``),o=`unknown_error`,s=i;try{let e=JSON.parse(i);o=e.error??o,s=e.error_description??s}catch{}if(r&&e.audience&&o===`invalid_request`&&/resource/i.test(s))return y(e,t,n,!1);throw new c(a.status,o,s)}return await a.json()}function b(e,t){return y(e,{grant_type:`authorization_code`,code:t.code,code_verifier:t.verifier,redirect_uri:t.redirectUri})}function x(e,t,n){return y(e,{grant_type:`refresh_token`,refresh_token:t},n===void 0?void 0:Date.now()+n)}async function S(e,t){let n=await g(e,`/api/auth/oauth2/userinfo`,{headers:{Authorization:`Bearer ${t}`}});if(!n.ok)throw new s(`Failed to load user info: ${n.status}`,n.status);let r=await n.json();return{id:r.sub,email:r.email??``,name:r.name??r.email??r.sub,image:r.picture??null}}function C(e,t,n){return{accessToken:e.access_token,refreshToken:e.refresh_token??t?.refreshToken??null,idToken:e.id_token??t?.idToken??null,accessTokenExpiresAt:typeof e.expires_at==`number`?e.expires_at*1e3:n+(e.expires_in??3600)*1e3}}export{o as _,b as a,f as c,C as d,s as f,a as g,u as h,v as i,x as l,c as m,g as n,h as o,n as p,_ as r,S as s,m as t,p as u};
package/dist/testing.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { c as AccountsSessionStore } from "./types-BcXM3ZEI.js";
1
+ import { u as AccountsSessionStore } from "./types-C5wNLE3i.js";
2
2
  //#region src/testing/index.d.ts
3
3
  /**
4
4
  * A behavioural conformance suite for the session store contract.
package/dist/testing.js CHANGED
@@ -1 +1 @@
1
- import e from"node:assert/strict";const t=17e11;function n(e,n){return{id:e,user:{id:`user_1`,email:`u@test`,name:`U`,image:null},tokens:{accessToken:`at_1`,refreshToken:`rt_1`,idToken:`idt_1`,accessTokenExpiresAt:17000036e5},data:n,createdAt:t,lastValidatedAt:t,refreshLeaseUntil:null}}function r(r){let i=r.data??{seed:1},a=r.otherData??{seed:2},o=()=>Promise.resolve(r.createStore()),s=async(e=`sess_1`)=>{let t=await o();return await t.create(n(e,i)),t};return[{name:`read returns every field create was given`,run:async()=>{let t=await s();e.deepEqual(await t.read(`sess_1`),n(`sess_1`,i))}},{name:`read answers null for an id that was never created`,run:async()=>{let t=await o();e.equal(await t.read(`missing`),null)}},{name:`create keeps a null refresh token null rather than empty`,run:async()=>{let t=await o(),r=n(`sess_1`,i);r.tokens={...r.tokens,refreshToken:null,idToken:null},await t.create(r);let a=await t.read(`sess_1`);e.equal(a?.tokens.refreshToken,null),e.equal(a?.tokens.idToken,null)}},{name:`update writes only the patched fields`,run:async()=>{let n=await(await s()).update(`sess_1`,{data:a});e.deepEqual(n?.data,a),e.equal(n?.lastValidatedAt,t),e.equal(n?.tokens.accessToken,`at_1`)}},{name:`update answers null for an id that is not there`,run:async()=>{let t=await o();e.equal(await t.update(`missing`,{lastValidatedAt:1}),null)}},{name:`update applies when ifLeaseUntil is the lease on the row`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t);let r=await n.update(`sess_1`,{lastValidatedAt:1700000000005},170000002e4);e.equal(r?.lastValidatedAt,1700000000005)}},{name:`update writes nothing when ifLeaseUntil is not the lease on the row`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t);let r=await n.update(`sess_1`,{lastValidatedAt:1700000000005},1700000999999);e.equal(r,null),e.equal((await n.read(`sess_1`))?.lastValidatedAt,t)}},{name:`update releases the lease when the patch sets it null`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t),await n.update(`sess_1`,{refreshLeaseUntil:null},170000002e4),e.equal((await n.read(`sess_1`))?.refreshLeaseUntil,null)}},{name:`claimRefreshLease takes a lease when the row holds none`,run:async()=>{let n=await s(),r=await n.claimRefreshLease(`sess_1`,170000002e4,t);e.equal(r?.refreshLeaseUntil,170000002e4),e.equal((await n.read(`sess_1`))?.refreshLeaseUntil,170000002e4)}},{name:`claimRefreshLease refuses while another lease is still live`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t),e.equal(await n.claimRefreshLease(`sess_1`,170000004e4,1700000001e3),null),e.equal((await n.read(`sess_1`))?.refreshLeaseUntil,170000002e4)}},{name:`claimRefreshLease takes over a lease that has expired`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t);let r=await n.claimRefreshLease(`sess_1`,170000006e4,1700000020001);e.equal(r?.refreshLeaseUntil,170000006e4)}},{name:`claimRefreshLease answers null for an id that is not there`,run:async()=>{let n=await o();e.equal(await n.claimRefreshLease(`missing`,170000002e4,t),null)}},{name:`delete without a lease removes the session and reports true`,run:async()=>{let t=await s();e.equal(await t.delete(`sess_1`),!0),e.equal(await t.read(`sess_1`),null)}},{name:`delete reports false for an id that is not there`,run:async()=>{let t=await o();e.equal(await t.delete(`missing`),!1)}},{name:`delete applies when ifLeaseUntil is the lease on the row`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t),e.equal(await n.delete(`sess_1`,170000002e4),!0),e.equal(await n.read(`sess_1`),null)}},{name:`delete keeps the session when ifLeaseUntil is not its lease`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t),e.equal(await n.delete(`sess_1`,1700000999999),!1),e.notEqual(await n.read(`sess_1`),null)}},{name:`sessions are addressed independently`,run:async()=>{let t=await s(`sess_1`);await t.create(n(`sess_2`,a)),await t.delete(`sess_1`),e.equal(await t.read(`sess_1`),null),e.deepEqual((await t.read(`sess_2`))?.data,a)}}]}export{r as sessionStoreConformance};
1
+ import e from"node:assert/strict";const t=17e11;function n(e,n){return{id:e,user:{id:`user_1`,email:`u@test`,name:`U`,image:null},tokens:{accessToken:`at_1`,refreshToken:`rt_1`,idToken:`idt_1`,accessTokenExpiresAt:17000036e5},data:n,createdAt:t,lastValidatedAt:t,refreshLeaseUntil:null}}function r(r){let i=r.data??{seed:1},a=r.otherData??{seed:2},o=()=>Promise.resolve(r.createStore()),s=async(e=`sess_1`)=>{let t=await o();return await t.create(n(e,i)),t};return[{name:`read returns every field create was given`,run:async()=>{let t=await s();e.deepEqual(await t.read(`sess_1`),n(`sess_1`,i))}},{name:`read answers null for an id that was never created`,run:async()=>{let t=await o();e.equal(await t.read(`missing`),null)}},{name:`create keeps a null refresh token null rather than empty`,run:async()=>{let t=await o(),r=n(`sess_1`,i);r.tokens={...r.tokens,refreshToken:null,idToken:null},await t.create(r);let a=await t.read(`sess_1`);e.equal(a?.tokens.refreshToken,null),e.equal(a?.tokens.idToken,null)}},{name:`update writes only the patched fields`,run:async()=>{let n=await(await s()).update(`sess_1`,{data:a});e.deepEqual(n?.data,a),e.equal(n?.lastValidatedAt,t),e.equal(n?.tokens.accessToken,`at_1`)}},{name:`update answers null for an id that is not there`,run:async()=>{let t=await o();e.equal(await t.update(`missing`,{lastValidatedAt:1}),null)}},{name:`update applies when ifLeaseUntil is the lease on the row`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t);let r=await n.update(`sess_1`,{lastValidatedAt:1700000000005},170000002e4);e.equal(r?.lastValidatedAt,1700000000005)}},{name:`update writes nothing when ifLeaseUntil is not the lease on the row`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t);let r=await n.update(`sess_1`,{lastValidatedAt:1700000000005},1700000999999);e.equal(r,null),e.equal((await n.read(`sess_1`))?.lastValidatedAt,t)}},{name:`update with an empty patch still honours ifLeaseUntil`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t),e.equal(await n.update(`sess_1`,{},1700000999999),null),e.notEqual(await n.update(`sess_1`,{},170000002e4),null)}},{name:`update releases the lease when the patch sets it null`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t),await n.update(`sess_1`,{refreshLeaseUntil:null},170000002e4),e.equal((await n.read(`sess_1`))?.refreshLeaseUntil,null)}},{name:`claimRefreshLease takes a lease when the row holds none`,run:async()=>{let n=await s(),r=await n.claimRefreshLease(`sess_1`,170000002e4,t);e.equal(r?.refreshLeaseUntil,170000002e4),e.equal((await n.read(`sess_1`))?.refreshLeaseUntil,170000002e4)}},{name:`claimRefreshLease refuses while another lease is still live`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t),e.equal(await n.claimRefreshLease(`sess_1`,170000004e4,1700000001e3),null),e.equal((await n.read(`sess_1`))?.refreshLeaseUntil,170000002e4)}},{name:`claimRefreshLease takes over a lease that has expired`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t);let r=await n.claimRefreshLease(`sess_1`,170000006e4,1700000020001);e.equal(r?.refreshLeaseUntil,170000006e4)}},{name:`claimRefreshLease answers null for an id that is not there`,run:async()=>{let n=await o();e.equal(await n.claimRefreshLease(`missing`,170000002e4,t),null)}},{name:`delete without a lease removes the session and reports true`,run:async()=>{let t=await s();e.equal(await t.delete(`sess_1`),!0),e.equal(await t.read(`sess_1`),null)}},{name:`delete reports false for an id that is not there`,run:async()=>{let t=await o();e.equal(await t.delete(`missing`),!1)}},{name:`delete applies when ifLeaseUntil is the lease on the row`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t),e.equal(await n.delete(`sess_1`,170000002e4),!0),e.equal(await n.read(`sess_1`),null)}},{name:`delete keeps the session when ifLeaseUntil is not its lease`,run:async()=>{let n=await s();await n.claimRefreshLease(`sess_1`,170000002e4,t),e.equal(await n.delete(`sess_1`,1700000999999),!1),e.notEqual(await n.read(`sess_1`),null)}},{name:`sessions are addressed independently`,run:async()=>{let t=await s(`sess_1`);await t.create(n(`sess_2`,a)),await t.delete(`sess_1`),e.equal(await t.read(`sess_1`),null),e.deepEqual((await t.read(`sess_2`))?.data,a)}},{name:`sweep removes the sessions created or last validated before the cutoffs`,run:async()=>{let t=await s(`sess_1`);if(!t.sweep)return;await t.create({...n(`sess_old`,i),createdAt:1699999999990}),await t.create({...n(`sess_idle`,i),lastValidatedAt:1699999999990});let r=await t.sweep({createdBefore:1699999999995,validatedBefore:1699999999995});e.equal(r,2),e.notEqual(await t.read(`sess_1`),null),e.equal(await t.read(`sess_old`),null),e.equal(await t.read(`sess_idle`),null),e.equal(await t.sweep({createdBefore:1699999999995,validatedBefore:1699999999995}),0)}}]}export{r as sessionStoreConformance};
@@ -1,10 +1,3 @@
1
- //#region src/core/codec.d.ts
2
- type SessionCodec = {
3
- encrypt(value: string): Promise<string>;
4
- decrypt(value: string): Promise<string>;
5
- };
6
- declare function createAesGcmCodec(hexKey: string): SessionCodec;
7
- //#endregion
8
1
  //#region src/core/types.d.ts
9
2
  type AccountsUser = {
10
3
  id: string;
@@ -28,6 +21,10 @@ type AccountsSession<Data> = {
28
21
  refreshLeaseUntil: number | null;
29
22
  };
30
23
  type AccountsSessionPatch<Data> = Partial<Pick<AccountsSession<Data>, "user" | "tokens" | "data" | "lastValidatedAt" | "refreshLeaseUntil">>;
24
+ type SessionSweepCutoffs = {
25
+ createdBefore: number;
26
+ validatedBefore: number;
27
+ };
31
28
  interface AccountsSessionStore<Data> {
32
29
  create(session: AccountsSession<Data>): Promise<void>;
33
30
  read(id: string): Promise<AccountsSession<Data> | null>;
@@ -40,6 +37,14 @@ interface AccountsSessionStore<Data> {
40
37
  * "gone" and "someone else owns it".
41
38
  */
42
39
  delete(id: string, ifLeaseUntil?: number): Promise<boolean>;
40
+ /**
41
+ * Removes every session created before `createdBefore` or last validated
42
+ * before `validatedBefore` and resolves how many went. Expiry is otherwise
43
+ * enforced only when a session is read, so rows whose cookie never returns
44
+ * stay until something sweeps them. A store whose backend expires keys on
45
+ * its own leaves it out.
46
+ */
47
+ sweep?(cutoffs: SessionSweepCutoffs): Promise<number>;
43
48
  }
44
49
  type AccountsPaths = {
45
50
  login: string;
@@ -56,6 +61,14 @@ type AccountsLifetimes = {
56
61
  flowMaxAgeMs: number;
57
62
  refreshLeaseMs: number;
58
63
  leaseWaitMs: number;
64
+ /** Bounds every call to accounts; a refresh is also bounded by its lease. */
65
+ requestTimeoutMs: number;
66
+ /**
67
+ * How long the cache cookie serves requests after a revalidation or refresh
68
+ * failed, so a provider outage is retried on an interval rather than on
69
+ * every request.
70
+ */
71
+ retryIntervalMs: number;
59
72
  };
60
73
  type SignInInput<Data> = {
61
74
  user: AccountsUser;
@@ -66,6 +79,19 @@ type SignInInput<Data> = {
66
79
  type RevalidateInput<Data> = {
67
80
  session: AccountsSession<Data>;
68
81
  accessToken: string;
82
+ /** The profile as accounts reports it now with `revalidateUser`, otherwise the copy taken at sign-in. */
83
+ user: AccountsUser;
84
+ };
85
+ /**
86
+ * Where a failure the flow handled rather than threw came from: the sign-in
87
+ * that became an error redirect, the store write behind `session_store_error`,
88
+ * the cache cookie that could not be written, the revalidation or the refresh
89
+ * that were kept quiet so a provider outage would not sign anyone out.
90
+ */
91
+ type AccountsErrorPhase = "sign_in" | "session_store" | "cache_cookie" | "revalidate" | "refresh";
92
+ type AccountsErrorEvent = {
93
+ phase: AccountsErrorPhase;
94
+ error: unknown;
69
95
  };
70
96
  type AccountsAuthConfig<Data> = {
71
97
  issuer: string;
@@ -87,6 +113,22 @@ type AccountsAuthConfig<Data> = {
87
113
  * Omit it when the app keeps nothing of its own on the session.
88
114
  */
89
115
  onRevalidate?(input: RevalidateInput<Data>): Promise<Data | null>;
116
+ /**
117
+ * Re-reads the account profile on every revalidation, hands it to
118
+ * `onRevalidate` and stores it, at the cost of one more call to accounts per
119
+ * user every `revalidateIntervalMs`. `session.user` is otherwise captured at
120
+ * sign-in and never changes again, so a renamed user keeps the old name
121
+ * until the next sign-in. Accounts rejecting the token (`isAccessDenied`)
122
+ * ends the session; any other failure keeps the captured copy.
123
+ */
124
+ revalidateUser?: boolean;
125
+ /**
126
+ * Reports a failure the flow handled rather than threw. Every one of them is
127
+ * invisible otherwise, which turns `session_store_error` into a redirect
128
+ * with no cause attached, so the default writes to `console.error`; pass a
129
+ * no-op to silence it or your own logger to route it.
130
+ */
131
+ onError?(event: AccountsErrorEvent): void;
90
132
  fetch?: typeof fetch;
91
133
  now?: () => number;
92
134
  cache?: AccountsCacheConfig;
@@ -96,20 +138,7 @@ type AccountsCacheConfig = {
96
138
  secret: string;
97
139
  maxAgeMs?: number;
98
140
  };
99
- type ResolvedAccountsAuthConfig<Data> = Omit<AccountsAuthConfig<Data>, "paths" | "lifetimes" | "scope" | "fetch" | "now" | "cache" | "audience" | "onSignIn" | "onRevalidate"> & {
100
- onSignIn(input: SignInInput<Data>): Promise<Data | null>;
101
- onRevalidate(input: RevalidateInput<Data>): Promise<Data | null>;
102
- scope: string;
103
- paths: AccountsPaths;
104
- lifetimes: AccountsLifetimes;
105
- fetch: typeof fetch;
106
- now: () => number;
107
- cache: {
108
- codec: () => Promise<SessionCodec>;
109
- maxAgeMs: number;
110
- } | null;
111
- audience: string | null;
112
- };
141
+ declare const DEFAULT_LIFETIMES: AccountsLifetimes;
113
142
  declare class AccountsRequestError extends Error {
114
143
  readonly status: number;
115
144
  constructor(message: string, status: number);
@@ -119,5 +148,12 @@ declare class OAuthTokenError extends AccountsRequestError {
119
148
  readonly description: string;
120
149
  constructor(status: number, code: string, description: string);
121
150
  }
151
+ /**
152
+ * Whether accounts rejected the token, the client, or the user, which ends
153
+ * the session, as opposed to failing to answer, which keeps it. Any 4xx but a
154
+ * timeout or a rate limit counts: the provider answers a revoked token or a
155
+ * disabled client with 400 as readily as with 401.
156
+ */
157
+ declare function isAccessDenied(error: unknown): boolean;
122
158
  //#endregion
123
- export { AccountsRequestError as a, AccountsSessionStore as c, OAuthTokenError as d, ResolvedAccountsAuthConfig as f, createAesGcmCodec as g, SessionCodec as h, AccountsPaths as i, AccountsTokens as l, SignInInput as m, AccountsCacheConfig as n, AccountsSession as o, RevalidateInput as p, AccountsLifetimes as r, AccountsSessionPatch as s, AccountsAuthConfig as t, AccountsUser as u };
159
+ export { SignInInput as _, AccountsLifetimes as a, AccountsSession as c, AccountsTokens as d, AccountsUser as f, SessionSweepCutoffs as g, RevalidateInput as h, AccountsErrorPhase as i, AccountsSessionPatch as l, OAuthTokenError as m, AccountsCacheConfig as n, AccountsPaths as o, DEFAULT_LIFETIMES as p, AccountsErrorEvent as r, AccountsRequestError as s, AccountsAuthConfig as t, AccountsSessionStore as u, isAccessDenied as v };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aui-auth",
3
- "version": "0.0.4",
3
+ "version": "0.0.5",
4
4
  "description": "Server side sessions for web apps that sign in through assistant-ui accounts: OIDC code flow with PKCE, tokens kept in the app's database, single flight refresh",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1 +0,0 @@
1
- import{t as e}from"./codec-BcgaGRoP.js";function t(e){if(!e)return;let t=e.split(`,`);return t[t.length-1].trim()||void 0}function n(e){let n=new URL(e.url);return`${t(e.headers.get(`x-forwarded-proto`))||n.protocol.replace(`:`,``)}://${t(e.headers.get(`x-forwarded-host`))||e.headers.get(`host`)||n.host}`}function r(e){return new URL(e).protocol===`https:`}function i(e,t){return t?`__Secure-${e}`:e}function a(e){let t=new Map;if(!e)return t;for(let n of e.split(`;`)){let e=n.indexOf(`=`);if(e===-1)continue;let r=n.slice(0,e).trim();!r||t.has(r)||t.set(r,n.slice(e+1).trim())}return t}function o(e,t){let n=a(e.headers.get(`cookie`));return n.get(i(t,!0))||n.get(i(t,!1))||null}function s(e,t,n,r){let i=[`${e}=${t}`,`Path=/`,`HttpOnly`,`SameSite=Lax`,`Max-Age=${r}`];return n&&i.push(`Secure`),i.join(`; `)}function c(e,t,n,a,o){let c=r(a);e.append(`set-cookie`,s(i(t,c),n,c,Math.floor(o/1e3)))}function l(e,t){let n=r(t);return[n,!n].map(t=>s(i(e,t),``,t,0))}function u(e,t,n){for(let r of l(t,n))e.append(`set-cookie`,r)}function d(e,t=307){return new Response(null,{status:t,headers:{location:e.toString()}})}function f(e,t){return`${e}_flow_${t}`}function p(e){return btoa(JSON.stringify(e)).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/g,``)}function m(e){if(!e)return null;try{let t=e.replace(/-/g,`+`).replace(/_/g,`/`),n=JSON.parse(atob(t));return typeof n.verifier==`string`?{verifier:n.verifier,redirectPath:typeof n.redirectPath==`string`?n.redirectPath:`/`,silent:n.silent===!0}:null}catch{return null}}function h(e,t){if(!e)return`/`;let n;try{n=new URL(e,t)}catch{return`/`}return n.origin===t?`${n.pathname}${n.search}${n.hash}`:`/`}const g={login:`/api/auth/login`,callback:`/api/auth/callback`,logout:`/api/auth/logout`,logoutComplete:`/api/auth/logout-complete`,error:`/auth/error`},_={absoluteMaxAgeMs:2592e6,idleMaxAgeMs:6048e5,revalidateIntervalMs:12e4,refreshBeforeExpiryMs:3e5,flowMaxAgeMs:6e5,refreshLeaseMs:2e4,leaseWaitMs:5e3},v=Object.freeze({});function y(e){return{...e,scope:e.scope??`openid profile email offline_access`,onSignIn:e.onSignIn??(async()=>v),onRevalidate:e.onRevalidate??(async()=>v),paths:{...g,...e.paths},lifetimes:{..._,...e.lifetimes},fetch:e.fetch??globalThis.fetch.bind(globalThis),now:e.now??Date.now,cache:e.cache?{codec:F(e.cache.secret),maxAgeMs:e.cache.maxAgeMs??6e4}:null,audience:e.audience===!1?null:e.audience??new URL(`/api/auth`,e.issuer).toString()}}var b=class extends Error{status;constructor(e,t){super(e),this.name=`AccountsRequestError`,this.status=t}},x=class extends b{code;description;constructor(e,t,n){super(`Token request failed: ${e} ${t} ${n}`,e),this.name=`OAuthTokenError`,this.code=t,this.description=n}};function S(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/g,``)}function C(e){return S(crypto.getRandomValues(new Uint8Array(e)))}async function w(e){let t=await crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(e));return S(new Uint8Array(t))}function T(e,t){return new URL(t,e).toString()}async function E(e,t){let n=C(24),r=C(32),i=new URL(T(e.issuer,`/api/auth/oauth2/authorize`));return i.searchParams.set(`client_id`,e.clientId),i.searchParams.set(`redirect_uri`,new URL(e.paths.callback,t.origin).toString()),i.searchParams.set(`response_type`,`code`),i.searchParams.set(`scope`,e.scope),i.searchParams.set(`state`,n),i.searchParams.set(`code_challenge`,await w(r)),i.searchParams.set(`code_challenge_method`,`S256`),t.silent&&i.searchParams.set(`prompt`,`none`),{url:i.toString(),state:n,verifier:r}}function D(e,t){let n=new URL(T(e.issuer,`/api/auth/oauth2/end-session`));return n.searchParams.set(`id_token_hint`,t.idToken),n.searchParams.set(`client_id`,e.clientId),n.searchParams.set(`post_logout_redirect_uri`,new URL(e.paths.logoutComplete,t.origin).toString()),n.toString()}async function O(e,t,n,r=!0){let i=new URLSearchParams({client_id:e.clientId,...t});e.clientSecret&&i.set(`client_secret`,e.clientSecret),e.audience&&r&&i.set(`resource`,e.audience);let a=n===void 0?void 0:n-Date.now();if(a!==void 0&&a<=0)throw new b(`Token request deadline passed`,0);let o=await e.fetch(T(e.issuer,`/api/auth/oauth2/token`),{method:`POST`,headers:{"Content-Type":`application/x-www-form-urlencoded`},body:i,cache:`no-store`,...a?{signal:AbortSignal.timeout(a)}:{}});if(!o.ok){let i=await o.text().catch(()=>``),a=`unknown_error`,s=i;try{let e=JSON.parse(i);a=e.error??a,s=e.error_description??s}catch{}if(r&&e.audience&&a===`invalid_request`&&/resource/i.test(s))return O(e,t,n,!1);throw new x(o.status,a,s)}return await o.json()}function k(e,t){return O(e,{grant_type:`authorization_code`,code:t.code,code_verifier:t.verifier,redirect_uri:t.redirectUri})}function A(e,t,n){return O(e,{grant_type:`refresh_token`,refresh_token:t},n===void 0?void 0:Date.now()+n)}async function j(e,t){let n=await(e.fetch??globalThis.fetch.bind(globalThis))(T(e.issuer,`/api/auth/oauth2/userinfo`),{headers:{Authorization:`Bearer ${t}`},cache:`no-store`});if(!n.ok)throw new b(`Failed to load user info: ${n.status}`,n.status);let r=await n.json();return{id:r.sub,email:r.email??``,name:r.name??r.email??r.sub,image:r.picture??null}}function M(e,t,n){return{accessToken:e.access_token,refreshToken:e.refresh_token??t?.refreshToken??null,idToken:e.id_token??t?.idToken??null,accessTokenExpiresAt:typeof e.expires_at==`number`?e.expires_at*1e3:n+(e.expires_in??3600)*1e3}}function N(e){return`${e}_cache`}function P(e){let t=``;for(let n of e)t+=n.toString(16).padStart(2,`0`);return t}function F(t){let n=null;return()=>n??=crypto.subtle.digest(`SHA-256`,new TextEncoder().encode(t)).then(t=>e(P(new Uint8Array(t))))}function I(e,t,n){if(!e.cache)return 0;let{lifetimes:r}=e;return Math.min(e.cache.maxAgeMs,t.lastValidatedAt+r.revalidateIntervalMs-n,t.tokens.accessTokenExpiresAt-r.refreshBeforeExpiryMs-n,t.createdAt+r.absoluteMaxAgeMs-n,t.lastValidatedAt+r.idleMaxAgeMs-n)}async function L(e,t,n){if(!e.cache)return null;let r=I(e,t,n);if(r<1e3)return null;let i={v:1,id:t.id,u:t.user,d:t.data,at:t.tokens.accessToken,ate:t.tokens.accessTokenExpiresAt,ca:t.createdAt,lv:t.lastValidatedAt,exp:n+r};return{value:await(await e.cache.codec()).encrypt(JSON.stringify(i)),maxAgeMs:r}}async function R(e,t,n,r){if(!e.cache||!t||!n)return null;let i;try{let n=await e.cache.codec();i=JSON.parse(await n.decrypt(t))}catch{return null}return i.v!==1||typeof i.exp!=`number`||i.exp<=r||i.id!==await w(n)?null:{id:i.id,user:i.u,tokens:{accessToken:i.at,refreshToken:null,idToken:null,accessTokenExpiresAt:i.ate},data:i.d,createdAt:i.ca,lastValidatedAt:i.lv,refreshLeaseUntil:null}}async function z(e,t,n,a){let o=await L(e,t,a);if(!o)return null;let c=r(n);return s(i(N(e.cookieName),c),o.value,c,Math.floor(o.maxAgeMs/1e3))}function B(e,t){return e.cache?l(N(e.cookieName),t):[]}export{o as C,c as D,h as E,f as S,n as T,u as _,T as a,m as b,k as c,A as d,w as f,y as g,x as h,R as i,j as l,b as m,N as n,E as o,M as p,z as r,D as s,B as t,C as u,l as v,d as w,p as x,i as y};
@@ -1 +0,0 @@
1
- function e(e){let t=new Uint8Array(e.length/2);for(let n=0;n<e.length;n+=2)t[n/2]=Number.parseInt(e.slice(n,n+2),16);return t}function t(e){let t=``;for(let n of e)t+=String.fromCharCode(n);return btoa(t).replace(/\+/g,`-`).replace(/\//g,`_`).replace(/=+$/g,``)}function n(e){let t=e.replace(/-/g,`+`).replace(/_/g,`/`),n=t+`=`.repeat((4-t.length%4)%4),r;try{r=atob(n)}catch{throw Error(`Malformed encrypted value`)}let i=new Uint8Array(r.length);for(let e=0;e<r.length;e++)i[e]=r.charCodeAt(e);return i}function r(r){if(!/^[0-9a-fA-F]{64}$/.test(r))throw Error(`AES-GCM codec key must be 64 hex characters (32 bytes)`);let i=e(r),a=null,o=()=>a??=crypto.subtle.importKey(`raw`,i,{name:`AES-GCM`},!1,[`encrypt`,`decrypt`]);return{async encrypt(e){let n=crypto.getRandomValues(new Uint8Array(12)),r=new Uint8Array(await crypto.subtle.encrypt({name:`AES-GCM`,iv:n},await o(),new TextEncoder().encode(e))),i=new Uint8Array(12+r.length);return i.set(n),i.set(r,12),t(i)},async decrypt(e){let t=n(e);if(t.length<28)throw Error(`Malformed encrypted value`);let r=await crypto.subtle.decrypt({name:`AES-GCM`,iv:t.subarray(0,12)},await o(),t.subarray(12));return new TextDecoder().decode(r)}}}export{r as t};