From 132c25b2711ef1c08d4cb849e5526a4f3846d04f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Sun, 13 Jul 2025 06:15:45 +0200 Subject: [PATCH 1/2] Log IP addresses used during registration Closes #836 --- .../migration/1752377661219-UserPending-ip.js | 14 ++++++++ packages/backend/src/models/UserPending.ts | 6 ++++ .../src/server/api/SignupApiService.ts | 35 ++++++++++++++++++- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 packages/backend/migration/1752377661219-UserPending-ip.js diff --git a/packages/backend/migration/1752377661219-UserPending-ip.js b/packages/backend/migration/1752377661219-UserPending-ip.js new file mode 100644 index 0000000000..12e5d6cc3e --- /dev/null +++ b/packages/backend/migration/1752377661219-UserPending-ip.js @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: наб and other Sharkey contributors + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class UserPendingIp1752377661219 { + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_pending" ADD "requestOriginIp" varchar(128)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user_pending" DROP COLUMN "requestOriginIp"`); + } +} diff --git a/packages/backend/src/models/UserPending.ts b/packages/backend/src/models/UserPending.ts index 972c862a1a..2b4d5ac329 100644 --- a/packages/backend/src/models/UserPending.ts +++ b/packages/backend/src/models/UserPending.ts @@ -37,4 +37,10 @@ export class MiUserPending { nullable: true, }) public reason: string; + + @Column('varchar', { + length: 128, + nullable: true, + }) + public requestOriginIp: string | null; } diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index c1864ce959..590ce44e5b 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common'; import * as argon2 from 'argon2'; import { IsNull } from 'typeorm'; import { DI } from '@/di-symbols.js'; -import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket, MiMeta } from '@/models/_.js'; +import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository, MiRegistrationTicket, MiMeta, UserIpsRepository } from '@/models/_.js'; import type { Config } from '@/config.js'; import { CaptchaService } from '@/core/CaptchaService.js'; import { IdService } from '@/core/IdService.js'; @@ -46,6 +46,9 @@ export class SignupApiService { @Inject(DI.registrationTicketsRepository) private registrationTicketsRepository: RegistrationTicketsRepository, + @Inject(DI.userIpsRepository) + private userIpsRepository: UserIpsRepository, + private userEntityService: UserEntityService, private idService: IdService, private captchaService: CaptchaService, @@ -213,6 +216,7 @@ export class SignupApiService { username: username, password: hash, reason: reason, + requestOriginIp: this.meta.enableIpLogging ? request.ip : null, }); const link = `${this.config.url}/signup-complete/${code}`; @@ -249,6 +253,10 @@ export class SignupApiService { }); } + if (this.meta.enableIpLogging) { + this.logIp(request.ip, null, account.id); + } + const moderators = await this.roleService.getModerators(); for (const moderator of moderators) { @@ -282,6 +290,10 @@ export class SignupApiService { }); } + if (this.meta.enableIpLogging) { + this.logIp(request.ip, null, account.id); + } + return { ...res, token: secret, @@ -332,6 +344,15 @@ export class SignupApiService { }); } + if (pendingUser.requestOriginIp) { + this.logIp(pendingUser.requestOriginIp, this.idService.parse(pendingUser.id).date, account.id); + } + + // The sign-up request and the confirmation may've come from different addresses: log both + if (this.meta.enableIpLogging) { + this.logIp(request.ip, null, account.id); + } + if (this.meta.approvalRequiredForSignup) { if (pendingUser.email) { this.emailService.sendEmail(pendingUser.email, 'Approval pending', @@ -359,4 +380,16 @@ export class SignupApiService { throw new FastifyReplyError(400, String(err), err); } } + + @bindThis + private logIp(ip: string, ipDate: Date | null, userId: MiLocalUser['id']) { + try { + this.userIpsRepository.createQueryBuilder().insert().values({ + createdAt: ipDate ?? new Date(), + userId, + ip, + }).orIgnore(true).execute(); + } catch { + } + } } From e22ae591b6757a147f28c968468aff54f81c0930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=BD=D0=B0=D0=B1?= Date: Mon, 21 Jul 2025 21:22:38 +0200 Subject: [PATCH 2/2] SignupApiService: logIp() additional logging --- packages/backend/src/server/api/SignupApiService.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index 590ce44e5b..86896264dd 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -19,11 +19,14 @@ import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { bindThis } from '@/decorators.js'; import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js'; import { RoleService } from '@/core/RoleService.js'; +import Logger from '@/logger.js'; +import { LoggerService } from '@/core/LoggerService.js'; import { SigninService } from './SigninService.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; @Injectable() export class SignupApiService { + private logger: Logger; constructor( @Inject(DI.config) private config: Config, @@ -56,7 +59,9 @@ export class SignupApiService { private signinService: SigninService, private emailService: EmailService, private roleService: RoleService, + private loggerService: LoggerService, ) { + this.logger = this.loggerService.getLogger('Signup'); } @bindThis @@ -389,7 +394,8 @@ export class SignupApiService { userId, ip, }).orIgnore(true).execute(); - } catch { + } catch (err) { + this.logger.error(err as Error); } } }