Add the profile page, and publish it as an embeddable panel
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>
This commit is contained in:
@@ -1,5 +1,20 @@
|
||||
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: {
|
||||
@@ -11,6 +26,34 @@ const nextConfig: NextConfig = {
|
||||
},
|
||||
],
|
||||
},
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user