mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-13 00:02:37 +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 route = useRoute();
|
||||||
const clientId = route.params.id;
|
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 completed = ref(false);
|
||||||
const error = ref();
|
const error = ref();
|
||||||
|
|
||||||
async function authorize() {
|
async function authorize() {
|
||||||
const redirect = await $fetch<string>("/api/v1/client/callback", {
|
const redirect = await $fetch<string>("/api/v1/client/auth/callback", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: { id: clientId },
|
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.
|
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
|
## 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.
|
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.
|
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
|
## 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.
|
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.
|
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
|
## 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) {
|
async storeClientCertificate(clientId: string, bundle: CertificateBundle) {
|
||||||
await this.certificateStore.store(`client:${clientId}`, bundle);
|
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