summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-11-29 16:23:45 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-11-29 16:23:45 +0900
commit1bc109b42c43dfa0dcad4b1331896ab5b2023892 (patch)
tree2185b3408754f78ad03b991e3c78415c6dee111b /src
parent10.58.2 (diff)
downloadmisskey-1bc109b42c43dfa0dcad4b1331896ab5b2023892.tar.gz
misskey-1bc109b42c43dfa0dcad4b1331896ab5b2023892.tar.bz2
misskey-1bc109b42c43dfa0dcad4b1331896ab5b2023892.zip
Implement email config
Diffstat (limited to 'src')
-rw-r--r--src/client/app/admin/views/instance.vue48
-rw-r--r--src/client/app/common/views/components/profile-editor.vue24
-rw-r--r--src/models/meta.ts8
-rw-r--r--src/models/user.ts8
-rw-r--r--src/server/api/endpoints/admin/update-meta.ts79
-rw-r--r--src/server/api/endpoints/i/update_email.ts85
-rw-r--r--src/server/api/endpoints/meta.ts7
-rw-r--r--src/server/index.ts19
8 files changed, 265 insertions, 13 deletions
diff --git a/src/client/app/admin/views/instance.vue b/src/client/app/admin/views/instance.vue
index da27b1e470..8609abf1e5 100644
--- a/src/client/app/admin/views/instance.vue
+++ b/src/client/app/admin/views/instance.vue
@@ -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',
diff --git a/src/client/app/common/views/components/profile-editor.vue b/src/client/app/common/views/components/profile-editor.vue
index c3118126d2..fc0fbb9e65 100644
--- a/src/client/app/common/views/components/profile-editor.vue
+++ b/src/client/app/common/views/components/profile-editor.vue
@@ -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
+ });
}
}
});
diff --git a/src/models/meta.ts b/src/models/meta.ts
index 99d770366d..c8ef18a691 100644
--- a/src/models/meta.ts
+++ b/src/models/meta.ts
@@ -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;
};
diff --git a/src/models/user.ts b/src/models/user.ts
index 241af892ae..db10e06d8a 100644
--- a/src/models/user.ts
+++ b/src/models/user.ts
@@ -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;
}
diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts
index cff9ff8e52..3f3cd4a844 100644
--- a/src/server/api/endpoints/admin/update-meta.ts
+++ b/src/server/api/endpoints/admin/update-meta.ts
@@ -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 });
diff --git a/src/server/api/endpoints/i/update_email.ts b/src/server/api/endpoints/i/update_email.ts
new file mode 100644
index 0000000000..c2699d47c2
--- /dev/null
+++ b/src/server/api/endpoints/i/update_email.ts
@@ -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);
+ });
+ }
+}));
diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts
index 49ce41c7da..d18e6a154b 100644
--- a/src/server/api/endpoints/meta.ts
+++ b/src/server/api/endpoints/meta.ts
@@ -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);
diff --git a/src/server/index.ts b/src/server/index.ts
index e26f73ff4d..88a39cd24a 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -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;