diff --git a/frontend/index.html b/frontend/index.html index ec04108..ec525ab 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,14 +1,17 @@ + %TITLE% - + +
+ diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 421eabb..f490a9d 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,5 +1,3 @@ -import '@mantine/core/styles.css'; - import { Alert, Box, Flex, MantineProvider, Title } from '@mantine/core'; import { useEtes } from "./useEtes"; import theme from './theme'; diff --git a/frontend/src/Commits.tsx b/frontend/src/Commits.tsx index bc22032..1ad07a5 100644 --- a/frontend/src/Commits.tsx +++ b/frontend/src/Commits.tsx @@ -25,7 +25,7 @@ export default function Commits({ state, dispatch }: CommitsProps) { Message - + {commit.message} diff --git a/frontend/src/DateTime.tsx b/frontend/src/DateTime.tsx index d2a1813..7d70382 100644 --- a/frontend/src/DateTime.tsx +++ b/frontend/src/DateTime.tsx @@ -40,7 +40,7 @@ export default function DateTime({ date }: { date: string }) { return ( - + ); } diff --git a/frontend/src/PullRequest.tsx b/frontend/src/PullRequest.tsx index 9241e34..fd0b7a6 100644 --- a/frontend/src/PullRequest.tsx +++ b/frontend/src/PullRequest.tsx @@ -10,7 +10,7 @@ interface PullRequestProps { export function PullRequest({ baseUrl, number, title }: PullRequestProps) { const badge = ( - #{number} + #{number} ); diff --git a/frontend/src/PullTable.tsx b/frontend/src/PullTable.tsx index 901a22b..9156883 100644 --- a/frontend/src/PullTable.tsx +++ b/frontend/src/PullTable.tsx @@ -99,7 +99,7 @@ export function PullTable({ state, dispatch }: PullTableProps) { > diff --git a/frontend/src/Releases.tsx b/frontend/src/Releases.tsx index 80efbcc..3561704 100644 --- a/frontend/src/Releases.tsx +++ b/frontend/src/Releases.tsx @@ -25,7 +25,7 @@ export default function Releases({ state, dispatch }: ReleasesProps) { Tag - + {release.tagName} diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..3fdf6da --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,11 @@ +.no-wrap { + white-space: nowrap; +} + +.cursor-pointer { + cursor: pointer; +} + +.cursor-default { + cursor: default; +} diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx index cd70595..3a5229b 100644 --- a/frontend/src/main.tsx +++ b/frontend/src/main.tsx @@ -1,3 +1,6 @@ +import '@mantine/core/styles.css'; +import './index.css'; + import React from 'react'; import { createRoot } from 'react-dom/client'; import { App } from './App.tsx'; diff --git a/src/main.rs b/src/main.rs index 4de5d47..a9e244e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,9 @@ use axum::{ Router, body::Body, extract::FromRef, + extract::State, + http::{HeaderValue, header, header::CONTENT_SECURITY_POLICY}, + middleware::{self, Next}, routing::{any, get, put}, }; use cookie::Key; @@ -117,10 +120,28 @@ impl AppStateContainer { } } +fn build_csp_header() -> String { + [ + "default-src 'none'", + "base-uri 'none'", + "frame-ancestors 'none'", + "object-src 'none'", + "script-src 'self'", + "style-src 'self' 'unsafe-inline'", + "img-src 'self' https://avatars.githubusercontent.com", + "font-src 'self'", + "connect-src 'self'", + "manifest-src 'none'", + "form-action 'self'", + ] + .join("; ") +} + async fn app(with_frontend: bool) -> Result<(AppState, Router)> { let state: AppState = AppStateContainer::new()?.into(); let mut app = Router::new() + .route("/favicon.svg", get(favicon_svg)) .route("/etes/login", get(auth::login)) .route("/etes/logout", get(auth::logout)) .route("/etes/authorize", get(auth::authorize)) @@ -136,13 +157,42 @@ async fn app(with_frontend: bool) -> Result<(AppState, Router)> { let index = include_str!("../frontend/index.html").replace("%FAVICON%", &state.config.favicon); let frontend = spaxum::load!(&state.config.title).set_html_template(index); - app = app.merge(frontend.router()); + + // add CSP for release builds + if !cfg!(debug_assertions) { + let csp = build_csp_header(); + let csp_header = HeaderValue::from_str(&csp).expect("CSP header value must be ASCII"); + app = app.layer(middleware::from_fn(move |req, next: Next| { + let csp_header = csp_header.clone(); + async move { + let mut res = next.run(req).await; + res.headers_mut() + .insert(CONTENT_SECURITY_POLICY, csp_header); + res + } + })); + } } Ok((state, app)) } +async fn favicon_svg(State(state): State) -> impl axum::response::IntoResponse { + let svg = format!( + "{}", + state.config.favicon + ); + + ( + [( + header::CONTENT_TYPE, + HeaderValue::from_static("image/svg+xml; charset=utf-8"), + )], + svg, + ) +} + #[tokio::main] async fn main() -> Result<()> { tracing_subscriber::registry()