fix: Discord Activity token exchange, CSP headers, subscription routes, and static asset 404

- Remove redirect_uri from Discord token exchange (Activities use proxy auth, not redirect flow)
- Add Content-Security-Policy with frame-ancestors for Discord embedding (was only in vercel.json)
- Wire up subscription create-checkout and manage routes in Express
- Add Studio arm to ArmSwitcher with external link
- Prevent SPA catch-all from serving HTML for missing static assets (fixes script.js Unexpected token error)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
AeThex 2026-04-14 23:49:50 +00:00
parent 34368e1dde
commit f1bcc957f9
4 changed files with 37 additions and 6 deletions

View file

@ -38,9 +38,6 @@ export default async function handler(req: any, res: any) {
client_secret: clientSecret, client_secret: clientSecret,
grant_type: "authorization_code", grant_type: "authorization_code",
code, code,
redirect_uri:
process.env.DISCORD_ACTIVITY_REDIRECT_URI ||
"https://aethex.dev/activity",
}).toString(), }).toString(),
}, },
); );

View file

@ -68,9 +68,22 @@ const ARMS: Arm[] = [
textColor: "text-purple-400", textColor: "text-purple-400",
href: "/staff", href: "/staff",
}, },
{
id: "studio",
name: "AeThex | Studio",
label: "Studio",
color: "#00ffff",
bgColor: "bg-cyan-500/20",
textColor: "text-cyan-400",
href: "https://aethex.studio",
external: true,
},
]; ];
const STUDIO_SVG = `data:image/svg+xml,${encodeURIComponent('<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><rect width="512" height="512" rx="96" fill="%23050505"/><polygon points="256,48 444,152 444,360 256,464 68,360 68,152" fill="none" stroke="%2300ffff" stroke-width="18" opacity="0.9"/><text x="256" y="320" text-anchor="middle" font-family="Orbitron,monospace" font-size="220" font-weight="700" fill="%2300ffff">&#198;</text></svg>')}`;
const LOGO_URLS: Record<string, string> = { const LOGO_URLS: Record<string, string> = {
studio: STUDIO_SVG,
staff: staff:
"https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2Fc0414efd7af54ef4b821a05d469150d0?format=webp&width=800", "https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2Fc0414efd7af54ef4b821a05d469150d0?format=webp&width=800",
labs: "https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2Fd93f7113d34347469e74421c3a3412e5?format=webp&width=800", labs: "https://cdn.builder.io/api/v1/image/assets%2Ffc53d607e21d497595ac97e0637001a1%2Fd93f7113d34347469e74421c3a3412e5?format=webp&width=800",

View file

@ -41,6 +41,8 @@ import blogIndexHandler from "../api/blog/index";
import blogSlugHandler from "../api/blog/[slug]"; import blogSlugHandler from "../api/blog/[slug]";
import aiChatHandler from "../api/ai/chat"; import aiChatHandler from "../api/ai/chat";
import aiTitleHandler from "../api/ai/title"; import aiTitleHandler from "../api/ai/title";
import createCheckoutHandler from "../api/subscriptions/create-checkout";
import manageSubscriptionHandler from "../api/subscriptions/manage";
// Developer API Keys handlers // Developer API Keys handlers
import { import {
@ -340,6 +342,11 @@ export function createServer() {
app.use((req, res, next) => { app.use((req, res, next) => {
// Allow embedding in iframes (Discord Activities need this) // Allow embedding in iframes (Discord Activities need this)
res.setHeader("X-Frame-Options", "ALLOWALL"); res.setHeader("X-Frame-Options", "ALLOWALL");
// CSP with frame-ancestors for Discord Activity embedding
res.setHeader(
"Content-Security-Policy",
"default-src 'self' https: data: blob:; script-src 'self' 'unsafe-inline' 'unsafe-eval' https:; style-src 'self' 'unsafe-inline' https:; img-src 'self' data: blob: https:; font-src 'self' data: https:; connect-src 'self' https: wss:; frame-ancestors 'self' https://discord.com https://*.discord.com https://*.discordsays.com",
);
// Allow Discord to access the iframe // Allow Discord to access the iframe
res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader( res.setHeader(
@ -1815,9 +1822,6 @@ export function createServer() {
client_secret: clientSecret, client_secret: clientSecret,
grant_type: "authorization_code", grant_type: "authorization_code",
code, code,
redirect_uri:
process.env.DISCORD_ACTIVITY_REDIRECT_URI ||
"https://aethex.dev/activity",
}).toString(), }).toString(),
}, },
); );
@ -8177,5 +8181,16 @@ export function createServer() {
app.post("/api/ai/chat", aiChatHandler); app.post("/api/ai/chat", aiChatHandler);
app.post("/api/ai/title", aiTitleHandler); app.post("/api/ai/title", aiTitleHandler);
// Subscription API routes
app.post("/api/subscriptions/create-checkout", (req: express.Request, res: express.Response) => {
return createCheckoutHandler(req as any, res as any);
});
app.get("/api/subscriptions/manage", (req: express.Request, res: express.Response) => {
return manageSubscriptionHandler(req as any, res as any);
});
app.post("/api/subscriptions/manage", (req: express.Request, res: express.Response) => {
return manageSubscriptionHandler(req as any, res as any);
});
return app; return app;
} }

View file

@ -335,6 +335,12 @@ app.get("*", async (req, res) => {
return res.status(404).json({ error: "API endpoint not found" }); return res.status(404).json({ error: "API endpoint not found" });
} }
// Don't serve the SPA shell for missing static-asset requests — they should 404
// cleanly rather than returning HTML (which causes "Unexpected token '<'" JS errors).
if (/\.(js|mjs|cjs|css|map|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|webp|avif)$/i.test(req.path)) {
return res.status(404).send("Not found");
}
try { try {
const template = getTemplate(); const template = getTemplate();
const meta = await resolveRouteMeta(req.path); const meta = await resolveRouteMeta(req.path);