@aooth/auth 0.1.7 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/atscript-db.cjs +36 -25
- package/dist/atscript-db.d.cts +28 -22
- package/dist/atscript-db.d.mts +28 -22
- package/dist/atscript-db.mjs +36 -25
- package/dist/authz.cjs +168 -0
- package/dist/authz.d.cts +252 -0
- package/dist/authz.d.mts +252 -0
- package/dist/authz.mjs +161 -0
- package/dist/client.cjs +59 -0
- package/dist/client.d.cts +64 -0
- package/dist/client.d.mts +64 -0
- package/dist/client.mjs +58 -0
- package/dist/clock-Bdsep_1j.mjs +4 -0
- package/dist/clock-BjXa0LXb.d.cts +14 -0
- package/dist/clock-BjXa0LXb.d.mts +14 -0
- package/dist/clock-Bl-H3eqE.cjs +9 -0
- package/dist/index.cjs +294 -65
- package/dist/index.d.cts +166 -44
- package/dist/index.d.mts +166 -44
- package/dist/index.mjs +289 -60
- package/dist/payload-BJjvj8AH.cjs +32 -0
- package/dist/payload-D-DzH5-J.mjs +27 -0
- package/dist/redis.cjs +9 -0
- package/dist/redis.d.cts +8 -7
- package/dist/redis.d.mts +8 -7
- package/dist/redis.mjs +9 -0
- package/dist/store-BG6m6oSJ.d.cts +263 -0
- package/dist/store-BG6m6oSJ.d.mts +263 -0
- package/package.json +28 -10
- package/src/atscript-db/auth-credential.as +16 -10
- package/src/atscript-db/auth-credential.as.d.ts +68 -0
- package/dist/store-B1t8KkfA.d.mts +0 -124
- package/dist/store-untAtWQz.d.cts +0 -124
package/dist/client.cjs
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/client/index.ts
|
|
3
|
+
/**
|
|
4
|
+
* Wraps `fetch` with cookie-session silent refresh. Every call forwards
|
|
5
|
+
* credentials; a response whose status is in `refreshOn` (401 by default)
|
|
6
|
+
* triggers a **single-flight** refresh — N concurrent failing requests share
|
|
7
|
+
* exactly one `POST {refreshPath}` — and, on success, the original request is
|
|
8
|
+
* retried **once**. A failed refresh fires `onLogout()` once and returns the
|
|
9
|
+
* original failing response (no retry, no refresh storm).
|
|
10
|
+
*
|
|
11
|
+
* Browser-safe: pairs with the bundled `@aooth/auth-moost` httpOnly-cookie
|
|
12
|
+
* transport, so the SPA never touches token material. The retried request reuses
|
|
13
|
+
* the original `init`; passing a one-shot body (a consumed `ReadableStream`) is
|
|
14
|
+
* not retry-safe — prefer string / `FormData` / `Blob` bodies.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* import { createAuthedFetch } from '@aooth/auth/client'
|
|
19
|
+
* const api = createAuthedFetch({ refreshPath: '/auth/refresh', onLogout: () => location.assign('/login') })
|
|
20
|
+
* const res = await api('/api/me') // 401 → silent refresh → retried once
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
function createAuthedFetch(options = {}) {
|
|
24
|
+
const refreshPath = options.refreshPath ?? "/auth/refresh";
|
|
25
|
+
const onLogout = options.onLogout;
|
|
26
|
+
const refreshOn = options.refreshOn ?? [401];
|
|
27
|
+
const baseFetch = options.fetch ?? globalThis.fetch;
|
|
28
|
+
if (typeof baseFetch !== "function") throw new Error("createAuthedFetch: no fetch implementation available — pass options.fetch");
|
|
29
|
+
let refreshInFlight = null;
|
|
30
|
+
const runRefresh = () => {
|
|
31
|
+
if (refreshInFlight) return refreshInFlight;
|
|
32
|
+
refreshInFlight = (async () => {
|
|
33
|
+
try {
|
|
34
|
+
if ((await baseFetch(refreshPath, {
|
|
35
|
+
method: "POST",
|
|
36
|
+
credentials: "include"
|
|
37
|
+
})).ok) return true;
|
|
38
|
+
} catch {}
|
|
39
|
+
onLogout?.();
|
|
40
|
+
return false;
|
|
41
|
+
})().finally(() => {
|
|
42
|
+
refreshInFlight = null;
|
|
43
|
+
});
|
|
44
|
+
return refreshInFlight;
|
|
45
|
+
};
|
|
46
|
+
const authedFetch = async (input, init) => {
|
|
47
|
+
const withCreds = {
|
|
48
|
+
credentials: "include",
|
|
49
|
+
...init
|
|
50
|
+
};
|
|
51
|
+
const res = await baseFetch(input, withCreds);
|
|
52
|
+
if (!refreshOn.includes(res.status)) return res;
|
|
53
|
+
if (!await runRefresh()) return res;
|
|
54
|
+
return baseFetch(input, withCreds);
|
|
55
|
+
};
|
|
56
|
+
return authedFetch;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
exports.createAuthedFetch = createAuthedFetch;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/client/index.d.ts
|
|
2
|
+
/** Minimal structural view of a fetch `Response` this helper reads. The DOM `Response` satisfies it. */
|
|
3
|
+
interface MinimalResponse {
|
|
4
|
+
readonly ok: boolean;
|
|
5
|
+
readonly status: number;
|
|
6
|
+
}
|
|
7
|
+
/** Subset of `RequestInit` this helper sets or forwards. The DOM `RequestInit` satisfies it. */
|
|
8
|
+
interface MinimalRequestInit {
|
|
9
|
+
method?: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
body?: unknown;
|
|
12
|
+
credentials?: "omit" | "same-origin" | "include";
|
|
13
|
+
signal?: unknown;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
/** A `fetch`-compatible function. The global `fetch` satisfies this. */
|
|
17
|
+
type FetchFn = (input: string | URL, init?: MinimalRequestInit) => Promise<MinimalResponse>;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves to the ambient `fetch` type when the consumer's tsconfig includes the
|
|
20
|
+
* DOM lib — so the returned wrapper yields a real `Response` with `.json()` etc.
|
|
21
|
+
* — and falls back to the structural {@link FetchFn} otherwise. This keeps
|
|
22
|
+
* `@aooth/auth/client` free of a hard DOM-lib dependency while preserving great
|
|
23
|
+
* DX in browser projects.
|
|
24
|
+
*/
|
|
25
|
+
type DefaultFetch = typeof globalThis extends {
|
|
26
|
+
fetch: infer F extends FetchFn;
|
|
27
|
+
} ? F : FetchFn;
|
|
28
|
+
interface CreateAuthedFetchOptions<TFetch extends FetchFn = DefaultFetch> {
|
|
29
|
+
/** Refresh endpoint path or URL. Default `'/auth/refresh'`. Match your `AuthController` mount. */
|
|
30
|
+
refreshPath?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Called exactly once per failed refresh attempt (non-OK response or network
|
|
33
|
+
* error). Use it to redirect to login / clear app state. Never called on a
|
|
34
|
+
* successful refresh.
|
|
35
|
+
*/
|
|
36
|
+
onLogout?: () => void;
|
|
37
|
+
/** Underlying fetch implementation. Default: the global `fetch`. */
|
|
38
|
+
fetch?: TFetch;
|
|
39
|
+
/** Response status codes that trigger a refresh + single retry. Default `[401]`. */
|
|
40
|
+
refreshOn?: number[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Wraps `fetch` with cookie-session silent refresh. Every call forwards
|
|
44
|
+
* credentials; a response whose status is in `refreshOn` (401 by default)
|
|
45
|
+
* triggers a **single-flight** refresh — N concurrent failing requests share
|
|
46
|
+
* exactly one `POST {refreshPath}` — and, on success, the original request is
|
|
47
|
+
* retried **once**. A failed refresh fires `onLogout()` once and returns the
|
|
48
|
+
* original failing response (no retry, no refresh storm).
|
|
49
|
+
*
|
|
50
|
+
* Browser-safe: pairs with the bundled `@aooth/auth-moost` httpOnly-cookie
|
|
51
|
+
* transport, so the SPA never touches token material. The retried request reuses
|
|
52
|
+
* the original `init`; passing a one-shot body (a consumed `ReadableStream`) is
|
|
53
|
+
* not retry-safe — prefer string / `FormData` / `Blob` bodies.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { createAuthedFetch } from '@aooth/auth/client'
|
|
58
|
+
* const api = createAuthedFetch({ refreshPath: '/auth/refresh', onLogout: () => location.assign('/login') })
|
|
59
|
+
* const res = await api('/api/me') // 401 → silent refresh → retried once
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function createAuthedFetch<TFetch extends FetchFn = DefaultFetch>(options?: CreateAuthedFetchOptions<TFetch>): TFetch;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { CreateAuthedFetchOptions, DefaultFetch, FetchFn, MinimalRequestInit, MinimalResponse, createAuthedFetch };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
//#region src/client/index.d.ts
|
|
2
|
+
/** Minimal structural view of a fetch `Response` this helper reads. The DOM `Response` satisfies it. */
|
|
3
|
+
interface MinimalResponse {
|
|
4
|
+
readonly ok: boolean;
|
|
5
|
+
readonly status: number;
|
|
6
|
+
}
|
|
7
|
+
/** Subset of `RequestInit` this helper sets or forwards. The DOM `RequestInit` satisfies it. */
|
|
8
|
+
interface MinimalRequestInit {
|
|
9
|
+
method?: string;
|
|
10
|
+
headers?: Record<string, string>;
|
|
11
|
+
body?: unknown;
|
|
12
|
+
credentials?: "omit" | "same-origin" | "include";
|
|
13
|
+
signal?: unknown;
|
|
14
|
+
[key: string]: unknown;
|
|
15
|
+
}
|
|
16
|
+
/** A `fetch`-compatible function. The global `fetch` satisfies this. */
|
|
17
|
+
type FetchFn = (input: string | URL, init?: MinimalRequestInit) => Promise<MinimalResponse>;
|
|
18
|
+
/**
|
|
19
|
+
* Resolves to the ambient `fetch` type when the consumer's tsconfig includes the
|
|
20
|
+
* DOM lib — so the returned wrapper yields a real `Response` with `.json()` etc.
|
|
21
|
+
* — and falls back to the structural {@link FetchFn} otherwise. This keeps
|
|
22
|
+
* `@aooth/auth/client` free of a hard DOM-lib dependency while preserving great
|
|
23
|
+
* DX in browser projects.
|
|
24
|
+
*/
|
|
25
|
+
type DefaultFetch = typeof globalThis extends {
|
|
26
|
+
fetch: infer F extends FetchFn;
|
|
27
|
+
} ? F : FetchFn;
|
|
28
|
+
interface CreateAuthedFetchOptions<TFetch extends FetchFn = DefaultFetch> {
|
|
29
|
+
/** Refresh endpoint path or URL. Default `'/auth/refresh'`. Match your `AuthController` mount. */
|
|
30
|
+
refreshPath?: string;
|
|
31
|
+
/**
|
|
32
|
+
* Called exactly once per failed refresh attempt (non-OK response or network
|
|
33
|
+
* error). Use it to redirect to login / clear app state. Never called on a
|
|
34
|
+
* successful refresh.
|
|
35
|
+
*/
|
|
36
|
+
onLogout?: () => void;
|
|
37
|
+
/** Underlying fetch implementation. Default: the global `fetch`. */
|
|
38
|
+
fetch?: TFetch;
|
|
39
|
+
/** Response status codes that trigger a refresh + single retry. Default `[401]`. */
|
|
40
|
+
refreshOn?: number[];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Wraps `fetch` with cookie-session silent refresh. Every call forwards
|
|
44
|
+
* credentials; a response whose status is in `refreshOn` (401 by default)
|
|
45
|
+
* triggers a **single-flight** refresh — N concurrent failing requests share
|
|
46
|
+
* exactly one `POST {refreshPath}` — and, on success, the original request is
|
|
47
|
+
* retried **once**. A failed refresh fires `onLogout()` once and returns the
|
|
48
|
+
* original failing response (no retry, no refresh storm).
|
|
49
|
+
*
|
|
50
|
+
* Browser-safe: pairs with the bundled `@aooth/auth-moost` httpOnly-cookie
|
|
51
|
+
* transport, so the SPA never touches token material. The retried request reuses
|
|
52
|
+
* the original `init`; passing a one-shot body (a consumed `ReadableStream`) is
|
|
53
|
+
* not retry-safe — prefer string / `FormData` / `Blob` bodies.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* import { createAuthedFetch } from '@aooth/auth/client'
|
|
58
|
+
* const api = createAuthedFetch({ refreshPath: '/auth/refresh', onLogout: () => location.assign('/login') })
|
|
59
|
+
* const res = await api('/api/me') // 401 → silent refresh → retried once
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function createAuthedFetch<TFetch extends FetchFn = DefaultFetch>(options?: CreateAuthedFetchOptions<TFetch>): TFetch;
|
|
63
|
+
//#endregion
|
|
64
|
+
export { CreateAuthedFetchOptions, DefaultFetch, FetchFn, MinimalRequestInit, MinimalResponse, createAuthedFetch };
|
package/dist/client.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
//#region src/client/index.ts
|
|
2
|
+
/**
|
|
3
|
+
* Wraps `fetch` with cookie-session silent refresh. Every call forwards
|
|
4
|
+
* credentials; a response whose status is in `refreshOn` (401 by default)
|
|
5
|
+
* triggers a **single-flight** refresh — N concurrent failing requests share
|
|
6
|
+
* exactly one `POST {refreshPath}` — and, on success, the original request is
|
|
7
|
+
* retried **once**. A failed refresh fires `onLogout()` once and returns the
|
|
8
|
+
* original failing response (no retry, no refresh storm).
|
|
9
|
+
*
|
|
10
|
+
* Browser-safe: pairs with the bundled `@aooth/auth-moost` httpOnly-cookie
|
|
11
|
+
* transport, so the SPA never touches token material. The retried request reuses
|
|
12
|
+
* the original `init`; passing a one-shot body (a consumed `ReadableStream`) is
|
|
13
|
+
* not retry-safe — prefer string / `FormData` / `Blob` bodies.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```ts
|
|
17
|
+
* import { createAuthedFetch } from '@aooth/auth/client'
|
|
18
|
+
* const api = createAuthedFetch({ refreshPath: '/auth/refresh', onLogout: () => location.assign('/login') })
|
|
19
|
+
* const res = await api('/api/me') // 401 → silent refresh → retried once
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
function createAuthedFetch(options = {}) {
|
|
23
|
+
const refreshPath = options.refreshPath ?? "/auth/refresh";
|
|
24
|
+
const onLogout = options.onLogout;
|
|
25
|
+
const refreshOn = options.refreshOn ?? [401];
|
|
26
|
+
const baseFetch = options.fetch ?? globalThis.fetch;
|
|
27
|
+
if (typeof baseFetch !== "function") throw new Error("createAuthedFetch: no fetch implementation available — pass options.fetch");
|
|
28
|
+
let refreshInFlight = null;
|
|
29
|
+
const runRefresh = () => {
|
|
30
|
+
if (refreshInFlight) return refreshInFlight;
|
|
31
|
+
refreshInFlight = (async () => {
|
|
32
|
+
try {
|
|
33
|
+
if ((await baseFetch(refreshPath, {
|
|
34
|
+
method: "POST",
|
|
35
|
+
credentials: "include"
|
|
36
|
+
})).ok) return true;
|
|
37
|
+
} catch {}
|
|
38
|
+
onLogout?.();
|
|
39
|
+
return false;
|
|
40
|
+
})().finally(() => {
|
|
41
|
+
refreshInFlight = null;
|
|
42
|
+
});
|
|
43
|
+
return refreshInFlight;
|
|
44
|
+
};
|
|
45
|
+
const authedFetch = async (input, init) => {
|
|
46
|
+
const withCreds = {
|
|
47
|
+
credentials: "include",
|
|
48
|
+
...init
|
|
49
|
+
};
|
|
50
|
+
const res = await baseFetch(input, withCreds);
|
|
51
|
+
if (!refreshOn.includes(res.status)) return res;
|
|
52
|
+
if (!await runRefresh()) return res;
|
|
53
|
+
return baseFetch(input, withCreds);
|
|
54
|
+
};
|
|
55
|
+
return authedFetch;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { createAuthedFetch };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/utils/clock.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Minimal time abstraction shared across stores and orchestrators.
|
|
4
|
+
*
|
|
5
|
+
* Defaults to wall-clock; tests inject a fake clock to deterministically
|
|
6
|
+
* advance time. Kept tiny (single `now()` method) so any timing primitive
|
|
7
|
+
* — Date, performance.now-ish, monotonic, etc. — can be plugged in.
|
|
8
|
+
*/
|
|
9
|
+
interface Clock {
|
|
10
|
+
now(): number;
|
|
11
|
+
}
|
|
12
|
+
declare const defaultClock: Clock;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { defaultClock as n, Clock as t };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/utils/clock.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Minimal time abstraction shared across stores and orchestrators.
|
|
4
|
+
*
|
|
5
|
+
* Defaults to wall-clock; tests inject a fake clock to deterministically
|
|
6
|
+
* advance time. Kept tiny (single `now()` method) so any timing primitive
|
|
7
|
+
* — Date, performance.now-ish, monotonic, etc. — can be plugged in.
|
|
8
|
+
*/
|
|
9
|
+
interface Clock {
|
|
10
|
+
now(): number;
|
|
11
|
+
}
|
|
12
|
+
declare const defaultClock: Clock;
|
|
13
|
+
//#endregion
|
|
14
|
+
export { defaultClock as n, Clock as t };
|