From 3f39f5ca1460fed33b69fb2478dd7820a98a429a Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Thu, 16 Sep 2021 23:09:35 +0100 Subject: [PATCH] Rename API keys to sessions --- Cargo.lock | 33 +++++++++++++++++++++++++++++++++ chartered-web/Cargo.toml | 3 ++- chartered-db/src/schema.rs | 22 +++++++++++++--------- chartered-db/src/users.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++--------------- chartered-frontend/src/useAuth.tsx | 1 + chartered-git/src/main.rs | 8 +++++--- migrations/2021-08-31-214501_create_crates_table/down.sql | 2 +- migrations/2021-08-31-214501_create_crates_table/up.sql | 6 ++++-- chartered-web/src/middleware/auth.rs | 2 +- chartered-web/src/endpoints/cargo_api/download.rs | 2 +- chartered-web/src/endpoints/cargo_api/owners.rs | 2 +- chartered-web/src/endpoints/cargo_api/yank.rs | 4 ++-- chartered-web/src/endpoints/web_api/crate_info.rs | 2 +- chartered-web/src/endpoints/web_api/login.rs | 22 +++++++++++++++++++++- chartered-web/src/endpoints/web_api/ssh_key.rs | 2 +- 15 files changed, 117 insertions(+), 53 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ad4bfc..1e43bf8 100644 --- a/Cargo.lock +++ a/Cargo.lock @@ -74,6 +74,7 @@ "bitflags", "bytes", "futures-util", + "headers", "http", "http-body", "hyper", @@ -266,6 +267,7 @@ "chrono", "env_logger", "futures", + "headers", "hex", "log", "nom", @@ -632,6 +634,31 @@ ] [[package]] +name = "headers" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0b7591fb62902706ae8e7aaff416b1b0fa2c0fd0878b46dc13baa3712d8a855" +dependencies = [ + "base64", + "bitflags", + "bytes", + "headers-core", + "http", + "mime", + "sha-1", + "time", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] name = "hermit-abi" version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -812,6 +839,12 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" + +[[package]] +name = "mime" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "minimal-lexical" diff --git a/chartered-web/Cargo.toml b/chartered-web/Cargo.toml index 44b1082..25a8848 100644 --- a/chartered-web/Cargo.toml +++ a/chartered-web/Cargo.toml @@ -10,11 +10,12 @@ chartered-fs = { path = "../chartered-fs" } chartered-types = { path = "../chartered-types" } -axum = "0.2" +axum = { version = "0.2", features = ["headers"] } bytes = "1" chrono = { version = "0.4", features = ["serde"] } env_logger = "0.9" futures = "0.3" +headers = "0.3" hex = "0.4" log = "0.4" nom = "7" diff --git a/chartered-db/src/schema.rs b/chartered-db/src/schema.rs index a80feeb..efe34f3 100644 --- a/chartered-db/src/schema.rs +++ a/chartered-db/src/schema.rs @@ -25,21 +25,23 @@ } table! { - user_api_keys (id) { + user_crate_permissions (id) { id -> Integer, user_id -> Integer, - api_key -> Text, - user_ssh_key_id -> Nullable, - expires_at -> Nullable, + crate_id -> Integer, + permissions -> Integer, } } table! { - user_crate_permissions (id) { + user_sessions (id) { id -> Integer, user_id -> Integer, - crate_id -> Integer, - permissions -> Integer, + session_key -> Text, + user_ssh_key_id -> Nullable, + expires_at -> Nullable, + user_agent -> Nullable, + ip -> Nullable, } } @@ -59,17 +61,17 @@ } joinable!(crate_versions -> crates (crate_id)); -joinable!(user_api_keys -> user_ssh_keys (user_ssh_key_id)); -joinable!(user_api_keys -> users (user_id)); joinable!(user_crate_permissions -> crates (crate_id)); joinable!(user_crate_permissions -> users (user_id)); +joinable!(user_sessions -> user_ssh_keys (user_ssh_key_id)); +joinable!(user_sessions -> users (user_id)); joinable!(user_ssh_keys -> users (user_id)); allow_tables_to_appear_in_same_query!( crate_versions, crates, - user_api_keys, user_crate_permissions, + user_sessions, user_ssh_keys, users, ); diff --git a/chartered-db/src/users.rs b/chartered-db/src/users.rs index e62ce4a..2cbbaac 100644 --- a/chartered-db/src/users.rs +++ a/chartered-db/src/users.rs @@ -1,5 +1,5 @@ use super::{ - schema::{user_api_keys, user_crate_permissions, user_ssh_keys, users}, + schema::{user_crate_permissions, user_sessions, user_ssh_keys, users}, ConnectionPool, Result, }; use diesel::{insert_into, prelude::*, Associations, Identifiable, Queryable}; @@ -31,22 +31,22 @@ .await? } - pub async fn find_by_api_key( + pub async fn find_by_session_key( conn: ConnectionPool, - given_api_key: String, + given_session_key: String, ) -> Result> { - use crate::schema::user_api_keys::dsl::{api_key, expires_at}; + use crate::schema::user_sessions::dsl::{expires_at, session_key}; tokio::task::spawn_blocking(move || { let conn = conn.get()?; - Ok(crate::schema::user_api_keys::table + Ok(user_sessions::table .filter( expires_at .is_null() .or(expires_at.gt(chrono::Utc::now().naive_utc())), ) - .filter(api_key.eq(given_api_key)) + .filter(session_key.eq(given_session_key)) .inner_join(users::table) .select((users::dsl::id, users::dsl::username)) .get_result(&conn) @@ -210,45 +210,51 @@ #[derive(Identifiable, Queryable, Associations, PartialEq, Eq, Hash, Debug)] #[belongs_to(User)] #[belongs_to(UserSshKey)] -pub struct UserApiKey { +pub struct UserSession { pub id: i32, pub user_id: i32, - pub api_key: String, + pub session_key: String, pub user_ssh_key_id: Option, pub expires_at: Option, + pub user_agent: Option, + pub ip: Option, } -impl UserApiKey { +impl UserSession { pub async fn generate( conn: ConnectionPool, given_user_id: i32, given_user_ssh_key_id: Option, given_expires_at: Option, - ) -> Result { - use crate::schema::user_api_keys::dsl::{ - api_key, expires_at, user_api_keys, user_id, user_ssh_key_id, + given_user_agent: Option, + given_ip: Option, + ) -> Result { + use crate::schema::user_sessions::dsl::{ + expires_at, ip, session_key, user_agent, user_id, user_sessions, user_ssh_key_id, }; tokio::task::spawn_blocking(move || { let conn = conn.get()?; - let generated_api_key: String = thread_rng() + let generated_session_key: String = thread_rng() .sample_iter(&rand::distributions::Alphanumeric) .take(48) .map(char::from) .collect(); - insert_into(user_api_keys) + insert_into(user_sessions) .values(( user_id.eq(given_user_id), - api_key.eq(&generated_api_key), + session_key.eq(&generated_session_key), user_ssh_key_id.eq(given_user_ssh_key_id), expires_at.eq(given_expires_at), + user_agent.eq(given_user_agent), + ip.eq(given_ip), )) .execute(&conn)?; - Ok(crate::schema::user_api_keys::table - .filter(api_key.eq(generated_api_key)) + Ok(crate::schema::user_sessions::table + .filter(session_key.eq(generated_session_key)) .get_result(&conn)?) }) .await? @@ -318,23 +324,24 @@ } impl UserSshKey { - /// Every SSH key should have a corresponding API key so when the config is pulled from git we - /// can return a key in there. The API key might have, however, been compromised and removed + /// Every SSH key should have a corresponding session so when the config is pulled from git we + /// can return a key in there. The session might have, however, been compromised and removed /// using the Web UI/database/etc - this function will regenerate the key on next pull so /// there's no disruption in service. - pub async fn get_or_insert_api_key( + pub async fn get_or_insert_session( self: Arc, conn: ConnectionPool, - ) -> Result { - use crate::schema::user_api_keys::dsl::{expires_at, user_id}; - - let res: Option = tokio::task::spawn_blocking({ + ip: Option, + ) -> Result { + use crate::schema::user_sessions::dsl::{expires_at, user_id}; + + let res: Option = tokio::task::spawn_blocking({ let conn = conn.clone(); let this = self.clone(); move || { let conn = conn.get()?; - UserApiKey::belonging_to(&*this) + UserSession::belonging_to(&*this) .filter( expires_at .is_null() @@ -351,7 +358,7 @@ if let Some(res) = res { Ok(res) } else { - UserApiKey::generate(conn, self.user_id, Some(self.id), None).await + UserSession::generate(conn, self.user_id, Some(self.id), None, None, ip).await } } } diff --git a/chartered-frontend/src/useAuth.tsx b/chartered-frontend/src/useAuth.tsx index ffd4b31..85810fa 100644 --- a/chartered-frontend/src/useAuth.tsx +++ a/chartered-frontend/src/useAuth.tsx @@ -33,6 +33,7 @@ method: 'POST', headers: { 'Content-Type': 'application/json', + 'User-Agent': window.navigator.userAgent, }, body: JSON.stringify({ username, password }), }); diff --git a/chartered-git/src/main.rs b/chartered-git/src/main.rs index 17c0862..7d57715 100644 --- a/chartered-git/src/main.rs +++ a/chartered-git/src/main.rs @@ -48,8 +48,9 @@ impl server::Server for Server { type Handler = Handler; - fn new(&mut self, _: Option) -> Self::Handler { + fn new(&mut self, ip: Option) -> Self::Handler { Handler { + ip, codec: GitCodec::default(), input_bytes: BytesMut::default(), output_bytes: BytesMut::default(), @@ -61,6 +62,7 @@ } struct Handler { + ip: Option, codec: GitCodec, input_bytes: BytesMut, output_bytes: BytesMut, @@ -251,9 +253,9 @@ key = self .user_ssh_key()? .clone() - .get_or_insert_api_key(self.db.clone()) + .get_or_insert_session(self.db.clone(), self.ip.map(|v| v.to_string())) .await? - .api_key, + .session_key, ); let config_file = PackFileEntry::Blob(config.as_bytes()); diff --git a/migrations/2021-08-31-214501_create_crates_table/down.sql b/migrations/2021-08-31-214501_create_crates_table/down.sql index 075945e..632c629 100644 --- a/migrations/2021-08-31-214501_create_crates_table/down.sql +++ a/migrations/2021-08-31-214501_create_crates_table/down.sql @@ -1,6 +1,6 @@ DROP TABLE crates; DROP TABLE crate_versions; DROP TABLE users; DROP TABLE user_ssh_keys; -DROP TABLE user_api_keys; +DROP TABLE user_sessions; DROP TABLE user_crate_permissions; diff --git a/migrations/2021-08-31-214501_create_crates_table/up.sql b/migrations/2021-08-31-214501_create_crates_table/up.sql index b394131..d1d3b05 100644 --- a/migrations/2021-08-31-214501_create_crates_table/up.sql +++ a/migrations/2021-08-31-214501_create_crates_table/up.sql @@ -34,12 +34,14 @@ FOREIGN KEY (user_id) REFERENCES users (id) ); -CREATE TABLE user_api_keys ( +CREATE TABLE user_sessions ( id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, - api_key VARCHAR(255) NOT NULL UNIQUE, + session_key VARCHAR(255) NOT NULL UNIQUE, user_ssh_key_id INTEGER, expires_at DATETIME, + user_agent VARCHAR(255), + ip VARCHAR(255), FOREIGN KEY (user_id) REFERENCES users (id) FOREIGN KEY (user_ssh_key_id) REFERENCES user_ssh_keys (id) ); diff --git a/chartered-web/src/middleware/auth.rs b/chartered-web/src/middleware/auth.rs index c3a53c8..96e2ab0 100644 --- a/chartered-web/src/middleware/auth.rs +++ a/chartered-web/src/middleware/auth.rs @@ -50,7 +50,7 @@ .unwrap() .clone(); - let user = match chartered_db::users::User::find_by_api_key(db, String::from(key)) + let user = match chartered_db::users::User::find_by_session_key(db, String::from(key)) .await .unwrap() { diff --git a/chartered-web/src/endpoints/cargo_api/download.rs b/chartered-web/src/endpoints/cargo_api/download.rs index ff81cf9..70dc1a2 100644 --- a/chartered-web/src/endpoints/cargo_api/download.rs +++ a/chartered-web/src/endpoints/cargo_api/download.rs @@ -34,7 +34,7 @@ define_error_response!(Error); pub async fn handle( - extract::Path((_api_key, name, version)): extract::Path<(String, String, String)>, + extract::Path((_session_key, name, version)): extract::Path<(String, String, String)>, extract::Extension(db): extract::Extension, extract::Extension(user): extract::Extension>, ) -> Result, Error> { diff --git a/chartered-web/src/endpoints/cargo_api/owners.rs b/chartered-web/src/endpoints/cargo_api/owners.rs index 9edc885..bdd9d96 100644 --- a/chartered-web/src/endpoints/cargo_api/owners.rs +++ a/chartered-web/src/endpoints/cargo_api/owners.rs @@ -42,7 +42,7 @@ } pub async fn handle_get( - extract::Path((_api_key, name)): extract::Path<(String, String)>, + extract::Path((_session_key, name)): extract::Path<(String, String)>, extract::Extension(db): extract::Extension, extract::Extension(user): extract::Extension>, ) -> Result, Error> { diff --git a/chartered-web/src/endpoints/cargo_api/yank.rs b/chartered-web/src/endpoints/cargo_api/yank.rs index 0a338e7..c3589cb 100644 --- a/chartered-web/src/endpoints/cargo_api/yank.rs +++ a/chartered-web/src/endpoints/cargo_api/yank.rs @@ -38,7 +38,7 @@ } pub async fn handle_yank( - extract::Path((_api_key, name, version)): extract::Path<(String, String, String)>, + extract::Path((_session_key, name, version)): extract::Path<(String, String, String)>, extract::Extension(db): extract::Extension, extract::Extension(user): extract::Extension>, ) -> Result, Error> { @@ -58,7 +58,7 @@ } pub async fn handle_unyank( - extract::Path((_api_key, name, version)): extract::Path<(String, String, String)>, + extract::Path((_session_key, name, version)): extract::Path<(String, String, String)>, extract::Extension(db): extract::Extension, extract::Extension(user): extract::Extension>, ) -> Result, Error> { diff --git a/chartered-web/src/endpoints/web_api/crate_info.rs b/chartered-web/src/endpoints/web_api/crate_info.rs index 3db842c..0359c85 100644 --- a/chartered-web/src/endpoints/web_api/crate_info.rs +++ a/chartered-web/src/endpoints/web_api/crate_info.rs @@ -33,7 +33,7 @@ define_error_response!(Error); pub async fn handle( - extract::Path((_api_key, name)): extract::Path<(String, String)>, + extract::Path((_session_key, name)): extract::Path<(String, String)>, extract::Extension(db): extract::Extension, extract::Extension(user): extract::Extension>, ) -> Result, Error> { diff --git a/chartered-web/src/endpoints/web_api/login.rs b/chartered-web/src/endpoints/web_api/login.rs index 5ca0bab..03bce1e 100644 --- a/chartered-web/src/endpoints/web_api/login.rs +++ a/chartered-web/src/endpoints/web_api/login.rs @@ -1,6 +1,6 @@ use axum::{extract, Json}; use chartered_db::{ - users::{User, UserApiKey}, + users::{User, UserSession}, ConnectionPool, }; use serde::{Deserialize, Serialize}; @@ -30,18 +30,34 @@ pub async fn handle( extract::Extension(db): extract::Extension, extract::Json(req): extract::Json, + user_agent: Option>, + extract::ConnectInfo(addr) : extract::ConnectInfo, ) -> Result, Error> { // TODO: passwords let user = User::find_by_username(db.clone(), req.username) .await? .ok_or(Error::UnknownUser)?; + let user_agent = if let Some(extract::TypedHeader(user_agent)) = user_agent { + Some(user_agent.as_str().to_string()) + } else { + None + }; + // todo: session? ip storage? etc... let expires = chrono::Utc::now() + chrono::Duration::hours(1); - let key = UserApiKey::generate(db, user.id, None, Some(expires.naive_utc())).await?; + let key = UserSession::generate( + db, + user.id, + None, + Some(expires.naive_utc()), + user_agent, + Some(addr.to_string()), + ) + .await?; Ok(Json(Response { - key: key.api_key, + key: key.session_key, expires, })) } diff --git a/chartered-web/src/endpoints/web_api/ssh_key.rs b/chartered-web/src/endpoints/web_api/ssh_key.rs index 9c60157..f378be7 100644 --- a/chartered-web/src/endpoints/web_api/ssh_key.rs +++ a/chartered-web/src/endpoints/web_api/ssh_key.rs @@ -60,7 +60,7 @@ pub async fn handle_delete( extract::Extension(db): extract::Extension, extract::Extension(user): extract::Extension>, - extract::Path((_api_key, ssh_key_id)): extract::Path<(String, i32)>, + extract::Path((_session_key, ssh_key_id)): extract::Path<(String, i32)>, ) -> Result, Error> { let deleted = user.delete_user_ssh_key_by_id(db, ssh_key_id).await?; -- rgit 0.1.3