@scalemule/news-publication 0.0.90 → 0.0.92
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/reader-sso.cjs +25 -5
- package/dist/reader-sso.d.cts +7 -1
- package/dist/reader-sso.d.ts +7 -1
- package/dist/reader-sso.js +23 -6
- package/package.json +1 -1
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
|
}
|
|
@@ -215,7 +225,7 @@ async function startSignIn(config, input) {
|
|
|
215
225
|
}
|
|
216
226
|
}
|
|
217
227
|
if (mode === "silent") {
|
|
218
|
-
if (input.cookies(c.names.signedOut)
|
|
228
|
+
if (input.cookies(c.names.signedOut)) {
|
|
219
229
|
return { ok: false, status: 204, error: "", reason: "suppressed", redirect, cookies: staleSession };
|
|
220
230
|
}
|
|
221
231
|
}
|
|
@@ -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
|
}
|
|
@@ -200,7 +210,7 @@ async function startSignIn(config, input) {
|
|
|
200
210
|
}
|
|
201
211
|
}
|
|
202
212
|
if (mode === "silent") {
|
|
203
|
-
if (input.cookies(c.names.signedOut)
|
|
213
|
+
if (input.cookies(c.names.signedOut)) {
|
|
204
214
|
return { ok: false, status: 204, error: "", reason: "suppressed", redirect, cookies: staleSession };
|
|
205
215
|
}
|
|
206
216
|
}
|
|
@@ -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.92",
|
|
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": {
|