From 0846e67ed6bc5e4b4b4fb6a99f2637dd5d7c0079 Mon Sep 17 00:00:00 2001 From: joshuakrueger-dfx Date: Tue, 9 Jun 2026 13:29:49 +0200 Subject: [PATCH] feat(auth): log wallet name on every login Adds a walletName column to IpLog and threads the login-request wallet through to the per-login audit trail: - IpCountryGuard forwards req.body.wallet for signature logins - mail login persists the resolved login wallet name and passes it on completion Captures which app/wallet a login originated from, complementing the mail-branding fix (#3846). Refs #3849. --- .../1781004217553-AddWalletNameToIpLog.js | 26 +++++++++++++++++++ src/shared/auth/ip-country.guard.ts | 2 +- src/shared/models/ip-log/ip-log.entity.ts | 3 +++ src/shared/models/ip-log/ip-log.service.ts | 15 ++++++++--- .../generic/user/models/auth/auth.service.ts | 11 +++++++- 5 files changed, 52 insertions(+), 5 deletions(-) create mode 100644 migration/1781004217553-AddWalletNameToIpLog.js diff --git a/migration/1781004217553-AddWalletNameToIpLog.js b/migration/1781004217553-AddWalletNameToIpLog.js new file mode 100644 index 0000000000..689d5b54ba --- /dev/null +++ b/migration/1781004217553-AddWalletNameToIpLog.js @@ -0,0 +1,26 @@ +/** + * @typedef {import('typeorm').MigrationInterface} MigrationInterface + * @typedef {import('typeorm').QueryRunner} QueryRunner + */ + +/** + * @class + * @implements {MigrationInterface} + */ +module.exports = class AddWalletNameToIpLog1781004217553 { + name = 'AddWalletNameToIpLog1781004217553' + + /** + * @param {QueryRunner} queryRunner + */ + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "ip_log" ADD "walletName" character varying(256)`); + } + + /** + * @param {QueryRunner} queryRunner + */ + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "ip_log" DROP COLUMN "walletName"`); + } +} diff --git a/src/shared/auth/ip-country.guard.ts b/src/shared/auth/ip-country.guard.ts index f4298cbb00..ef0639803d 100644 --- a/src/shared/auth/ip-country.guard.ts +++ b/src/shared/auth/ip-country.guard.ts @@ -12,7 +12,7 @@ export class IpCountryGuard implements CanActivate { const address = req.body?.address ?? req.user?.address; if (!address) throw new BadRequestException('Address is required'); - const ipLog = await this.ipLogService.create(ip, req.url, address, req.body?.walletType); + const ipLog = await this.ipLogService.create(ip, req.url, address, req.body?.walletType, req.body?.wallet); if (!ipLog.result) throw new ForbiddenException('The country of IP address is not allowed'); const region = +req.body?.region; diff --git a/src/shared/models/ip-log/ip-log.entity.ts b/src/shared/models/ip-log/ip-log.entity.ts index cb322554f7..2fa1fb7c35 100644 --- a/src/shared/models/ip-log/ip-log.entity.ts +++ b/src/shared/models/ip-log/ip-log.entity.ts @@ -24,6 +24,9 @@ export class IpLog extends IEntity { @Column({ length: 256, nullable: true }) walletType?: WalletType; + @Column({ length: 256, nullable: true }) + walletName?: string; + @Index() @ManyToOne(() => User, { nullable: true }) user?: User; diff --git a/src/shared/models/ip-log/ip-log.service.ts b/src/shared/models/ip-log/ip-log.service.ts index ce7a575844..5c49d7ec0b 100644 --- a/src/shared/models/ip-log/ip-log.service.ts +++ b/src/shared/models/ip-log/ip-log.service.ts @@ -25,10 +25,17 @@ export class IpLogService { private readonly idCache = new AsyncCache(CacheItemResetPeriod.EVERY_6_MONTHS); private readonly recentLogCache = new AsyncCache(CacheItemResetPeriod.EVERY_10_SECONDS); - async create(ip: string, url: string, address: string, walletType?: WalletType, userData?: UserData): Promise { - const cacheKey = `${ip}:${address}:${url}:${walletType ?? ''}:${userData?.id ?? ''}`; + async create( + ip: string, + url: string, + address: string, + walletType?: WalletType, + walletName?: string, + userData?: UserData, + ): Promise { + const cacheKey = `${ip}:${address}:${url}:${walletType ?? ''}:${walletName ?? ''}:${userData?.id ?? ''}`; - return this.recentLogCache.get(cacheKey, () => this.doCreate(ip, url, address, walletType, userData)); + return this.recentLogCache.get(cacheKey, () => this.doCreate(ip, url, address, walletType, walletName, userData)); } private async doCreate( @@ -36,6 +43,7 @@ export class IpLogService { url: string, address: string, walletType?: WalletType, + walletName?: string, userData?: UserData, ): Promise { const { country, result, user } = await this.checkIpCountry(ip, address); @@ -48,6 +56,7 @@ export class IpLogService { user, userData: userData ?? user?.userData, walletType, + walletName, }); return this.ipLogRepo.save(ipLog); diff --git a/src/subdomains/generic/user/models/auth/auth.service.ts b/src/subdomains/generic/user/models/auth/auth.service.ts index 69189c7154..f1a57ad382 100644 --- a/src/subdomains/generic/user/models/auth/auth.service.ts +++ b/src/subdomains/generic/user/models/auth/auth.service.ts @@ -63,6 +63,7 @@ export interface MailKeyData { userDataId: number; loginUrl: string; redirectUri?: string; + walletName?: string; } @Injectable() @@ -297,6 +298,7 @@ export class AuthService { userDataId: userData.id, loginUrl: url, redirectUri: dto.redirectUri, + walletName: loginWallet?.name, }); // send notification @@ -337,7 +339,14 @@ export class AuthService { const account = await this.userDataService.getUserData(entry.userDataId, { users: true, wallet: true }); if (account.status === UserDataStatus.MERGED) throw new UnauthorizedException('User data is merged'); - const ipLog = await this.ipLogService.create(ip, entry.loginUrl, entry.mail, undefined, account); + const ipLog = await this.ipLogService.create( + ip, + entry.loginUrl, + entry.mail, + undefined, + entry.walletName, + account, + ); if (!ipLog.result) throw new Error('The country of IP address is not allowed'); const token = this.generateAccountToken(account, ip);