garage_api_admin/
admin_token.rs

1use std::sync::Arc;
2
3use chrono::{DateTime, Utc};
4
5use garage_table::*;
6use garage_util::time::now_msec;
7
8use garage_model::admin_token_table::*;
9use garage_model::garage::Garage;
10
11use crate::api::*;
12use crate::error::*;
13use crate::{Admin, RequestHandler};
14
15impl RequestHandler for ListAdminTokensRequest {
16	type Response = ListAdminTokensResponse;
17
18	async fn handle(
19		self,
20		garage: &Arc<Garage>,
21		_admin: &Admin,
22	) -> Result<ListAdminTokensResponse, Error> {
23		let now = now_msec();
24
25		let mut res = garage
26			.admin_token_table
27			.get_range(
28				&EmptyKey,
29				None,
30				Some(KeyFilter::Deleted(DeletedFilter::NotDeleted)),
31				10000,
32				EnumerationOrder::Forward,
33			)
34			.await?
35			.iter()
36			.map(|t| admin_token_info_results(t, now))
37			.collect::<Vec<_>>();
38
39		if garage.config.admin.metrics_token.is_some() {
40			res.insert(
41				0,
42				GetAdminTokenInfoResponse {
43					id: None,
44					created: None,
45					name: "metrics_token (from daemon configuration)".into(),
46					expiration: None,
47					expired: false,
48					scope: vec!["Metrics".into()],
49				},
50			);
51		}
52
53		if garage.config.admin.admin_token.is_some() {
54			res.insert(
55				0,
56				GetAdminTokenInfoResponse {
57					id: None,
58					created: None,
59					name: "admin_token (from daemon configuration)".into(),
60					expiration: None,
61					expired: false,
62					scope: vec!["*".into()],
63				},
64			);
65		}
66
67		Ok(ListAdminTokensResponse(res))
68	}
69}
70
71impl RequestHandler for GetAdminTokenInfoRequest {
72	type Response = GetAdminTokenInfoResponse;
73
74	async fn handle(
75		self,
76		garage: &Arc<Garage>,
77		_admin: &Admin,
78	) -> Result<GetAdminTokenInfoResponse, Error> {
79		let token = match (self.id, self.search) {
80			(Some(id), None) => get_existing_admin_token(garage, &id).await?,
81			(None, Some(search)) => {
82				let candidates = garage
83					.admin_token_table
84					.get_range(
85						&EmptyKey,
86						None,
87						Some(KeyFilter::MatchesAndNotDeleted(search.to_string())),
88						10,
89						EnumerationOrder::Forward,
90					)
91					.await?
92					.into_iter()
93					.collect::<Vec<_>>();
94				if candidates.len() != 1 {
95					return Err(Error::bad_request(format!(
96						"{} matching admin tokens",
97						candidates.len()
98					)));
99				}
100				candidates.into_iter().next().unwrap()
101			}
102			_ => {
103				return Err(Error::bad_request(
104					"Either id or search must be provided (but not both)",
105				));
106			}
107		};
108
109		Ok(admin_token_info_results(&token, now_msec()))
110	}
111}
112
113impl RequestHandler for CreateAdminTokenRequest {
114	type Response = CreateAdminTokenResponse;
115
116	async fn handle(
117		self,
118		garage: &Arc<Garage>,
119		_admin: &Admin,
120	) -> Result<CreateAdminTokenResponse, Error> {
121		let (mut token, secret) = if self.0.name.is_some() {
122			AdminApiToken::new("")
123		} else {
124			AdminApiToken::new(&format!("token_{}", Utc::now().format("%Y%m%d_%H%M")))
125		};
126
127		apply_token_updates(&mut token, self.0)?;
128
129		garage.admin_token_table.insert(&token).await?;
130
131		Ok(CreateAdminTokenResponse {
132			secret_token: secret,
133			info: admin_token_info_results(&token, now_msec()),
134		})
135	}
136}
137
138impl RequestHandler for UpdateAdminTokenRequest {
139	type Response = UpdateAdminTokenResponse;
140
141	async fn handle(
142		self,
143		garage: &Arc<Garage>,
144		_admin: &Admin,
145	) -> Result<UpdateAdminTokenResponse, Error> {
146		let mut token = get_existing_admin_token(&garage, &self.id).await?;
147
148		apply_token_updates(&mut token, self.body)?;
149
150		garage.admin_token_table.insert(&token).await?;
151
152		Ok(UpdateAdminTokenResponse(admin_token_info_results(
153			&token,
154			now_msec(),
155		)))
156	}
157}
158
159impl RequestHandler for DeleteAdminTokenRequest {
160	type Response = DeleteAdminTokenResponse;
161
162	async fn handle(
163		self,
164		garage: &Arc<Garage>,
165		_admin: &Admin,
166	) -> Result<DeleteAdminTokenResponse, Error> {
167		let token = get_existing_admin_token(&garage, &self.id).await?;
168
169		garage
170			.admin_token_table
171			.insert(&AdminApiToken::delete(token.prefix))
172			.await?;
173
174		Ok(DeleteAdminTokenResponse)
175	}
176}
177
178impl RequestHandler for GetCurrentAdminTokenInfoRequest {
179	type Response = GetCurrentAdminTokenInfoResponse;
180
181	async fn handle(
182		self,
183		garage: &Arc<Garage>,
184		_admin: &Admin,
185	) -> Result<GetCurrentAdminTokenInfoResponse, Error> {
186		let now = now_msec();
187
188		if garage
189			.config
190			.admin
191			.metrics_token
192			.as_ref()
193			.is_some_and(|s| s == &self.admin_token)
194		{
195			return Ok(GetCurrentAdminTokenInfoResponse(
196				GetAdminTokenInfoResponse {
197					id: None,
198					created: None,
199					name: "metrics_token (from daemon configuration)".into(),
200					expiration: None,
201					expired: false,
202					scope: vec!["Metrics".into()],
203				},
204			));
205		}
206
207		if garage
208			.config
209			.admin
210			.admin_token
211			.as_ref()
212			.is_some_and(|s| s == &self.admin_token)
213		{
214			return Ok(GetCurrentAdminTokenInfoResponse(
215				GetAdminTokenInfoResponse {
216					id: None,
217					created: None,
218					name: "admin_token (from daemon configuration)".into(),
219					expiration: None,
220					expired: false,
221					scope: vec!["*".into()],
222				},
223			));
224		}
225
226		let (prefix, _) = self.admin_token.split_once('.').unwrap();
227		let token = get_existing_admin_token(&garage, &prefix.to_string()).await?;
228
229		Ok(GetCurrentAdminTokenInfoResponse(admin_token_info_results(
230			&token, now,
231		)))
232	}
233}
234
235// ---- helpers ----
236
237fn admin_token_info_results(token: &AdminApiToken, now: u64) -> GetAdminTokenInfoResponse {
238	let params = token.params().unwrap();
239
240	GetAdminTokenInfoResponse {
241		id: Some(token.prefix.clone()),
242		created: Some(
243			DateTime::from_timestamp_millis(params.created as i64)
244				.expect("invalid timestamp stored in db"),
245		),
246		name: params.name.get().to_string(),
247		expiration: params.expiration.get().map(|x| {
248			DateTime::from_timestamp_millis(x as i64).expect("invalid timestamp stored in db")
249		}),
250		expired: params.is_expired(now),
251		scope: params.scope.get().0.clone(),
252	}
253}
254
255async fn get_existing_admin_token(garage: &Garage, id: &String) -> Result<AdminApiToken, Error> {
256	garage
257		.admin_token_table
258		.get(&EmptyKey, id)
259		.await?
260		.filter(|k| !k.state.is_deleted())
261		.ok_or_else(|| Error::NoSuchAdminToken(id.to_string()))
262}
263
264fn apply_token_updates(
265	token: &mut AdminApiToken,
266	updates: UpdateAdminTokenRequestBody,
267) -> Result<(), Error> {
268	if updates.never_expires && updates.expiration.is_some() {
269		return Err(Error::bad_request(
270			"cannot specify `expiration` and `never_expires`",
271		));
272	}
273
274	let params = token.params_mut().unwrap();
275
276	if let Some(name) = updates.name {
277		params.name.update(name);
278	}
279	if let Some(expiration) = updates.expiration {
280		params
281			.expiration
282			.update(Some(expiration.timestamp_millis() as u64));
283	}
284	if updates.never_expires {
285		params.expiration.update(None);
286	}
287	if let Some(scope) = updates.scope {
288		params.scope.update(AdminApiTokenScope(scope));
289	}
290
291	Ok(())
292}