1
use std::{error::Error as StdError, sync::Arc};
2

            
3
use axum::{
4
    body::{Body, Bytes},
5
    extract::State,
6
    http::{header, StatusCode},
7
    response::IntoResponse,
8
    Extension,
9
};
10
use chrono::Utc;
11
use log::{error, warn};
12

            
13
use sylvia_iot_corelib::{
14
    constants::ContentType,
15
    err::ErrResp,
16
    http::{Json, Path, Query},
17
    role::Role,
18
    strings::{self, time_str},
19
};
20

            
21
use super::{
22
    super::super::{ErrReq, State as AppState},
23
    request, response,
24
};
25
use crate::models::{
26
    access_token, authorization_code,
27
    client::{
28
        Client, ListOptions, ListQueryCond, QueryCond, SortCond, SortKey, UpdateQueryCond, Updates,
29
    },
30
    refresh_token,
31
    user::{QueryCond as UserQueryCond, User},
32
    Model,
33
};
34

            
35
const LIST_LIMIT_DEFAULT: u64 = 100;
36
const LIST_CURSOR_MAX: u64 = 100;
37
const ID_RAND_LEN: usize = 8;
38
const SECRET_LEN: usize = 16;
39

            
40
/// `POST /{base}/api/v1/client`
41
22
pub async fn post_client(
42
22
    State(state): State<AppState>,
43
22
    Extension(user): Extension<User>,
44
22
    Json(mut body): Json<request::PostClientBody>,
45
22
) -> impl IntoResponse {
46
    const FN_NAME: &'static str = "post_client";
47

            
48
22
    body.data.redirect_uris.sort();
49
22
    body.data.redirect_uris.dedup();
50
22
    for v in body.data.redirect_uris.iter() {
51
18
        if !strings::is_uri(v.as_str()) {
52
4
            return Err(ErrResp::ErrParam(Some(
53
4
                "`redirectUris` must with invalid item(s)".to_string(),
54
4
            )));
55
14
        }
56
    }
57
18
    body.data.scopes.sort();
58
18
    body.data.scopes.dedup();
59
20
    for v in body.data.scopes.iter() {
60
20
        if !strings::is_scope(v.as_str()) {
61
4
            return Err(ErrResp::ErrParam(Some(
62
4
                "`scopes` with invalid item(s)".to_string(),
63
4
            )));
64
16
        }
65
    }
66
14
    let user_id = match Role::is_role(&user.roles, Role::ADMIN) {
67
4
        false => user.user_id,
68
10
        true => match body.data.user_id {
69
4
            None => user.user_id,
70
6
            Some(user_id) => {
71
6
                if user_id.len() == 0 {
72
2
                    return Err(ErrResp::ErrParam(Some(
73
2
                        "`userId` must not be empty".to_string(),
74
2
                    )));
75
4
                }
76
4
                let cond = UserQueryCond {
77
4
                    user_id: Some(user_id.as_str()),
78
4
                    ..Default::default()
79
4
                };
80
8
                match state.model.user().get(&cond).await {
81
                    Err(e) => {
82
                        error!("[{}] get error: {}", FN_NAME, e);
83
                        return Err(ErrResp::ErrDb(Some(e.to_string())));
84
                    }
85
                    Ok(None) => {
86
2
                        return Err(ErrResp::Custom(
87
2
                            ErrReq::USER_NOT_EXIST.0,
88
2
                            ErrReq::USER_NOT_EXIST.1,
89
2
                            None,
90
2
                        ))
91
                    }
92
2
                    Ok(_) => user_id,
93
                }
94
            }
95
        },
96
    };
97

            
98
10
    let now = Utc::now();
99
10
    let client_id = strings::random_id(&now, ID_RAND_LEN);
100
10
    let mut client = Client {
101
10
        client_id: client_id.clone(),
102
10
        created_at: now,
103
10
        modified_at: now,
104
10
        client_secret: None,
105
10
        redirect_uris: body.data.redirect_uris.clone(),
106
10
        scopes: body.data.scopes.clone(),
107
10
        user_id,
108
10
        name: body.data.name.clone(),
109
10
        image_url: match body.data.image.as_ref() {
110
6
            None => None,
111
4
            Some(url) => Some(url.clone()),
112
        },
113
    };
114
10
    if let Some(credentials) = body.credentials {
115
6
        if credentials {
116
4
            client.client_secret = Some(strings::randomstring(SECRET_LEN));
117
4
        }
118
4
    }
119
20
    if let Err(e) = state.model.client().add(&client).await {
120
        error!("[{}] add error: {}", FN_NAME, e);
121
        return Err(ErrResp::ErrDb(Some(e.to_string())));
122
10
    }
123
10
    Ok(Json(response::PostClient {
124
10
        data: response::PostClientData { client_id },
125
10
    }))
126
22
}
127

            
128
/// `GET /{base}/api/v1/client/count`
129
8
pub async fn get_client_count(
130
8
    State(state): State<AppState>,
131
8
    Extension(user): Extension<User>,
132
8
    Query(query): Query<request::GetClientCountQuery>,
133
8
) -> impl IntoResponse {
134
    const FN_NAME: &'static str = "get_client_count";
135

            
136
8
    let user_id = match Role::is_role(&user.roles, Role::ADMIN) {
137
4
        false => Some(user.user_id),
138
4
        true => query.user,
139
    };
140
8
    let cond = ListQueryCond {
141
8
        user_id: match user_id.as_ref() {
142
2
            None => None,
143
6
            Some(user_id) => Some(user_id.as_str()),
144
        },
145
8
        ..Default::default()
146
8
    };
147
16
    match state.model.client().count(&cond).await {
148
        Err(e) => {
149
            error!("[{}] count error: {}", FN_NAME, e);
150
            Err(ErrResp::ErrDb(Some(e.to_string())))
151
        }
152
8
        Ok(count) => Ok(Json(response::GetClientCount {
153
8
            data: response::GetCountData { count },
154
8
        })),
155
    }
156
8
}
157

            
158
/// `GET /{base}/api/v1/client/list`
159
50
pub async fn get_client_list(
160
50
    State(state): State<AppState>,
161
50
    Extension(user): Extension<User>,
162
50
    Query(query): Query<request::GetClientListQuery>,
163
50
) -> impl IntoResponse {
164
    const FN_NAME: &'static str = "get_client_list";
165

            
166
50
    let mut is_admin = false;
167
50
    let user_id = match Role::is_role(&user.roles, Role::ADMIN) {
168
4
        false => Some(user.user_id),
169
        true => {
170
46
            is_admin = true;
171
46
            query.user.clone()
172
        }
173
    };
174
50
    let cond = ListQueryCond {
175
50
        user_id: match user_id.as_ref() {
176
26
            None => None,
177
24
            Some(user_id) => Some(user_id.as_str()),
178
        },
179
50
        ..Default::default()
180
    };
181
50
    let sort_cond = match get_sort_cond(&query.sort) {
182
10
        Err(e) => return Err(e),
183
40
        Ok(cond) => cond,
184
    };
185
40
    let opts = ListOptions {
186
40
        cond: &cond,
187
40
        offset: query.offset,
188
40
        limit: match query.limit {
189
26
            None => Some(LIST_LIMIT_DEFAULT),
190
14
            Some(limit) => match limit {
191
4
                0 => None,
192
10
                _ => Some(limit),
193
            },
194
        },
195
40
        sort: Some(sort_cond.as_slice()),
196
40
        cursor_max: Some(LIST_CURSOR_MAX),
197
    };
198

            
199
80
    let (list, cursor) = match state.model.client().list(&opts, None).await {
200
        Err(e) => {
201
            error!("[{}] list error: {}", FN_NAME, e);
202
            return Err(ErrResp::ErrDb(Some(e.to_string())));
203
        }
204
40
        Ok((list, cursor)) => match cursor {
205
2
            None => match query.format {
206
                Some(request::ListFormat::Array) => {
207
2
                    return Ok(Json(client_list_transform(&list, is_admin)).into_response())
208
                }
209
                _ => {
210
26
                    return Ok(Json(response::GetClientList {
211
26
                        data: client_list_transform(&list, is_admin),
212
26
                    })
213
26
                    .into_response())
214
                }
215
            },
216
12
            Some(_) => (list, cursor),
217
12
        },
218
12
    };
219
12

            
220
12
    let body = Body::from_stream(async_stream::stream! {
221
12
        let user_id = user_id;
222
12
        let cond = ListQueryCond {
223
12
            user_id: match user_id.as_ref() {
224
12
                None => None,
225
12
                Some(user_id) => Some(user_id.as_str()),
226
12
            },
227
12
            ..Default::default()
228
12
        };
229
12
        let opts = ListOptions {
230
12
            cond: &cond,
231
12
            offset: query.offset,
232
12
            limit: match query.limit {
233
12
                None => Some(LIST_LIMIT_DEFAULT),
234
12
                Some(limit) => match limit {
235
12
                    0 => None,
236
12
                    _ => Some(limit),
237
12
                },
238
12
            },
239
12
            sort: Some(sort_cond.as_slice()),
240
12
            cursor_max: Some(LIST_CURSOR_MAX),
241
12
        };
242
12

            
243
12
        let mut list = list;
244
12
        let mut cursor = cursor;
245
12
        let mut is_first = true;
246
12
        loop {
247
12
            yield client_list_transform_bytes(&list, is_admin, is_first, cursor.is_none(), query.format.as_ref());
248
12
            is_first = false;
249
12
            if cursor.is_none() {
250
12
                break;
251
12
            }
252
12
            let (_list, _cursor) = match state.model.client().list(&opts, cursor).await {
253
12
                Err(_) => break,
254
12
                Ok((list, cursor)) => (list, cursor),
255
12
            };
256
12
            list = _list;
257
12
            cursor = _cursor;
258
12
        }
259
12
    });
260
12
    Ok(([(header::CONTENT_TYPE, ContentType::JSON)], body).into_response())
261
50
}
262

            
263
/// `GET /{base}/api/v1/client/{clientId}`
264
10
pub async fn get_client(
265
10
    State(state): State<AppState>,
266
10
    Extension(user): Extension<User>,
267
10
    Path(param): Path<request::ClientIdPath>,
268
10
) -> impl IntoResponse {
269
    const FN_NAME: &'static str = "get_client";
270

            
271
10
    let mut is_admin = false;
272
10
    let user_id = match Role::is_role(&user.roles, Role::ADMIN) {
273
4
        false => Some(user.user_id),
274
        true => {
275
6
            is_admin = true;
276
6
            None
277
        }
278
    };
279
10
    let cond = QueryCond {
280
10
        user_id: match user_id.as_ref() {
281
6
            None => None,
282
4
            Some(user_id) => Some(user_id.as_str()),
283
        },
284
10
        client_id: Some(param.client_id.as_str()),
285
10
    };
286
20
    match state.model.client().get(&cond).await {
287
        Err(e) => {
288
            error!("[{}] get error: {}", FN_NAME, e);
289
            return Err(ErrResp::ErrDb(Some(e.to_string())));
290
        }
291
10
        Ok(client) => match client {
292
4
            None => return Err(ErrResp::ErrNotFound(None)),
293
6
            Some(client) => Ok(Json(response::GetClient {
294
6
                data: client_transform(&client, is_admin),
295
6
            })),
296
        },
297
    }
298
10
}
299

            
300
/// `PATCH /{base}/api/v1/client/{clientId}`
301
36
pub async fn patch_client(
302
36
    State(state): State<AppState>,
303
36
    Extension(user): Extension<User>,
304
36
    Path(param): Path<request::ClientIdPath>,
305
36
    Json(mut body): Json<request::PatchClientBody>,
306
36
) -> impl IntoResponse {
307
    const FN_NAME: &'static str = "patch_client";
308

            
309
36
    if let Some(data) = body.data.as_mut() {
310
30
        if let Some(redirect_uris) = data.redirect_uris.as_mut() {
311
16
            redirect_uris.sort();
312
16
            redirect_uris.dedup();
313
28
            for v in redirect_uris {
314
16
                if !strings::is_uri(v.as_str()) {
315
4
                    return Err(ErrResp::ErrParam(Some(
316
4
                        "`redirectUris` must with invalid item(s)".to_string(),
317
4
                    )));
318
12
                }
319
            }
320
14
        }
321
26
        if let Some(scopes) = data.scopes.as_mut() {
322
16
            scopes.sort();
323
16
            scopes.dedup();
324
28
            for v in scopes {
325
16
                if !strings::is_scope(v.as_str()) {
326
4
                    return Err(ErrResp::ErrParam(Some(
327
4
                        "`scopes` with invalid item(s)".to_string(),
328
4
                    )));
329
12
                }
330
            }
331
10
        }
332
6
    }
333

            
334
28
    let mut is_admin = false;
335
28
    if Role::is_role(&user.roles, Role::ADMIN) {
336
18
        is_admin = true;
337
18
    }
338
28
    let user_id = user.user_id;
339
28

            
340
28
    let cond = QueryCond {
341
28
        client_id: Some(param.client_id.as_str()),
342
28
        ..Default::default()
343
28
    };
344
56
    let client = match state.model.client().get(&cond).await {
345
        Err(e) => {
346
            error!("[{}] get error: {}", FN_NAME, e);
347
            return Err(ErrResp::ErrDb(Some(e.to_string())));
348
        }
349
28
        Ok(client) => match client {
350
2
            None => return Err(ErrResp::ErrNotFound(None)),
351
26
            Some(client) => {
352
26
                if !is_admin && client.user_id != user_id {
353
2
                    warn!(
354
                        "[{}] {} try to patch other client",
355
                        FN_NAME,
356
                        user_id.as_str()
357
                    );
358
2
                    return Err(ErrResp::ErrNotFound(None));
359
24
                }
360
24
                client
361
24
            }
362
24
        },
363
24
    };
364
24

            
365
24
    let cond = UpdateQueryCond {
366
24
        user_id: client.user_id.as_str(),
367
24
        client_id: param.client_id.as_str(),
368
24
    };
369
24
    let updates = get_updates(&body, client.client_secret.is_some())?;
370
36
    if let Err(e) = state.model.client().update(&cond, &updates).await {
371
        error!("[{}] update error: {}", FN_NAME, e);
372
        return Err(ErrResp::ErrDb(Some(e.to_string())));
373
18
    }
374
18
    if updates.client_secret.is_some() {
375
12
        remove_tokens(&FN_NAME, &state.model, cond.client_id).await;
376
16
    }
377
18
    Ok(StatusCode::NO_CONTENT)
378
36
}
379

            
380
/// `DELETE /{base}/api/v1/client/{clientId}`
381
10
pub async fn delete_client(
382
10
    State(state): State<AppState>,
383
10
    Extension(user): Extension<User>,
384
10
    Extension(client): Extension<Client>,
385
10
    Path(param): Path<request::ClientIdPath>,
386
10
) -> impl IntoResponse {
387
    const FN_NAME: &'static str = "delete_client";
388

            
389
10
    let user_id = match Role::is_role(&user.roles, Role::ADMIN) {
390
6
        false => Some(user.user_id),
391
4
        true => None,
392
    };
393
10
    if client.client_id.as_str().eq(param.client_id.as_str()) {
394
2
        return Err(ErrResp::ErrPerm(Some(
395
2
            "cannot delete the client itself".to_string(),
396
2
        )));
397
8
    }
398

            
399
8
    let cond = QueryCond {
400
8
        user_id: match user_id.as_ref() {
401
4
            None => None,
402
4
            Some(user_id) => Some(user_id.as_str()),
403
        },
404
8
        client_id: Some(param.client_id.as_str()),
405
8
    };
406
16
    match state.model.client().del(&cond).await {
407
        Err(e) => {
408
            error!("[{}] del error: {}", FN_NAME, e);
409
            return Err(ErrResp::ErrDb(Some(e.to_string())));
410
        }
411
8
        Ok(_) => Ok(StatusCode::NO_CONTENT),
412
    }
413
10
}
414

            
415
/// `DELETE /{base}/api/v1/client/user/{userId}`
416
2
pub async fn delete_client_user(
417
2
    State(state): State<AppState>,
418
2
    Path(param): Path<request::UserIdPath>,
419
2
) -> impl IntoResponse {
420
    const FN_NAME: &'static str = "delete_client_user";
421

            
422
2
    let cond = QueryCond {
423
2
        user_id: Some(param.user_id.as_str()),
424
2
        ..Default::default()
425
2
    };
426
4
    match state.model.client().del(&cond).await {
427
        Err(e) => {
428
            error!("[{}] del error: {}", FN_NAME, e);
429
            return Err(ErrResp::ErrDb(Some(e.to_string())));
430
        }
431
2
        Ok(_) => Ok(StatusCode::NO_CONTENT),
432
    }
433
2
}
434

            
435
50
fn get_sort_cond(sort_args: &Option<String>) -> Result<Vec<SortCond>, ErrResp> {
436
50
    match sort_args.as_ref() {
437
28
        None => Ok(vec![SortCond {
438
28
            key: SortKey::Name,
439
28
            asc: true,
440
28
        }]),
441
22
        Some(args) => {
442
22
            let mut args = args.split(",");
443
22
            let mut sort_cond = vec![];
444
36
            while let Some(arg) = args.next() {
445
24
                let mut cond = arg.split(":");
446
24
                let key = match cond.next() {
447
                    None => return Err(ErrResp::ErrParam(Some("wrong sort argument".to_string()))),
448
24
                    Some(field) => match field {
449
24
                        "created" => SortKey::CreatedAt,
450
14
                        "modified" => SortKey::ModifiedAt,
451
10
                        "name" => SortKey::Name,
452
                        _ => {
453
4
                            return Err(ErrResp::ErrParam(Some(format!(
454
4
                                "invalid sort key {}",
455
4
                                field
456
4
                            ))))
457
                        }
458
                    },
459
                };
460
20
                let asc = match cond.next() {
461
2
                    None => return Err(ErrResp::ErrParam(Some("wrong sort argument".to_string()))),
462
18
                    Some(asc) => match asc {
463
18
                        "asc" => true,
464
8
                        "desc" => false,
465
                        _ => {
466
2
                            return Err(ErrResp::ErrParam(Some(format!(
467
2
                                "invalid sort asc {}",
468
2
                                asc
469
2
                            ))))
470
                        }
471
                    },
472
                };
473
16
                if cond.next().is_some() {
474
2
                    return Err(ErrResp::ErrParam(Some(
475
2
                        "invalid sort condition".to_string(),
476
2
                    )));
477
14
                }
478
14
                sort_cond.push(SortCond { key, asc });
479
            }
480
12
            Ok(sort_cond)
481
        }
482
    }
483
50
}
484

            
485
24
fn get_updates(body: &request::PatchClientBody, has_secret: bool) -> Result<Updates, ErrResp> {
486
24
    let mut updates = Updates {
487
24
        ..Default::default()
488
24
    };
489
24
    let mut count = 0;
490
24
    if let Some(body) = body.data.as_ref() {
491
18
        if let Some(redirect_uris) = body.redirect_uris.as_ref() {
492
12
            updates.redirect_uris = Some(redirect_uris);
493
12
            count += 1;
494
12
        }
495
18
        if let Some(scopes) = body.scopes.as_ref() {
496
12
            updates.scopes = Some(scopes);
497
12
            count += 1;
498
12
        }
499
18
        if let Some(name) = body.name.as_ref() {
500
6
            updates.name = Some(name.as_str());
501
6
            count += 1;
502
12
        }
503
18
        if let Some(image) = body.image.as_ref() {
504
12
            updates.image_url = match image.as_ref() {
505
6
                None => Some(None),
506
6
                Some(image) => Some(Some(image.as_str())),
507
            };
508
12
            count += 1;
509
6
        }
510
6
    }
511
24
    if let Some(regen_secret) = body.regen_secret {
512
6
        if regen_secret {
513
6
            if !has_secret {
514
4
                return Err(ErrResp::ErrParam(Some(
515
4
                    "cannot re-generate secret for public client".to_string(),
516
4
                )));
517
2
            }
518
2
            updates.client_secret = Some(Some(strings::randomstring(SECRET_LEN)));
519
2
            count += 1;
520
        }
521
18
    }
522
20
    if count == 0 {
523
2
        return Err(ErrResp::ErrParam(Some(
524
2
            "at least one parameter".to_string(),
525
2
        )));
526
18
    }
527
18
    updates.modified_at = Some(Utc::now());
528
18
    Ok(updates)
529
24
}
530

            
531
28
fn client_list_transform(list: &Vec<Client>, is_admin: bool) -> Vec<response::GetClientData> {
532
28
    let mut ret = vec![];
533
116
    for client in list.iter() {
534
116
        ret.push(client_transform(&client, is_admin));
535
116
    }
536
28
    ret
537
28
}
538

            
539
26
fn client_list_transform_bytes(
540
26
    list: &Vec<Client>,
541
26
    is_admin: bool,
542
26
    with_start: bool,
543
26
    with_end: bool,
544
26
    format: Option<&request::ListFormat>,
545
26
) -> Result<Bytes, Box<dyn StdError + Send + Sync>> {
546
26
    let mut build_str = match with_start {
547
14
        false => "".to_string(),
548
2
        true => match format {
549
2
            Some(request::ListFormat::Array) => "[".to_string(),
550
10
            _ => "{\"data\":[".to_string(),
551
        },
552
    };
553
26
    let mut is_first = with_start;
554

            
555
1644
    for item in list {
556
1618
        if is_first {
557
12
            is_first = false;
558
1606
        } else {
559
1606
            build_str.push(',');
560
1606
        }
561
1618
        let json_str = match serde_json::to_string(&client_transform(item, is_admin)) {
562
            Err(e) => return Err(Box::new(e)),
563
1618
            Ok(str) => str,
564
1618
        };
565
1618
        build_str += json_str.as_str();
566
    }
567

            
568
26
    if with_end {
569
12
        build_str += match format {
570
2
            Some(request::ListFormat::Array) => "]",
571
10
            _ => "]}",
572
        }
573
14
    }
574
26
    Ok(Bytes::copy_from_slice(build_str.as_str().as_bytes()))
575
26
}
576

            
577
1740
fn client_transform(client: &Client, is_admin: bool) -> response::GetClientData {
578
1740
    response::GetClientData {
579
1740
        client_id: client.client_id.clone(),
580
1740
        created_at: time_str(&client.created_at),
581
1740
        modified_at: time_str(&client.modified_at),
582
1740
        client_secret: match client.client_secret.as_ref() {
583
1720
            None => None,
584
20
            Some(secret) => Some(secret.clone()),
585
        },
586
1740
        redirect_uris: client.redirect_uris.clone(),
587
1740
        scopes: client.scopes.clone(),
588
1740
        user_id: match is_admin {
589
6
            false => None,
590
1734
            true => Some(client.user_id.clone()),
591
        },
592
1740
        name: client.name.clone(),
593
1740
        image: match client.image_url.as_ref() {
594
1738
            None => None,
595
2
            Some(image) => Some(image.clone()),
596
        },
597
    }
598
1740
}
599

            
600
2
async fn remove_tokens(fn_name: &str, model: &Arc<dyn Model>, client_id: &str) {
601
2
    let cond = authorization_code::QueryCond {
602
2
        client_id: Some(client_id),
603
2
        ..Default::default()
604
2
    };
605
4
    if let Err(e) = model.authorization_code().del(&cond).await {
606
        error!("[{}] delete access token error: {}", fn_name, e);
607
2
    }
608
2
    let cond = access_token::QueryCond {
609
2
        client_id: Some(client_id),
610
2
        ..Default::default()
611
2
    };
612
4
    if let Err(e) = model.access_token().del(&cond).await {
613
        error!("[{}] delete access token error: {}", fn_name, e);
614
2
    }
615
2
    let cond = refresh_token::QueryCond {
616
2
        client_id: Some(client_id),
617
2
        ..Default::default()
618
2
    };
619
4
    if let Err(e) = model.refresh_token().del(&cond).await {
620
        error!("[{}] delete refresh token error: {}", fn_name, e);
621
2
    }
622
2
}