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

            
3
use axum::{
4
    Extension,
5
    body::{Body, Bytes},
6
    extract::State,
7
    http::{StatusCode, header},
8
    response::IntoResponse,
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
    Model, 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
};
33

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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