api.rs

  1use crate::{auth, db::UserId, 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").get(get_users);
 10    app.at("/users").post(create_user);
 11    app.at("/users/:id").put(update_user);
 12    app.at("/users/:github_login").get(get_user);
 13    app.at("/users/:github_login/access_tokens")
 14        .post(create_access_token);
 15}
 16
 17async fn get_user(request: Request) -> tide::Result {
 18    request.require_token().await?;
 19
 20    let user = request
 21        .db()
 22        .get_user_by_github_login(request.param("github_login")?)
 23        .await?
 24        .ok_or_else(|| surf::Error::from_str(404, "user not found"))?;
 25
 26    Ok(tide::Response::builder(StatusCode::Ok)
 27        .body(tide::Body::from_json(&user)?)
 28        .build())
 29}
 30
 31async fn get_users(request: Request) -> tide::Result {
 32    request.require_token().await?;
 33
 34    let users = request.db().get_all_users().await?;
 35
 36    Ok(tide::Response::builder(StatusCode::Ok)
 37        .body(tide::Body::from_json(&users)?)
 38        .build())
 39}
 40
 41async fn create_user(mut request: Request) -> tide::Result {
 42    request.require_token().await?;
 43
 44    #[derive(Deserialize)]
 45    struct Params {
 46        github_login: String,
 47        admin: bool,
 48    }
 49    let params = request.body_json::<Params>().await?;
 50
 51    let user_id = request
 52        .db()
 53        .create_user(&params.github_login, params.admin)
 54        .await?;
 55
 56    let user = request.db().get_user_by_id(user_id).await?.ok_or_else(|| {
 57        surf::Error::from_str(
 58            StatusCode::InternalServerError,
 59            "couldn't find the user we just created",
 60        )
 61    })?;
 62
 63    Ok(tide::Response::builder(StatusCode::Ok)
 64        .body(tide::Body::from_json(&user)?)
 65        .build())
 66}
 67
 68async fn update_user(mut request: Request) -> tide::Result {
 69    request.require_token().await?;
 70
 71    #[derive(Deserialize)]
 72    struct Params {
 73        admin: bool,
 74    }
 75    let user_id = UserId(
 76        request
 77            .param("id")?
 78            .parse::<i32>()
 79            .map_err(|error| surf::Error::from_str(StatusCode::BadRequest, error.to_string()))?,
 80    );
 81    let params = request.body_json::<Params>().await?;
 82
 83    request
 84        .db()
 85        .set_user_is_admin(user_id, params.admin)
 86        .await?;
 87
 88    Ok(tide::Response::builder(StatusCode::Ok).build())
 89}
 90
 91async fn create_access_token(request: Request) -> tide::Result {
 92    request.require_token().await?;
 93
 94    let user = request
 95        .db()
 96        .get_user_by_github_login(request.param("github_login")?)
 97        .await?
 98        .ok_or_else(|| surf::Error::from_str(StatusCode::NotFound, "user not found"))?;
 99    let access_token = auth::create_access_token(request.db(), user.id).await?;
100
101    #[derive(Deserialize)]
102    struct QueryParams {
103        public_key: String,
104        impersonate: Option<String>,
105    }
106
107    let query_params: QueryParams = request.query().map_err(|_| {
108        surf::Error::from_str(StatusCode::UnprocessableEntity, "invalid query params")
109    })?;
110
111    let encrypted_access_token =
112        auth::encrypt_access_token(&access_token, query_params.public_key.clone())?;
113
114    let mut user_id = user.id;
115    if let Some(impersonate) = query_params.impersonate {
116        if user.admin {
117            if let Some(impersonated_user) =
118                request.db().get_user_by_github_login(&impersonate).await?
119            {
120                user_id = impersonated_user.id;
121            } else {
122                return Ok(tide::Response::builder(StatusCode::UnprocessableEntity)
123                    .body(format!(
124                        "Can't impersonate non-existent user {}",
125                        impersonate
126                    ))
127                    .build());
128            }
129        } else {
130            return Ok(tide::Response::builder(StatusCode::Unauthorized)
131                .body(format!(
132                    "Can't impersonate user {} because the real user isn't an admin",
133                    impersonate
134                ))
135                .build());
136        }
137    }
138
139    Ok(tide::Response::builder(StatusCode::Ok)
140        .body(json!({"user_id": user_id, "encrypted_access_token": encrypted_access_token}))
141        .build())
142}
143
144#[async_trait]
145pub trait RequestExt {
146    async fn require_token(&self) -> tide::Result<()>;
147}
148
149#[async_trait]
150impl RequestExt for Request {
151    async fn require_token(&self) -> tide::Result<()> {
152        let token = self
153            .header("Authorization")
154            .and_then(|header| header.get(0))
155            .and_then(|header| header.as_str().strip_prefix("token "))
156            .ok_or_else(|| surf::Error::from_str(403, "invalid authorization header"))?;
157
158        if token == self.state().config.api_token {
159            Ok(())
160        } else {
161            Err(tide::Error::from_str(403, "invalid authorization token"))
162        }
163    }
164}