Skip to content

Commit 93340c6

Browse files
committed
init
1 parent 6e6a180 commit 93340c6

File tree

20 files changed

+1485
-20747
lines changed

20 files changed

+1485
-20747
lines changed

backend/Rocket.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ content_security_policy = "default-src 'none'; base-uri 'self'; script-src 'self
1616
content_security_policy_inject_nonce_paths = ["^/$","^/index.html$","^/ui/*"]
1717
content_security_policy_nonce_headers = ["script-src"]
1818
content_security_policy_inject_nonce_tags = ["script"]
19-
permission_policy = "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()"
19+
permissions_policy = "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()"
2020
referrer_policy = "no-referrer"
2121
cross_origin_embedder_policy = "require-corp; report-to=\"default\""
2222
cross_origin_opener_policy = "same-origin; report-to=\"default\""

backend/docs/ARCHITECTURE.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,4 +163,6 @@ pub async fn inventory_handler(
163163
user: OidcUser,
164164
) -> crate::database::Result<Json<Vec<inventory::product::Product>>> {
165165
....
166-
```
166+
```
167+
168+
The OIDC IdToken, AccessToken, mapped roles are stored encryped and tamperproof in a private cookie in the user's browser. This is important, because otherwise the user may give themselves different roles that they are not authorised for.

backend/docs/CONFIGURE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ content_security_policy = "default-src 'none'; base-uri 'self'; script-src 'self
109109
content_security_policy_inject_nonce_paths = ["^/$","^/index.html$","^/ui/*"]
110110
content_security_policy_nonce_headers = ["script-src"]
111111
content_security_policy_inject_nonce_tags = ["script"]
112-
permission_policy = "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()"
112+
permissions_policy = "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=()"
113113
referrer_policy = "no-referrer"
114114
cross_origin_embedder_policy = "require-corp; report-to=\"default\""
115115
cross_origin_opener_policy = "same-origin; report-to=\"default\""

backend/src/configuration/config.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! Manage application-specific configuration
2+
13
use std::collections::HashMap;
24

35
use rocket::{Build, Rocket, serde::Deserialize};
@@ -30,6 +32,11 @@ pub struct CustomAppHttpHeadersConfig {
3032
pub content_security_policy_inject_nonce_paths: Option<Vec<String>>,
3133
pub content_security_policy_inject_nonce_tags: Option<Vec<String>>,
3234
pub content_security_policy_nonce_headers: Option<Vec<String>>,
35+
pub permissions_policy: Option<String>,
36+
pub referrer_policy: Option<String>,
37+
pub cross_origin_embedder_policy: Option<String>,
38+
pub cross_origin_opener_policy: Option<String>,
39+
pub cross_origin_resource_policy: Option<String>,
3340
}
3441

3542
/// Configuration of static file serving
@@ -60,6 +67,9 @@ pub struct Config {
6067
/// # Arguments
6168
/// * `config` - Configuration of the Rocket app
6269
///
70+
/// # Returns
71+
/// Fairing that can be attached using rocket.attach
72+
///
6373
pub fn read_security_http_headers_config(config: &Config) -> SecurityHttpHeaders {
6474
let mut regex_paths = regex::RegexSet::new(Vec::<String>::new()).unwrap();
6575
match &config
@@ -79,10 +89,28 @@ pub fn read_security_http_headers_config(config: &Config) -> SecurityHttpHeaders
7989
}
8090
}
8191

92+
/// Configure static file server for static files (e.g. frontend)
93+
///
94+
/// # Arguments
95+
/// * `rocket` - variable representing a Rocket instance
96+
/// * `config` - Configuration of the Rocket app
97+
///
98+
/// # Returns
99+
/// rocket representing Rocket instance with static file server configured
100+
///
82101
pub fn configure_fileserver(rocket: Rocket<Build>, config: &Config) -> Rocket<Build> {
83102
rocket.manage(config.app.fileserver.clone())
84103
}
85104

105+
/// Configure OIDC authentication with Rocket instance
106+
///
107+
/// # Arguments
108+
/// * `rocket` - variable representing a rocket instance
109+
/// * `config` - Configuration of the Rocket app
110+
///
111+
/// # Returns
112+
/// rocket representing rocket instance with OIDC authentication configured
113+
///
86114
pub fn configure_oidc(rocket: Rocket<Build>, config: &Config) -> Rocket<Build> {
87115
let oidc_flow = read_oidc_config(&config);
88116
rocket
@@ -94,6 +122,14 @@ pub fn configure_oidc(rocket: Rocket<Build>, config: &Config) -> Rocket<Build> {
94122
)
95123
}
96124

125+
/// Reads the OIDC configuration and creates an OIDC client
126+
///
127+
/// # Arguments
128+
/// * `config` - Configuration of the Rocket app
129+
///
130+
/// # Returns
131+
/// OIDC client that should be managed in a state in Rocket
132+
///
97133
fn read_oidc_config(config: &Config) -> OidcFlow {
98134
let issuer_url = match &config.app.oidc.issuer_url {
99135
Some(issuer_url) => issuer_url,

backend/src/database/mod.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! Manage database and create/update schemas
2+
13
use rocket::fairing::{self};
24
use rocket::{Build, Rocket};
35
use rocket_db_pools::Database;
@@ -9,6 +11,14 @@ pub struct Db(sqlx::SqlitePool);
911

1012
pub type Result<T, E = rocket::response::Debug<sqlx::Error>> = std::result::Result<T, E>;
1113

14+
/// Create/update schemas in database
15+
///
16+
/// # Arguments
17+
/// * `rocket` - rocket variable representing the Rocket instance
18+
///
19+
/// # Returns
20+
/// Fairing that can be attached using rocket.attach
21+
///
1222
pub async fn run_migrations(rocket: Rocket<Build>) -> fairing::Result {
1323
match Db::fetch(&rocket) {
1424
Some(db) => match sqlx::migrate!("db/sqlx/migrations").run(&**db).await {

backend/src/httpfirewall/securityhttpheaders.rs

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//! Rocket fairing to configure secure HTTP headers, such as content security policies
1+
//! Rocket fairing to inject HTTP security headers, such as content security policies, into responses
22
33
use std::io;
44

@@ -10,12 +10,14 @@ use rocket::http::Status;
1010
use rocket::{Data, Request, Response};
1111
use tracing::{Level, event};
1212

13+
// Configuration of Fairing
1314
#[derive(Clone)]
1415
pub struct SecurityHttpHeaders {
1516
pub config: crate::configuration::config::CustomAppConfig,
1617
pub regex_paths: regex::RegexSet,
1718
}
1819

20+
// Fairing implementation
1921
#[rocket::async_trait]
2022
impl Fairing for SecurityHttpHeaders {
2123
fn info(&self) -> Info {
@@ -25,12 +27,27 @@ impl Fairing for SecurityHttpHeaders {
2527
}
2628
}
2729

30+
/// Executed on every request (we do not need it, but it is part of the Fairing trait)
31+
///
32+
/// # Arguments
33+
/// * `self` - Struct Security HTTP Headers
34+
/// * `req` - Request object
35+
///
36+
///
2837
async fn on_request(&self, req: &mut Request<'_>, _: &mut Data<'_>) {
2938
// do nothing
3039
}
3140

41+
/// Executed on every response. We inject here the HTTP Security Headers
42+
///
43+
/// # Arguments
44+
/// * `self` - Struct Security HTTP Headers
45+
/// * `req` - Request object
46+
/// * `res` - Response object
47+
///
3248
async fn on_response<'r>(&self, req: &'r Request<'_>, res: &mut Response<'r>) {
3349
if (res.status() == Status::Ok) {
50+
// Configure Content-Security Policy Header and insert nonces
3451
let mut body_bytes = res.body_mut().to_bytes().await.unwrap();
3552
match self.config.clone().httpheaders.content_security_policy {
3653
Some(csp) => {
@@ -95,9 +112,48 @@ impl Fairing for SecurityHttpHeaders {
95112
"Configuration: Content-Security-Policy: You did not specify a tag to inject a nonce"
96113
),
97114
}
98-
99115
res.set_raw_header("Content-Security-Policy", csp_value);
100116
res.set_sized_body(body_bytes.len(), io::Cursor::new(body_bytes));
117+
// Set other HTTP Security Headers
118+
match self.config.clone().httpheaders.permissions_policy {
119+
Some(permissions_policy) => {
120+
res.set_raw_header("Permissions-Policy", permissions_policy);
121+
}
122+
None => (),
123+
};
124+
match self.config.clone().httpheaders.referrer_policy {
125+
Some(referrer_policy) => {
126+
res.set_raw_header("Referrer-Policy", referrer_policy);
127+
}
128+
None => (),
129+
};
130+
match self.config.clone().httpheaders.cross_origin_embedder_policy {
131+
Some(cross_origin_embedder_policy) => {
132+
res.set_raw_header(
133+
"Cross-Origin-Embedder-Policy",
134+
cross_origin_embedder_policy,
135+
);
136+
}
137+
None => (),
138+
};
139+
match self.config.clone().httpheaders.cross_origin_opener_policy {
140+
Some(cross_origin_opener_policy) => {
141+
res.set_raw_header(
142+
"Cross-Origin-Opener-Policy",
143+
cross_origin_opener_policy,
144+
);
145+
}
146+
None => (),
147+
};
148+
match self.config.clone().httpheaders.cross_origin_resource_policy {
149+
Some(cross_origin_resource_policy) => {
150+
res.set_raw_header(
151+
"Cross-Origin-Resource-Policy",
152+
cross_origin_resource_policy,
153+
);
154+
}
155+
None => (),
156+
};
101157
}
102158
}
103159
None => (),

backend/src/inventory/product.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! Business data and logic for products
2+
13
use rocket::serde::{Deserialize, Serialize};
24

35
use rust_decimal::prelude::*;

backend/src/main.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub mod order;
2424
pub mod routes;
2525
pub mod services;
2626

27-
/// Launch endpoints of the web application
27+
/// Launch our application configured with custom routes, OIDC autentication and static file serving
2828
#[launch]
2929
fn rocket() -> _ {
3030
let rocket = rocket::build()

backend/src/oidc/guard.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! Request guard to ensure OIDC authentication to routes in Rocket
2+
13
use super::oidcflow::{OidcFlow, OidcSessionCookie};
24

35
use openidconnect::{ClaimsVerificationError, EndUserUsername, SubjectIdentifier};
@@ -10,13 +12,15 @@ use rocket::{
1012
use serde::Serialize;
1113
use tracing::{Level, event};
1214

15+
// Represents an authenticated user in a Rocket route
1316
#[derive(Serialize)]
1417
pub struct OidcUser {
1518
pub subject: SubjectIdentifier,
1619
pub preferred_username: Option<EndUserUsername>,
1720
pub mapped_roles: Vec<String>,
1821
}
1922

23+
// Loads user authentication information from the oidc session cookie
2024
impl OidcUser {
2125
fn load_from_session(
2226
oidc: &OidcFlow,
@@ -45,10 +49,20 @@ impl OidcUser {
4549
}
4650
}
4751

52+
// Implementation of the request guard to ensure that the user is authenticated via OIDC
53+
4854
#[rocket::async_trait]
4955
impl<'r> FromRequest<'r> for OidcUser {
5056
type Error = ();
5157

58+
/// Executed for each request on which route the OidcUser is included
59+
/// Reads from the cookie the user information including the OIDC token
60+
/// If they are not presented then a cookie is added from which route the user came from so the user is redirected there again after authentication
61+
///
62+
/// # Arguments
63+
/// * `req` - Request object
64+
///
65+
///
5266
async fn from_request(req: &'r Request<'_>) -> request::Outcome<Self, Self::Error> {
5367
let mut cookies = req.cookies();
5468
if let Some(serialized_session) = cookies.get_private("oidc_user_session") {

0 commit comments

Comments
 (0)