mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-12 15:52:39 +10:00
finalised client APIs and authentication method
This commit is contained in:
@ -134,13 +134,13 @@ import { CheckCircleIcon } from "@heroicons/vue/24/outline";
|
||||
const route = useRoute();
|
||||
const clientId = route.params.id;
|
||||
|
||||
const clientData = await useFetch(`/api/v1/client/callback?id=${clientId}`);
|
||||
const clientData = await useFetch(`/api/v1/client/auth/callback?id=${clientId}`);
|
||||
|
||||
const completed = ref(false);
|
||||
const error = ref();
|
||||
|
||||
async function authorize() {
|
||||
const redirect = await $fetch<string>("/api/v1/client/callback", {
|
||||
const redirect = await $fetch<string>("/api/v1/client/auth/callback", {
|
||||
method: "POST",
|
||||
body: { id: clientId },
|
||||
});
|
||||
|
||||
7
server/api/v1/client/user/index.get.ts
Normal file
7
server/api/v1/client/user/index.get.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
|
||||
export default defineClientEventHandler(async (h3, { fetchUser }) => {
|
||||
const user = await fetchUser();
|
||||
|
||||
return user;
|
||||
});
|
||||
@ -3,7 +3,7 @@
|
||||
Drop clients need to complete a handshake in order to connect to a Drop server. It also trades certificates for encrypted P2P connections.
|
||||
|
||||
## 1. Client requests a handshake
|
||||
Client makes request: `POST /api/v1/client/initiate` with information about the client.
|
||||
Client makes request: `POST /api/v1/client/auth/initiate` with information about the client.
|
||||
|
||||
Server responds with a URL to send the user to. It generates a device ID, which has all the metadata attached.
|
||||
|
||||
@ -13,7 +13,7 @@ Client sends user to the provided URL (in external browser). User signs in using
|
||||
Server sends redirect to `drop://handshake/[id]/[token]`, where the token is an authentication token to generate the necessary certificates, and the ID is the client ID as generated by the server.
|
||||
|
||||
## 3. Client requests certificates
|
||||
Client makes request: `POST /api/v1/client/handshake` with the token recieved in the previous step.
|
||||
Client makes request: `POST /api/v1/client/auth/handshake` with the token recieved in the previous step.
|
||||
|
||||
The server uses it's CA to generate a public-private key pair, the CN of the client ID. It then sends that pair, plus the CA's public key, to the client, which stores it all.
|
||||
|
||||
@ -23,4 +23,4 @@ The server uses it's CA to generate a public-private key pair, the CN of the cli
|
||||
The client generates a nonce and signs it with their private key. This is then attached to any device-related request.
|
||||
|
||||
## 4.b Client wants a long-lived session
|
||||
The client does the same as above, but instead makes the request to `POST /api/v1/client/session`, which generates a session token that lasts for a day. This can then be used in the request to provide authentication.
|
||||
The client does the same as above, but instead makes the request to `POST /api/v1/client/auth/session`, which generates a session token that lasts for a day. This can then be used in the request to provide authentication.
|
||||
@ -51,4 +51,8 @@ export class CertificateAuthority {
|
||||
async storeClientCertificate(clientId: string, bundle: CertificateBundle) {
|
||||
await this.certificateStore.store(`client:${clientId}`, bundle);
|
||||
}
|
||||
|
||||
async fetchClientCertificate(clientId: string) {
|
||||
return await this.certificateStore.fetch(`client:${clientId}`);
|
||||
}
|
||||
}
|
||||
|
||||
96
server/internal/clients/event-handler.ts
Normal file
96
server/internal/clients/event-handler.ts
Normal file
@ -0,0 +1,96 @@
|
||||
import { Client, User } from "@prisma/client";
|
||||
import { EventHandlerRequest, H3Event } from "h3";
|
||||
import droplet from "@drop/droplet";
|
||||
import { useGlobalCertificateAuthority } from "~/server/plugins/ca";
|
||||
import prisma from "../db/database";
|
||||
|
||||
export type EventHandlerFunction<T> = (
|
||||
h3: H3Event<EventHandlerRequest>,
|
||||
utils: ClientUtils
|
||||
) => Promise<T> | T;
|
||||
|
||||
type ClientUtils = {
|
||||
clientId: string;
|
||||
fetchClient: () => Promise<Client>;
|
||||
fetchUser: () => Promise<User>;
|
||||
};
|
||||
|
||||
export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
|
||||
return defineEventHandler(async (h3) => {
|
||||
const header = await getHeader(h3, "Authorization");
|
||||
if (!header) throw createError({ statusCode: 403 });
|
||||
const [method, ...parts] = header.split(" ");
|
||||
|
||||
let clientId: string;
|
||||
switch (method) {
|
||||
case "Nonce":
|
||||
clientId = parts[0];
|
||||
const nonce = parts[1];
|
||||
const signature = parts[2];
|
||||
|
||||
if (!clientId || !nonce || !signature)
|
||||
throw createError({ statusCode: 403 });
|
||||
|
||||
const ca = useGlobalCertificateAuthority();
|
||||
const certBundle = await ca.fetchClientCertificate(clientId);
|
||||
if (!certBundle)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Invalid client ID",
|
||||
});
|
||||
|
||||
const valid = droplet.verifyNonce(certBundle.cert, nonce, signature);
|
||||
if (!valid)
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: "Invalid nonce signature.",
|
||||
});
|
||||
break;
|
||||
default:
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
});
|
||||
}
|
||||
|
||||
if (clientId === undefined)
|
||||
throw createError({
|
||||
statusCode: 500,
|
||||
statusMessage: "Failed to execute authentication pipeline.",
|
||||
});
|
||||
|
||||
async function fetchClient() {
|
||||
const client = await prisma.client.findUnique({
|
||||
where: { id: clientId },
|
||||
});
|
||||
if (!client)
|
||||
throw new Error(
|
||||
"client util fetch client broke - this should NOT happen"
|
||||
);
|
||||
return client;
|
||||
}
|
||||
|
||||
async function fetchUser() {
|
||||
const client = await prisma.client.findUnique({
|
||||
where: { id: clientId },
|
||||
select: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!client)
|
||||
throw new Error(
|
||||
"client util fetch client broke - this should NOT happen"
|
||||
);
|
||||
|
||||
return client.user;
|
||||
}
|
||||
|
||||
const utils: ClientUtils = {
|
||||
clientId,
|
||||
fetchClient,
|
||||
fetchUser,
|
||||
};
|
||||
|
||||
return await handler(h3, utils);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user