api.rs

  1use crate::{auth, AppState, Request, RequestExt as _};
  2use async_trait::async_trait;
  3use serde::Deserialize;
  4use serde_json::json;
  5use std::sync::Arc;
  6use surf::StatusCode;
  7
  8pub fn add_routes(app: &mut tide::Server<Arc<AppState>>) {
  9    app.at("/users/:github_login").get(get_user);
 10    app.at("/users/:github_login/access_tokens")
 11        .post(create_access_token);
 12}
 13
 14async fn get_user(request: Request) -> tide::Result {
 15    request.require_token().await?;
 16
 17    let user = request
 18        .db()
 19        .get_user_by_github_login(request.param("github_login")?)
 20        .await?
 21        .ok_or_else(|| surf::Error::from_str(404, "user not found"))?;
 22
 23    Ok(tide::Response::builder(StatusCode::Ok)
 24        .body(tide::Body::from_json(&user)?)
 25        .build())
 26}
 27
 28async fn create_access_token(request: Request) -> tide::Result {
 29    request.require_token().await?;
 30
 31    let user = request
 32        .db()
 33        .get_user_by_github_login(request.param("github_login")?)
 34        .await?
 35        .ok_or_else(|| surf::Error::from_str(StatusCode::NotFound, "user not found"))?;
 36    let access_token = auth::create_access_token(request.db(), user.id).await?;
 37
 38    #[derive(Deserialize)]
 39    struct QueryParams {
 40        public_key: String,
 41        impersonate: Option<String>,
 42    }
 43
 44    let query_params: QueryParams = request.query().map_err(|_| {
 45        surf::Error::from_str(StatusCode::UnprocessableEntity, "invalid query params")
 46    })?;
 47
 48    let encrypted_access_token =
 49        auth::encrypt_access_token(&access_token, query_params.public_key.clone())?;
 50
 51    let mut user_id = user.id;
 52    if let Some(impersonate) = query_params.impersonate {
 53        if user.admin {
 54            if let Some(impersonated_user) =
 55                request.db().get_user_by_github_login(&impersonate).await?
 56            {
 57                user_id = impersonated_user.id;
 58            } else {
 59                return Ok(tide::Response::builder(StatusCode::UnprocessableEntity)
 60                    .body(format!(
 61                        "Can't impersonate non-existent user {}",
 62                        impersonate
 63                    ))
 64                    .build());
 65            }
 66        } else {
 67            return Ok(tide::Response::builder(StatusCode::Unauthorized)
 68                .body(format!(
 69                    "Can't impersonate user {} because the real user isn't an admin",
 70                    impersonate
 71                ))
 72                .build());
 73        }
 74    }
 75
 76    Ok(tide::Response::builder(StatusCode::Ok)
 77        .body(json!({"user_id": user_id, "encrypted_access_token": encrypted_access_token}))
 78        .build())
 79}
 80
 81#[async_trait]
 82pub trait RequestExt {
 83    async fn require_token(&self) -> tide::Result<()>;
 84}
 85
 86#[async_trait]
 87impl RequestExt for Request {
 88    async fn require_token(&self) -> tide::Result<()> {
 89        let token = self
 90            .header("Authorization")
 91            .and_then(|header| header.get(0))
 92            .and_then(|header| header.as_str().strip_prefix("token "))
 93            .ok_or_else(|| surf::Error::from_str(403, "invalid authorization header"))?;
 94
 95        if token == self.state().config.api_token {
 96            Ok(())
 97        } else {
 98            Err(tide::Error::from_str(403, "invalid authorization token"))
 99        }
100    }
101}