First Commit(なのに未完成)
This commit is contained in:
@@ -0,0 +1,189 @@
|
||||
<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>
|
||||
Reference in New Issue
Block a user