1dfa2c0a04
Developed in a separate session; committed here alongside the calendar work
that shares this repository's migration journal.
A person can now see their own Plex link, their Watch Now slots and their watch
history at /profile. The same panel is published in two further forms so that
accounts.sticknife.com on charon can carry it as one section of a wider
sticknife profile, next to the other services' sections.
- watch_history (0024) records what has been played, keyed on the Plex
history id so a re-sync cannot duplicate a row. Partial unique index,
because that id is null for anything entered by hand.
- plex_accounts.is_server_owner (0025) marks the one account whose viewing
the server files under local account 1 rather than under its plex.tv id.
- The embed carries its own layout, origin allowlist and a frame-height
reporter, so the host page can size it without guessing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
60 lines
2.0 KiB
TypeScript
60 lines
2.0 KiB
TypeScript
import type { NextConfig } from "next";
|
|
|
|
/**
|
|
* Who may put Ampelos in an iframe.
|
|
*
|
|
* Only /embed/* is framable, and only by the sticknife accounts page. The rest
|
|
* of the app is denied outright below -- framing the catalog or the admin views
|
|
* has no legitimate use and is how a clickjacked "Remove" button gets pressed.
|
|
*
|
|
* Configurable because the accounts host is charon's to name, not ours: a
|
|
* staging origin or a rename should not need a code change. Comma-separated,
|
|
* and note these are ORIGINS (scheme and host), which is what frame-ancestors
|
|
* takes -- a bare hostname is silently ignored by the CSP parser.
|
|
*/
|
|
const EMBED_ANCESTORS =
|
|
process.env.AMPELOS_EMBED_ANCESTORS ?? "https://accounts.sticknife.com";
|
|
|
|
const nextConfig: NextConfig = {
|
|
allowedDevOrigins: ["10.24.88.95", "ampelos.sticknife.com"],
|
|
images: {
|
|
remotePatterns: [
|
|
{
|
|
protocol: "https",
|
|
hostname: "image.tmdb.org",
|
|
pathname: "/t/p/**",
|
|
},
|
|
],
|
|
},
|
|
async headers() {
|
|
return [
|
|
{
|
|
// Everything EXCEPT the embed, framed by nobody.
|
|
//
|
|
// The exclusion is in the matcher rather than left to header override,
|
|
// because X-Frame-Options has no "allow these origins" form -- the
|
|
// multi-origin ALLOW-FROM was never implemented by any browser -- so a
|
|
// blanket rule could set CSP correctly for /embed and still stamp a
|
|
// DENY that some agent honours in preference. Not matching at all is
|
|
// the only version with no precedence question in it.
|
|
source: "/:path((?!embed/).*)",
|
|
headers: [
|
|
{ key: "Content-Security-Policy", value: "frame-ancestors 'none'" },
|
|
{ key: "X-Frame-Options", value: "DENY" },
|
|
],
|
|
},
|
|
{
|
|
source: "/embed/:path*",
|
|
headers: [
|
|
{
|
|
key: "Content-Security-Policy",
|
|
value: `frame-ancestors 'self' ${EMBED_ANCESTORS.split(",").map((o) => o.trim()).filter(Boolean).join(" ")}`,
|
|
},
|
|
],
|
|
},
|
|
];
|
|
},
|
|
};
|
|
|
|
export default nextConfig;
|