Prevent username reusing
This commit is contained in:
		@@ -1,6 +1,11 @@
 | 
				
			|||||||
ChangeLog
 | 
					ChangeLog
 | 
				
			||||||
=========
 | 
					=========
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					unreleased
 | 
				
			||||||
 | 
					--------------------
 | 
				
			||||||
 | 
					### 🐛Fixes
 | 
				
			||||||
 | 
					* すでに使われたことのあるユーザー名を再度使えないように
 | 
				
			||||||
 | 
					
 | 
				
			||||||
11.26.1 (2019/07/21)
 | 
					11.26.1 (2019/07/21)
 | 
				
			||||||
--------------------
 | 
					--------------------
 | 
				
			||||||
### 🐛Fixes
 | 
					### 🐛Fixes
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								migration/1563757595828-UsedUsername.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								migration/1563757595828-UsedUsername.ts
									
									
									
									
									
										Normal 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"`);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								src/models/entities/used-username.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/models/entities/used-username.ts
									
									
									
									
									
										Normal 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;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
	};
 | 
						};
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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);
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user