logout from other sessions

This commit is contained in:
Hamid Reza Mohammadi 2026-09-27 15:49:07 +03:30
parent 7a722455ec
commit 601c29af36
3 changed files with 61 additions and 1 deletions

View File

@ -1,6 +1,7 @@
@page
@model GanjooRazor.Areas.User.Pages.MySessionsModel
@using DNTPersianUtils.Core
@using System.Linq
@{
Layout = "_UserPanelLayout";
ViewData["Title"] = "نشست‌های من";
@ -10,6 +11,14 @@
<h1>@ViewData["Title"]</h1>
<div class="up-page-subtitle">دستگاه‌ها و مرورگرهایی که هم‌اکنون با حساب کاربری شما وارد گنجور شده‌اند. اگر دستگاهی را نمی‌شناسید یا دیگر از آن استفاده نمی‌کنید، آن را از این فهرست خارج کنید.</div>
</div>
@if (Model.LastError == "" && Model.Sessions != null && Model.Sessions.Count(s => s.Id != Model.CurrentSessionId) > 0)
{
<div style="margin-bottom: var(--up-space-3);">
<a role="button" onclick="logoutOtherSessions()" class="up-btn up-btn--danger" id="logout-others-btn">
خروج از سایر دستگاه‌ها
</a>
</div>
}
@if (Model.LastError != "")
{
<div class="up-alert up-alert--danger">@Model.LastError</div>
@ -42,6 +51,29 @@ else
}
});
}
async function logoutOtherSessions() {
var ok = await upConfirm('با این کار از حساب کاربری خود در تمام دستگاه‌ها و مرورگرهای دیگر (به‌جز همین یکی) خارج خواهید شد. آیا ادامه می‌دهید؟');
if (!ok) return;
$.ajax({
type: "DELETE",
url: '?handler=LogoutOthers',
success: function (res) {
var removedCount = (res && res.removedCount) || 0;
$('.up-list-item').each(function () {
if (this.id !== 'session-@Model.CurrentSessionId') {
$(this).remove();
}
});
$('#logout-others-btn').closest('div').remove();
upToast(removedCount > 0 ? 'از ' + removedCount.toLocaleString('fa-IR') + ' دستگاه دیگر خارج شدید.' : 'دستگاه دیگری برای خروج یافت نشد.', 'success');
},
error: function (e) {
upToast(e.responseText || 'خروج از سایر دستگاه‌ها با خطا مواجه شد.', 'error');
}
});
}
</script>
@if (Model.Sessions.Count == 0)

View File

@ -121,5 +121,33 @@ namespace GanjooRazor.Areas.User.Pages
return new JsonResult(new { loggedOutSelf });
}
/// <summary>
/// Logs the user out of every session but this one (a critical-account cleanup shortcut
/// for someone logged in on several computers, instead of removing each session one at a
/// time). This browser's own session is always the one preserved - it never removes the
/// session the request itself is authenticated with, so unlike OnDeleteSessionAsync there
/// is no "logged out myself" case to special-case here.
/// </summary>
public async Task<IActionResult> OnDeleteLogoutOthersAsync()
{
using (HttpClient secureClient = new HttpClient(new GanjoorReloginHandler(Request, Response)))
{
if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response))
{
var response = await secureClient.DeleteAsync($"{APIRoot.Url}/api/users/delothersessions?userId={Request.Cookies["UserId"]}");
if (response.StatusCode != HttpStatusCode.OK)
{
return new BadRequestObjectResult(await ReadErrorMessageAsync(response));
}
int removedCount = int.Parse(await response.Content.ReadAsStringAsync());
return new JsonResult(new { removedCount });
}
else
{
return new BadRequestObjectResult(NotLoggedInMessage);
}
}
}
}
}

View File

@ -29,7 +29,7 @@
</PackageReference>
<PackageReference Include="Microsoft.ML.OnnxRuntime" Version="1.29.0" />
<PackageReference Include="Microsoft.ML.Tokenizers" Version="2.0.0" />
<PackageReference Include="RSecurityBackend" Version="1.8.2" />
<PackageReference Include="RSecurityBackend" Version="1.8.3" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="10.2.3" />
<PackageReference Include="Swashbuckle.AspNetCore.Filters" Version="10.0.1" />