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:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user