190 lines
6.1 KiB
Vue
190 lines
6.1 KiB
Vue
<template>
|
|
<div
|
|
:class='[
|
|
"w-full", "h-full", "pt-10",
|
|
"flex", "justify-center",
|
|
]'
|
|
>
|
|
<div :class='[
|
|
"w-130", "h-fit", "flex", "flex-col", "p-6",
|
|
"bg-white", "text-black",
|
|
"dark:bg-neutral-600", "dark:text-white",
|
|
"shadow-sm", "rounded-2xl",
|
|
]'>
|
|
<h1 class="font-bold">サインイン</h1>
|
|
<SmallText v-html="
|
|
`uwuzu ${sprtVerTxt}に対応しています。<br />
|
|
ただし、最新版でない場合一部の機能が制限される可能性があります。`
|
|
"/>
|
|
|
|
<hr class="my-3" />
|
|
|
|
<form class="flex flex-col gap-2" @submit="onSubmit">
|
|
<div class="flex flex-col">
|
|
<label for="origin">オリジン(必須)</label>
|
|
<InputText
|
|
type="url"
|
|
placeholder="https://uwuzu.net"
|
|
id="origin"
|
|
v-model="origin"
|
|
:disabled="isSubmitting"
|
|
/>
|
|
<SmallText>
|
|
サインインするサーバーのオリジンを入力してください。
|
|
</SmallText>
|
|
</div>
|
|
|
|
<hr class="w-[90%] m-auto" />
|
|
|
|
<details>
|
|
<summary class="select-none cursor-pointer">その他のオプション</summary>
|
|
<div class="flex flex-col mt-1">
|
|
<label for="token">APIトークン(任意)</label>
|
|
<InputText
|
|
type="password"
|
|
placeholder="ABCD123456789"
|
|
id="token"
|
|
v-model="token"
|
|
:disabled="isSubmitting"
|
|
/>
|
|
<div class="text-yellow-200">
|
|
通常は必要ありません。
|
|
</div>
|
|
<SmallText>
|
|
サインインするアカウントのAPIトークンを入力してください。権限の確認は行いません。<br />
|
|
以下の権限が必要です。
|
|
</SmallText>
|
|
<ul>
|
|
<li class="list-disc list-inside" v-for="scope in requiredScopes">
|
|
{{ scope }}
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</details>
|
|
|
|
<Button
|
|
type="submit"
|
|
class="m-auto"
|
|
:disabled="isSubmitting"
|
|
>
|
|
<span v-if="isSubmitting">サインイン中...</span>
|
|
<span v-else>サインイン</span>
|
|
</Button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts" setup>
|
|
import { useHead } from "@vueuse/head";
|
|
import { ref } from "vue";
|
|
import { z } from "zod/v3";
|
|
import { v4 as UUID } from "uuid";
|
|
import { useRouter } from "vue-router";
|
|
import Database from "@/lib/db";
|
|
import { isSignin } from "@/lib/account";
|
|
import useAPI from "@/lib/api";
|
|
import { isVersionAvailable } from "@/lib/version";
|
|
import SmallText from "@/components/SmallText.vue";
|
|
import InputText from "@/components/InputText.vue";
|
|
import Button from "@/components/Button.vue";
|
|
|
|
useHead({
|
|
title: "サインイン | Clean Follow uwuzu",
|
|
});
|
|
|
|
if (await isSignin(new Database())) {
|
|
useRouter().replace("/");
|
|
}
|
|
|
|
const emit = defineEmits(["failed"]);
|
|
const sprtVer = __CONFIG.uwuzu.supportedVersion;
|
|
const sprtVerTxt = `v${sprtVer.min}${
|
|
sprtVer.min !== sprtVer.max
|
|
? `からv${sprtVer.max}`
|
|
: ''
|
|
}`;
|
|
const requiredScopes = __CONFIG.uwuzu.requiredScopes;
|
|
const origin = ref<string>("");
|
|
const token = ref<string>("");
|
|
const isSubmitting = ref<boolean>(false);
|
|
|
|
const onSubmit = async (e: Event) => {
|
|
e.preventDefault();
|
|
isSubmitting.value = true;
|
|
|
|
const result = z.object({
|
|
origin: z.string({
|
|
invalid_type_error: "オリジンが文字列ではありません",
|
|
})
|
|
.min(1, "オリジンが入力されていません")
|
|
.refine(value => {
|
|
if (value.length === 0)
|
|
return true;
|
|
|
|
try {
|
|
const url = new URL(value);
|
|
return url.origin === value;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}, "オリジンが有効ではありません"),
|
|
token: z.string({
|
|
invalid_type_error: "APIトークンが文字列ではありません",
|
|
})
|
|
.min(1, "APIトークンが入力されていません")
|
|
.length(64, "APIトークンは64文字です")
|
|
.or(z.literal("")),
|
|
}).safeParse({
|
|
origin: origin.value,
|
|
token: token.value,
|
|
});
|
|
|
|
if (!result.success) {
|
|
emit("failed", result.error.errors.map(err => err.message).join("\n"));
|
|
return;
|
|
}
|
|
|
|
const serverinfo = await useAPI(result.data.origin, "/serverinfo-api", {
|
|
method: "POST",
|
|
cache: "no-store",
|
|
});
|
|
|
|
if (serverinfo.software.name !== "uwuzu") {
|
|
throw new Error("サーバーがuwuzuではありません。");
|
|
}
|
|
|
|
if (!isVersionAvailable({
|
|
current: serverinfo.software.version,
|
|
min: sprtVer.min,
|
|
max: sprtVer.max,
|
|
})) {
|
|
throw "サーバーのバージョンが対象外です。";
|
|
}
|
|
|
|
if (result.data.token.length === 64) {
|
|
const callback = new URL("/signin/callback", window.location.origin);
|
|
callback.searchParams.set("type", "token");
|
|
callback.searchParams.set("origin", result.data.origin);
|
|
callback.searchParams.set("session", result.data.token);
|
|
window.location.href = callback.toString();
|
|
return;
|
|
}
|
|
|
|
const session = UUID();
|
|
const callback = new URL("/signin/callback", window.location.origin);
|
|
callback.searchParams.set("type", "auth");
|
|
callback.searchParams.set("origin", result.data.origin);
|
|
callback.searchParams.set("session", session);
|
|
const authURL = new URL("/api/auth", result.data.origin);
|
|
authURL.searchParams.set("session", session);
|
|
authURL.searchParams.set("scope", requiredScopes.join(","));
|
|
authURL.searchParams.set("callback", callback.toString());
|
|
authURL.searchParams.set("client", "Clean Follow uwuzu");
|
|
authURL.searchParams.set("about", "uwuzu向けのフォロー整理アプリです。");
|
|
authURL.searchParams.set("icon", new URL("/cfu.svg", window.location.origin).toString());
|
|
window.location.href = authURL.toString();
|
|
return;
|
|
};
|
|
</script>
|