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