feat: アカウント作成にメールアドレス必須にするオプション (#7856)
* feat: アカウント作成にメールアドレス必須にするオプション
* ui
* fix bug
* fix bug
* fix bug
* 🎨
This commit is contained in:
parent
e568c3888f
commit
b875cc9949
22 changed files with 356 additions and 37 deletions
|
@ -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();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -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?.();
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
|
|
50
src/client/pages/signup-complete.vue
Normal file
50
src/client/pages/signup-complete.vue
Normal file
|
@ -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>
|
|
@ -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') },
|
||||
|
|
|
@ -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
|
||||
];
|
||||
|
||||
|
|
|
@ -148,6 +148,11 @@ export class Meta {
|
|||
@JoinColumn()
|
||||
public proxyAccount: User | null;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public emailRequiredForSignup: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
|
|
32
src/models/entities/user-pending.ts
Normal file
32
src/models/entities/user-pending.ts
Normal file
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
37
src/server/api/endpoints/email-address/available.ts
Normal file
37
src/server/api/endpoints/email-address/available.ts
Normal file
|
@ -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
|
||||
};
|
||||
});
|
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
|
|
35
src/server/api/private/signup-pending.ts
Normal file
35
src/server/api/private/signup-pending.ts
Normal file
|
@ -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);
|
||||
}
|
||||
};
|
|
@ -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);
|
||||
|
||||
const res = await Users.pack(account, account, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
// Generate hash of password
|
||||
const salt = await bcrypt.genSalt(8);
|
||||
const hash = await bcrypt.hash(password, salt);
|
||||
|
||||
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}`;
|
||||
|
||||
ctx.body = res;
|
||||
} catch (e) {
|
||||
ctx.throw(400, e);
|
||||
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.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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue