Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/routes/user/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod api;
mod avatar;
mod sso;
mod user_create;
mod user_keys;
mod user_login;
mod user_logout;
mod user_verify;
Expand All @@ -25,4 +26,6 @@ pub(crate) fn init(config: &mut ServiceConfig) {

config.service(sso::initiate_sso);
config.service(sso::sso_callback);

config.service(user_keys::get_keys);
}
24 changes: 24 additions & 0 deletions src/routes/user/user_keys.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
use crate::ssh::SshKey;
use crate::user::User;
use actix_web::{web, Responder};
use gitarena_macros::route;
use itertools::Itertools;
use sqlx::PgPool;

#[route("/{user}.keys", method = "GET", err = "text")]
pub(crate) async fn get_keys(
user: User,
db_pool: web::Data<PgPool>,
) -> anyhow::Result<impl Responder> {
let mut transaction = db_pool.begin().await?;

let result = match SshKey::all_from_user(&user, &mut transaction).await {
Some(keys) if keys.is_empty() => String::new(),
Some(keys) => keys.into_iter().map(|key| key.as_string()).join("\n"),
_ => String::new(),
};

transaction.commit().await?;

Ok(result)
}
22 changes: 21 additions & 1 deletion src/ssh.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use crate::user::User;
use chrono::{DateTime, Utc};
use derive_more::Display;
use gitarena_common::database::models::KeyType;
use serde::Serialize;
use sqlx::FromRow;
use sqlx::{Executor, FromRow, Postgres};

#[derive(FromRow, Display, Debug, Serialize)]
#[display(fmt = "{}", title)]
Expand All @@ -16,3 +17,22 @@ pub(crate) struct SshKey {
pub(crate) created_at: DateTime<Utc>,
pub(crate) expires_at: Option<DateTime<Utc>>,
}

impl SshKey {
pub(crate) async fn all_from_user<'e, E>(user: &User, executor: E) -> Option<Vec<SshKey>>
where
E: Executor<'e, Database = Postgres>,
{
let keys = sqlx::query_as::<_, SshKey>("select * from ssh_keys where owner = $1")
.bind(user.id)
.fetch_all(executor)
.await
.ok();

keys
}

pub(crate) fn as_string(&self) -> String {
format!("{} {}", &self.algorithm, base64::encode(&self.key))
}
}
56 changes: 54 additions & 2 deletions src/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use futures::Future;
use ipnetwork::IpNetwork;
use serde::Serialize;
use sqlx::{Executor, FromRow, PgPool, Postgres};
use tracing_unwrap::OptionExt;

#[derive(FromRow, Display, Debug, Serialize)]
#[display(fmt = "{}", username)]
Expand Down Expand Up @@ -88,6 +89,57 @@ impl TryFrom<WebUser> for User {
}
}

impl FromRequest for User {
type Error = GitArenaError;
type Future = Pin<Box<dyn Future<Output = Result<User, Self::Error>>>>;

fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let match_info = req.match_info();

// If this method gets called from a handler that does not have username or repository in the match info
// it is safe to assume the programmer made a mistake, thus .expect_or_log is OK
let username = match_info
.get("user")
.or_else(|| match_info.get("username"))
.expect_or_log("from_request called on User despite not having user/username argument")
.to_owned();

match req.app_data::<Data<PgPool>>() {
Some(db_pool) => {
// Data<PgPool> is just a wrapper around `Arc<P>` so .clone() is cheap
let db_pool = db_pool.clone();

Box::pin(async move {
extract_user_from_request(db_pool, username.as_str())
.await
.map_err(|err| GitArenaError {
source: Arc::new(err),
display_type: ErrorDisplayType::Html, // TODO: Check whenever route is err = "html|json|git" etc...
})
})
}
None => Box::pin(async {
Err(GitArenaError {
source: Arc::new(anyhow!("No PgPool in application data")),
display_type: ErrorDisplayType::Html, // TODO: Check whenever route is err = "html|json|git" etc...
})
}),
}
}
}

async fn extract_user_from_request(db_pool: Data<PgPool>, username: &str) -> Result<User> {
let mut transaction = db_pool.begin().await?;

let user = User::find_using_name(username, &mut transaction)
.await
.ok_or_else(|| err!(NOT_FOUND, "Repository not found"))?;

transaction.commit().await?;

Ok(user)
}

#[derive(Debug, Display)]
pub(crate) enum WebUser {
Anonymous,
Expand Down Expand Up @@ -129,7 +181,7 @@ impl FromRequest for WebUser {
let db_pool = db_pool.clone();

Box::pin(async move {
extract_from_request(db_pool, id_future, ip_network, user_agent)
extract_webuser_from_request(db_pool, id_future, ip_network, user_agent)
.await
.map_err(|err| GitArenaError {
source: Arc::new(err),
Expand All @@ -147,7 +199,7 @@ impl FromRequest for WebUser {
}
}

async fn extract_from_request<F: Future<Output = actix_web::Result<Identity>>>(
async fn extract_webuser_from_request<F: Future<Output = actix_web::Result<Identity>>>(
db_pool: Data<PgPool>,
id_future: F,
ip_network: IpNetwork,
Expand Down