Merge remote-tracking branch 'origin/develop' into more-fixes

This commit is contained in:
Huskydog9988
2025-05-15 13:38:46 -04:00
22 changed files with 331 additions and 161 deletions

View File

@ -6,6 +6,7 @@
# Drop
[![Website](https://img.shields.io/badge/website-000000?style=for-the-badge&logo=About.me&logoColor=white)](https://droposs.org)
[![Static Badge](https://img.shields.io/badge/FORUM-blue?style=for-the-badge)](https://forum.droposs.org)
[![GitHub License](https://img.shields.io/badge/AGPL--3.0-red?style=for-the-badge)](LICENSE)
[![Discord](https://img.shields.io/badge/Discord-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/ACq4qZp4a9)
[![Open Collective](https://img.shields.io/badge/OpenCollective-1F87FF?style=for-the-badge&logo=OpenCollective&logoColor=white)](https://opencollective.com/drop-oss)

View File

@ -1,10 +1,14 @@
<template>
<div class="flex">
<a
href="/auth/oidc"
:href="`/auth/oidc?redirect=${route.query.redirect ?? '/'}`"
class="transition rounded-md grow inline-flex items-center justify-center bg-white/10 px-3.5 py-2.5 text-sm font-semibold text-white shadow-xs hover:bg-white/20"
>
Sign in with external provider &rarr;
</a>
</div>
</template>
<script setup lang="ts">
const route = useRoute();
</script>

View File

@ -1,82 +1,141 @@
<!-- eslint-disable vue/no-v-html -->
<template>
<div>
<!-- import games button -->
<NuxtLink
v-if="unimportedVersions !== undefined"
:href="
unimportedVersions.length > 0 ? `/admin/library/${game.id}/import` : ''
"
type="button"
:class="[
unimportedVersions.length > 0
? 'bg-blue-600 hover:bg-blue-700'
: 'bg-blue-800/50',
'inline-flex w-fit items-center gap-x-2 rounded-md px-3 py-1 text-sm font-semibold font-display text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600',
]"
<div
v-if="game && unimportedVersions !== undefined"
class="grow flex flex-col gap-y-8"
>
{{
unimportedVersions.length > 0
? "Import version"
: "No versions to import"
}}
</NuxtLink>
<!-- version priority -->
<div>
<div class="border-b border-zinc-800 pb-3">
<div class="flex flex-wrap items-center justify-between sm:flex-nowrap">
<h3
class="text-base font-semibold font-display leading-6 text-zinc-100"
>
Version priority
</h3>
</div>
</div>
<div class="mt-4 text-center w-full text-sm text-zinc-600">lowest</div>
<draggable
:list="game.versions"
handle=".handle"
class="mt-2 space-y-4"
@update="() => updateVersionOrder()"
<div
class="grow w-full h-full lg:pr-[30vw] px-6 py-4 flex flex-col"
></div>
<div
class="lg:overflow-y-auto lg:border-l lg:border-zinc-800 lg:fixed lg:inset-y-0 lg:z-50 lg:w-[30vw] flex flex-col lg:right-0 gap-y-8 px-6 py-4"
>
<template #item="{ element: item }: { element: GameVersion }">
<div
class="w-full inline-flex items-center px-4 py-2 bg-zinc-800 rounded justify-between"
<!-- toolbar -->
<div class="inline-flex justify-end items-stretch gap-x-4">
<!-- open in library button -->
<NuxtLink
:href="`/admin/metadata/games/${game.id}`"
type="button"
class="inline-flex w-fit items-center gap-x-2 rounded-md bg-zinc-800 px-3 py-1 text-sm font-semibold font-display text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
<div class="text-zinc-100 font-semibold">
{{ item.versionName }}
Open in Metadata
<ArrowTopRightOnSquareIcon
class="-mr-0.5 h-7 w-7 p-1"
aria-hidden="true"
/>
</NuxtLink>
<!-- open in store button -->
<NuxtLink
:href="`/store/${game.id}`"
type="button"
class="inline-flex w-fit items-center gap-x-2 rounded-md bg-zinc-800 px-3 py-1 text-sm font-semibold font-display text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Open in Store
<ArrowTopRightOnSquareIcon
class="-mr-0.5 h-7 w-7 p-1"
aria-hidden="true"
/>
</NuxtLink>
</div>
<!-- version manager -->
<div>
<!-- version priority -->
<div>
<div class="border-b border-zinc-800 pb-3">
<div
class="flex flex-wrap items-center justify-between sm:flex-nowrap"
>
<h3
class="text-base font-semibold font-display leading-6 text-zinc-100"
>
Version priority
<!-- import games button -->
<NuxtLink
v-if="unimportedVersions !== undefined"
:href="
unimportedVersions.length > 0
? `/admin/library/${game.id}/import`
: ''
"
type="button"
:class="[
unimportedVersions.length > 0
? 'bg-blue-600 hover:bg-blue-700'
: 'bg-blue-800/50',
'inline-flex w-fit items-center gap-x-2 rounded-md px-3 py-1 text-sm font-semibold font-display text-white shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600',
]"
>
{{
unimportedVersions.length > 0
? "Import version"
: "No versions to import"
}}
</NuxtLink>
</h3>
</div>
</div>
<div class="text-zinc-400">
{{ item.delta ? "Upgrade mode" : "" }}
<div class="mt-4 text-center w-full text-sm text-zinc-600">
lowest
</div>
<div class="inline-flex items-center gap-x-2">
<component
:is="PLATFORM_ICONS[item.platform]"
class="size-6 text-blue-600"
/>
<Bars3Icon class="cursor-move w-6 h-6 text-zinc-400 handle" />
<button @click="() => deleteVersion(item.versionName)">
<TrashIcon class="w-5 h-5 text-red-600" />
</button>
<draggable
:list="game.versions"
handle=".handle"
class="mt-2 space-y-4"
@update="() => updateVersionOrder()"
>
<template #item="{ element: item }: { element: GameVersion }">
<div
class="w-full inline-flex items-center px-4 py-2 bg-zinc-800 rounded justify-between"
>
<div class="text-zinc-100 font-semibold">
{{ item.versionName }}
</div>
<div class="text-zinc-400">
{{ item.delta ? "Upgrade mode" : "" }}
</div>
<div class="inline-flex items-center gap-x-2">
<component
:is="PLATFORM_ICONS[item.platform]"
class="size-6 text-blue-600"
/>
<Bars3Icon
class="cursor-move w-6 h-6 text-zinc-400 handle"
/>
<button @click="() => deleteVersion(item.versionName)">
<TrashIcon class="w-5 h-5 text-red-600" />
</button>
</div>
</div>
</template>
</draggable>
<div
v-if="game.versions.length == 0"
class="text-center font-bold text-zinc-400 my-3"
>
no versions added
</div>
<div class="mt-2 text-center w-full text-sm text-zinc-600">
highest
</div>
</div>
</template>
</draggable>
<div
v-if="game.versions.length == 0"
class="text-center font-bold text-zinc-400 my-3"
>
no versions added
</div>
</div>
<div class="mt-2 text-center w-full text-sm text-zinc-600">highest</div>
</div>
</div>
</template>
<script setup lang="ts">
import { Bars3Icon, TrashIcon } from "@heroicons/vue/16/solid";
import type { GameVersion } from "~/prisma/client";
import {
ArrowTopRightOnSquareIcon,
Bars3Icon,
TrashIcon,
} from "@heroicons/vue/24/solid";
definePageMeta({
layout: "admin",

View File

@ -94,7 +94,7 @@
</NuxtLink>
<NuxtLink
:href="`/admin/metadata/games/${game.id}`"
class="w-fit rounded-md bg-zinc-800 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
class="w-fit rounded-md bg-zinc-800 px-2.5 py-1.5 text-sm font-semibold text-white shadow-sm hover:bg-zinc-700u focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Open with Metadata &rarr;
</NuxtLink>

View File

@ -184,7 +184,7 @@
type="button"
class="inline-flex w-fit items-center gap-x-2 rounded-md bg-zinc-800 px-3 py-1 text-sm font-semibold font-display text-white shadow-sm hover:bg-zinc-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Open in Library &rarr;
Open in Library
<ArrowTopRightOnSquareIcon
class="-mr-0.5 h-7 w-7 p-1"
aria-hidden="true"

View File

@ -21,8 +21,8 @@
class="transition group aspect-[3/2] flex flex-col justify-center items-center rounded-lg bg-zinc-950 hover:bg-zinc-950/50 shadow"
>
<span
class="transition-all text-4xl font-bold text-zinc-300 group-hover:text-zinc-100 uppercase tracking-widest"
>GAMES</span
class="transition-all text-4xl font-bold text-zinc-400 group-hover:text-zinc-100 uppercase tracking-widest"
>Games</span
>
</NuxtLink>
<NuxtLink
@ -30,7 +30,7 @@
class="transition group aspect-[3/2] flex flex-col justify-center items-center rounded-lg bg-zinc-950 hover:bg-zinc-950/50 shadow"
>
<span
class="transition-all text-4xl font-bold text-zinc-300 group-hover:text-zinc-100 uppercase tracking-widest"
class="transition-all text-4xl font-bold text-zinc-400 group-hover:text-zinc-100 uppercase tracking-widest"
>Companies</span
>
</NuxtLink>

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Notification" ADD COLUMN "acls" TEXT[];

View File

@ -16,17 +16,6 @@ model Client {
platform Platform
lastConnected DateTime
peerAPI ClientPeerAPIConfiguration?
lastAccessedSaves SaveSlot[]
tokens APIToken[]
}
model ClientPeerAPIConfiguration {
id String @id @default(uuid())
clientId String @unique
client Client @relation(fields: [clientId], references: [id])
endpoints String[]
}

View File

@ -27,7 +27,8 @@ model Notification {
nonce String?
userId String
user User @relation(fields: [userId], references: [id])
user User @relation(fields: [userId], references: [id])
acls String[]
created DateTime @default(now())
title String

View File

@ -1,3 +1,10 @@
import type {
CapabilityConfiguration,
InternalClientCapability,
} from "~/server/internal/clients/capabilities";
import capabilityManager, {
validCapabilities,
} from "~/server/internal/clients/capabilities";
import clientHandler from "~/server/internal/clients/handler";
import { parsePlatform } from "~/server/internal/utils/parseplatform";
@ -6,6 +13,8 @@ export default defineEventHandler(async (h3) => {
const name = body.name;
const platformRaw = body.platform;
const capabilities: Partial<CapabilityConfiguration> =
body.capabilities ?? {};
if (!name || !platformRaw)
throw createError({
@ -20,7 +29,46 @@ export default defineEventHandler(async (h3) => {
statusMessage: "Invalid or unsupported platform",
});
const clientId = await clientHandler.initiate({ name, platform });
if (!capabilities || typeof capabilities !== "object")
throw createError({
statusCode: 400,
statusMessage: "Capabilities must be an array",
});
const capabilityIterable = Object.entries(capabilities) as Array<
[InternalClientCapability, object]
>;
if (
capabilityIterable.length > 0 &&
capabilityIterable
.map(([capability]) => validCapabilities.find((v) => capability == v))
.filter((e) => e).length == 0
)
throw createError({
statusCode: 400,
statusMessage: "Invalid capabilities.",
});
if (
capabilityIterable.length > 0 &&
capabilityIterable.filter(
([capability, configuration]) =>
!capabilityManager.validateCapabilityConfiguration(
capability,
configuration,
),
).length > 0
)
throw createError({
statusCode: 400,
statusMessage: "Invalid capability configuration.",
});
const clientId = await clientHandler.initiate({
name,
platform,
capabilities,
});
return `/client/${clientId}/callback`;
});

View File

@ -55,7 +55,7 @@ export default defineClientEventHandler(
title: `"${client.name}" can now access ${capability}`,
description: `A device called "${client.name}" now has access to your ${capability}.`,
actions: ["Review|/account/devices"],
requiredPerms: ["clients:read"],
acls: ["user:clients:read"],
});
return {};

View File

@ -5,17 +5,19 @@ export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["notifications:read"]);
if (!userId) throw createError({ statusCode: 403 });
const userIds = [userId];
const hasSystemPerms = await aclManager.allowSystemACL(h3, [
"notifications:mark",
]);
if (hasSystemPerms) {
userIds.push("system");
}
const acls = await aclManager.fetchAllACLs(h3);
if (!acls)
throw createError({
statusCode: 500,
statusMessage: "Got userId but no ACLs - what?",
});
const notifications = await prisma.notification.findMany({
where: {
userId: { in: userIds },
userId,
acls: {
hasSome: acls,
},
},
orderBy: {
created: "desc", // Newest first

View File

@ -5,17 +5,19 @@ export default defineEventHandler(async (h3) => {
const userId = await aclManager.getUserIdACL(h3, ["notifications:mark"]);
if (!userId) throw createError({ statusCode: 403 });
const userIds = [userId];
const hasSystemPerms = await aclManager.allowSystemACL(h3, [
"notifications:mark",
]);
if (hasSystemPerms) {
userIds.push("system");
}
const acls = await aclManager.fetchAllACLs(h3);
if (!acls)
throw createError({
statusCode: 500,
statusMessage: "Got userId but no ACLs - what?",
});
await prisma.notification.updateMany({
where: {
userId: { in: userIds },
userId,
acls: {
hasSome: acls,
},
},
data: {
read: true,

View File

@ -14,22 +14,17 @@ export default defineWebSocketHandler({
return;
}
const userIds = [userId];
const hasSystemPerms = await aclManager.allowSystemACL(h3, [
"notifications:listen",
]);
if (hasSystemPerms) {
userIds.push("system");
const acls = await aclManager.fetchAllACLs(h3);
if (!acls) {
peer.send("unauthenticated");
return;
}
socketSessions.set(peer.id, userId);
for (const listenUserId of userIds) {
notificationSystem.listen(listenUserId, peer.id, (notification) => {
peer.send(JSON.stringify(notification));
});
}
notificationSystem.listen(userId, acls, peer.id, (notification) => {
peer.send(JSON.stringify(notification));
});
},
async close(peer, _details) {
const userId = socketSessions.get(peer.id);

View File

@ -70,7 +70,9 @@ const systemACLPrefix = "system:";
export type SystemACL = Array<(typeof systemACLs)[number]>;
export type ValidACLItems = Array<SystemACL[number] | UserACL[number]>;
export type GlobalACL =
| `${typeof systemACLPrefix}${(typeof systemACLs)[number]}`
| `${typeof userACLPrefix}${(typeof userACLs)[number]}`;
class ACLManager {
private getAuthorizationToken(request: MinimumRequestObject) {
@ -175,6 +177,38 @@ class ACLManager {
return true;
}
async fetchAllACLs(
request: MinimumRequestObject,
): Promise<GlobalACL[] | undefined> {
const userSession = await sessionHandler.getSession(request);
if (!userSession) {
const authorizationToken = this.getAuthorizationToken(request);
if (!authorizationToken) return undefined;
const token = await prisma.aPIToken.findUnique({
where: { token: authorizationToken },
});
if (!token) return undefined;
return token.acls as GlobalACL[];
}
const user = await prisma.user.findUnique({
where: { id: userSession.userId },
select: {
admin: true,
},
});
if (!user)
throw new Error("User session without user - did something break?");
const acls = userACLs.map((e) => `${userACLPrefix}${e}`);
if (user.admin) {
acls.push(...systemACLs.map((e) => `${systemACLPrefix}${e}`));
}
return acls as GlobalACL[];
}
}
export const aclManager = new ACLManager();

View File

@ -1,6 +1,4 @@
import type { EnumDictionary } from "../utils/types";
import https from "https";
import { useCertificateAuthority } from "~/server/plugins/ca";
import prisma from "../db/database";
import { ClientCapabilities } from "~/prisma/client";
@ -17,7 +15,7 @@ export enum InternalClientCapability {
export const validCapabilities = Object.values(InternalClientCapability);
export type CapabilityConfiguration = {
[InternalClientCapability.PeerAPI]: { endpoints: string[] };
[InternalClientCapability.PeerAPI]: object;
[InternalClientCapability.UserStatus]: object;
[InternalClientCapability.CloudSaves]: object;
};
@ -27,6 +25,7 @@ class CapabilityManager {
InternalClientCapability,
(configuration: object) => Promise<boolean>
> = {
/*
[InternalClientCapability.PeerAPI]: async (rawConfiguration) => {
const configuration =
rawConfiguration as CapabilityConfiguration[InternalClientCapability.PeerAPI];
@ -71,12 +70,13 @@ class CapabilityManager {
valid = true;
break;
} catch {
/* empty */
}
}
return valid;
},
*/
[InternalClientCapability.PeerAPI]: async () => true,
[InternalClientCapability.UserStatus]: async () => true, // No requirements for user status
[InternalClientCapability.CloudSaves]: async () => true, // No requirements for cloud saves
};
@ -92,7 +92,7 @@ class CapabilityManager {
async upsertClientCapability(
capability: InternalClientCapability,
rawCapability: object,
rawCapabilityConfiguration: object,
clientId: string,
) {
const upsertFunctions: EnumDictionary<
@ -100,8 +100,7 @@ class CapabilityManager {
() => Promise<void> | void
> = {
[InternalClientCapability.PeerAPI]: async function () {
const configuration =
rawCapability as CapabilityConfiguration[InternalClientCapability.PeerAPI];
// const configuration =rawCapability as CapabilityConfiguration[InternalClientCapability.PeerAPI];
const currentClient = await prisma.client.findUnique({
where: { id: clientId },
@ -110,6 +109,7 @@ class CapabilityManager {
},
});
if (!currentClient) throw new Error("Invalid client ID");
/*
if (currentClient.capabilities.includes(ClientCapabilities.PeerAPI)) {
await prisma.clientPeerAPIConfiguration.update({
where: { clientId },
@ -126,6 +126,7 @@ class CapabilityManager {
endpoints: configuration.endpoints,
},
});
*/
await prisma.client.update({
where: { id: clientId },

View File

@ -2,10 +2,13 @@ import { randomUUID } from "node:crypto";
import prisma from "../db/database";
import type { Platform } from "~/prisma/client";
import { useCertificateAuthority } from "~/server/plugins/ca";
import type { CapabilityConfiguration, InternalClientCapability } from "./capabilities";
import capabilityManager from "./capabilities";
export interface ClientMetadata {
name: string;
platform: Platform;
capabilities: Partial<CapabilityConfiguration>;
}
export class ClientHandler {
@ -75,7 +78,7 @@ export class ClientHandler {
if (!metadata) throw new Error("Invalid client ID");
if (!metadata.userId) throw new Error("Un-authorized client ID");
return await prisma.client.create({
const client = await prisma.client.create({
data: {
id: id,
userId: metadata.userId,
@ -87,6 +90,20 @@ export class ClientHandler {
lastConnected: new Date(),
},
});
for (const [capability, configuration] of Object.entries(
metadata.data.capabilities,
)) {
await capabilityManager.upsertClientCapability(
capability as InternalClientCapability,
configuration,
client.id,
);
}
this.temporaryClientTable.delete(id);
return client;
}
async removeClient(id: string) {

View File

@ -306,7 +306,7 @@ class LibraryManager {
title: `'${game.mName}' ('${versionName}') finished importing.`,
description: `Drop finished importing version ${versionName} for ${game.mName}.`,
actions: [`View|/admin/library/${gameId}`],
requiredPerms: ["import:game:new"],
acls: ["system:import:version:read"],
});
progress(100);

View File

@ -8,28 +8,35 @@ Design goals:
import type { Notification } from "~/prisma/client";
import prisma from "../db/database";
import type { GlobalACL } from "../acls";
// type Optional<T, K extends keyof T> = Pick<Partial<T>, K> & Omit<T, K>;
// TODO: document notification action format
export type NotificationCreateArgs = Pick<
Notification,
"title" | "description" | "actions" | "nonce" | "requiredPerms"
>;
"title" | "description" | "actions" | "nonce"
> & { acls: Array<GlobalACL> };
class NotificationSystem {
// userId to acl to listenerId
private listeners = new Map<
string,
Map<string, (notification: Notification) => void>
Map<
string,
{ callback: (notification: Notification) => void; acls: GlobalACL[] }
>
>();
listen(
userId: string,
acls: Array<GlobalACL>,
id: string,
callback: (notification: Notification) => void,
) {
this.listeners.set(userId, new Map());
this.listeners.get(userId)?.set(id, callback);
if (!this.listeners.has(userId)) this.listeners.set(userId, new Map());
// eslint-disable-next-line @typescript-eslint/no-extra-non-null-assertion
this.listeners.get(userId)!!.set(id, { callback, acls });
this.catchupListener(userId, id);
}
@ -39,23 +46,27 @@ class NotificationSystem {
}
private async catchupListener(userId: string, id: string) {
const callback = this.listeners.get(userId)?.get(id);
if (!callback)
const listener = this.listeners.get(userId)?.get(id);
if (!listener)
throw new Error("Failed to catch-up listener: callback does not exist");
const notifications = await prisma.notification.findMany({
where: { userId: userId },
where: { userId: userId, acls: { hasSome: listener.acls } },
orderBy: {
created: "asc", // Oldest first, because they arrive in reverse order
},
});
for (const notification of notifications) {
await callback(notification);
await listener.callback(notification);
}
}
private async pushNotification(userId: string, notification: Notification) {
for (const listener of this.listeners.get(userId) ?? []) {
await listener[1](notification);
for (const [_, listener] of this.listeners.get(userId) ?? []) {
const hasSome =
notification.acls.findIndex(
(e) => listener.acls.findIndex((v) => v === e) != -1,
) != -1;
if (hasSome) await listener.callback(notification);
}
}
@ -100,25 +111,7 @@ class NotificationSystem {
}
async systemPush(notificationCreateArgs: NotificationCreateArgs) {
await this.push("system", notificationCreateArgs);
}
async pushAllAdmins(notificationCreateArgs: NotificationCreateArgs) {
const users = await prisma.user.findMany({
where: {
admin: true,
},
select: {
id: true,
},
});
const res: Promise<void>[] = [];
for (const user of users) {
res.push(this.push(user.id, notificationCreateArgs));
}
// wait for all notifications to pass
await Promise.all(res);
return await this.pushAll(notificationCreateArgs);
}
}

View File

@ -1,5 +1,6 @@
import { randomUUID } from "crypto";
import prisma from "../db/database";
import type { User } from "~/prisma/client";
import { AuthMec } from "~/prisma/client";
import objectHandler from "../objects";
import type { Readable } from "stream";
@ -12,10 +13,15 @@ interface OIDCWellKnown {
scopes_supported: string[];
}
interface OIDCAuthSessionOptions {
redirect: string | undefined;
}
interface OIDCAuthSession {
redirectUrl: string;
callbackUrl: string;
state: string;
options: OIDCAuthSessionOptions;
}
interface OIDCUserInfo {
@ -132,7 +138,7 @@ export class OIDCManager {
};
}
generateAuthSession(): OIDCAuthSession {
generateAuthSession(options?: OIDCAuthSessionOptions): OIDCAuthSession {
const stateKey = randomUUID();
const normalisedUrl = new URL(
@ -148,12 +154,16 @@ export class OIDCManager {
redirectUrl: finalUrl,
callbackUrl: redirectUrl,
state: stateKey,
options: options ?? { redirect: undefined },
};
this.signinStateTable[stateKey] = session;
return session;
}
async authorize(code: string, state: string) {
async authorize(
code: string,
state: string,
): Promise<{ user: User; options: OIDCAuthSessionOptions } | string> {
const session = this.signinStateTable[state];
if (!session) return "Invalid state parameter";
@ -191,7 +201,9 @@ export class OIDCManager {
const user = await this.fetchOrCreateUser(userinfo);
return user;
if (typeof user === "string") return user;
return { user, options: session.options };
} catch (e) {
console.error(e);
return `Request to identity provider failed: ${e}`;

View File

@ -29,15 +29,19 @@ export default defineEventHandler(async (h3) => {
statusMessage: "No state in query params.",
});
const user = await manager.authorize(code, state);
const result = await manager.authorize(code, state);
if (typeof user === "string")
if (typeof result === "string")
throw createError({
statusCode: 403,
statusMessage: `Failed to sign in: "${user}". Please try again.`,
statusMessage: `Failed to sign in: "${result}". Please try again.`,
});
await sessionHandler.signin(h3, user.id, true);
await sessionHandler.signin(h3, result.user.id, true);
if (result.options.redirect) {
return sendRedirect(h3, result.options.redirect);
}
return sendRedirect(h3, "/");
});

View File

@ -9,10 +9,16 @@ defineRouteMeta({
});
export default defineEventHandler((h3) => {
if (!enabledAuthManagers.OpenID) return sendRedirect(h3, "/auth/signin");
const redirect = getQuery(h3).redirect?.toString();
if (!enabledAuthManagers.OpenID)
return sendRedirect(
h3,
`/auth/signin${redirect ? `?redirect=${encodeURIComponent(redirect)}` : ""}`,
);
const manager = enabledAuthManagers.OpenID;
const { redirectUrl } = manager.generateAuthSession();
const { redirectUrl } = manager.generateAuthSession({ redirect });
return sendRedirect(h3, redirectUrl);
});