feat(auth): allow owner to force OTP-only login per user

Team menu «ورود با کد» blocks password login with a clear message and switches the login screen to OTP.
This commit is contained in:
2026-10-22 15:43:24 +03:30
parent 80fb5bf1bb
commit 65cf4e9a66
9 changed files with 181 additions and 11 deletions
+65
View File
@@ -509,6 +509,26 @@ async function toggleStatus(user: OrgUserDto) {
}
}
const LOGIN_OTP_ONLY_TIP =
'ورود فقط با کد پیامکی؛ رمز عبور کار نمی‌کند — حتی اگر از قبل رمز داشته باشد.';
async function toggleLoginOtpOnly(user: OrgUserDto) {
menuUserId.value = null;
if (user.role === 'owner') return;
error.value = '';
ok.value = '';
try {
const next = !user.loginOtpOnly;
await api.patch(`/users/${user.id}/login-otp-only`, { loginOtpOnly: next });
ok.value = next
? `«${user.fullName}» فقط با کد پیامکی وارد می‌شود.`
: `ورود با رمز برای «${user.fullName}» دوباره فعال شد.`;
await load();
} catch (e) {
error.value = apiError(e, 'تغییر شیوهٔ ورود ناموفق بود');
}
}
const logoutModal = ref<{ user: OrgUserDto; time: string } | null>(null);
const logoutSaving = ref(false);
@@ -1152,6 +1172,13 @@ onUnmounted(() => {
<i />
{{ user.isActive === false ? 'غیرفعال' : 'فعال' }}
</span>
<span
v-if="user.loginOtpOnly"
class="otp-badge"
:title="LOGIN_OTP_ONLY_TIP"
>
ورود با کد
</span>
</td>
<td class="muted">{{ formatLastLogin(user.lastLoginAt) }}</td>
<td v-if="canSetLogoutTime" @click.stop>
@@ -1203,6 +1230,15 @@ onUnmounted(() => {
>
ویرایش مشخصات
</button>
<button
v-if="canInvite && user.role !== 'owner'"
type="button"
:class="{ on: user.loginOtpOnly }"
:title="LOGIN_OTP_ONLY_TIP"
@click="toggleLoginOtpOnly(user)"
>
{{ user.loginOtpOnly ? 'لغو ورود با کد' : 'ورود با کد' }}
</button>
<button type="button" @click="toggleStatus(user)">
{{ user.isActive === false ? 'فعال کردن' : 'غیرفعال کردن' }}
</button>
@@ -1255,6 +1291,16 @@ onUnmounted(() => {
>
ویرایش
</button>
<button
v-if="canInvite && user.role !== 'owner'"
type="button"
class="btn ghost"
:class="{ 'otp-on': user.loginOtpOnly }"
:title="LOGIN_OTP_ONLY_TIP"
@click="toggleLoginOtpOnly(user)"
>
{{ user.loginOtpOnly ? 'لغو ورود با کد' : 'ورود با کد' }}
</button>
</div>
</article>
<p v-if="!loading && !pagedUsers.length" class="empty">کاربری یافت نشد.</p>
@@ -1805,6 +1851,25 @@ tbody tr + tr td {
color: #94a3b8;
}
.otp-badge {
display: inline-block;
margin-inline-start: 0.4rem;
padding: 0.12rem 0.45rem;
border-radius: 999px;
background: #ecfdf5;
color: #0f766e;
font-size: 0.72rem;
font-weight: 700;
vertical-align: middle;
cursor: help;
}
.btn.otp-on {
color: #0f766e;
border-color: #99f6e4;
background: #ecfdf5;
}
.ghost-ico {
width: 32px;
height: 32px;