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}