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 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 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 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 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 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}