@anthusai/papyrus 1.0.0-next.9 → 1.1.0
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/amplify/auth/prospectus-google-client.js +66 -0
- package/amplify/auth/prospectus-google-client.ts +71 -0
- package/amplify/auth/redirect-urls.js +54 -0
- package/amplify/auth/redirect-urls.ts +66 -0
- package/amplify/auth/resource.js +23 -5
- package/amplify/auth/resource.ts +31 -5
- package/amplify/data/resource.js +42 -80
- package/amplify/data/resource.ts +42 -80
- package/amplify/data/schema.js +45 -62
- package/amplify/data/schema.ts +47 -62
- package/amplify/functions/category-action/handler.ts +5 -3
- package/amplify/functions/console-chat-responder/py/execute_tactus_runner.py +0 -10
- package/amplify/functions/console-chat-responder/resource.js +0 -7
- package/amplify/functions/console-chat-responder/resource.ts +0 -8
- package/amplify/functions/console-chat-responder/src/main.rs +0 -9
- package/amplify/functions/content-actions/handler.py +143 -1
- package/amplify/functions/content-actions/requirements.txt +2 -1
- package/amplify/functions/content-actions/resource.js +6 -1
- package/amplify/functions/content-actions/resource.ts +9 -2
- package/amplify/functions/email-submission-processor/handler.py +3 -1
- package/amplify/functions/renditions/handler.py +120 -0
- package/amplify/functions/renditions/requirements.txt +4 -0
- package/amplify/functions/ses-inbound-receive/handler.ts +32 -4
- package/amplify/functions/shared/email-submission.ts +109 -1
- package/amplify/functions/shared/python-bundle.js +42 -2
- package/amplify/functions/shared/python-bundle.ts +47 -2
- package/amplify/functions/shared/reference-curation-metadata.ts +29 -0
- package/amplify/inbound-email/ses-intake.js +86 -0
- package/amplify/inbound-email/ses-intake.ts +110 -0
- package/amplify/plugins/registry.js +19 -0
- package/amplify/plugins/registry.ts +23 -0
- package/amplify/plugins/signup/function/handler.py +200 -0
- package/amplify/plugins/signup/function/resource.js +27 -0
- package/amplify/plugins/signup/function/resource.ts +35 -0
- package/amplify/plugins/signup/papyrus.plugin.js +59 -0
- package/amplify/plugins/signup/papyrus.plugin.ts +74 -0
- package/amplify/plugins/signup/schema.js +40 -0
- package/amplify/plugins/signup/schema.ts +41 -0
- package/amplify/plugins/signup/waf.js +48 -0
- package/amplify/plugins/signup/waf.ts +51 -0
- package/amplify/plugins/types.js +1 -0
- package/amplify/plugins/types.ts +39 -0
- package/amplify/plugins/wire.js +24 -0
- package/amplify/plugins/wire.ts +26 -0
- package/amplify/publication/default-publication.js +18 -0
- package/amplify/publication/default-publication.ts +20 -0
- package/amplify/publication/invalidation-alarm.js +26 -0
- package/amplify/publication/invalidation-alarm.ts +30 -0
- package/amplify/publication/redirect-and-index.js +37 -0
- package/amplify/publication/renditions-function.js +74 -0
- package/amplify/publication/renditions-function.ts +112 -0
- package/amplify/publication/renditions-settings.js +51 -0
- package/amplify/publication/renditions-settings.ts +58 -0
- package/amplify/publication/reproject-on-deploy.js +73 -0
- package/amplify/publication/reproject-on-deploy.ts +78 -0
- package/amplify/publication/s3-projection-settings.js +67 -0
- package/amplify/publication/s3-projection-settings.ts +101 -0
- package/amplify/publication/s3-projection.js +151 -0
- package/amplify/publication/s3-projection.ts +201 -0
- package/amplify/resolver-waves.js +26 -0
- package/amplify/resolver-waves.ts +34 -0
- package/amplify/revalidate-invalidation-settings.js +30 -0
- package/amplify/revalidate-invalidation-settings.ts +35 -0
- package/amplify/revalidate-secret-policy.js +13 -0
- package/amplify/revalidate-secret-policy.ts +19 -0
- package/amplify/site-backend-config.js +65 -1
- package/amplify/site-backend-config.ts +153 -2
- package/amplify/site-backend-names.js +103 -0
- package/amplify/site-backend-names.ts +166 -0
- package/amplify/site-backend.js +150 -176
- package/amplify/site-backend.ts +189 -201
- package/amplify/storage-backups/construct.js +31 -0
- package/amplify/storage-backups/construct.ts +47 -0
- package/app/[year]/[month]/[day]/[articleSlug]/page.tsx +8 -1
- package/app/[year]/[month]/[day]/edition-route-page.tsx +2 -0
- package/app/[year]/[month]/[day]/page/[pageNumber]/page.tsx +6 -1
- package/app/[year]/[month]/[day]/page.tsx +1 -1
- package/app/[year]/[month]/[day]/section/[sectionKey]/page.tsx +6 -1
- package/app/api/archive/editions/route.ts +1 -1
- package/app/api/auth/prospectus/callback/route.ts +9 -0
- package/app/api/auth/prospectus/selection/route.ts +9 -0
- package/app/api/auth/prospectus/session/route.ts +9 -0
- package/app/api/auth/prospectus/sign-in/route.ts +9 -0
- package/app/api/auth/prospectus/sign-out/route.ts +9 -0
- package/app/api/cyclotron/priming-proof/route.ts +11 -0
- package/app/api/media/[...path]/route.ts +5 -16
- package/app/api/revalidate/route.ts +15 -7
- package/app/archive/page.tsx +1 -1
- package/app/articles/[slug]/page.tsx +7 -5
- package/app/blog-rhythm.css +278 -0
- package/app/cyclotron/page.tsx +13 -0
- package/app/globals.css +2 -0
- package/app/layout.tsx +16 -0
- package/app/newsroom/[section]/[...selection]/page.tsx +9 -6
- package/app/newsroom/[section]/page.tsx +6 -3
- package/app/newsroom/layout.tsx +4 -9
- package/app/newsroom/settings/page.tsx +6 -0
- package/app/newsroom/work/page.tsx +10 -0
- package/app/page.tsx +22 -8
- package/app/robots.ts +2 -5
- package/app/tailwind.css +1 -6
- package/components/amplify-client-provider.tsx +5 -22
- package/components/archive-shell.tsx +2 -22
- package/components/article-video.tsx +152 -5
- package/components/blog-page-background.tsx +23 -6
- package/components/news-desk-page.tsx +4 -15
- package/components/news-desk-taxonomy-client.ts +90 -3
- package/components/newspaper.tsx +6 -2
- package/components/newsroom-access-gate.tsx +2 -1
- package/components/newsroom-board-view.tsx +99 -0
- package/components/newsroom-client-shell.tsx +12 -0
- package/components/newsroom-curation-provider.tsx +17 -0
- package/components/newsroom-fonts/playfair-display-700.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-900.ttf +0 -0
- package/components/newsroom-fonts/playfair-display-OFL.txt +93 -0
- package/components/newsroom-fonts/plus-jakarta-sans-400.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-600.ttf +0 -0
- package/components/newsroom-fonts/plus-jakarta-sans-OFL.txt +93 -0
- package/components/newsroom-newsprint-board.css +48 -0
- package/components/newsroom-ops-overview.tsx +28 -11
- package/components/newsroom-ops-shell.tsx +7 -1
- package/components/newsroom-organizer-shell.tsx +130 -0
- package/components/newsroom-organizer.css +63 -0
- package/components/newsroom-references-view.tsx +92 -8
- package/components/papyrus-console-shell.tsx +4 -2
- package/components/pictogram-figure.tsx +9 -2
- package/components/presentation-header.tsx +135 -0
- package/components/presentation-rhythm-hrule.tsx +17 -0
- package/components/priming-proof-page.tsx +94 -0
- package/components/publication-cyclotron-panel.tsx +57 -0
- package/components/reader-auth-control.tsx +8 -5
- package/components/reference-curation-controls.tsx +77 -0
- package/components/reference-cyclotron-status.tsx +109 -0
- package/components/reference-priming.tsx +18 -0
- package/components/reference-relevance-review.tsx +75 -0
- package/components/settings-page.tsx +2 -1
- package/components/signup-entries-panel.tsx +94 -0
- package/components/topic-steering-workspace.tsx +78 -8
- package/components/use-blog-header-obstacles.ts +81 -0
- package/components/use-brand-fonts-ready.ts +28 -0
- package/components/use-near-viewport.ts +30 -0
- package/components/use-rhythm-overlay.ts +29 -0
- package/infra/amplify-app-shell.js +166 -27
- package/infra/amplify-app-shell.ts +180 -31
- package/infra/build-specs.js +23 -7
- package/infra/build-specs.ts +27 -10
- package/infra/site-config.js +248 -36
- package/infra/site-config.ts +313 -51
- package/lib/amplify-outputs-path.ts +5 -1
- package/lib/amplify-server-runtime.ts +36 -1
- package/lib/approved-priming-proof-reports.ts +6 -0
- package/lib/blog-feature-solver.ts +459 -0
- package/lib/blog-rhythm.ts +134 -0
- package/lib/brand-fonts-watch.ts +43 -0
- package/lib/brand-fonts.ts +112 -0
- package/lib/cached-content-repository.ts +12 -0
- package/lib/category-repository.ts +43 -93
- package/lib/content-item-lifecycle.ts +55 -0
- package/lib/content-repository.ts +3 -0
- package/lib/content-types.ts +4 -0
- package/lib/curation-client.ts +61 -0
- package/lib/edition-routes.ts +31 -29
- package/lib/graphql-content-repository.ts +56 -22
- package/lib/media-route-response.ts +45 -0
- package/lib/newspaper-layout.ts +3 -2
- package/lib/newsroom-base-path.ts +12 -3
- package/lib/newsroom-board.ts +99 -0
- package/lib/newsroom-category-drilldown.ts +3 -2
- package/lib/newsroom-forum-routes.ts +6 -4
- package/lib/newsroom-index-filters.ts +14 -10
- package/lib/newsroom-nav.ts +3 -1
- package/lib/newsroom-organizer-nav.ts +37 -0
- package/lib/newsroom-section-routing.ts +20 -0
- package/lib/oauth-redirect-priority.ts +40 -0
- package/lib/papyrus-web-locations.ts +7 -6
- package/lib/plugin-settings.ts +11 -0
- package/lib/presentation-footer.ts +3 -2
- package/lib/pretext-layout.ts +12 -3
- package/lib/priming-proof-report.ts +90 -0
- package/lib/prospectus-publication-access.ts +53 -0
- package/lib/prospectus-session.ts +196 -0
- package/lib/prospectus-sign-in-routes.ts +284 -0
- package/lib/prospectus-site-identity.ts +303 -0
- package/lib/publication-cyclotron.ts +74 -0
- package/lib/rate-limit-retry.ts +55 -0
- package/lib/reader-base-path.ts +24 -0
- package/lib/reader-cache-lifetime.ts +5 -0
- package/lib/reader-static-params.ts +85 -4
- package/lib/reader-storage-url.ts +9 -12
- package/lib/reference-policy.ts +5 -0
- package/lib/relevance-decisions.ts +103 -0
- package/lib/relevance-status.ts +63 -0
- package/lib/renderer.ts +7 -0
- package/lib/revalidate-prewarm.ts +53 -0
- package/lib/revalidate-secret.ts +59 -0
- package/lib/robots-policy.ts +14 -0
- package/lib/root-route-redirect.ts +16 -0
- package/lib/semantic-graph.ts +7 -0
- package/lib/shared-credentials-provider.ts +61 -0
- package/lib/signup-csv.ts +24 -0
- package/lib/site-brand.ts +59 -0
- package/lib/staging-gated-path.ts +23 -0
- package/lib/staging-preview-object.ts +21 -5
- package/lib/test-editor-override.ts +4 -0
- package/lib/video-loading.ts +22 -0
- package/lib/video-script.ts +14 -0
- package/middleware.ts +11 -19
- package/package.json +73 -8
- package/renderers/pretext/article-page.tsx +39 -3
- package/renderers/pretext/presentation-shared.tsx +113 -0
- package/renderers/pretext/presentation-shell.tsx +15 -102
- package/renderers/pretext/rhythm-article-page.tsx +165 -0
- package/renderers/pretext/rhythm-blog-presentation.tsx +279 -0
- package/renderers/pretext/rhythm-shell-style.ts +35 -0
- package/routes.manifest.json +123 -12
- package/src/sync.mjs +30 -6
- package/src/with-papyrus.d.mts +9 -0
- package/src/with-papyrus.mjs +48 -3
- package/amplify/functions/graphql-jwt-authorizer/handler.ts +0 -193
- package/amplify/functions/graphql-jwt-authorizer/resource.js +0 -14
- package/amplify/functions/graphql-jwt-authorizer/resource.ts +0 -15
- package/components/ai-elements/message.tsx +0 -360
package/src/with-papyrus.mjs
CHANGED
|
@@ -4,11 +4,14 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Wrap a publication repo's Next config for Papyrus (SPIKE, PPY-82be6c).
|
|
7
|
-
* - transpile the package
|
|
7
|
+
* - transpile the package and the dependencies that ship TypeScript source (cyclotron),
|
|
8
8
|
* - alias `papyrus-site` -> ./papyrus.config.ts,
|
|
9
9
|
* `papyrus-amplify-outputs` -> ./amplify_outputs.json and
|
|
10
10
|
* `papyrus-site-theme` -> ./publication/theme.css when it exists, else the shipped
|
|
11
11
|
* empty lib/empty-theme.css (both webpack and turbopack),
|
|
12
|
+
* - trace the corpora/ YAML and amplify_outputs.json that the reader reads via fs at runtime,
|
|
13
|
+
* - cap static generation at 2 workers x 3 pages (override with PAPYRUS_STATIC_GENERATION_WORKERS / _CONCURRENCY) and retry
|
|
14
|
+
* failed pages 3 times, so pre-building the reader pages stays under Cognito guest-credential throttles,
|
|
12
15
|
* - carry Papyrus's own server-external packages and image settings.
|
|
13
16
|
*/
|
|
14
17
|
const SERVER_EXTERNAL = [
|
|
@@ -22,6 +25,46 @@ const SERVER_EXTERNAL = [
|
|
|
22
25
|
"@aws-amplify/data-schema",
|
|
23
26
|
];
|
|
24
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Next sends `stale-while-revalidate` = `expireTime` - `revalidate` (one year by default), so a shared cache may serve a
|
|
30
|
+
* page for a year past its `s-maxage` while it refreshes in the background. Twice the reader's 60 s page lifetime
|
|
31
|
+
* (lib/reader-cache-lifetime.ts) bounds that to 60 s.
|
|
32
|
+
*/
|
|
33
|
+
export const READER_EXPIRE_TIME_SECONDS = 120;
|
|
34
|
+
|
|
35
|
+
export const DEFAULT_STATIC_GENERATION_WORKERS = 2;
|
|
36
|
+
export const DEFAULT_STATIC_GENERATION_CONCURRENCY = 3;
|
|
37
|
+
export const DEFAULT_STATIC_GENERATION_RETRY_COUNT = 3;
|
|
38
|
+
|
|
39
|
+
function positiveIntegerFromEnvironment(name) {
|
|
40
|
+
const parsed = Number.parseInt(process.env[name] ?? "", 10);
|
|
41
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function staticGenerationSettings() {
|
|
45
|
+
return {
|
|
46
|
+
cpus: positiveIntegerFromEnvironment("PAPYRUS_STATIC_GENERATION_WORKERS") ?? DEFAULT_STATIC_GENERATION_WORKERS,
|
|
47
|
+
staticGenerationMaxConcurrency:
|
|
48
|
+
positiveIntegerFromEnvironment("PAPYRUS_STATIC_GENERATION_CONCURRENCY") ?? DEFAULT_STATIC_GENERATION_CONCURRENCY,
|
|
49
|
+
staticGenerationRetryCount: DEFAULT_STATIC_GENERATION_RETRY_COUNT,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const DEFAULT_TRANSPILE_PACKAGES = ["@anthusai/papyrus", "cyclotron"];
|
|
54
|
+
|
|
55
|
+
export function mergeTranspilePackages(existing = []) {
|
|
56
|
+
return [...new Set([...existing, ...DEFAULT_TRANSPILE_PACKAGES])];
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const RUNTIME_TRACED_FILES = ["./corpora/**/*", "./amplify_outputs.json"];
|
|
60
|
+
|
|
61
|
+
export function mergeOutputFileTracingIncludes(existing = {}) {
|
|
62
|
+
const merged = { ...existing };
|
|
63
|
+
const current = merged["/**"] ?? [];
|
|
64
|
+
merged["/**"] = [...new Set([...current, ...RUNTIME_TRACED_FILES])];
|
|
65
|
+
return merged;
|
|
66
|
+
}
|
|
67
|
+
|
|
25
68
|
const EMPTY_THEME = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../lib/empty-theme.css");
|
|
26
69
|
|
|
27
70
|
export function withPapyrus(nextConfig = {}, options = {}) {
|
|
@@ -38,11 +81,13 @@ export function withPapyrus(nextConfig = {}, options = {}) {
|
|
|
38
81
|
};
|
|
39
82
|
return {
|
|
40
83
|
distDir: process.env.NEXT_DIST_DIR || ".next",
|
|
84
|
+
expireTime: READER_EXPIRE_TIME_SECONDS,
|
|
41
85
|
...nextConfig,
|
|
42
|
-
transpilePackages:
|
|
86
|
+
transpilePackages: mergeTranspilePackages(nextConfig.transpilePackages),
|
|
43
87
|
serverExternalPackages: [...new Set([...(nextConfig.serverExternalPackages ?? []), ...SERVER_EXTERNAL])],
|
|
88
|
+
outputFileTracingIncludes: mergeOutputFileTracingIncludes(nextConfig.outputFileTracingIncludes),
|
|
44
89
|
images: nextConfig.images ?? { remotePatterns: [{ protocol: "https", hostname: "**" }] },
|
|
45
|
-
experimental: { devtoolSegmentExplorer: false, ...nextConfig.experimental },
|
|
90
|
+
experimental: { devtoolSegmentExplorer: false, ...staticGenerationSettings(), ...nextConfig.experimental },
|
|
46
91
|
turbopack: {
|
|
47
92
|
...nextConfig.turbopack,
|
|
48
93
|
resolveAlias: {
|
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
import { createHmac, timingSafeEqual } from "node:crypto";
|
|
2
|
-
import { GetParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
3
|
-
|
|
4
|
-
type AppSyncAuthorizerEvent = {
|
|
5
|
-
authorizationToken?: string;
|
|
6
|
-
};
|
|
7
|
-
|
|
8
|
-
type JwtHeader = {
|
|
9
|
-
alg?: string;
|
|
10
|
-
typ?: string;
|
|
11
|
-
};
|
|
12
|
-
|
|
13
|
-
type JwtClaims = {
|
|
14
|
-
iss?: string;
|
|
15
|
-
sub?: string;
|
|
16
|
-
aud?: string | string[];
|
|
17
|
-
exp?: number;
|
|
18
|
-
nbf?: number;
|
|
19
|
-
iat?: number;
|
|
20
|
-
scope?: string;
|
|
21
|
-
scp?: string | string[];
|
|
22
|
-
groups?: string | string[];
|
|
23
|
-
roles?: string | string[];
|
|
24
|
-
[key: string]: unknown;
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
export const handler = async (event: AppSyncAuthorizerEvent) => {
|
|
28
|
-
try {
|
|
29
|
-
const token = normalizeAuthorizationToken(event.authorizationToken);
|
|
30
|
-
const jwtSecret = await getJwtSecret();
|
|
31
|
-
const claims = verifyHs256Jwt(token, jwtSecret);
|
|
32
|
-
assertExpectedIssuer(claims);
|
|
33
|
-
assertExpectedAudience(claims);
|
|
34
|
-
assertRequiredScope(claims);
|
|
35
|
-
|
|
36
|
-
return {
|
|
37
|
-
isAuthorized: true,
|
|
38
|
-
resolverContext: {
|
|
39
|
-
sub: claims.sub ?? "unknown",
|
|
40
|
-
iss: claims.iss ?? "unknown",
|
|
41
|
-
scope: getClaimValues(claims.scope).join(" "),
|
|
42
|
-
},
|
|
43
|
-
ttlOverride: 300,
|
|
44
|
-
};
|
|
45
|
-
} catch (error) {
|
|
46
|
-
console.warn(`JWT authorizer rejected request: ${(error as Error).message}`);
|
|
47
|
-
return {
|
|
48
|
-
isAuthorized: false,
|
|
49
|
-
ttlOverride: 0,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
function normalizeAuthorizationToken(value: string | undefined): string {
|
|
55
|
-
const token = value
|
|
56
|
-
?.replace(/^PapyrusJwt\s+/i, "")
|
|
57
|
-
.replace(/^papyrus-jwt:/i, "")
|
|
58
|
-
.replace(/^Bearer\s+/i, "")
|
|
59
|
-
.trim();
|
|
60
|
-
if (!token) throw new Error("Missing Authorization token.");
|
|
61
|
-
return token;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function verifyHs256Jwt(token: string, jwtSecret: string): JwtClaims {
|
|
65
|
-
const [encodedHeader, encodedPayload, encodedSignature] = token.split(".");
|
|
66
|
-
if (!encodedHeader || !encodedPayload || !encodedSignature) {
|
|
67
|
-
throw new Error("JWT must have header, payload, and signature.");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const header = decodeJson<JwtHeader>(encodedHeader);
|
|
71
|
-
if (header.alg !== "HS256") {
|
|
72
|
-
throw new Error(`Unsupported JWT alg ${header.alg ?? "unknown"}.`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const claims = decodeJson<JwtClaims>(encodedPayload);
|
|
76
|
-
const expectedSignature = createHmac("sha256", jwtSecret)
|
|
77
|
-
.update(`${encodedHeader}.${encodedPayload}`)
|
|
78
|
-
.digest("base64url");
|
|
79
|
-
|
|
80
|
-
if (!constantTimeEqual(encodedSignature, expectedSignature)) {
|
|
81
|
-
throw new Error("JWT signature verification failed.");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const now = Math.floor(Date.now() / 1000);
|
|
85
|
-
if (typeof claims.exp === "number" && claims.exp <= now) {
|
|
86
|
-
throw new Error("JWT is expired.");
|
|
87
|
-
}
|
|
88
|
-
if (typeof claims.nbf === "number" && claims.nbf > now) {
|
|
89
|
-
throw new Error("JWT is not yet valid.");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
return claims;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function decodeJson<T>(encoded: string): T {
|
|
96
|
-
try {
|
|
97
|
-
return JSON.parse(Buffer.from(encoded, "base64url").toString("utf8")) as T;
|
|
98
|
-
} catch {
|
|
99
|
-
throw new Error("JWT contains invalid base64url JSON.");
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
let cachedJwtSecret: string | null = null;
|
|
104
|
-
let ssmClient: SSMClient | null = null;
|
|
105
|
-
|
|
106
|
-
async function getJwtSecret(): Promise<string> {
|
|
107
|
-
if (cachedJwtSecret) return cachedJwtSecret;
|
|
108
|
-
|
|
109
|
-
const directSecret = normalizeOptionalString(process.env.PAPYRUS_JWT_SECRET);
|
|
110
|
-
if (directSecret && !isAmplifySecretPlaceholder(directSecret)) {
|
|
111
|
-
cachedJwtSecret = directSecret;
|
|
112
|
-
return directSecret;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const parameterName = resolveAmplifySsmSecretPath("PAPYRUS_JWT_SECRET");
|
|
116
|
-
if (!parameterName) throw new Error("PAPYRUS_JWT_SECRET is not configured.");
|
|
117
|
-
ssmClient ??= new SSMClient({});
|
|
118
|
-
const response = await ssmClient.send(new GetParameterCommand({
|
|
119
|
-
Name: parameterName,
|
|
120
|
-
WithDecryption: true,
|
|
121
|
-
}));
|
|
122
|
-
const secret = normalizeOptionalString(response.Parameter?.Value);
|
|
123
|
-
if (!secret) throw new Error(`SSM parameter ${parameterName} did not return a value.`);
|
|
124
|
-
cachedJwtSecret = secret;
|
|
125
|
-
return secret;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function isAmplifySecretPlaceholder(value: string): boolean {
|
|
129
|
-
return /^<.*will be resolved.*>$/i.test(value);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function resolveAmplifySsmSecretPath(name: string): string | null {
|
|
133
|
-
const rawConfig = normalizeOptionalString(process.env.AMPLIFY_SSM_ENV_CONFIG);
|
|
134
|
-
if (!rawConfig) return null;
|
|
135
|
-
try {
|
|
136
|
-
const config = JSON.parse(rawConfig) as Record<string, { path?: string; sharedPath?: string } | undefined>;
|
|
137
|
-
return normalizeOptionalString(config[name]?.path) ?? normalizeOptionalString(config[name]?.sharedPath);
|
|
138
|
-
} catch {
|
|
139
|
-
throw new Error("AMPLIFY_SSM_ENV_CONFIG contains invalid JSON.");
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function normalizeOptionalString(value: unknown): string | null {
|
|
144
|
-
if (typeof value !== "string") return null;
|
|
145
|
-
const trimmed = value.trim();
|
|
146
|
-
return trimmed ? trimmed : null;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function assertExpectedIssuer(claims: JwtClaims): void {
|
|
150
|
-
const expectedIssuer = process.env.PAPYRUS_JWT_ISSUER;
|
|
151
|
-
if (expectedIssuer && claims.iss !== expectedIssuer) {
|
|
152
|
-
throw new Error("JWT issuer is not authorized.");
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function assertExpectedAudience(claims: JwtClaims): void {
|
|
157
|
-
const expectedAudience = process.env.PAPYRUS_JWT_AUDIENCE;
|
|
158
|
-
if (!expectedAudience) return;
|
|
159
|
-
|
|
160
|
-
const audiences = Array.isArray(claims.aud) ? claims.aud : claims.aud ? [claims.aud] : [];
|
|
161
|
-
if (!audiences.includes(expectedAudience)) {
|
|
162
|
-
throw new Error("JWT audience is not authorized.");
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function assertRequiredScope(claims: JwtClaims): void {
|
|
167
|
-
const requiredScope = process.env.PAPYRUS_JWT_REQUIRED_SCOPE;
|
|
168
|
-
if (!requiredScope) return;
|
|
169
|
-
|
|
170
|
-
const values = new Set([
|
|
171
|
-
...getClaimValues(claims.scope),
|
|
172
|
-
...getClaimValues(claims.scp),
|
|
173
|
-
...getClaimValues(claims.groups),
|
|
174
|
-
...getClaimValues(claims.roles),
|
|
175
|
-
]);
|
|
176
|
-
|
|
177
|
-
if (!values.has(requiredScope)) {
|
|
178
|
-
throw new Error("JWT does not include the required authoring scope.");
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function getClaimValues(value: unknown): string[] {
|
|
183
|
-
if (Array.isArray(value)) return value.flatMap(getClaimValues);
|
|
184
|
-
if (typeof value === "string") return value.split(/[,\s]+/).filter(Boolean);
|
|
185
|
-
return [];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
function constantTimeEqual(left: string, right: string): boolean {
|
|
189
|
-
const leftBuffer = Buffer.from(left);
|
|
190
|
-
const rightBuffer = Buffer.from(right);
|
|
191
|
-
if (leftBuffer.length !== rightBuffer.length) return false;
|
|
192
|
-
return timingSafeEqual(leftBuffer, rightBuffer);
|
|
193
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { defineFunction, secret } from "@aws-amplify/backend";
|
|
2
|
-
export const graphqlJwtAuthorizer = defineFunction({
|
|
3
|
-
name: "papyrus-graphql-jwt-authorizer",
|
|
4
|
-
entry: "./handler.ts",
|
|
5
|
-
timeoutSeconds: 5,
|
|
6
|
-
memoryMB: 256,
|
|
7
|
-
resourceGroupName: "data",
|
|
8
|
-
environment: {
|
|
9
|
-
PAPYRUS_JWT_SECRET: secret("PAPYRUS_JWT_SECRET"),
|
|
10
|
-
PAPYRUS_JWT_ISSUER: "papyrus-cli",
|
|
11
|
-
PAPYRUS_JWT_AUDIENCE: "papyrus-authoring",
|
|
12
|
-
PAPYRUS_JWT_REQUIRED_SCOPE: "papyrus:write",
|
|
13
|
-
},
|
|
14
|
-
});
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { defineFunction, secret } from "@aws-amplify/backend";
|
|
2
|
-
|
|
3
|
-
export const graphqlJwtAuthorizer = defineFunction({
|
|
4
|
-
name: "papyrus-graphql-jwt-authorizer",
|
|
5
|
-
entry: "./handler.ts",
|
|
6
|
-
timeoutSeconds: 5,
|
|
7
|
-
memoryMB: 256,
|
|
8
|
-
resourceGroupName: "data",
|
|
9
|
-
environment: {
|
|
10
|
-
PAPYRUS_JWT_SECRET: secret("PAPYRUS_JWT_SECRET"),
|
|
11
|
-
PAPYRUS_JWT_ISSUER: "papyrus-cli",
|
|
12
|
-
PAPYRUS_JWT_AUDIENCE: "papyrus-authoring",
|
|
13
|
-
PAPYRUS_JWT_REQUIRED_SCOPE: "papyrus:write",
|
|
14
|
-
},
|
|
15
|
-
});
|
|
@@ -1,360 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { Button } from "../ui/button";
|
|
4
|
-
import {
|
|
5
|
-
ButtonGroup,
|
|
6
|
-
ButtonGroupText,
|
|
7
|
-
} from "../ui/button-group";
|
|
8
|
-
import {
|
|
9
|
-
Tooltip,
|
|
10
|
-
TooltipContent,
|
|
11
|
-
TooltipProvider,
|
|
12
|
-
TooltipTrigger,
|
|
13
|
-
} from "../ui/tooltip";
|
|
14
|
-
import { cn } from "../../lib/utils";
|
|
15
|
-
import { cjk } from "@streamdown/cjk";
|
|
16
|
-
import { code } from "@streamdown/code";
|
|
17
|
-
import { math } from "@streamdown/math";
|
|
18
|
-
import { mermaid } from "@streamdown/mermaid";
|
|
19
|
-
import type { UIMessage } from "ai";
|
|
20
|
-
import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react";
|
|
21
|
-
import type { ComponentProps, HTMLAttributes, ReactElement } from "react";
|
|
22
|
-
import {
|
|
23
|
-
createContext,
|
|
24
|
-
memo,
|
|
25
|
-
useCallback,
|
|
26
|
-
useContext,
|
|
27
|
-
useEffect,
|
|
28
|
-
useMemo,
|
|
29
|
-
useState,
|
|
30
|
-
} from "react";
|
|
31
|
-
import { Streamdown } from "streamdown";
|
|
32
|
-
|
|
33
|
-
export type MessageProps = HTMLAttributes<HTMLDivElement> & {
|
|
34
|
-
from: UIMessage["role"];
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const Message = ({ className, from, ...props }: MessageProps) => (
|
|
38
|
-
<div
|
|
39
|
-
className={cn(
|
|
40
|
-
"group flex w-full max-w-[95%] flex-col gap-2",
|
|
41
|
-
from === "user" ? "is-user ml-auto justify-end" : "is-assistant",
|
|
42
|
-
className
|
|
43
|
-
)}
|
|
44
|
-
{...props}
|
|
45
|
-
/>
|
|
46
|
-
);
|
|
47
|
-
|
|
48
|
-
export type MessageContentProps = HTMLAttributes<HTMLDivElement>;
|
|
49
|
-
|
|
50
|
-
export const MessageContent = ({
|
|
51
|
-
children,
|
|
52
|
-
className,
|
|
53
|
-
...props
|
|
54
|
-
}: MessageContentProps) => (
|
|
55
|
-
<div
|
|
56
|
-
className={cn(
|
|
57
|
-
"is-user:dark flex w-fit min-w-0 max-w-full flex-col gap-2 overflow-hidden text-sm",
|
|
58
|
-
"group-[.is-user]:ml-auto group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
|
|
59
|
-
"group-[.is-assistant]:text-foreground",
|
|
60
|
-
className
|
|
61
|
-
)}
|
|
62
|
-
{...props}
|
|
63
|
-
>
|
|
64
|
-
{children}
|
|
65
|
-
</div>
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
export type MessageActionsProps = ComponentProps<"div">;
|
|
69
|
-
|
|
70
|
-
export const MessageActions = ({
|
|
71
|
-
className,
|
|
72
|
-
children,
|
|
73
|
-
...props
|
|
74
|
-
}: MessageActionsProps) => (
|
|
75
|
-
<div className={cn("flex items-center gap-1", className)} {...props}>
|
|
76
|
-
{children}
|
|
77
|
-
</div>
|
|
78
|
-
);
|
|
79
|
-
|
|
80
|
-
export type MessageActionProps = ComponentProps<typeof Button> & {
|
|
81
|
-
tooltip?: string;
|
|
82
|
-
label?: string;
|
|
83
|
-
};
|
|
84
|
-
|
|
85
|
-
export const MessageAction = ({
|
|
86
|
-
tooltip,
|
|
87
|
-
children,
|
|
88
|
-
label,
|
|
89
|
-
variant = "ghost",
|
|
90
|
-
size = "icon-sm",
|
|
91
|
-
...props
|
|
92
|
-
}: MessageActionProps) => {
|
|
93
|
-
const button = (
|
|
94
|
-
<Button size={size} type="button" variant={variant} {...props}>
|
|
95
|
-
{children}
|
|
96
|
-
<span className="sr-only">{label || tooltip}</span>
|
|
97
|
-
</Button>
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
if (tooltip) {
|
|
101
|
-
return (
|
|
102
|
-
<TooltipProvider>
|
|
103
|
-
<Tooltip>
|
|
104
|
-
<TooltipTrigger>{button}</TooltipTrigger>
|
|
105
|
-
<TooltipContent>
|
|
106
|
-
<p>{tooltip}</p>
|
|
107
|
-
</TooltipContent>
|
|
108
|
-
</Tooltip>
|
|
109
|
-
</TooltipProvider>
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
return button;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
interface MessageBranchContextType {
|
|
117
|
-
currentBranch: number;
|
|
118
|
-
totalBranches: number;
|
|
119
|
-
goToPrevious: () => void;
|
|
120
|
-
goToNext: () => void;
|
|
121
|
-
branches: ReactElement[];
|
|
122
|
-
setBranches: (branches: ReactElement[]) => void;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const MessageBranchContext = createContext<MessageBranchContextType | null>(
|
|
126
|
-
null
|
|
127
|
-
);
|
|
128
|
-
|
|
129
|
-
const useMessageBranch = () => {
|
|
130
|
-
const context = useContext(MessageBranchContext);
|
|
131
|
-
|
|
132
|
-
if (!context) {
|
|
133
|
-
throw new Error(
|
|
134
|
-
"MessageBranch components must be used within MessageBranch"
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
return context;
|
|
139
|
-
};
|
|
140
|
-
|
|
141
|
-
export type MessageBranchProps = HTMLAttributes<HTMLDivElement> & {
|
|
142
|
-
defaultBranch?: number;
|
|
143
|
-
onBranchChange?: (branchIndex: number) => void;
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
export const MessageBranch = ({
|
|
147
|
-
defaultBranch = 0,
|
|
148
|
-
onBranchChange,
|
|
149
|
-
className,
|
|
150
|
-
...props
|
|
151
|
-
}: MessageBranchProps) => {
|
|
152
|
-
const [currentBranch, setCurrentBranch] = useState(defaultBranch);
|
|
153
|
-
const [branches, setBranches] = useState<ReactElement[]>([]);
|
|
154
|
-
|
|
155
|
-
const handleBranchChange = useCallback(
|
|
156
|
-
(newBranch: number) => {
|
|
157
|
-
setCurrentBranch(newBranch);
|
|
158
|
-
onBranchChange?.(newBranch);
|
|
159
|
-
},
|
|
160
|
-
[onBranchChange]
|
|
161
|
-
);
|
|
162
|
-
|
|
163
|
-
const goToPrevious = useCallback(() => {
|
|
164
|
-
const newBranch =
|
|
165
|
-
currentBranch > 0 ? currentBranch - 1 : branches.length - 1;
|
|
166
|
-
handleBranchChange(newBranch);
|
|
167
|
-
}, [currentBranch, branches.length, handleBranchChange]);
|
|
168
|
-
|
|
169
|
-
const goToNext = useCallback(() => {
|
|
170
|
-
const newBranch =
|
|
171
|
-
currentBranch < branches.length - 1 ? currentBranch + 1 : 0;
|
|
172
|
-
handleBranchChange(newBranch);
|
|
173
|
-
}, [currentBranch, branches.length, handleBranchChange]);
|
|
174
|
-
|
|
175
|
-
const contextValue = useMemo<MessageBranchContextType>(
|
|
176
|
-
() => ({
|
|
177
|
-
branches,
|
|
178
|
-
currentBranch,
|
|
179
|
-
goToNext,
|
|
180
|
-
goToPrevious,
|
|
181
|
-
setBranches,
|
|
182
|
-
totalBranches: branches.length,
|
|
183
|
-
}),
|
|
184
|
-
[branches, currentBranch, goToNext, goToPrevious]
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
return (
|
|
188
|
-
<MessageBranchContext.Provider value={contextValue}>
|
|
189
|
-
<div
|
|
190
|
-
className={cn("grid w-full gap-2 [&>div]:pb-0", className)}
|
|
191
|
-
{...props}
|
|
192
|
-
/>
|
|
193
|
-
</MessageBranchContext.Provider>
|
|
194
|
-
);
|
|
195
|
-
};
|
|
196
|
-
|
|
197
|
-
export type MessageBranchContentProps = HTMLAttributes<HTMLDivElement>;
|
|
198
|
-
|
|
199
|
-
export const MessageBranchContent = ({
|
|
200
|
-
children,
|
|
201
|
-
...props
|
|
202
|
-
}: MessageBranchContentProps) => {
|
|
203
|
-
const { currentBranch, setBranches, branches } = useMessageBranch();
|
|
204
|
-
const childrenArray = useMemo(
|
|
205
|
-
() => (Array.isArray(children) ? children : [children]),
|
|
206
|
-
[children]
|
|
207
|
-
);
|
|
208
|
-
|
|
209
|
-
// Use useEffect to update branches when they change
|
|
210
|
-
useEffect(() => {
|
|
211
|
-
if (branches.length !== childrenArray.length) {
|
|
212
|
-
setBranches(childrenArray);
|
|
213
|
-
}
|
|
214
|
-
}, [childrenArray, branches, setBranches]);
|
|
215
|
-
|
|
216
|
-
return childrenArray.map((branch, index) => (
|
|
217
|
-
<div
|
|
218
|
-
className={cn(
|
|
219
|
-
"grid gap-2 overflow-hidden [&>div]:pb-0",
|
|
220
|
-
index === currentBranch ? "block" : "hidden"
|
|
221
|
-
)}
|
|
222
|
-
key={branch.key}
|
|
223
|
-
{...props}
|
|
224
|
-
>
|
|
225
|
-
{branch}
|
|
226
|
-
</div>
|
|
227
|
-
));
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
export type MessageBranchSelectorProps = ComponentProps<typeof ButtonGroup>;
|
|
231
|
-
|
|
232
|
-
export const MessageBranchSelector = ({
|
|
233
|
-
className,
|
|
234
|
-
...props
|
|
235
|
-
}: MessageBranchSelectorProps) => {
|
|
236
|
-
const { totalBranches } = useMessageBranch();
|
|
237
|
-
|
|
238
|
-
// Don't render if there's only one branch
|
|
239
|
-
if (totalBranches <= 1) {
|
|
240
|
-
return null;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
return (
|
|
244
|
-
<ButtonGroup
|
|
245
|
-
className={cn(
|
|
246
|
-
"[&>*:not(:first-child)]:rounded-l-md [&>*:not(:last-child)]:rounded-r-md",
|
|
247
|
-
className
|
|
248
|
-
)}
|
|
249
|
-
orientation="horizontal"
|
|
250
|
-
{...props}
|
|
251
|
-
/>
|
|
252
|
-
);
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
export type MessageBranchPreviousProps = ComponentProps<typeof Button>;
|
|
256
|
-
|
|
257
|
-
export const MessageBranchPrevious = ({
|
|
258
|
-
children,
|
|
259
|
-
...props
|
|
260
|
-
}: MessageBranchPreviousProps) => {
|
|
261
|
-
const { goToPrevious, totalBranches } = useMessageBranch();
|
|
262
|
-
|
|
263
|
-
return (
|
|
264
|
-
<Button
|
|
265
|
-
aria-label="Previous branch"
|
|
266
|
-
disabled={totalBranches <= 1}
|
|
267
|
-
onClick={goToPrevious}
|
|
268
|
-
size="icon-sm"
|
|
269
|
-
type="button"
|
|
270
|
-
variant="ghost"
|
|
271
|
-
{...props}
|
|
272
|
-
>
|
|
273
|
-
{children ?? <ChevronLeftIcon size={14} />}
|
|
274
|
-
</Button>
|
|
275
|
-
);
|
|
276
|
-
};
|
|
277
|
-
|
|
278
|
-
export type MessageBranchNextProps = ComponentProps<typeof Button>;
|
|
279
|
-
|
|
280
|
-
export const MessageBranchNext = ({
|
|
281
|
-
children,
|
|
282
|
-
...props
|
|
283
|
-
}: MessageBranchNextProps) => {
|
|
284
|
-
const { goToNext, totalBranches } = useMessageBranch();
|
|
285
|
-
|
|
286
|
-
return (
|
|
287
|
-
<Button
|
|
288
|
-
aria-label="Next branch"
|
|
289
|
-
disabled={totalBranches <= 1}
|
|
290
|
-
onClick={goToNext}
|
|
291
|
-
size="icon-sm"
|
|
292
|
-
type="button"
|
|
293
|
-
variant="ghost"
|
|
294
|
-
{...props}
|
|
295
|
-
>
|
|
296
|
-
{children ?? <ChevronRightIcon size={14} />}
|
|
297
|
-
</Button>
|
|
298
|
-
);
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
export type MessageBranchPageProps = HTMLAttributes<HTMLSpanElement>;
|
|
302
|
-
|
|
303
|
-
export const MessageBranchPage = ({
|
|
304
|
-
className,
|
|
305
|
-
...props
|
|
306
|
-
}: MessageBranchPageProps) => {
|
|
307
|
-
const { currentBranch, totalBranches } = useMessageBranch();
|
|
308
|
-
|
|
309
|
-
return (
|
|
310
|
-
<ButtonGroupText
|
|
311
|
-
className={cn(
|
|
312
|
-
"border-none bg-transparent text-muted-foreground shadow-none",
|
|
313
|
-
className
|
|
314
|
-
)}
|
|
315
|
-
{...props}
|
|
316
|
-
>
|
|
317
|
-
{currentBranch + 1} of {totalBranches}
|
|
318
|
-
</ButtonGroupText>
|
|
319
|
-
);
|
|
320
|
-
};
|
|
321
|
-
|
|
322
|
-
export type MessageResponseProps = ComponentProps<typeof Streamdown>;
|
|
323
|
-
|
|
324
|
-
const streamdownPlugins = { cjk, code, math, mermaid };
|
|
325
|
-
|
|
326
|
-
export const MessageResponse = memo(
|
|
327
|
-
({ className, ...props }: MessageResponseProps) => (
|
|
328
|
-
<Streamdown
|
|
329
|
-
className={cn(
|
|
330
|
-
"size-full [&>*:first-child]:mt-0 [&>*:last-child]:mb-0",
|
|
331
|
-
className
|
|
332
|
-
)}
|
|
333
|
-
plugins={streamdownPlugins}
|
|
334
|
-
{...props}
|
|
335
|
-
/>
|
|
336
|
-
),
|
|
337
|
-
(prevProps, nextProps) =>
|
|
338
|
-
prevProps.children === nextProps.children &&
|
|
339
|
-
nextProps.isAnimating === prevProps.isAnimating
|
|
340
|
-
);
|
|
341
|
-
|
|
342
|
-
MessageResponse.displayName = "MessageResponse";
|
|
343
|
-
|
|
344
|
-
export type MessageToolbarProps = ComponentProps<"div">;
|
|
345
|
-
|
|
346
|
-
export const MessageToolbar = ({
|
|
347
|
-
className,
|
|
348
|
-
children,
|
|
349
|
-
...props
|
|
350
|
-
}: MessageToolbarProps) => (
|
|
351
|
-
<div
|
|
352
|
-
className={cn(
|
|
353
|
-
"mt-4 flex w-full items-center justify-between gap-4",
|
|
354
|
-
className
|
|
355
|
-
)}
|
|
356
|
-
{...props}
|
|
357
|
-
>
|
|
358
|
-
{children}
|
|
359
|
-
</div>
|
|
360
|
-
);
|