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(¶ms.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 let access_token = auth::create_access_token(request.db().as_ref(), user.id).await?;
115
116 #[derive(Deserialize)]
117 struct QueryParams {
118 public_key: String,
119 impersonate: Option<String>,
120 }
121
122 let query_params: QueryParams = request.query().map_err(|_| {
123 surf::Error::from_str(StatusCode::UnprocessableEntity, "invalid query params")
124 })?;
125
126 let encrypted_access_token =
127 auth::encrypt_access_token(&access_token, query_params.public_key.clone())?;
128
129 let mut user_id = user.id;
130 if let Some(impersonate) = query_params.impersonate {
131 if user.admin {
132 if let Some(impersonated_user) =
133 request.db().get_user_by_github_login(&impersonate).await?
134 {
135 user_id = impersonated_user.id;
136 } else {
137 return Ok(tide::Response::builder(StatusCode::UnprocessableEntity)
138 .body(format!(
139 "Can't impersonate non-existent user {}",
140 impersonate
141 ))
142 .build());
143 }
144 } else {
145 return Ok(tide::Response::builder(StatusCode::Unauthorized)
146 .body(format!(
147 "Can't impersonate user {} because the real user isn't an admin",
148 impersonate
149 ))
150 .build());
151 }
152 }
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}