beginnings of download implementation

This commit is contained in:
DecDuck
2024-10-12 17:34:09 +11:00
parent 328b9ba46c
commit 8674ac7211
5 changed files with 62 additions and 12 deletions

View File

@ -0,0 +1,5 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
export default defineClientEventHandler(async (h3) => {
});

View File

@ -0,0 +1,20 @@
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
import prisma from "~/server/internal/db/database";
export default defineClientEventHandler(async (h3, {}) => {
const query = getQuery(h3);
const id = query.id?.toString();
if (!id)
throw createError({
statusCode: 400,
statusMessage: "No ID in request query",
});
const versions = await prisma.gameVersion.findMany({
where: {
gameId: id,
},
});
return versions;
});

View File

@ -3,24 +3,29 @@
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/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.
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.
## 2. User signs in
Client sends user to the provided URL (in external browser). User signs in using the existing authentication stack.
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.
Client sends user to the provided URL (in external browser). User signs in using the existing authentication stack.
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/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.
Client makes request: `POST /api/v1/client/auth/handshake` with the token recieved in the previous step.
*The certificate lasts for a year, and is rotated when it has 3 months or less left on it's expiry.*
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 certificate lasts for a year, and is rotated when it has 3 months or less left on it's expiry._
## 4.a Client requests one-time device endpoint
The client generates a nonce and signs it with their private key. This is then attached to any device-related request.
The client uses a millisecond UNIX timestamp and signs it with their private key. This is then attached to any device-related request. It has 30 seconds to make the request before the nonce becomes invalid (this is to prevent credential stealing & reusing).
## 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/auth/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.

View File

@ -14,6 +14,8 @@ type ClientUtils = {
fetchUser: () => Promise<User>;
};
const NONCE_LENIENCE = 30_000;
export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
return defineEventHandler(async (h3) => {
const header = await getHeader(h3, "Authorization");
@ -30,6 +32,21 @@ export function defineClientEventHandler<T>(handler: EventHandlerFunction<T>) {
if (!clientId || !nonce || !signature)
throw createError({ statusCode: 403 });
const nonceTime = parseInt(nonce);
const current = Date.now();
if (
// If it was generated in the future
nonceTime > current ||
// Or more than thirty seconds ago
nonceTime < current - NONCE_LENIENCE
) {
// We reject the request
throw createError({
statusCode: 403,
statusMessage: "Nonce expired",
});
}
const ca = h3.context.ca;
const certBundle = await ca.fetchClientCertificate(clientId);
if (!certBundle)

View File

@ -1,5 +1,8 @@
# Drop Download System
The Drop download system uses a torrent-*like* system. It is not torrenting, nor is it compatible with torrenting clients.
Drop downloads come in two types:
## Clients
Drop clients have built-in HTTP APIs that they forward with UPnP. This API exposes different capabilities for different Drop features, like download aggegration and P2P networking. When they sign on, they send a list of supported capabilities to the server.
## Public (not quite) HTTPS downloads endpoints
These use public HTTPS certificate, and while are authenticated, are 'public' in the sense that they aren't P2P; anyone can connect to them
## Private mTLS P2P endpoints
Drop clients use P2P mTLS aided by the P2P co-ordinator to transfer chunks between themselves.