summaryrefslogtreecommitdiff
path: root/src
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
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')
-rw-r--r--src/client/components/signup-dialog.vue6
-rw-r--r--src/client/components/signup.vue71
-rw-r--r--src/client/pages/instance/security.vue5
-rw-r--r--src/client/pages/signup-complete.vue50
-rw-r--r--src/client/router.ts1
-rw-r--r--src/db/postgre.ts2
-rw-r--r--src/models/entities/meta.ts5
-rw-r--r--src/models/entities/user-pending.ts32
-rw-r--r--src/models/index.ts2
-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
-rw-r--r--src/server/nodeinfo.ts1
18 files changed, 319 insertions, 37 deletions
diff --git a/src/client/components/signup-dialog.vue b/src/client/components/signup-dialog.vue
index df1a525055..9741e8c73b 100644
--- a/src/client/components/signup-dialog.vue
+++ b/src/client/components/signup-dialog.vue
@@ -9,7 +9,7 @@
<div class="_monolithic_">
<div class="_section">
- <XSignup :auto-set="autoSet" @signup="onSignup"/>
+ <XSignup :auto-set="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending"/>
</div>
</div>
</XModalWindow>
@@ -40,6 +40,10 @@ export default defineComponent({
onSignup(res) {
this.$emit('done', res);
this.$refs.dialog.close();
+ },
+
+ onSignupEmailPending() {
+ this.$refs.dialog.close();
}
}
});
diff --git a/src/client/components/signup.vue b/src/client/components/signup.vue
index f555c1df6d..b420bca5a3 100644
--- a/src/client/components/signup.vue
+++ b/src/client/components/signup.vue
@@ -10,13 +10,23 @@
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
<template #caption>
- <span v-if="usernameState == 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span>
- <span v-if="usernameState == 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span>
- <span v-if="usernameState == 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span>
- <span v-if="usernameState == 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span>
- <span v-if="usernameState == 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span>
- <span v-if="usernameState == 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span>
- <span v-if="usernameState == 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span>
+ <span v-if="usernameState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span>
+ <span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span>
+ <span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span>
+ <span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span>
+ <span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.usernameInvalidFormat }}</span>
+ <span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooShort }}</span>
+ <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.tooLong }}</span>
+ </template>
+ </MkInput>
+ <MkInput v-if="meta.emailRequiredForSignup" class="_formBlock" v-model="email" type="email" :autocomplete="Math.random()" spellcheck="false" required @update:modelValue="onChangeEmail" data-cy-signup-email>
+ <template #label>{{ $ts.emailAddress }} <div class="_button _help" v-tooltip:dialog="$ts._signup.emailAddressInfo"><i class="far fa-question-circle"></i></div></template>
+ <template #prefix><i class="fas fa-envelope"></i></template>
+ <template #caption>
+ <span v-if="emailState === 'wait'" style="color:#999"><i class="fas fa-spinner fa-pulse fa-fw"></i> {{ $ts.checking }}</span>
+ <span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="fas fa-check fa-fw"></i> {{ $ts.available }}</span>
+ <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.unavailable }}</span>
+ <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="fas fa-exclamation-triangle fa-fw"></i> {{ $ts.error }}</span>
</template>
</MkInput>
<MkInput class="_formBlock" v-model="password" type="password" :autocomplete="Math.random()" required @update:modelValue="onChangePassword" data-cy-signup-password>
@@ -87,8 +97,10 @@ export default defineComponent({
password: '',
retypedPassword: '',
invitationCode: '',
+ email: '',
url,
usernameState: null,
+ emailState: null,
passwordStrength: '',
passwordRetypeState: null,
submitting: false,
@@ -148,6 +160,23 @@ export default defineComponent({
});
},
+ onChangeEmail() {
+ if (this.email == '') {
+ this.emailState = null;
+ return;
+ }
+
+ this.emailState = 'wait';
+
+ os.api('email-address/available', {
+ emailAddress: this.email
+ }).then(result => {
+ this.emailState = result.available ? 'ok' : 'unavailable';
+ }).catch(err => {
+ this.emailState = 'error';
+ });
+ },
+
onChangePassword() {
if (this.password == '') {
this.passwordStrength = '';
@@ -174,20 +203,30 @@ export default defineComponent({
os.api('signup', {
username: this.username,
password: this.password,
+ emailAddress: this.email,
invitationCode: this.invitationCode,
'hcaptcha-response': this.hCaptchaResponse,
'g-recaptcha-response': this.reCaptchaResponse,
}).then(() => {
- return os.api('signin', {
- username: this.username,
- password: this.password
- }).then(res => {
- this.$emit('signup', res);
+ if (this.meta.emailRequiredForSignup) {
+ os.dialog({
+ type: 'success',
+ title: this.$ts._signup.almostThere,
+ text: this.$t('_signup.emailSent', { email: this.email }),
+ });
+ this.$emit('signupEmailPending');
+ } else {
+ os.api('signin', {
+ username: this.username,
+ password: this.password
+ }).then(res => {
+ this.$emit('signup', res);
- if (this.autoSet) {
- return login(res.i);
- }
- });
+ if (this.autoSet) {
+ login(res.i);
+ }
+ });
+ }
}).catch(() => {
this.submitting = false;
this.$refs.hcaptcha?.reset?.();
diff --git a/src/client/pages/instance/security.vue b/src/client/pages/instance/security.vue
index 53f923643a..2b525261ae 100644
--- a/src/client/pages/instance/security.vue
+++ b/src/client/pages/instance/security.vue
@@ -10,6 +10,8 @@
<FormSwitch v-model="enableRegistration">{{ $ts.enableRegistration }}</FormSwitch>
+ <FormSwitch v-model="emailRequiredForSignup">{{ $ts.emailRequiredForSignup }}</FormSwitch>
+
<FormButton @click="save" primary><i class="fas fa-save"></i> {{ $ts.save }}</FormButton>
</FormSuspense>
</FormBase>
@@ -50,6 +52,7 @@ export default defineComponent({
enableHcaptcha: false,
enableRecaptcha: false,
enableRegistration: false,
+ emailRequiredForSignup: false,
}
},
@@ -63,11 +66,13 @@ export default defineComponent({
this.enableHcaptcha = meta.enableHcaptcha;
this.enableRecaptcha = meta.enableRecaptcha;
this.enableRegistration = !meta.disableRegistration;
+ this.emailRequiredForSignup = meta.emailRequiredForSignup;
},
save() {
os.apiWithDialog('admin/update-meta', {
disableRegistration: !this.enableRegistration,
+ emailRequiredForSignup: this.emailRequiredForSignup,
}).then(() => {
fetchInstance();
});
diff --git a/src/client/pages/signup-complete.vue b/src/client/pages/signup-complete.vue
new file mode 100644
index 0000000000..dada92031a
--- /dev/null
+++ b/src/client/pages/signup-complete.vue
@@ -0,0 +1,50 @@
+<template>
+<div>
+ {{ $ts.processing }}
+</div>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+import * as os from '@client/os';
+import * as symbols from '@client/symbols';
+import { login } from '@client/account';
+
+export default defineComponent({
+ components: {
+
+ },
+
+ props: {
+ code: {
+ type: String,
+ required: true
+ }
+ },
+
+ data() {
+ return {
+ [symbols.PAGE_INFO]: {
+ title: this.$ts.signup,
+ icon: 'fas fa-user'
+ },
+ }
+ },
+
+ mounted() {
+ os.apiWithDialog('signup-pending', {
+ code: this.code,
+ }).then(res => {
+ login(res.i, '/');
+ });
+ },
+
+ methods: {
+
+ }
+});
+</script>
+
+<style lang="scss" scoped>
+
+</style>
diff --git a/src/client/router.ts b/src/client/router.ts
index 573f285c79..56dc948669 100644
--- a/src/client/router.ts
+++ b/src/client/router.ts
@@ -23,6 +23,7 @@ const defaultRoutes = [
{ path: '/@:acct/room', props: true, component: page('room/room') },
{ path: '/settings/:page(.*)?', name: 'settings', component: page('settings/index'), props: route => ({ initialPage: route.params.page || null }) },
{ path: '/reset-password/:token?', component: page('reset-password'), props: route => ({ token: route.params.token }) },
+ { path: '/signup-complete/:code', component: page('signup-complete'), props: route => ({ code: route.params.code }) },
{ path: '/announcements', component: page('announcements') },
{ path: '/about', component: page('about') },
{ path: '/about-misskey', component: page('about-misskey') },
diff --git a/src/db/postgre.ts b/src/db/postgre.ts
index c963242488..8948f22cdc 100644
--- a/src/db/postgre.ts
+++ b/src/db/postgre.ts
@@ -72,6 +72,7 @@ import { ChannelNotePining } from '@/models/entities/channel-note-pining';
import { RegistryItem } from '@/models/entities/registry-item';
import { Ad } from '@/models/entities/ad';
import { PasswordResetRequest } from '@/models/entities/password-reset-request';
+import { UserPending } from '@/models/entities/user-pending';
const sqlLogger = dbLogger.createSubLogger('sql', 'white', false);
@@ -173,6 +174,7 @@ export const entities = [
RegistryItem,
Ad,
PasswordResetRequest,
+ UserPending,
...charts as any
];
diff --git a/src/models/entities/meta.ts b/src/models/entities/meta.ts
index 6428aacdf1..9a1a87c155 100644
--- a/src/models/entities/meta.ts
+++ b/src/models/entities/meta.ts
@@ -151,6 +151,11 @@ export class Meta {
@Column('boolean', {
default: false,
})
+ public emailRequiredForSignup: boolean;
+
+ @Column('boolean', {
+ default: false,
+ })
public enableHcaptcha: boolean;
@Column('varchar', {
diff --git a/src/models/entities/user-pending.ts b/src/models/entities/user-pending.ts
new file mode 100644
index 0000000000..40482af333
--- /dev/null
+++ b/src/models/entities/user-pending.ts
@@ -0,0 +1,32 @@
+import { PrimaryColumn, Entity, Index, Column } from 'typeorm';
+import { id } from '../id';
+
+@Entity()
+export class UserPending {
+ @PrimaryColumn(id())
+ public id: string;
+
+ @Column('timestamp with time zone')
+ public createdAt: Date;
+
+ @Index({ unique: true })
+ @Column('varchar', {
+ length: 128,
+ })
+ public code: string;
+
+ @Column('varchar', {
+ length: 128,
+ })
+ public username: string;
+
+ @Column('varchar', {
+ length: 128,
+ })
+ public email: string;
+
+ @Column('varchar', {
+ length: 128,
+ })
+ public password: string;
+}
diff --git a/src/models/index.ts b/src/models/index.ts
index 9f8bd104e9..059a3d7b87 100644
--- a/src/models/index.ts
+++ b/src/models/index.ts
@@ -62,6 +62,7 @@ import { ChannelNotePining } from './entities/channel-note-pining';
import { RegistryItem } from './entities/registry-item';
import { Ad } from './entities/ad';
import { PasswordResetRequest } from './entities/password-reset-request';
+import { UserPending } from './entities/user-pending';
export const Announcements = getRepository(Announcement);
export const AnnouncementReads = getRepository(AnnouncementRead);
@@ -76,6 +77,7 @@ export const PollVotes = getRepository(PollVote);
export const Users = getCustomRepository(UserRepository);
export const UserProfiles = getRepository(UserProfile);
export const UserKeypairs = getRepository(UserKeypair);
+export const UserPendings = getRepository(UserPending);
export const AttestationChallenges = getRepository(AttestationChallenge);
export const UserSecurityKeys = getRepository(UserSecurityKey);
export const UserPublickeys = getRepository(UserPublickey);
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);
+ }
}
};
diff --git a/src/server/nodeinfo.ts b/src/server/nodeinfo.ts
index dec2615086..6a864fcc52 100644
--- a/src/server/nodeinfo.ts
+++ b/src/server/nodeinfo.ts
@@ -68,6 +68,7 @@ const nodeinfo2 = async () => {
disableRegistration: meta.disableRegistration,
disableLocalTimeline: meta.disableLocalTimeline,
disableGlobalTimeline: meta.disableGlobalTimeline,
+ emailRequiredForSignup: meta.emailRequiredForSignup,
enableHcaptcha: meta.enableHcaptcha,
enableRecaptcha: meta.enableRecaptcha,
maxNoteTextLength: meta.maxNoteTextLength,