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(-)
@@ -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"
@@ -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"
@@ -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,
);
@@ -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 {
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
}
}
}
@@ -33,6 +33,7 @@
method: 'POST',
headers: {
'Content-Type': 'application/json',
'User-Agent': window.navigator.userAgent,
},
body: JSON.stringify({ username, password }),
});
@@ -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());
@@ -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;
@@ -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)
);
@@ -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()
{
@@ -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> {
@@ -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> {
@@ -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> {
@@ -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> {
@@ -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> {
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
};
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,
}))
}
@@ -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?;