diff --git a/composables/request.ts b/composables/request.ts index 9e3a7fa..584a6be 100644 --- a/composables/request.ts +++ b/composables/request.ts @@ -42,7 +42,7 @@ export const $dropFetch: DropFetch = async (request, opts) => { return object; } - const headers = useRequestHeaders(["cookie"]); + const headers = useRequestHeaders(["cookie", "authorization"]); const data = await $fetch(request, { ...opts, headers: { ...opts?.headers, ...headers }, diff --git a/prisma/migrations/20250407090729_add_client_token_mode/migration.sql b/prisma/migrations/20250407090729_add_client_token_mode/migration.sql new file mode 100644 index 0000000..3af3976 --- /dev/null +++ b/prisma/migrations/20250407090729_add_client_token_mode/migration.sql @@ -0,0 +1,2 @@ +-- AlterEnum +ALTER TYPE "APITokenMode" ADD VALUE 'Client'; diff --git a/prisma/migrations/20250407091012_add_client_token_field_to_apitoken/migration.sql b/prisma/migrations/20250407091012_add_client_token_field_to_apitoken/migration.sql new file mode 100644 index 0000000..406edd4 --- /dev/null +++ b/prisma/migrations/20250407091012_add_client_token_field_to_apitoken/migration.sql @@ -0,0 +1,5 @@ +-- AlterTable +ALTER TABLE "APIToken" ADD COLUMN "clientId" TEXT; + +-- AddForeignKey +ALTER TABLE "APIToken" ADD CONSTRAINT "APIToken_clientId_fkey" FOREIGN KEY ("clientId") REFERENCES "Client"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema/auth.prisma b/prisma/schema/auth.prisma index e8505f9..d1253cb 100644 --- a/prisma/schema/auth.prisma +++ b/prisma/schema/auth.prisma @@ -27,6 +27,7 @@ model Invitation { enum APITokenMode { User System + Client } model APIToken { @@ -38,6 +39,9 @@ model APIToken { userId String? user User? @relation(fields: [userId], references: [id]) + clientId String? + client Client? @relation(fields: [clientId], references: [id], onDelete: Cascade) + acls String[] @@index([token]) diff --git a/prisma/schema/client.prisma b/prisma/schema/client.prisma index c6cb06d..acff358 100644 --- a/prisma/schema/client.prisma +++ b/prisma/schema/client.prisma @@ -1,7 +1,7 @@ enum ClientCapabilities { PeerAPI @map("peerAPI") // other clients can use the HTTP API to P2P with this client UserStatus @map("userStatus") // this client can report this user's status (playing, online, etc etc) - CloudSaves @map("cloudSaves") // ability to save to save slots + CloudSaves @map("cloudSaves") // ability to save to save slots } // References a device @@ -19,6 +19,7 @@ model Client { peerAPI ClientPeerAPIConfiguration? lastAccessedSaves SaveSlot[] + tokens APIToken[] } model ClientPeerAPIConfiguration { diff --git a/server/api/v1/client/user/webtoken.post.ts b/server/api/v1/client/user/webtoken.post.ts new file mode 100644 index 0000000..8d0869e --- /dev/null +++ b/server/api/v1/client/user/webtoken.post.ts @@ -0,0 +1,31 @@ +import { APITokenMode } from "@prisma/client"; +import { DateTime } from "luxon"; +import { UserACL } from "~/server/internal/acls"; +import { defineClientEventHandler } from "~/server/internal/clients/event-handler"; +import prisma from "~/server/internal/db/database"; + +export default defineClientEventHandler( + async (h3, { fetchUser, fetchClient, clientId }) => { + const user = await fetchUser(); + const client = await fetchClient(); + + const acls: UserACL = [ + "read", + "store:read", + "collections:read", + "object:read", + ]; + + const token = await prisma.aPIToken.create({ + data: { + name: `${client.name} Web Access Token ${DateTime.now().toISO()}`, + clientId, + userId: user.id, + mode: APITokenMode.Client, + acls, + }, + }); + + return token.token; + } +); diff --git a/server/api/v1/collection/index.get.ts b/server/api/v1/collection/index.get.ts index 2d4c420..e912e96 100644 --- a/server/api/v1/collection/index.get.ts +++ b/server/api/v1/collection/index.get.ts @@ -2,7 +2,7 @@ import aclManager from "~/server/internal/acls"; import userLibraryManager from "~/server/internal/userlibrary"; export default defineEventHandler(async (h3) => { - const userId = await aclManager.getUserIdACL(h3, ["collections:new"]); + const userId = await aclManager.getUserIdACL(h3, ["collections:read"]); if (!userId) throw createError({ statusCode: 403, diff --git a/server/internal/acls/index.ts b/server/internal/acls/index.ts index ad7ac79..217ea43 100644 --- a/server/internal/acls/index.ts +++ b/server/internal/acls/index.ts @@ -33,7 +33,7 @@ export const userACLs = [ ] as const; const userACLPrefix = "user:"; -type UserACL = Array<(typeof userACLs)[number]>; +export type UserACL = Array<(typeof userACLs)[number]>; export const systemACLs = [ "auth:read", @@ -69,7 +69,7 @@ export const systemACLs = [ ] as const; const systemACLPrefix = "system:"; -type SystemACL = Array<(typeof systemACLs)[number]>; +export type SystemACL = Array<(typeof systemACLs)[number]>; class ACLManager { private getAuthorizationToken(request: MinimumRequestObject) { @@ -90,16 +90,25 @@ class ACLManager { const authorizationToken = this.getAuthorizationToken(request); if (!authorizationToken) return undefined; const token = await prisma.aPIToken.findUnique({ - where: { token: authorizationToken }, + where: { + token: authorizationToken, + mode: { in: [APITokenMode.User, APITokenMode.Client] }, + }, }); if (!token) return undefined; - if (token.mode != APITokenMode.User || !token.userId) return undefined; // If it's a system token + if (!token.userId) + throw new Error( + "No userId on user or client token - is something broken?" + ); for (const acl of acls) { const tokenACLIndex = token.acls.findIndex((e) => e == acl); if (tokenACLIndex != -1) return token.userId; } + console.log(token); + console.log(acls); + return undefined; }