diff --git a/CHANGELOG.md b/CHANGELOG.md index 00f20d14ce..de8db29bb4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ ChangeLog ========= +unreleased +-------------------- +### 🐛Fixes +* すでに使われたことのあるユーザー名を再度使えないように + 11.26.1 (2019/07/21) -------------------- ### 🐛Fixes diff --git a/migration/1563757595828-UsedUsername.ts b/migration/1563757595828-UsedUsername.ts new file mode 100644 index 0000000000..a076fcc0c5 --- /dev/null +++ b/migration/1563757595828-UsedUsername.ts @@ -0,0 +1,13 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class UsedUsername1563757595828 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + 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 { + await queryRunner.query(`DROP TABLE "used_username"`); + } + +} diff --git a/src/db/postgre.ts b/src/db/postgre.ts index 16cfbd2b2f..00476b8774 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -48,6 +48,7 @@ import { AttestationChallenge } from '../models/entities/attestation-challenge'; import { Page } from '../models/entities/page'; import { PageLike } from '../models/entities/page-like'; import { ModerationLog } from '../models/entities/moderation-log'; +import { UsedUsername } from '../models/entities/used-username'; const sqlLogger = dbLogger.createSubLogger('sql', 'white', false); @@ -100,6 +101,7 @@ export const entities = [ UserGroupInvite, UserNotePining, UserSecurityKey, + UsedUsername, AttestationChallenge, Following, FollowRequest, diff --git a/src/models/entities/used-username.ts b/src/models/entities/used-username.ts new file mode 100644 index 0000000000..eb90bef6ca --- /dev/null +++ b/src/models/entities/used-username.ts @@ -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) { + if (data == null) return; + + for (const [k, v] of Object.entries(data)) { + (this as any)[k] = v; + } + } +} diff --git a/src/models/index.ts b/src/models/index.ts index 388bdc8f6f..fc40ebfb23 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -43,6 +43,7 @@ import { HashtagRepository } from './repositories/hashtag'; import { PageRepository } from './repositories/page'; import { PageLikeRepository } from './repositories/page-like'; import { ModerationLogRepository } from './repositories/moderation-logs'; +import { UsedUsername } from './entities/used-username'; export const Apps = getCustomRepository(AppRepository); export const Notes = getCustomRepository(NoteRepository); @@ -64,6 +65,7 @@ export const UserGroups = getCustomRepository(UserGroupRepository); export const UserGroupJoinings = getRepository(UserGroupJoining); export const UserGroupInvites = getCustomRepository(UserGroupInviteRepository); export const UserNotePinings = getRepository(UserNotePining); +export const UsedUsernames = getRepository(UsedUsername); export const Followings = getCustomRepository(FollowingRepository); export const FollowRequests = getCustomRepository(FollowRequestRepository); export const Instances = getRepository(Instance); diff --git a/src/server/api/endpoints/username/available.ts b/src/server/api/endpoints/username/available.ts index 724bb3a0c3..f393d6ed0d 100644 --- a/src/server/api/endpoints/username/available.ts +++ b/src/server/api/endpoints/username/available.ts @@ -1,6 +1,6 @@ import $ from 'cafy'; import define from '../../define'; -import { Users } from '../../../../models'; +import { Users, UsedUsernames } from '../../../../models'; export const meta = { tags: ['users'], @@ -21,7 +21,9 @@ export default define(meta, async (ps) => { usernameLower: ps.username.toLowerCase() }); + const exist2 = await UsedUsernames.count({ username: ps.username.toLowerCase() }); + return { - available: exist === 0 + available: exist === 0 && exist2 === 0 }; }); diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index 026fe7485b..c1f06fd339 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -5,7 +5,7 @@ import generateUserToken from '../common/generate-native-user-token'; import config from '../../../config'; import { fetchMeta } from '../../../misc/fetch-meta'; 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 { usersChart } from '../../../services/chart'; import { User } from '../../../models/entities/user'; @@ -13,6 +13,7 @@ import { UserKeypair } from '../../../models/entities/user-keypair'; import { toPunyNullable } from '../../../misc/convert-host'; import { UserProfile } from '../../../models/entities/user-profile'; import { getConnection } from 'typeorm'; +import { UsedUsername } from '../../../models/entities/used-username'; export default async (ctx: Koa.BaseContext) => { const body = ctx.request.body as any; @@ -78,11 +79,18 @@ export default async (ctx: Koa.BaseContext) => { // Generate secret const secret = generateUserToken(); + // Check username duplication if (await Users.findOne({ usernameLower: username.toLowerCase(), host: null })) { ctx.status = 400; return; } + // Check deleted username duplication + if (await UsedUsernames.findOne({ username: username.toLowerCase() })) { + ctx.status = 400; + return; + } + const keyPair = await new Promise((s, j) => generateKeyPair('rsa', { modulusLength: 4096, @@ -133,6 +141,10 @@ export default async (ctx: Koa.BaseContext) => { autoWatch: false, password: hash, })); + + await transactionalEntityManager.save(new UsedUsername({ + username: username.toLowerCase(), + })); }); usersChart.update(account, true);