From 18913e7064dd7a67516d856bdf16190e5c7ac760 Mon Sep 17 00:00:00 2001 From: yumoqing Date: Fri, 10 Jul 2026 11:24:25 +0800 Subject: [PATCH] feat: domain-based reseller binding on registration + test SMS bypass --- rbac/check_perm.py | 2 + wwwroot/gen_sms_code.dspy | 68 +++---- wwwroot/user/register.dspy | 156 ++++++++------- wwwroot/user/sms_register.dspy | 335 +++++++++++++++++---------------- 4 files changed, 301 insertions(+), 260 deletions(-) diff --git a/rbac/check_perm.py b/rbac/check_perm.py index d584b5d..a395f13 100644 --- a/rbac/check_perm.py +++ b/rbac/check_perm.py @@ -118,6 +118,8 @@ async def register_user(sor, ns): ns.created_at = timestampstr() ns.login_fail_count = 0 ns1 = DictObject(id=id, orgname=ns.username) + if ns.get('parentid'): + ns1.parentid = ns.parentid await create_org(sor, ns1) roles = [ { diff --git a/wwwroot/gen_sms_code.dspy b/wwwroot/gen_sms_code.dspy index 6acbabf..3516989 100644 --- a/wwwroot/gen_sms_code.dspy +++ b/wwwroot/gen_sms_code.dspy @@ -1,36 +1,40 @@ +# TEST MODE: 不发送真实短信,返回固定验证码 123456 phone = params_kw.cellphone if phone is None: - return { - "status": "error", - "data":{ - "message": "没有收到手机号" - } - } -# 使用短信模块发布的sms_engine实例生成验证码,参数手机号 + return { + "status": "error", + "data": { + "message": "没有收到手机号" + } + } try: - xx = await sms_engine.generate_sms_code(phone) + code = '123456' + code_id = getID() + from datetime import datetime, timedelta + expire_time = datetime.now() + timedelta(minutes=5) + env = request._run_ns + async with get_sor_context(env, 'smssend') as sor: + await sor.C('validatecode', { + 'id': code_id, + 'vcode': code, + 'expire_time': expire_time, + 'del_flg': '0', + 'create_at': datetime.now() + }) + debug(f'TEST MODE: code_id={code_id}, code={code}, phone={phone}') + return { + "status": "ok", + "data": { + "message": f"测试模式: 验证码 {code}", + "key": code_id + } + } except Exception as e: - debug(f'gen_sms_code error: {e}') - exception(f'gen_sms_code error for {phone}: {e}') - return { - "status": "error", - "data": { - "message": f"发送验证码出错: {e}" - } - } -if xx is None: - return { - "status": "error", - "data": { - "message": "发送验证码出错,请检查短信模板配置和百度API连接" - } - } -id, code = xx -debug(f'{id=},{code=}') -return { - "status": "ok", - "data": { - "message": "短信码已生成", - "key": id - } -} \ No newline at end of file + debug(f'gen_sms_code error: {e}') + exception(f'gen_sms_code error for {phone}: {e}') + return { + "status": "error", + "data": { + "message": f"发送验证码出错: {e}" + } + } diff --git a/wwwroot/user/register.dspy b/wwwroot/user/register.dspy index 0c03da4..538588a 100644 --- a/wwwroot/user/register.dspy +++ b/wwwroot/user/register.dspy @@ -1,78 +1,96 @@ debug(f'register.dspy: {params_kw=}') db = DBPools() dbname = get_module_dbname('rbac') + +# 根据注册域名查找所属分销商 +parentid = None +try: + host = request.host + env = request._run_ns + domain_info = await env.get_domain_by_host(request, host) + if domain_info: + parentid = domain_info.resellerid + debug(f'register: domain={host} -> resellerid={parentid}') + else: + debug(f'register: domain={host} not found in tenant_domain') +except Exception as e: + exception(f'register: domain lookup error: {e}') + +if parentid: + params_kw.parentid = parentid + async with db.sqlorContext(dbname) as sor: - data = await register_user(sor, params_kw) - data = DictObject(**data) - if data.status == 'error': - debug(f"register error: {data.data.message}") - return { - "widgettype": "Error", - "options": { - "timeout": 5, - "title": "注册失败", - "message": data.data.message - } - } - user = data.data.user - orgid = user.orgid - try: - await openCustomerAccounts(sor, '0', orgid) - debug(f'{orgid} accounts opened') - except Exception as e: - exception(f'{e},{orgid=}') + data = await register_user(sor, params_kw) + data = DictObject(**data) + if data.status == 'error': + debug(f"register error: {data.data.message}") + return { + "widgettype": "Error", + "options": { + "timeout": 5, + "title": "注册失败", + "message": data.data.message + } + } + user = data.data.user + orgid = user.orgid + try: + await openCustomerAccounts(sor, '0', orgid) + debug(f'{orgid} accounts opened') + except Exception as e: + exception(f'{e},{orgid=}') - # 检查是否有促销码,如果有则绑定客户归属关系 - promo_code_id = params_kw.get('promo_code_id') - if promo_code_id: - try: - await bind_customer(request, DictObject(promo_code_id=promo_code_id, customerid=orgid)) - debug(f'register: customer {orgid} bound via promo_code {promo_code_id}') - except Exception as e: - exception(f'register: bind_customer failed for {orgid} via {promo_code_id}: {e}') + # 检查是否有促销码,如果有则绑定客户归属关系 + promo_code_id = params_kw.get('promo_code_id') + if promo_code_id: + try: + await bind_customer(request, DictObject(promo_code_id=promo_code_id, customerid=orgid)) + debug(f'register: customer {orgid} bound via promo_code {promo_code_id}') + except Exception as e: + exception(f'register: bind_customer failed for {orgid} via {promo_code_id}: {e}') - # 注册成功后自动登录 - await remember_user(user.id, username=user.username, userorgid=user.orgid) - return { - "widgettype": "Message", - "options": { - "timeout": 3, - "auto_open": True, - "title": "注册成功", - "message": f"{user.username} 注册成功,已自动登录" - }, - "binds": [ - { - "wid": "self", - "event": "dismissed", - "actiontype": "urlwidget", - "target": "window.user_container", - "options": { - "url": entire_url('/rbac/user/userinfo.ui') - } - }, - { - "wid": "self", - "event": "dismissed", - "actiontype": "script", - "target": "body.login_window", - "script": "if(this.destroy) this.destroy()" - }, - { - "wid": "self", - "event": "dismissed", - "actiontype": "script", - "target": "self", - "script": "if(bricks.app && bricks.app.dispatch) bricks.app.dispatch('user_logined')" - } - ] - } + # 注册成功后自动登录 + await remember_user(user.id, username=user.username, userorgid=user.orgid) + return { + "widgettype": "Message", + "options": { + "timeout": 3, + "auto_open": True, + "title": "注册成功", + "message": f"{user.username} 注册成功,已自动登录" + }, + "binds": [ + { + "wid": "self", + "event": "dismissed", + "actiontype": "urlwidget", + "target": "window.user_container", + "options": { + "url": entire_url('/rbac/user/userinfo.ui') + } + }, + { + "wid": "self", + "event": "dismissed", + "actiontype": "script", + "target": "body.login_window", + "script": "if(this.destroy) this.destroy()" + }, + { + "wid": "self", + "event": "dismissed", + "actiontype": "script", + "target": "self", + "script": "if(bricks.app && bricks.app.dispatch) bricks.app.dispatch('user_logined')" + } + ] + } return { - "widgettype": "Error", - "options": { - "timeout": 5, - "title": "注册失败", - "message": "系统错误,请稍后重试" - } + "widgettype": "Error", + "options": { + "timeout": 5, + "title": "注册失败", + "message": "系统错误,请稍后重试" + } } diff --git a/wwwroot/user/sms_register.dspy b/wwwroot/user/sms_register.dspy index 762a1cf..6afece4 100644 --- a/wwwroot/user/sms_register.dspy +++ b/wwwroot/user/sms_register.dspy @@ -11,186 +11,203 @@ cfm_password = params_kw.cfm_password # 基本参数校验 if not username: - return { - "widgettype": "Error", - "options": { - "timeout": 3, - "title": "注册失败", - "message": "请输入用户名" - } - } + return { + "widgettype": "Error", + "options": { + "timeout": 3, + "title": "注册失败", + "message": "请输入用户名" + } + } if not mobile: - return { - "widgettype": "Error", - "options": { - "timeout": 3, - "title": "注册失败", - "message": "请输入手机号" - } - } + return { + "widgettype": "Error", + "options": { + "timeout": 3, + "title": "注册失败", + "message": "请输入手机号" + } + } if not password: - return { - "widgettype": "Error", - "options": { - "timeout": 3, - "title": "注册失败", - "message": "请输入密码" - } - } + return { + "widgettype": "Error", + "options": { + "timeout": 3, + "title": "注册失败", + "message": "请输入密码" + } + } if password != cfm_password: - return { - "widgettype": "Error", - "options": { - "timeout": 3, - "title": "注册失败", - "message": "两次输入的密码不一致" - } - } + return { + "widgettype": "Error", + "options": { + "timeout": 3, + "title": "注册失败", + "message": "两次输入的密码不一致" + } + } if not key or not sms_code: - return { - "widgettype": "Error", - "options": { - "timeout": 3, - "title": "注册失败", - "message": "请先发送并输入短信验证码" - } - } + return { + "widgettype": "Error", + "options": { + "timeout": 3, + "title": "注册失败", + "message": "请先发送并输入短信验证码" + } + } # 验证短信码 try: - ok = await sms_engine.check_sms_code(key, sms_code) + ok = await sms_engine.check_sms_code(key, sms_code) except Exception as e: - exception(f'sms_register sms check error: {e}') - ok = False + exception(f'sms_register sms check error: {e}') + ok = False if not ok: - return { - "widgettype": "Error", - "options": { - "timeout": 3, - "title": "验证失败", - "message": "短信验证码错误或已过期,请重新获取" - } - } + return { + "widgettype": "Error", + "options": { + "timeout": 3, + "title": "验证失败", + "message": "短信验证码错误或已过期,请重新获取" + } + } + +# 根据注册域名查找所属分销商 +parentid = None +try: + host = request.host + env = request._run_ns + domain_info = await env.get_domain_by_host(request, host) + if domain_info: + parentid = domain_info.resellerid + debug(f'sms_register: domain={host} -> resellerid={parentid}') + else: + debug(f'sms_register: domain={host} not found in tenant_domain') +except Exception as e: + exception(f'sms_register: domain lookup error: {e}') # 短信验证通过,注册用户 db = DBPools() dbname = get_module_dbname('rbac') try: - async with db.sqlorContext(dbname) as sor: - # 检查手机号是否已注册 - existing = await sor.R('users', {'mobile': mobile}) - if existing: - return { - "widgettype": "Error", - "options": { - "timeout": 5, - "title": "注册失败", - "message": "该手机号已注册,请直接登录" - } - } + async with db.sqlorContext(dbname) as sor: + # 检查手机号是否已注册 + existing = await sor.R('users', {'mobile': mobile}) + if existing: + return { + "widgettype": "Error", + "options": { + "timeout": 5, + "title": "注册失败", + "message": "该手机号已注册,请直接登录" + } + } - # 检查用户名是否已存在 - existing_user = await sor.R('users', {'username': username}) - if existing_user: - return { - "widgettype": "Error", - "options": { - "timeout": 5, - "title": "注册失败", - "message": "用户名已被占用" - } - } + # 检查用户名是否已存在 + existing_user = await sor.R('users', {'username': username}) + if existing_user: + return { + "widgettype": "Error", + "options": { + "timeout": 5, + "title": "注册失败", + "message": "用户名已被占用" + } + } - # 调用注册函数 - reg_params = DictObject( - username=username, - mobile=mobile, - password=password, - cfm_password=cfm_password - ) - data = await register_user(sor, reg_params) - data = DictObject(**data) - if data.status == 'error': - debug(f"sms_register error: {data.data.message}") - return { - "widgettype": "Error", - "options": { - "timeout": 5, - "title": "注册失败", - "message": data.data.message - } - } + # 调用注册函数 + reg_params = DictObject( + username=username, + mobile=mobile, + password=password, + cfm_password=cfm_password + ) + if parentid: + reg_params.parentid = parentid - user = data.data.user - orgid = user.orgid - try: - await openCustomerAccounts(sor, '0', orgid) - debug(f'{orgid} accounts opened') - except Exception as e: - exception(f'{e},{orgid=}') + data = await register_user(sor, reg_params) + data = DictObject(**data) + if data.status == 'error': + debug(f"sms_register error: {data.data.message}") + return { + "widgettype": "Error", + "options": { + "timeout": 5, + "title": "注册失败", + "message": data.data.message + } + } - # 检查是否有促销码,如果有则绑定客户归属关系 - promo_code_id = params_kw.get('promo_code_id') - if promo_code_id: - try: - await bind_customer(request, DictObject(promo_code_id=promo_code_id, customerid=orgid)) - debug(f'sms_register: customer {orgid} bound via promo_code {promo_code_id}') - except Exception as e: - exception(f'sms_register: bind_customer failed for {orgid} via {promo_code_id}: {e}') + user = data.data.user + orgid = user.orgid + try: + await openCustomerAccounts(sor, '0', orgid) + debug(f'{orgid} accounts opened') + except Exception as e: + exception(f'{e},{orgid=}') - # 注册成功后自动登录 - await remember_user(user.id, username=user.username, userorgid=user.orgid) - return { - "widgettype": "Message", - "options": { - "timeout": 3, - "auto_open": True, - "title": "注册成功", - "message": f"{user.username} 注册成功,已自动登录" - }, - "binds": [ - { - "wid": "self", - "event": "dismissed", - "actiontype": "urlwidget", - "target": "window.user_container", - "options": { - "url": entire_url('/rbac/user/userinfo.ui') - } - }, - { - "wid": "self", - "event": "dismissed", - "actiontype": "script", - "target": "body.login_window", - "script": "this.destroy()" - }, - { - "wid": "self", - "event": "dismissed", - "actiontype": "script", - "target": "self", - "script": "if(bricks.app && bricks.app.dispatch) bricks.app.dispatch('user_logined')" - } - ] - } + # 检查是否有促销码,如果有则绑定客户归属关系 + promo_code_id = params_kw.get('promo_code_id') + if promo_code_id: + try: + await bind_customer(request, DictObject(promo_code_id=promo_code_id, customerid=orgid)) + debug(f'sms_register: customer {orgid} bound via promo_code {promo_code_id}') + except Exception as e: + exception(f'sms_register: bind_customer failed for {orgid} via {promo_code_id}: {e}') + + # 注册成功后自动登录 + await remember_user(user.id, username=user.username, userorgid=user.orgid) + return { + "widgettype": "Message", + "options": { + "timeout": 3, + "auto_open": True, + "title": "注册成功", + "message": f"{user.username} 注册成功,已自动登录" + }, + "binds": [ + { + "wid": "self", + "event": "dismissed", + "actiontype": "urlwidget", + "target": "window.user_container", + "options": { + "url": entire_url('/rbac/user/userinfo.ui') + } + }, + { + "wid": "self", + "event": "dismissed", + "actiontype": "script", + "target": "body.login_window", + "script": "this.destroy()" + }, + { + "wid": "self", + "event": "dismissed", + "actiontype": "script", + "target": "self", + "script": "if(bricks.app && bricks.app.dispatch) bricks.app.dispatch('user_logined')" + } + ] + } except Exception as e: - exception(f'sms_register error: {e}') - return { - "widgettype": "Error", - "options": { - "timeout": 5, - "title": "系统错误", - "message": f"注册失败: {e}" - } - } + exception(f'sms_register error: {e}') + return { + "widgettype": "Error", + "options": { + "timeout": 5, + "title": "系统错误", + "message": f"注册失败: {e}" + } + } return { - "widgettype": "Error", - "options": { - "timeout": 5, - "title": "注册失败", - "message": "系统错误,请稍后重试" - } + "widgettype": "Error", + "options": { + "timeout": 5, + "title": "注册失败", + "message": "系统错误,请稍后重试" + } }