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