From 8e3ae01a3037eb4dcabe72672438f9f6ed23b0bf Mon Sep 17 00:00:00 2001 From: DecDuck Date: Thu, 15 May 2025 16:05:46 +1000 Subject: [PATCH] feat: backend inline capability registration --- prisma/models/client.prisma | 13 +----- server/api/v1/client/auth/initiate.post.ts | 50 +++++++++++++++++++++- server/internal/clients/capabilities.ts | 15 ++++--- server/internal/clients/handler.ts | 19 +++++++- 4 files changed, 76 insertions(+), 21 deletions(-) diff --git a/prisma/models/client.prisma b/prisma/models/client.prisma index acff358..e01787a 100644 --- a/prisma/models/client.prisma +++ b/prisma/models/client.prisma @@ -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[] -} +} \ No newline at end of file diff --git a/server/api/v1/client/auth/initiate.post.ts b/server/api/v1/client/auth/initiate.post.ts index 39de29b..b03ac7d 100644 --- a/server/api/v1/client/auth/initiate.post.ts +++ b/server/api/v1/client/auth/initiate.post.ts @@ -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 = + 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`; }); diff --git a/server/internal/clients/capabilities.ts b/server/internal/clients/capabilities.ts index 9837fd6..5e30e00 100644 --- a/server/internal/clients/capabilities.ts +++ b/server/internal/clients/capabilities.ts @@ -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 > = { + /* [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 > = { [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 }, diff --git a/server/internal/clients/handler.ts b/server/internal/clients/handler.ts index ecc1e45..ca63f7b 100644 --- a/server/internal/clients/handler.ts +++ b/server/internal/clients/handler.ts @@ -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; } 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) {