diff --git a/.sqlx/query-d6b173d90cd227a65bfc4a47ed3ba7b19650ed4d3dc44c1519775a6d0c9026b7.json b/.sqlx/query-125d60c302bfc35fa7edc71b4c23c1a4fd81060df92388ccbfd43dd8c5771031.json similarity index 80% rename from .sqlx/query-d6b173d90cd227a65bfc4a47ed3ba7b19650ed4d3dc44c1519775a6d0c9026b7.json rename to .sqlx/query-125d60c302bfc35fa7edc71b4c23c1a4fd81060df92388ccbfd43dd8c5771031.json index 2033c85..c63bcb2 100644 --- a/.sqlx/query-d6b173d90cd227a65bfc4a47ed3ba7b19650ed4d3dc44c1519775a6d0c9026b7.json +++ b/.sqlx/query-125d60c302bfc35fa7edc71b4c23c1a4fd81060df92388ccbfd43dd8c5771031.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT user_name, user_id, login_session_id, csrf_secret\n FROM login_sessions INNER JOIN users USING (user_id)\n WHERE login_session_token_hash = $1\n ", + "query": "\n SELECT user_name, user_id, login_session_id, xsrf_secret\n FROM login_sessions INNER JOIN users USING (user_id)\n WHERE login_session_token_hash = $1\n ", "describe": { "columns": [ { @@ -20,7 +20,7 @@ }, { "ordinal": 3, - "name": "csrf_secret", + "name": "xsrf_secret", "type_info": "Bytea" } ], @@ -36,5 +36,5 @@ false ] }, - "hash": "d6b173d90cd227a65bfc4a47ed3ba7b19650ed4d3dc44c1519775a6d0c9026b7" + "hash": "125d60c302bfc35fa7edc71b4c23c1a4fd81060df92388ccbfd43dd8c5771031" } diff --git a/.sqlx/query-2633347de5bc78129d60926bbeab5571eddce6f76d80db323622db9648d918c3.json b/.sqlx/query-cfc3e3102493b5b385bcab8eb8d4ea73d38cfb32cdf5c34b6cc76738669490fd.json similarity index 65% rename from .sqlx/query-2633347de5bc78129d60926bbeab5571eddce6f76d80db323622db9648d918c3.json rename to .sqlx/query-cfc3e3102493b5b385bcab8eb8d4ea73d38cfb32cdf5c34b6cc76738669490fd.json index 5934efe..1dfa800 100644 --- a/.sqlx/query-2633347de5bc78129d60926bbeab5571eddce6f76d80db323622db9648d918c3.json +++ b/.sqlx/query-cfc3e3102493b5b385bcab8eb8d4ea73d38cfb32cdf5c34b6cc76738669490fd.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "INSERT INTO login_sessions (login_session_token_hash, user_id, started_at_utc, csrf_secret)\n VALUES ($1, $2, NOW(), $3)", + "query": "INSERT INTO login_sessions (login_session_token_hash, user_id, started_at_utc, xsrf_secret)\n VALUES ($1, $2, NOW(), $3)", "describe": { "columns": [], "parameters": { @@ -12,5 +12,5 @@ }, "nullable": [] }, - "hash": "2633347de5bc78129d60926bbeab5571eddce6f76d80db323622db9648d918c3" + "hash": "cfc3e3102493b5b385bcab8eb8d4ea73d38cfb32cdf5c34b6cc76738669490fd" } diff --git a/migrations/20231205205358_login_sessions.sql b/migrations/20231205205358_login_sessions.sql index 7be5d51..9019717 100644 --- a/migrations/20231205205358_login_sessions.sql +++ b/migrations/20231205205358_login_sessions.sql @@ -5,7 +5,7 @@ CREATE TABLE login_sessions ( login_session_token_hash BYTEA NOT NULL UNIQUE, user_id UUID NOT NULL REFERENCES users(user_id), started_at_utc TIMESTAMP NOT NULL, - csrf_secret BYTEA NOT NULL + xsrf_secret BYTEA NOT NULL ); CREATE INDEX ON login_sessions (user_id); @@ -20,4 +20,4 @@ COMMENT ON COLUMN login_sessions.user_id IS 'ID of the user that this login sess COMMENT ON COLUMN login_sessions.started_at_utc IS 'Timestamp in UTC when the session was started.'; -COMMENT ON COLUMN login_sessions.csrf_secret IS 'Key for a Blake2sMac256 which is used to prevent Cross-Site Request Forgery.'; +COMMENT ON COLUMN login_sessions.xsrf_secret IS 'Key for a Blake2sMac256 which is used to prevent Cross-Site Request Forgery.'; diff --git a/src/store.rs b/src/store.rs index dacd5a0..1ed4e6b 100644 --- a/src/store.rs +++ b/src/store.rs @@ -8,7 +8,7 @@ use sqlx::{types::Uuid, Connection, PgPool, Postgres, Transaction}; use tracing::error; use crate::web::login::{ - LoginSession, LOGIN_SESSION_CSRF_SECRET_BYTES, LOGIN_SESSION_TOKEN_HASH_BYTES, + LoginSession, LOGIN_SESSION_TOKEN_HASH_BYTES, LOGIN_SESSION_XSRF_SECRET_BYTES, }; /// Postgres-backed storage for IdCoop @@ -239,12 +239,12 @@ impl<'a, 'txn> IdCoopStoreTxn<'a, 'txn> { &mut self, login_session_token_hash: &[u8; LOGIN_SESSION_TOKEN_HASH_BYTES], user_id: Uuid, - csrf_secret: &[u8; LOGIN_SESSION_CSRF_SECRET_BYTES], + xsrf_secret: &[u8; LOGIN_SESSION_XSRF_SECRET_BYTES], ) -> eyre::Result<()> { sqlx::query!( - "INSERT INTO login_sessions (login_session_token_hash, user_id, started_at_utc, csrf_secret) + "INSERT INTO login_sessions (login_session_token_hash, user_id, started_at_utc, xsrf_secret) VALUES ($1, $2, NOW(), $3)", - login_session_token_hash, user_id, csrf_secret + login_session_token_hash, user_id, xsrf_secret ) .execute(&mut **self.txn) .await @@ -258,7 +258,7 @@ impl<'a, 'txn> IdCoopStoreTxn<'a, 'txn> { ) -> eyre::Result> { let row_opt = sqlx::query!( " - SELECT user_name, user_id, login_session_id, csrf_secret + SELECT user_name, user_id, login_session_id, xsrf_secret FROM login_sessions INNER JOIN users USING (user_id) WHERE login_session_token_hash = $1 ", @@ -274,10 +274,10 @@ impl<'a, 'txn> IdCoopStoreTxn<'a, 'txn> { user_name: row.user_name, user_id: row.user_id, login_session_id: row.login_session_id, - csrf_secret: row - .csrf_secret + xsrf_secret: row + .xsrf_secret .try_into() - .map_err(|_| eyre!("cannot retrieve login session: has invalid CSRF token"))?, + .map_err(|_| eyre!("cannot retrieve login session: has invalid XSRF token"))?, })) } } diff --git a/src/web/login.rs b/src/web/login.rs index 0cabf58..66c037e 100644 --- a/src/web/login.rs +++ b/src/web/login.rs @@ -32,28 +32,28 @@ pub const LOGIN_SESSION_TOKEN_BYTES: usize = 32; /// Size of a Blake2s hash of the login token (which is what we store in the database) pub const LOGIN_SESSION_TOKEN_HASH_BYTES: usize = 32; -/// Size of the CSRF secret in bytes; this is a Blake2sMac256 salt size. +/// Size of the XSRF secret in bytes; this is a Blake2sMac256 salt size. /// TODO the Blake2sMac256 also has a 'personal' item which might give us more bytes to play with and /// perhaps we should be using that too. -pub const LOGIN_SESSION_CSRF_SECRET_BYTES: usize = 8; +pub const LOGIN_SESSION_XSRF_SECRET_BYTES: usize = 8; pub struct LoginSession { pub user_name: String, pub user_id: Uuid, pub login_session_id: i32, - pub csrf_secret: [u8; LOGIN_SESSION_CSRF_SECRET_BYTES], + pub xsrf_secret: [u8; LOGIN_SESSION_XSRF_SECRET_BYTES], } -/// CSRF token expiry time is 1 week. -pub const CSRF_TOKEN_EXPIRY_TIME: Duration = Duration::milliseconds(1000 * 86400 * 7); +/// XSRF token expiry time is 1 week. +pub const XSRF_TOKEN_EXPIRY_TIME: Duration = Duration::milliseconds(1000 * 86400 * 7); impl LoginSession { - /// Generates a CSRF token which is bound to this session and expires 1 week in the future. - pub fn generate_csrf_token(&self, now: DateTime) -> eyre::Result { + /// Generates a XSRF token which is bound to this session and expires 1 week in the future. + pub fn generate_xsrf_token(&self, now: DateTime) -> eyre::Result { let now_timestamp = now.timestamp(); let now_8bytes = now_timestamp.to_be_bytes(); - let mac_tag_bytes = Blake2sMac256::new_with_salt_and_personal(&self.csrf_secret, &[], &[])? + let mac_tag_bytes = Blake2sMac256::new_with_salt_and_personal(&self.xsrf_secret, &[], &[])? .chain_update(&now_8bytes) .finalize() .into_bytes(); @@ -62,34 +62,34 @@ impl LoginSession { Ok(format!("{now_timestamp}.{mac_b64}")) } - /// Validates a CSRF token to check it is bound to this session and hasn't expired (is less than a week old). - pub fn validate_csrf_token(&self, token: &str, now: DateTime) -> eyre::Result<()> { + /// Validates a XSRF token to check it is bound to this session and hasn't expired (is less than a week old). + pub fn validate_xsrf_token(&self, token: &str, now: DateTime) -> eyre::Result<()> { let (timestamp_str, mac_b64) = token .split_once('.') - .context("CSRF token in wrong format")?; + .context("XSRF token in wrong format")?; let timestamp: i64 = timestamp_str .parse() - .context("timestamp in CSRF token is wrong")?; + .context("timestamp in XSRF token is wrong")?; let mac_tag_bytes: Vec = BASE64_URL_SAFE_NO_PAD .decode(mac_b64) - .context("failed to b64decode the MAC in CSRF token")?; + .context("failed to b64decode the MAC in XSRF token")?; let timestamp_8bytes = timestamp.to_be_bytes(); - Blake2sMac256::new_with_salt_and_personal(&self.csrf_secret, &[], &[])? + Blake2sMac256::new_with_salt_and_personal(&self.xsrf_secret, &[], &[])? .chain_update(×tamp_8bytes) .verify_slice(&mac_tag_bytes) - .context("bad MAC in CSRF token")?; + .context("bad MAC in XSRF token")?; // At this point, the MAC is correct. All that's left is to check that the timestamp isn't too old. if now.signed_duration_since( Utc.timestamp_opt(timestamp, 0) .earliest() - .context("CSRF timestamp not valid")?, - ) > CSRF_TOKEN_EXPIRY_TIME + .context("XSRF timestamp not valid")?, + ) > XSRF_TOKEN_EXPIRY_TIME { - bail!("CSRF token expired."); + bail!("XSRF token expired."); } Ok(()) @@ -162,8 +162,8 @@ pub async fn get_login( match current_session { Some(_session) => make_post_login_redirect(query.then), None => { - let csrf_token = sessionless_xsrf::get_token(&cookies); - Html(format!("
UN PW (temporary form)
", csrf_token)).into_response() + let xsrf_token = sessionless_xsrf::get_token(&cookies); + Html(format!("
UN PW (temporary form)
", xsrf_token)).into_response() } } } @@ -172,7 +172,7 @@ pub async fn get_login( pub struct PostLoginForm { username: String, password: String, - csrf: String, + xsrf: String, } fn dummy_password_hash( @@ -197,8 +197,8 @@ pub async fn post_login( Extension(config): Extension>, Form(form): Form, ) -> WebResult { - if !sessionless_xsrf::check_token(&cookies, &form.csrf) { - // Invalid CSRF token: try again + if !sessionless_xsrf::check_token(&cookies, &form.xsrf) { + // Invalid XSRF token: try again return Ok(get_login(None, Query(query), cookies).await); } @@ -240,13 +240,13 @@ pub async fn post_login( let login_session_token_b64 = BASE64_URL_SAFE_NO_PAD.encode(login_session_token); let login_session_token_hash: [u8; LOGIN_SESSION_TOKEN_HASH_BYTES] = Blake2s256::digest(&login_session_token).into(); - let csrf_secret = thread_rng().gen::<[u8; LOGIN_SESSION_CSRF_SECRET_BYTES]>(); + let xsrf_secret = thread_rng().gen::<[u8; LOGIN_SESSION_XSRF_SECRET_BYTES]>(); // store session in the database store .txn(|mut txn| { Box::pin(async move { - txn.create_login_session(&login_session_token_hash, user.user_id, &csrf_secret) + txn.create_login_session(&login_session_token_hash, user.user_id, &xsrf_secret) .await }) }) diff --git a/src/web/oauth_openid/authorisation.rs b/src/web/oauth_openid/authorisation.rs index a3483dd..4e5679c 100644 --- a/src/web/oauth_openid/authorisation.rs +++ b/src/web/oauth_openid/authorisation.rs @@ -106,7 +106,7 @@ pub async fn oidc_authorisation( #[derive(Deserialize)] pub struct PostConsentForm { action: String, - csrf: String, + xsrf: String, } pub async fn post_oidc_authorisation_consent( @@ -128,7 +128,7 @@ pub async fn post_oidc_authorisation_consent( }; if login_session - .validate_csrf_token(&form.csrf, Utc::now()) + .validate_xsrf_token(&form.xsrf, Utc::now()) .is_err() { // XSRF token is not valid, so show the consent form again... @@ -212,12 +212,12 @@ async fn show_consent_page( client_config: &OidcClientConfiguration, _config: &Configuration, ) -> Response { - let csrf_token = login_session - .generate_csrf_token(Utc::now()) - .expect("must be able to create a CSRF token"); + let xsrf_token = login_session + .generate_xsrf_token(Utc::now()) + .expect("must be able to create a XSRF token"); Html(format!( - "hi {}, consent to {}?
", - login_session.user_name, client_config.name, csrf_token + "hi {}, consent to {}?
", + login_session.user_name, client_config.name, xsrf_token )) .into_response() }