Login now send a notification

This commit is contained in:
2025-08-17 23:43:13 +08:00
parent add16ffdad
commit 2761abf405
5 changed files with 59 additions and 8 deletions

View File

@@ -3,8 +3,14 @@ using Microsoft.AspNetCore.Mvc;
using NodaTime; using NodaTime;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using DysonNetwork.Pass.Account; using DysonNetwork.Pass.Account;
using DysonNetwork.Pass.Localization;
using DysonNetwork.Shared.Data; using DysonNetwork.Shared.Data;
using DysonNetwork.Shared.GeoIp; using DysonNetwork.Shared.GeoIp;
using DysonNetwork.Shared.Proto;
using Microsoft.Extensions.Localization;
using AccountAuthFactor = DysonNetwork.Pass.Account.AccountAuthFactor;
using AccountService = DysonNetwork.Pass.Account.AccountService;
using ActionLogService = DysonNetwork.Pass.Account.ActionLogService;
namespace DysonNetwork.Pass.Auth; namespace DysonNetwork.Pass.Auth;
@@ -16,7 +22,9 @@ public class AuthController(
AuthService auth, AuthService auth,
GeoIpService geo, GeoIpService geo,
ActionLogService als, ActionLogService als,
IConfiguration configuration PusherService.PusherServiceClient pusher,
IConfiguration configuration,
IStringLocalizer<NotificationResource> localizer
) : ControllerBase ) : ControllerBase
{ {
private readonly string _cookieDomain = configuration["AuthToken:CookieDomain"]!; private readonly string _cookieDomain = configuration["AuthToken:CookieDomain"]!;
@@ -50,7 +58,8 @@ public class AuthController(
request.DeviceName ??= userAgent; request.DeviceName ??= userAgent;
var device = await auth.GetOrCreateDeviceAsync(account.Id, request.DeviceId, request.DeviceName, request.Platform); var device =
await auth.GetOrCreateDeviceAsync(account.Id, request.DeviceId, request.DeviceName, request.Platform);
// Trying to pick up challenges from the same IP address and user agent // Trying to pick up challenges from the same IP address and user agent
var existingChallenge = await db.AuthChallenges var existingChallenge = await db.AuthChallenges
@@ -64,7 +73,8 @@ public class AuthController(
.FirstOrDefaultAsync(); .FirstOrDefaultAsync();
if (existingChallenge is not null) if (existingChallenge is not null)
{ {
var existingSession = await db.AuthSessions.Where(e => e.ChallengeId == existingChallenge.Id).FirstOrDefaultAsync(); var existingSession = await db.AuthSessions.Where(e => e.ChallengeId == existingChallenge.Id)
.FirstOrDefaultAsync();
if (existingSession is null) return existingChallenge; if (existingSession is null) return existingChallenge;
} }
@@ -156,7 +166,10 @@ public class AuthController(
[FromBody] PerformChallengeRequest request [FromBody] PerformChallengeRequest request
) )
{ {
var challenge = await db.AuthChallenges.Include(e => e.Account).FirstOrDefaultAsync(e => e.Id == id); var challenge = await db.AuthChallenges
.Include(e => e.Account)
.Include(authChallenge => authChallenge.Client)
.FirstOrDefaultAsync(e => e.Id == id);
if (challenge is null) return NotFound("Auth challenge was not found."); if (challenge is null) return NotFound("Auth challenge was not found.");
var factor = await db.AccountAuthFactors.FindAsync(request.FactorId); var factor = await db.AccountAuthFactors.FindAsync(request.FactorId);
@@ -206,6 +219,19 @@ public class AuthController(
if (challenge.StepRemain == 0) if (challenge.StepRemain == 0)
{ {
AccountService.SetCultureInfo(challenge.Account);
await pusher.SendPushNotificationToUserAsync(new SendPushNotificationToUserRequest
{
Notification = new PushNotification()
{
Topic = "auth.login",
Title = localizer["NewLoginTitle"],
Body = localizer["NewLoginBody", challenge.Client?.DeviceName ?? "unknown",
challenge.IpAddress ?? "unknown"],
IsSavable = true
},
UserId = challenge.AccountId.ToString()
});
als.CreateActionLogFromRequest(ActionLogType.NewLogin, als.CreateActionLogFromRequest(ActionLogType.NewLogin,
new Dictionary<string, object> new Dictionary<string, object>
{ {

View File

@@ -28,7 +28,7 @@ namespace DysonNetwork.Sphere.Resources.Localization {
internal static System.Resources.ResourceManager ResourceManager { internal static System.Resources.ResourceManager ResourceManager {
get { get {
if (object.Equals(null, resourceMan)) { if (object.Equals(null, resourceMan)) {
System.Resources.ResourceManager temp = new System.Resources.ResourceManager("DysonNetwork.Sphere.Resources.Localization.NotificationResource", typeof(NotificationResource).Assembly); System.Resources.ResourceManager temp = new System.Resources.ResourceManager("DysonNetwork.Pass.Resources.Localization.NotificationResource", typeof(NotificationResource).Assembly);
resourceMan = temp; resourceMan = temp;
} }
return resourceMan; return resourceMan;
@@ -158,5 +158,17 @@ namespace DysonNetwork.Sphere.Resources.Localization {
return ResourceManager.GetString("OrderPaidBody", resourceCulture); return ResourceManager.GetString("OrderPaidBody", resourceCulture);
} }
} }
internal static string NewLoginTitle {
get {
return ResourceManager.GetString("NewLoginTitle", resourceCulture);
}
}
internal static string NewLoginBody {
get {
return ResourceManager.GetString("NewLoginBody", resourceCulture);
}
}
} }
} }

View File

@@ -80,4 +80,10 @@
<data name="OrderPaidBody" xml:space="preserve"> <data name="OrderPaidBody" xml:space="preserve">
<value>{0} {1} was removed from your wallet to pay {2}</value> <value>{0} {1} was removed from your wallet to pay {2}</value>
</data> </data>
<data name="NewLoginTitle" xml:space="preserve">
<value>New login detected</value>
</data>
<data name="NewLoginBody" xml:space="preserve">
<value>Your account logged on to a device named {0} at {1}</value>
</data>
</root> </root>

View File

@@ -72,4 +72,10 @@
<data name="OrderPaidBody" xml:space="preserve"> <data name="OrderPaidBody" xml:space="preserve">
<value>{0} {1} 已从你的帐户中扣除来支付 {2}</value> <value>{0} {1} 已从你的帐户中扣除来支付 {2}</value>
</data> </data>
<data name="NewLoginTitle" xml:space="preserve">
<value>检测到新登陆</value>
</data>
<data name="NewLoginBody" xml:space="preserve">
<value>您的帐号在位于 {1} 的设备 {0} 上刚刚登陆了</value>
</data>
</root> </root>

View File

@@ -148,6 +148,7 @@
&lt;Assembly Path="/opt/homebrew/Cellar/dotnet/9.0.6/libexec/packs/Microsoft.AspNetCore.App.Ref/9.0.6/ref/net9.0/Microsoft.AspNetCore.RateLimiting.dll" /&gt; &lt;Assembly Path="/opt/homebrew/Cellar/dotnet/9.0.6/libexec/packs/Microsoft.AspNetCore.App.Ref/9.0.6/ref/net9.0/Microsoft.AspNetCore.RateLimiting.dll" /&gt;
&lt;/AssemblyExplorer&gt;</s:String> &lt;/AssemblyExplorer&gt;</s:String>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002EPass_002FResources_002FLocalization_002FAccountEventResource/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002EPass_002FResources_002FLocalization_002FAccountEventResource/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002EPass_002FResources_002FLocalization_002FNotificationResource/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FAccountEventResource/@EntryIndexedValue">False</s:Boolean> <s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FAccountEventResource/@EntryIndexedValue">False</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FAccountEventResource/@EntryIndexRemoved">True</s:Boolean> <s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FAccountEventResource/@EntryIndexRemoved">True</s:Boolean>
<s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FSharedResource/@EntryIndexedValue">False</s:Boolean> <s:Boolean x:Key="/Default/ResxEditorPersonal/CheckedGroups/=DysonNetwork_002ESphere_002FLocalization_002FResources_002FSharedResource/@EntryIndexedValue">False</s:Boolean>