summaryrefslogtreecommitdiff
path: root/src/server/api
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2021-10-08 13:37:02 +0900
committerGitHub <noreply@github.com>2021-10-08 13:37:02 +0900
commitb875cc994968bb334dfb9d83707e56ab3971a0d1 (patch)
tree5655892af829ecad9f040624f8b6cd31410284f9 /src/server/api
parentupdate dependencies (diff)
downloadsharkey-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.ts26
-rw-r--r--src/server/api/endpoints/admin/accounts/create.ts5
-rw-r--r--src/server/api/endpoints/admin/update-meta.ts8
-rw-r--r--src/server/api/endpoints/email-address/available.ts37
-rw-r--r--src/server/api/endpoints/meta.ts6
-rw-r--r--src/server/api/index.ts2
-rw-r--r--src/server/api/private/signup-pending.ts35
-rw-r--r--src/server/api/private/signup.ts62
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);
+ }
}
};