@scalemule/news-publication 0.0.89 → 0.0.91
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/{chunk-F6HV2HRP.js → chunk-2E42LCN3.js} +1 -1
- package/dist/{chunk-EPAFIBBS.js → chunk-D27NMCRK.js} +1 -1
- package/dist/{chunk-RSKB5NNE.js → chunk-FJ672GWO.js} +1 -1
- package/dist/{chunk-CKTTYAZY.js → chunk-NNEY6VBX.js} +1 -2
- package/dist/{chunk-R6WTBTXO.js → chunk-PBRDIXQI.js} +1 -1
- package/dist/cookie-consent-banner.js +2 -2
- package/dist/cookie-settings-view.js +2 -2
- package/dist/index.cjs +1 -2
- package/dist/index.js +6 -6
- package/dist/network-bar.js +2 -2
- package/dist/network-copyright.js +2 -2
- package/dist/network.cjs +1 -2
- package/dist/network.d.cts +5 -1
- package/dist/network.d.ts +5 -1
- package/dist/network.js +1 -1
- package/dist/reader-sso.cjs +24 -4
- package/dist/reader-sso.d.cts +7 -1
- package/dist/reader-sso.d.ts +7 -1
- package/dist/reader-sso.js +22 -5
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { setupNetworkConsentDelegation, COOKIE_CONSENT_EVENT, listedPublications, decorateNetworkUrl } from './chunk-
|
|
2
|
+
import { setupNetworkConsentDelegation, COOKIE_CONSENT_EVENT, listedPublications, decorateNetworkUrl } from './chunk-NNEY6VBX.js';
|
|
3
3
|
import { useState, useId, useRef, useEffect } from 'react';
|
|
4
4
|
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { hasConsented, rejectOptionalCookies, DEFAULT_CONSENT_HUB_URL, consentModel, isGpcActive, COOKIE_CONSENT_EVENT, acceptAllCookies, recordConsentToCompliance } from './chunk-
|
|
2
|
+
import { hasConsented, rejectOptionalCookies, DEFAULT_CONSENT_HUB_URL, consentModel, isGpcActive, COOKIE_CONSENT_EVENT, acceptAllCookies, recordConsentToCompliance } from './chunk-NNEY6VBX.js';
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -368,10 +368,9 @@ function getNetworkPublicationUrl(slug) {
|
|
|
368
368
|
if (!pub) return null;
|
|
369
369
|
return decorateNetworkUrl(pub.url + "/");
|
|
370
370
|
}
|
|
371
|
-
var readerHandoffReady = /* @__PURE__ */ new Set(["sanramontimes", "trivalleyweekly"]);
|
|
372
371
|
function getNetworkReaderHandoffUrl(slug) {
|
|
373
372
|
const publication = findPublicationBySlug(slug);
|
|
374
|
-
return publication
|
|
373
|
+
return publication ? `${publication.url}/api/news/network-handoff` : null;
|
|
375
374
|
}
|
|
376
375
|
var NETWORK_HOSTNAMES = new Set(
|
|
377
376
|
localPublications.map((p) => {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { effectivePreferences, getCookieConsent, COOKIE_CATEGORIES, setCookieConsent, recordConsentToCompliance, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED } from './chunk-
|
|
2
|
+
import { effectivePreferences, getCookieConsent, COOKIE_CATEGORIES, setCookieConsent, recordConsentToCompliance, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED } from './chunk-NNEY6VBX.js';
|
|
3
3
|
import { useState, useEffect } from 'react';
|
|
4
4
|
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
5
5
|
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
export { CookieConsentBanner } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { CookieConsentBanner } from './chunk-D27NMCRK.js';
|
|
3
|
+
import './chunk-NNEY6VBX.js';
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
export { CookieSettingsView } from './chunk-
|
|
3
|
-
import './chunk-
|
|
2
|
+
export { CookieSettingsView } from './chunk-PBRDIXQI.js';
|
|
3
|
+
import './chunk-NNEY6VBX.js';
|
package/dist/index.cjs
CHANGED
|
@@ -1477,10 +1477,9 @@ function getNetworkPublicationUrl(slug) {
|
|
|
1477
1477
|
if (!pub) return null;
|
|
1478
1478
|
return compliance.decorateNetworkUrl(pub.url + "/");
|
|
1479
1479
|
}
|
|
1480
|
-
var readerHandoffReady = /* @__PURE__ */ new Set(["sanramontimes", "trivalleyweekly"]);
|
|
1481
1480
|
function getNetworkReaderHandoffUrl(slug) {
|
|
1482
1481
|
const publication = findPublicationBySlug(slug);
|
|
1483
|
-
return publication
|
|
1482
|
+
return publication ? `${publication.url}/api/news/network-handoff` : null;
|
|
1484
1483
|
}
|
|
1485
1484
|
var NETWORK_HOSTNAMES = new Set(
|
|
1486
1485
|
localPublications.map((p) => {
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
export { LocalNetworkBar } from './chunk-
|
|
2
|
-
export { NetworkCopyrightNotice } from './chunk-
|
|
1
|
+
export { LocalNetworkBar } from './chunk-2E42LCN3.js';
|
|
2
|
+
export { NetworkCopyrightNotice } from './chunk-FJ672GWO.js';
|
|
3
3
|
export { CURATED_COMMUNITY_CARDS, VerifiedCardComponent, VerifiedCardSection, getArticleCards, isSafeActionUrl } from './chunk-KYUVXEJY.js';
|
|
4
4
|
export { CLASSIFIED_CATEGORIES, JOB_EMPLOYMENT_TYPES, categoryLabel, classifiedPath, classifiedSearchPath, clipChars, dealDollars, dealGoPath, dealsFeedPath, employmentLabel, isClassifiedCategory, isEmploymentType, jobEmployerBody, jobOpeningBody, jobPostingBody, prepareClassified, prepareJobPost, publicJobPath, publicJobsPath } from './chunk-C7ZPLH2K.js';
|
|
5
5
|
export { MEET_ACTIVITIES, activityLabel, canHostMeetup, isAdult, prepareMeetup, screenMessage } from './chunk-AMLVCZDP.js';
|
|
6
|
-
export { CookieConsentBanner } from './chunk-
|
|
7
|
-
export { CookieSettingsView } from './chunk-
|
|
8
|
-
import { localPublications } from './chunk-
|
|
9
|
-
export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EDITORIAL_PRINCIPLES, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, NETWORK_PARENT, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, consentModel, decorateNetworkUrl, effectivePreferences, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub, topicalPublications } from './chunk-
|
|
6
|
+
export { CookieConsentBanner } from './chunk-D27NMCRK.js';
|
|
7
|
+
export { CookieSettingsView } from './chunk-PBRDIXQI.js';
|
|
8
|
+
import { localPublications } from './chunk-NNEY6VBX.js';
|
|
9
|
+
export { COOKIE_CATEGORIES, COOKIE_CONSENT_BACKUP_KEY, COOKIE_CONSENT_EVENT, COOKIE_CONSENT_KEY, COOKIE_CONSENT_MAX_AGE_SECONDS, DEFAULT_CONSENT_HUB_URL, DEFAULT_PREFERENCES_ACCEPTED, DEFAULT_PREFERENCES_REJECTED, EDITORIAL_PRINCIPLES, EU_EEA_UK_COUNTRIES, NETWORK_CONSENT_STORAGE_KEY, NETWORK_PARENT, acceptAllCookies, checkUrlConsentBridge, clearCookieConsent, communityPublications, consentModel, decorateNetworkUrl, effectivePreferences, findPublicationByHost, findPublicationBySlug, getCookieConsent, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, hasConsented, impliedPreferences, isCategoryAllowed, isGeoConsentRequired, listedPublications, localPublications, queryNetworkConsentHub, recordConsentToCompliance, rejectOptionalCookies, setCookieConsent, setNetworkConsentHub, topicalPublications } from './chunk-NNEY6VBX.js';
|
|
10
10
|
export { THEME_COOKIE, THEME_EVENT, THEME_MAX_AGE_SECONDS, THEME_STORAGE_KEY, ThemeToggle, applyTheme, clearTheme, getStoredTheme, getSystemTheme, resolveEffectiveTheme, setTheme, subscribeTheme, themeInitScript } from './chunk-NQD3M7ZY.js';
|
|
11
11
|
export { communityMapEmbeds } from './chunk-IMYBSG7J.js';
|
|
12
12
|
export { articleFormatLabel, articleHeroRatio, articlePresentation, formatArtworkCaption, parseDocumentBlocks, renderArticleBlocksToHtml, sanitizeInlineMarkdown } from './chunk-DN3TBZLK.js';
|
package/dist/network-bar.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { LocalNetworkBar } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { LocalNetworkBar } from './chunk-2E42LCN3.js';
|
|
2
|
+
import './chunk-NNEY6VBX.js';
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { NetworkCopyrightNotice } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { NetworkCopyrightNotice } from './chunk-FJ672GWO.js';
|
|
2
|
+
import './chunk-NNEY6VBX.js';
|
package/dist/network.cjs
CHANGED
|
@@ -245,10 +245,9 @@ function getNetworkPublicationUrl(slug) {
|
|
|
245
245
|
if (!pub) return null;
|
|
246
246
|
return compliance.decorateNetworkUrl(pub.url + "/");
|
|
247
247
|
}
|
|
248
|
-
var readerHandoffReady = /* @__PURE__ */ new Set(["sanramontimes", "trivalleyweekly"]);
|
|
249
248
|
function getNetworkReaderHandoffUrl(slug) {
|
|
250
249
|
const publication = findPublicationBySlug(slug);
|
|
251
|
-
return publication
|
|
250
|
+
return publication ? `${publication.url}/api/news/network-handoff` : null;
|
|
252
251
|
}
|
|
253
252
|
var NETWORK_HOSTNAMES = new Set(
|
|
254
253
|
localPublications.map((p) => {
|
package/dist/network.d.cts
CHANGED
|
@@ -58,7 +58,11 @@ declare function findPublicationBySlug(slug: string): PublicationNetworkItem | u
|
|
|
58
58
|
declare function findPublicationByHost(host: string): PublicationNetworkItem | undefined;
|
|
59
59
|
/** Get decorated URL for cross-domain navigation within the network with consent transfer. */
|
|
60
60
|
declare function getNetworkPublicationUrl(slug: string): string | null;
|
|
61
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* The fixed destination route for a publication in this reviewed network list.
|
|
63
|
+
* Sites only use it when their handoff switch is enabled after every destination
|
|
64
|
+
* has deployed the route. No identity or caller-selected return URL is carried.
|
|
65
|
+
*/
|
|
62
66
|
declare function getNetworkReaderHandoffUrl(slug: string): string | null;
|
|
63
67
|
/** Check whether a given hostname belongs to any publication in the ScaleMule news network. */
|
|
64
68
|
declare function isNetworkHostname(hostname: string): boolean;
|
package/dist/network.d.ts
CHANGED
|
@@ -58,7 +58,11 @@ declare function findPublicationBySlug(slug: string): PublicationNetworkItem | u
|
|
|
58
58
|
declare function findPublicationByHost(host: string): PublicationNetworkItem | undefined;
|
|
59
59
|
/** Get decorated URL for cross-domain navigation within the network with consent transfer. */
|
|
60
60
|
declare function getNetworkPublicationUrl(slug: string): string | null;
|
|
61
|
-
/**
|
|
61
|
+
/**
|
|
62
|
+
* The fixed destination route for a publication in this reviewed network list.
|
|
63
|
+
* Sites only use it when their handoff switch is enabled after every destination
|
|
64
|
+
* has deployed the route. No identity or caller-selected return URL is carried.
|
|
65
|
+
*/
|
|
62
66
|
declare function getNetworkReaderHandoffUrl(slug: string): string | null;
|
|
63
67
|
/** Check whether a given hostname belongs to any publication in the ScaleMule news network. */
|
|
64
68
|
declare function isNetworkHostname(hostname: string): boolean;
|
package/dist/network.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications } from './chunk-
|
|
1
|
+
export { EDITORIAL_PRINCIPLES, NETWORK_PARENT, communityPublications, findPublicationByHost, findPublicationBySlug, getNetworkPublicationUrl, getNetworkReaderHandoffUrl, isNetworkHostname, listedPublications, localPublications, topicalPublications } from './chunk-NNEY6VBX.js';
|
package/dist/reader-sso.cjs
CHANGED
|
@@ -20,6 +20,7 @@ var READER_SSO_FLOW_SECONDS = 600;
|
|
|
20
20
|
var READER_SSO_NEGATIVE_CACHE_SECONDS = 300;
|
|
21
21
|
var READER_SSO_SIGNED_OUT_SECONDS = 86400;
|
|
22
22
|
var READER_SSO_PENDING_SECONDS = 600;
|
|
23
|
+
var READER_SSO_LINK_SECONDS = 600;
|
|
23
24
|
var LEDVERY_ISSUER_ORIGIN = "https://api.ledvery.com";
|
|
24
25
|
var SILENT_NEGATIVE_ERRORS = ["login_required", "consent_required", "interaction_required", "account_selection_required"];
|
|
25
26
|
var COOKIE_PREFIX = /^__Host-[A-Za-z0-9_-]{1,60}$/;
|
|
@@ -35,7 +36,8 @@ function readerSsoCookieNames(prefix) {
|
|
|
35
36
|
ssoChecked: `${prefix}-sso-checked`,
|
|
36
37
|
signedOut: `${prefix}-signed-out`,
|
|
37
38
|
pending: `${prefix}-sso-pending`,
|
|
38
|
-
linkCode: `${prefix}-link-code
|
|
39
|
+
linkCode: `${prefix}-link-code`,
|
|
40
|
+
linkFlow: `${prefix}-sso-link`
|
|
39
41
|
};
|
|
40
42
|
}
|
|
41
43
|
var readerSsoCookieOptions = { httpOnly: true, secure: true, sameSite: "lax", path: "/" };
|
|
@@ -160,6 +162,14 @@ function openReaderFlow(cookie, key, now = Date.now(), returnPath = (next) => lo
|
|
|
160
162
|
const tx = typeof raw.tx === "string" && /^[a-f0-9]{1,64}$/.test(raw.tx) ? raw.tx : void 0;
|
|
161
163
|
return { digest: raw.digest, subscribe: raw.subscribe && mode === "interactive", expires: raw.expires, returnTo, mode, provider, tx };
|
|
162
164
|
}
|
|
165
|
+
function sealLinkChallenge(challengeId, key, now = Date.now()) {
|
|
166
|
+
if (!CHALLENGE_ID.test(challengeId)) throw new Error("Invalid reader link challenge");
|
|
167
|
+
return seal("reader-link:v1", { challengeId, expires: now + READER_SSO_LINK_SECONDS * 1e3 }, key);
|
|
168
|
+
}
|
|
169
|
+
function openLinkChallenge(cookie, key, now = Date.now()) {
|
|
170
|
+
const raw = open("reader-link:v1", cookie, key);
|
|
171
|
+
return raw && typeof raw.challengeId === "string" && CHALLENGE_ID.test(raw.challengeId) && typeof raw.expires === "number" && Number.isFinite(raw.expires) && raw.expires > now ? raw.challengeId : null;
|
|
172
|
+
}
|
|
163
173
|
function sealPendingSession(pending, key, now = Date.now()) {
|
|
164
174
|
return seal("reader-pending:v1", { ...pending, until: now + READER_SSO_PENDING_SECONDS * 1e3 }, key);
|
|
165
175
|
}
|
|
@@ -237,7 +247,7 @@ async function startSignIn(config, input) {
|
|
|
237
247
|
...staleSession,
|
|
238
248
|
{ name: c.names.flow, value: sealReaderFlow({ state: value.state, subscribe: input.subscribe === true && mode === "interactive", returnTo: next, mode, provider, tx }, c.key, c.now()), maxAge: READER_SSO_FLOW_SECONDS }
|
|
239
249
|
];
|
|
240
|
-
if (mode === "interactive") cookies.push(clear(c.names.signedOut), clear(c.names.ssoChecked));
|
|
250
|
+
if (mode === "interactive") cookies.push(clear(c.names.signedOut), clear(c.names.ssoChecked), clear(c.names.linkFlow), clear(c.names.linkCode));
|
|
241
251
|
emit(c, { name: "reader_signin_started", properties: { method: provider ?? "unknown", mode, transaction: tx } });
|
|
242
252
|
return { ok: true, url, tx, cookies };
|
|
243
253
|
} catch {
|
|
@@ -326,6 +336,7 @@ async function completeSignIn(config, input) {
|
|
|
326
336
|
if (hint) link.set("hint", hint);
|
|
327
337
|
if (flow.returnTo) link.set("next", flow.returnTo);
|
|
328
338
|
if (flow.subscribe) link.set("subscribe", "1");
|
|
339
|
+
cookies.push({ name: c.names.linkFlow, value: sealLinkChallenge(challengeId, c.key, now), maxAge: READER_SSO_LINK_SECONDS });
|
|
329
340
|
return redirect(`${c.accountPath}?${link}`, "link_required");
|
|
330
341
|
}
|
|
331
342
|
if (failed(exchange)) throw new Error("exchange");
|
|
@@ -410,7 +421,11 @@ async function linkAccount(config, input) {
|
|
|
410
421
|
const c = resolve(config);
|
|
411
422
|
const challengeId = typeof input.challenge_id === "string" && CHALLENGE_ID.test(input.challenge_id) ? input.challenge_id : "";
|
|
412
423
|
const password = typeof input.password === "string" && input.password.length > 0 && input.password.length <= 1024 ? input.password : "";
|
|
413
|
-
if (!challengeId ||
|
|
424
|
+
if (c.enabled === false || !challengeId || openLinkChallenge(input.cookies(c.names.linkFlow), c.key, c.now()) !== challengeId) {
|
|
425
|
+
emit(c, { name: "reader_signin_failed", properties: { reason: "link_invalid", mode: "interactive" } });
|
|
426
|
+
return { ok: false, status: 400, error: "Start sign-in again to connect your existing reader account.", cookies: [] };
|
|
427
|
+
}
|
|
428
|
+
if (!password) {
|
|
414
429
|
emit(c, { name: "reader_signin_failed", properties: { reason: "link_invalid", mode: "interactive" } });
|
|
415
430
|
return { ok: false, status: 400, error: "Enter the password of your existing reader account.", cookies: [] };
|
|
416
431
|
}
|
|
@@ -439,7 +454,7 @@ async function linkAccount(config, input) {
|
|
|
439
454
|
}
|
|
440
455
|
const session = issuedSession(response, c.now());
|
|
441
456
|
const flow = { digest: "", subscribe: input.subscribe === true, expires: 0, returnTo: next, mode: "interactive", provider: void 0 };
|
|
442
|
-
const result = await establish(c, { session, flow, method: "link", cookies: emailChallenge ? [clear(c.names.linkCode)] : [], input, negativeCache: { name: c.names.ssoChecked, value: "1", maxAge: READER_SSO_NEGATIVE_CACHE_SECONDS } });
|
|
457
|
+
const result = await establish(c, { session, flow, method: "link", cookies: [clear(c.names.linkFlow), ...emailChallenge ? [clear(c.names.linkCode)] : []], input, negativeCache: { name: c.names.ssoChecked, value: "1", maxAge: READER_SSO_NEGATIVE_CACHE_SECONDS } });
|
|
443
458
|
if (result.kind === "signed-in") emit(c, { name: "reader_account_linked", properties: {} });
|
|
444
459
|
return result.kind === "signed-in" ? { ok: true, switch: false, cookies: result.cookies, token: result.token, expiresAt: result.expiresAt, user: result.user, location: result.location } : { ok: true, switch: true, cookies: result.cookies, location: result.location };
|
|
445
460
|
} catch {
|
|
@@ -507,6 +522,8 @@ async function signOut(config, input) {
|
|
|
507
522
|
clear(c.names.newsletter),
|
|
508
523
|
clear(c.names.pending),
|
|
509
524
|
clear(c.names.ssoChecked),
|
|
525
|
+
clear(c.names.linkFlow),
|
|
526
|
+
clear(c.names.linkCode),
|
|
510
527
|
{ name: c.names.signedOut, value: "1", maxAge: READER_SSO_SIGNED_OUT_SECONDS },
|
|
511
528
|
...input.extraCookies ?? []
|
|
512
529
|
],
|
|
@@ -536,6 +553,7 @@ function readerSsoBootFlags(cookies, names) {
|
|
|
536
553
|
exports.LEDVERY_ISSUER_ORIGIN = LEDVERY_ISSUER_ORIGIN;
|
|
537
554
|
exports.READER_SSO_EVENTS = READER_SSO_EVENTS;
|
|
538
555
|
exports.READER_SSO_FLOW_SECONDS = READER_SSO_FLOW_SECONDS;
|
|
556
|
+
exports.READER_SSO_LINK_SECONDS = READER_SSO_LINK_SECONDS;
|
|
539
557
|
exports.READER_SSO_NEGATIVE_CACHE_SECONDS = READER_SSO_NEGATIVE_CACHE_SECONDS;
|
|
540
558
|
exports.READER_SSO_PENDING_SECONDS = READER_SSO_PENDING_SECONDS;
|
|
541
559
|
exports.READER_SSO_PROPERTY_KEYS = READER_SSO_PROPERTY_KEYS;
|
|
@@ -549,6 +567,7 @@ exports.linkAccount = linkAccount;
|
|
|
549
567
|
exports.linkFailureMessage = linkFailureMessage;
|
|
550
568
|
exports.localReturnPath = localReturnPath;
|
|
551
569
|
exports.networkHandoffReturnPath = networkHandoffReturnPath;
|
|
570
|
+
exports.openLinkChallenge = openLinkChallenge;
|
|
552
571
|
exports.openPendingSession = openPendingSession;
|
|
553
572
|
exports.openReaderFlow = openReaderFlow;
|
|
554
573
|
exports.pendingSwitch = pendingSwitch;
|
|
@@ -556,6 +575,7 @@ exports.precedence = precedence;
|
|
|
556
575
|
exports.readerSsoBootFlags = readerSsoBootFlags;
|
|
557
576
|
exports.readerSsoCookieNames = readerSsoCookieNames;
|
|
558
577
|
exports.readerSsoCookieOptions = readerSsoCookieOptions;
|
|
578
|
+
exports.sealLinkChallenge = sealLinkChallenge;
|
|
559
579
|
exports.sealPendingSession = sealPendingSession;
|
|
560
580
|
exports.sealReaderFlow = sealReaderFlow;
|
|
561
581
|
exports.sessionSummary = sessionSummary;
|
package/dist/reader-sso.d.cts
CHANGED
|
@@ -7,6 +7,7 @@ declare const READER_SSO_FLOW_SECONDS = 600;
|
|
|
7
7
|
declare const READER_SSO_NEGATIVE_CACHE_SECONDS = 300;
|
|
8
8
|
declare const READER_SSO_SIGNED_OUT_SECONDS = 86400;
|
|
9
9
|
declare const READER_SSO_PENDING_SECONDS = 600;
|
|
10
|
+
declare const READER_SSO_LINK_SECONDS = 600;
|
|
10
11
|
declare const LEDVERY_ISSUER_ORIGIN = "https://api.ledvery.com";
|
|
11
12
|
/** Ledvery results that mean "no silent session here"; never authentication failures. */
|
|
12
13
|
declare const SILENT_NEGATIVE_ERRORS: readonly ["login_required", "consent_required", "interaction_required", "account_selection_required"];
|
|
@@ -25,6 +26,8 @@ type ReaderSsoCookieNames = {
|
|
|
25
26
|
pending: string;
|
|
26
27
|
/** Linking onto an unconfirmed account: the platform's emailed-code challenge token (10 minutes). */
|
|
27
28
|
linkCode: string;
|
|
29
|
+
/** Signed proof that this browser received the account-link challenge from our callback. */
|
|
30
|
+
linkFlow: string;
|
|
28
31
|
};
|
|
29
32
|
/** Cookie names derived from a `__Host-` prefix, e.g. `__Host-media` → `__Host-media-reader`. */
|
|
30
33
|
declare function readerSsoCookieNames(prefix: string): ReaderSsoCookieNames;
|
|
@@ -158,6 +161,9 @@ declare function sealReaderFlow(input: {
|
|
|
158
161
|
tx?: string;
|
|
159
162
|
}, key: string, now?: number): string;
|
|
160
163
|
declare function openReaderFlow(cookie: string | undefined, key: string, now?: number, returnPath?: (next: string, mode: ReaderSsoMode) => string): ReaderSsoFlow | null;
|
|
164
|
+
/** A URL's challenge ID is not enough to start linking an existing account. */
|
|
165
|
+
declare function sealLinkChallenge(challengeId: string, key: string, now?: number): string;
|
|
166
|
+
declare function openLinkChallenge(cookie: string | undefined, key: string, now?: number): string | null;
|
|
161
167
|
/** A verified session the reader has not yet chosen over their current one. */
|
|
162
168
|
type PendingSession = {
|
|
163
169
|
token: string;
|
|
@@ -327,4 +333,4 @@ declare function readerSsoBootFlags(cookies: CookieReader, names: ReaderSsoCooki
|
|
|
327
333
|
suppressed: boolean;
|
|
328
334
|
};
|
|
329
335
|
|
|
330
|
-
export { type CompleteSignInInput, type CompleteSignInResult, type CookieInstruction, type CookieReader, LEDVERY_ISSUER_ORIGIN, type LinkAccountInput, type LinkAccountResult, type PendingSession, READER_SSO_FLOW_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, type ReaderSsoCheckOutcome, type ReaderSsoConfig, type ReaderSsoCookieNames, type ReaderSsoEvent, type ReaderSsoFailure, type ReaderSsoFlow, type ReaderSsoMethod, type ReaderSsoMode, type ReaderSsoProvider, type ReaderSsoRequest, type ReaderSsoResponse, SILENT_NEGATIVE_ERRORS, type SessionSummary, type SessionUser, type SignOutResult, type StartSignInInput, type StartSignInResult, type SwitchAccountResult, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, networkHandoffReturnPath, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
|
336
|
+
export { type CompleteSignInInput, type CompleteSignInResult, type CookieInstruction, type CookieReader, LEDVERY_ISSUER_ORIGIN, type LinkAccountInput, type LinkAccountResult, type PendingSession, READER_SSO_FLOW_SECONDS, READER_SSO_LINK_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, type ReaderSsoCheckOutcome, type ReaderSsoConfig, type ReaderSsoCookieNames, type ReaderSsoEvent, type ReaderSsoFailure, type ReaderSsoFlow, type ReaderSsoMethod, type ReaderSsoMode, type ReaderSsoProvider, type ReaderSsoRequest, type ReaderSsoResponse, SILENT_NEGATIVE_ERRORS, type SessionSummary, type SessionUser, type SignOutResult, type StartSignInInput, type StartSignInResult, type SwitchAccountResult, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, networkHandoffReturnPath, openLinkChallenge, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealLinkChallenge, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
package/dist/reader-sso.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ declare const READER_SSO_FLOW_SECONDS = 600;
|
|
|
7
7
|
declare const READER_SSO_NEGATIVE_CACHE_SECONDS = 300;
|
|
8
8
|
declare const READER_SSO_SIGNED_OUT_SECONDS = 86400;
|
|
9
9
|
declare const READER_SSO_PENDING_SECONDS = 600;
|
|
10
|
+
declare const READER_SSO_LINK_SECONDS = 600;
|
|
10
11
|
declare const LEDVERY_ISSUER_ORIGIN = "https://api.ledvery.com";
|
|
11
12
|
/** Ledvery results that mean "no silent session here"; never authentication failures. */
|
|
12
13
|
declare const SILENT_NEGATIVE_ERRORS: readonly ["login_required", "consent_required", "interaction_required", "account_selection_required"];
|
|
@@ -25,6 +26,8 @@ type ReaderSsoCookieNames = {
|
|
|
25
26
|
pending: string;
|
|
26
27
|
/** Linking onto an unconfirmed account: the platform's emailed-code challenge token (10 minutes). */
|
|
27
28
|
linkCode: string;
|
|
29
|
+
/** Signed proof that this browser received the account-link challenge from our callback. */
|
|
30
|
+
linkFlow: string;
|
|
28
31
|
};
|
|
29
32
|
/** Cookie names derived from a `__Host-` prefix, e.g. `__Host-media` → `__Host-media-reader`. */
|
|
30
33
|
declare function readerSsoCookieNames(prefix: string): ReaderSsoCookieNames;
|
|
@@ -158,6 +161,9 @@ declare function sealReaderFlow(input: {
|
|
|
158
161
|
tx?: string;
|
|
159
162
|
}, key: string, now?: number): string;
|
|
160
163
|
declare function openReaderFlow(cookie: string | undefined, key: string, now?: number, returnPath?: (next: string, mode: ReaderSsoMode) => string): ReaderSsoFlow | null;
|
|
164
|
+
/** A URL's challenge ID is not enough to start linking an existing account. */
|
|
165
|
+
declare function sealLinkChallenge(challengeId: string, key: string, now?: number): string;
|
|
166
|
+
declare function openLinkChallenge(cookie: string | undefined, key: string, now?: number): string | null;
|
|
161
167
|
/** A verified session the reader has not yet chosen over their current one. */
|
|
162
168
|
type PendingSession = {
|
|
163
169
|
token: string;
|
|
@@ -327,4 +333,4 @@ declare function readerSsoBootFlags(cookies: CookieReader, names: ReaderSsoCooki
|
|
|
327
333
|
suppressed: boolean;
|
|
328
334
|
};
|
|
329
335
|
|
|
330
|
-
export { type CompleteSignInInput, type CompleteSignInResult, type CookieInstruction, type CookieReader, LEDVERY_ISSUER_ORIGIN, type LinkAccountInput, type LinkAccountResult, type PendingSession, READER_SSO_FLOW_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, type ReaderSsoCheckOutcome, type ReaderSsoConfig, type ReaderSsoCookieNames, type ReaderSsoEvent, type ReaderSsoFailure, type ReaderSsoFlow, type ReaderSsoMethod, type ReaderSsoMode, type ReaderSsoProvider, type ReaderSsoRequest, type ReaderSsoResponse, SILENT_NEGATIVE_ERRORS, type SessionSummary, type SessionUser, type SignOutResult, type StartSignInInput, type StartSignInResult, type SwitchAccountResult, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, networkHandoffReturnPath, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
|
336
|
+
export { type CompleteSignInInput, type CompleteSignInResult, type CookieInstruction, type CookieReader, LEDVERY_ISSUER_ORIGIN, type LinkAccountInput, type LinkAccountResult, type PendingSession, READER_SSO_FLOW_SECONDS, READER_SSO_LINK_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, type ReaderSsoCheckOutcome, type ReaderSsoConfig, type ReaderSsoCookieNames, type ReaderSsoEvent, type ReaderSsoFailure, type ReaderSsoFlow, type ReaderSsoMethod, type ReaderSsoMode, type ReaderSsoProvider, type ReaderSsoRequest, type ReaderSsoResponse, SILENT_NEGATIVE_ERRORS, type SessionSummary, type SessionUser, type SignOutResult, type StartSignInInput, type StartSignInResult, type SwitchAccountResult, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, networkHandoffReturnPath, openLinkChallenge, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealLinkChallenge, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
package/dist/reader-sso.js
CHANGED
|
@@ -5,6 +5,7 @@ var READER_SSO_FLOW_SECONDS = 600;
|
|
|
5
5
|
var READER_SSO_NEGATIVE_CACHE_SECONDS = 300;
|
|
6
6
|
var READER_SSO_SIGNED_OUT_SECONDS = 86400;
|
|
7
7
|
var READER_SSO_PENDING_SECONDS = 600;
|
|
8
|
+
var READER_SSO_LINK_SECONDS = 600;
|
|
8
9
|
var LEDVERY_ISSUER_ORIGIN = "https://api.ledvery.com";
|
|
9
10
|
var SILENT_NEGATIVE_ERRORS = ["login_required", "consent_required", "interaction_required", "account_selection_required"];
|
|
10
11
|
var COOKIE_PREFIX = /^__Host-[A-Za-z0-9_-]{1,60}$/;
|
|
@@ -20,7 +21,8 @@ function readerSsoCookieNames(prefix) {
|
|
|
20
21
|
ssoChecked: `${prefix}-sso-checked`,
|
|
21
22
|
signedOut: `${prefix}-signed-out`,
|
|
22
23
|
pending: `${prefix}-sso-pending`,
|
|
23
|
-
linkCode: `${prefix}-link-code
|
|
24
|
+
linkCode: `${prefix}-link-code`,
|
|
25
|
+
linkFlow: `${prefix}-sso-link`
|
|
24
26
|
};
|
|
25
27
|
}
|
|
26
28
|
var readerSsoCookieOptions = { httpOnly: true, secure: true, sameSite: "lax", path: "/" };
|
|
@@ -145,6 +147,14 @@ function openReaderFlow(cookie, key, now = Date.now(), returnPath = (next) => lo
|
|
|
145
147
|
const tx = typeof raw.tx === "string" && /^[a-f0-9]{1,64}$/.test(raw.tx) ? raw.tx : void 0;
|
|
146
148
|
return { digest: raw.digest, subscribe: raw.subscribe && mode === "interactive", expires: raw.expires, returnTo, mode, provider, tx };
|
|
147
149
|
}
|
|
150
|
+
function sealLinkChallenge(challengeId, key, now = Date.now()) {
|
|
151
|
+
if (!CHALLENGE_ID.test(challengeId)) throw new Error("Invalid reader link challenge");
|
|
152
|
+
return seal("reader-link:v1", { challengeId, expires: now + READER_SSO_LINK_SECONDS * 1e3 }, key);
|
|
153
|
+
}
|
|
154
|
+
function openLinkChallenge(cookie, key, now = Date.now()) {
|
|
155
|
+
const raw = open("reader-link:v1", cookie, key);
|
|
156
|
+
return raw && typeof raw.challengeId === "string" && CHALLENGE_ID.test(raw.challengeId) && typeof raw.expires === "number" && Number.isFinite(raw.expires) && raw.expires > now ? raw.challengeId : null;
|
|
157
|
+
}
|
|
148
158
|
function sealPendingSession(pending, key, now = Date.now()) {
|
|
149
159
|
return seal("reader-pending:v1", { ...pending, until: now + READER_SSO_PENDING_SECONDS * 1e3 }, key);
|
|
150
160
|
}
|
|
@@ -222,7 +232,7 @@ async function startSignIn(config, input) {
|
|
|
222
232
|
...staleSession,
|
|
223
233
|
{ name: c.names.flow, value: sealReaderFlow({ state: value.state, subscribe: input.subscribe === true && mode === "interactive", returnTo: next, mode, provider, tx }, c.key, c.now()), maxAge: READER_SSO_FLOW_SECONDS }
|
|
224
234
|
];
|
|
225
|
-
if (mode === "interactive") cookies.push(clear(c.names.signedOut), clear(c.names.ssoChecked));
|
|
235
|
+
if (mode === "interactive") cookies.push(clear(c.names.signedOut), clear(c.names.ssoChecked), clear(c.names.linkFlow), clear(c.names.linkCode));
|
|
226
236
|
emit(c, { name: "reader_signin_started", properties: { method: provider ?? "unknown", mode, transaction: tx } });
|
|
227
237
|
return { ok: true, url, tx, cookies };
|
|
228
238
|
} catch {
|
|
@@ -311,6 +321,7 @@ async function completeSignIn(config, input) {
|
|
|
311
321
|
if (hint) link.set("hint", hint);
|
|
312
322
|
if (flow.returnTo) link.set("next", flow.returnTo);
|
|
313
323
|
if (flow.subscribe) link.set("subscribe", "1");
|
|
324
|
+
cookies.push({ name: c.names.linkFlow, value: sealLinkChallenge(challengeId, c.key, now), maxAge: READER_SSO_LINK_SECONDS });
|
|
314
325
|
return redirect(`${c.accountPath}?${link}`, "link_required");
|
|
315
326
|
}
|
|
316
327
|
if (failed(exchange)) throw new Error("exchange");
|
|
@@ -395,7 +406,11 @@ async function linkAccount(config, input) {
|
|
|
395
406
|
const c = resolve(config);
|
|
396
407
|
const challengeId = typeof input.challenge_id === "string" && CHALLENGE_ID.test(input.challenge_id) ? input.challenge_id : "";
|
|
397
408
|
const password = typeof input.password === "string" && input.password.length > 0 && input.password.length <= 1024 ? input.password : "";
|
|
398
|
-
if (!challengeId ||
|
|
409
|
+
if (c.enabled === false || !challengeId || openLinkChallenge(input.cookies(c.names.linkFlow), c.key, c.now()) !== challengeId) {
|
|
410
|
+
emit(c, { name: "reader_signin_failed", properties: { reason: "link_invalid", mode: "interactive" } });
|
|
411
|
+
return { ok: false, status: 400, error: "Start sign-in again to connect your existing reader account.", cookies: [] };
|
|
412
|
+
}
|
|
413
|
+
if (!password) {
|
|
399
414
|
emit(c, { name: "reader_signin_failed", properties: { reason: "link_invalid", mode: "interactive" } });
|
|
400
415
|
return { ok: false, status: 400, error: "Enter the password of your existing reader account.", cookies: [] };
|
|
401
416
|
}
|
|
@@ -424,7 +439,7 @@ async function linkAccount(config, input) {
|
|
|
424
439
|
}
|
|
425
440
|
const session = issuedSession(response, c.now());
|
|
426
441
|
const flow = { digest: "", subscribe: input.subscribe === true, expires: 0, returnTo: next, mode: "interactive", provider: void 0 };
|
|
427
|
-
const result = await establish(c, { session, flow, method: "link", cookies: emailChallenge ? [clear(c.names.linkCode)] : [], input, negativeCache: { name: c.names.ssoChecked, value: "1", maxAge: READER_SSO_NEGATIVE_CACHE_SECONDS } });
|
|
442
|
+
const result = await establish(c, { session, flow, method: "link", cookies: [clear(c.names.linkFlow), ...emailChallenge ? [clear(c.names.linkCode)] : []], input, negativeCache: { name: c.names.ssoChecked, value: "1", maxAge: READER_SSO_NEGATIVE_CACHE_SECONDS } });
|
|
428
443
|
if (result.kind === "signed-in") emit(c, { name: "reader_account_linked", properties: {} });
|
|
429
444
|
return result.kind === "signed-in" ? { ok: true, switch: false, cookies: result.cookies, token: result.token, expiresAt: result.expiresAt, user: result.user, location: result.location } : { ok: true, switch: true, cookies: result.cookies, location: result.location };
|
|
430
445
|
} catch {
|
|
@@ -492,6 +507,8 @@ async function signOut(config, input) {
|
|
|
492
507
|
clear(c.names.newsletter),
|
|
493
508
|
clear(c.names.pending),
|
|
494
509
|
clear(c.names.ssoChecked),
|
|
510
|
+
clear(c.names.linkFlow),
|
|
511
|
+
clear(c.names.linkCode),
|
|
495
512
|
{ name: c.names.signedOut, value: "1", maxAge: READER_SSO_SIGNED_OUT_SECONDS },
|
|
496
513
|
...input.extraCookies ?? []
|
|
497
514
|
],
|
|
@@ -518,4 +535,4 @@ function readerSsoBootFlags(cookies, names) {
|
|
|
518
535
|
};
|
|
519
536
|
}
|
|
520
537
|
|
|
521
|
-
export { LEDVERY_ISSUER_ORIGIN, READER_SSO_FLOW_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, SILENT_NEGATIVE_ERRORS, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, networkHandoffReturnPath, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
|
538
|
+
export { LEDVERY_ISSUER_ORIGIN, READER_SSO_FLOW_SECONDS, READER_SSO_LINK_SECONDS, READER_SSO_NEGATIVE_CACHE_SECONDS, READER_SSO_PENDING_SECONDS, READER_SSO_SIGNED_OUT_SECONDS, SILENT_NEGATIVE_ERRORS, applyCookies, authorizeUrl, completeSignIn, cookieReader, linkAccount, linkFailureMessage, localReturnPath, networkHandoffReturnPath, openLinkChallenge, openPendingSession, openReaderFlow, pendingSwitch, precedence, readerSsoBootFlags, readerSsoCookieNames, readerSsoCookieOptions, sealLinkChallenge, sealPendingSession, sealReaderFlow, sessionSummary, sessionSummaryWithCookies, setCookieHeader, signOut, startSignIn, stateDigest, switchAccount, validState };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scalemule/news-publication",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.91",
|
|
4
4
|
"description": "Shared server framework for ScaleMule local news publications. Walnut Creek Times is the broadsheet default; each paper supplies its own identity and feature flags.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|