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> <ul>
<p class="id">{{ app.id }}</p> <li v-for="p in app.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li>
<p class="description">{{ app.description }}</p> </ul>
</div> </div>
<div class=""> <div>{{ i18n.t('_auth.shareAccess', { name: `${name} (${app.id})` }) }}</div>
<h2>{{ $ts._auth.permissionAsk }}</h2> <div :class="$style.buttons">
<ul> <MkButton inline @click="cancel">{{ i18n.ts.cancel }}</MkButton>
<li v-for="p in app.permission" :key="p">{{ $t(`_permissions.${p}`) }}</li> <MkButton inline primary @click="accept">{{ i18n.ts.accept }}</MkButton>
</ul> </div>
</div> </section>
<div class="">
<MkButton inline @click="cancel">{{ $ts.cancel }}</MkButton>
<MkButton inline primary @click="accept">{{ $ts.accept }}</MkButton>
</div>
</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'],
computed: {
name(): string {
const el = document.createElement('div');
el.textContent = this.app.name;
return el.innerHTML;
},
app(): any {
return this.session.app;
},
},
methods: {
cancel() {
os.api('auth/deny', {
token: this.session.token,
}).then(() => {
this.$emit('denied');
});
},
accept() { const emit = defineEmits<{
os.api('auth/accept', { (event: 'accepted'): void;
token: this.session.token, (event: 'denied'): void;
}).then(() => { }>();
this.$emit('accepted');
}); const app = $computed(() => props.session.app);
},
}, const name = $computed(() => {
const el = document.createElement('div');
el.textContent = app.name;
return el.innerHTML;
}); });
function cancel() {
os.api('auth/deny', {
token: props.session.token,
}).then(() => {
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>
<MkLoading/> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs" /></template>
</div> <MkSpacer :content-max="500">
<div v-else-if="$i"> <div v-if="state == 'fetch-session-error'">
<XForm <p>{{ i18n.ts.somethingHappened }}</p>
v-if="state == 'waiting'" </div>
ref="form" <div v-else-if="$i && !session">
class="form" <MkLoading />
:session="session" </div>
@denied="state = 'denied'" <div v-else-if="$i && session">
@accepted="accepted" <XForm
/> v-if="state == 'waiting'"
<div v-if="state == 'denied'" class="denied"> class="form"
<h1>{{ $ts._auth.denied }}</h1> :session="session"
</div> @denied="state = 'denied'"
<div v-if="state == 'accepted'" class="accepted"> @accepted="accepted"
<h1>{{ session.app.isAuthorized ? $t('already-authorized') : $ts.allowed }}</h1> />
<p v-if="session.app.callbackUrl">{{ $ts._auth.callback }}<MkEllipsis/></p> <div v-if="state == 'denied'">
<p v-if="!session.app.callbackUrl">{{ $ts._auth.pleaseGoBack }}</p> <h1>{{ i18n.ts._auth.denied }}</h1>
</div> </div>
<div v-if="state == 'fetch-session-error'" class="error"> <div v-if="state == 'accepted' && session">
<p>{{ $ts.somethingHappened }}</p> <h1>{{ session.app.isAuthorized ? $t('already-authorized') : i18n.ts.allowed }}</h1>
</div> <p v-if="session.app.callbackUrl">{{ i18n.ts._auth.callback }}
</div> <MkEllipsis />
<div v-else class="signin"> </p>
<MkSignin @login="onLogin"/> <p v-if="!session.app.callbackUrl">{{ i18n.ts._auth.pleaseGoBack }}</p>
</div> </div>
</div>
<div v-else>
<p :class="$style.loginMessage">{{ i18n.ts._auth.pleaseLogin }}</p>
<MkSignin @login="onLogin" />
</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 => {
this.session = session;
this.fetching = false;
// function accepted() {
if (this.session.app.isAuthorized) { state = 'accepted';
os.api('auth/accept', { if (session && session.app.callbackUrl) {
token: this.session.token, const url = new URL(session.app.callbackUrl);
}).then(() => { if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url');
this.accepted(); location.href = `${session.app.callbackUrl}?token=${session.token}`;
}); }
} else { }
this.state = 'waiting';
} function onLogin(res) {
}).catch(error => { login(res.i);
this.state = 'fetch-session-error'; }
this.fetching = false;
onMounted(async () => {
if (!$i) return;
try {
session = await os.api('auth/session/show', {
token: props.token,
}); });
},
methods: { //
accepted() { if (session.app.isAuthorized) {
this.state = 'accepted'; await os.api('auth/accept', {
if (this.session.app.callbackUrl) { token: session.token,
const url = new URL(this.session.app.callbackUrl); });
if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url'); accepted();
location.href = `${this.session.app.callbackUrl}?token=${this.session.token}`; } else {
} state = 'waiting';
}, onLogin(res) { }
login(res.i); } catch (e) {
}, state = 'fetch-session-error';
}, }
});
const headerActions = $computed(() => []);
const headerTabs = $computed(() => []);
definePageMetadata({
title: i18n.ts._auth.shareAccessTitle,
icon: 'ti ti-apps',
}); });
</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>
<MkSpacer :content-max="800"> <MkStickyContainer>
<div v-if="$i"> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs" /></template>
<div v-if="state == 'waiting'" class="waiting"> <MkSpacer :content-max="800">
<div class=""> <div v-if="$i">
<div v-if="state == 'waiting'">
<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="$style.buttons">
<div class=""> <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>
<div v-else class="signin"> <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>