Skip to content

Commit 1f096bc

Browse files
committed
fixup! Admin API to list user registration tokens
1 parent fc6b0e7 commit 1f096bc

File tree

2 files changed

+331
-2
lines changed

2 files changed

+331
-2
lines changed

crates/handlers/src/admin/v1/user_registration_tokens/list.rs

Lines changed: 299 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,17 @@ pub struct FilterParams {
3838
/// Retrieve tokens that are (or are not) revoked
3939
#[serde(rename = "filter[revoked]")]
4040
revoked: Option<bool>,
41+
42+
/// Retrieve tokens that are (or are not) expired
43+
#[serde(rename = "filter[expired]")]
44+
expired: Option<bool>,
45+
46+
/// Retrieve tokens that are (or are not) valid
47+
///
48+
/// Valid means that the token has not expired, is not revoked, and has not
49+
/// reached its usage limit.
50+
#[serde(rename = "filter[valid]")]
51+
valid: Option<bool>,
4152
}
4253

4354
impl std::fmt::Display for FilterParams {
@@ -52,6 +63,14 @@ impl std::fmt::Display for FilterParams {
5263
write!(f, "{sep}filter[revoked]={revoked}")?;
5364
sep = '&';
5465
}
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+
}
5574

5675
let _ = sep;
5776
Ok(())
@@ -109,12 +128,14 @@ pub fn doc(operation: TransformOperation) -> TransformOperation {
109128

110129
#[tracing::instrument(name = "handler.admin.v1.registration_tokens.list", skip_all)]
111130
pub async fn handler(
112-
CallContext { mut repo, .. }: CallContext,
131+
CallContext {
132+
mut repo, clock, ..
133+
}: CallContext,
113134
Pagination(pagination): Pagination,
114135
params: FilterParams,
115136
) -> Result<Json<PaginatedResponse<UserRegistrationToken>>, RouteError> {
116137
let base = format!("{path}{params}", path = UserRegistrationToken::PATH);
117-
let mut filter = UserRegistrationTokenFilter::new();
138+
let mut filter = UserRegistrationTokenFilter::new(clock.now());
118139

119140
if let Some(used) = params.used {
120141
filter = filter.with_been_used(used);
@@ -124,6 +145,14 @@ pub async fn handler(
124145
filter = filter.with_revoked(revoked);
125146
}
126147

148+
if let Some(expired) = params.expired {
149+
filter = filter.with_expired(expired);
150+
}
151+
152+
if let Some(valid) = params.valid {
153+
filter = filter.with_valid(valid);
154+
}
155+
127156
let page = repo
128157
.user_registration_token()
129158
.list(filter, pagination)
@@ -612,6 +641,274 @@ mod tests {
612641
"#);
613642
}
614643

644+
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
645+
async fn test_filter_by_expired(pool: PgPool) {
646+
setup();
647+
let mut state = TestState::from_pool(pool).await.unwrap();
648+
let admin_token = state.token_with_scope("urn:mas:admin").await;
649+
create_test_tokens(&mut state).await;
650+
651+
// Filter for expired tokens
652+
let request = Request::get("/api/admin/v1/user-registration-tokens?filter[expired]=true")
653+
.bearer(&admin_token)
654+
.empty();
655+
let response = state.request(request).await;
656+
response.assert_status(StatusCode::OK);
657+
658+
let body: serde_json::Value = response.json();
659+
insta::assert_json_snapshot!(body, @r#"
660+
{
661+
"meta": {
662+
"count": 1
663+
},
664+
"data": [
665+
{
666+
"type": "user-registration_token",
667+
"id": "01FSHN9AG064K8BYZXSY5G511Z",
668+
"attributes": {
669+
"token": "token_expired",
670+
"usage_limit": 5,
671+
"times_used": 0,
672+
"created_at": "2022-01-16T14:40:00Z",
673+
"last_used_at": null,
674+
"expires_at": "2022-01-15T14:40:00Z",
675+
"revoked_at": null
676+
},
677+
"links": {
678+
"self": "/api/admin/v1/user-registration-tokens/01FSHN9AG064K8BYZXSY5G511Z"
679+
}
680+
}
681+
],
682+
"links": {
683+
"self": "/api/admin/v1/user-registration-tokens?filter[expired]=true&page[first]=10",
684+
"first": "/api/admin/v1/user-registration-tokens?filter[expired]=true&page[first]=10",
685+
"last": "/api/admin/v1/user-registration-tokens?filter[expired]=true&page[last]=10"
686+
}
687+
}
688+
"#);
689+
690+
// Filter for non-expired tokens
691+
let request = Request::get("/api/admin/v1/user-registration-tokens?filter[expired]=false")
692+
.bearer(&admin_token)
693+
.empty();
694+
let response = state.request(request).await;
695+
response.assert_status(StatusCode::OK);
696+
697+
let body: serde_json::Value = response.json();
698+
insta::assert_json_snapshot!(body, @r#"
699+
{
700+
"meta": {
701+
"count": 4
702+
},
703+
"data": [
704+
{
705+
"type": "user-registration_token",
706+
"id": "01FSHN9AG07HNEZXNQM2KNBNF6",
707+
"attributes": {
708+
"token": "token_used",
709+
"usage_limit": 10,
710+
"times_used": 1,
711+
"created_at": "2022-01-16T14:40:00Z",
712+
"last_used_at": "2022-01-16T14:40:00Z",
713+
"expires_at": null,
714+
"revoked_at": null
715+
},
716+
"links": {
717+
"self": "/api/admin/v1/user-registration-tokens/01FSHN9AG07HNEZXNQM2KNBNF6"
718+
}
719+
},
720+
{
721+
"type": "user-registration_token",
722+
"id": "01FSHN9AG09AVTNSQFMSR34AJC",
723+
"attributes": {
724+
"token": "token_revoked",
725+
"usage_limit": 10,
726+
"times_used": 0,
727+
"created_at": "2022-01-16T14:40:00Z",
728+
"last_used_at": null,
729+
"expires_at": null,
730+
"revoked_at": "2022-01-16T14:40:00Z"
731+
},
732+
"links": {
733+
"self": "/api/admin/v1/user-registration-tokens/01FSHN9AG09AVTNSQFMSR34AJC"
734+
}
735+
},
736+
{
737+
"type": "user-registration_token",
738+
"id": "01FSHN9AG0MZAA6S4AF7CTV32E",
739+
"attributes": {
740+
"token": "token_unused",
741+
"usage_limit": 10,
742+
"times_used": 0,
743+
"created_at": "2022-01-16T14:40:00Z",
744+
"last_used_at": null,
745+
"expires_at": null,
746+
"revoked_at": null
747+
},
748+
"links": {
749+
"self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E"
750+
}
751+
},
752+
{
753+
"type": "user-registration_token",
754+
"id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
755+
"attributes": {
756+
"token": "token_used_revoked",
757+
"usage_limit": 10,
758+
"times_used": 1,
759+
"created_at": "2022-01-16T14:40:00Z",
760+
"last_used_at": "2022-01-16T14:40:00Z",
761+
"expires_at": null,
762+
"revoked_at": "2022-01-16T14:40:00Z"
763+
},
764+
"links": {
765+
"self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
766+
}
767+
}
768+
],
769+
"links": {
770+
"self": "/api/admin/v1/user-registration-tokens?filter[expired]=false&page[first]=10",
771+
"first": "/api/admin/v1/user-registration-tokens?filter[expired]=false&page[first]=10",
772+
"last": "/api/admin/v1/user-registration-tokens?filter[expired]=false&page[last]=10"
773+
}
774+
}
775+
"#);
776+
}
777+
778+
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
779+
async fn test_filter_by_valid(pool: PgPool) {
780+
setup();
781+
let mut state = TestState::from_pool(pool).await.unwrap();
782+
let admin_token = state.token_with_scope("urn:mas:admin").await;
783+
create_test_tokens(&mut state).await;
784+
785+
// Filter for valid tokens
786+
let request = Request::get("/api/admin/v1/user-registration-tokens?filter[valid]=true")
787+
.bearer(&admin_token)
788+
.empty();
789+
let response = state.request(request).await;
790+
response.assert_status(StatusCode::OK);
791+
792+
let body: serde_json::Value = response.json();
793+
insta::assert_json_snapshot!(body, @r#"
794+
{
795+
"meta": {
796+
"count": 2
797+
},
798+
"data": [
799+
{
800+
"type": "user-registration_token",
801+
"id": "01FSHN9AG07HNEZXNQM2KNBNF6",
802+
"attributes": {
803+
"token": "token_used",
804+
"usage_limit": 10,
805+
"times_used": 1,
806+
"created_at": "2022-01-16T14:40:00Z",
807+
"last_used_at": "2022-01-16T14:40:00Z",
808+
"expires_at": null,
809+
"revoked_at": null
810+
},
811+
"links": {
812+
"self": "/api/admin/v1/user-registration-tokens/01FSHN9AG07HNEZXNQM2KNBNF6"
813+
}
814+
},
815+
{
816+
"type": "user-registration_token",
817+
"id": "01FSHN9AG0MZAA6S4AF7CTV32E",
818+
"attributes": {
819+
"token": "token_unused",
820+
"usage_limit": 10,
821+
"times_used": 0,
822+
"created_at": "2022-01-16T14:40:00Z",
823+
"last_used_at": null,
824+
"expires_at": null,
825+
"revoked_at": null
826+
},
827+
"links": {
828+
"self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0MZAA6S4AF7CTV32E"
829+
}
830+
}
831+
],
832+
"links": {
833+
"self": "/api/admin/v1/user-registration-tokens?filter[valid]=true&page[first]=10",
834+
"first": "/api/admin/v1/user-registration-tokens?filter[valid]=true&page[first]=10",
835+
"last": "/api/admin/v1/user-registration-tokens?filter[valid]=true&page[last]=10"
836+
}
837+
}
838+
"#);
839+
840+
// Filter for invalid tokens
841+
let request = Request::get("/api/admin/v1/user-registration-tokens?filter[valid]=false")
842+
.bearer(&admin_token)
843+
.empty();
844+
let response = state.request(request).await;
845+
response.assert_status(StatusCode::OK);
846+
847+
let body: serde_json::Value = response.json();
848+
insta::assert_json_snapshot!(body, @r#"
849+
{
850+
"meta": {
851+
"count": 3
852+
},
853+
"data": [
854+
{
855+
"type": "user-registration_token",
856+
"id": "01FSHN9AG064K8BYZXSY5G511Z",
857+
"attributes": {
858+
"token": "token_expired",
859+
"usage_limit": 5,
860+
"times_used": 0,
861+
"created_at": "2022-01-16T14:40:00Z",
862+
"last_used_at": null,
863+
"expires_at": "2022-01-15T14:40:00Z",
864+
"revoked_at": null
865+
},
866+
"links": {
867+
"self": "/api/admin/v1/user-registration-tokens/01FSHN9AG064K8BYZXSY5G511Z"
868+
}
869+
},
870+
{
871+
"type": "user-registration_token",
872+
"id": "01FSHN9AG09AVTNSQFMSR34AJC",
873+
"attributes": {
874+
"token": "token_revoked",
875+
"usage_limit": 10,
876+
"times_used": 0,
877+
"created_at": "2022-01-16T14:40:00Z",
878+
"last_used_at": null,
879+
"expires_at": null,
880+
"revoked_at": "2022-01-16T14:40:00Z"
881+
},
882+
"links": {
883+
"self": "/api/admin/v1/user-registration-tokens/01FSHN9AG09AVTNSQFMSR34AJC"
884+
}
885+
},
886+
{
887+
"type": "user-registration_token",
888+
"id": "01FSHN9AG0S3ZJD8CXQ7F11KXN",
889+
"attributes": {
890+
"token": "token_used_revoked",
891+
"usage_limit": 10,
892+
"times_used": 1,
893+
"created_at": "2022-01-16T14:40:00Z",
894+
"last_used_at": "2022-01-16T14:40:00Z",
895+
"expires_at": null,
896+
"revoked_at": "2022-01-16T14:40:00Z"
897+
},
898+
"links": {
899+
"self": "/api/admin/v1/user-registration-tokens/01FSHN9AG0S3ZJD8CXQ7F11KXN"
900+
}
901+
}
902+
],
903+
"links": {
904+
"self": "/api/admin/v1/user-registration-tokens?filter[valid]=false&page[first]=10",
905+
"first": "/api/admin/v1/user-registration-tokens?filter[valid]=false&page[first]=10",
906+
"last": "/api/admin/v1/user-registration-tokens?filter[valid]=false&page[last]=10"
907+
}
908+
}
909+
"#);
910+
}
911+
615912
#[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
616913
async fn test_combined_filters(pool: PgPool) {
617914
setup();

docs/api/spec.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2209,6 +2209,28 @@
22092209
"nullable": true
22102210
},
22112211
"style": "form"
2212+
},
2213+
{
2214+
"in": "query",
2215+
"name": "filter[expired]",
2216+
"description": "Retrieve tokens that are (or are not) expired",
2217+
"schema": {
2218+
"description": "Retrieve tokens that are (or are not) expired",
2219+
"type": "boolean",
2220+
"nullable": true
2221+
},
2222+
"style": "form"
2223+
},
2224+
{
2225+
"in": "query",
2226+
"name": "filter[valid]",
2227+
"description": "Retrieve tokens that are (or are not) valid\n\nValid means that the token has not expired, is not revoked, and has not reached its usage limit.",
2228+
"schema": {
2229+
"description": "Retrieve tokens that are (or are not) valid\n\nValid means that the token has not expired, is not revoked, and has not reached its usage limit.",
2230+
"type": "boolean",
2231+
"nullable": true
2232+
},
2233+
"style": "form"
22122234
}
22132235
],
22142236
"responses": {
@@ -3948,6 +3970,16 @@
39483970
"description": "Retrieve tokens that are (or are not) revoked",
39493971
"type": "boolean",
39503972
"nullable": true
3973+
},
3974+
"filter[expired]": {
3975+
"description": "Retrieve tokens that are (or are not) expired",
3976+
"type": "boolean",
3977+
"nullable": true
3978+
},
3979+
"filter[valid]": {
3980+
"description": "Retrieve tokens that are (or are not) valid\n\nValid means that the token has not expired, is not revoked, and has not reached its usage limit.",
3981+
"type": "boolean",
3982+
"nullable": true
39513983
}
39523984
}
39533985
},

0 commit comments

Comments
 (0)