merge: Log IP addresses used during registration (!1163)

View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/1163

Closes #836

Approved-by: Hazelnoot <acomputerdog@gmail.com>
Approved-by: Marie <github@yuugi.dev>
This commit is contained in:
Marie 2025-08-16 08:06:34 +00:00
commit 1f26659995
3 changed files with 60 additions and 1 deletions

View file

@ -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"`);
}
}

View file

@ -37,4 +37,10 @@ export class MiUserPending {
nullable: true,
})
public reason: string;
@Column('varchar', {
length: 128,
nullable: true,
})
public requestOriginIp: string | null;
}

View file

@ -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';
@ -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,
@ -46,6 +49,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,
@ -53,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
@ -213,6 +221,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 +258,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 +295,10 @@ export class SignupApiService {
});
}
if (this.meta.enableIpLogging) {
this.logIp(request.ip, null, account.id);
}
return {
...res,
token: secret,
@ -332,6 +349,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 +385,17 @@ 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 (err) {
this.logger.error(err as Error);
}
}
}