@konneal/engine 0.2.22 → 0.2.23
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/ask-U3DLB37U.js +12 -0
- package/dist/{chunk-RESCBSX6.js → chunk-434NRPSS.js} +1 -1
- package/dist/{chunk-BV7IKH3N.js → chunk-FYGJDJPN.js} +2 -2
- package/dist/{chunk-M24JWPVN.js → chunk-GZZWHWGM.js} +6 -6
- package/dist/chunk-HXWDVAVN.js +24441 -0
- package/dist/{chunk-YTWPZE5O.js → chunk-HYI32HMI.js} +0 -32
- package/dist/{chunk-COKARI7O.js → chunk-IOZAZA7Y.js} +2 -2
- package/dist/{chunk-IROEVHEV.js → chunk-PZLYPUYJ.js} +4 -4
- package/dist/{chunk-FNJ457SE.js → chunk-RZ3UZVRR.js} +1 -1
- package/dist/livedata.d.ts +3 -4
- package/dist/oidc-refresh.d.ts +19 -0
- package/dist/oidc.d.ts +1 -0
- package/dist/refresh-core.d.ts +28 -0
- package/dist/search-OJ776DMH.js +12 -0
- package/dist/worker_mcp/src/index.js +3 -3
- package/dist/worker_public/src/config.js +2 -2
- package/dist/worker_public/src/index.js +9 -9
- package/dist/worker_public/src/profile.js +1 -1
- package/dist/worker_public/src/refusal.js +2 -2
- package/dist/worker_public/src/requestScope.js +3 -3
- package/package.json +2 -3
- package/workers/worker_public/src/auth.ts +45 -10
- package/workers/worker_public/src/codecs.ts +33 -32
- package/workers/worker_public/src/livedata.ts +35 -0
- package/workers/worker_public/src/oidc-refresh.ts +76 -0
- package/workers/worker_public/src/oidc.ts +1 -0
- package/workers/worker_public/src/refresh-core.ts +67 -0
- package/dist/ask-HLELROYT.js +0 -12
- package/dist/chunk-4XBU4J6B.js +0 -4149
- package/dist/search-GVDELP3N.js +0 -12
|
@@ -1,33 +1,3 @@
|
|
|
1
|
-
var __create = Object.create;
|
|
2
|
-
var __defProp = Object.defineProperty;
|
|
3
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
-
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
-
try {
|
|
9
|
-
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
-
} catch (e) {
|
|
11
|
-
throw mod = 0, e;
|
|
12
|
-
}
|
|
13
|
-
};
|
|
14
|
-
var __copyProps = (to, from, except, desc) => {
|
|
15
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
-
for (let key of __getOwnPropNames(from))
|
|
17
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
-
}
|
|
20
|
-
return to;
|
|
21
|
-
};
|
|
22
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
-
mod
|
|
29
|
-
));
|
|
30
|
-
|
|
31
1
|
// workers/worker_public/src/profile.gen.ts
|
|
32
2
|
var PROFILE = {
|
|
33
3
|
"publisher": {
|
|
@@ -176,8 +146,6 @@ function P() {
|
|
|
176
146
|
}
|
|
177
147
|
|
|
178
148
|
export {
|
|
179
|
-
__commonJS,
|
|
180
|
-
__toESM,
|
|
181
149
|
setProfile,
|
|
182
150
|
P
|
|
183
151
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
DATASETS,
|
|
3
3
|
datasetAllowed
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-434NRPSS.js";
|
|
5
5
|
import {
|
|
6
6
|
P
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-HYI32HMI.js";
|
|
8
8
|
|
|
9
9
|
// workers/worker_public/src/requestScope.ts
|
|
10
10
|
function licenseDeclared() {
|
|
@@ -7,24 +7,24 @@ import {
|
|
|
7
7
|
sessionFrom,
|
|
8
8
|
telemetry,
|
|
9
9
|
understandQuery
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HXWDVAVN.js";
|
|
11
11
|
import {
|
|
12
12
|
corsHeaders,
|
|
13
13
|
err,
|
|
14
14
|
json,
|
|
15
15
|
readJson,
|
|
16
16
|
validateQuery
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-FYGJDJPN.js";
|
|
18
18
|
import {
|
|
19
19
|
entitlementScope,
|
|
20
20
|
standardKeysFrom
|
|
21
|
-
} from "./chunk-
|
|
21
|
+
} from "./chunk-IOZAZA7Y.js";
|
|
22
22
|
import {
|
|
23
23
|
LIMITS,
|
|
24
24
|
MODELS,
|
|
25
25
|
num,
|
|
26
26
|
sha256Hex
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-434NRPSS.js";
|
|
28
28
|
|
|
29
29
|
// workers/worker_public/src/search.ts
|
|
30
30
|
async function handleSearch(env, ctx, req, tier, key) {
|
package/dist/livedata.d.ts
CHANGED
|
@@ -12,10 +12,9 @@ export interface LiveDataConfig {
|
|
|
12
12
|
clientSecret?: string;
|
|
13
13
|
}
|
|
14
14
|
export declare function liveDataConfig(env: any): LiveDataConfig | null;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
* the window. */
|
|
15
|
+
export declare function retainRefreshToken(env: any, sessionRaw: string, refreshToken: string): Promise<void>;
|
|
16
|
+
export declare function readRefreshToken(env: any, sessionRaw: string): Promise<string | null>;
|
|
17
|
+
export declare function dropRefreshToken(env: any, sessionRaw: string): Promise<void>;
|
|
19
18
|
export declare function retainOpAccessToken(env: any, sessionRaw: string, opAccessToken: string, expiresInSec: number): Promise<void>;
|
|
20
19
|
/** Drop the retained subject + the cached exchanged token (the sign-out
|
|
21
20
|
* closes the window deliberately, never by expiry alone). */
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { SessionClaims } from "./session";
|
|
2
|
+
export interface RenewedSession {
|
|
3
|
+
claims: Omit<SessionClaims, "iat" | "exp">;
|
|
4
|
+
refreshToken: string;
|
|
5
|
+
}
|
|
6
|
+
export type RenewOutcome = {
|
|
7
|
+
kind: "ok";
|
|
8
|
+
renewed: RenewedSession;
|
|
9
|
+
} | {
|
|
10
|
+
kind: "revoked";
|
|
11
|
+
} | {
|
|
12
|
+
kind: "unavailable";
|
|
13
|
+
};
|
|
14
|
+
export declare function renewSessionClaims(env: any, cfg: {
|
|
15
|
+
issuer: string;
|
|
16
|
+
clientId: string;
|
|
17
|
+
clientSecret?: string;
|
|
18
|
+
sessionSecret: string;
|
|
19
|
+
}, session: SessionClaims, sessionRaw: string): Promise<RenewOutcome>;
|
package/dist/oidc.d.ts
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { OidcIdTokenClaims, OidcTokenResponse } from "./oidc.ts";
|
|
2
|
+
import type { SessionClaims } from "./session";
|
|
3
|
+
export interface OidcMetadata {
|
|
4
|
+
token_endpoint: string;
|
|
5
|
+
jwks_uri: string;
|
|
6
|
+
}
|
|
7
|
+
export type RefreshResult = {
|
|
8
|
+
kind: "ok";
|
|
9
|
+
token: OidcTokenResponse;
|
|
10
|
+
} | {
|
|
11
|
+
kind: "refused";
|
|
12
|
+
} | {
|
|
13
|
+
kind: "unavailable";
|
|
14
|
+
};
|
|
15
|
+
/** The refresh grant's backchannel: the same shape as the code
|
|
16
|
+
* exchange's POST (the OP's WAF wants the issuer's own origin), with
|
|
17
|
+
* the three outcomes the renewal policy needs — a 400 is the OP
|
|
18
|
+
* REFUSING (revocation, expiry: dead), a network throw is
|
|
19
|
+
* unavailability (keep the session, retry next load). */
|
|
20
|
+
export declare function refreshGrant(meta: OidcMetadata, cfg: {
|
|
21
|
+
clientId: string;
|
|
22
|
+
clientSecret?: string;
|
|
23
|
+
}, refreshToken: string): Promise<RefreshResult>;
|
|
24
|
+
/** The fresh session claims from the refresh grant's ID token. The sub
|
|
25
|
+
* MUST match (a different account is a provisioning error, never an
|
|
26
|
+
* update); every profile field falls back to the standing session's so
|
|
27
|
+
* a claim the OP omits never erases what the user already had. */
|
|
28
|
+
export declare function renewedSessionClaims(session: SessionClaims, claims: OidcIdTokenClaims): Omit<SessionClaims, "iat" | "exp"> | null;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import {
|
|
2
|
+
handleSearch
|
|
3
|
+
} from "./chunk-PZLYPUYJ.js";
|
|
4
|
+
import "./chunk-HXWDVAVN.js";
|
|
5
|
+
import "./chunk-FYGJDJPN.js";
|
|
6
|
+
import "./chunk-RZ3UZVRR.js";
|
|
7
|
+
import "./chunk-IOZAZA7Y.js";
|
|
8
|
+
import "./chunk-434NRPSS.js";
|
|
9
|
+
import "./chunk-HYI32HMI.js";
|
|
10
|
+
export {
|
|
11
|
+
handleSearch
|
|
12
|
+
};
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
authenticate
|
|
3
|
-
} from "../../chunk-
|
|
4
|
-
import "../../chunk-
|
|
3
|
+
} from "../../chunk-FYGJDJPN.js";
|
|
4
|
+
import "../../chunk-434NRPSS.js";
|
|
5
5
|
import {
|
|
6
6
|
P,
|
|
7
7
|
setProfile
|
|
8
|
-
} from "../../chunk-
|
|
8
|
+
} from "../../chunk-HYI32HMI.js";
|
|
9
9
|
|
|
10
10
|
// workers/worker_mcp/src/index.ts
|
|
11
11
|
var PROTOCOL_VERSION = "2025-06-18";
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
handleSearch
|
|
3
|
-
} from "../../chunk-
|
|
3
|
+
} from "../../chunk-PZLYPUYJ.js";
|
|
4
4
|
import {
|
|
5
5
|
checkQuoteAnchors,
|
|
6
6
|
handleAsk,
|
|
7
7
|
handleMemories,
|
|
8
8
|
scoreJudge,
|
|
9
9
|
standardForDocNumber
|
|
10
|
-
} from "../../chunk-
|
|
10
|
+
} from "../../chunk-GZZWHWGM.js";
|
|
11
11
|
import {
|
|
12
12
|
buildMessages,
|
|
13
13
|
citations,
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
sessionFrom,
|
|
30
30
|
telemetry,
|
|
31
31
|
understandQuery
|
|
32
|
-
} from "../../chunk-
|
|
32
|
+
} from "../../chunk-HXWDVAVN.js";
|
|
33
33
|
import {
|
|
34
34
|
authenticate,
|
|
35
35
|
corsHeaders,
|
|
@@ -38,14 +38,14 @@ import {
|
|
|
38
38
|
readJson,
|
|
39
39
|
validateQuery,
|
|
40
40
|
withCors
|
|
41
|
-
} from "../../chunk-
|
|
41
|
+
} from "../../chunk-FYGJDJPN.js";
|
|
42
42
|
import {
|
|
43
43
|
canonicalRefusal
|
|
44
|
-
} from "../../chunk-
|
|
44
|
+
} from "../../chunk-RZ3UZVRR.js";
|
|
45
45
|
import {
|
|
46
46
|
entitlementScope,
|
|
47
47
|
standardKeysFrom
|
|
48
|
-
} from "../../chunk-
|
|
48
|
+
} from "../../chunk-IOZAZA7Y.js";
|
|
49
49
|
import {
|
|
50
50
|
LIMITS,
|
|
51
51
|
MODELS,
|
|
@@ -56,11 +56,11 @@ import {
|
|
|
56
56
|
roleModel,
|
|
57
57
|
sha256Hex,
|
|
58
58
|
today
|
|
59
|
-
} from "../../chunk-
|
|
59
|
+
} from "../../chunk-434NRPSS.js";
|
|
60
60
|
import {
|
|
61
61
|
P,
|
|
62
62
|
setProfile
|
|
63
|
-
} from "../../chunk-
|
|
63
|
+
} from "../../chunk-HYI32HMI.js";
|
|
64
64
|
|
|
65
65
|
// workers/worker_public/src/conversations.ts
|
|
66
66
|
var ID_RE = /^[a-zA-Z0-9_-]{8,64}$/;
|
|
@@ -979,7 +979,7 @@ async function handleMcp(env, ctx, req, tier, key) {
|
|
|
979
979
|
// stream:false forces the JSON lane (anon defaults to SSE)
|
|
980
980
|
body: JSON.stringify({ ...args, stream: false })
|
|
981
981
|
});
|
|
982
|
-
const res = name === "ask" ? await (await import("../../ask-
|
|
982
|
+
const res = name === "ask" ? await (await import("../../ask-U3DLB37U.js")).handleAsk(env, ctx, inner, tier, key) : await (await import("../../search-OJ776DMH.js")).handleSearch(env, ctx, inner, tier, key);
|
|
983
983
|
return res.json().catch(() => ({ error: { message: "tool transport failed", status: res.status } }));
|
|
984
984
|
});
|
|
985
985
|
if (out.ok && "accepted" in out) return new Response(null, { status: 202 });
|
|
@@ -4,9 +4,9 @@ import {
|
|
|
4
4
|
requestSalt,
|
|
5
5
|
resolveRequestScope,
|
|
6
6
|
standardKeysFrom
|
|
7
|
-
} from "../../chunk-
|
|
8
|
-
import "../../chunk-
|
|
9
|
-
import "../../chunk-
|
|
7
|
+
} from "../../chunk-IOZAZA7Y.js";
|
|
8
|
+
import "../../chunk-434NRPSS.js";
|
|
9
|
+
import "../../chunk-HYI32HMI.js";
|
|
10
10
|
export {
|
|
11
11
|
entitlementScope,
|
|
12
12
|
licenseDeclared,
|
package/package.json
CHANGED
|
@@ -20,15 +20,14 @@
|
|
|
20
20
|
"@astrojs/mdx": "^4.3.14",
|
|
21
21
|
"@cloudflare/workers-types": "^5.20260911.1",
|
|
22
22
|
"@konneal/client": "^0.2.1",
|
|
23
|
-
"@
|
|
24
|
-
"@pubid/pubid": "^0.1.4",
|
|
23
|
+
"@pubid/pubid": "^0.1.5",
|
|
25
24
|
"esbuild": "^0.28.2",
|
|
26
25
|
"jsdom": "^29.1.1",
|
|
27
26
|
"openapi-typescript": "^7.13.0",
|
|
28
27
|
"playwright": "^1.62.1",
|
|
29
28
|
"yaml": "^2.9.1"
|
|
30
29
|
},
|
|
31
|
-
"version": "0.2.
|
|
30
|
+
"version": "0.2.23",
|
|
32
31
|
"description": "The Konneal engine: the publisher-agnostic build pipeline and API plane for standards intelligence (retrieval, answer contract, verdicts, evaluation).",
|
|
33
32
|
"license": "BSD-3-Clause",
|
|
34
33
|
"type": "module",
|
|
@@ -14,6 +14,8 @@ import {
|
|
|
14
14
|
OidcError,
|
|
15
15
|
} from "./oidc";
|
|
16
16
|
import { clearSessionCookie, mintSessionCookie, mintSessionToken, rawSessionToken, readSession, sessionCookieFromToken, SessionClaims } from "./session";
|
|
17
|
+
import { renewSessionClaims } from "./oidc-refresh";
|
|
18
|
+
import { retainRefreshToken } from "./livedata";
|
|
17
19
|
import { bubbleConfirmPage, isAllowedBubbleOrigin } from "./bubble";
|
|
18
20
|
import { dropOpAccessToken, retainOpAccessToken } from "./livedata";
|
|
19
21
|
|
|
@@ -47,6 +49,9 @@ export interface AuthConfig {
|
|
|
47
49
|
sessionSecret: string;
|
|
48
50
|
}
|
|
49
51
|
|
|
52
|
+
// how long a session serves before renewal re-judges it against the OP
|
|
53
|
+
const RENEW_AFTER_MS = 60 * 60 * 1000;
|
|
54
|
+
|
|
50
55
|
function authConfig(env: any): AuthConfig | null {
|
|
51
56
|
const issuer = (env.OIDC_ISSUER ?? "").trim().replace(/\/$/, "");
|
|
52
57
|
const clientId = env.OIDC_CLIENT_ID;
|
|
@@ -89,7 +94,7 @@ export async function handleLogin(env: any, req: Request): Promise<Response> {
|
|
|
89
94
|
const url = buildAuthorizationUrl(meta, {
|
|
90
95
|
clientId: cfg.clientId,
|
|
91
96
|
redirectUri: cfg.redirectUri,
|
|
92
|
-
scopes: "openid profile email roles",
|
|
97
|
+
scopes: "openid profile email roles offline_access",
|
|
93
98
|
state,
|
|
94
99
|
nonce,
|
|
95
100
|
codeChallenge: pkce.challenge,
|
|
@@ -181,6 +186,11 @@ export async function handleCallback(env: any, req: Request): Promise<Response>
|
|
|
181
186
|
if (typeof token.access_token === "string" && typeof token.expires_in === "number") {
|
|
182
187
|
await retainOpAccessToken(env, session.token, token.access_token, token.expires_in);
|
|
183
188
|
}
|
|
189
|
+
// the session's lifeline (oidc-refresh.ts): the OP's refresh token
|
|
190
|
+
// is what makes renewal a RE-JUDGMENT instead of a re-stamp
|
|
191
|
+
if (typeof token.refresh_token === "string" && token.refresh_token) {
|
|
192
|
+
await retainRefreshToken(env, session.token, token.refresh_token);
|
|
193
|
+
}
|
|
184
194
|
// Bubble bridge: hand the session to the embedded panel as a Bearer
|
|
185
195
|
// token via the confirm page — postMessage to the validated origin
|
|
186
196
|
// ONLY, and only on the user's explicit click (bubble.ts). The
|
|
@@ -211,9 +221,34 @@ export async function handleMe(env: any, req: Request): Promise<Response> {
|
|
|
211
221
|
const cfg = authConfig(env);
|
|
212
222
|
const session = cfg ? await readSession(req, cfg.sessionSecret) : null;
|
|
213
223
|
const headers: Record<string, string> = { "content-type": "application/json" };
|
|
214
|
-
//
|
|
215
|
-
//
|
|
216
|
-
|
|
224
|
+
// Renewal is a RE-JUDGMENT, not a re-stamp (oidc-refresh.ts): a
|
|
225
|
+
// session older than the threshold presents the OP's refresh token,
|
|
226
|
+
// whose grant re-judges the live standing — a deactivated account's
|
|
227
|
+
// grant family is dead, the OP refuses, and the session ENDS here
|
|
228
|
+
// instead of silently serving stale claims for a week. A session with
|
|
229
|
+
// no stored refresh token (pre-offline_access logins) falls back to
|
|
230
|
+
// the legacy re-stamp; a network refusal keeps the session and
|
|
231
|
+
// retries on the next load.
|
|
232
|
+
let effective: SessionClaims | null = session;
|
|
233
|
+
if (session && cfg && Date.now() - session.iat > RENEW_AFTER_MS) {
|
|
234
|
+
const raw = rawSessionToken(req);
|
|
235
|
+
if (raw) {
|
|
236
|
+
const renewed = await renewSessionClaims(env, { issuer: cfg.issuer, clientId: cfg.clientId, clientSecret: env.OIDC_CLIENT_SECRET, sessionSecret: cfg.sessionSecret }, session, raw);
|
|
237
|
+
if (renewed.kind === "ok") {
|
|
238
|
+
const freshClaims = renewed.renewed.claims;
|
|
239
|
+
const minted = await mintSessionToken(cfg.sessionSecret, freshClaims);
|
|
240
|
+
effective = { ...freshClaims, iat: Date.now(), exp: minted.expiresAt };
|
|
241
|
+
headers["set-cookie"] = sessionCookieFromToken(minted.token);
|
|
242
|
+
} else if (renewed.kind === "revoked") {
|
|
243
|
+
effective = null;
|
|
244
|
+
headers["set-cookie"] = clearSessionCookie();
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (effective && cfg && session && effective === session && Date.now() - session.iat > 24 * 3600 * 1000) {
|
|
249
|
+
// the legacy re-stamp: no refresh token available (pre-offline_access
|
|
250
|
+
// sessions) — the claims are what they were, but the 7-day wall still
|
|
251
|
+
// slides for active users
|
|
217
252
|
headers["set-cookie"] = await mintSessionCookie(cfg.sessionSecret, {
|
|
218
253
|
sub: session.sub,
|
|
219
254
|
name: session.name,
|
|
@@ -224,12 +259,12 @@ export async function handleMe(env: any, req: Request): Promise<Response> {
|
|
|
224
259
|
}
|
|
225
260
|
return new Response(
|
|
226
261
|
JSON.stringify({
|
|
227
|
-
authenticated: !!
|
|
228
|
-
name:
|
|
229
|
-
email:
|
|
230
|
-
picture:
|
|
231
|
-
roles:
|
|
232
|
-
tier:
|
|
262
|
+
authenticated: !!effective,
|
|
263
|
+
name: effective?.name ?? null,
|
|
264
|
+
email: effective?.email ?? null,
|
|
265
|
+
picture: effective?.picture ?? null,
|
|
266
|
+
roles: effective?.roles ?? [],
|
|
267
|
+
tier: effective ? "member" : "anon",
|
|
233
268
|
sign_in_available: !!cfg,
|
|
234
269
|
}),
|
|
235
270
|
{ headers },
|
|
@@ -24,18 +24,15 @@ export interface RefCodec {
|
|
|
24
24
|
familyOf(docidentifier: string): string | null;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// The OIML grammar is @
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
//
|
|
36
|
-
// bundle carries only the OIML grammar), and the retrieval spine of a
|
|
37
|
-
// dual is its OIML side, whichever side prints first.
|
|
38
|
-
import { parseOimlPubid } from "@oimlsmart/oiml-pubid";
|
|
27
|
+
// The OIML grammar is the unified pubid grammar (@pubid/pubid's oiml
|
|
28
|
+
// flavor — single-flavor import so the worker bundle carries the OIML
|
|
29
|
+
// grammar and nothing else). This codec's job is the ADAPTATION: the
|
|
30
|
+
// grammar takes the prefixed form and returns null for everything
|
|
31
|
+
// else; our callers also send bare forms ("R 60-1:2021"), the URN
|
|
32
|
+
// provenance shape, and the dual-published print ("ISO
|
|
33
|
+
// 4064-1:2024|OIML R 49-1:2024") whose retrieval spine is its OIML
|
|
34
|
+
// side, whichever side prints first. The estate's former grammar
|
|
35
|
+
// (@oimlsmart/oiml-pubid) retired into this one (pubid/pubid-ts#63).
|
|
39
36
|
import { oimlGrammarImplementation } from "@pubid/pubid/dist/flavors/oiml/implementation.js";
|
|
40
37
|
|
|
41
38
|
const oimlParser = oimlGrammarImplementation();
|
|
@@ -55,12 +52,13 @@ interface PubidHash {
|
|
|
55
52
|
edition?: string;
|
|
56
53
|
}
|
|
57
54
|
|
|
58
|
-
/** The
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
|
|
55
|
+
/** The spine of a printed identifier through the unified grammar: the
|
|
56
|
+
* type letter, the normalized number, the part and the edition year.
|
|
57
|
+
* A dual-published print resolves to its OIML member (the caller picks
|
|
58
|
+
* the side); unknown type kinds refuse — never a fabricated letter. */
|
|
59
|
+
function parseOimlSpine(display: string): { doc_number: string; edition?: string; label: string } | null {
|
|
62
60
|
try {
|
|
63
|
-
const h = oimlParser.parse(
|
|
61
|
+
const h = oimlParser.parse(display.trim()).toHash() as PubidHash;
|
|
64
62
|
if (h.number === undefined) return null;
|
|
65
63
|
const kind = String(h._type ?? "").split(":").pop() ?? "";
|
|
66
64
|
const letter = TYPE_LETTER[kind] ?? "";
|
|
@@ -89,8 +87,15 @@ const urnToDisplay = (u: string) => {
|
|
|
89
87
|
};
|
|
90
88
|
|
|
91
89
|
const parsePubid = (doc: string) => {
|
|
90
|
+
// a dual-published print: the OIML side is the spine, whichever side
|
|
91
|
+
// prints first — pick it before the bare-form normalization, which
|
|
92
|
+
// would mis-prefix the co-publisher's side
|
|
93
|
+
if (!/^urn:/i.test(doc) && doc.includes("|")) {
|
|
94
|
+
const side = doc.split("|").map((s) => s.trim()).find((s) => /^(?:OIML|oiml)\b/i.test(s));
|
|
95
|
+
return side ? parseOimlSpine(side) : null;
|
|
96
|
+
}
|
|
92
97
|
const src = /^urn:/i.test(doc) ? urnToDisplay(doc) : /^(?:OIML|oiml)\b/i.test(doc) ? doc : `OIML ${doc}`;
|
|
93
|
-
return src ?
|
|
98
|
+
return src ? parseOimlSpine(src) : null;
|
|
94
99
|
};
|
|
95
100
|
|
|
96
101
|
/** OIML's grammar (delegated): type letter + 1–3 digits, optional part,
|
|
@@ -98,20 +103,13 @@ const parsePubid = (doc: string) => {
|
|
|
98
103
|
* edition is never part of the number. */
|
|
99
104
|
export const oimlPubid: RefCodec = {
|
|
100
105
|
parse(doc, edition) {
|
|
101
|
-
// the dual-published print: the OIML side is the spine, whichever
|
|
102
|
-
// side prints first — resolved before the strict grammar, whose
|
|
103
|
-
// null would otherwise be the whole answer
|
|
104
|
-
if (!/^urn:/i.test(doc) && doc.includes("|")) {
|
|
105
|
-
const side = doc.split("|").map((s) => s.trim()).find((s) => /^(?:OIML|oiml)\b/i.test(s));
|
|
106
|
-
const dual = side ? dualOimlSpine(side) : null;
|
|
107
|
-
if (dual) return dual;
|
|
108
|
-
}
|
|
109
106
|
const p = parsePubid(doc);
|
|
110
|
-
if (!p
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
if (!p) return null;
|
|
108
|
+
// the caller's edition is bibdata's truth — it wins over the print's
|
|
109
|
+
if (edition && p.edition !== edition) {
|
|
110
|
+
return { ...p, edition, label: p.label.split(":")[0] + `:${edition}` };
|
|
111
|
+
}
|
|
112
|
+
return p;
|
|
115
113
|
},
|
|
116
114
|
scanQuestion(query) {
|
|
117
115
|
const re = /\b(OIML\s+)?([RDBGE])(\s*)0*(\d{1,3})(?:\s*[-–]\s*\d+)?(?:\s*:\s*(\d{4}))?/gi;
|
|
@@ -131,7 +129,10 @@ export const oimlPubid: RefCodec = {
|
|
|
131
129
|
},
|
|
132
130
|
familyOf(di) {
|
|
133
131
|
const p = parsePubid(di);
|
|
134
|
-
if (p
|
|
132
|
+
if (p) {
|
|
133
|
+
const m = /^OIML ([A-Z]+) (\d{1,3})/.exec(p.label);
|
|
134
|
+
return m ? `${m[1]}-${m[2]}` : null;
|
|
135
|
+
}
|
|
135
136
|
const m = /^(?:OIML\s+)?([A-Z])\s?(\d{1,3})(?:[-–]([0-9A-Za-z]+))?/.exec(di);
|
|
136
137
|
return m ? `${m[1]}-${m[2]}` : null;
|
|
137
138
|
},
|
|
@@ -65,6 +65,41 @@ const EXCHANGED_KEY = (sessionHash: string) => `ossx:${sessionHash}`;
|
|
|
65
65
|
* window (the OP token's own TTL, minus a small margin). Called ONCE
|
|
66
66
|
* per sign-in from the auth callback; the token never persists past
|
|
67
67
|
* the window. */
|
|
68
|
+
// The session's refresh-token store (oidc-refresh.ts): the OP's refresh
|
|
69
|
+
// grant is what makes session renewal a RE-JUDGMENT of live standing
|
|
70
|
+
// instead of a re-stamp of frozen claims. Same KV discipline as the
|
|
71
|
+
// access token: keyed by the session token's hash, TTL = the grant's
|
|
72
|
+
// own life, never D1.
|
|
73
|
+
const REFRESH_KEY = (sessionHash: string) => `ort:${sessionHash}`;
|
|
74
|
+
const REFRESH_TTL_SEC = 30 * 24 * 60 * 60; // the OP's grant TTL default
|
|
75
|
+
|
|
76
|
+
export async function retainRefreshToken(env: any, sessionRaw: string, refreshToken: string): Promise<void> {
|
|
77
|
+
try {
|
|
78
|
+
await env.CACHE.put(REFRESH_KEY(await sha256Hex(sessionRaw)), JSON.stringify({ token: refreshToken }), {
|
|
79
|
+
expirationTtl: REFRESH_TTL_SEC,
|
|
80
|
+
});
|
|
81
|
+
} catch {
|
|
82
|
+
// a KV hiccup degrades to the legacy staleness, never blocks sign-in
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function readRefreshToken(env: any, sessionRaw: string): Promise<string | null> {
|
|
87
|
+
try {
|
|
88
|
+
const hit = await env.CACHE.get(REFRESH_KEY(await sha256Hex(sessionRaw)), "json");
|
|
89
|
+
return typeof hit?.token === "string" ? hit.token : null;
|
|
90
|
+
} catch {
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function dropRefreshToken(env: any, sessionRaw: string): Promise<void> {
|
|
96
|
+
try {
|
|
97
|
+
await env.CACHE.delete(REFRESH_KEY(await sha256Hex(sessionRaw)));
|
|
98
|
+
} catch {
|
|
99
|
+
// best effort
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
68
103
|
export async function retainOpAccessToken(
|
|
69
104
|
env: any,
|
|
70
105
|
sessionRaw: string,
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// The session's lifeline (TODO.identity-rp/01): a session's claims are
|
|
2
|
+
// frozen at mint — a deactivated account, a revoked role, a new avatar
|
|
3
|
+
// would all surface weeks late. So renewal is not a re-stamp of the old
|
|
4
|
+
// claims: when a session ages past the threshold, the RP presents the
|
|
5
|
+
// OP's refresh token, and the OP's refresh grant re-judges the LIVE
|
|
6
|
+
// standing (deactivation kills the grant family → the next renewal ends
|
|
7
|
+
// the session, and the response carries today's name/picture/roles).
|
|
8
|
+
// The rotated refresh token must be stored after every use — the OP's
|
|
9
|
+
// rotation is one-time, and a reuse verdict revokes the whole family.
|
|
10
|
+
|
|
11
|
+
import { discoverIssuer, validateIdToken, type OidcIdTokenClaims } from "./oidc.ts";
|
|
12
|
+
import { renewedSessionClaims, refreshGrant, type OidcMetadata } from "./refresh-core.ts";
|
|
13
|
+
import type { SessionClaims } from "./session";
|
|
14
|
+
import { readRefreshToken, dropRefreshToken, retainRefreshToken } from "./livedata";
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
export interface RenewedSession {
|
|
19
|
+
claims: Omit<SessionClaims, "iat" | "exp">;
|
|
20
|
+
refreshToken: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type RenewOutcome =
|
|
24
|
+
| { kind: "ok"; renewed: RenewedSession }
|
|
25
|
+
| { kind: "revoked" }
|
|
26
|
+
| { kind: "unavailable" };
|
|
27
|
+
|
|
28
|
+
export async function renewSessionClaims(
|
|
29
|
+
env: any,
|
|
30
|
+
cfg: { issuer: string; clientId: string; clientSecret?: string; sessionSecret: string },
|
|
31
|
+
session: SessionClaims,
|
|
32
|
+
sessionRaw: string,
|
|
33
|
+
): Promise<RenewOutcome> {
|
|
34
|
+
const stored = await readRefreshToken(env, sessionRaw);
|
|
35
|
+
if (!stored) return { kind: "unavailable" };
|
|
36
|
+
let meta;
|
|
37
|
+
try {
|
|
38
|
+
meta = (await discoverIssuer(cfg.issuer)) as unknown as OidcMetadata;
|
|
39
|
+
} catch {
|
|
40
|
+
return { kind: "unavailable" };
|
|
41
|
+
}
|
|
42
|
+
const granted = await refreshGrant(meta, cfg, stored);
|
|
43
|
+
// the OP's refusal IS the revocation signal (deactivated, signed out
|
|
44
|
+
// everywhere, expired) — the renewal ends; unavailability keeps the
|
|
45
|
+
// session for the next load
|
|
46
|
+
if (granted.kind === "refused") {
|
|
47
|
+
await dropRefreshToken(env, sessionRaw);
|
|
48
|
+
return { kind: "revoked" };
|
|
49
|
+
}
|
|
50
|
+
if (granted.kind === "unavailable") return granted;
|
|
51
|
+
const token = granted.token;
|
|
52
|
+
if (typeof token.refresh_token !== "string" || !token.refresh_token) {
|
|
53
|
+
// a rotation without a successor: the OP is telling us the grant is
|
|
54
|
+
// finished (narrowed away) — treat as revoked
|
|
55
|
+
await dropRefreshToken(env, sessionRaw);
|
|
56
|
+
return { kind: "revoked" };
|
|
57
|
+
}
|
|
58
|
+
let claims: OidcIdTokenClaims;
|
|
59
|
+
try {
|
|
60
|
+
claims = await validateIdToken(token.id_token, {
|
|
61
|
+
issuer: cfg.issuer,
|
|
62
|
+
clientId: cfg.clientId,
|
|
63
|
+
nonce: undefined as unknown as string,
|
|
64
|
+
jwksUri: meta.jwks_uri,
|
|
65
|
+
});
|
|
66
|
+
} catch {
|
|
67
|
+
return { kind: "unavailable" };
|
|
68
|
+
}
|
|
69
|
+
const fresh = renewedSessionClaims(session, claims);
|
|
70
|
+
if (!fresh) {
|
|
71
|
+
await dropRefreshToken(env, sessionRaw);
|
|
72
|
+
return { kind: "revoked" };
|
|
73
|
+
}
|
|
74
|
+
await retainRefreshToken(env, sessionRaw, token.refresh_token);
|
|
75
|
+
return { kind: "ok", renewed: { claims: fresh, refreshToken: token.refresh_token } };
|
|
76
|
+
}
|