From b75184ec8e3436200bacdcd832e3324702553d20 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 18 Sep 2022 03:27:08 +0900 Subject: なんかもうめっちゃ変えた MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/core/SignupService.ts | 141 +++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 packages/backend/src/core/SignupService.ts (limited to 'packages/backend/src/core/SignupService.ts') diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts new file mode 100644 index 0000000000..a876668b94 --- /dev/null +++ b/packages/backend/src/core/SignupService.ts @@ -0,0 +1,141 @@ +import { generateKeyPair } from 'node:crypto'; +import { Inject, Injectable } from '@nestjs/common'; +import bcrypt from 'bcryptjs'; +import { DataSource, IsNull } from 'typeorm'; +import { DI } from '@/di-symbols.js'; +import { UsedUsernamesRepository } from '@/models/index.js'; +import { Config } from '@/config.js'; +import { User } from '@/models/entities/User.js'; +import { UserProfile } from '@/models/entities/UserProfile.js'; +import { IdService } from '@/core/IdService.js'; +import { UserKeypair } from '@/models/entities/UserKeypair.js'; +import { UsedUsername } from '@/models/entities/UsedUsername.js'; +import generateUserToken from '@/misc/generate-native-user-token.js'; +import UsersChart from './chart/charts/users.js'; +import { UserEntityService } from './entities/UserEntityService.js'; +import { UtilityService } from './UtilityService.js'; + +@Injectable() +export class SignupService { + constructor( + @Inject(DI.db) + private db: DataSource, + + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + + @Inject(DI.usedUsernamesRepository) + private usedUsernamesRepository: UsedUsernamesRepository, + + private utilityService: UtilityService, + private userEntityService: UserEntityService, + private idService: IdService, + private usersChart: UsersChart, + ) { + } + + public async signup(opts: { + username: User['username']; + password?: string | null; + passwordHash?: UserProfile['password'] | null; + host?: string | null; + }) { + const { username, password, passwordHash, host } = opts; + let hash = passwordHash; + + // Validate username + if (!this.userEntityService.validateLocalUsername(username)) { + throw new Error('INVALID_USERNAME'); + } + + if (password != null && passwordHash == null) { + // Validate password + if (!this.userEntityService.validatePassword(password)) { + throw new Error('INVALID_PASSWORD'); + } + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + hash = await bcrypt.hash(password, salt); + } + + // Generate secret + const secret = generateUserToken(); + + // Check username duplication + if (await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) { + throw new Error('DUPLICATED_USERNAME'); + } + + // Check deleted username duplication + if (await this.usedUsernamesRepository.findOneBy({ username: username.toLowerCase() })) { + throw new Error('USED_USERNAME'); + } + + const keyPair = await new Promise((res, rej) => + generateKeyPair('rsa', { + modulusLength: 4096, + publicKeyEncoding: { + type: 'spki', + format: 'pem', + }, + privateKeyEncoding: { + type: 'pkcs8', + format: 'pem', + cipher: undefined, + passphrase: undefined, + }, + } as any, (err, publicKey, privateKey) => + err ? rej(err) : res([publicKey, privateKey]), + )); + + let account!: User; + + // Start transaction + await this.db.transaction(async transactionalEntityManager => { + const exist = await transactionalEntityManager.findOneBy(User, { + usernameLower: username.toLowerCase(), + host: IsNull(), + }); + + if (exist) throw new Error(' the username is already used'); + + account = await transactionalEntityManager.save(new User({ + id: this.idService.genId(), + createdAt: new Date(), + username: username, + usernameLower: username.toLowerCase(), + host: this.utilityService.toPunyNullable(host), + token: secret, + isAdmin: (await this.usersRepository.countBy({ + host: IsNull(), + })) === 0, + })); + + await transactionalEntityManager.save(new UserKeypair({ + publicKey: keyPair[0], + privateKey: keyPair[1], + userId: account.id, + })); + + await transactionalEntityManager.save(new UserProfile({ + userId: account.id, + autoAcceptFollowed: true, + password: hash, + })); + + await transactionalEntityManager.save(new UsedUsername({ + createdAt: new Date(), + username: username.toLowerCase(), + })); + }); + + this.usersChart.update(account, true); + + return { account, secret }; + } +} + -- cgit v1.2.3-freya