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
44
pub async fn post_client(
42
44
    State(state): State<AppState>,
43
44
    Extension(user): Extension<User>,
44
44
    Json(mut body): Json<request::PostClientBody>,
45
44
) -> impl IntoResponse {
46
    const FN_NAME: &'static str = "post_client";
47

            
48
44
    body.data.redirect_uris.sort();
49
44
    body.data.redirect_uris.dedup();
50
44
    for v in body.data.redirect_uris.iter() {
51
36
        if !strings::is_uri(v.as_str()) {
52
8
            return Err(ErrResp::ErrParam(Some(
53
8
                "`redirectUris` must with invalid item(s)".to_string(),
54
8
            )));
55
28
        }
56
    }
57
36
    body.data.scopes.sort();
58
36
    body.data.scopes.dedup();
59
40
    for v in body.data.scopes.iter() {
60
40
        if !strings::is_scope(v.as_str()) {
61
8
            return Err(ErrResp::ErrParam(Some(
62
8
                "`scopes` with invalid item(s)".to_string(),
63
8
            )));
64
32
        }
65
    }
66
28
    let user_id = match Role::is_role(&user.roles, Role::ADMIN) {
67
8
        false => user.user_id,
68
20
        true => match body.data.user_id {
69
8
            None => user.user_id,
70
12
            Some(user_id) => {
71
12
                if user_id.len() == 0 {
72
4
                    return Err(ErrResp::ErrParam(Some(
73
4
                        "`userId` must not be empty".to_string(),
74
4
                    )));
75
8
                }
76
8
                let cond = UserQueryCond {
77
8
                    user_id: Some(user_id.as_str()),
78
8
                    ..Default::default()
79
8
                };
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
4
                        return Err(ErrResp::Custom(
87
4
                            ErrReq::USER_NOT_EXIST.0,
88
4
                            ErrReq::USER_NOT_EXIST.1,
89
4
                            None,
90
4
                        ))
91
                    }
92
4
                    Ok(_) => user_id,
93
                }
94
            }
95
        },
96
    };
97

            
98
20
    let now = Utc::now();
99
20
    let client_id = strings::random_id(&now, ID_RAND_LEN);
100
20
    let mut client = Client {
101
20
        client_id: client_id.clone(),
102
20
        created_at: now,
103
20
        modified_at: now,
104
20
        client_secret: None,
105
20
        redirect_uris: body.data.redirect_uris.clone(),
106
20
        scopes: body.data.scopes.clone(),
107
20
        user_id,
108
20
        name: body.data.name.clone(),
109
20
        image_url: match body.data.image.as_ref() {
110
12
            None => None,
111
8
            Some(url) => Some(url.clone()),
112
        },
113
    };
114
20
    if let Some(credentials) = body.credentials {
115
12
        if credentials {
116
8
            client.client_secret = Some(strings::randomstring(SECRET_LEN));
117
8
        }
118
8
    }
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
20
    }
123
20
    Ok(Json(response::PostClient {
124
20
        data: response::PostClientData { client_id },
125
20
    }))
126
44
}
127

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

            
136
16
    let user_id = match Role::is_role(&user.roles, Role::ADMIN) {
137
8
        false => Some(user.user_id),
138
8
        true => query.user,
139
    };
140
16
    let cond = ListQueryCond {
141
16
        user_id: match user_id.as_ref() {
142
4
            None => None,
143
12
            Some(user_id) => Some(user_id.as_str()),
144
        },
145
16
        ..Default::default()
146
16
    };
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
16
        Ok(count) => Ok(Json(response::GetClientCount {
153
16
            data: response::GetCountData { count },
154
16
        })),
155
    }
156
16
}
157

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

            
166
100
    let mut is_admin = false;
167
100
    let user_id = match Role::is_role(&user.roles, Role::ADMIN) {
168
8
        false => Some(user.user_id),
169
        true => {
170
92
            is_admin = true;
171
92
            query.user.clone()
172
        }
173
    };
174
100
    let cond = ListQueryCond {
175
100
        user_id: match user_id.as_ref() {
176
52
            None => None,
177
48
            Some(user_id) => Some(user_id.as_str()),
178
        },
179
100
        ..Default::default()
180
    };
181
100
    let sort_cond = match get_sort_cond(&query.sort) {
182
20
        Err(e) => return Err(e),
183
80
        Ok(cond) => cond,
184
    };
185
80
    let opts = ListOptions {
186
80
        cond: &cond,
187
80
        offset: query.offset,
188
80
        limit: match query.limit {
189
52
            None => Some(LIST_LIMIT_DEFAULT),
190
28
            Some(limit) => match limit {
191
8
                0 => None,
192
20
                _ => Some(limit),
193
            },
194
        },
195
80
        sort: Some(sort_cond.as_slice()),
196
80
        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
80
        Ok((list, cursor)) => match cursor {
205
4
            None => match query.format {
206
                Some(request::ListFormat::Array) => {
207
4
                    return Ok(Json(client_list_transform(&list, is_admin)).into_response())
208
                }
209
                _ => {
210
52
                    return Ok(Json(response::GetClientList {
211
52
                        data: client_list_transform(&list, is_admin),
212
52
                    })
213
52
                    .into_response())
214
                }
215
            },
216
24
            Some(_) => (list, cursor),
217
24
        },
218
24
    };
219
24

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

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

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

            
271
20
    let mut is_admin = false;
272
20
    let user_id = match Role::is_role(&user.roles, Role::ADMIN) {
273
8
        false => Some(user.user_id),
274
        true => {
275
12
            is_admin = true;
276
12
            None
277
        }
278
    };
279
20
    let cond = QueryCond {
280
20
        user_id: match user_id.as_ref() {
281
12
            None => None,
282
8
            Some(user_id) => Some(user_id.as_str()),
283
        },
284
20
        client_id: Some(param.client_id.as_str()),
285
20
    };
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
20
        Ok(client) => match client {
292
8
            None => return Err(ErrResp::ErrNotFound(None)),
293
12
            Some(client) => Ok(Json(response::GetClient {
294
12
                data: client_transform(&client, is_admin),
295
12
            })),
296
        },
297
    }
298
20
}
299

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

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

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

            
340
56
    let cond = QueryCond {
341
56
        client_id: Some(param.client_id.as_str()),
342
56
        ..Default::default()
343
56
    };
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
56
        Ok(client) => match client {
350
4
            None => return Err(ErrResp::ErrNotFound(None)),
351
52
            Some(client) => {
352
52
                if !is_admin && client.user_id != user_id {
353
4
                    warn!(
354
                        "[{}] {} try to patch other client",
355
                        FN_NAME,
356
                        user_id.as_str()
357
                    );
358
4
                    return Err(ErrResp::ErrNotFound(None));
359
48
                }
360
48
                client
361
48
            }
362
48
        },
363
48
    };
364
48

            
365
48
    let cond = UpdateQueryCond {
366
48
        user_id: client.user_id.as_str(),
367
48
        client_id: param.client_id.as_str(),
368
48
    };
369
48
    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
36
    }
374
36
    if updates.client_secret.is_some() {
375
4
        remove_tokens(&FN_NAME, &state.model, cond.client_id).await;
376
32
    }
377
36
    Ok(StatusCode::NO_CONTENT)
378
72
}
379

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

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

            
399
16
    let cond = QueryCond {
400
16
        user_id: match user_id.as_ref() {
401
8
            None => None,
402
8
            Some(user_id) => Some(user_id.as_str()),
403
        },
404
16
        client_id: Some(param.client_id.as_str()),
405
16
    };
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
16
        Ok(_) => Ok(StatusCode::NO_CONTENT),
412
    }
413
20
}
414

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

            
422
4
    let cond = QueryCond {
423
4
        user_id: Some(param.user_id.as_str()),
424
4
        ..Default::default()
425
4
    };
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
4
        Ok(_) => Ok(StatusCode::NO_CONTENT),
432
    }
433
4
}
434

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

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

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

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

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

            
568
52
    if with_end {
569
24
        build_str += match format {
570
4
            Some(request::ListFormat::Array) => "]",
571
20
            _ => "]}",
572
        }
573
28
    }
574
52
    Ok(Bytes::copy_from_slice(build_str.as_str().as_bytes()))
575
52
}
576

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

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