user sessions
This commit is contained in:
parent
ece7f41484
commit
226f7c3370
@ -47,6 +47,10 @@ else
|
|||||||
<strong>مشقهای پیشنهادی من</strong>
|
<strong>مشقهای پیشنهادی من</strong>
|
||||||
<span class="up-text-muted">اشعاری که به عنوان مشق پیشنهاد دادهاید</span>
|
<span class="up-text-muted">اشعاری که به عنوان مشق پیشنهاد دادهاید</span>
|
||||||
</a>
|
</a>
|
||||||
|
<a class="up-card up-quick-link" asp-area="User" asp-page="/MySessions">
|
||||||
|
<strong>نشستهای من</strong>
|
||||||
|
<span class="up-text-muted">دستگاههای واردشده به حساب شما و خروج از هر یک از آنها</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="up-card">
|
<div class="up-card">
|
||||||
|
|||||||
84
GanjooRazor/Areas/User/Pages/MySessions.cshtml
Normal file
84
GanjooRazor/Areas/User/Pages/MySessions.cshtml
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
@page
|
||||||
|
@model GanjooRazor.Areas.User.Pages.MySessionsModel
|
||||||
|
@using DNTPersianUtils.Core
|
||||||
|
@{
|
||||||
|
Layout = "_UserPanelLayout";
|
||||||
|
ViewData["Title"] = "نشستهای من";
|
||||||
|
await GanjooRazor.Utils.GanjoorSessionChecker.ApplyPermissionsToViewData(Request, Response, ViewData);
|
||||||
|
}
|
||||||
|
<div class="up-page-header">
|
||||||
|
<h1>@ViewData["Title"]</h1>
|
||||||
|
<div class="up-page-subtitle">دستگاهها و مرورگرهایی که هماکنون با حساب کاربری شما وارد گنجور شدهاند. اگر دستگاهی را نمیشناسید یا دیگر از آن استفاده نمیکنید، آن را از این فهرست خارج کنید.</div>
|
||||||
|
</div>
|
||||||
|
@if (Model.LastError != "")
|
||||||
|
{
|
||||||
|
<div class="up-alert up-alert--danger">@Model.LastError</div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<script>
|
||||||
|
async function deleteSession(sessionId, isCurrent) {
|
||||||
|
var message = isCurrent
|
||||||
|
? 'این همان نشستی است که هماکنون با آن وارد شدهاید. با حذف آن از این دستگاه هم خارج خواهید شد. آیا ادامه میدهید؟'
|
||||||
|
: 'آیا از خروج این دستگاه از حساب کاربری خود اطمینان دارید؟';
|
||||||
|
var ok = await upConfirm(message);
|
||||||
|
if (!ok) return;
|
||||||
|
|
||||||
|
$.ajax({
|
||||||
|
type: "DELETE",
|
||||||
|
url: '?handler=Session',
|
||||||
|
data: { id: sessionId },
|
||||||
|
success: function (res) {
|
||||||
|
if (res && res.loggedOutSelf) {
|
||||||
|
upToast('از این دستگاه خارج شدید.', 'success');
|
||||||
|
location.href = '/';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$('#session-' + sessionId).remove();
|
||||||
|
upToast('نشست حذف شد.', 'success');
|
||||||
|
},
|
||||||
|
error: function (e) {
|
||||||
|
upToast(e.responseText || 'حذف نشست با خطا مواجه شد.', 'error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
@if (Model.Sessions.Count == 0)
|
||||||
|
{
|
||||||
|
<partial name="_EmptyState" model="@("هیچ نشست فعالی یافت نشد.")" />
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<div class="up-list">
|
||||||
|
@foreach (var session in Model.Sessions)
|
||||||
|
{
|
||||||
|
bool isCurrent = session.Id == Model.CurrentSessionId;
|
||||||
|
<div class="up-list-item" id="session-@session.Id">
|
||||||
|
<div>
|
||||||
|
<strong>@(string.IsNullOrEmpty(session.ClientAppName) ? "برنامهٔ نامشخص" : session.ClientAppName)</strong>
|
||||||
|
@if (isCurrent)
|
||||||
|
{
|
||||||
|
<span class="up-badge up-badge--primary">این دستگاه</span>
|
||||||
|
}
|
||||||
|
<div class="up-list-item__meta up-field--ltr" style="direction: ltr; text-align: right; unicode-bidi: plaintext;">
|
||||||
|
@session.ClientIPAddress
|
||||||
|
</div>
|
||||||
|
<div class="up-list-item__meta">
|
||||||
|
ورود: @session.LoginTime.ToLongPersianDateTimeString()
|
||||||
|
·
|
||||||
|
آخرین تمدید: @session.LastRenewal.ToLongPersianDateTimeString()
|
||||||
|
·
|
||||||
|
معتبر تا: @session.ValidUntil.ToLongPersianDateTimeString()
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="up-list-item__actions">
|
||||||
|
<a role="button" onclick="deleteSession('@session.Id', @(isCurrent ? "true" : "false"))" class="up-icon-btn" title="خروج این دستگاه" aria-label="خروج این دستگاه از حساب کاربری">
|
||||||
|
<i class="noindent-info-button delete-icon"></i>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
126
GanjooRazor/Areas/User/Pages/MySessions.cshtml.cs
Normal file
126
GanjooRazor/Areas/User/Pages/MySessions.cshtml.cs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using GanjooRazor.Pages;
|
||||||
|
using GanjooRazor.Utils;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
using RSecurityBackend.Models.Auth.ViewModels;
|
||||||
|
|
||||||
|
namespace GanjooRazor.Areas.User.Pages
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Lets a logged-on user see every session (device/browser) currently able to use their
|
||||||
|
/// account, and revoke any one of them - the self-service alternative to a hard absolute
|
||||||
|
/// session ceiling: instead of forcing everyone to relogin periodically, the user can look at
|
||||||
|
/// this list and kill a session they don't recognize (a lost/stolen device, a shared computer
|
||||||
|
/// they forgot to log out of, ...) whenever they suspect something.
|
||||||
|
/// </summary>
|
||||||
|
[IgnoreAntiforgeryToken(Order = 1001)]
|
||||||
|
public class MySessionsModel : GanjoorPageModelBase
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Last Error
|
||||||
|
/// </summary>
|
||||||
|
public string LastError { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the user's own sessions, most recently active first
|
||||||
|
/// </summary>
|
||||||
|
public List<PublicRUserSession> Sessions { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// the SessionId cookie of the browser rendering this page, so the view can mark it as
|
||||||
|
/// "this device" and the delete handler can special-case removing it.
|
||||||
|
/// </summary>
|
||||||
|
public Guid CurrentSessionId { get; set; }
|
||||||
|
|
||||||
|
public MySessionsModel(HttpClient httpClient) : base(httpClient)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IActionResult> OnGetAsync()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(Request.Cookies["Token"]))
|
||||||
|
return Redirect("/");
|
||||||
|
|
||||||
|
Guid.TryParse(Request.Cookies["SessionId"], out Guid currentSessionId);
|
||||||
|
CurrentSessionId = currentSessionId;
|
||||||
|
|
||||||
|
LastError = "";
|
||||||
|
using (HttpClient secureClient = new HttpClient(new GanjoorReloginHandler(Request, Response)))
|
||||||
|
if (await GanjoorSessionChecker.PrepareClient(secureClient, Request, Response))
|
||||||
|
{
|
||||||
|
var response = await secureClient.GetAsync($"{APIRoot.Url}/api/users/sessions?userId={Request.Cookies["UserId"]}");
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
LastError = await ReadErrorMessageAsync(response);
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
Sessions = JArray.Parse(await response.Content.ReadAsStringAsync())
|
||||||
|
.ToObject<List<PublicRUserSession>>()
|
||||||
|
.OrderByDescending(s => s.LastRenewal)
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
LastError = NotLoggedInMessage;
|
||||||
|
}
|
||||||
|
return Page();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deletes one of the user's own sessions (the API only allows deleting your own session
|
||||||
|
/// here without the extra user:delothersession permission - see AppUserControllerBase.Logout).
|
||||||
|
/// If the session being deleted is the one this very request is authenticated with, this
|
||||||
|
/// browser's auth cookies are cleared too (mirroring LoginPartialEnabledPageModel's
|
||||||
|
/// OnPostLogoutAsync), so it doesn't keep showing a "logged in" page for a session that no
|
||||||
|
/// longer exists server-side; the caller (see the view) then redirects home instead of just
|
||||||
|
/// removing the row from the list.
|
||||||
|
/// </summary>
|
||||||
|
public async Task<IActionResult> OnDeleteSessionAsync(Guid id)
|
||||||
|
{
|
||||||
|
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/delsession?userId={Request.Cookies["UserId"]}&sessionId={id}");
|
||||||
|
if (response.StatusCode != HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
return new BadRequestObjectResult(await ReadErrorMessageAsync(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new BadRequestObjectResult(NotLoggedInMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool loggedOutSelf = Request.Cookies["SessionId"] == id.ToString();
|
||||||
|
if (loggedOutSelf)
|
||||||
|
{
|
||||||
|
var cookieOption = new CookieOptions()
|
||||||
|
{
|
||||||
|
Expires = DateTime.Now.AddDays(-1),
|
||||||
|
HttpOnly = true,
|
||||||
|
Secure = true,
|
||||||
|
SameSite = SameSiteMode.Lax,
|
||||||
|
};
|
||||||
|
foreach (var cookieName in new string[] { "UserId", "SessionId", "Token", "Username", "Name", "NickName", "CanEdit", "KeepHistory", "CanTranslate" })
|
||||||
|
{
|
||||||
|
if (Request.Cookies[cookieName] != null)
|
||||||
|
{
|
||||||
|
Response.Cookies.Append(cookieName, "", cookieOption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new JsonResult(new { loggedOutSelf });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -102,6 +102,10 @@
|
|||||||
<a class="nav-link text-dark" asp-area="User" asp-page="/Notifications">اعلانهای من</a>
|
<a class="nav-link text-dark" asp-area="User" asp-page="/Notifications">اعلانهای من</a>
|
||||||
</li>
|
</li>
|
||||||
|
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link text-dark" asp-area="User" asp-page="/MySessions">نشستهای من</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
<li class="nav-item dropdown">
|
<li class="nav-item dropdown">
|
||||||
<a class="nav-link text-dark dropdown-toggle" href="#" id="upContribsDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">مشارکتهای من</a>
|
<a class="nav-link text-dark dropdown-toggle" href="#" id="upContribsDropdown" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">مشارکتهای من</a>
|
||||||
<div class="dropdown-menu" aria-labelledby="upContribsDropdown">
|
<div class="dropdown-menu" aria-labelledby="upContribsDropdown">
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user