🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-09-16 23:09:35.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-09-16 23:20:14.0 +01:00:00
commit
3f39f5ca1460fed33b69fb2478dd7820a98a429a [patch]
tree
eb72ea90e6e7a254c0dbb66c326575ca9d12b971
parent
7ee0fa5742041e3719f5328b4fe43d7d2b152d4a
download
3f39f5ca1460fed33b69fb2478dd7820a98a429a.tar.gz

Rename API keys to sessions



Diff

 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<Integer>,
        expires_at -> Nullable<Timestamp>,
        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<Integer>,
        expires_at -> Nullable<Timestamp>,
        user_agent -> Nullable<Text>,
        ip -> Nullable<Text>,
    }
}

@@ -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<Option<User>> {
        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<i32>,
    pub expires_at: Option<chrono::NaiveDateTime>,
    pub user_agent: Option<String>,
    pub ip: Option<String>,
}

impl UserApiKey {
impl UserSession {
    pub async fn generate(
        conn: ConnectionPool,
        given_user_id: i32,
        given_user_ssh_key_id: Option<i32>,
        given_expires_at: Option<chrono::NaiveDateTime>,
    ) -> Result<UserApiKey> {
        use crate::schema::user_api_keys::dsl::{
            api_key, expires_at, user_api_keys, user_id, user_ssh_key_id,
        given_user_agent: Option<String>,
        given_ip: Option<String>,
    ) -> Result<Self> {
        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<Self>,
        conn: ConnectionPool,
    ) -> Result<UserApiKey> {
        use crate::schema::user_api_keys::dsl::{expires_at, user_id};

        let res: Option<UserApiKey> = tokio::task::spawn_blocking({
        ip: Option<String>,
    ) -> Result<UserSession> {
        use crate::schema::user_sessions::dsl::{expires_at, user_id};

        let res: Option<UserSession> = 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<std::net::SocketAddr>) -> Self::Handler {
    fn new(&mut self, ip: Option<std::net::SocketAddr>) -> Self::Handler {
        Handler {
            ip,
            codec: GitCodec::default(),
            input_bytes: BytesMut::default(),
            output_bytes: BytesMut::default(),
@@ -61,6 +62,7 @@
}

struct Handler {
    ip: Option<std::net::SocketAddr>,
    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<ConnectionPool>,
    extract::Extension(user): extract::Extension<Arc<User>>,
) -> Result<Vec<u8>, 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<ConnectionPool>,
    extract::Extension(user): extract::Extension<Arc<User>>,
) -> Result<Json<GetResponse>, 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<ConnectionPool>,
    extract::Extension(user): extract::Extension<Arc<User>>,
) -> Result<Json<Response>, 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<ConnectionPool>,
    extract::Extension(user): extract::Extension<Arc<User>>,
) -> Result<Json<Response>, 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<ConnectionPool>,
    extract::Extension(user): extract::Extension<Arc<User>>,
) -> Result<Json<Response>, 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<ConnectionPool>,
    extract::Json(req): extract::Json<Request>,
    user_agent: Option<extract::TypedHeader<headers::UserAgent>>,
    extract::ConnectInfo(addr) : extract::ConnectInfo<std::net::SocketAddr>,
) -> Result<Json<Response>, 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<ConnectionPool>,
    extract::Extension(user): extract::Extension<Arc<User>>,
    extract::Path((_api_key, ssh_key_id)): extract::Path<(String, i32)>,
    extract::Path((_session_key, ssh_key_id)): extract::Path<(String, i32)>,
) -> Result<Json<DeleteResponse>, Error> {
    let deleted = user.delete_user_ssh_key_by_id(db, ssh_key_id).await?;