mirror of
https://github.com/MisskeyIO/misskey
synced 2024-11-27 14:28:49 +09:00
fix(backend): アカウントの作成と削除の途中でリトライが発生しても無視するように (MisskeyIO#580)
This commit is contained in:
parent
1fb7fb8187
commit
acc10c0709
@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
|
import * as Redis from 'ioredis';
|
||||||
import { bindThis } from '@/decorators.js';
|
import { bindThis } from '@/decorators.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type Logger from '@/logger.js';
|
import type Logger from '@/logger.js';
|
||||||
@ -20,6 +21,8 @@ export class DeleteAccountService {
|
|||||||
public logger: Logger;
|
public logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.redis)
|
||||||
|
private redisClient: Redis.Redis,
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
|
|
||||||
@ -29,7 +32,7 @@ export class DeleteAccountService {
|
|||||||
private globalEventService: GlobalEventService,
|
private globalEventService: GlobalEventService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
) {
|
) {
|
||||||
this.logger = this.loggerService.getLogger('delete-account');
|
this.logger = this.loggerService.getLogger('account:delete');
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@ -39,9 +42,20 @@ export class DeleteAccountService {
|
|||||||
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
const _user = await this.usersRepository.findOneByOrFail({ id: user.id });
|
||||||
if (_user.isRoot) throw new Error('cannot delete a root account');
|
if (_user.isRoot) throw new Error('cannot delete a root account');
|
||||||
|
|
||||||
|
// 5分間の間に同じアカウントに対して削除リクエストが複数回来た場合、最初のリクエストのみを処理する
|
||||||
|
const lock = await this.redisClient.set(`account:delete:lock:${user.id}`, Date.now(), 'EX', 60 * 5, 'NX');
|
||||||
|
if (lock === null) {
|
||||||
|
this.logger.warn(`Delete account is already in progress for ${user.id}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// noinspection ES6MissingAwait APIで呼び出される際にタイムアウトされないように
|
||||||
|
(async () => {
|
||||||
|
try {
|
||||||
// 物理削除する前にDelete activityを送信する
|
// 物理削除する前にDelete activityを送信する
|
||||||
await this.userSuspendService.doPostSuspend(user).catch(err => this.logger.error(err));
|
await this.userSuspendService.doPostSuspend(user).catch(err => this.logger.error(err));
|
||||||
|
|
||||||
|
// noinspection ES6MissingAwait
|
||||||
this.queueService.createDeleteAccountJob(user, {
|
this.queueService.createDeleteAccountJob(user, {
|
||||||
force: me ? await this.roleService.isModerator(me) : false,
|
force: me ? await this.roleService.isModerator(me) : false,
|
||||||
soft: soft,
|
soft: soft,
|
||||||
@ -52,6 +66,14 @@ export class DeleteAccountService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.globalEventService.publishInternalEvent('userChangeDeletedState', { id: user.id, isDeleted: true });
|
this.globalEventService.publishInternalEvent('userChangeDeletedState', { id: user.id, isDeleted: true });
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(`Failed to delete account ${user.id}, request by ${me ? me.id : 'remote'} (soft: ${soft})`, { error: err });
|
||||||
|
// すでにcallstackから離れてるので、ここでエラーをthrowしても意味がない
|
||||||
|
} finally {
|
||||||
|
// 成功・失敗に関わらずロックを解除
|
||||||
|
await this.redisClient.unlink(`account:delete:lock:${user.id}`);
|
||||||
|
}
|
||||||
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
|
@ -41,11 +41,12 @@ export class FetchInstanceMetadataService {
|
|||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@Inject(DI.redis)
|
||||||
|
private redisClient: Redis.Redis,
|
||||||
|
|
||||||
private httpRequestService: HttpRequestService,
|
private httpRequestService: HttpRequestService,
|
||||||
private loggerService: LoggerService,
|
private loggerService: LoggerService,
|
||||||
private federatedInstanceService: FederatedInstanceService,
|
private federatedInstanceService: FederatedInstanceService,
|
||||||
@Inject(DI.redis)
|
|
||||||
private redisClient: Redis.Redis,
|
|
||||||
) {
|
) {
|
||||||
this.logger = this.loggerService.getLogger('metadata', 'cyan');
|
this.logger = this.loggerService.getLogger('metadata', 'cyan');
|
||||||
}
|
}
|
||||||
|
@ -6,27 +6,34 @@
|
|||||||
import { generateKeyPair } from 'node:crypto';
|
import { generateKeyPair } from 'node:crypto';
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import bcrypt from 'bcryptjs';
|
import bcrypt from 'bcryptjs';
|
||||||
|
import * as Redis from 'ioredis';
|
||||||
import { DataSource, IsNull } from 'typeorm';
|
import { DataSource, IsNull } from 'typeorm';
|
||||||
|
import { bindThis } from '@/decorators.js';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
|
import type Logger from '@/logger.js';
|
||||||
|
import generateUserToken from '@/misc/generate-native-user-token.js';
|
||||||
import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
|
import type { UsedUsernamesRepository, UsersRepository } from '@/models/_.js';
|
||||||
import { MiUser } from '@/models/User.js';
|
import { MiUser } from '@/models/User.js';
|
||||||
import { MiUserProfile } from '@/models/UserProfile.js';
|
import { MiUserProfile } from '@/models/UserProfile.js';
|
||||||
import { IdService } from '@/core/IdService.js';
|
|
||||||
import { MiUserKeypair } from '@/models/UserKeypair.js';
|
import { MiUserKeypair } from '@/models/UserKeypair.js';
|
||||||
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
import { MiUsedUsername } from '@/models/UsedUsername.js';
|
||||||
import generateUserToken from '@/misc/generate-native-user-token.js';
|
import { IdService } from '@/core/IdService.js';
|
||||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|
||||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
|
||||||
import { bindThis } from '@/decorators.js';
|
|
||||||
import UsersChart from '@/core/chart/charts/users.js';
|
|
||||||
import { UtilityService } from '@/core/UtilityService.js';
|
|
||||||
import { MetaService } from '@/core/MetaService.js';
|
import { MetaService } from '@/core/MetaService.js';
|
||||||
|
import { UtilityService } from '@/core/UtilityService.js';
|
||||||
|
import { LoggerService } from '@/core/LoggerService.js';
|
||||||
|
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||||
|
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||||
|
import UsersChart from '@/core/chart/charts/users.js';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SignupService {
|
export class SignupService {
|
||||||
|
public logger: Logger;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(DI.db)
|
@Inject(DI.db)
|
||||||
private db: DataSource,
|
private db: DataSource,
|
||||||
|
@Inject(DI.redis)
|
||||||
|
private redisClient: Redis.Redis,
|
||||||
|
|
||||||
@Inject(DI.usersRepository)
|
@Inject(DI.usersRepository)
|
||||||
private usersRepository: UsersRepository,
|
private usersRepository: UsersRepository,
|
||||||
@ -34,13 +41,15 @@ export class SignupService {
|
|||||||
@Inject(DI.usedUsernamesRepository)
|
@Inject(DI.usedUsernamesRepository)
|
||||||
private usedUsernamesRepository: UsedUsernamesRepository,
|
private usedUsernamesRepository: UsedUsernamesRepository,
|
||||||
|
|
||||||
private utilityService: UtilityService,
|
|
||||||
private userEntityService: UserEntityService,
|
|
||||||
private idService: IdService,
|
private idService: IdService,
|
||||||
private metaService: MetaService,
|
private metaService: MetaService,
|
||||||
|
private utilityService: UtilityService,
|
||||||
|
private loggerService: LoggerService,
|
||||||
private instanceActorService: InstanceActorService,
|
private instanceActorService: InstanceActorService,
|
||||||
|
private userEntityService: UserEntityService,
|
||||||
private usersChart: UsersChart,
|
private usersChart: UsersChart,
|
||||||
) {
|
) {
|
||||||
|
this.logger = this.loggerService.getLogger('account:create');
|
||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
@ -110,6 +119,13 @@ export class SignupService {
|
|||||||
err ? rej(err) : res([publicKey, privateKey]),
|
err ? rej(err) : res([publicKey, privateKey]),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
// 5分間のロックを取得
|
||||||
|
const lock = await this.redisClient.set(`account:create:lock:${username.toLowerCase()}`, Date.now(), 'EX', 60 * 5, 'NX');
|
||||||
|
if (lock === null) {
|
||||||
|
throw new Error('ALREADY_IN_PROGRESS');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
let account!: MiUser;
|
let account!: MiUser;
|
||||||
|
|
||||||
// Start transaction
|
// Start transaction
|
||||||
@ -151,6 +167,13 @@ export class SignupService {
|
|||||||
this.usersChart.update(account, true);
|
this.usersChart.update(account, true);
|
||||||
|
|
||||||
return { account, secret };
|
return { account, secret };
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(`Failed to create account ${username}`, { error: err });
|
||||||
|
throw err;
|
||||||
|
} finally {
|
||||||
|
// 成功・失敗に関わらずロックを解除
|
||||||
|
await this.redisClient.unlink(`account:create:lock:${username.toLowerCase()}`);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user