mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-10 04:22:09 +10:00
feat: allow client-based web tokens
This commit is contained in:
@ -42,7 +42,7 @@ export const $dropFetch: DropFetch = async (request, opts) => {
|
|||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
const headers = useRequestHeaders(["cookie"]);
|
const headers = useRequestHeaders(["cookie", "authorization"]);
|
||||||
const data = await $fetch(request, {
|
const data = await $fetch(request, {
|
||||||
...opts,
|
...opts,
|
||||||
headers: { ...opts?.headers, ...headers },
|
headers: { ...opts?.headers, ...headers },
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
-- AlterEnum
|
||||||
|
ALTER TYPE "APITokenMode" ADD VALUE 'Client';
|
||||||
@ -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;
|
||||||
@ -27,6 +27,7 @@ model Invitation {
|
|||||||
enum APITokenMode {
|
enum APITokenMode {
|
||||||
User
|
User
|
||||||
System
|
System
|
||||||
|
Client
|
||||||
}
|
}
|
||||||
|
|
||||||
model APIToken {
|
model APIToken {
|
||||||
@ -38,6 +39,9 @@ model APIToken {
|
|||||||
userId String?
|
userId String?
|
||||||
user User? @relation(fields: [userId], references: [id])
|
user User? @relation(fields: [userId], references: [id])
|
||||||
|
|
||||||
|
clientId String?
|
||||||
|
client Client? @relation(fields: [clientId], references: [id], onDelete: Cascade)
|
||||||
|
|
||||||
acls String[]
|
acls String[]
|
||||||
|
|
||||||
@@index([token])
|
@@index([token])
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
enum ClientCapabilities {
|
enum ClientCapabilities {
|
||||||
PeerAPI @map("peerAPI") // other clients can use the HTTP API to P2P with this client
|
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)
|
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
|
// References a device
|
||||||
@ -19,6 +19,7 @@ model Client {
|
|||||||
peerAPI ClientPeerAPIConfiguration?
|
peerAPI ClientPeerAPIConfiguration?
|
||||||
|
|
||||||
lastAccessedSaves SaveSlot[]
|
lastAccessedSaves SaveSlot[]
|
||||||
|
tokens APIToken[]
|
||||||
}
|
}
|
||||||
|
|
||||||
model ClientPeerAPIConfiguration {
|
model ClientPeerAPIConfiguration {
|
||||||
|
|||||||
31
server/api/v1/client/user/webtoken.post.ts
Normal file
31
server/api/v1/client/user/webtoken.post.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
);
|
||||||
@ -2,7 +2,7 @@ import aclManager from "~/server/internal/acls";
|
|||||||
import userLibraryManager from "~/server/internal/userlibrary";
|
import userLibraryManager from "~/server/internal/userlibrary";
|
||||||
|
|
||||||
export default defineEventHandler(async (h3) => {
|
export default defineEventHandler(async (h3) => {
|
||||||
const userId = await aclManager.getUserIdACL(h3, ["collections:new"]);
|
const userId = await aclManager.getUserIdACL(h3, ["collections:read"]);
|
||||||
if (!userId)
|
if (!userId)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 403,
|
statusCode: 403,
|
||||||
|
|||||||
@ -33,7 +33,7 @@ export const userACLs = [
|
|||||||
] as const;
|
] as const;
|
||||||
const userACLPrefix = "user:";
|
const userACLPrefix = "user:";
|
||||||
|
|
||||||
type UserACL = Array<(typeof userACLs)[number]>;
|
export type UserACL = Array<(typeof userACLs)[number]>;
|
||||||
|
|
||||||
export const systemACLs = [
|
export const systemACLs = [
|
||||||
"auth:read",
|
"auth:read",
|
||||||
@ -69,7 +69,7 @@ export const systemACLs = [
|
|||||||
] as const;
|
] as const;
|
||||||
const systemACLPrefix = "system:";
|
const systemACLPrefix = "system:";
|
||||||
|
|
||||||
type SystemACL = Array<(typeof systemACLs)[number]>;
|
export type SystemACL = Array<(typeof systemACLs)[number]>;
|
||||||
|
|
||||||
class ACLManager {
|
class ACLManager {
|
||||||
private getAuthorizationToken(request: MinimumRequestObject) {
|
private getAuthorizationToken(request: MinimumRequestObject) {
|
||||||
@ -90,16 +90,25 @@ class ACLManager {
|
|||||||
const authorizationToken = this.getAuthorizationToken(request);
|
const authorizationToken = this.getAuthorizationToken(request);
|
||||||
if (!authorizationToken) return undefined;
|
if (!authorizationToken) return undefined;
|
||||||
const token = await prisma.aPIToken.findUnique({
|
const token = await prisma.aPIToken.findUnique({
|
||||||
where: { token: authorizationToken },
|
where: {
|
||||||
|
token: authorizationToken,
|
||||||
|
mode: { in: [APITokenMode.User, APITokenMode.Client] },
|
||||||
|
},
|
||||||
});
|
});
|
||||||
if (!token) return undefined;
|
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) {
|
for (const acl of acls) {
|
||||||
const tokenACLIndex = token.acls.findIndex((e) => e == acl);
|
const tokenACLIndex = token.acls.findIndex((e) => e == acl);
|
||||||
if (tokenACLIndex != -1) return token.userId;
|
if (tokenACLIndex != -1) return token.userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(token);
|
||||||
|
console.log(acls);
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user