From 5ae6b0058ff4e5be19d4561713fc37e49be5ae1d Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 6 Jul 2019 00:46:00 +0900 Subject: [PATCH 01/30] =?UTF-8?q?=E3=82=84=E3=81=A3=E3=81=9F=20(#5110)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../views/components/games/reversi/reversi.game.vue | 6 +++--- src/client/app/common/views/components/mfm.ts | 12 ++++++------ .../views/components/misskey-flavored-markdown.vue | 4 ++-- src/client/app/common/views/components/poll.vue | 2 +- src/client/app/common/views/components/user-list.vue | 2 +- src/client/app/common/views/components/user-name.vue | 2 +- .../app/common/views/deck/deck.notification.vue | 6 +++--- .../app/common/views/deck/deck.user-column.vue | 2 +- src/client/app/common/views/pages/follow.vue | 2 +- .../app/desktop/views/components/notifications.vue | 12 ++++++------ .../app/desktop/views/home/user/user.header.vue | 2 +- .../app/mobile/views/components/notification.vue | 6 +++--- src/client/app/mobile/views/pages/settings.vue | 2 +- src/client/app/mobile/views/pages/user/index.vue | 2 +- 14 files changed, 31 insertions(+), 31 deletions(-) diff --git a/src/client/app/common/views/components/games/reversi/reversi.game.vue b/src/client/app/common/views/components/games/reversi/reversi.game.vue index 3f481e8eb5..a7c918aa71 100644 --- a/src/client/app/common/views/components/games/reversi/reversi.game.vue +++ b/src/client/app/common/views/components/games/reversi/reversi.game.vue @@ -5,17 +5,17 @@

- +

- +

{{ $t('@.reversi.opponent-turn') }}

{{ $t('@.reversi.my-turn') }}

diff --git a/src/client/app/common/views/components/mfm.ts b/src/client/app/common/views/components/mfm.ts index fa798504c7..561c3d8e30 100644 --- a/src/client/app/common/views/components/mfm.ts +++ b/src/client/app/common/views/components/mfm.ts @@ -22,11 +22,11 @@ export default Vue.component('misskey-flavored-markdown', { type: String, required: true }, - shouldBreak: { + plain: { type: Boolean, - default: true + default: false }, - plainText: { + nowrap: { type: Boolean, default: false }, @@ -50,7 +50,7 @@ export default Vue.component('misskey-flavored-markdown', { render(createElement) { if (this.text == null || this.text == '') return; - const ast = (this.plainText ? parsePlain : parse)(this.text); + const ast = (this.plain ? parsePlain : parse)(this.text); let bigCount = 0; let motionCount = 0; @@ -60,7 +60,7 @@ export default Vue.component('misskey-flavored-markdown', { case 'text': { const text = token.node.props.text.replace(/(\r\n|\n|\r)/g, '\n'); - if (this.shouldBreak) { + if (!this.plain) { const x = text.split('\n') .map(t => t == '' ? [createElement('br')] : [createElement('span', t), createElement('br')]); x[x.length - 1].pop(); @@ -270,7 +270,7 @@ export default Vue.component('misskey-flavored-markdown', { }, props: { customEmojis: this.customEmojis || customEmojis, - normal: this.plainText + normal: this.plain } })]; } diff --git a/src/client/app/common/views/components/misskey-flavored-markdown.vue b/src/client/app/common/views/components/misskey-flavored-markdown.vue index a0ecbecbb2..64496f9c84 100644 --- a/src/client/app/common/views/components/misskey-flavored-markdown.vue +++ b/src/client/app/common/views/components/misskey-flavored-markdown.vue @@ -1,5 +1,5 @@ diff --git a/src/client/app/common/views/pages/page/page.button.vue b/src/client/app/common/views/pages/page/page.button.vue index 3747be96ce..9f760bf48c 100644 --- a/src/client/app/common/views/pages/page/page.button.vue +++ b/src/client/app/common/views/pages/page/page.button.vue @@ -27,6 +27,16 @@ export default Vue.extend({ } else if (this.value.action === 'resetRandom') { this.script.aiScript.updateRandomSeed(Math.random()); this.script.eval(); + } else if (this.value.action === 'pushEvent') { + this.$root.api('page-push', { + pageId: this.script.page.id, + event: this.value.event + }); + + this.$root.dialog({ + type: 'success', + text: this.script.interpolate(this.value.message) + }); } } } diff --git a/src/client/app/common/views/pages/page/page.vue b/src/client/app/common/views/pages/page/page.vue index 96a2cfafb7..a93d5316d5 100644 --- a/src/client/app/common/views/pages/page/page.vue +++ b/src/client/app/common/views/pages/page/page.vue @@ -35,8 +35,10 @@ class Script { public aiScript: ASEvaluator; private onError: any; public vars: Record; + public page: Record; - constructor(aiScript, onError) { + constructor(page, aiScript, onError) { + this.page = page; this.aiScript = aiScript; this.onError = onError; this.eval(); @@ -113,7 +115,7 @@ export default Vue.extend({ icon: faStickyNote }); const pageVars = this.getPageVars(); - this.script = new Script(new ASEvaluator(this.page.variables, pageVars, { + this.script = new Script(this.page, new ASEvaluator(this.page.variables, pageVars, { randomSeed: Math.random(), user: page.user, visitor: this.$store.state.i, diff --git a/src/server/api/endpoints/page-push.ts b/src/server/api/endpoints/page-push.ts new file mode 100644 index 0000000000..300df7c250 --- /dev/null +++ b/src/server/api/endpoints/page-push.ts @@ -0,0 +1,44 @@ +import $ from 'cafy'; +import define from '../define'; +import { ID } from '../../../misc/cafy-id'; +import { publishMainStream } from '../../../services/stream'; +import { Users, Pages } from '../../../models'; +import { ApiError } from '../error'; + +export const meta = { + requireCredential: true, + secure: true, + + params: { + pageId: { + validator: $.type(ID) + }, + + event: { + validator: $.str + } + }, + + errors: { + noSuchPage: { + message: 'No such page.', + code: 'NO_SUCH_PAGE', + id: '4a13ad31-6729-46b4-b9af-e86b265c2e74' + } + } +}; + +export default define(meta, async (ps, user) => { + const page = await Pages.findOne(ps.pageId); + if (page == null) { + throw new ApiError(meta.errors.noSuchPage); + } + + publishMainStream(user.id, 'pageEvent', { + pageId: ps.pageId, + event: ps.event, + user: await Users.pack(user, page.userId, { + detail: true + }) + }); +}); From 067e9ec6f4ca6f6741456f7d46bf8b237edffa77 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 6 Jul 2019 22:53:15 +0900 Subject: [PATCH 03/30] Fix bug --- src/client/app/common/views/components/url-preview.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/app/common/views/components/url-preview.vue b/src/client/app/common/views/components/url-preview.vue index 20fbcbb046..476c671e77 100644 --- a/src/client/app/common/views/components/url-preview.vue +++ b/src/client/app/common/views/components/url-preview.vue @@ -63,6 +63,7 @@ export default Vue.extend({ data() { const isSelf = this.url.startsWith(local); const hasRoute = + (this.url.substr(local.length) === '/') || this.url.substr(local.length).startsWith('/@') || this.url.substr(local.length).startsWith('/notes/') || this.url.substr(local.length).startsWith('/pages/'); From e97dd13e815b277b1aafab4b217ebdeeaa5fb347 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 6 Jul 2019 23:11:16 +0900 Subject: [PATCH 04/30] =?UTF-8?q?Pages:=20=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=82=92=E8=89=B2=E4=BB=98=E3=81=8D=E8=A1=A8=E7=A4=BA=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/ja-JP.yml | 1 + .../views/pages/page-editor/els/page-editor.el.button.vue | 2 ++ src/client/app/common/views/pages/page/page.button.vue | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9726d49c25..6666e630a3 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2024,6 +2024,7 @@ pages: _button: text: "タイトル" + colored: "色付き" action: "ボタンを押したときの動作" _action: dialog: "ダイアログを表示する" diff --git a/src/client/app/common/views/pages/page-editor/els/page-editor.el.button.vue b/src/client/app/common/views/pages/page-editor/els/page-editor.el.button.vue index 579de6a8fc..04001d8560 100644 --- a/src/client/app/common/views/pages/page-editor/els/page-editor.el.button.vue +++ b/src/client/app/common/views/pages/page-editor/els/page-editor.el.button.vue @@ -4,6 +4,7 @@

{{ $t('blocks._button.text') }} + {{ $t('blocks._button.colored') }} @@ -52,6 +53,7 @@ export default Vue.extend({ if (this.value.content == null) Vue.set(this.value, 'content', null); if (this.value.event == null) Vue.set(this.value, 'event', null); if (this.value.message == null) Vue.set(this.value, 'message', null); + if (this.value.message == null) Vue.set(this.value, 'primary', false); }, }); diff --git a/src/client/app/common/views/pages/page/page.button.vue b/src/client/app/common/views/pages/page/page.button.vue index 9f760bf48c..d3f0307625 100644 --- a/src/client/app/common/views/pages/page/page.button.vue +++ b/src/client/app/common/views/pages/page/page.button.vue @@ -1,6 +1,6 @@ From 047a46d96689a97bee4c843fcd86e63b816846f1 Mon Sep 17 00:00:00 2001 From: Satsuki Yanagi <17376330+u1-liquid@users.noreply.github.com> Date: Sun, 7 Jul 2019 01:38:36 +0900 Subject: [PATCH 05/30] Support password-less login with WebAuthn (#5112) * Support password-less login with WebAuthn * Fix initial value of usePasswordLessLogin --- locales/ja-JP.yml | 1 + migration/1562422242907-PasswordLessLogin.ts | 13 +++++++ .../common/views/components/settings/2fa.vue | 16 +++++++++ .../app/common/views/components/signin.vue | 6 +++- src/models/entities/user-profile.ts | 5 +++ src/models/repositories/user.ts | 2 +- .../api/endpoints/i/2fa/password-less.ts | 21 +++++++++++ src/server/api/private/signin.ts | 36 ++++++++++++++----- 8 files changed, 90 insertions(+), 10 deletions(-) create mode 100644 migration/1562422242907-PasswordLessLogin.ts create mode 100644 src/server/api/endpoints/i/2fa/password-less.ts diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 6666e630a3..5daf7e7e3a 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1112,6 +1112,7 @@ desktop/views/components/settings.2fa.vue: register-security-key: "キーの登録を完了" something-went-wrong: "わー! キーを登録する際に問題が発生しました:" key-unregistered: "キーが削除されました" + use-password-less-login: "パスワードなしのログインを使用" common/views/components/media-image.vue: sensitive: "閲覧注意" diff --git a/migration/1562422242907-PasswordLessLogin.ts b/migration/1562422242907-PasswordLessLogin.ts new file mode 100644 index 0000000000..e789a34334 --- /dev/null +++ b/migration/1562422242907-PasswordLessLogin.ts @@ -0,0 +1,13 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class PasswordLessLogin1562422242907 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "usePasswordLessLogin" boolean DEFAULT false NOT NULL`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "usePasswordLessLogin"`); + } + +} diff --git a/src/client/app/common/views/components/settings/2fa.vue b/src/client/app/common/views/components/settings/2fa.vue index eb645898e2..813a91b5c0 100644 --- a/src/client/app/common/views/components/settings/2fa.vue +++ b/src/client/app/common/views/components/settings/2fa.vue @@ -28,6 +28,10 @@
+ + {{ $t('use-password-less-login') }} + + {{ $t('something-went-wrong') }} {{ registration.error }} {{ $t('register') }} @@ -80,6 +84,7 @@ export default Vue.extend({ return { data: null, supportsCredentials: !!navigator.credentials, + usePasswordLessLogin: this.$store.state.i.usePasswordLessLogin, registration: null, keyName: '', token: null @@ -112,6 +117,9 @@ export default Vue.extend({ if (canceled) return; this.$root.api('i/2fa/unregister', { password: password + }).then(() => { + this.usePasswordLessLogin = false; + this.updatePasswordLessLogin(); }).then(() => { this.$notify(this.$t('unregistered')); this.$store.state.i.twoFactorEnabled = false; @@ -157,6 +165,9 @@ export default Vue.extend({ return this.$root.api('i/2fa/remove-key', { password, credentialId: key.id + }).then(() => { + this.usePasswordLessLogin = false; + this.updatePasswordLessLogin(); }).then(() => { this.$notify(this.$t('key-unregistered')); }); @@ -213,6 +224,11 @@ export default Vue.extend({ this.registration.stage = -1; }); }); + }, + updatePasswordLessLogin() { + this.$root.api('i/2fa/password-less', { + value: !!this.usePasswordLessLogin + }); } } }); diff --git a/src/client/app/common/views/components/signin.vue b/src/client/app/common/views/components/signin.vue index 8498a1dc3e..f76f989d6d 100644 --- a/src/client/app/common/views/components/signin.vue +++ b/src/client/app/common/views/components/signin.vue @@ -7,7 +7,7 @@ - + {{ $t('password') }} @@ -28,6 +28,10 @@

{{ $t('enter-2fa-code') }}

+ + {{ $t('password') }} + + {{ $t('@.2fa') }} diff --git a/src/models/entities/user-profile.ts b/src/models/entities/user-profile.ts index 6f960f1b7b..4a588ebfbf 100644 --- a/src/models/entities/user-profile.ts +++ b/src/models/entities/user-profile.ts @@ -81,6 +81,11 @@ export class UserProfile { }) public securityKeysAvailable: boolean; + @Column('boolean', { + default: false, + }) + public usePasswordLessLogin: boolean; + @Column('varchar', { length: 128, nullable: true, comment: 'The password hash of the User. It will be null if the origin of the user is local.' diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index cc89b674c5..06da74197f 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -156,6 +156,7 @@ export class UserRepository extends Repository { detail: true }), twoFactorEnabled: profile!.twoFactorEnabled, + usePasswordLessLogin: profile!.usePasswordLessLogin, securityKeys: profile!.twoFactorEnabled ? UserSecurityKeys.count({ userId: user.id @@ -208,7 +209,6 @@ export class UserRepository extends Repository { select: ['id', 'name', 'lastUsed'] }) : [] - } : {}), ...(relation ? { diff --git a/src/server/api/endpoints/i/2fa/password-less.ts b/src/server/api/endpoints/i/2fa/password-less.ts new file mode 100644 index 0000000000..19e75ca1c5 --- /dev/null +++ b/src/server/api/endpoints/i/2fa/password-less.ts @@ -0,0 +1,21 @@ +import $ from 'cafy'; +import define from '../../../define'; +import { UserProfiles } from '../../../../../models'; + +export const meta = { + requireCredential: true, + + secure: true, + + params: { + value: { + validator: $.boolean + } + } +}; + +export default define(meta, async (ps, user) => { + await UserProfiles.update(user.id, { + usePasswordLessLogin: ps.value + }); +}); diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts index bc9346d088..67afed760b 100644 --- a/src/server/api/private/signin.ts +++ b/src/server/api/private/signin.ts @@ -72,19 +72,25 @@ export default async (ctx: Koa.BaseContext) => { } } - if (!same) { - await fail(403, { - error: 'incorrect password' - }); - return; - } - if (!profile.twoFactorEnabled) { - signin(ctx, user); + if (same) { + signin(ctx, user); + } else { + await fail(403, { + error: 'incorrect password' + }); + } return; } if (token) { + if (!same) { + await fail(403, { + error: 'incorrect password' + }); + return; + } + const verified = (speakeasy as any).totp.verify({ secret: profile.twoFactorSecret, encoding: 'base32', @@ -101,6 +107,13 @@ export default async (ctx: Koa.BaseContext) => { return; } } else if (body.credentialId) { + if (!same && !profile.usePasswordLessLogin) { + await fail(403, { + error: 'incorrect password' + }); + return; + } + const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex'); const clientData = JSON.parse(clientDataJSON.toString('utf-8')); const challenge = await AttestationChallenges.findOne({ @@ -163,6 +176,13 @@ export default async (ctx: Koa.BaseContext) => { return; } } else { + if (!same && !profile.usePasswordLessLogin) { + await fail(403, { + error: 'incorrect password' + }); + return; + } + const keys = await UserSecurityKeys.find({ userId: user.id }); From 82d2b0608fc8301b18ba4c1b90a7c2a8c767bddf Mon Sep 17 00:00:00 2001 From: tamaina Date: Thu, 21 Mar 2019 04:10:49 +0900 Subject: [PATCH 06/30] fix #1442, fix #2106 --- src/client/app/boot.js | 2 +- src/client/app/desktop/views/home/home.vue | 6 ++-- .../app/desktop/views/home/timeline.vue | 28 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/client/app/boot.js b/src/client/app/boot.js index 6cb1488e40..01104bf715 100644 --- a/src/client/app/boot.js +++ b/src/client/app/boot.js @@ -84,7 +84,7 @@ // Detect the user agent const ua = navigator.userAgent.toLowerCase(); - const isMobile = /mobile|iphone|ipad|android/.test(ua); + const isMobile = /mobile|iphone|ipad|android/.test(ua) || window.innerWidth < 576; // Get the element const head = document.getElementsByTagName('head')[0]; diff --git a/src/client/app/desktop/views/home/home.vue b/src/client/app/desktop/views/home/home.vue index 3d05bee48d..a4232a90a8 100644 --- a/src/client/app/desktop/views/home/home.vue +++ b/src/client/app/desktop/views/home/home.vue @@ -39,7 +39,7 @@
-
+