fix(auth): hide registration when disabled

This commit is contained in:
kuaifan 2026-08-19 23:18:21 +00:00
parent 826058b842
commit cb60728711
6 changed files with 68 additions and 23 deletions

View File

@ -339,9 +339,9 @@ class UsersController extends AbstractController
} }
/** /**
* @api {get} api/users/reg/needinvite 是否需要邀请码 * @api {get} api/users/reg/needinvite 注册状态
* *
* @apiDescription 用于判断注册是否需要邀请码 * @apiDescription 用于判断是否允许注册是否需要邀请码
* @apiVersion 1.0.0 * @apiVersion 1.0.0
* @apiGroup users * @apiGroup users
* @apiName reg__needinvite * @apiName reg__needinvite
@ -349,11 +349,15 @@ class UsersController extends AbstractController
* @apiSuccess {Number} ret 返回状态码1正确、0错误 * @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述) * @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据 * @apiSuccess {Object} data 返回数据
* @apiSuccess {Boolean} data.enabled 是否允许注册
* @apiSuccess {Boolean} data.need 是否需要邀请码
*/ */
public function reg__needinvite() public function reg__needinvite()
{ {
$reg = Base::settingFind('system', 'reg', 'open');
return Base::retSuccess('success', [ return Base::retSuccess('success', [
'need' => Base::settingFind('system', 'reg') == 'invite' 'enabled' => $reg !== 'close',
'need' => $reg === 'invite',
]); ]);
} }

View File

@ -19,7 +19,7 @@ negative:
- 邀请码与「邀请同事加入项目」是两回事;这里指的是系统级开放注册的密钥 - 邀请码与「邀请同事加入项目」是两回事;这里指的是系统级开放注册的密钥
- 邀请码只有一个全局值,不区分用户、不限次数、不过期 - 邀请码只有一个全局值,不区分用户、不限次数、不过期
- 验证不通过会直接拒绝,没有「邀请码错了几次锁定」的限制 - 验证不通过会直接拒绝,没有「邀请码错了几次锁定」的限制
last_verified: v1.7.90 last_verified: v1.8.89
--- ---
# 注册是否需要邀请码 # 注册是否需要邀请码
@ -39,9 +39,11 @@ DooTask 的注册方式由系统设置 `system.reg` 控制,共三档:
- 注册时把这个字符串填到「邀请码」字段提交,后端比对 `Request.invite == setting.reg_invite` - 注册时把这个字符串填到「邀请码」字段提交,后端比对 `Request.invite == setting.reg_invite`
## 接口判定 ## 接口判定
前端可调 `api/users/reg/needinvite` 拿到 `{ need: true/false }`,据此决定登录页注册 tab 是否展示「邀请码」输入框: 前端可调 `api/users/reg/needinvite` 拿到 `{ enabled: true/false, need: true/false }`,据此决定是否显示注册入口,以及注册表单是否展示「邀请码」输入框:
- `enabled=false` → reg=close隐藏注册入口即使通过 `/login?type=reg` 直达也停留在登录表单
- `enabled=true` → reg=open 或 reg=invite显示注册入口
- `need=true` → reg=invite显示邀请码输入框 - `need=true` → reg=invite显示邀请码输入框
- `need=false` → reg=openreg=close 时注册入口本身就该隐藏) - `need=false` → reg=open 或 reg=close`enabled` 为准
## 与「项目邀请加入」的区别 ## 与「项目邀请加入」的区别
- **本概念**:决定能否成为 DooTask 用户(账号级别) - **本概念**:决定能否成为 DooTask 用户(账号级别)

View File

@ -21,7 +21,7 @@ negative:
- 注册方式为「邀请码」时必须填正确邀请码,否则报「请输入正确的邀请码」 - 注册方式为「邀请码」时必须填正确邀请码,否则报「请输入正确的邀请码」
- 邮箱长度和密码长度都不能超过 32 个字符 - 邮箱长度和密码长度都不能超过 32 个字符
- 开启「注册需邮箱验证」时,注册后必须先验证邮箱才能登录 - 开启「注册需邮箱验证」时,注册后必须先验证邮箱才能登录
last_verified: v1.7.90 last_verified: v1.8.89
--- ---
# 注册账号 # 注册账号
@ -35,7 +35,7 @@ last_verified: v1.7.90
- **open开放**:直接填邮箱+密码即可注册 - **open开放**:直接填邮箱+密码即可注册
- **invite邀请码**:除邮箱+密码外,还需填邀请码;可调 `api/users/reg/needinvite` 提前判断(详见 [[user-account.reg-need-invite.concept]] - **invite邀请码**:除邮箱+密码外,还需填邀请码;可调 `api/users/reg/needinvite` 提前判断(详见 [[user-account.reg-need-invite.concept]]
- **close关闭**:注册入口不可用,提示「未开放注册」 - **close关闭**登录页隐藏注册入口,直达 `/login?type=reg` 也停留在登录表单;直接提交注册接口时提示「未开放注册」
## 操作步骤 ## 操作步骤
1. 进入登录页切到「注册」tab 1. 进入登录页切到「注册」tab

View File

@ -100,7 +100,7 @@
<Button type="primary" :loading="loadIng > 0 || loginJump" size="large" long @click="onLogin">{{$L(loginText)}}</Button> <Button type="primary" :loading="loadIng > 0 || loginJump" size="large" long @click="onLogin">{{$L(loginText)}}</Button>
<div v-if="loginType=='reg'" class="login-switch">{{$L('已经有帐号?')}} <a href="javascript:void(0)" @click="loginType='login'">{{$L('登录帐号')}}</a></div> <div v-if="loginType=='reg'" class="login-switch">{{$L('已经有帐号?')}} <a href="javascript:void(0)" @click="loginType='login'">{{$L('登录帐号')}}</a></div>
<div v-else class="login-switch">{{$L('还没有帐号?')}} <a href="javascript:void(0)" @click="loginType='reg'">{{$L('注册帐号')}}</a></div> <div v-else-if="registrationAvailable" class="login-switch">{{$L('还没有帐号?')}} <a href="javascript:void(0)" @click="loginType='reg'">{{$L('注册帐号')}}</a></div>
</div> </div>
</transition> </transition>
</div> </div>
@ -199,6 +199,7 @@ export default {
invite: '', invite: '',
needInvite: false, needInvite: false,
registrationAvailable: false,
privacyShow: false, privacyShow: false,
} }
@ -225,7 +226,7 @@ export default {
}, },
activated() { activated() {
this.loginType = this.$route.query.type === 'reg' ? 'reg' : 'login' this.refreshRegistrationSetting();
// //
this.getDemoAccount(); this.getDemoAccount();
}, },
@ -284,20 +285,14 @@ export default {
watch: { watch: {
'$route' ({query}) { '$route' ({query}) {
if (query.type=='reg'){ if (this.routeName !== 'login') {
this.$nextTick(()=>{ return;
this.loginType = "reg"
})
} }
this.refreshRegistrationSetting(query.type === 'reg');
}, },
loginMode() { loginMode() {
this.qrcodeRefresh() this.qrcodeRefresh()
}, },
loginType(val) {
if (val == 'reg') {
this.getNeedInvite();
}
},
}, },
methods: { methods: {
@ -321,13 +316,23 @@ export default {
}); });
}, },
getNeedInvite() { refreshRegistrationSetting(requestRegistration = this.$route.query.type === 'reg') {
this.$store.dispatch("call", { if (this.isNotServer()) {
this.registrationAvailable = false;
this.loginType = 'login';
return Promise.resolve();
}
return this.$store.dispatch("call", {
url: 'users/reg/needinvite', url: 'users/reg/needinvite',
}).then(({data}) => { }).then(({data}) => {
this.needInvite = !!data.need; this.needInvite = !!data.need;
// Older servers only return `need`; preserve their original registration behavior.
this.registrationAvailable = typeof data.enabled === 'undefined' || !!data.enabled;
this.loginType = requestRegistration && this.registrationAvailable ? 'reg' : 'login';
}).catch(_ => { }).catch(_ => {
this.needInvite = false; this.needInvite = false;
this.registrationAvailable = false;
this.loginType = 'login';
}); });
}, },

View File

@ -23,7 +23,7 @@ API 使用动态路由(见 `routes/web.php`URL 段映射为控制器方
| api/users/login/codejson | login__codejson() | get | 验证码json | | api/users/login/codejson | login__codejson() | get | 验证码json |
| api/users/logout | logout() | get | 退出登录 | | api/users/logout | logout() | get | 退出登录 |
| api/users/token/expire | token__expire() | get | 查询 token 过期时间 | | api/users/token/expire | token__expire() | get | 查询 token 过期时间 |
| api/users/reg/needinvite | reg__needinvite() | get | 是否需要邀请码 | | api/users/reg/needinvite | reg__needinvite() | get | 注册状态 |
| api/users/info | info() | get | 获取我的信息 | | api/users/info | info() | get | 获取我的信息 |
| api/users/info/managed_departments | info__managed_departments() | get | 获取我可切换负责人视角的部门列表 | | api/users/info/managed_departments | info__managed_departments() | get | 获取我可切换负责人视角的部门列表 |
| api/users/info/departments | info__departments() | get | 获取我的部门列表 | | api/users/info/departments | info__departments() | get | 获取我的部门列表 |

View File

@ -0,0 +1,34 @@
<?php
namespace Tests\Feature;
use App\Module\Base;
use Illuminate\Foundation\Testing\DatabaseTransactions;
use Tests\TestCase;
class RegistrationAvailabilityTest extends TestCase
{
use DatabaseTransactions;
public function test_registration_status_reports_closed_registration(): void
{
Base::setting('system', ['reg' => 'close'], true);
$this->getJson('/api/users/reg/needinvite')
->assertOk()
->assertJsonPath('ret', 1)
->assertJsonPath('data.enabled', false)
->assertJsonPath('data.need', false);
}
public function test_registration_status_reports_invite_registration(): void
{
Base::setting('system', ['reg' => 'invite'], true);
$this->getJson('/api/users/reg/needinvite')
->assertOk()
->assertJsonPath('ret', 1)
->assertJsonPath('data.enabled', true)
->assertJsonPath('data.need', true);
}
}