Prevent username reusing
This commit is contained in:
		| @@ -1,6 +1,11 @@ | ||||
| ChangeLog | ||||
| ========= | ||||
|  | ||||
| unreleased | ||||
| -------------------- | ||||
| ### 🐛Fixes | ||||
| * すでに使われたことのあるユーザー名を再度使えないように | ||||
|  | ||||
| 11.26.1 (2019/07/21) | ||||
| -------------------- | ||||
| ### 🐛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 { 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, | ||||
|   | ||||
							
								
								
									
										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 { 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); | ||||
|   | ||||
| @@ -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 | ||||
| 	}; | ||||
| }); | ||||
|   | ||||
| @@ -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<string[]>((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); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 syuilo
					syuilo