Implement a better authorization page
Fix default profile not making the button active Fix it properly Fix default profile Fix invalid option value
This commit is contained in:
parent
69dd3272b2
commit
a029fa4b48
@ -113,16 +113,13 @@ public class UserController : ControllerBase
|
||||
p.ProfileUrl,
|
||||
Clients = p.Clients.Select(c => new
|
||||
{
|
||||
c.Client.Id,
|
||||
c.Scope,
|
||||
c.FirstSeen,
|
||||
c.LastSeen,
|
||||
Client = new
|
||||
{
|
||||
c.Client.Id,
|
||||
c.Client.ClientId,
|
||||
c.Client.RedirectUri,
|
||||
c.Client.Type
|
||||
}
|
||||
c.Client.ClientId,
|
||||
c.Client.RedirectUri,
|
||||
c.Client.Type
|
||||
})
|
||||
})
|
||||
.ToListAsync();
|
||||
|
3364
Web/package-lock.json
generated
3364
Web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -2,9 +2,20 @@ import { ref } from "vue";
|
||||
import { defineStore } from "pinia";
|
||||
import { get, send } from "@/utils";
|
||||
|
||||
interface UserClients {
|
||||
id: number;
|
||||
scope?: string;
|
||||
firstSeen: string;
|
||||
lastSeen: string;
|
||||
clientId: string;
|
||||
redirectUri: string;
|
||||
type: number; // TODO: enum
|
||||
}
|
||||
|
||||
interface UserProfiles {
|
||||
id: number;
|
||||
profileUrl: string;
|
||||
clients: UserClients[];
|
||||
}
|
||||
|
||||
interface UserInfo {
|
||||
|
@ -26,11 +26,6 @@ export async function send<Treceived, Tsent = any | undefined>(
|
||||
method: "POST" | "PUT" | "PATCH" | "DELETE" | "GET" = "POST",
|
||||
headers?: Record<string, string>
|
||||
): Promise<Treceived> {
|
||||
if (!url.startsWith("htt") && window.location.port == "5173")
|
||||
url =
|
||||
"http://localhost:3000" +
|
||||
(url.startsWith("/") ? "" : window.location.pathname + "/") +
|
||||
url;
|
||||
if (fetch) {
|
||||
return fetch(url, {
|
||||
method: method,
|
||||
|
@ -1,55 +1,14 @@
|
||||
<template>
|
||||
<main>
|
||||
<h2>
|
||||
IndieAuth login request for: {{ state.request?.clientId }} ({{
|
||||
state.request?.responseType
|
||||
}})
|
||||
</h2>
|
||||
<form @submit.prevent="sendAuthorize">
|
||||
<div>
|
||||
<div v-if="state.request?.scope">
|
||||
Scopes requested:
|
||||
<ul>
|
||||
<li v-for="scope in state.request.scope.split(' ')">
|
||||
{{ scope }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else>No scopes requested</div>
|
||||
</div>
|
||||
<label>
|
||||
<span>Profile ID</span>
|
||||
<input
|
||||
class="border border-slate-500"
|
||||
placeholder="Profile ID"
|
||||
v-model="state.profileId"
|
||||
/>
|
||||
</label>
|
||||
<label v-if="!hasPkce">
|
||||
<input v-model="state.ignoreMissingPkce" type="checkbox" />
|
||||
Ignore missing PKCE
|
||||
</label>
|
||||
<div v-if="state.errorText">{{ state.errorText }}</div>
|
||||
<button
|
||||
type="submit"
|
||||
:disabled="!hasPkce && !state.ignoreMissingPkce"
|
||||
>
|
||||
Authorize
|
||||
</button>
|
||||
<a v-if="state.redirectUri" :href="state.redirectUri">
|
||||
Client redirect page
|
||||
</a>
|
||||
</form>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import TailwindButton from "@/components/TailwindButton.vue";
|
||||
import useAuthStore from "@/stores/auth";
|
||||
import { send } from "@/utils";
|
||||
import { computed } from "@vue/reactivity";
|
||||
import { onMounted, ref } from "vue";
|
||||
|
||||
const auth = useAuthStore();
|
||||
|
||||
const state = ref({
|
||||
profileId: "",
|
||||
profileId: null as number | null,
|
||||
errorText: "",
|
||||
request: null as AuthRequest | null,
|
||||
redirectUri: "",
|
||||
@ -67,7 +26,8 @@ interface AuthRequest {
|
||||
me?: string;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
onMounted(async () => {
|
||||
await auth.ready;
|
||||
const paramData = {} as { [idx: string]: string };
|
||||
for (const iterator of new URLSearchParams(window.location.search)) {
|
||||
paramData[
|
||||
@ -75,6 +35,9 @@ onMounted(() => {
|
||||
] = iterator[1];
|
||||
}
|
||||
state.value.request = paramData as any as AuthRequest;
|
||||
// Plain default
|
||||
if (!state.value.request.responseType)
|
||||
state.value.request.responseType = "code";
|
||||
});
|
||||
|
||||
const hasPkce = computed(
|
||||
@ -83,6 +46,16 @@ const hasPkce = computed(
|
||||
!!state.value.request.codeChallengeMethod
|
||||
);
|
||||
|
||||
const defaultProfile = computed(() => auth.userInfo?.profiles[0]);
|
||||
|
||||
const validRequest = computed(
|
||||
() =>
|
||||
state.value.request &&
|
||||
state.value.request.clientId &&
|
||||
(state.value.profileId || defaultProfile.value) &&
|
||||
(hasPkce.value || state.value.ignoreMissingPkce)
|
||||
);
|
||||
|
||||
async function sendAuthorize() {
|
||||
if (!hasPkce.value && !state.value.ignoreMissingPkce) return;
|
||||
|
||||
@ -90,6 +63,11 @@ async function sendAuthorize() {
|
||||
|
||||
if (!request) return;
|
||||
|
||||
if (!request.responseType) {
|
||||
state.value.errorText = "No response type specified";
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!request.clientId ||
|
||||
!request.redirectUri ||
|
||||
@ -104,7 +82,7 @@ async function sendAuthorize() {
|
||||
"/api/indieauth/request",
|
||||
{
|
||||
...request,
|
||||
profileId: +state.value.profileId,
|
||||
profileId: state.value.profileId ?? defaultProfile.value?.id,
|
||||
}
|
||||
);
|
||||
state.value.redirectUri = result.redirectUri;
|
||||
@ -129,3 +107,70 @@ async function sendAuthorize() {
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="divide-y bg-zinc-800 rounded">
|
||||
<div class="py-2 px-4">
|
||||
<h2 class="text-2xl">IndieAuth login request</h2>
|
||||
<div v-if="!state.request?.clientId" class="text-red-500">
|
||||
No client ID!
|
||||
</div>
|
||||
<div v-else>
|
||||
{{ state.request?.clientId }}
|
||||
({{ state.request?.responseType }})
|
||||
</div>
|
||||
</div>
|
||||
<form @submit.prevent="sendAuthorize" class="flex flex-col px-8 py-2">
|
||||
<div class="mb-3">
|
||||
<div v-if="state.request?.scope">
|
||||
Scopes requested:
|
||||
<ul>
|
||||
<li v-for="scope in state.request.scope.split(' ')">
|
||||
{{ scope }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-else>No scopes requested</div>
|
||||
</div>
|
||||
<label class="mb-3">
|
||||
<span>Profile ID: </span>
|
||||
<select
|
||||
class="border px-3 py-1 border-slate-500 text-slate-200"
|
||||
placeholder="Profile ID"
|
||||
v-model="state.profileId"
|
||||
>
|
||||
<option :value="null">
|
||||
<template v-if="defaultProfile">
|
||||
{{ defaultProfile?.profileUrl }} (Default)
|
||||
</template>
|
||||
<template v-else>No profiles</template>
|
||||
</option>
|
||||
<template v-for="profile in auth.userInfo?.profiles">
|
||||
<option
|
||||
v-if="profile != defaultProfile"
|
||||
:value="profile.id"
|
||||
>
|
||||
{{ profile.profileUrl }}
|
||||
</option>
|
||||
</template>
|
||||
</select>
|
||||
</label>
|
||||
<label class="mb-3" v-if="!hasPkce">
|
||||
<input v-model="state.ignoreMissingPkce" type="checkbox" />
|
||||
Ignore missing PKCE
|
||||
</label>
|
||||
<div v-if="state.errorText">{{ state.errorText }}</div>
|
||||
<TailwindButton
|
||||
custom-class="rounded bg-zinc-900 border-zinc-600 disabled:bg-zinc-800 disabled:text-zinc-600 disabled:border-zinc-700 hover:bg-zinc-700 hover:border-zinc-600"
|
||||
class="mx-3"
|
||||
type="submit"
|
||||
:disabled="!validRequest"
|
||||
>
|
||||
Authorize
|
||||
</TailwindButton>
|
||||
<a v-if="state.redirectUri" :href="state.redirectUri">
|
||||
Client redirect page
|
||||
</a>
|
||||
</form>
|
||||
</main>
|
||||
</template>
|
||||
|
@ -1,11 +1,36 @@
|
||||
<script setup lang="ts">
|
||||
import TailwindButton from "@/components/TailwindButton.vue";
|
||||
import useAuthStore from "@/stores/auth";
|
||||
import { onMounted } from "vue";
|
||||
|
||||
const auth = useAuthStore();
|
||||
|
||||
onMounted(async () => {
|
||||
await auth.ready;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main class="flex flex-col w-80">
|
||||
<h2 class="mb-2 text-center text-xl">Home</h2>
|
||||
<div>TODO</div>
|
||||
<div v-if="!auth.userInfo" class="m-2">Loading...</div>
|
||||
<div v-else class="m-2">
|
||||
<div>
|
||||
<span class="text-zinc-500">Logged in as:</span>
|
||||
{{ auth.userInfo.username }}
|
||||
</div>
|
||||
<div class="my-4">
|
||||
<div v-if="auth.userInfo.profiles.length == 0">
|
||||
No profiles registered
|
||||
</div>
|
||||
<div
|
||||
v-for="profile in auth.userInfo.profiles"
|
||||
class="bg-zinc-600 text-zinc-200 py-3 px-4 first:rounded-t-lg last:rounded-b-lg cursor-pointer transition-colors focus:outline outline-2 outline-offset-2"
|
||||
>
|
||||
{{ profile.profileUrl }} - ID: {{ profile.id }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<tailwind-button
|
||||
title="This will erase all local data!"
|
||||
to="logout"
|
||||
|
@ -28,4 +28,13 @@ export default defineConfig({
|
||||
"@": fileURLToPath(new URL("./src", import.meta.url)),
|
||||
},
|
||||
},
|
||||
server: {
|
||||
port: 5183,
|
||||
proxy: {
|
||||
"/api": {
|
||||
target: "http://localhost:3000/",
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user