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
	 syuilo
					syuilo