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()