Prevent username reusing

This commit is contained in:
syuilo 2019-07-22 10:15:00 +09:00
parent 3432d6e615
commit 85008303f5
7 changed files with 59 additions and 3 deletions

View file

@ -1,6 +1,11 @@
ChangeLog ChangeLog
========= =========
unreleased
--------------------
### 🐛Fixes
* すでに使われたことのあるユーザー名を再度使えないように
11.26.1 (2019/07/21) 11.26.1 (2019/07/21)
-------------------- --------------------
### 🐛Fixes ### 🐛Fixes

View file

@ -0,0 +1,13 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class UsedUsername1563757595828 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`CREATE TABLE "used_username" ("username" character varying(128) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_78fd79d2d24c6ac2f4cc9a31a5d" PRIMARY KEY ("username"))`);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`DROP TABLE "used_username"`);
}
}

View file

@ -48,6 +48,7 @@ import { AttestationChallenge } from '../models/entities/attestation-challenge';
import { Page } from '../models/entities/page'; import { Page } from '../models/entities/page';
import { PageLike } from '../models/entities/page-like'; import { PageLike } from '../models/entities/page-like';
import { ModerationLog } from '../models/entities/moderation-log'; import { ModerationLog } from '../models/entities/moderation-log';
import { UsedUsername } from '../models/entities/used-username';
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false); const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
@ -100,6 +101,7 @@ export const entities = [
UserGroupInvite, UserGroupInvite,
UserNotePining, UserNotePining,
UserSecurityKey, UserSecurityKey,
UsedUsername,
AttestationChallenge, AttestationChallenge,
Following, Following,
FollowRequest, FollowRequest,

View file

@ -0,0 +1,20 @@
import { PrimaryColumn, Entity, Column } from 'typeorm';
@Entity()
export class UsedUsername {
@PrimaryColumn('varchar', {
length: 128,
})
public username: string;
@Column('timestamp with time zone')
public createdAt: Date;
constructor(data: Partial<UsedUsername>) {
if (data == null) return;
for (const [k, v] of Object.entries(data)) {
(this as any)[k] = v;
}
}
}

View file

@ -43,6 +43,7 @@ import { HashtagRepository } from './repositories/hashtag';
import { PageRepository } from './repositories/page'; import { PageRepository } from './repositories/page';
import { PageLikeRepository } from './repositories/page-like'; import { PageLikeRepository } from './repositories/page-like';
import { ModerationLogRepository } from './repositories/moderation-logs'; import { ModerationLogRepository } from './repositories/moderation-logs';
import { UsedUsername } from './entities/used-username';
export const Apps = getCustomRepository(AppRepository); export const Apps = getCustomRepository(AppRepository);
export const Notes = getCustomRepository(NoteRepository); export const Notes = getCustomRepository(NoteRepository);
@ -64,6 +65,7 @@ export const UserGroups = getCustomRepository(UserGroupRepository);
export const UserGroupJoinings = getRepository(UserGroupJoining); export const UserGroupJoinings = getRepository(UserGroupJoining);
export const UserGroupInvites = getCustomRepository(UserGroupInviteRepository); export const UserGroupInvites = getCustomRepository(UserGroupInviteRepository);
export const UserNotePinings = getRepository(UserNotePining); export const UserNotePinings = getRepository(UserNotePining);
export const UsedUsernames = getRepository(UsedUsername);
export const Followings = getCustomRepository(FollowingRepository); export const Followings = getCustomRepository(FollowingRepository);
export const FollowRequests = getCustomRepository(FollowRequestRepository); export const FollowRequests = getCustomRepository(FollowRequestRepository);
export const Instances = getRepository(Instance); export const Instances = getRepository(Instance);

View file

@ -1,6 +1,6 @@
import $ from 'cafy'; import $ from 'cafy';
import define from '../../define'; import define from '../../define';
import { Users } from '../../../../models'; import { Users, UsedUsernames } from '../../../../models';
export const meta = { export const meta = {
tags: ['users'], tags: ['users'],
@ -21,7 +21,9 @@ export default define(meta, async (ps) => {
usernameLower: ps.username.toLowerCase() usernameLower: ps.username.toLowerCase()
}); });
const exist2 = await UsedUsernames.count({ username: ps.username.toLowerCase() });
return { return {
available: exist === 0 available: exist === 0 && exist2 === 0
}; };
}); });

View file

@ -5,7 +5,7 @@ import generateUserToken from '../common/generate-native-user-token';
import config from '../../../config'; import config from '../../../config';
import { fetchMeta } from '../../../misc/fetch-meta'; import { fetchMeta } from '../../../misc/fetch-meta';
import * as recaptcha from 'recaptcha-promise'; import * as recaptcha from 'recaptcha-promise';
import { Users, Signins, RegistrationTickets } from '../../../models'; import { Users, Signins, RegistrationTickets, UsedUsernames } from '../../../models';
import { genId } from '../../../misc/gen-id'; import { genId } from '../../../misc/gen-id';
import { usersChart } from '../../../services/chart'; import { usersChart } from '../../../services/chart';
import { User } from '../../../models/entities/user'; import { User } from '../../../models/entities/user';
@ -13,6 +13,7 @@ import { UserKeypair } from '../../../models/entities/user-keypair';
import { toPunyNullable } from '../../../misc/convert-host'; import { toPunyNullable } from '../../../misc/convert-host';
import { UserProfile } from '../../../models/entities/user-profile'; import { UserProfile } from '../../../models/entities/user-profile';
import { getConnection } from 'typeorm'; import { getConnection } from 'typeorm';
import { UsedUsername } from '../../../models/entities/used-username';
export default async (ctx: Koa.BaseContext) => { export default async (ctx: Koa.BaseContext) => {
const body = ctx.request.body as any; const body = ctx.request.body as any;
@ -78,11 +79,18 @@ export default async (ctx: Koa.BaseContext) => {
// Generate secret // Generate secret
const secret = generateUserToken(); const secret = generateUserToken();
// Check username duplication
if (await Users.findOne({ usernameLower: username.toLowerCase(), host: null })) { if (await Users.findOne({ usernameLower: username.toLowerCase(), host: null })) {
ctx.status = 400; ctx.status = 400;
return; return;
} }
// Check deleted username duplication
if (await UsedUsernames.findOne({ username: username.toLowerCase() })) {
ctx.status = 400;
return;
}
const keyPair = await new Promise<string[]>((s, j) => const keyPair = await new Promise<string[]>((s, j) =>
generateKeyPair('rsa', { generateKeyPair('rsa', {
modulusLength: 4096, modulusLength: 4096,
@ -133,6 +141,10 @@ export default async (ctx: Koa.BaseContext) => {
autoWatch: false, autoWatch: false,
password: hash, password: hash,
})); }));
await transactionalEntityManager.save(new UsedUsername({
username: username.toLowerCase(),
}));
}); });
usersChart.update(account, true); usersChart.update(account, true);