6
6
use aide:: { NoApi , OperationIo , transform:: TransformOperation } ;
7
7
use axum:: { Json , response:: IntoResponse } ;
8
8
use hyper:: StatusCode ;
9
- use mas_storage:: { BoxRng , upstream_oauth2 :: UpstreamOAuthLinkFilter } ;
9
+ use mas_storage:: BoxRng ;
10
10
use schemars:: JsonSchema ;
11
11
use serde:: Deserialize ;
12
12
use ulid:: Ulid ;
13
13
14
14
use crate :: {
15
15
admin:: {
16
16
call_context:: CallContext ,
17
- model:: { Resource , UpstreamOAuthLink , User } ,
17
+ model:: { Resource , UpstreamOAuthLink } ,
18
18
response:: { ErrorResponse , SingleResponse } ,
19
19
} ,
20
20
impl_from_error_for_route,
@@ -26,8 +26,8 @@ pub enum RouteError {
26
26
#[ error( transparent) ]
27
27
Internal ( Box < dyn std:: error:: Error + Send + Sync + ' static > ) ,
28
28
29
- #[ error( "User ID {0} already has an upstream link for Upstream Oauth 2.0 Provider ID {1} " ) ]
30
- LinkAlreadyExists ( Ulid , Ulid ) ,
29
+ #[ error( "Upstream Oauth 2.0 Provider ID {0} with subject {1} is already linked to a user " ) ]
30
+ LinkAlreadyExists ( Ulid , String ) ,
31
31
32
32
#[ error( "User ID {0} not found" ) ]
33
33
UserNotFound ( Ulid ) ,
@@ -74,20 +74,25 @@ pub fn doc(operation: TransformOperation) -> TransformOperation {
74
74
. id ( "addUpstreamOAuthLink" )
75
75
. summary ( "Add an upstream OAuth 2.0 link" )
76
76
. tag ( "upstream-oauth-link" )
77
+ . response_with :: < 200 , Json < SingleResponse < UpstreamOAuthLink > > , _ > ( |t| {
78
+ let [ sample, ..] = UpstreamOAuthLink :: samples ( ) ;
79
+ let response = SingleResponse :: new_canonical ( sample) ;
80
+ t. description ( "An existing Upstream OAuth 2.0 link was associated to a user" )
81
+ . example ( response)
82
+ } )
77
83
. response_with :: < 201 , Json < SingleResponse < UpstreamOAuthLink > > , _ > ( |t| {
78
84
let [ sample, ..] = UpstreamOAuthLink :: samples ( ) ;
79
85
let response = SingleResponse :: new_canonical ( sample) ;
80
- t. description ( "Upstream OAuth 2.0 link was created" )
86
+ t. description ( "A new Upstream OAuth 2.0 link was created" )
81
87
. example ( response)
82
88
} )
83
89
. response_with :: < 409 , RouteError , _ > ( |t| {
84
90
let [ provider_sample, ..] = UpstreamOAuthLink :: samples ( ) ;
85
- let [ user_sample, ..] = User :: samples ( ) ;
86
91
let response = ErrorResponse :: from_error ( & RouteError :: LinkAlreadyExists (
87
- user_sample. id ( ) ,
88
92
provider_sample. id ( ) ,
93
+ String :: from ( "subject1" ) ,
89
94
) ) ;
90
- t. description ( "User already has an upstream link for this provider " )
95
+ t. description ( "The subject from the provider is already linked to another user " )
91
96
. example ( response)
92
97
} )
93
98
. response_with :: < 404 , RouteError , _ > ( |t| {
@@ -119,15 +124,28 @@ pub async fn handler(
119
124
. await ?
120
125
. ok_or ( RouteError :: ProviderNotFound ( params. provider_id ) ) ?;
121
126
122
- let filter = UpstreamOAuthLinkFilter :: new ( )
123
- . for_user ( & user)
124
- . for_provider ( & provider) ;
125
- let count = repo. upstream_oauth_link ( ) . count ( filter) . await ?;
127
+ let maybe_link = repo
128
+ . upstream_oauth_link ( )
129
+ . find_by_subject ( & provider, & params. subject )
130
+ . await ?;
131
+ if let Some ( mut link) = maybe_link {
132
+ if link. user_id . is_some ( ) {
133
+ return Err ( RouteError :: LinkAlreadyExists (
134
+ link. provider_id ,
135
+ link. subject ,
136
+ ) ) ;
137
+ }
138
+
139
+ repo. upstream_oauth_link ( )
140
+ . associate_to_user ( & link, & user)
141
+ . await ?;
142
+ link. user_id = Some ( user. id ) ;
126
143
127
- if count > 0 {
128
- return Err ( RouteError :: LinkAlreadyExists (
129
- params. user_id ,
130
- params. provider_id ,
144
+ repo. save ( ) . await ?;
145
+
146
+ return Ok ( (
147
+ StatusCode :: OK ,
148
+ Json ( SingleResponse :: new_canonical ( link. into ( ) ) ) ,
131
149
) ) ;
132
150
}
133
151
@@ -224,6 +242,77 @@ mod tests {
224
242
"### ) ;
225
243
}
226
244
245
+ #[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
246
+ async fn test_association ( pool : PgPool ) {
247
+ setup ( ) ;
248
+ let mut state = TestState :: from_pool ( pool) . await . unwrap ( ) ;
249
+ let token = state. token_with_scope ( "urn:mas:admin" ) . await ;
250
+ let mut rng = state. rng ( ) ;
251
+ let mut repo = state. repository ( ) . await . unwrap ( ) ;
252
+
253
+ let alice = repo
254
+ . user ( )
255
+ . add ( & mut rng, & state. clock , "alice" . to_owned ( ) )
256
+ . await
257
+ . unwrap ( ) ;
258
+
259
+ let provider = repo
260
+ . upstream_oauth_provider ( )
261
+ . add (
262
+ & mut rng,
263
+ & state. clock ,
264
+ test_utils:: oidc_provider_params ( "provider1" ) ,
265
+ )
266
+ . await
267
+ . unwrap ( ) ;
268
+
269
+ // Existing unfinished link
270
+ repo. upstream_oauth_link ( )
271
+ . add (
272
+ & mut rng,
273
+ & state. clock ,
274
+ & provider,
275
+ String :: from ( "subject1" ) ,
276
+ None ,
277
+ )
278
+ . await
279
+ . unwrap ( ) ;
280
+
281
+ repo. save ( ) . await . unwrap ( ) ;
282
+
283
+ let request = Request :: post ( "/api/admin/v1/upstream-oauth-links" )
284
+ . bearer ( & token)
285
+ . json ( serde_json:: json!( {
286
+ "user_id" : alice. id,
287
+ "provider_id" : provider. id,
288
+ "subject" : "subject1"
289
+ } ) ) ;
290
+ let response = state. request ( request) . await ;
291
+ response. assert_status ( StatusCode :: OK ) ;
292
+ let body: serde_json:: Value = response. json ( ) ;
293
+ assert_json_snapshot ! ( body, @r###"
294
+ {
295
+ "data": {
296
+ "type": "upstream-oauth-link",
297
+ "id": "01FSHN9AG09NMZYX8MFYH578R9",
298
+ "attributes": {
299
+ "created_at": "2022-01-16T14:40:00Z",
300
+ "provider_id": "01FSHN9AG0AJ6AC5HQ9X6H4RP4",
301
+ "subject": "subject1",
302
+ "user_id": "01FSHN9AG0MZAA6S4AF7CTV32E",
303
+ "human_account_name": null
304
+ },
305
+ "links": {
306
+ "self": "/api/admin/v1/upstream-oauth-links/01FSHN9AG09NMZYX8MFYH578R9"
307
+ }
308
+ },
309
+ "links": {
310
+ "self": "/api/admin/v1/upstream-oauth-links/01FSHN9AG09NMZYX8MFYH578R9"
311
+ }
312
+ }
313
+ "### ) ;
314
+ }
315
+
227
316
#[ sqlx:: test( migrator = "mas_storage_pg::MIGRATOR" ) ]
228
317
async fn test_link_already_exists ( pool : PgPool ) {
229
318
setup ( ) ;
@@ -238,6 +327,12 @@ mod tests {
238
327
. await
239
328
. unwrap ( ) ;
240
329
330
+ let bob = repo
331
+ . user ( )
332
+ . add ( & mut rng, & state. clock , "bob" . to_owned ( ) )
333
+ . await
334
+ . unwrap ( ) ;
335
+
241
336
let provider = repo
242
337
. upstream_oauth_provider ( )
243
338
. add (
@@ -270,7 +365,7 @@ mod tests {
270
365
let request = Request :: post ( "/api/admin/v1/upstream-oauth-links" )
271
366
. bearer ( & token)
272
367
. json ( serde_json:: json!( {
273
- "user_id" : alice . id,
368
+ "user_id" : bob . id,
274
369
"provider_id" : provider. id,
275
370
"subject" : "subject1"
276
371
} ) ) ;
@@ -281,7 +376,7 @@ mod tests {
281
376
{
282
377
"errors": [
283
378
{
284
- "title": "User ID 01FSHN9AG0MZAA6S4AF7CTV32E already has an upstream link for Upstream Oauth 2.0 Provider ID 01FSHN9AG0AJ6AC5HQ9X6H4RP4 "
379
+ "title": "Upstream Oauth 2.0 Provider ID 01FSHN9AG09NMZYX8MFYH578R9 with subject subject1 is already linked to a user "
285
380
}
286
381
]
287
382
}
0 commit comments