diff options
| author | syuilo <Syuilotan@yahoo.co.jp> | 2021-10-08 13:37:02 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-10-08 13:37:02 +0900 |
| commit | b875cc994968bb334dfb9d83707e56ab3971a0d1 (patch) | |
| tree | 5655892af829ecad9f040624f8b6cd31410284f9 /src/server/api | |
| parent | update dependencies (diff) | |
| download | sharkey-b875cc994968bb334dfb9d83707e56ab3971a0d1.tar.gz sharkey-b875cc994968bb334dfb9d83707e56ab3971a0d1.tar.bz2 sharkey-b875cc994968bb334dfb9d83707e56ab3971a0d1.zip | |
feat: アカウント作成にメールアドレス必須にするオプション (#7856)
* feat: アカウント作成にメールアドレス必須にするオプション
* ui
* fix bug
* fix bug
* fix bug
* :art:
Diffstat (limited to 'src/server/api')
| -rw-r--r-- | src/server/api/common/signup.ts | 26 | ||||
| -rw-r--r-- | src/server/api/endpoints/admin/accounts/create.ts | 5 | ||||
| -rw-r--r-- | src/server/api/endpoints/admin/update-meta.ts | 8 | ||||
| -rw-r--r-- | src/server/api/endpoints/email-address/available.ts | 37 | ||||
| -rw-r--r-- | src/server/api/endpoints/meta.ts | 6 | ||||
| -rw-r--r-- | src/server/api/index.ts | 2 | ||||
| -rw-r--r-- | src/server/api/private/signup-pending.ts | 35 | ||||
| -rw-r--r-- | src/server/api/private/signup.ts | 62 |
8 files changed, 161 insertions, 20 deletions
diff --git a/src/server/api/common/signup.ts b/src/server/api/common/signup.ts index eb3aa09c8c..2ba0d8e479 100644 --- a/src/server/api/common/signup.ts +++ b/src/server/api/common/signup.ts @@ -11,20 +11,30 @@ import { UserKeypair } from '@/models/entities/user-keypair'; import { usersChart } from '@/services/chart/index'; import { UsedUsername } from '@/models/entities/used-username'; -export async function signup(username: User['username'], password: UserProfile['password'], host: string | null = null) { +export async function 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 (!Users.validateLocalUsername.ok(username)) { throw new Error('INVALID_USERNAME'); } - // Validate password - if (!Users.validatePassword.ok(password)) { - throw new Error('INVALID_PASSWORD'); - } + if (password != null && passwordHash == null) { + // Validate password + if (!Users.validatePassword.ok(password)) { + throw new Error('INVALID_PASSWORD'); + } - // Generate hash of password - const salt = await bcrypt.genSalt(8); - const hash = await bcrypt.hash(password, salt); + // Generate hash of password + const salt = await bcrypt.genSalt(8); + hash = await bcrypt.hash(password, salt); + } // Generate secret const secret = generateUserToken(); diff --git a/src/server/api/endpoints/admin/accounts/create.ts b/src/server/api/endpoints/admin/accounts/create.ts index 9691b9c7e3..fa15e84f77 100644 --- a/src/server/api/endpoints/admin/accounts/create.ts +++ b/src/server/api/endpoints/admin/accounts/create.ts @@ -35,7 +35,10 @@ export default define(meta, async (ps, _me) => { })) === 0; if (!noUsers && !me?.isAdmin) throw new Error('access denied'); - const { account, secret } = await signup(ps.username, ps.password); + const { account, secret } = await signup({ + username: ps.username, + password: ps.password, + }); const res = await Users.pack(account, account, { detail: true, diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 46f30fef7d..55447098dc 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -93,6 +93,10 @@ export const meta = { validator: $.optional.bool, }, + emailRequiredForSignup: { + validator: $.optional.bool, + }, + enableHcaptcha: { validator: $.optional.bool, }, @@ -374,6 +378,10 @@ export default define(meta, async (ps, me) => { set.proxyRemoteFiles = ps.proxyRemoteFiles; } + if (ps.emailRequiredForSignup !== undefined) { + set.emailRequiredForSignup = ps.emailRequiredForSignup; + } + if (ps.enableHcaptcha !== undefined) { set.enableHcaptcha = ps.enableHcaptcha; } diff --git a/src/server/api/endpoints/email-address/available.ts b/src/server/api/endpoints/email-address/available.ts new file mode 100644 index 0000000000..65fe6f9178 --- /dev/null +++ b/src/server/api/endpoints/email-address/available.ts @@ -0,0 +1,37 @@ +import $ from 'cafy'; +import define from '../../define'; +import { UserProfiles } from '@/models/index'; + +export const meta = { + tags: ['users'], + + requireCredential: false as const, + + params: { + emailAddress: { + validator: $.str + } + }, + + res: { + type: 'object' as const, + optional: false as const, nullable: false as const, + properties: { + available: { + type: 'boolean' as const, + optional: false as const, nullable: false as const, + } + } + } +}; + +export default define(meta, async (ps) => { + const exist = await UserProfiles.count({ + emailVerified: true, + email: ps.emailAddress, + }); + + return { + available: exist === 0 + }; +}); diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index 3f422dff07..ce21556243 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -104,6 +104,10 @@ export const meta = { type: 'boolean' as const, optional: false as const, nullable: false as const }, + emailRequiredForSignup: { + type: 'boolean' as const, + optional: false as const, nullable: false as const + }, enableHcaptcha: { type: 'boolean' as const, optional: false as const, nullable: false as const @@ -488,6 +492,7 @@ export default define(meta, async (ps, me) => { disableGlobalTimeline: instance.disableGlobalTimeline, driveCapacityPerLocalUserMb: instance.localDriveCapacityMb, driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb, + emailRequiredForSignup: instance.emailRequiredForSignup, enableHcaptcha: instance.enableHcaptcha, hcaptchaSiteKey: instance.hcaptchaSiteKey, enableRecaptcha: instance.enableRecaptcha, @@ -537,6 +542,7 @@ export default define(meta, async (ps, me) => { registration: !instance.disableRegistration, localTimeLine: !instance.disableLocalTimeline, globalTimeLine: !instance.disableGlobalTimeline, + emailRequiredForSignup: instance.emailRequiredForSignup, elasticsearch: config.elasticsearch ? true : false, hcaptcha: instance.enableHcaptcha, recaptcha: instance.enableRecaptcha, diff --git a/src/server/api/index.ts b/src/server/api/index.ts index db35fdf9e0..82579075eb 100644 --- a/src/server/api/index.ts +++ b/src/server/api/index.ts @@ -12,6 +12,7 @@ import endpoints from './endpoints'; import handler from './api-handler'; import signup from './private/signup'; import signin from './private/signin'; +import signupPending from './private/signup-pending'; import discord from './service/discord'; import github from './service/github'; import twitter from './service/twitter'; @@ -65,6 +66,7 @@ for (const endpoint of endpoints) { router.post('/signup', signup); router.post('/signin', signin); +router.post('/signup-pending', signupPending); router.use(discord.routes()); router.use(github.routes()); diff --git a/src/server/api/private/signup-pending.ts b/src/server/api/private/signup-pending.ts new file mode 100644 index 0000000000..c0638a1cda --- /dev/null +++ b/src/server/api/private/signup-pending.ts @@ -0,0 +1,35 @@ +import * as Koa from 'koa'; +import { Users, UserPendings, UserProfiles } from '@/models/index'; +import { signup } from '../common/signup'; +import signin from '../common/signin'; + +export default async (ctx: Koa.Context) => { + const body = ctx.request.body; + + const code = body['code']; + + try { + const pendingUser = await UserPendings.findOneOrFail({ code }); + + const { account, secret } = await signup({ + username: pendingUser.username, + passwordHash: pendingUser.password, + }); + + UserPendings.delete({ + id: pendingUser.id, + }); + + const profile = await UserProfiles.findOneOrFail(account.id); + + await UserProfiles.update({ userId: profile.userId }, { + email: pendingUser.email, + emailVerified: true, + emailVerifyCode: null, + }); + + signin(ctx, account); + } catch (e) { + ctx.throw(400, e); + } +}; diff --git a/src/server/api/private/signup.ts b/src/server/api/private/signup.ts index ef61767f65..93caaea935 100644 --- a/src/server/api/private/signup.ts +++ b/src/server/api/private/signup.ts @@ -1,8 +1,13 @@ import * as Koa from 'koa'; +import rndstr from 'rndstr'; +import * as bcrypt from 'bcryptjs'; import { fetchMeta } from '@/misc/fetch-meta'; import { verifyHcaptcha, verifyRecaptcha } from '@/misc/captcha'; -import { Users, RegistrationTickets } from '@/models/index'; +import { Users, RegistrationTickets, UserPendings } from '@/models/index'; import { signup } from '../common/signup'; +import config from '@/config'; +import { sendEmail } from '@/services/send-email'; +import { genId } from '@/misc/gen-id'; export default async (ctx: Koa.Context) => { const body = ctx.request.body; @@ -29,8 +34,16 @@ export default async (ctx: Koa.Context) => { const password = body['password']; const host: string | null = process.env.NODE_ENV === 'test' ? (body['host'] || null) : null; const invitationCode = body['invitationCode']; + const emailAddress = body['emailAddress']; - if (instance && instance.disableRegistration) { + if (instance.emailRequiredForSignup) { + if (emailAddress == null || typeof emailAddress != 'string') { + ctx.status = 400; + return; + } + } + + if (instance.disableRegistration) { if (invitationCode == null || typeof invitationCode != 'string') { ctx.status = 400; return; @@ -48,18 +61,45 @@ export default async (ctx: Koa.Context) => { RegistrationTickets.delete(ticket.id); } - try { - const { account, secret } = await signup(username, password, host); + if (instance.emailRequiredForSignup) { + const code = rndstr('a-z0-9', 16); + + // Generate hash of password + const salt = await bcrypt.genSalt(8); + const hash = await bcrypt.hash(password, salt); - const res = await Users.pack(account, account, { - detail: true, - includeSecrets: true + await UserPendings.insert({ + id: genId(), + createdAt: new Date(), + code, + email: emailAddress, + username: username, + password: hash, }); - (res as any).token = secret; + const link = `${config.url}/signup-complete/${code}`; + + sendEmail(emailAddress, 'Signup', + `To complete signup, please click this link:<br><a href="${link}">${link}</a>`, + `To complete signup, please click this link: ${link}`); - ctx.body = res; - } catch (e) { - ctx.throw(400, e); + ctx.status = 204; + } else { + try { + const { account, secret } = await signup({ + username, password, host + }); + + const res = await Users.pack(account, account, { + detail: true, + includeSecrets: true + }); + + (res as any).token = secret; + + ctx.body = res; + } catch (e) { + ctx.throw(400, e); + } } }; |