mirror of
https://github.com/DorkSquadRR/DorkNet.git
synced 2026-10-25 23:18:44 -07:00
f0e4679887
Players sign in to the website, and admins to the admin panel, with an OpenID Connect provider (DorkNet:SignIn:*). The first sign-in links the provider identity (issuer + subject) to a DorkNet account with that account's password; after that the provider sign-in is enough. - Cookie sessions per host, keys persisted in the database so redeploys keep people signed in; unlink, deletion or a ban ends the session. - Link attempts limited to 5 failures per 15 minutes per identity and per username. - Admin panel: "Sign in with ..." exchanges the session for the usual admin tokens via GET /api/admin/v1/sso (IsAdmin still required). - Downloads: optional DorkNet:Downloads:RequireSignIn serves the manifest and files only to signed-in players; the Downloads and Quest pages offer sign-in instead. - Website: sign-in in the header, /link and /account pages. The game client's login is unchanged.
67 lines
2.1 KiB
TypeScript
67 lines
2.1 KiB
TypeScript
// The client builds CI publishes (ci/downloads/publish.py), served through
|
|
// the server's DownloadsController. The manifest keeps the snake_case keys
|
|
// publish.py writes.
|
|
import { ApiError, get } from './api';
|
|
|
|
export interface DownloadFile {
|
|
id: string; // pc-full | pc-patch | quest | quest-installer
|
|
platform: 'pc' | 'quest' | string;
|
|
name: string;
|
|
path: string;
|
|
size: number;
|
|
sha256: string;
|
|
build?: string;
|
|
commit?: string;
|
|
built_at?: string;
|
|
}
|
|
|
|
export interface DownloadRelease {
|
|
id: string;
|
|
title: string;
|
|
game_version?: string;
|
|
pc_exe?: string;
|
|
files: DownloadFile[];
|
|
}
|
|
|
|
export interface DownloadsManifest {
|
|
releases: DownloadRelease[];
|
|
updated?: string;
|
|
}
|
|
|
|
/** The server only serves downloads to signed-in players
|
|
* (DorkNet:Downloads:RequireSignIn) and nobody is signed in. */
|
|
export class SignInRequiredError extends Error {
|
|
constructor() { super('sign_in_required'); this.name = 'SignInRequiredError'; }
|
|
}
|
|
|
|
let cached: Promise<DownloadsManifest | null> | null = null;
|
|
|
|
/** The manifest, or null when this server doesn't offer downloads. Fetched
|
|
* once per page load; pass `fresh` to re-read it. */
|
|
export function getDownloads(fresh = false): Promise<DownloadsManifest | null> {
|
|
if (!cached || fresh) {
|
|
cached = get<DownloadsManifest>('/downloads').catch((e) => {
|
|
if (e instanceof ApiError && e.status === 404) return null;
|
|
if (e instanceof ApiError && e.status === 401) { cached = null; throw new SignInRequiredError(); }
|
|
cached = null;
|
|
throw e;
|
|
});
|
|
}
|
|
return cached;
|
|
}
|
|
|
|
export const fileUrl = (f: DownloadFile) =>
|
|
`/api/site/v1/downloads/files/${f.path.split('/').map(encodeURIComponent).join('/')}`;
|
|
|
|
export interface QuestBuild { release: DownloadRelease; file: DownloadFile }
|
|
|
|
/** A published Quest package: the one at `path` when given, otherwise the
|
|
* newest release's. Null when there's no match. */
|
|
export function questBuild(m: DownloadsManifest | null, path?: string | null): QuestBuild | null {
|
|
for (const release of m?.releases ?? []) {
|
|
const file = release.files.find((f) => f.id === 'quest' && (!path || f.path === path));
|
|
if (file) return { release, file };
|
|
}
|
|
return null;
|
|
}
|