mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-12-23 21:43:09 +02:00
Implement email config
This commit is contained in:
parent
0ce64f8c33
commit
1bc109b42c
10 changed files with 280 additions and 13 deletions
|
@ -503,6 +503,10 @@ common/views/components/profile-editor.vue:
|
|||
saved: "プロフィールを保存しました"
|
||||
uploading: "アップロード中"
|
||||
upload-failed: "アップロードに失敗しました"
|
||||
email: "メール設定"
|
||||
email-address: "メールアドレス"
|
||||
email-verified: "メールアドレスが確認されました"
|
||||
email-not-verified: "メールアドレスが確認されていません。メールボックスをご確認ください。"
|
||||
|
||||
common/views/widgets/broadcast.vue:
|
||||
fetching: "確認中"
|
||||
|
@ -1123,6 +1127,15 @@ admin/views/instance.vue:
|
|||
external-user-recommendation-engine-desc: "例: https://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-misskey-api.cgi?{{host}}+{{user}}+{{limit}}+{{offset}}"
|
||||
external-user-recommendation-timeout: "タイムアウト"
|
||||
external-user-recommendation-timeout-desc: "ミリ秒単位 (例: 300000)"
|
||||
email-config: "メールサーバーの設定"
|
||||
email-config-info: "メールアドレス確認やパスワードリセットの際に使われます。"
|
||||
enable-email: "メール配信を有効にする"
|
||||
email: "メールアドレス"
|
||||
smtp-use-ssl: "SMTPサーバーはSSLを使用"
|
||||
smtp-host: "SMTPホスト"
|
||||
smtp-port: "SMTPポート"
|
||||
smtp-user: "SMTPユーザー"
|
||||
smtp-pass: "SMTPパスワード"
|
||||
|
||||
admin/views/charts.vue:
|
||||
title: "チャート"
|
||||
|
|
|
@ -64,6 +64,7 @@
|
|||
"@types/mongodb": "3.1.14",
|
||||
"@types/ms": "0.7.30",
|
||||
"@types/node": "10.12.10",
|
||||
"@types/nodemailer": "4.6.5",
|
||||
"@types/oauth": "0.9.1",
|
||||
"@types/parsimmon": "1.10.0",
|
||||
"@types/portscanner": "2.1.0",
|
||||
|
@ -166,6 +167,7 @@
|
|||
"ms": "2.1.1",
|
||||
"nan": "2.11.1",
|
||||
"nested-property": "0.0.7",
|
||||
"nodemailer": "4.7.0",
|
||||
"nprogress": "0.2.0",
|
||||
"object-assign-deep": "0.4.0",
|
||||
"on-build-webpack": "0.1.0",
|
||||
|
|
|
@ -12,11 +12,15 @@
|
|||
<section class="fit-bottom">
|
||||
<header><fa :icon="faHeadset"/> {{ $t('maintainer-config') }}</header>
|
||||
<ui-input v-model="maintainerName">{{ $t('maintainer-name') }}</ui-input>
|
||||
<ui-input v-model="maintainerEmail" type="email"><i slot="icon"><fa :icon="['far', 'envelope']"/></i>{{ $t('maintainer-email') }}</ui-input>
|
||||
<ui-input v-model="maintainerEmail" type="email"><i slot="icon"><fa :icon="farEnvelope"/></i>{{ $t('maintainer-email') }}</ui-input>
|
||||
</section>
|
||||
<section class="fit-top fit-bottom">
|
||||
<ui-input v-model="maxNoteTextLength">{{ $t('max-note-text-length') }}</ui-input>
|
||||
</section>
|
||||
<section>
|
||||
<ui-switch v-model="disableRegistration">{{ $t('disable-registration') }}</ui-switch>
|
||||
<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch>
|
||||
</section>
|
||||
<section class="fit-bottom">
|
||||
<header><fa icon="cloud"/> {{ $t('drive-config') }}</header>
|
||||
<ui-switch v-model="cacheRemoteFiles">{{ $t('cache-remote-files') }}<span slot="desc">{{ $t('cache-remote-files-desc') }}</span></ui-switch>
|
||||
|
@ -37,10 +41,18 @@
|
|||
<ui-info warn>{{ $t('proxy-account-warn') }}</ui-info>
|
||||
</section>
|
||||
<section>
|
||||
<ui-switch v-model="disableRegistration">{{ $t('disable-registration') }}</ui-switch>
|
||||
</section>
|
||||
<section>
|
||||
<ui-switch v-model="disableLocalTimeline">{{ $t('disable-local-timeline') }}</ui-switch>
|
||||
<header><fa :icon="farEnvelope"/> {{ $t('email-config') }}</header>
|
||||
<ui-switch v-model="enableEmail">{{ $t('enable-email') }}<span slot="desc">{{ $t('email-config-info') }}</span></ui-switch>
|
||||
<ui-input v-model="email" type="email" :disabled="!enableEmail">{{ $t('email') }}</ui-input>
|
||||
<ui-horizon-group inputs>
|
||||
<ui-input v-model="smtpHost" :disabled="!enableEmail">{{ $t('smtp-host') }}</ui-input>
|
||||
<ui-input v-model="smtpPort" type="number" :disabled="!enableEmail">{{ $t('smtp-port') }}</ui-input>
|
||||
</ui-horizon-group>
|
||||
<ui-horizon-group inputs>
|
||||
<ui-input v-model="smtpUser" :disabled="!enableEmail">{{ $t('smtp-user') }}</ui-input>
|
||||
<ui-input v-model="smtpPass" :disabled="!enableEmail">{{ $t('smtp-pass') }}</ui-input>
|
||||
</ui-horizon-group>
|
||||
<ui-switch v-model="smtpSecure" :disabled="!enableEmail">{{ $t('smtp-use-ssl') }}</ui-switch>
|
||||
</section>
|
||||
<section>
|
||||
<header>summaly Proxy</header>
|
||||
|
@ -106,6 +118,7 @@ import i18n from '../../i18n';
|
|||
import { url, host } from '../../config';
|
||||
import { toUnicode } from 'punycode';
|
||||
import { faHeadset, faShieldAlt, faGhost, faUserPlus } from '@fortawesome/free-solid-svg-icons';
|
||||
import { faEnvelope as farEnvelope } from '@fortawesome/free-regular-svg-icons';
|
||||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('admin/views/instance.vue'),
|
||||
|
@ -144,7 +157,14 @@ export default Vue.extend({
|
|||
externalUserRecommendationEngine: null,
|
||||
externalUserRecommendationTimeout: null,
|
||||
summalyProxy: null,
|
||||
faHeadset, faShieldAlt, faGhost, faUserPlus
|
||||
enableEmail: false,
|
||||
email: null,
|
||||
smtpSecure: false,
|
||||
smtpHost: null,
|
||||
smtpPort: null,
|
||||
smtpUser: null,
|
||||
smtpPass: null,
|
||||
faHeadset, faShieldAlt, faGhost, faUserPlus, farEnvelope
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -177,6 +197,13 @@ export default Vue.extend({
|
|||
this.externalUserRecommendationEngine = meta.externalUserRecommendationEngine;
|
||||
this.externalUserRecommendationTimeout = meta.externalUserRecommendationTimeout;
|
||||
this.summalyProxy = meta.summalyProxy;
|
||||
this.enableEmail = meta.enableEmail;
|
||||
this.email = meta.email;
|
||||
this.smtpSecure = meta.smtpSecure;
|
||||
this.smtpHost = meta.smtpHost;
|
||||
this.smtpPort = meta.smtpPort;
|
||||
this.smtpUser = meta.smtpUser;
|
||||
this.smtpPass = meta.smtpPass;
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -222,7 +249,14 @@ export default Vue.extend({
|
|||
enableExternalUserRecommendation: this.enableExternalUserRecommendation,
|
||||
externalUserRecommendationEngine: this.externalUserRecommendationEngine,
|
||||
externalUserRecommendationTimeout: parseInt(this.externalUserRecommendationTimeout, 10),
|
||||
summalyProxy: this.summalyProxy
|
||||
summalyProxy: this.summalyProxy,
|
||||
enableEmail: this.enableEmail,
|
||||
email: this.email,
|
||||
smtpSecure: this.smtpSecure,
|
||||
smtpHost: this.smtpHost,
|
||||
smtpPort: parseInt(this.smtpPort, 10),
|
||||
smtpUser: this.smtpUser,
|
||||
smtpPass: this.smtpPass
|
||||
}).then(() => {
|
||||
this.$root.alert({
|
||||
type: 'success',
|
||||
|
|
|
@ -66,6 +66,19 @@
|
|||
<ui-switch v-model="carefulBot" @change="save(false)">{{ $t('careful-bot') }}</ui-switch>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<header>{{ $t('email') }}</header>
|
||||
|
||||
<div>
|
||||
<template v-if="$store.state.i.email != null">
|
||||
<ui-info v-if="$store.state.i.emailVerified">{{ $t('email-verified') }}</ui-info>
|
||||
<ui-info v-else warn>{{ $t('email-not-verified') }}</ui-info>
|
||||
</template>
|
||||
<ui-input v-model="email" type="email"><span>{{ $t('email-address') }}</span></ui-input>
|
||||
<ui-button @click="updateEmail()">{{ $t('save') }}</ui-button>
|
||||
</div>
|
||||
</section>
|
||||
</ui-card>
|
||||
</template>
|
||||
|
||||
|
@ -77,9 +90,11 @@ import { toUnicode } from 'punycode';
|
|||
|
||||
export default Vue.extend({
|
||||
i18n: i18n('common/views/components/profile-editor.vue'),
|
||||
|
||||
data() {
|
||||
return {
|
||||
host: toUnicode(host),
|
||||
email: null,
|
||||
name: null,
|
||||
username: null,
|
||||
location: null,
|
||||
|
@ -113,7 +128,8 @@ export default Vue.extend({
|
|||
},
|
||||
|
||||
created() {
|
||||
this.name = this.$store.state.i.name || '';
|
||||
this.email = this.$store.state.i.email;
|
||||
this.name = this.$store.state.i.name;
|
||||
this.username = this.$store.state.i.username;
|
||||
this.location = this.$store.state.i.profile.location;
|
||||
this.description = this.$store.state.i.description;
|
||||
|
@ -199,6 +215,12 @@ export default Vue.extend({
|
|||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
updateEmail() {
|
||||
this.$root.api('i/update_email', {
|
||||
email: this.email == '' ? null : this.email
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
@ -214,4 +214,12 @@ export type IMeta = {
|
|||
enableExternalUserRecommendation?: boolean;
|
||||
externalUserRecommendationEngine?: string;
|
||||
externalUserRecommendationTimeout?: number;
|
||||
|
||||
enableEmail?: boolean;
|
||||
email?: string;
|
||||
smtpSecure?: boolean;
|
||||
smtpHost?: string;
|
||||
smtpPort?: number;
|
||||
smtpUser?: string;
|
||||
smtpPass?: string;
|
||||
};
|
||||
|
|
|
@ -78,6 +78,8 @@ export interface ILocalUser extends IUserBase {
|
|||
host: null;
|
||||
keypair: string;
|
||||
email: string;
|
||||
emailVerified?: boolean;
|
||||
emailVerifyCode?: string;
|
||||
password: string;
|
||||
token: string;
|
||||
twitter: {
|
||||
|
@ -99,9 +101,6 @@ export interface ILocalUser extends IUserBase {
|
|||
username: string;
|
||||
discriminator: string;
|
||||
};
|
||||
line: {
|
||||
userId: string;
|
||||
};
|
||||
profile: {
|
||||
location: string;
|
||||
birthday: string; // 'YYYY-MM-DD'
|
||||
|
@ -286,6 +285,7 @@ export const pack = (
|
|||
delete _user._id;
|
||||
|
||||
delete _user.usernameLower;
|
||||
delete _user.emailVerifyCode;
|
||||
|
||||
if (_user.host == null) {
|
||||
// Remove private properties
|
||||
|
@ -306,11 +306,11 @@ export const pack = (
|
|||
delete _user.discord.refreshToken;
|
||||
delete _user.discord.expiresDate;
|
||||
}
|
||||
delete _user.line;
|
||||
|
||||
// Visible via only the official client
|
||||
if (!opts.includeSecrets) {
|
||||
delete _user.email;
|
||||
delete _user.emailVerified;
|
||||
delete _user.settings;
|
||||
delete _user.clientSettings;
|
||||
}
|
||||
|
|
|
@ -228,7 +228,56 @@ export const meta = {
|
|||
desc: {
|
||||
'ja-JP': '外部ユーザーレコメンデーションのタイムアウト (ミリ秒)'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
enableEmail: {
|
||||
validator: $.bool.optional,
|
||||
desc: {
|
||||
'ja-JP': 'メール配信を有効にするか否か'
|
||||
}
|
||||
},
|
||||
|
||||
email: {
|
||||
validator: $.str.optional.nullable,
|
||||
desc: {
|
||||
'ja-JP': 'メール配信する際に利用するメールアドレス'
|
||||
}
|
||||
},
|
||||
|
||||
smtpSecure: {
|
||||
validator: $.bool.optional,
|
||||
desc: {
|
||||
'ja-JP': 'SMTPサーバがSSLを使用しているか否か'
|
||||
}
|
||||
},
|
||||
|
||||
smtpHost: {
|
||||
validator: $.str.optional,
|
||||
desc: {
|
||||
'ja-JP': 'SMTPサーバのホスト'
|
||||
}
|
||||
},
|
||||
|
||||
smtpPort: {
|
||||
validator: $.num.optional,
|
||||
desc: {
|
||||
'ja-JP': 'SMTPサーバのポート'
|
||||
}
|
||||
},
|
||||
|
||||
smtpUser: {
|
||||
validator: $.str.optional,
|
||||
desc: {
|
||||
'ja-JP': 'SMTPサーバのユーザー名'
|
||||
}
|
||||
},
|
||||
|
||||
smtpPass: {
|
||||
validator: $.str.optional,
|
||||
desc: {
|
||||
'ja-JP': 'SMTPサーバのパスワード'
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -359,6 +408,34 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
|
|||
set.externalUserRecommendationTimeout = ps.externalUserRecommendationTimeout;
|
||||
}
|
||||
|
||||
if (ps.enableEmail !== undefined) {
|
||||
set.enableEmail = ps.enableEmail;
|
||||
}
|
||||
|
||||
if (ps.email !== undefined) {
|
||||
set.email = ps.email;
|
||||
}
|
||||
|
||||
if (ps.smtpSecure !== undefined) {
|
||||
set.smtpSecure = ps.smtpSecure;
|
||||
}
|
||||
|
||||
if (ps.smtpHost !== undefined) {
|
||||
set.smtpHost = ps.smtpHost;
|
||||
}
|
||||
|
||||
if (ps.smtpPort !== undefined) {
|
||||
set.smtpPort = ps.smtpPort;
|
||||
}
|
||||
|
||||
if (ps.smtpUser !== undefined) {
|
||||
set.smtpUser = ps.smtpUser;
|
||||
}
|
||||
|
||||
if (ps.smtpPass !== undefined) {
|
||||
set.smtpPass = ps.smtpPass;
|
||||
}
|
||||
|
||||
await Meta.update({}, {
|
||||
$set: set
|
||||
}, { upsert: true });
|
||||
|
|
85
src/server/api/endpoints/i/update_email.ts
Normal file
85
src/server/api/endpoints/i/update_email.ts
Normal file
|
@ -0,0 +1,85 @@
|
|||
import $ from 'cafy';
|
||||
import User, { pack } from '../../../../models/user';
|
||||
import { publishMainStream } from '../../../../stream';
|
||||
import define from '../../define';
|
||||
import * as nodemailer from 'nodemailer';
|
||||
import fetchMeta from '../../../../misc/fetch-meta';
|
||||
import rndstr from 'rndstr';
|
||||
import config from '../../../../config';
|
||||
const ms = require('ms');
|
||||
|
||||
export const meta = {
|
||||
requireCredential: true,
|
||||
|
||||
secure: true,
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 3
|
||||
},
|
||||
|
||||
params: {
|
||||
email: {
|
||||
validator: $.str.optional.nullable
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
email: ps.email,
|
||||
emailVerified: false,
|
||||
emailVerifyCode: null
|
||||
}
|
||||
});
|
||||
|
||||
// Serialize
|
||||
const iObj = await pack(user._id, user, {
|
||||
detail: true,
|
||||
includeSecrets: true
|
||||
});
|
||||
|
||||
// Send response
|
||||
res(iObj);
|
||||
|
||||
// Publish meUpdated event
|
||||
publishMainStream(user._id, 'meUpdated', iObj);
|
||||
|
||||
if (ps.email != null) {
|
||||
const code = rndstr('a-z0-9', 16);
|
||||
|
||||
await User.update(user._id, {
|
||||
$set: {
|
||||
emailVerifyCode: code
|
||||
}
|
||||
});
|
||||
|
||||
const meta = await fetchMeta();
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: meta.smtpHost,
|
||||
port: meta.smtpPort,
|
||||
secure: meta.smtpSecure,
|
||||
auth: {
|
||||
user: meta.smtpUser,
|
||||
pass: meta.smtpPass
|
||||
}
|
||||
});
|
||||
|
||||
const link = `${config.url}/vefify-email/${code}`;
|
||||
|
||||
transporter.sendMail({
|
||||
from: meta.email,
|
||||
to: ps.email,
|
||||
subject: meta.name,
|
||||
text: `To verify email, please click this link: ${link}`
|
||||
}, (error, info) => {
|
||||
if (error) {
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
console.log('Message sent: %s', info.messageId);
|
||||
});
|
||||
}
|
||||
}));
|
|
@ -108,6 +108,13 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
|
|||
response.discordClientId = instance.discordClientId;
|
||||
response.discordClientSecret = instance.discordClientSecret;
|
||||
response.summalyProxy = instance.summalyProxy;
|
||||
response.enableEmail = instance.enableEmail;
|
||||
response.email = instance.email;
|
||||
response.smtpSecure = instance.smtpSecure;
|
||||
response.smtpHost = instance.smtpHost;
|
||||
response.smtpPort = instance.smtpPort;
|
||||
response.smtpUser = instance.smtpUser;
|
||||
response.smtpPass = instance.smtpPass;
|
||||
}
|
||||
|
||||
res(response);
|
||||
|
|
|
@ -20,6 +20,7 @@ import config from '../config';
|
|||
import networkChart from '../chart/network';
|
||||
import apiServer from './api';
|
||||
import { sum } from '../prelude/array';
|
||||
import User from '../models/user';
|
||||
|
||||
// Init app
|
||||
const app = new Koa();
|
||||
|
@ -59,6 +60,24 @@ const router = new Router();
|
|||
router.use(activityPub.routes());
|
||||
router.use(webFinger.routes());
|
||||
|
||||
router.get('/verify-email/:code', async ctx => {
|
||||
const user = await User.findOne({ emailVerifyCode: ctx.params.code });
|
||||
|
||||
if (user != null) {
|
||||
ctx.body = 'Verify succeeded!';
|
||||
ctx.status = 200;
|
||||
|
||||
User.update({ _id: user._id }, {
|
||||
$set: {
|
||||
emailVerified: true,
|
||||
emailVerifyCode: null
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ctx.status = 404;
|
||||
}
|
||||
});
|
||||
|
||||
// Return 404 for other .well-known
|
||||
router.all('/.well-known/*', async ctx => {
|
||||
ctx.status = 404;
|
||||
|
|
Loading…
Reference in a new issue