diff --git a/locales/en-US.yml b/locales/en-US.yml index fd8e96588..158b8c649 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -601,6 +601,8 @@ common/views/components/signin.vue: signin-with-github: "Sign in with GitHub" signin-with-discord: "Sign in with Discord" login-failed: "Logging in has failed. Make sure you have entered the correct username and password." + tap-key: "Activate your security key by tapping or clicking it to login" + enter-2fa-code: "Enter your 2FA code below" common/views/components/signup.vue: invitation-code: "Invitation code" invitation-info: "If you do not have an invitation code, please contact an administrator." @@ -984,7 +986,7 @@ desktop/views/components/settings.2fa.vue: url: "https://www.google.com/landing/2step/" caution: "If you lose access to your registered device, you won't be able to connect to Misskey anymore!" register: "Register a device" - already-registered: "This device is already registered" + already-registered: "Your account is currently registered to an authenticator application" unregister: "Unregister" unregistered: "Two-factor authentication has been disabled." enter-password: "Enter the password" @@ -997,6 +999,15 @@ desktop/views/components/settings.2fa.vue: success: "Settings saved!" failed: "Failed to setup. Please ensure that the token is correct." info: "From the next time you sign in to Misskey, the token displayed on your device will be necessary too, as well as the password." + totp-header: "Authenticator App" + security-key-header: "Security Keys" + security-key: "You can use a hardware security key supporting FIDO2 to log into your account for enhanced security. When you sign-in, you'll need a registered security key or your authenticator app." + last-used: "Last used:" + activate-key: "Please activate your security key by tapping or clicking it" + security-key-name: "Key Name" + register-security-key: "Finish Key Registration" + something-went-wrong: "Oops! Something went wrong while trying to register your key:" + key-unregistered: "Key Removed" common/views/components/media-image.vue: sensitive: "NSFW" click-to-show: "Click to show" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 9d104456f..5767a51b0 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -646,6 +646,8 @@ common/views/components/signin.vue: signin-with-github: "GitHubでログイン" signin-with-discord: "Discordでログイン" login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。" + tap-key: "セキュリティキーをクリックしてログイン" + enter-2fa-code: "認証コードを入力してください" common/views/components/signup.vue: invitation-code: "招待コード" @@ -1100,6 +1102,15 @@ desktop/views/components/settings.2fa.vue: success: "設定が完了しました!" failed: "設定に失敗しました。トークンに誤りがないかご確認ください。" info: "次回サインインからは、同様にパスワードに加えてデバイスに表示されているトークンを入力します。" + totp-header: "認証アプリ" + security-key-header: "セキュリティキー" + security-key: "セキュリティを強化するために、FIDO2をサポートするハードウェアセキュリティキーを使用してアカウントにログインできます。 サインインの際は、登録されたセキュリティキーまたは認証アプリが必要になります。" + last-used: "最後の使用:" + activate-key: "クリックしてセキュリティキーをアクティベートしてください" + security-key-name: "キー名" + register-security-key: "キーの登録を完了" + something-went-wrong: "わー! キーを登録する際に問題が発生しました:" + key-unregistered: "キーが削除されました" common/views/components/media-image.vue: sensitive: "閲覧注意" diff --git a/migration/1561706992953-webauthn.ts b/migration/1561706992953-webauthn.ts new file mode 100644 index 000000000..fc1f0c042 --- /dev/null +++ b/migration/1561706992953-webauthn.ts @@ -0,0 +1,29 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class webauthn1561706992953 implements MigrationInterface { + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "attestation_challenge" ("id" character varying(32) NOT NULL, "userId" character varying(32) NOT NULL, "challenge" character varying(64) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "registrationChallenge" boolean NOT NULL DEFAULT false, CONSTRAINT "PK_d0ba6786e093f1bcb497572a6b5" PRIMARY KEY ("id", "userId"))`); + await queryRunner.query(`CREATE INDEX "IDX_f1a461a618fa1755692d0e0d59" ON "attestation_challenge" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_47efb914aed1f72dd39a306c7b" ON "attestation_challenge" ("challenge") `); + await queryRunner.query(`CREATE TABLE "user_security_key" ("id" character varying NOT NULL, "userId" character varying(32) NOT NULL, "publicKey" character varying NOT NULL, "lastUsed" TIMESTAMP WITH TIME ZONE NOT NULL, "name" character varying(30) NOT NULL, CONSTRAINT "PK_3e508571121ab39c5f85d10c166" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_ff9ca3b5f3ee3d0681367a9b44" ON "user_security_key" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_0d7718e562dcedd0aa5cf2c9f7" ON "user_security_key" ("publicKey") `); + await queryRunner.query(`ALTER TABLE "user_profile" ADD "securityKeysAvailable" boolean NOT NULL DEFAULT false`); + await queryRunner.query(`ALTER TABLE "attestation_challenge" ADD CONSTRAINT "FK_f1a461a618fa1755692d0e0d592" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + await queryRunner.query(`ALTER TABLE "user_security_key" ADD CONSTRAINT "FK_ff9ca3b5f3ee3d0681367a9b447" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user_security_key" DROP CONSTRAINT "FK_ff9ca3b5f3ee3d0681367a9b447"`); + await queryRunner.query(`ALTER TABLE "attestation_challenge" DROP CONSTRAINT "FK_f1a461a618fa1755692d0e0d592"`); + await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "securityKeysAvailable"`); + await queryRunner.query(`DROP INDEX "IDX_0d7718e562dcedd0aa5cf2c9f7"`); + await queryRunner.query(`DROP INDEX "IDX_ff9ca3b5f3ee3d0681367a9b44"`); + await queryRunner.query(`DROP TABLE "user_security_key"`); + await queryRunner.query(`DROP INDEX "IDX_47efb914aed1f72dd39a306c7b"`); + await queryRunner.query(`DROP INDEX "IDX_f1a461a618fa1755692d0e0d59"`); + await queryRunner.query(`DROP TABLE "attestation_challenge"`); + } + +} diff --git a/package.json b/package.json index 79009380c..119deacaf 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "@koa/cors": "3.0.0", "@types/bcryptjs": "2.4.2", "@types/bull": "3.5.15", + "@types/cbor": "2.0.0", "@types/dateformat": "3.0.0", "@types/deep-equal": "1.0.1", "@types/double-ended-queue": "2.1.1", @@ -104,9 +105,11 @@ "autosize": "4.0.2", "autwh": "0.1.0", "bcryptjs": "2.4.3", + "bootstrap": "4.3.1", "bootstrap-vue": "2.0.0-rc.13", "bull": "3.10.0", "cafy": "15.1.1", + "cbor": "4.1.5", "chai": "4.2.0", "chalk": "2.4.2", "cli-highlight": "2.1.1", @@ -148,6 +151,7 @@ "jsdom": "15.1.1", "json5": "2.1.0", "json5-loader": "3.0.0", + "jsrsasign": "8.0.12", "katex": "0.10.2", "koa": "2.7.0", "koa-bodyparser": "4.2.1", diff --git a/src/boot/master.ts b/src/boot/master.ts index 6c23a528f..b698548d4 100644 --- a/src/boot/master.ts +++ b/src/boot/master.ts @@ -79,6 +79,7 @@ export async function masterMain() { require('../daemons/server-stats').default(); require('../daemons/notes-stats').default(); require('../daemons/queue-stats').default(); + require('../daemons/janitor').default(); } bootLogger.succ(`Now listening on port ${config.port} on ${config.url}`, null, true); diff --git a/src/client/app/common/scripts/2fa.ts b/src/client/app/common/scripts/2fa.ts new file mode 100644 index 000000000..f638cce15 --- /dev/null +++ b/src/client/app/common/scripts/2fa.ts @@ -0,0 +1,5 @@ +export function hexifyAB(buffer) { + return Array.from(new Uint8Array(buffer)) + .map(item => item.toString(16).padStart(2, 0)) + .join(''); +} diff --git a/src/client/app/common/views/components/settings/2fa.vue b/src/client/app/common/views/components/settings/2fa.vue index 6e8d19d83..eb645898e 100644 --- a/src/client/app/common/views/components/settings/2fa.vue +++ b/src/client/app/common/views/components/settings/2fa.vue @@ -1,11 +1,54 @@