summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/client/src/pages/settings/2fa.vue317
1 files changed, 153 insertions, 164 deletions
diff --git a/packages/client/src/pages/settings/2fa.vue b/packages/client/src/pages/settings/2fa.vue
index 10599d99ff..9ebf5101cd 100644
--- a/packages/client/src/pages/settings/2fa.vue
+++ b/packages/client/src/pages/settings/2fa.vue
@@ -1,49 +1,49 @@
<template>
<div>
- <MkButton v-if="!data && !$i.twoFactorEnabled" @click="register">{{ $ts._2fa.registerDevice }}</MkButton>
+ <MkButton v-if="!twoFactorData && !$i.twoFactorEnabled" @click="register">{{ i18n.ts._2fa.registerDevice }}</MkButton>
<template v-if="$i.twoFactorEnabled">
- <p>{{ $ts._2fa.alreadyRegistered }}</p>
- <MkButton @click="unregister">{{ $ts.unregister }}</MkButton>
+ <p>{{ i18n.ts._2fa.alreadyRegistered }}</p>
+ <MkButton @click="unregister">{{ i18n.ts.unregister }}</MkButton>
<template v-if="supportsCredentials">
<hr class="totp-method-sep">
- <h2 class="heading">{{ $ts.securityKey }}</h2>
- <p>{{ $ts._2fa.securityKeyInfo }}</p>
+ <h2 class="heading">{{ i18n.ts.securityKey }}</h2>
+ <p>{{ i18n.ts._2fa.securityKeyInfo }}</p>
<div class="key-list">
<div v-for="key in $i.securityKeysList" class="key">
<h3>{{ key.name }}</h3>
- <div class="last-used">{{ $ts.lastUsed }}<MkTime :time="key.lastUsed"/></div>
- <MkButton @click="unregisterKey(key)">{{ $ts.unregister }}</MkButton>
+ <div class="last-used">{{ i18n.ts.lastUsed }}<MkTime :time="key.lastUsed"/></div>
+ <MkButton @click="unregisterKey(key)">{{ i18n.ts.unregister }}</MkButton>
</div>
</div>
- <MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin">{{ $ts.passwordLessLogin }}</MkSwitch>
+ <MkSwitch v-if="$i.securityKeysList.length > 0" v-model="usePasswordLessLogin" @update:modelValue="updatePasswordLessLogin">{{ i18n.ts.passwordLessLogin }}</MkSwitch>
- <MkInfo v-if="registration && registration.error" warn>{{ $ts.error }} {{ registration.error }}</MkInfo>
- <MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ $ts._2fa.registerKey }}</MkButton>
+ <MkInfo v-if="registration && registration.error" warn>{{ i18n.ts.error }} {{ registration.error }}</MkInfo>
+ <MkButton v-if="!registration || registration.error" @click="addSecurityKey">{{ i18n.ts._2fa.registerKey }}</MkButton>
<ol v-if="registration && !registration.error">
<li v-if="registration.stage >= 0">
- {{ $ts.tapSecurityKey }}
+ {{ i18n.ts.tapSecurityKey }}
<i v-if="registration.saving && registration.stage == 0" class="fas fa-spinner fa-pulse fa-fw"></i>
</li>
<li v-if="registration.stage >= 1">
<MkForm :disabled="registration.stage != 1 || registration.saving">
<MkInput v-model="keyName" :max="30">
- <template #label>{{ $ts.securityKeyName }}</template>
+ <template #label>{{ i18n.ts.securityKeyName }}</template>
</MkInput>
- <MkButton :disabled="keyName.length == 0" @click="registerKey">{{ $ts.registerSecurityKey }}</MkButton>
+ <MkButton :disabled="keyName.length == 0" @click="registerKey">{{ i18n.ts.registerSecurityKey }}</MkButton>
<i v-if="registration.saving && registration.stage == 1" class="fas fa-spinner fa-pulse fa-fw"></i>
</MkForm>
</li>
</ol>
</template>
</template>
- <div v-if="data && !$i.twoFactorEnabled">
+ <div v-if="twoFactorData && !$i.twoFactorEnabled">
<ol style="margin: 0; padding: 0 0 0 1em;">
<li>
- <I18n :src="$ts._2fa.step1" tag="span">
+ <I18n :src="i18n.ts._2fa.step1" tag="span">
<template #a>
<a href="https://authy.com/" rel="noopener" target="_blank" class="_link">Authy</a>
</template>
@@ -52,19 +52,19 @@
</template>
</I18n>
</li>
- <li>{{ $ts._2fa.step2 }}<br><img :src="data.qr"></li>
- <li>{{ $ts._2fa.step3 }}<br>
- <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ $ts.token }}</template></MkInput>
- <MkButton primary @click="submit">{{ $ts.done }}</MkButton>
+ <li>{{ i18n.ts._2fa.step2 }}<br><img :src="twoFactorData.qr"></li>
+ <li>{{ i18n.ts._2fa.step3 }}<br>
+ <MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false"><template #label>{{ i18n.ts.token }}</template></MkInput>
+ <MkButton primary @click="submit">{{ i18n.ts.done }}</MkButton>
</li>
</ol>
- <MkInfo>{{ $ts._2fa.step4 }}</MkInfo>
+ <MkInfo>{{ i18n.ts._2fa.step4 }}</MkInfo>
</div>
</div>
</template>
-<script lang="ts">
-import { defineComponent } from 'vue';
+<script lang="ts" setup>
+import { ref } from 'vue';
import { hostname } from '@/config';
import { byteify, hexify, stringify } from '@/scripts/2fa';
import MkButton from '@/components/ui/button.vue';
@@ -72,155 +72,144 @@ import MkInfo from '@/components/ui/info.vue';
import MkInput from '@/components/form/input.vue';
import MkSwitch from '@/components/form/switch.vue';
import * as os from '@/os';
-import * as symbols from '@/symbols';
+import { $i } from '@/account';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- MkButton, MkInfo, MkInput, MkSwitch
- },
+const twoFactorData = ref<any>(null);
+const supportsCredentials = ref(!!navigator.credentials);
+const usePasswordLessLogin = ref($i!.usePasswordLessLogin);
+const registration = ref<any>(null);
+const keyName = ref('');
+const token = ref(null);
- data() {
- return {
- data: null,
- supportsCredentials: !!navigator.credentials,
- usePasswordLessLogin: this.$i.usePasswordLessLogin,
- registration: null,
- keyName: '',
- token: null,
- };
- },
+function register() {
+ os.inputText({
+ title: i18n.ts.password,
+ type: 'password'
+ }).then(({ canceled, result: password }) => {
+ if (canceled) return;
+ os.api('i/2fa/register', {
+ password: password
+ }).then(data => {
+ twoFactorData.value = data;
+ });
+ });
+}
- methods: {
- register() {
- os.inputText({
- title: this.$ts.password,
- type: 'password'
- }).then(({ canceled, result: password }) => {
- if (canceled) return;
- os.api('i/2fa/register', {
- password: password
- }).then(data => {
- this.data = data;
- });
- });
- },
-
- unregister() {
- os.inputText({
- title: this.$ts.password,
- type: 'password'
- }).then(({ canceled, result: password }) => {
- if (canceled) return;
- os.api('i/2fa/unregister', {
- password: password
- }).then(() => {
- this.usePasswordLessLogin = false;
- this.updatePasswordLessLogin();
- }).then(() => {
- os.success();
- this.$i.twoFactorEnabled = false;
- });
- });
- },
+function unregister() {
+ os.inputText({
+ title: i18n.ts.password,
+ type: 'password'
+ }).then(({ canceled, result: password }) => {
+ if (canceled) return;
+ os.api('i/2fa/unregister', {
+ password: password
+ }).then(() => {
+ usePasswordLessLogin.value = false;
+ updatePasswordLessLogin();
+ }).then(() => {
+ os.success();
+ $i!.twoFactorEnabled = false;
+ });
+ });
+}
- submit() {
- os.api('i/2fa/done', {
- token: this.token
- }).then(() => {
- os.success();
- this.$i.twoFactorEnabled = true;
- }).catch(e => {
- os.alert({
- type: 'error',
- text: e
- });
- });
- },
+function submit() {
+ os.api('i/2fa/done', {
+ token: token.value
+ }).then(() => {
+ os.success();
+ $i!.twoFactorEnabled = true;
+ }).catch(e => {
+ os.alert({
+ type: 'error',
+ text: e
+ });
+ });
+}
- registerKey() {
- this.registration.saving = true;
- os.api('i/2fa/key-done', {
- password: this.registration.password,
- name: this.keyName,
- challengeId: this.registration.challengeId,
- // we convert each 16 bits to a string to serialise
- clientDataJSON: stringify(this.registration.credential.response.clientDataJSON),
- attestationObject: hexify(this.registration.credential.response.attestationObject)
- }).then(key => {
- this.registration = null;
- key.lastUsed = new Date();
- os.success();
- })
- },
+function registerKey() {
+ registration.value.saving = true;
+ os.api('i/2fa/key-done', {
+ password: registration.value.password,
+ name: keyName.value,
+ challengeId: registration.value.challengeId,
+ // we convert each 16 bits to a string to serialise
+ clientDataJSON: stringify(registration.value.credential.response.clientDataJSON),
+ attestationObject: hexify(registration.value.credential.response.attestationObject)
+ }).then(key => {
+ registration.value = null;
+ key.lastUsed = new Date();
+ os.success();
+ })
+}
- unregisterKey(key) {
- os.inputText({
- title: this.$ts.password,
- type: 'password'
- }).then(({ canceled, result: password }) => {
- if (canceled) return;
- return os.api('i/2fa/remove-key', {
- password,
- credentialId: key.id
- }).then(() => {
- this.usePasswordLessLogin = false;
- this.updatePasswordLessLogin();
- }).then(() => {
- os.success();
- });
- });
- },
+function unregisterKey(key) {
+ os.inputText({
+ title: i18n.ts.password,
+ type: 'password'
+ }).then(({ canceled, result: password }) => {
+ if (canceled) return;
+ return os.api('i/2fa/remove-key', {
+ password,
+ credentialId: key.id
+ }).then(() => {
+ usePasswordLessLogin.value = false;
+ updatePasswordLessLogin();
+ }).then(() => {
+ os.success();
+ });
+ });
+}
- addSecurityKey() {
- os.inputText({
- title: this.$ts.password,
- type: 'password'
- }).then(({ canceled, result: password }) => {
- if (canceled) return;
- os.api('i/2fa/register-key', {
- password
- }).then(registration => {
- this.registration = {
- password,
- challengeId: registration.challengeId,
- stage: 0,
- publicKeyOptions: {
- challenge: byteify(registration.challenge, 'base64'),
- rp: {
- id: hostname,
- name: 'Misskey'
- },
- user: {
- id: byteify(this.$i.id, 'ascii'),
- name: this.$i.username,
- displayName: this.$i.name,
- },
- pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
- timeout: 60000,
- attestation: 'direct'
- },
- saving: true
- };
- return navigator.credentials.create({
- publicKey: this.registration.publicKeyOptions
- });
- }).then(credential => {
- this.registration.credential = credential;
- this.registration.saving = false;
- this.registration.stage = 1;
- }).catch(err => {
- console.warn('Error while registering?', err);
- this.registration.error = err.message;
- this.registration.stage = -1;
- });
+function addSecurityKey() {
+ os.inputText({
+ title: i18n.ts.password,
+ type: 'password'
+ }).then(({ canceled, result: password }) => {
+ if (canceled) return;
+ os.api('i/2fa/register-key', {
+ password
+ }).then(reg => {
+ registration.value = {
+ password,
+ challengeId: reg!.challengeId,
+ stage: 0,
+ publicKeyOptions: {
+ challenge: byteify(reg!.challenge, 'base64'),
+ rp: {
+ id: hostname,
+ name: 'Misskey'
+ },
+ user: {
+ id: byteify($i!.id, 'ascii'),
+ name: $i!.username,
+ displayName: $i!.name,
+ },
+ pubKeyCredParams: [{ alg: -7, type: 'public-key' }],
+ timeout: 60000,
+ attestation: 'direct'
+ },
+ saving: true
+ };
+ return navigator.credentials.create({
+ publicKey: registration.value.publicKeyOptions
});
- },
+ }).then(credential => {
+ registration.value.credential = credential;
+ registration.value.saving = false;
+ registration.value.stage = 1;
+ }).catch(err => {
+ console.warn('Error while registering?', err);
+ registration.value.error = err.message;
+ registration.value.stage = -1;
+ });
+ });
+}
- updatePasswordLessLogin() {
- os.api('i/2fa/password-less', {
- value: !!this.usePasswordLessLogin
- });
- }
- }
-});
+async function updatePasswordLessLogin() {
+ await os.api('i/2fa/password-less', {
+ value: !!usePasswordLessLogin.value
+ });
+}
</script>