bios_iam/basic/serv/
iam_cert_phone_vcode_serv.rs

1use bios_basic::rbum::dto::rbum_filer_dto::{RbumBasicFilterReq, RbumCertConfFilterReq, RbumCertFilterReq};
2
3use tardis::basic::dto::TardisContext;
4use tardis::basic::field::TrimString;
5use tardis::basic::result::TardisResult;
6use tardis::log::info;
7
8use tardis::rand::Rng;
9use tardis::TardisFunsInst;
10
11use bios_basic::rbum::dto::rbum_cert_conf_dto::{RbumCertConfAddReq, RbumCertConfModifyReq};
12use bios_basic::rbum::dto::rbum_cert_dto::{RbumCertAddReq, RbumCertModifyReq};
13use bios_basic::rbum::rbum_enumeration::{RbumCertConfStatusKind, RbumCertRelKind, RbumCertStatusKind};
14use bios_basic::rbum::serv::rbum_cert_serv::{RbumCertConfServ, RbumCertServ};
15use bios_basic::rbum::serv::rbum_crud_serv::RbumCrudOperation;
16
17use crate::basic::dto::iam_cert_conf_dto::IamCertConfPhoneVCodeAddOrModifyReq;
18use crate::basic::dto::iam_cert_dto::{IamCertPhoneVCodeAddReq, IamCertPhoneVCodeModifyReq};
19use crate::iam_config::IamBasicConfigApi;
20use crate::iam_enumeration::IamCertKernelKind;
21
22use super::clients::iam_log_client::{IamLogClient, LogParamTag};
23use super::clients::iam_search_client::IamSearchClient;
24use super::clients::sms_client::SmsClient;
25use super::iam_account_serv::IamAccountServ;
26use super::iam_cert_serv::IamCertServ;
27use super::iam_tenant_serv::IamTenantServ;
28
29pub struct IamCertPhoneVCodeServ;
30
31impl IamCertPhoneVCodeServ {
32    pub async fn add_cert_conf(add_req: &IamCertConfPhoneVCodeAddOrModifyReq, rel_iam_item_id: Option<String>, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<String> {
33        let id = RbumCertConfServ::add_rbum(
34            &mut RbumCertConfAddReq {
35                kind: TrimString(IamCertKernelKind::PhoneVCode.to_string()),
36                supplier: None,
37                name: TrimString(IamCertKernelKind::PhoneVCode.to_string()),
38                note: None,
39                ak_note: add_req.ak_note.clone(),
40                ak_rule: add_req.ak_rule.clone(),
41                sk_note: None,
42                sk_rule: None,
43                ext: None,
44                sk_need: Some(false),
45                sk_dynamic: Some(true),
46                sk_encrypted: Some(false),
47                repeatable: None,
48                is_basic: Some(false),
49                rest_by_kinds: None,
50                expire_sec: None,
51                sk_lock_cycle_sec: None,
52                sk_lock_err_times: None,
53                sk_lock_duration_sec: None,
54                coexist_num: Some(1),
55                conn_uri: None,
56                status: RbumCertConfStatusKind::Enabled,
57                rel_rbum_domain_id: funs.iam_basic_domain_iam_id(),
58                rel_rbum_item_id: rel_iam_item_id,
59            },
60            funs,
61            ctx,
62        )
63        .await?;
64        Ok(id)
65    }
66
67    pub async fn modify_cert_conf(id: &str, modify_req: &IamCertConfPhoneVCodeAddOrModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
68        RbumCertConfServ::modify_rbum(
69            id,
70            &mut RbumCertConfModifyReq {
71                name: None,
72                note: None,
73                ak_note: modify_req.ak_note.clone(),
74                ak_rule: modify_req.ak_rule.clone(),
75                sk_note: None,
76                sk_rule: None,
77                ext: None,
78                sk_need: None,
79                sk_encrypted: None,
80                repeatable: None,
81                is_basic: None,
82                rest_by_kinds: None,
83                expire_sec: None,
84                sk_lock_cycle_sec: None,
85                sk_lock_err_times: None,
86                sk_lock_duration_sec: None,
87                coexist_num: None,
88                conn_uri: None,
89                status: None,
90            },
91            funs,
92            ctx,
93        )
94        .await?;
95        Ok(())
96    }
97
98    pub async fn add_cert(add_req: &IamCertPhoneVCodeAddReq, account_id: &str, rel_rbum_cert_conf_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<String> {
99        let vcode = Self::get_vcode();
100        let id = RbumCertServ::add_rbum(
101            &mut RbumCertAddReq {
102                ak: TrimString(add_req.phone.to_string()),
103                sk: None,
104                sk_invisible: None,
105
106                kind: None,
107                supplier: None,
108                vcode: Some(TrimString(vcode.clone())),
109                ext: None,
110                start_time: None,
111                end_time: None,
112                conn_uri: None,
113                status: RbumCertStatusKind::Pending,
114                rel_rbum_cert_conf_id: Some(rel_rbum_cert_conf_id.to_string()),
115                rel_rbum_kind: RbumCertRelKind::Item,
116                rel_rbum_id: account_id.to_string(),
117                is_outside: false,
118                ignore_check_sk: false,
119            },
120            funs,
121            ctx,
122        )
123        .await?;
124        Self::send_activation_phone(&add_req.phone, &vcode, funs, ctx).await?;
125        Ok(id)
126    }
127
128    pub async fn modify_cert(id: &str, modify_req: &IamCertPhoneVCodeModifyReq, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
129        RbumCertServ::modify_rbum(
130            id,
131            &mut RbumCertModifyReq {
132                ak: Some(TrimString(modify_req.phone.to_string())),
133                sk: None,
134                sk_invisible: None,
135
136                ext: None,
137                start_time: None,
138                end_time: None,
139                conn_uri: None,
140                status: None,
141                ignore_check_sk: false,
142            },
143            funs,
144            ctx,
145        )
146        .await?;
147        Ok(())
148    }
149
150    pub async fn add_or_modify_cert(phone: &str, account_id: &str, rel_rbum_cert_conf_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
151        let resp = IamCertServ::get_kernel_cert(account_id, &IamCertKernelKind::PhoneVCode, funs, ctx).await;
152        match resp {
153            Ok(cert) => {
154                Self::modify_cert(
155                    &cert.id,
156                    &IamCertPhoneVCodeModifyReq {
157                        phone: TrimString(phone.to_string()),
158                    },
159                    funs,
160                    ctx,
161                )
162                .await?;
163            }
164            Err(_) => {
165                Self::add_cert(
166                    &IamCertPhoneVCodeAddReq {
167                        phone: TrimString(phone.to_string()),
168                    },
169                    account_id,
170                    rel_rbum_cert_conf_id,
171                    funs,
172                    ctx,
173                )
174                .await?;
175            }
176        }
177        Ok(())
178    }
179
180    ///不需要验证直接添加cert
181    pub async fn add_cert_skip_vcode(
182        add_req: &IamCertPhoneVCodeAddReq,
183        account_id: &str,
184        rel_rbum_cert_conf_id: &str,
185        funs: &TardisFunsInst,
186        ctx: &TardisContext,
187    ) -> TardisResult<String> {
188        let vcode = Self::get_vcode();
189        let id = RbumCertServ::add_rbum(
190            &mut RbumCertAddReq {
191                ak: TrimString(add_req.phone.to_string()),
192                sk: None,
193                sk_invisible: None,
194
195                kind: None,
196                supplier: None,
197                vcode: Some(TrimString(vcode.clone())),
198                ext: None,
199                start_time: None,
200                end_time: None,
201                conn_uri: None,
202                status: RbumCertStatusKind::Enabled,
203                rel_rbum_cert_conf_id: Some(rel_rbum_cert_conf_id.to_string()),
204                rel_rbum_kind: RbumCertRelKind::Item,
205                rel_rbum_id: account_id.to_string(),
206                is_outside: false,
207                ignore_check_sk: false,
208            },
209            funs,
210            ctx,
211        )
212        .await?;
213        Ok(id)
214    }
215
216    //TODO remove?
217    pub async fn resend_activation_phone(phone: &str, cool_down_in_sec: Option<u32>, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
218        let vcode = Self::get_vcode();
219        let rel_rbum_cert_conf_id =
220            IamCertServ::get_cert_conf_id_by_kind(IamCertKernelKind::PhoneVCode.to_string().as_str(), Some(IamTenantServ::get_id_by_ctx(ctx, funs)?), funs).await?;
221        RbumCertServ::add_vcode_to_cache(phone, &vcode, &rel_rbum_cert_conf_id, cool_down_in_sec, funs, ctx).await?;
222        Self::send_activation_phone(phone, &vcode, funs, ctx).await
223    }
224
225    async fn send_activation_phone(phone: &str, vcode: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
226        SmsClient::send_vcode(phone, vcode, funs, ctx).await?;
227        Ok(())
228    }
229
230    pub async fn activate_phone(phone: &str, input_vcode: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
231        let ctx = IamAccountServ::new_context_if_account_is_global(ctx, funs).await?;
232        if let Some(cached_vcode) = RbumCertServ::get_vcode_in_cache(phone, &ctx.own_paths, funs).await? {
233            if cached_vcode == input_vcode {
234                let cert = RbumCertServ::find_one_rbum(
235                    &RbumCertFilterReq {
236                        ak: Some(phone.to_string()),
237                        status: Some(RbumCertStatusKind::Pending),
238                        rel_rbum_kind: Some(RbumCertRelKind::Item),
239                        rel_rbum_cert_conf_ids: Some(vec![
240                            IamCertServ::get_cert_conf_id_by_kind(IamCertKernelKind::PhoneVCode.to_string().as_str(), Some(IamTenantServ::get_id_by_ctx(&ctx, funs)?), funs)
241                                .await?,
242                        ]),
243                        ..Default::default()
244                    },
245                    funs,
246                    &ctx,
247                )
248                .await?;
249                return if let Some(cert) = cert {
250                    RbumCertServ::modify_rbum(
251                        &cert.id,
252                        &mut RbumCertModifyReq {
253                            status: Some(RbumCertStatusKind::Enabled),
254                            ak: None,
255                            sk: None,
256                            sk_invisible: None,
257
258                            ignore_check_sk: false,
259                            ext: None,
260                            start_time: None,
261                            end_time: None,
262                            conn_uri: None,
263                        },
264                        funs,
265                        &ctx,
266                    )
267                    .await?;
268                    Ok(())
269                } else {
270                    Err(funs.err().not_found(
271                        "iam_cert_phone_vcode",
272                        "activate",
273                        &format!("not found credential of kind {:?}", IamCertKernelKind::PhoneVCode),
274                        "404-iam-cert-kind-not-exist",
275                    ))
276                };
277            }
278        }
279        Err(funs.err().unauthorized("iam_cert_phone_vcode", "activate", "email or verification code error", "401-iam-cert-valid"))
280    }
281
282    pub async fn send_bind_phone(phone: &str, cool_down_in_sec: Option<u32>, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
283        let ctx = IamAccountServ::new_context_if_account_is_global(ctx, funs).await?;
284        let rel_rbum_cert_conf_id =
285            IamCertServ::get_cert_conf_id_by_kind(IamCertKernelKind::PhoneVCode.to_string().as_str(), Some(IamTenantServ::get_id_by_ctx(&ctx, funs)?), funs).await?;
286        // Self::check_bind_phone(phone, vec![rel_rbum_cert_conf_id], &ctx.owner.clone(), funs, &ctx).await?;
287        let vcode = Self::get_vcode();
288        RbumCertServ::add_vcode_to_cache(phone, &vcode, &rel_rbum_cert_conf_id, cool_down_in_sec, funs, &ctx).await?;
289        SmsClient::send_vcode(phone, &vcode, funs, &ctx).await
290    }
291
292    pub async fn bind_phone(phone: &str, input_vcode: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<String> {
293        let ctx = IamAccountServ::new_context_if_account_is_global(ctx, funs).await?;
294        if let Some(cached_vcode) = RbumCertServ::get_vcode_in_cache(phone, &ctx.own_paths, funs).await? {
295            if cached_vcode == input_vcode {
296                let rel_rbum_cert_conf_id =
297                    IamCertServ::get_cert_conf_id_by_kind(IamCertKernelKind::PhoneVCode.to_string().as_str(), Some(IamTenantServ::get_id_by_ctx(&ctx, funs)?), funs).await?;
298                Self::check_phone_bound(phone, vec![rel_rbum_cert_conf_id.clone()], funs, &ctx).await?;
299                let id = if Self::check_account_bind_phone(vec![rel_rbum_cert_conf_id.clone()], &ctx.owner.clone(), funs, &ctx).await.is_ok() {
300                    RbumCertServ::add_rbum(
301                        &mut RbumCertAddReq {
302                            ak: TrimString(phone.trim().to_string()),
303                            sk: None,
304                            sk_invisible: None,
305                            kind: None,
306                            supplier: None,
307                            vcode: Some(TrimString(input_vcode.to_string())),
308                            ext: None,
309                            start_time: None,
310                            end_time: None,
311                            conn_uri: None,
312                            status: RbumCertStatusKind::Enabled,
313                            rel_rbum_cert_conf_id: Some(rel_rbum_cert_conf_id),
314                            rel_rbum_kind: RbumCertRelKind::Item,
315                            rel_rbum_id: ctx.owner.clone(),
316                            is_outside: false,
317                            ignore_check_sk: false,
318                        },
319                        funs,
320                        &ctx,
321                    )
322                    .await?
323                } else {
324                    let id = RbumCertServ::find_id_rbums(
325                        &RbumCertFilterReq {
326                            status: Some(RbumCertStatusKind::Enabled),
327                            rel_rbum_id: Some(ctx.owner.clone()),
328                            rel_rbum_kind: Some(RbumCertRelKind::Item),
329                            rel_rbum_cert_conf_ids: Some(vec![rel_rbum_cert_conf_id]),
330                            ..Default::default()
331                        },
332                        None,
333                        None,
334                        funs,
335                        &ctx,
336                    )
337                    .await?
338                    .pop()
339                    .ok_or_else(|| funs.err().unauthorized("iam_cert_mail_vcode", "activate", "email or verification code error", "401-iam-cert-valid"))?;
340                    RbumCertServ::modify_rbum(
341                        &id,
342                        &mut RbumCertModifyReq {
343                            ak: Some(TrimString(phone.trim().to_string())),
344                            sk: None,
345                            sk_invisible: None,
346                            ignore_check_sk: true,
347                            ext: None,
348                            start_time: None,
349                            end_time: None,
350                            conn_uri: None,
351                            status: None,
352                        },
353                        funs,
354                        &ctx,
355                    )
356                    .await?;
357                    id
358                };
359                IamSearchClient::async_add_or_modify_account_search(&ctx.owner, Box::new(true), "", funs, &ctx).await?;
360                let op_describe = format!("绑定手机号为{}", phone);
361                let _ = IamLogClient::add_ctx_task(LogParamTag::IamAccount, Some(ctx.owner.to_string()), op_describe, Some("BindPhone".to_string()), &ctx).await;
362                return Ok(id);
363            }
364        }
365        Err(funs.err().unauthorized("iam_cert_phone_vcode", "bind", "phone or verification code error", "401-iam-cert-valid"))
366    }
367
368    async fn check_account_bind_phone(rel_rbum_cert_conf_ids: Vec<String>, rel_rbum_id: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
369        // check bind or not
370        if RbumCertServ::count_rbums(
371            &RbumCertFilterReq {
372                basic: RbumBasicFilterReq {
373                    own_paths: Some("".to_string()),
374                    with_sub_own_paths: true,
375                    ..Default::default()
376                },
377                rel_rbum_id: Some(rel_rbum_id.to_owned()),
378                rel_rbum_kind: Some(RbumCertRelKind::Item),
379                rel_rbum_cert_conf_ids: Some(rel_rbum_cert_conf_ids.clone()),
380                ..Default::default()
381            },
382            funs,
383            ctx,
384        )
385        .await?
386            > 0
387        {
388            return Err(funs.err().conflict("iam_cert_phone_vcode", "bind", "phone already exist bind", "409-iam-cert-phone-bind-already-exist"));
389        }
390        Ok(())
391    }
392
393    async fn check_phone_bound(phone: &str, rel_rbum_cert_conf_ids: Vec<String>, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
394        // check existence or not
395        if RbumCertServ::count_rbums(
396            &RbumCertFilterReq {
397                basic: RbumBasicFilterReq {
398                    own_paths: Some("".to_string()),
399                    with_sub_own_paths: true,
400                    ..Default::default()
401                },
402                ak: Some(phone.to_string()),
403                rel_rbum_kind: Some(RbumCertRelKind::Item),
404                rel_rbum_cert_conf_ids: Some(rel_rbum_cert_conf_ids),
405                ..Default::default()
406            },
407            funs,
408            ctx,
409        )
410        .await?
411            > 0
412        {
413            return Err(funs.err().unauthorized("iam_cert_phone_vcode", "activate", "phone already exist", "404-iam-cert-phone-not-exist"));
414        }
415        Ok(())
416    }
417
418    pub async fn send_login_phone(phone: &str, tenant_id: &str, cool_down_in_sec: Option<u32>, funs: &TardisFunsInst) -> TardisResult<()> {
419        let own_paths = tenant_id.to_string();
420        let mock_ctx = TardisContext {
421            own_paths: own_paths.to_string(),
422            ..Default::default()
423        };
424        let global_rbum_cert_conf_id = IamCertServ::get_cert_conf_id_by_kind(&IamCertKernelKind::PhoneVCode.to_string(), None, funs).await?;
425        let tenant_rbum_cert_conf_id = IamCertServ::get_cert_conf_id_by_kind(&IamCertKernelKind::PhoneVCode.to_string(), Some(tenant_id.to_owned()), funs).await?;
426        if RbumCertServ::count_rbums(
427            &RbumCertFilterReq {
428                basic: RbumBasicFilterReq {
429                    own_paths: Some("".to_string()),
430                    with_sub_own_paths: true,
431                    ..Default::default()
432                },
433                ak: Some(phone.to_string()),
434                rel_rbum_kind: Some(RbumCertRelKind::Item),
435                rel_rbum_cert_conf_ids: Some(vec![tenant_rbum_cert_conf_id.clone()]),
436                ..Default::default()
437            },
438            funs,
439            &mock_ctx,
440        )
441        .await?
442            > 0
443        {
444            let vcode = Self::get_vcode();
445            RbumCertServ::add_vcode_to_cache(phone, &vcode, &tenant_rbum_cert_conf_id, cool_down_in_sec, funs, &mock_ctx).await?;
446            return SmsClient::send_vcode(phone, &vcode, funs, &mock_ctx).await;
447        }
448
449        if RbumCertServ::count_rbums(
450            &RbumCertFilterReq {
451                basic: RbumBasicFilterReq {
452                    own_paths: Some("".to_string()),
453                    with_sub_own_paths: true,
454                    ..Default::default()
455                },
456                ak: Some(phone.to_string()),
457                rel_rbum_kind: Some(RbumCertRelKind::Item),
458                rel_rbum_cert_conf_ids: Some(vec![global_rbum_cert_conf_id.clone()]),
459                ..Default::default()
460            },
461            funs,
462            &mock_ctx,
463        )
464        .await?
465            > 0
466        {
467            let vcode = Self::get_vcode();
468            RbumCertServ::add_vcode_to_cache(
469                phone,
470                &vcode,
471                &global_rbum_cert_conf_id,
472                cool_down_in_sec,
473                funs,
474                &TardisContext {
475                    own_paths: "".to_string(),
476                    ..Default::default()
477                },
478            )
479            .await?;
480            return SmsClient::send_vcode(phone, &vcode, funs, &mock_ctx).await;
481        }
482        return Err(funs.err().not_found("iam_cert_phone_vcode", "send", "phone not find", "404-iam-cert-phone-not-exist"));
483    }
484
485    fn get_vcode() -> String {
486        let mut rand = tardis::rand::thread_rng();
487        let vcode: i32 = rand.gen_range(100000..999999);
488        format!("{vcode}")
489    }
490
491    pub async fn add_or_enable_cert_conf(
492        add_req: &IamCertConfPhoneVCodeAddOrModifyReq,
493        rel_iam_item_id: Option<String>,
494        funs: &TardisFunsInst,
495        ctx: &TardisContext,
496    ) -> TardisResult<String> {
497        let cert_result = RbumCertConfServ::do_find_one_rbum(
498            &RbumCertConfFilterReq {
499                kind: Some(TrimString(IamCertKernelKind::PhoneVCode.to_string())),
500                rel_rbum_item_id: rel_iam_item_id.clone(),
501                ..Default::default()
502            },
503            funs,
504            ctx,
505        )
506        .await?;
507        let result = if let Some(cert_result) = cert_result {
508            IamCertServ::enabled_cert_conf(&cert_result.id, funs, ctx).await?;
509            cert_result.id
510        } else {
511            Self::add_cert_conf(add_req, rel_iam_item_id, funs, ctx).await?
512        };
513        Ok(result)
514    }
515
516    pub async fn send_pwd(account_id: &str, pwd: &str, funs: &TardisFunsInst, ctx: &TardisContext) -> TardisResult<()> {
517        let resp = IamCertServ::get_kernel_cert(account_id, &IamCertKernelKind::PhoneVCode, funs, ctx).await;
518        match resp {
519            Ok(cert) => {
520                let _ = SmsClient::async_send_pwd(&cert.ak, pwd, funs, ctx).await;
521            }
522            Err(_) => info!("phone pwd not found"),
523        }
524        Ok(())
525    }
526}