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(¶ms.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}