Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ RUN bun run build

FROM nginx:alpine-slim AS prod

COPY nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /app/builder/dist/watch-list/browser /usr/share/nginx/html

EXPOSE 80
8 changes: 8 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,14 @@
},
"configurations": {
"production": {
"optimization": {
"scripts": true,
"styles": {
"minify": true,
"inlineCritical": false
},
"fonts": true
},
"budgets": [
{
"type": "initial",
Expand Down
31 changes: 31 additions & 0 deletions nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Production server for the Docker image (copied to
# /etc/nginx/conf.d/default.conf in the Dockerfile).
#
# The Content-Security-Policy here must stay in sync with the <meta> fallback
# in src/index.html (GitHub Pages cannot send custom HTTP headers, so the meta
# tag is its only enforcement). If the inline theme script in index.html
# changes, recompute its sha256 and update script-src in both places.
#
# Policy notes:
# - img-src allows blob:/data:/https: (IndexedDB posters via object URLs,
# inline SVG placeholder, TMDB/MyAnimeList/AniList CDN posters).
# - connect-src enumerates the API + image origins the app fetches from
# (poster save uses fetch() on image CDN URLs, so those need connect-src).
# - style-src 'unsafe-inline' is required: Angular injects component styles
# at runtime. No external stylesheets are used.
server {
listen 80;
server_name _;

root /usr/share/nginx/html;
index index.html;

add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'; script-src 'self' 'sha256-MEU5LmGgErYBNZ8qIkvEKZWSLfoRWEtbAbW+YA5ToCE='; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data: https:; connect-src 'self' https://api.themoviedb.org https://api.jikan.moe https://graphql.anilist.co https://image.tmdb.org https://cdn.myanimelist.net https://s4.anilist.co; font-src 'self' data:; manifest-src 'self'; worker-src 'self'" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "DENY" always;
add_header Referrer-Policy "no-referrer" always;

location / {
try_files $uri $uri/ /index.html;
}
}
9 changes: 9 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
<html lang="en">
<head>
<meta charset="utf-8" />
<!-- Security headers fallback for hosts that cannot send HTTP headers (e.g. GitHub Pages).
The Docker/nginx image sends this same policy plus nosniff/DENY/no-referrer as real
headers (see nginx.conf). If the inline theme script below changes, recompute its sha256
and update script-src here and in nginx.conf. -->
<meta
http-equiv="Content-Security-Policy"
content="default-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'; script-src 'self' 'sha256-MEU5LmGgErYBNZ8qIkvEKZWSLfoRWEtbAbW+YA5ToCE='; style-src 'self' 'unsafe-inline'; img-src 'self' blob: data: https:; connect-src 'self' https://api.themoviedb.org https://api.jikan.moe https://graphql.anilist.co https://image.tmdb.org https://cdn.myanimelist.net https://s4.anilist.co; font-src 'self' data:; manifest-src 'self'; worker-src 'self'"
/>
<meta name="referrer" content="no-referrer" />
<title>WatchList</title>
<base href="/" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
Expand Down
Loading