summaryrefslogtreecommitdiff
path: root/packages/client/src/components
diff options
context:
space:
mode:
authorAndreas Nedbal <github-bf215181b5140522137b3d4f6b73544a@desu.email>2022-05-19 13:28:08 +0200
committerGitHub <noreply@github.com>2022-05-19 20:28:08 +0900
commit1d9a4f68f472def57c259290cfacb7b7d238e490 (patch)
treef63520add50095632bfccd078eebc8ab81fc997a /packages/client/src/components
parentchore(deps): bump copy-props from 2.0.4 to 2.0.5 (#8709) (diff)
downloadmisskey-1d9a4f68f472def57c259290cfacb7b7d238e490.tar.gz
misskey-1d9a4f68f472def57c259290cfacb7b7d238e490.tar.bz2
misskey-1d9a4f68f472def57c259290cfacb7b7d238e490.zip
Refactor pleaseLogin to show a sign-in dialog (#8630)
* refactor(client): refactor pleaseLogin to show a sign-in dialog * Apply review suggestions from @Johann150 Co-authored-by: Johann150 <johann@qwertqwefsday.eu> Co-authored-by: Johann150 <johann@qwertqwefsday.eu> Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
Diffstat (limited to 'packages/client/src/components')
-rw-r--r--packages/client/src/components/signin-dialog.vue16
-rw-r--r--packages/client/src/components/signin.vue366
2 files changed, 193 insertions, 189 deletions
diff --git a/packages/client/src/components/signin-dialog.vue b/packages/client/src/components/signin-dialog.vue
index 5c2048e7b0..848b11fada 100644
--- a/packages/client/src/components/signin-dialog.vue
+++ b/packages/client/src/components/signin-dialog.vue
@@ -2,12 +2,12 @@
<XModalWindow ref="dialog"
:width="370"
:height="400"
- @close="dialog.close()"
+ @close="onClose"
@closed="emit('closed')"
>
<template #header>{{ $ts.login }}</template>
- <MkSignin :auto-set="autoSet" @login="onLogin"/>
+ <MkSignin :auto-set="autoSet" :message="message" @login="onLogin"/>
</XModalWindow>
</template>
@@ -18,17 +18,25 @@ import MkSignin from './signin.vue';
const props = withDefaults(defineProps<{
autoSet?: boolean;
+ message?: string,
}>(), {
autoSet: false,
+ message: ''
});
const emit = defineEmits<{
- (e: 'done'): void;
- (e: 'closed'): void;
+ (ev: 'done'): void;
+ (ev: 'closed'): void;
+ (ev: 'cancelled'): void;
}>();
const dialog = $ref<InstanceType<typeof XModalWindow>>();
+function onClose() {
+ emit('cancelled');
+ dialog.close();
+}
+
function onLogin(res) {
emit('done', res);
dialog.close();
diff --git a/packages/client/src/components/signin.vue b/packages/client/src/components/signin.vue
index d140e143d3..e3d92dc431 100644
--- a/packages/client/src/components/signin.vue
+++ b/packages/client/src/components/signin.vue
@@ -1,41 +1,44 @@
<template>
<form class="eppvobhk _monolithic_" :class="{ signing, totpLogin }" @submit.prevent="onSubmit">
<div class="auth _section _formRoot">
- <div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null }"></div>
+ <div v-show="withAvatar" class="avatar" :style="{ backgroundImage: user ? `url('${ user.avatarUrl }')` : null, marginBottom: message ? '1.5em' : null }"></div>
+ <MkInfo v-if="message">
+ {{ message }}
+ </MkInfo>
<div v-if="!totpLogin" class="normal-signin">
- <MkInput v-model="username" class="_formBlock" :placeholder="$ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
+ <MkInput v-model="username" class="_formBlock" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" spellcheck="false" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange">
<template #prefix>@</template>
<template #suffix>@{{ host }}</template>
</MkInput>
- <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="$ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password>
+ <MkInput v-if="!user || user && !user.usePasswordLessLogin" v-model="password" class="_formBlock" :placeholder="i18n.ts.password" type="password" :with-password-toggle="true" required data-cy-signin-password>
<template #prefix><i class="fas fa-lock"></i></template>
- <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ $ts.forgotPassword }}</button></template>
+ <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template>
</MkInput>
<MkCaptcha v-if="meta.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" class="_formBlock captcha" provider="hcaptcha" :sitekey="meta.hcaptchaSiteKey"/>
<MkCaptcha v-if="meta.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" class="_formBlock captcha" provider="recaptcha" :sitekey="meta.recaptchaSiteKey"/>
- <MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
+ <MkButton class="_formBlock" type="submit" primary :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
</div>
<div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }">
<div v-if="user && user.securityKeys" class="twofa-group tap-group">
- <p>{{ $ts.tapSecurityKey }}</p>
+ <p>{{ i18n.ts.tapSecurityKey }}</p>
<MkButton v-if="!queryingKey" @click="queryKey">
- {{ $ts.retry }}
+ {{ i18n.ts.retry }}
</MkButton>
</div>
<div v-if="user && user.securityKeys" class="or-hr">
- <p class="or-msg">{{ $ts.or }}</p>
+ <p class="or-msg">{{ i18n.ts.or }}</p>
</div>
<div class="twofa-group totp-group">
- <p style="margin-bottom:0;">{{ $ts.twoStepAuthentication }}</p>
+ <p style="margin-bottom:0;">{{ i18n.ts.twoStepAuthentication }}</p>
<MkInput v-if="user && user.usePasswordLessLogin" v-model="password" type="password" :with-password-toggle="true" required>
- <template #label>{{ $ts.password }}</template>
+ <template #label>{{ i18n.ts.password }}</template>
<template #prefix><i class="fas fa-lock"></i></template>
</MkInput>
<MkInput v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
- <template #label>{{ $ts.token }}</template>
+ <template #label>{{ i18n.ts.token }}</template>
<template #prefix><i class="fas fa-gavel"></i></template>
</MkInput>
- <MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? $ts.loggingIn : $ts.login }}</MkButton>
+ <MkButton type="submit" :disabled="signing" primary style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton>
</div>
</div>
</div>
@@ -47,199 +50,192 @@
</form>
</template>
-<script lang="ts">
-import { defineAsyncComponent, defineComponent } from 'vue';
+<script lang="ts" setup>
+import { defineAsyncComponent } from 'vue';
import { toUnicode } from 'punycode/';
import MkButton from '@/components/ui/button.vue';
import MkInput from '@/components/form/input.vue';
-import { apiUrl, host } from '@/config';
+import MkInfo from '@/components/ui/info.vue';
+import { apiUrl, host as configHost } from '@/config';
import { byteify, hexify } from '@/scripts/2fa';
import * as os from '@/os';
import { login } from '@/account';
import { showSuspendedDialog } from '../scripts/show-suspended-dialog';
+import { instance } from '@/instance';
+import { i18n } from '@/i18n';
-export default defineComponent({
- components: {
- MkButton,
- MkInput,
- MkCaptcha: defineAsyncComponent(() => import('./captcha.vue')),
- },
+const MkCaptcha = defineAsyncComponent(() => import('./captcha.vue'));
- props: {
- withAvatar: {
- type: Boolean,
- required: false,
- default: true
- },
- autoSet: {
- type: Boolean,
- required: false,
- default: false,
- }
- },
+let signing = $ref(false);
+let user = $ref(null);
+let username = $ref('');
+let password = $ref('');
+let token = $ref('');
+let host = $ref(toUnicode(configHost));
+let totpLogin = $ref(false);
+let credential = $ref(null);
+let challengeData = $ref(null);
+let queryingKey = $ref(false);
+let hCaptchaResponse = $ref(null);
+let reCaptchaResponse = $ref(null);
- emits: ['login'],
+const meta = $computed(() => instance);
- data() {
- return {
- signing: false,
- user: null,
- username: '',
- password: '',
- token: '',
- apiUrl,
- host: toUnicode(host),
- totpLogin: false,
- credential: null,
- challengeData: null,
- queryingKey: false,
- hCaptchaResponse: null,
- reCaptchaResponse: null,
- };
- },
+const emit = defineEmits<{
+ (ev: 'login', v: any): void;
+}>();
- computed: {
- meta() {
- return this.$instance;
- },
+const props = defineProps({
+ withAvatar: {
+ type: Boolean,
+ required: false,
+ default: true
},
+ autoSet: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ message: {
+ type: String,
+ required: false,
+ default: ''
+ }
+});
- methods: {
- onUsernameChange() {
- os.api('users/show', {
- username: this.username
- }).then(user => {
- this.user = user;
- }, () => {
- this.user = null;
- });
- },
-
- onLogin(res) {
- if (this.autoSet) {
- return login(res.i);
- } else {
- return;
- }
- },
-
- queryKey() {
- this.queryingKey = true;
- return navigator.credentials.get({
- publicKey: {
- challenge: byteify(this.challengeData.challenge, 'base64'),
- allowCredentials: this.challengeData.securityKeys.map(key => ({
- id: byteify(key.id, 'hex'),
- type: 'public-key',
- transports: ['usb', 'nfc', 'ble', 'internal']
- })),
- timeout: 60 * 1000
- }
- }).catch(() => {
- this.queryingKey = false;
- return Promise.reject(null);
- }).then(credential => {
- this.queryingKey = false;
- this.signing = true;
- return os.api('signin', {
- username: this.username,
- password: this.password,
- 'hcaptcha-response': this.hCaptchaResponse,
- 'g-recaptcha-response': this.reCaptchaResponse,
- signature: hexify(credential.response.signature),
- authenticatorData: hexify(credential.response.authenticatorData),
- clientDataJSON: hexify(credential.response.clientDataJSON),
- credentialId: credential.id,
- challengeId: this.challengeData.challengeId,
- });
- }).then(res => {
- this.$emit('login', res);
- return this.onLogin(res);
- }).catch(err => {
- if (err === null) return;
- os.alert({
- type: 'error',
- text: this.$ts.signinFailed
- });
- this.signing = false;
- });
- },
+function onUsernameChange() {
+ os.api('users/show', {
+ username: username
+ }).then(user => {
+ user = user;
+ }, () => {
+ user = null;
+ });
+}
- onSubmit() {
- this.signing = true;
- if (!this.totpLogin && this.user && this.user.twoFactorEnabled) {
- if (window.PublicKeyCredential && this.user.securityKeys) {
- os.api('signin', {
- username: this.username,
- password: this.password,
- 'hcaptcha-response': this.hCaptchaResponse,
- 'g-recaptcha-response': this.reCaptchaResponse,
- }).then(res => {
- this.totpLogin = true;
- this.signing = false;
- this.challengeData = res;
- return this.queryKey();
- }).catch(this.loginFailed);
- } else {
- this.totpLogin = true;
- this.signing = false;
- }
- } else {
- os.api('signin', {
- username: this.username,
- password: this.password,
- 'hcaptcha-response': this.hCaptchaResponse,
- 'g-recaptcha-response': this.reCaptchaResponse,
- token: this.user && this.user.twoFactorEnabled ? this.token : undefined,
- }).then(res => {
- this.$emit('login', res);
- this.onLogin(res);
- }).catch(this.loginFailed);
- }
- },
+function onLogin(res) {
+ if (props.autoSet) {
+ return login(res.i);
+ }
+}
- loginFailed(err) {
- switch (err.id) {
- case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
- os.alert({
- type: 'error',
- title: this.$ts.loginFailed,
- text: this.$ts.noSuchUser
- });
- break;
- }
- case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': {
- os.alert({
- type: 'error',
- title: this.$ts.loginFailed,
- text: this.$ts.incorrectPassword,
- });
- break;
- }
- case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
- showSuspendedDialog();
- break;
- }
- default: {
- os.alert({
- type: 'error',
- title: this.$ts.loginFailed,
- text: JSON.stringify(err)
- });
- }
- }
+function queryKey() {
+ queryingKey = true;
+ return navigator.credentials.get({
+ publicKey: {
+ challenge: byteify(challengeData.challenge, 'base64'),
+ allowCredentials: challengeData.securityKeys.map(key => ({
+ id: byteify(key.id, 'hex'),
+ type: 'public-key',
+ transports: ['usb', 'nfc', 'ble', 'internal']
+ })),
+ timeout: 60 * 1000
+ }
+ }).catch(() => {
+ queryingKey = false;
+ return Promise.reject(null);
+ }).then(credential => {
+ queryingKey = false;
+ signing = true;
+ return os.api('signin', {
+ username,
+ password,
+ signature: hexify(credential.response.signature),
+ authenticatorData: hexify(credential.response.authenticatorData),
+ clientDataJSON: hexify(credential.response.clientDataJSON),
+ credentialId: credential.id,
+ challengeId: challengeData.challengeId,
+ 'hcaptcha-response': hCaptchaResponse,
+ 'g-recaptcha-response': reCaptchaResponse,
+ });
+ }).then(res => {
+ emit('login', res);
+ return onLogin(res);
+ }).catch(err => {
+ if (err === null) return;
+ os.alert({
+ type: 'error',
+ text: i18n.ts.signinFailed
+ });
+ signing = false;
+ });
+}
- this.challengeData = null;
- this.totpLogin = false;
- this.signing = false;
- },
+function onSubmit() {
+ signing = true;
+ console.log('submit')
+ if (!totpLogin && user && user.twoFactorEnabled) {
+ if (window.PublicKeyCredential && user.securityKeys) {
+ os.api('signin', {
+ username,
+ password,
+ 'hcaptcha-response': hCaptchaResponse,
+ 'g-recaptcha-response': reCaptchaResponse,
+ }).then(res => {
+ totpLogin = true;
+ signing = false;
+ challengeData = res;
+ return queryKey();
+ }).catch(loginFailed);
+ } else {
+ totpLogin = true;
+ signing = false;
+ }
+ } else {
+ os.api('signin', {
+ username,
+ password,
+ 'hcaptcha-response': hCaptchaResponse,
+ 'g-recaptcha-response': reCaptchaResponse,
+ token: user && user.twoFactorEnabled ? token : undefined
+ }).then(res => {
+ emit('login', res);
+ onLogin(res);
+ }).catch(loginFailed);
+ }
+}
- resetPassword() {
- os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {
- }, 'closed');
+function loginFailed(err) {
+ switch (err.id) {
+ case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
+ os.alert({
+ type: 'error',
+ title: i18n.ts.loginFailed,
+ text: i18n.ts.noSuchUser
+ });
+ break;
+ }
+ case '932c904e-9460-45b7-9ce6-7ed33be7eb2c': {
+ os.alert({
+ type: 'error',
+ title: i18n.ts.loginFailed,
+ text: i18n.ts.incorrectPassword,
+ });
+ break;
+ }
+ case 'e03a5f46-d309-4865-9b69-56282d94e1eb': {
+ showSuspendedDialog();
+ break;
+ }
+ default: {
+ console.log(err)
+ os.alert({
+ type: 'error',
+ title: i18n.ts.loginFailed,
+ text: JSON.stringify(err)
+ });
}
}
-});
+
+ challengeData = null;
+ totpLogin = false;
+ signing = false;
+}
+
+function resetPassword() {
+ os.popup(defineAsyncComponent(() => import('@/components/forgot-password.vue')), {}, {
+ }, 'closed');
+}
</script>
<style lang="scss" scoped>