enhance(client): /authおよびMiAuthのUIをブラッシュアップ

Fix #9742
This commit is contained in:
tamaina 2023-02-09 17:18:27 +00:00
parent 686a709e87
commit ca0d53ec5d
5 changed files with 207 additions and 160 deletions

View File

@ -8,6 +8,15 @@
You should also include the user name that made the change. You should also include the user name that made the change.
--> -->
## 13.x.x (unreleased)
### Improvements
- 非ログイン時にMiAuthを踏んだ際にMiAuthであることを表示する
- /auth/のUIをアップデート
### Bugfixes
-
## 13.5.4 (2023/02/09) ## 13.5.4 (2023/02/09)
### Improvements ### Improvements

View File

@ -1628,12 +1628,14 @@ _permissions:
"write:gallery-likes": "ギャラリーのいいねを操作する" "write:gallery-likes": "ギャラリーのいいねを操作する"
_auth: _auth:
shareAccessTitle: "アプリへのアクセス許可"
shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?" shareAccess: "「{name}」がアカウントにアクセスすることを許可しますか?"
shareAccessAsk: "アカウントへのアクセスを許可しますか?" shareAccessAsk: "アカウントへのアクセスを許可しますか?"
permissionAsk: "このアプリは次の権限を要求しています" permissionAsk: "このアプリは次の権限を要求しています"
pleaseGoBack: "アプリケーションに戻ってやっていってください" pleaseGoBack: "アプリケーションに戻ってやっていってください"
callback: "アプリケーションに戻っています" callback: "アプリケーションに戻っています"
denied: "アクセスを拒否しました" denied: "アクセスを拒否しました"
pleaseLogin: "アプリケーションにアクセス許可を与えるには、ログインが必要です。"
_antennaSources: _antennaSources:
all: "全てのノート" all: "全てのノート"

View File

@ -1,60 +1,66 @@
<template> <template>
<section class=""> <section>
<div class="">{{ $t('_auth.shareAccess', { name: app.name }) }}</div> <div v-if="app.permission.length > 0">
<div class=""> <p>{{ i18n.ts._auth.permissionAsk }}</p>
<h2>{{ app.name }}</h2>
<p class="id">{{ app.id }}</p>
<p class="description">{{ app.description }}</p>
</div>
<div class="">
<h2>{{ $ts._auth.permissionAsk }}</h2>
<ul> <ul>
<li v-for="p in app.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li> <li v-for="p in app.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li>
</ul> </ul>
</div> </div>
<div class=""> <div>{{ i18n.t('_auth.shareAccess', { name: `${name} (${app.id})` }) }}</div>
<MkButton inline @click="cancel">{{ $ts.cancel }}</MkButton> <div :class="$style.buttons">
<MkButton inline primary @click="accept">{{ $ts.accept }}</MkButton> <MkButton inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
<MkButton inline primary @click="accept">{{ i18n.ts.accept }}</MkButton>
</div> </div>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { } from 'vue';
import MkButton from '@/components/MkButton.vue'; import MkButton from '@/components/MkButton.vue';
import * as os from '@/os'; import * as os from '@/os';
import { i18n } from '@/i18n';
import { AuthSession } from 'misskey-js/built/entities';
export default defineComponent({ const props = defineProps<{
components: { session: AuthSession;
MkButton, }>();
},
props: ['session'], const emit = defineEmits<{
computed: { (event: 'accepted'): void;
name(): string { (event: 'denied'): void;
}>();
const app = $computed(() => props.session.app);
const name = $computed(() => {
const el = document.createElement('div'); const el = document.createElement('div');
el.textContent = this.app.name; el.textContent = app.name;
return el.innerHTML; return el.innerHTML;
},
app(): any {
return this.session.app;
},
},
methods: {
cancel() {
os.api('auth/deny', {
token: this.session.token,
}).then(() => {
this.$emit('denied');
}); });
},
accept() { function cancel() {
os.api('auth/accept', { os.api('auth/deny', {
token: this.session.token, token: props.session.token,
}).then(() => { }).then(() => {
this.$emit('accepted'); emit('denied');
}); });
}, }
},
function accept() {
os.api('auth/accept', {
token: props.session.token,
}).then(() => {
emit('accepted');
}); });
}
</script> </script>
<style lang="scss" module>
.buttons {
margin-top: 16px;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
</style>

View File

@ -1,93 +1,105 @@
<template> <template>
<div v-if="$i && fetching" class=""> <MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs" /></template>
<MkSpacer :content-max="500">
<div v-if="state == 'fetch-session-error'">
<p>{{ i18n.ts.somethingHappened }}</p>
</div>
<div v-else-if="$i && !session">
<MkLoading /> <MkLoading />
</div> </div>
<div v-else-if="$i"> <div v-else-if="$i && session">
<XForm <XForm
v-if="state == 'waiting'" v-if="state == 'waiting'"
ref="form"
class="form" class="form"
:session="session" :session="session"
@denied="state = 'denied'" @denied="state = 'denied'"
@accepted="accepted" @accepted="accepted"
/> />
<div v-if="state == 'denied'" class="denied"> <div v-if="state == 'denied'">
<h1>{{ $ts._auth.denied }}</h1> <h1>{{ i18n.ts._auth.denied }}</h1>
</div> </div>
<div v-if="state == 'accepted'" class="accepted"> <div v-if="state == 'accepted' && session">
<h1>{{ session.app.isAuthorized ? $t('already-authorized') : $ts.allowed }}</h1> <h1>{{ session.app.isAuthorized ? $t('already-authorized') : i18n.ts.allowed }}</h1>
<p v-if="session.app.callbackUrl">{{ $ts._auth.callback }}<MkEllipsis/></p> <p v-if="session.app.callbackUrl">{{ i18n.ts._auth.callback }}
<p v-if="!session.app.callbackUrl">{{ $ts._auth.pleaseGoBack }}</p> <MkEllipsis />
</div> </p>
<div v-if="state == 'fetch-session-error'" class="error"> <p v-if="!session.app.callbackUrl">{{ i18n.ts._auth.pleaseGoBack }}</p>
<p>{{ $ts.somethingHappened }}</p>
</div> </div>
</div> </div>
<div v-else class="signin"> <div v-else>
<p :class="$style.loginMessage">{{ i18n.ts._auth.pleaseLogin }}</p>
<MkSignin @login="onLogin" /> <MkSignin @login="onLogin" />
</div> </div>
</MkSpacer>
</MkStickyContainer>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent } from 'vue'; import { onMounted } from 'vue';
import XForm from './auth.form.vue'; import XForm from './auth.form.vue';
import MkSignin from '@/components/MkSignin.vue'; import MkSignin from '@/components/MkSignin.vue';
import * as os from '@/os'; import * as os from '@/os';
import { login } from '@/account'; import { $i, login } from '@/account';
import { definePageMetadata } from '@/scripts/page-metadata';
import { AuthSession } from 'misskey-js/built/entities';
import { i18n } from '@/i18n';
export default defineComponent({ const props = defineProps<{
components: { token: string;
XForm, }>();
MkSignin,
},
props: ['token'],
data() {
return {
state: null,
session: null,
fetching: true,
};
},
mounted() {
if (!this.$i) return;
// Fetch session let state = $ref<'waiting' | 'accepted' | 'fetch-session-error' | 'denied' | null>(null);
os.api('auth/session/show', { let session = $ref<AuthSession | null>(null);
token: this.token,
}).then(session => { function accepted() {
this.session = session; state = 'accepted';
this.fetching = false; if (session && session.app.callbackUrl) {
const url = new URL(session.app.callbackUrl);
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
location.href = `${session.app.callbackUrl}?token=${session.token}`;
}
}
function onLogin(res) {
login(res.i);
}
onMounted(async () => {
if (!$i) return;
try {
session = await os.api('auth/session/show', {
token: props.token,
});
// //
if (this.session.app.isAuthorized) { if (session.app.isAuthorized) {
os.api('auth/accept', { await os.api('auth/accept', {
token: this.session.token, token: session.token,
}).then(() => {
this.accepted();
}); });
accepted();
} else { } else {
this.state = 'waiting'; state = 'waiting';
}
} catch (e) {
state = 'fetch-session-error';
} }
}).catch(error => {
this.state = 'fetch-session-error';
this.fetching = false;
}); });
},
methods: { const headerActions = $computed(() => []);
accepted() {
this.state = 'accepted'; const headerTabs = $computed(() => []);
if (this.session.app.callbackUrl) {
const url = new URL(this.session.app.callbackUrl); definePageMetadata({
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url'); title: i18n.ts._auth.shareAccessTitle,
location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`; icon: 'ti ti-apps',
}
}, onLogin(res) {
login(res.i);
},
},
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.loginMessage {
text-align: center;
margin: 8px 0 24px;
}
</style> </style>

View File

@ -1,41 +1,39 @@
<template> <template>
<MkStickyContainer>
<template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs" /></template>
<MkSpacer :content-max="800"> <MkSpacer :content-max="800">
<div v-if="$i"> <div v-if="$i">
<div v-if="state == 'waiting'" class="waiting"> <div v-if="state == 'waiting'">
<div class="">
<MkLoading/> <MkLoading/>
</div> </div>
</div> <div v-if="state == 'denied'">
<div v-if="state == 'denied'" class="denied">
<div class="">
<p>{{ i18n.ts._auth.denied }}</p> <p>{{ i18n.ts._auth.denied }}</p>
</div> </div>
</div>
<div v-else-if="state == 'accepted'" class="accepted"> <div v-else-if="state == 'accepted'" class="accepted">
<div class="">
<p v-if="callback">{{ i18n.ts._auth.callback }}<MkEllipsis/></p> <p v-if="callback">{{ i18n.ts._auth.callback }}<MkEllipsis/></p>
<p v-else>{{ i18n.ts._auth.pleaseGoBack }}</p> <p v-else>{{ i18n.ts._auth.pleaseGoBack }}</p>
</div> </div>
</div> <div v-else>
<div v-else class=""> <div v-if="name">{{ $t('_auth.shareAccess', { name: name }) }}</div>
<div v-if="name" class="">{{ $t('_auth.shareAccess', { name: name }) }}</div> <div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div>
<div v-else class="">{{ i18n.ts._auth.shareAccessAsk }}</div> <div v-if="_permissions.length > 0">
<div class="">
<p>{{ i18n.ts._auth.permissionAsk }}</p> <p>{{ i18n.ts._auth.permissionAsk }}</p>
<ul> <ul>
<li v-for="p in _permissions" :key="p">{{ $t(`_permissions.${p}`) }}</li> <li v-for="p in _permissions" :key="p">{{ $t(`_permissions.${p}`) }}</li>
</ul> </ul>
</div> </div>
<div class=""> <div :class="$style.buttons">
<MkButton inline @click="deny">{{ i18n.ts.cancel }}</MkButton> <MkButton inline @click="deny">{{ i18n.ts.cancel }}</MkButton>
<MkButton inline primary @click="accept">{{ i18n.ts.accept }}</MkButton> <MkButton inline primary @click="accept">{{ i18n.ts.accept }}</MkButton>
</div> </div>
</div> </div>
</div> </div>
<div v-else class="signin"> <div v-else>
<p :class="$style.loginMessage">{{ i18n.ts._auth.pleaseLogin }}</p>
<MkSignin @login="onLogin"/> <MkSignin @login="onLogin"/>
</div> </div>
</MkSpacer> </MkSpacer>
</MkStickyContainer>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -45,6 +43,7 @@ import MkButton from '@/components/MkButton.vue';
import * as os from '@/os'; import * as os from '@/os';
import { $i, login } from '@/account'; import { $i, login } from '@/account';
import { i18n } from '@/i18n'; import { i18n } from '@/i18n';
import { definePageMetadata } from '@/scripts/page-metadata';
const props = defineProps<{ const props = defineProps<{
session: string; session: string;
@ -54,7 +53,7 @@ const props = defineProps<{
permission: string; // permission: string; //
}>(); }>();
const _permissions = props.permission.split(','); const _permissions = props.permission ? props.permission.split(',') : [];
let state = $ref<string | null>(null); let state = $ref<string | null>(null);
@ -83,8 +82,27 @@ function deny(): void {
function onLogin(res): void { function onLogin(res): void {
login(res.i); login(res.i);
} }
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: 'MiAuth',
icon: 'ti ti-apps',
});
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.buttons {
margin-top: 16px;
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.loginMessage {
text-align: center;
margin: 8px 0 24px;
}
</style> </style>