1use aide::{OperationIo, transform::TransformOperation};
7use axum::{
8 Json,
9 extract::{Query, rejection::QueryRejection},
10 response::IntoResponse,
11};
12use axum_macros::FromRequestParts;
13use hyper::StatusCode;
14use mas_axum_utils::record_error;
15use mas_storage::{Page, user::UserRegistrationTokenFilter};
16use schemars::JsonSchema;
17use serde::Deserialize;
18
19use crate::{
20 admin::{
21 call_context::CallContext,
22 model::{Resource, UserRegistrationToken},
23 params::Pagination,
24 response::{ErrorResponse, PaginatedResponse},
25 },
26 impl_from_error_for_route,
27};
28
29#[derive(FromRequestParts, Deserialize, JsonSchema, OperationIo)]
30#[serde(rename = "RegistrationTokenFilter")]
31#[aide(input_with = "Query<FilterParams>")]
32#[from_request(via(Query), rejection(RouteError))]
33pub struct FilterParams {
34 #[serde(rename = "filter[used]")]
36 used: Option<bool>,
37
38 #[serde(rename = "filter[revoked]")]
40 revoked: Option<bool>,
41
42 #[serde(rename = "filter[expired]")]
44 expired: Option<bool>,
45
46 #[serde(rename = "filter[valid]")]
51 valid: Option<bool>,
52}
53
54impl std::fmt::Display for FilterParams {
55 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56 let mut sep = '?';
57
58 if let Some(used) = self.used {
59 write!(f, "{sep}filter[used]={used}")?;
60 sep = '&';
61 }
62 if let Some(revoked) = self.revoked {
63 write!(f, "{sep}filter[revoked]={revoked}")?;
64 sep = '&';
65 }
66 if let Some(expired) = self.expired {
67 write!(f, "{sep}filter[expired]={expired}")?;
68 sep = '&';
69 }
70 if let Some(valid) = self.valid {
71 write!(f, "{sep}filter[valid]={valid}")?;
72 sep = '&';
73 }
74
75 let _ = sep;
76 Ok(())
77 }
78}
79
80#[derive(Debug, thiserror::Error, OperationIo)]
81#[aide(output_with = "Json<ErrorResponse>")]
82pub enum RouteError {
83 #[error(transparent)]
84 Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
85
86 #[error("Invalid filter parameters")]
87 InvalidFilter(#[from] QueryRejection),
88}
89
90impl_from_error_for_route!(mas_storage::RepositoryError);
91
92impl IntoResponse for RouteError {
93 fn into_response(self) -> axum::response::Response {
94 let error = ErrorResponse::from_error(&self);
95 let sentry_event_id = record_error!(self, Self::Internal(_));
96 let status = match self {
97 Self::Internal(_) => StatusCode::INTERNAL_SERVER_ERROR,
98 Self::InvalidFilter(_) => StatusCode::BAD_REQUEST,
99 };
100
101 (status, sentry_event_id, Json(error)).into_response()
102 }
103}
104
105pub fn doc(operation: TransformOperation) -> TransformOperation {
106 operation
107 .id("listUserRegistrationTokens")
108 .summary("List user registration tokens")
109 .tag("user-registration-token")
110 .response_with::<200, Json<PaginatedResponse<UserRegistrationToken>>, _>(|t| {
111 let tokens = UserRegistrationToken::samples();
112 let pagination = mas_storage::Pagination::first(tokens.len());
113 let page = Page {
114 edges: tokens.into(),
115 has_next_page: true,
116 has_previous_page: false,
117 };
118
119 t.description("Paginated response of registration tokens")
120 .example(PaginatedResponse::new(
121 page,
122 pagination,
123 42,
124 UserRegistrationToken::PATH,
125 ))
126 })
127}
128
129#[tracing::instrument(name = "handler.admin.v1.registration_tokens.list", skip_all)]
130pub async fn handler(
131 CallContext {
132 mut repo, clock, ..
133 }: CallContext,
134 Pagination(pagination): Pagination,
135 params: FilterParams,
136) -> Result<Json<PaginatedResponse<UserRegistrationToken>>, RouteError> {
137 let base = format!("{path}{params}", path = UserRegistrationToken::PATH);
138 let now = clock.now();
139 let mut filter = UserRegistrationTokenFilter::new(now);
140
141 if let Some(used) = params.used {
142 filter = filter.with_been_used(used);
143 }
144
145 if let Some(revoked) = params.revoked {
146 filter = filter.with_revoked(revoked);
147 }
148
149 if let Some(expired) = params.expired {
150 filter = filter.with_expired(expired);
151 }
152
153 if let Some(valid) = params.valid {
154 filter = filter.with_valid(valid);
155 }
156
157 let page = repo
158 .user_registration_token()
159 .list(filter, pagination)
160 .await?;
161 let count = repo.user_registration_token().count(filter).await?;
162
163 Ok(Json(PaginatedResponse::new(
164 page.map(|token| UserRegistrationToken::new(token, now)),
165 pagination,
166 count,
167 &base,
168 )))
169}
170
171#[cfg(test)]
172mod tests {
173 use chrono::Duration;
174 use hyper::{Request, StatusCode};
175 use mas_storage::Clock as _;
176 use sqlx::PgPool;
177
178 use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
179
180 async fn create_test_tokens(state: &mut TestState) {
181 let mut repo = state.repository().await.unwrap();
182
183 repo.user_registration_token()
185 .add(
186 &mut state.rng(),
187 &state.clock,
188 "token_unused".to_owned(),
189 Some(10),
190 None,
191 )
192 .await
193 .unwrap();
194
195 let token = repo
197 .user_registration_token()
198 .add(
199 &mut state.rng(),
200 &state.clock,
201 "token_used".to_owned(),
202 Some(10),
203 None,
204 )
205 .await
206 .unwrap();
207 repo.user_registration_token()
208 .use_token(&state.clock, token)
209 .await
210 .unwrap();
211
212 let token = repo
214 .user_registration_token()
215 .add(
216 &mut state.rng(),
217 &state.clock,
218 "token_revoked".to_owned(),
219 Some(10),
220 None,
221 )
222 .await
223 .unwrap();
224 repo.user_registration_token()
225 .revoke(&state.clock, token)
226 .await
227 .unwrap();
228
229 let token = repo
231 .user_registration_token()
232 .add(
233 &mut state.rng(),
234 &state.clock,
235 "token_used_revoked".to_owned(),
236 Some(10),
237 None,
238 )
239 .await
240 .unwrap();
241 let token = repo
242 .user_registration_token()
243 .use_token(&state.clock, token)
244 .await
245 .unwrap();
246 repo.user_registration_token()
247 .revoke(&state.clock, token)
248 .await
249 .unwrap();
250
251 let expires_at = state.clock.now() - Duration::try_days(1).unwrap();
253 repo.user_registration_token()
254 .add(
255 &mut state.rng(),
256 &state.clock,
257 "token_expired".to_owned(),
258 Some(5),
259 Some(expires_at),
260 )
261 .await
262 .unwrap();
263
264 repo.save().await.unwrap();
265 }
266
267 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
268 async fn test_list_all_tokens(pool: PgPool) {
269 setup();
270 let mut state = TestState::from_pool(pool).await.unwrap();
271 let admin_token = state.token_with_scope("urn:mas:admin").await;
272 create_test_tokens(&mut state).await;
273
274 let request = Request::get("/api/admin/v1/user-registration-tokens")
275 .bearer(&admin_token)
276 .empty();
277 let response = state.request(request).await;
278 response.assert_status(StatusCode::OK);
279
280 let body: serde_json::Value = response.json();
281 insta::assert_json_snapshot!(body, @r#"
282 {
283 "meta": {
284 "count": 5
285 },
286 "data": [
287 {
288 "type": "user-registration_token",
289 "id": "01FSHN9AG064K8BYZXSY5G511Z",
290 "attributes": {
291 "token": "token_expired",
292 "valid": false,
293 "usage_limit": 5,
294 "times_used": 0,
295 "created_at": "2022-01-16T14:40:00Z",
296 "last_used_at": null,
297 "expires_at": "2022-01-15T14:40:00Z",
298 "revoked_at": null
299 },
300 "links": {
301 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG064K8BYZXSY5G511Z"
302 }
303 },
304 {
305 "type": "user-registration_token",
306 "id": "01FSHN9AG07HNEZXNQM2KNBNF6",
307 "attributes": {
308 "token": "token_used",
309 "valid": true,
310 "usage_limit": 10,
311 "times_used": 1,
312 "created_at": "2022-01-16T14:40:00Z",
313 "last_used_at": "2022-01-16T14:40:00Z",
314 "expires_at": null,
315 "revoked_at": null
316 },
317 "links": {
318 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG07HNEZXNQM2KNBNF6"
319 }
320 },
321 {
322 "type": "user-registration_token",
323 "id": "01FSHN9AG09AVTNSQFMSR34AJC",
324 "attributes": {
325 "token": "token_revoked",
326 "valid": false,
327 "usage_limit": 10,
328 "times_used": 0,
329 "created_at": "2022-01-16T14:40:00Z",
330 "last_used_at": null,
331 "expires_at": null,
332 "revoked_at": "2022-01-16T14:40:00Z"
333 },
334 "links": {
335 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG09AVTNSQFMSR34AJC"
336 }
337 },
338 {
339 "type": "user-registration_token",
340 "id": "01FSHN9AG0MZAA6S4AF7CTV32E",
341 "attributes": {
342 "token": "token_unused",
343 "valid": true,
344 "usage_limit": 10,
345 "times_used": 0,
346 "created_at": "2022-01-16T14:40:00Z",
347 "last_used_at": null,
348 "expires_at": null,
349 "revoked_at": null
350 },
351 "links": {
352 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E"
353 }
354 },
355 {
356 "type": "user-registration_token",
357 "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
358 "attributes": {
359 "token": "token_used_revoked",
360 "valid": false,
361 "usage_limit": 10,
362 "times_used": 1,
363 "created_at": "2022-01-16T14:40:00Z",
364 "last_used_at": "2022-01-16T14:40:00Z",
365 "expires_at": null,
366 "revoked_at": "2022-01-16T14:40:00Z"
367 },
368 "links": {
369 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
370 }
371 }
372 ],
373 "links": {
374 "self": "/api/admin/v1/user-registration-tokens?page[first]=10",
375 "first": "/api/admin/v1/user-registration-tokens?page[first]=10",
376 "last": "/api/admin/v1/user-registration-tokens?page[last]=10"
377 }
378 }
379 "#);
380 }
381
382 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
383 async fn test_filter_by_used(pool: PgPool) {
384 setup();
385 let mut state = TestState::from_pool(pool).await.unwrap();
386 let admin_token = state.token_with_scope("urn:mas:admin").await;
387 create_test_tokens(&mut state).await;
388
389 let request = Request::get("/api/admin/v1/user-registration-tokens?filter[used]=true")
391 .bearer(&admin_token)
392 .empty();
393 let response = state.request(request).await;
394 response.assert_status(StatusCode::OK);
395
396 let body: serde_json::Value = response.json();
397 insta::assert_json_snapshot!(body, @r#"
398 {
399 "meta": {
400 "count": 2
401 },
402 "data": [
403 {
404 "type": "user-registration_token",
405 "id": "01FSHN9AG07HNEZXNQM2KNBNF6",
406 "attributes": {
407 "token": "token_used",
408 "valid": true,
409 "usage_limit": 10,
410 "times_used": 1,
411 "created_at": "2022-01-16T14:40:00Z",
412 "last_used_at": "2022-01-16T14:40:00Z",
413 "expires_at": null,
414 "revoked_at": null
415 },
416 "links": {
417 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG07HNEZXNQM2KNBNF6"
418 }
419 },
420 {
421 "type": "user-registration_token",
422 "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
423 "attributes": {
424 "token": "token_used_revoked",
425 "valid": false,
426 "usage_limit": 10,
427 "times_used": 1,
428 "created_at": "2022-01-16T14:40:00Z",
429 "last_used_at": "2022-01-16T14:40:00Z",
430 "expires_at": null,
431 "revoked_at": "2022-01-16T14:40:00Z"
432 },
433 "links": {
434 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
435 }
436 }
437 ],
438 "links": {
439 "self": "/api/admin/v1/user-registration-tokens?filter[used]=true&page[first]=10",
440 "first": "/api/admin/v1/user-registration-tokens?filter[used]=true&page[first]=10",
441 "last": "/api/admin/v1/user-registration-tokens?filter[used]=true&page[last]=10"
442 }
443 }
444 "#);
445
446 let request = Request::get("/api/admin/v1/user-registration-tokens?filter[used]=false")
448 .bearer(&admin_token)
449 .empty();
450 let response = state.request(request).await;
451 response.assert_status(StatusCode::OK);
452
453 let body: serde_json::Value = response.json();
454 insta::assert_json_snapshot!(body, @r#"
455 {
456 "meta": {
457 "count": 3
458 },
459 "data": [
460 {
461 "type": "user-registration_token",
462 "id": "01FSHN9AG064K8BYZXSY5G511Z",
463 "attributes": {
464 "token": "token_expired",
465 "valid": false,
466 "usage_limit": 5,
467 "times_used": 0,
468 "created_at": "2022-01-16T14:40:00Z",
469 "last_used_at": null,
470 "expires_at": "2022-01-15T14:40:00Z",
471 "revoked_at": null
472 },
473 "links": {
474 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG064K8BYZXSY5G511Z"
475 }
476 },
477 {
478 "type": "user-registration_token",
479 "id": "01FSHN9AG09AVTNSQFMSR34AJC",
480 "attributes": {
481 "token": "token_revoked",
482 "valid": false,
483 "usage_limit": 10,
484 "times_used": 0,
485 "created_at": "2022-01-16T14:40:00Z",
486 "last_used_at": null,
487 "expires_at": null,
488 "revoked_at": "2022-01-16T14:40:00Z"
489 },
490 "links": {
491 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG09AVTNSQFMSR34AJC"
492 }
493 },
494 {
495 "type": "user-registration_token",
496 "id": "01FSHN9AG0MZAA6S4AF7CTV32E",
497 "attributes": {
498 "token": "token_unused",
499 "valid": true,
500 "usage_limit": 10,
501 "times_used": 0,
502 "created_at": "2022-01-16T14:40:00Z",
503 "last_used_at": null,
504 "expires_at": null,
505 "revoked_at": null
506 },
507 "links": {
508 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E"
509 }
510 }
511 ],
512 "links": {
513 "self": "/api/admin/v1/user-registration-tokens?filter[used]=false&page[first]=10",
514 "first": "/api/admin/v1/user-registration-tokens?filter[used]=false&page[first]=10",
515 "last": "/api/admin/v1/user-registration-tokens?filter[used]=false&page[last]=10"
516 }
517 }
518 "#);
519 }
520
521 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
522 async fn test_filter_by_revoked(pool: PgPool) {
523 setup();
524 let mut state = TestState::from_pool(pool).await.unwrap();
525 let admin_token = state.token_with_scope("urn:mas:admin").await;
526 create_test_tokens(&mut state).await;
527
528 let request = Request::get("/api/admin/v1/user-registration-tokens?filter[revoked]=true")
530 .bearer(&admin_token)
531 .empty();
532 let response = state.request(request).await;
533 response.assert_status(StatusCode::OK);
534
535 let body: serde_json::Value = response.json();
536 insta::assert_json_snapshot!(body, @r#"
537 {
538 "meta": {
539 "count": 2
540 },
541 "data": [
542 {
543 "type": "user-registration_token",
544 "id": "01FSHN9AG09AVTNSQFMSR34AJC",
545 "attributes": {
546 "token": "token_revoked",
547 "valid": false,
548 "usage_limit": 10,
549 "times_used": 0,
550 "created_at": "2022-01-16T14:40:00Z",
551 "last_used_at": null,
552 "expires_at": null,
553 "revoked_at": "2022-01-16T14:40:00Z"
554 },
555 "links": {
556 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG09AVTNSQFMSR34AJC"
557 }
558 },
559 {
560 "type": "user-registration_token",
561 "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
562 "attributes": {
563 "token": "token_used_revoked",
564 "valid": false,
565 "usage_limit": 10,
566 "times_used": 1,
567 "created_at": "2022-01-16T14:40:00Z",
568 "last_used_at": "2022-01-16T14:40:00Z",
569 "expires_at": null,
570 "revoked_at": "2022-01-16T14:40:00Z"
571 },
572 "links": {
573 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
574 }
575 }
576 ],
577 "links": {
578 "self": "/api/admin/v1/user-registration-tokens?filter[revoked]=true&page[first]=10",
579 "first": "/api/admin/v1/user-registration-tokens?filter[revoked]=true&page[first]=10",
580 "last": "/api/admin/v1/user-registration-tokens?filter[revoked]=true&page[last]=10"
581 }
582 }
583 "#);
584
585 let request = Request::get("/api/admin/v1/user-registration-tokens?filter[revoked]=false")
587 .bearer(&admin_token)
588 .empty();
589 let response = state.request(request).await;
590 response.assert_status(StatusCode::OK);
591
592 let body: serde_json::Value = response.json();
593 insta::assert_json_snapshot!(body, @r#"
594 {
595 "meta": {
596 "count": 3
597 },
598 "data": [
599 {
600 "type": "user-registration_token",
601 "id": "01FSHN9AG064K8BYZXSY5G511Z",
602 "attributes": {
603 "token": "token_expired",
604 "valid": false,
605 "usage_limit": 5,
606 "times_used": 0,
607 "created_at": "2022-01-16T14:40:00Z",
608 "last_used_at": null,
609 "expires_at": "2022-01-15T14:40:00Z",
610 "revoked_at": null
611 },
612 "links": {
613 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG064K8BYZXSY5G511Z"
614 }
615 },
616 {
617 "type": "user-registration_token",
618 "id": "01FSHN9AG07HNEZXNQM2KNBNF6",
619 "attributes": {
620 "token": "token_used",
621 "valid": true,
622 "usage_limit": 10,
623 "times_used": 1,
624 "created_at": "2022-01-16T14:40:00Z",
625 "last_used_at": "2022-01-16T14:40:00Z",
626 "expires_at": null,
627 "revoked_at": null
628 },
629 "links": {
630 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG07HNEZXNQM2KNBNF6"
631 }
632 },
633 {
634 "type": "user-registration_token",
635 "id": "01FSHN9AG0MZAA6S4AF7CTV32E",
636 "attributes": {
637 "token": "token_unused",
638 "valid": true,
639 "usage_limit": 10,
640 "times_used": 0,
641 "created_at": "2022-01-16T14:40:00Z",
642 "last_used_at": null,
643 "expires_at": null,
644 "revoked_at": null
645 },
646 "links": {
647 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E"
648 }
649 }
650 ],
651 "links": {
652 "self": "/api/admin/v1/user-registration-tokens?filter[revoked]=false&page[first]=10",
653 "first": "/api/admin/v1/user-registration-tokens?filter[revoked]=false&page[first]=10",
654 "last": "/api/admin/v1/user-registration-tokens?filter[revoked]=false&page[last]=10"
655 }
656 }
657 "#);
658 }
659
660 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
661 async fn test_filter_by_expired(pool: PgPool) {
662 setup();
663 let mut state = TestState::from_pool(pool).await.unwrap();
664 let admin_token = state.token_with_scope("urn:mas:admin").await;
665 create_test_tokens(&mut state).await;
666
667 let request = Request::get("/api/admin/v1/user-registration-tokens?filter[expired]=true")
669 .bearer(&admin_token)
670 .empty();
671 let response = state.request(request).await;
672 response.assert_status(StatusCode::OK);
673
674 let body: serde_json::Value = response.json();
675 insta::assert_json_snapshot!(body, @r#"
676 {
677 "meta": {
678 "count": 1
679 },
680 "data": [
681 {
682 "type": "user-registration_token",
683 "id": "01FSHN9AG064K8BYZXSY5G511Z",
684 "attributes": {
685 "token": "token_expired",
686 "valid": false,
687 "usage_limit": 5,
688 "times_used": 0,
689 "created_at": "2022-01-16T14:40:00Z",
690 "last_used_at": null,
691 "expires_at": "2022-01-15T14:40:00Z",
692 "revoked_at": null
693 },
694 "links": {
695 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG064K8BYZXSY5G511Z"
696 }
697 }
698 ],
699 "links": {
700 "self": "/api/admin/v1/user-registration-tokens?filter[expired]=true&page[first]=10",
701 "first": "/api/admin/v1/user-registration-tokens?filter[expired]=true&page[first]=10",
702 "last": "/api/admin/v1/user-registration-tokens?filter[expired]=true&page[last]=10"
703 }
704 }
705 "#);
706
707 let request = Request::get("/api/admin/v1/user-registration-tokens?filter[expired]=false")
709 .bearer(&admin_token)
710 .empty();
711 let response = state.request(request).await;
712 response.assert_status(StatusCode::OK);
713
714 let body: serde_json::Value = response.json();
715 insta::assert_json_snapshot!(body, @r#"
716 {
717 "meta": {
718 "count": 4
719 },
720 "data": [
721 {
722 "type": "user-registration_token",
723 "id": "01FSHN9AG07HNEZXNQM2KNBNF6",
724 "attributes": {
725 "token": "token_used",
726 "valid": true,
727 "usage_limit": 10,
728 "times_used": 1,
729 "created_at": "2022-01-16T14:40:00Z",
730 "last_used_at": "2022-01-16T14:40:00Z",
731 "expires_at": null,
732 "revoked_at": null
733 },
734 "links": {
735 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG07HNEZXNQM2KNBNF6"
736 }
737 },
738 {
739 "type": "user-registration_token",
740 "id": "01FSHN9AG09AVTNSQFMSR34AJC",
741 "attributes": {
742 "token": "token_revoked",
743 "valid": false,
744 "usage_limit": 10,
745 "times_used": 0,
746 "created_at": "2022-01-16T14:40:00Z",
747 "last_used_at": null,
748 "expires_at": null,
749 "revoked_at": "2022-01-16T14:40:00Z"
750 },
751 "links": {
752 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG09AVTNSQFMSR34AJC"
753 }
754 },
755 {
756 "type": "user-registration_token",
757 "id": "01FSHN9AG0MZAA6S4AF7CTV32E",
758 "attributes": {
759 "token": "token_unused",
760 "valid": true,
761 "usage_limit": 10,
762 "times_used": 0,
763 "created_at": "2022-01-16T14:40:00Z",
764 "last_used_at": null,
765 "expires_at": null,
766 "revoked_at": null
767 },
768 "links": {
769 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E"
770 }
771 },
772 {
773 "type": "user-registration_token",
774 "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
775 "attributes": {
776 "token": "token_used_revoked",
777 "valid": false,
778 "usage_limit": 10,
779 "times_used": 1,
780 "created_at": "2022-01-16T14:40:00Z",
781 "last_used_at": "2022-01-16T14:40:00Z",
782 "expires_at": null,
783 "revoked_at": "2022-01-16T14:40:00Z"
784 },
785 "links": {
786 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
787 }
788 }
789 ],
790 "links": {
791 "self": "/api/admin/v1/user-registration-tokens?filter[expired]=false&page[first]=10",
792 "first": "/api/admin/v1/user-registration-tokens?filter[expired]=false&page[first]=10",
793 "last": "/api/admin/v1/user-registration-tokens?filter[expired]=false&page[last]=10"
794 }
795 }
796 "#);
797 }
798
799 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
800 async fn test_filter_by_valid(pool: PgPool) {
801 setup();
802 let mut state = TestState::from_pool(pool).await.unwrap();
803 let admin_token = state.token_with_scope("urn:mas:admin").await;
804 create_test_tokens(&mut state).await;
805
806 let request = Request::get("/api/admin/v1/user-registration-tokens?filter[valid]=true")
808 .bearer(&admin_token)
809 .empty();
810 let response = state.request(request).await;
811 response.assert_status(StatusCode::OK);
812
813 let body: serde_json::Value = response.json();
814 insta::assert_json_snapshot!(body, @r#"
815 {
816 "meta": {
817 "count": 2
818 },
819 "data": [
820 {
821 "type": "user-registration_token",
822 "id": "01FSHN9AG07HNEZXNQM2KNBNF6",
823 "attributes": {
824 "token": "token_used",
825 "valid": true,
826 "usage_limit": 10,
827 "times_used": 1,
828 "created_at": "2022-01-16T14:40:00Z",
829 "last_used_at": "2022-01-16T14:40:00Z",
830 "expires_at": null,
831 "revoked_at": null
832 },
833 "links": {
834 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG07HNEZXNQM2KNBNF6"
835 }
836 },
837 {
838 "type": "user-registration_token",
839 "id": "01FSHN9AG0MZAA6S4AF7CTV32E",
840 "attributes": {
841 "token": "token_unused",
842 "valid": true,
843 "usage_limit": 10,
844 "times_used": 0,
845 "created_at": "2022-01-16T14:40:00Z",
846 "last_used_at": null,
847 "expires_at": null,
848 "revoked_at": null
849 },
850 "links": {
851 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E"
852 }
853 }
854 ],
855 "links": {
856 "self": "/api/admin/v1/user-registration-tokens?filter[valid]=true&page[first]=10",
857 "first": "/api/admin/v1/user-registration-tokens?filter[valid]=true&page[first]=10",
858 "last": "/api/admin/v1/user-registration-tokens?filter[valid]=true&page[last]=10"
859 }
860 }
861 "#);
862
863 let request = Request::get("/api/admin/v1/user-registration-tokens?filter[valid]=false")
865 .bearer(&admin_token)
866 .empty();
867 let response = state.request(request).await;
868 response.assert_status(StatusCode::OK);
869
870 let body: serde_json::Value = response.json();
871 insta::assert_json_snapshot!(body, @r#"
872 {
873 "meta": {
874 "count": 3
875 },
876 "data": [
877 {
878 "type": "user-registration_token",
879 "id": "01FSHN9AG064K8BYZXSY5G511Z",
880 "attributes": {
881 "token": "token_expired",
882 "valid": false,
883 "usage_limit": 5,
884 "times_used": 0,
885 "created_at": "2022-01-16T14:40:00Z",
886 "last_used_at": null,
887 "expires_at": "2022-01-15T14:40:00Z",
888 "revoked_at": null
889 },
890 "links": {
891 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG064K8BYZXSY5G511Z"
892 }
893 },
894 {
895 "type": "user-registration_token",
896 "id": "01FSHN9AG09AVTNSQFMSR34AJC",
897 "attributes": {
898 "token": "token_revoked",
899 "valid": false,
900 "usage_limit": 10,
901 "times_used": 0,
902 "created_at": "2022-01-16T14:40:00Z",
903 "last_used_at": null,
904 "expires_at": null,
905 "revoked_at": "2022-01-16T14:40:00Z"
906 },
907 "links": {
908 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG09AVTNSQFMSR34AJC"
909 }
910 },
911 {
912 "type": "user-registration_token",
913 "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
914 "attributes": {
915 "token": "token_used_revoked",
916 "valid": false,
917 "usage_limit": 10,
918 "times_used": 1,
919 "created_at": "2022-01-16T14:40:00Z",
920 "last_used_at": "2022-01-16T14:40:00Z",
921 "expires_at": null,
922 "revoked_at": "2022-01-16T14:40:00Z"
923 },
924 "links": {
925 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
926 }
927 }
928 ],
929 "links": {
930 "self": "/api/admin/v1/user-registration-tokens?filter[valid]=false&page[first]=10",
931 "first": "/api/admin/v1/user-registration-tokens?filter[valid]=false&page[first]=10",
932 "last": "/api/admin/v1/user-registration-tokens?filter[valid]=false&page[last]=10"
933 }
934 }
935 "#);
936 }
937
938 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
939 async fn test_combined_filters(pool: PgPool) {
940 setup();
941 let mut state = TestState::from_pool(pool).await.unwrap();
942 let admin_token = state.token_with_scope("urn:mas:admin").await;
943 create_test_tokens(&mut state).await;
944
945 let request = Request::get(
947 "/api/admin/v1/user-registration-tokens?filter[used]=true&filter[revoked]=true",
948 )
949 .bearer(&admin_token)
950 .empty();
951 let response = state.request(request).await;
952 response.assert_status(StatusCode::OK);
953
954 let body: serde_json::Value = response.json();
955 insta::assert_json_snapshot!(body, @r#"
956 {
957 "meta": {
958 "count": 1
959 },
960 "data": [
961 {
962 "type": "user-registration_token",
963 "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
964 "attributes": {
965 "token": "token_used_revoked",
966 "valid": false,
967 "usage_limit": 10,
968 "times_used": 1,
969 "created_at": "2022-01-16T14:40:00Z",
970 "last_used_at": "2022-01-16T14:40:00Z",
971 "expires_at": null,
972 "revoked_at": "2022-01-16T14:40:00Z"
973 },
974 "links": {
975 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
976 }
977 }
978 ],
979 "links": {
980 "self": "/api/admin/v1/user-registration-tokens?filter[used]=true&filter[revoked]=true&page[first]=10",
981 "first": "/api/admin/v1/user-registration-tokens?filter[used]=true&filter[revoked]=true&page[first]=10",
982 "last": "/api/admin/v1/user-registration-tokens?filter[used]=true&filter[revoked]=true&page[last]=10"
983 }
984 }
985 "#);
986 }
987
988 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
989 async fn test_pagination(pool: PgPool) {
990 setup();
991 let mut state = TestState::from_pool(pool).await.unwrap();
992 let admin_token = state.token_with_scope("urn:mas:admin").await;
993 create_test_tokens(&mut state).await;
994
995 let request = Request::get("/api/admin/v1/user-registration-tokens?page[first]=2")
997 .bearer(&admin_token)
998 .empty();
999 let response = state.request(request).await;
1000 response.assert_status(StatusCode::OK);
1001
1002 let body: serde_json::Value = response.json();
1003 insta::assert_json_snapshot!(body, @r#"
1004 {
1005 "meta": {
1006 "count": 5
1007 },
1008 "data": [
1009 {
1010 "type": "user-registration_token",
1011 "id": "01FSHN9AG064K8BYZXSY5G511Z",
1012 "attributes": {
1013 "token": "token_expired",
1014 "valid": false,
1015 "usage_limit": 5,
1016 "times_used": 0,
1017 "created_at": "2022-01-16T14:40:00Z",
1018 "last_used_at": null,
1019 "expires_at": "2022-01-15T14:40:00Z",
1020 "revoked_at": null
1021 },
1022 "links": {
1023 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG064K8BYZXSY5G511Z"
1024 }
1025 },
1026 {
1027 "type": "user-registration_token",
1028 "id": "01FSHN9AG07HNEZXNQM2KNBNF6",
1029 "attributes": {
1030 "token": "token_used",
1031 "valid": true,
1032 "usage_limit": 10,
1033 "times_used": 1,
1034 "created_at": "2022-01-16T14:40:00Z",
1035 "last_used_at": "2022-01-16T14:40:00Z",
1036 "expires_at": null,
1037 "revoked_at": null
1038 },
1039 "links": {
1040 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG07HNEZXNQM2KNBNF6"
1041 }
1042 }
1043 ],
1044 "links": {
1045 "self": "/api/admin/v1/user-registration-tokens?page[first]=2",
1046 "first": "/api/admin/v1/user-registration-tokens?page[first]=2",
1047 "last": "/api/admin/v1/user-registration-tokens?page[last]=2",
1048 "next": "/api/admin/v1/user-registration-tokens?page[after]=01FSHN9AG07HNEZXNQM2KNBNF6&page[first]=2"
1049 }
1050 }
1051 "#);
1052
1053 let request = Request::get("/api/admin/v1/user-registration-tokens?page[after]=01FSHN9AG07HNEZXNQM2KNBNF6&page[first]=2")
1055 .bearer(&admin_token)
1056 .empty();
1057 let response = state.request(request).await;
1058 response.assert_status(StatusCode::OK);
1059
1060 let body: serde_json::Value = response.json();
1061 insta::assert_json_snapshot!(body, @r#"
1062 {
1063 "meta": {
1064 "count": 5
1065 },
1066 "data": [
1067 {
1068 "type": "user-registration_token",
1069 "id": "01FSHN9AG09AVTNSQFMSR34AJC",
1070 "attributes": {
1071 "token": "token_revoked",
1072 "valid": false,
1073 "usage_limit": 10,
1074 "times_used": 0,
1075 "created_at": "2022-01-16T14:40:00Z",
1076 "last_used_at": null,
1077 "expires_at": null,
1078 "revoked_at": "2022-01-16T14:40:00Z"
1079 },
1080 "links": {
1081 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG09AVTNSQFMSR34AJC"
1082 }
1083 },
1084 {
1085 "type": "user-registration_token",
1086 "id": "01FSHN9AG0MZAA6S4AF7CTV32E",
1087 "attributes": {
1088 "token": "token_unused",
1089 "valid": true,
1090 "usage_limit": 10,
1091 "times_used": 0,
1092 "created_at": "2022-01-16T14:40:00Z",
1093 "last_used_at": null,
1094 "expires_at": null,
1095 "revoked_at": null
1096 },
1097 "links": {
1098 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E"
1099 }
1100 }
1101 ],
1102 "links": {
1103 "self": "/api/admin/v1/user-registration-tokens?page[after]=01FSHN9AG07HNEZXNQM2KNBNF6&page[first]=2",
1104 "first": "/api/admin/v1/user-registration-tokens?page[first]=2",
1105 "last": "/api/admin/v1/user-registration-tokens?page[last]=2",
1106 "next": "/api/admin/v1/user-registration-tokens?page[after]=01FSHN9AG0MZAA6S4AF7CTV32E&page[first]=2"
1107 }
1108 }
1109 "#);
1110
1111 let request = Request::get("/api/admin/v1/user-registration-tokens?page[last]=1")
1113 .bearer(&admin_token)
1114 .empty();
1115 let response = state.request(request).await;
1116 response.assert_status(StatusCode::OK);
1117
1118 let body: serde_json::Value = response.json();
1119 insta::assert_json_snapshot!(body, @r#"
1120 {
1121 "meta": {
1122 "count": 5
1123 },
1124 "data": [
1125 {
1126 "type": "user-registration_token",
1127 "id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
1128 "attributes": {
1129 "token": "token_used_revoked",
1130 "valid": false,
1131 "usage_limit": 10,
1132 "times_used": 1,
1133 "created_at": "2022-01-16T14:40:00Z",
1134 "last_used_at": "2022-01-16T14:40:00Z",
1135 "expires_at": null,
1136 "revoked_at": "2022-01-16T14:40:00Z"
1137 },
1138 "links": {
1139 "self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
1140 }
1141 }
1142 ],
1143 "links": {
1144 "self": "/api/admin/v1/user-registration-tokens?page[last]=1",
1145 "first": "/api/admin/v1/user-registration-tokens?page[first]=1",
1146 "last": "/api/admin/v1/user-registration-tokens?page[last]=1",
1147 "prev": "/api/admin/v1/user-registration-tokens?page[before]=01FSHN9AG0S3ZJD8CXQ7F11KXN&page[last]=1"
1148 }
1149 }
1150 "#);
1151 }
1152
1153 #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1154 async fn test_invalid_filter(pool: PgPool) {
1155 setup();
1156 let mut state = TestState::from_pool(pool).await.unwrap();
1157 let admin_token = state.token_with_scope("urn:mas:admin").await;
1158
1159 let request = Request::get("/api/admin/v1/user-registration-tokens?filter[used]=invalid")
1161 .bearer(&admin_token)
1162 .empty();
1163 let response = state.request(request).await;
1164 response.assert_status(StatusCode::BAD_REQUEST);
1165
1166 let body: serde_json::Value = response.json();
1167 assert!(
1168 body["errors"][0]["title"]
1169 .as_str()
1170 .unwrap()
1171 .contains("Invalid filter parameters")
1172 );
1173 }
1174}