Mobile phone verification code authentication factors#
Features#
User table extension mobile phone number field,Allows users to certify through the mobile phone number and verification code,register,Reset the password and replace the mobile phone number。
general user:
- exist “register” Page implementation mobile phone number+Verification code user registration
- exist “Log in” Page implementation mobile phone number+Verification code user login
- exist “change password” Page implementation mobile phone number+The password changes at the verification code method
- exist “mine - Certification management“ Add the function of resetting the mobile phone number
Tenant administrator
- exist”User Management - user list“In the editor page adds mobile phone number editing function
precondition#
Need to be used with SMS plug -in,The system has default to provide Alibaba Cloud SMS plug -in,If you need to view the configuration method, please move the Alibaba Cloud SMS plug -in documentation。
Configuration guide#
Enter through the menu bar on the left【Tenant management】->【Plug -in management】,Find the mobile phone verification code authentication factor plug -in card in the plug -in lease page,Click to rent

Implementation#
- general user:mobile phone number+Verification code user registration/Log in/reset Password:
sequenceDiagram
    participant D as user
    participant C as Platform core
    participant A as Mobile phone verification code authentication factors plugin
    participant B as SMS plugin
    C->>A: Loading plug -in
    A->>C: Register and monitor the related events of the mobile phone verification code(register/Log in/Reset password, etc.)
    C->>B: Loading plug -in
    B->>C: SMS event
    D->>C: Visit Registration/Log in/Reset password page
    C->>A: Send Create_LOGIN_PAGE_AUTH_Factor event
    A->>C: Response event,Assemble/Log in/Reset the password page element
    C->>D: Rendering/Log in/Reset password page
    D->>A: Enter the mobile phone number,Clicked【send messages】Button,Access SMS sending interface
    A->>B: Generate SMS verification code,Send and_SMS event
    B->>A: Response event,Send a text message
    A->>D: Return to SMS sending results
    D->>C: Enter the relevant information,Clicked【register/Log in/reset Password】Button
    C->>A: Register/Log in/Reset password event
    A->>C: Response event,Complete registration/Log in/Reset password process,Return result
    C->>D: test result,Such as completing registration/Log in the relevant operation to generate token and jump to the desktop,If you complete the reset password operation or not complete the registration/Login operation, it prompts an error
- general user:Reset the mobile phone number:
sequenceDiagram
    participant D as user
    participant C as Platform core
    participant A as Mobile phone verification code authentication factors plugin
    participant B as SMS plugin
    C->>A: Loading plug -in
    A->>C: Towards“mine - Certification management“ Add a reset mobile phone number element to the page,Register to reset the mobile phone number interface from the core
    C->>B: Loading plug -in
    B->>C: SMS event
    D->>C: access“mine - Certification management“ Reset the mobile phone number function in the page
    D->>A: Enter the mobile phone number,Clicked【send messages】Button,Access SMS sending interface
    A->>B: Generate SMS verification code,Send and_SMS event
    B->>A: Response event,Send a text message
    A->>D: Return to SMS sending results
    D->>C: Enter the verification code information,Clicked【confirm】Button
    C->>A: Access reset mobile phone number interface
    A->>C: Response interface,Check the input parameter,Return result
    C->>D: test result,And prompt whether to complete the change
- Administrator user: Replace the user's mobile phone number
sequenceDiagram
    participant D as user
    participant C as Platform core
    participant A as Mobile phone verification code authentication factors plugin
    C->>A: Loading plug -in
    A->>C: Towards“user list-Edit user”Page injection of mobile phone number elements
    D->>C: Administrator login,Visiting User List page,Edit the user's mobile phone number and save
    C->>D: Write data to database
Abstract method implementation#
- load
- authenticate
- register
- reset_password
- create_login_page
- create_register_page
- create_password_page
- create_other_page
- create_auth_manage_page
- check_auth_data
- fix_login_page
Code#
        
extension_root.com_longgui_auth_factor_mobile.MobileAuthFactorExtension            (AuthFactorExtension)
        
#
    手机短信验证码认证因素插件
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          class MobileAuthFactorExtension(AuthFactorExtension):
    """手机短信验证码认证因素插件
    """
    def load(self):
        """加载插件
        """
        super().load()
        self.create_extension_config_schema()
        self.register_extend_field(UserMobile, "mobile")
        from api.v1.schema.auth import AuthIn
        from api.v1.schema.user import UserCreateIn,UserItemOut,UserUpdateIn,UserListItemOut
        from api.v1.schema.mine import ProfileSchemaOut
        self.register_extend_api(
            AuthIn,
            UserCreateIn, 
            UserItemOut, 
            UserUpdateIn, 
            UserListItemOut,
            mobile=(Optional[str],Field(title=_("电话号码"))),
            # areacode=(str,Field(title=_("区号")))
        )
        self.register_extend_api(
            ProfileSchemaOut, 
            mobile=(Optional[str],Field(readonly=True))
        )
        # 注册发送短信接口
        self.url_send_sms_code = self.register_api(
            '/config/{config_id}/send_sms_code/',
            'POST',
            self.send_sms_code,
            tenant_path=True,
            auth=None,
            response=SendSMSCodeOut,
        )
        print(self.url_send_sms_code)
    def authenticate(self, event, **kwargs):
        """ 认证
        通过手机号码查找用户并校验短信验证码
        Args:
            event (Event): 事件
        """
        tenant = event.tenant
        request = event.request
        data = request.POST or json.load(request.body)
        mobile = data.get('mobile')
        sms_code = data.get('sms_code')
        # user = User.expand_objects.filter(tenant=tenant,mobile=mobile)
        temp_users = tenant.users.all()
        user_ids = []
        for temp_user in temp_users:
            user_ids.append(temp_user.id)
        user = User.expand_objects.filter(
            is_active=True,
            is_del=False,
            id__in=user_ids,
            mobile=mobile
        )
        if len(user) > 1:
            logger.error(f'{mobile}在数据库中匹配到多个用户')
            return self.auth_failed(event, data=self.error(ErrorCode.CONTACT_MANAGER))
        if user:
            user = user[0]
            if check_sms_code(tenant, mobile, sms_code):
                user = User.active_objects.get(id=user.get("id"))
                return self.auth_success(user,event)
            else:
                msg = ErrorCode.SMS_CODE_MISMATCH
        else:
            msg = ErrorCode.MOBILE_NOT_EXISTS_ERROR
        return self.auth_failed(event, data=self.error(msg))
    @transaction.atomic()
    def register(self, event, **kwargs):
        """ 注册用户
        Args:
            event (Event): 事件
        """
        tenant = event.tenant
        request = event.request
        data = request.POST or json.load(request.body)
        mobile = data.get('mobile')
        sms_code = data.get('sms_code')
        username = data.get('username')
        config = self.get_current_config(event)
        ret, message = self.check_mobile_exists(mobile, tenant)
        if not ret:
            return self.error(message)
        if not check_sms_code(tenant, mobile, sms_code):
            return self.error(ErrorCode.SMS_CODE_MISMATCH)
        ret, message = self.check_username_exists(username, tenant)
        if not ret:
            return self.error(message)
        user = User(tenant=tenant)
        user.mobile = mobile
        user.username = username
        user.save()
        tenant.users.add(user)
        tenant.save()
        return user
    def reset_password(self, event, **kwargs):
        """ 重置密码
        Args:
            event (Event): 事件
        """
        tenant = event.tenant
        request = event.request
        data = request.POST or json.load(request.body)
        mobile = data.get('mobile')
        sms_code = data.get('sms_code')
        password = data.get('password')
        checkpassword = data.get('checkpassword')
        if password != checkpassword:
            return self.error(ErrorCode.PASSWORD_IS_INCONSISTENT)
        if not check_sms_code(tenant, mobile, sms_code):
            return self.error(ErrorCode.SMS_CODE_MISMATCH)
        # user = User.expand_objects.filter(tenant=tenant,mobile=mobile)
        temp_users = tenant.users.all()
        user_ids = []
        for temp_user in temp_users:
            user_ids.append(temp_user.id)
        user = User.expand_objects.filter(
            is_active=True,
            is_del=False,
            id__in=user_ids,
            mobile=mobile
        )
        if len(user) > 1:
            logger.error(f'{mobile}在数据库中匹配到多个用户')
            return self.error(ErrorCode.CONTACT_MANAGER)
        if user:
            user = user[0]
            user.password = make_password(password)
            user.save()
            return self.success()
        return self.error(ErrorCode.MOBILE_NOT_EXISTS_ERROR)
    def create_login_page(self, event, config, config_data):
        """ 生成手机验证码登录页面Schema描述
        Args:
            event (Event): 事件
            config (TenantExtensionConfig): 插件运行时配置
        """
        items = [
            {
                "type": "text",
                "name":"mobile",
                "placeholder": "手机号码",
                "append": {
                    "title": "发送验证码",
                    "http": {
                        "url": self.url_send_sms_code,
                        "method": "post",
                        "params": {
                            "mobile": "mobile",
                            "areacode": "86",
                        },
                    },
                    "delay": 60
                }
            },
            {
                "type": "text",
                "name":"sms_code",
                "placeholder": "验证码",
            }
        ]
        self.add_page_form(config, self.LOGIN, "手机验证码登录", items, config_data)
    def create_register_page(self, event, config, config_data):
        """生成手机验证码用户注册页面Schema描述
        因本插件提供重置密码功能,此处需用户指定账号用户名
        Args:
            event (Event): 事件
            config (TenantExtensionConfig): 插件运行时配置
        """
        items = [
            {
                "type": "text",
                "name": "username",
                "placeholder": "用户名"
            },
            {
                "type": "text",
                "name":"mobile",
                "placeholder": "手机号码",
                "append": {
                    "title": "发送验证码",
                    "http": {
                        "url": self.url_send_sms_code,
                        "method": "post",
                        "params": {
                            "mobile": "mobile",
                            "areacode": "86",
                        },
                    },
                    "delay": 60
                }
            },
            {
                "type": "text",
                "name":"sms_code",
                "placeholder": "验证码"
            }
        ]
        self.add_page_form(config, self.REGISTER, "手机验证码注册", items, config_data)
    def create_password_page(self, event, config, config_data):
        """生成重置密码页面Schema描述
        通过手机验证码重置密码时需提供手机号码以及对应验证码,同时此处添加新密码确认机制
        注意:重置密码功能需要启用用户名密码认证插件以提供完整支持
        Args:
            event (Event): 事件
            config (TenantExtensionConfig): 插件运行时配置
        """
        items = [
            {
                "type": "text",
                "name":"mobile",
                "placeholder": "手机号码",
                "append": {
                    "title": "发送验证码",
                    "http": {
                        "url": self.url_send_sms_code,
                        "method": "post",
                        "params": {
                            "mobile": "mobile",
                            "areacode": "86",
                        },
                    },
                }
            },
            {
                "type": "text",
                "name":"sms_code",
                "placeholder": "验证码"
            },
            {
                "type": "password",
                "name":"password",
                "placeholder": "密码"
            },
            {
                "type": "password",
                "name":"checkpassword",
                "placeholder": "密码确认"
            }
        ]
        self.add_page_form(config, self.RESET_PASSWORD, "手机验证码重置密码", items, config_data)
    def create_other_page(self, event, config, config_data):
        """创建其他页面(本插件无相关页面)
        Args:
            event (Event): 事件
            config (TenantExtensionConfig): 插件运行时配置
        """
        pass
    def check_mobile_exists(self, mobile, tenant):
        """检查电话号码是否已存在
        Args:
            mobile (str): 手机号
            tenant (Tenant): 租户
        Returns:
            (bool,ErrorCode): mobile是否存在以及对应错误
        """
        if not mobile:
            return False, ErrorCode.MOBILE_EMPTY
        # 需要临时存储
        temp_users = tenant.users.all()
        user_ids = []
        for temp_user in temp_users:
            user_ids.append(temp_user.id)
        if User.expand_objects.filter(
            is_active=True,
            is_del=False,
            id__in=user_ids,
            mobile=mobile
        ).count():
        # if User.expand_objects.filter(tenant=tenant,mobile=mobile).count():
            return False, ErrorCode.MOBILE_EXISTS_ERROR
        return True, None
    def check_username_exists(self,username,tenant):
        """检查用户名是否已存在
        Args:
            username (str): 用户名
            tenant (Tenant): 租户
        Returns:
            (bool,ErrorCode): username是否存在以及对应错误
        """
        # 检查username是否为空
        if not username:
            return False, ErrorCode.USERNAME_EMPTY
        # 检查username是否已存在
        if tenant.users.filter(is_active=True, is_del=False).filter(username=username).count():
        # if User.expand_objects.filter(tenant=tenant,username=username).count():
            return False, ErrorCode.USERNAME_EXISTS_ERROR
        return True, None
    def check_auth_data(self, event, **kwargs):
        pass
    def fix_login_page(self, event, **kwargs):
        pass
    def create_auth_manage_page(self):
        """ 创建“我的-认证管理”中的更换手机号码页面
        """
        _pages = []
        mine_mobile_path = self.register_api(
            "/mine_mobile/",
            "GET",
            self.mine_mobile,
            tenant_path=True,
            auth=GlobalAuth(),
            response=MineMobileOut
        )
        upodate_mine_mobile_path = self.register_api(
            "/mine_mobile/",
            'POST',
            self.update_mine_mobile,
            tenant_path=True,
            auth=GlobalAuth(),
            response=UpdateMineMobileOut
        )
        name = '更改手机号码'
        page = pages.FormPage(name=name)
        page.create_actions(
            init_action=actions.DirectAction(
                path=mine_mobile_path,
                method=actions.FrontActionMethod.GET,
            ),
            global_actions={
                'confirm': actions.ConfirmAction(
                    path=upodate_mine_mobile_path
                ),
            }
        )
        _pages.append(page)
        return _pages
    def create_extension_config_schema(self):
        """创建插件运行时配置schema描述
        """
        select_sms_page = pages.TablePage(select=True,name=_("指定短信插件运行时"))
        self.register_front_pages(select_sms_page)
        select_sms_page.create_actions(
            init_action=actions.DirectAction(
                path='/api/v1/tenants/{tenant_id}/config_select/?extension__type=sms',
                method=actions.FrontActionMethod.GET
            )
        )
        MobileAuthFactorSchema = create_extension_schema(
            'MobileAuthFactorSchema',
            __file__, 
            [
                (
                    'sms_config', 
                    MobileAuthFactorConfigSchema, 
                    Field(
                        title=_('sms extension config', '短信插件运行时'),
                        page=select_sms_page.tag,
                    ),
                ),
                (
                    'code_length', 
                    int, 
                    Field(
                        title=_('code_length', '验证码长度'),
                        default=6
                    )
                ),
                (
                    'expired', 
                    Optional[int],
                    Field(
                        title=_('expired', '有效期/分钟'),
                        default=10,
                    )
                ),
            ],
            BaseAuthFactorSchema,
        )
        self.register_auth_factor_schema(MobileAuthFactorSchema, 'mobile')
    @operation(SendSMSCodeOut)
    def send_sms_code(self,request,tenant_id,config_id:str,data:SendSMSCodeIn):
        """发送短信验证码
        """
        tenant = request.tenant
        mobile = data.mobile
        config = self.get_config_by_id(config_id)
        if not config:
            return self.error(ErrorCode.CONFIG_IS_NOT_EXISTS)
        if not mobile or mobile=="mobile":
            return self.error(ErrorCode.MOBILE_EMPTY)
        code = create_sms_code(tenant,mobile,config.config.get('code_length',6),config.config.get("expired",10)*60)
        responses = dispatch_event(
            Event(
                tag=SEND_SMS,
                tenant=tenant,
                request=request,
                data={
                    "config_id":config.config["sms_config"]["id"],
                    "mobile":data.mobile,
                    "code": code,
                    "areacode": data.areacode,
                    "username": request.user.username if request.user else ""
                },
                packages=config.config["sms_config"]["package"]
            )
        )
        if not responses:
            return self.error(ErrorCode.SMS_EXTENSION_NOT_EXISTS)
        useless, (data, extension) = responses[0]
        if data:
            return self.success()
        else:
            return self.error(ErrorCode.SMS_SEND_FAILED)
    @operation(UpdateMineMobileOut,roles=[TENANT_ADMIN, PLATFORM_ADMIN, NORMAL_USER])
    def update_mine_mobile(self, request, tenant_id: str,data:UpdateMineMobileIn):
        """ 普通用户:更新手机号码
        """
        mobile = data.mobile
        ret, message = self.check_mobile_exists(mobile, request.tenant)
        if not ret:
            return self.error(message)
        if not check_sms_code(request.tenant,mobile,data.code):
            return self.error(ErrorCode.SMS_CODE_MISMATCH)
        user = request.user
        user.mobile=data.mobile
        user.save()
        return self.success()
    @operation(MineMobileOut,roles=[TENANT_ADMIN, PLATFORM_ADMIN, NORMAL_USER])
    def mine_mobile(self,request,tenant_id: str):
        user = request.user
        user_expand = User.expand_objects.filter(id=user.id).first()
        config = self.get_tenant_configs(request.tenant).first()
        if not config:
            return self.error(
                ErrorCode.CONFIG_IS_NOT_EXISTS
            )
        return self.success(
            data={
                "current_mobile": user_expand.get("mobile",None),
                "mobile": "",
                "code": "",
                "config_id": config.id.hex,
            },
        )
authenticate(self, event, **kwargs)
#
    认证
通过手机号码查找用户并校验短信验证码
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| event | Event | 事件 | required | 
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          def authenticate(self, event, **kwargs):
    """ 认证
    通过手机号码查找用户并校验短信验证码
    Args:
        event (Event): 事件
    """
    tenant = event.tenant
    request = event.request
    data = request.POST or json.load(request.body)
    mobile = data.get('mobile')
    sms_code = data.get('sms_code')
    # user = User.expand_objects.filter(tenant=tenant,mobile=mobile)
    temp_users = tenant.users.all()
    user_ids = []
    for temp_user in temp_users:
        user_ids.append(temp_user.id)
    user = User.expand_objects.filter(
        is_active=True,
        is_del=False,
        id__in=user_ids,
        mobile=mobile
    )
    if len(user) > 1:
        logger.error(f'{mobile}在数据库中匹配到多个用户')
        return self.auth_failed(event, data=self.error(ErrorCode.CONTACT_MANAGER))
    if user:
        user = user[0]
        if check_sms_code(tenant, mobile, sms_code):
            user = User.active_objects.get(id=user.get("id"))
            return self.auth_success(user,event)
        else:
            msg = ErrorCode.SMS_CODE_MISMATCH
    else:
        msg = ErrorCode.MOBILE_NOT_EXISTS_ERROR
    return self.auth_failed(event, data=self.error(msg))
check_auth_data(self, event, **kwargs)
#
    
  
check_mobile_exists(self, mobile, tenant)
#
    检查电话号码是否已存在
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| mobile | str | 手机号 | required | 
| tenant | Tenant | 租户 | required | 
Returns:
| Type | Description | 
|---|---|
| (bool,ErrorCode) | mobile是否存在以及对应错误 | 
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          def check_mobile_exists(self, mobile, tenant):
    """检查电话号码是否已存在
    Args:
        mobile (str): 手机号
        tenant (Tenant): 租户
    Returns:
        (bool,ErrorCode): mobile是否存在以及对应错误
    """
    if not mobile:
        return False, ErrorCode.MOBILE_EMPTY
    # 需要临时存储
    temp_users = tenant.users.all()
    user_ids = []
    for temp_user in temp_users:
        user_ids.append(temp_user.id)
    if User.expand_objects.filter(
        is_active=True,
        is_del=False,
        id__in=user_ids,
        mobile=mobile
    ).count():
    # if User.expand_objects.filter(tenant=tenant,mobile=mobile).count():
        return False, ErrorCode.MOBILE_EXISTS_ERROR
    return True, None
check_username_exists(self, username, tenant)
#
    检查用户名是否已存在
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| username | str | 用户名 | required | 
| tenant | Tenant | 租户 | required | 
Returns:
| Type | Description | 
|---|---|
| (bool,ErrorCode) | username是否存在以及对应错误 | 
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          def check_username_exists(self,username,tenant):
    """检查用户名是否已存在
    Args:
        username (str): 用户名
        tenant (Tenant): 租户
    Returns:
        (bool,ErrorCode): username是否存在以及对应错误
    """
    # 检查username是否为空
    if not username:
        return False, ErrorCode.USERNAME_EMPTY
    # 检查username是否已存在
    if tenant.users.filter(is_active=True, is_del=False).filter(username=username).count():
    # if User.expand_objects.filter(tenant=tenant,username=username).count():
        return False, ErrorCode.USERNAME_EXISTS_ERROR
    return True, None
create_auth_manage_page(self)
#
    创建“我的-认证管理”中的更换手机号码页面
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          def create_auth_manage_page(self):
    """ 创建“我的-认证管理”中的更换手机号码页面
    """
    _pages = []
    mine_mobile_path = self.register_api(
        "/mine_mobile/",
        "GET",
        self.mine_mobile,
        tenant_path=True,
        auth=GlobalAuth(),
        response=MineMobileOut
    )
    upodate_mine_mobile_path = self.register_api(
        "/mine_mobile/",
        'POST',
        self.update_mine_mobile,
        tenant_path=True,
        auth=GlobalAuth(),
        response=UpdateMineMobileOut
    )
    name = '更改手机号码'
    page = pages.FormPage(name=name)
    page.create_actions(
        init_action=actions.DirectAction(
            path=mine_mobile_path,
            method=actions.FrontActionMethod.GET,
        ),
        global_actions={
            'confirm': actions.ConfirmAction(
                path=upodate_mine_mobile_path
            ),
        }
    )
    _pages.append(page)
    return _pages
create_extension_config_schema(self)
#
    创建插件运行时配置schema描述
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          def create_extension_config_schema(self):
    """创建插件运行时配置schema描述
    """
    select_sms_page = pages.TablePage(select=True,name=_("指定短信插件运行时"))
    self.register_front_pages(select_sms_page)
    select_sms_page.create_actions(
        init_action=actions.DirectAction(
            path='/api/v1/tenants/{tenant_id}/config_select/?extension__type=sms',
            method=actions.FrontActionMethod.GET
        )
    )
    MobileAuthFactorSchema = create_extension_schema(
        'MobileAuthFactorSchema',
        __file__, 
        [
            (
                'sms_config', 
                MobileAuthFactorConfigSchema, 
                Field(
                    title=_('sms extension config', '短信插件运行时'),
                    page=select_sms_page.tag,
                ),
            ),
            (
                'code_length', 
                int, 
                Field(
                    title=_('code_length', '验证码长度'),
                    default=6
                )
            ),
            (
                'expired', 
                Optional[int],
                Field(
                    title=_('expired', '有效期/分钟'),
                    default=10,
                )
            ),
        ],
        BaseAuthFactorSchema,
    )
    self.register_auth_factor_schema(MobileAuthFactorSchema, 'mobile')
create_login_page(self, event, config, config_data)
#
    生成手机验证码登录页面Schema描述
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| event | Event | 事件 | required | 
| config | TenantExtensionConfig | 插件运行时配置 | required | 
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          def create_login_page(self, event, config, config_data):
    """ 生成手机验证码登录页面Schema描述
    Args:
        event (Event): 事件
        config (TenantExtensionConfig): 插件运行时配置
    """
    items = [
        {
            "type": "text",
            "name":"mobile",
            "placeholder": "手机号码",
            "append": {
                "title": "发送验证码",
                "http": {
                    "url": self.url_send_sms_code,
                    "method": "post",
                    "params": {
                        "mobile": "mobile",
                        "areacode": "86",
                    },
                },
                "delay": 60
            }
        },
        {
            "type": "text",
            "name":"sms_code",
            "placeholder": "验证码",
        }
    ]
    self.add_page_form(config, self.LOGIN, "手机验证码登录", items, config_data)
create_other_page(self, event, config, config_data)
#
    创建其他页面(本插件无相关页面)
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| event | Event | 事件 | required | 
| config | TenantExtensionConfig | 插件运行时配置 | required | 
create_password_page(self, event, config, config_data)
#
    生成重置密码页面Schema描述
通过手机验证码重置密码时需提供手机号码以及对应验证码,同时此处添加新密码确认机制
注意:重置密码功能需要启用用户名密码认证插件以提供完整支持
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| event | Event | 事件 | required | 
| config | TenantExtensionConfig | 插件运行时配置 | required | 
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          def create_password_page(self, event, config, config_data):
    """生成重置密码页面Schema描述
    通过手机验证码重置密码时需提供手机号码以及对应验证码,同时此处添加新密码确认机制
    注意:重置密码功能需要启用用户名密码认证插件以提供完整支持
    Args:
        event (Event): 事件
        config (TenantExtensionConfig): 插件运行时配置
    """
    items = [
        {
            "type": "text",
            "name":"mobile",
            "placeholder": "手机号码",
            "append": {
                "title": "发送验证码",
                "http": {
                    "url": self.url_send_sms_code,
                    "method": "post",
                    "params": {
                        "mobile": "mobile",
                        "areacode": "86",
                    },
                },
            }
        },
        {
            "type": "text",
            "name":"sms_code",
            "placeholder": "验证码"
        },
        {
            "type": "password",
            "name":"password",
            "placeholder": "密码"
        },
        {
            "type": "password",
            "name":"checkpassword",
            "placeholder": "密码确认"
        }
    ]
    self.add_page_form(config, self.RESET_PASSWORD, "手机验证码重置密码", items, config_data)
create_register_page(self, event, config, config_data)
#
    生成手机验证码用户注册页面Schema描述
因本插件提供重置密码功能,此处需用户指定账号用户名
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| event | Event | 事件 | required | 
| config | TenantExtensionConfig | 插件运行时配置 | required | 
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          def create_register_page(self, event, config, config_data):
    """生成手机验证码用户注册页面Schema描述
    因本插件提供重置密码功能,此处需用户指定账号用户名
    Args:
        event (Event): 事件
        config (TenantExtensionConfig): 插件运行时配置
    """
    items = [
        {
            "type": "text",
            "name": "username",
            "placeholder": "用户名"
        },
        {
            "type": "text",
            "name":"mobile",
            "placeholder": "手机号码",
            "append": {
                "title": "发送验证码",
                "http": {
                    "url": self.url_send_sms_code,
                    "method": "post",
                    "params": {
                        "mobile": "mobile",
                        "areacode": "86",
                    },
                },
                "delay": 60
            }
        },
        {
            "type": "text",
            "name":"sms_code",
            "placeholder": "验证码"
        }
    ]
    self.add_page_form(config, self.REGISTER, "手机验证码注册", items, config_data)
fix_login_page(self, event, **kwargs)
#
    
  
load(self)
#
    加载插件
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          def load(self):
    """加载插件
    """
    super().load()
    self.create_extension_config_schema()
    self.register_extend_field(UserMobile, "mobile")
    from api.v1.schema.auth import AuthIn
    from api.v1.schema.user import UserCreateIn,UserItemOut,UserUpdateIn,UserListItemOut
    from api.v1.schema.mine import ProfileSchemaOut
    self.register_extend_api(
        AuthIn,
        UserCreateIn, 
        UserItemOut, 
        UserUpdateIn, 
        UserListItemOut,
        mobile=(Optional[str],Field(title=_("电话号码"))),
        # areacode=(str,Field(title=_("区号")))
    )
    self.register_extend_api(
        ProfileSchemaOut, 
        mobile=(Optional[str],Field(readonly=True))
    )
    # 注册发送短信接口
    self.url_send_sms_code = self.register_api(
        '/config/{config_id}/send_sms_code/',
        'POST',
        self.send_sms_code,
        tenant_path=True,
        auth=None,
        response=SendSMSCodeOut,
    )
    print(self.url_send_sms_code)
register(self, event, **kwargs)
#
    注册用户
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| event | Event | 事件 | required | 
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          @transaction.atomic()
def register(self, event, **kwargs):
    """ 注册用户
    Args:
        event (Event): 事件
    """
    tenant = event.tenant
    request = event.request
    data = request.POST or json.load(request.body)
    mobile = data.get('mobile')
    sms_code = data.get('sms_code')
    username = data.get('username')
    config = self.get_current_config(event)
    ret, message = self.check_mobile_exists(mobile, tenant)
    if not ret:
        return self.error(message)
    if not check_sms_code(tenant, mobile, sms_code):
        return self.error(ErrorCode.SMS_CODE_MISMATCH)
    ret, message = self.check_username_exists(username, tenant)
    if not ret:
        return self.error(message)
    user = User(tenant=tenant)
    user.mobile = mobile
    user.username = username
    user.save()
    tenant.users.add(user)
    tenant.save()
    return user
reset_password(self, event, **kwargs)
#
    重置密码
Parameters:
| Name | Type | Description | Default | 
|---|---|---|---|
| event | Event | 事件 | required | 
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          def reset_password(self, event, **kwargs):
    """ 重置密码
    Args:
        event (Event): 事件
    """
    tenant = event.tenant
    request = event.request
    data = request.POST or json.load(request.body)
    mobile = data.get('mobile')
    sms_code = data.get('sms_code')
    password = data.get('password')
    checkpassword = data.get('checkpassword')
    if password != checkpassword:
        return self.error(ErrorCode.PASSWORD_IS_INCONSISTENT)
    if not check_sms_code(tenant, mobile, sms_code):
        return self.error(ErrorCode.SMS_CODE_MISMATCH)
    # user = User.expand_objects.filter(tenant=tenant,mobile=mobile)
    temp_users = tenant.users.all()
    user_ids = []
    for temp_user in temp_users:
        user_ids.append(temp_user.id)
    user = User.expand_objects.filter(
        is_active=True,
        is_del=False,
        id__in=user_ids,
        mobile=mobile
    )
    if len(user) > 1:
        logger.error(f'{mobile}在数据库中匹配到多个用户')
        return self.error(ErrorCode.CONTACT_MANAGER)
    if user:
        user = user[0]
        user.password = make_password(password)
        user.save()
        return self.success()
    return self.error(ErrorCode.MOBILE_NOT_EXISTS_ERROR)
send_sms_code(self, request, tenant_id, config_id, data)
#
    发送短信验证码
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          @operation(SendSMSCodeOut)
def send_sms_code(self,request,tenant_id,config_id:str,data:SendSMSCodeIn):
    """发送短信验证码
    """
    tenant = request.tenant
    mobile = data.mobile
    config = self.get_config_by_id(config_id)
    if not config:
        return self.error(ErrorCode.CONFIG_IS_NOT_EXISTS)
    if not mobile or mobile=="mobile":
        return self.error(ErrorCode.MOBILE_EMPTY)
    code = create_sms_code(tenant,mobile,config.config.get('code_length',6),config.config.get("expired",10)*60)
    responses = dispatch_event(
        Event(
            tag=SEND_SMS,
            tenant=tenant,
            request=request,
            data={
                "config_id":config.config["sms_config"]["id"],
                "mobile":data.mobile,
                "code": code,
                "areacode": data.areacode,
                "username": request.user.username if request.user else ""
            },
            packages=config.config["sms_config"]["package"]
        )
    )
    if not responses:
        return self.error(ErrorCode.SMS_EXTENSION_NOT_EXISTS)
    useless, (data, extension) = responses[0]
    if data:
        return self.success()
    else:
        return self.error(ErrorCode.SMS_SEND_FAILED)
update_mine_mobile(self, request, tenant_id, data)
#
    普通用户:更新手机号码
Source code in extension_root/com_longgui_auth_factor_mobile/__init__.py
          @operation(UpdateMineMobileOut,roles=[TENANT_ADMIN, PLATFORM_ADMIN, NORMAL_USER])
def update_mine_mobile(self, request, tenant_id: str,data:UpdateMineMobileIn):
    """ 普通用户:更新手机号码
    """
    mobile = data.mobile
    ret, message = self.check_mobile_exists(mobile, request.tenant)
    if not ret:
        return self.error(message)
    if not check_sms_code(request.tenant,mobile,data.code):
        return self.error(ErrorCode.SMS_CODE_MISMATCH)
    user = request.user
    user.mobile=data.mobile
    user.save()
    return self.success()




