feat(cli): Add generate-keys CLI command

This commit is contained in:
Jesse Wierzbinski 2024-10-24 18:18:39 +02:00
parent 33f16bb9b1
commit 11bb0a6f49
No known key found for this signature in database
5 changed files with 77 additions and 77 deletions

View file

@ -0,0 +1,18 @@
import chalk from "chalk";
import { User } from "~/classes/database/user";
import { BaseCommand } from "~/cli/base";
export default class GenerateKeys extends BaseCommand<typeof GenerateKeys> {
static override args = {};
static override description = "Generates keys to use in Versia Server";
static override flags = {};
public async run(): Promise<void> {
const { public_key, private_key } = await User.generateKeys();
this.log(`Generated public key: ${chalk.gray(public_key)}`);
this.log(`Generated private key: ${chalk.gray(private_key)}`);
}
}

View file

@ -1,37 +1,31 @@
import { configureLoggers } from "@/loggers"; import { configureLoggers } from "@/loggers";
import { execute } from "@oclif/core"; import { execute } from "@oclif/core";
import EmojiAdd from "./commands/emoji/add.ts";
import EmojiDelete from "./commands/emoji/delete.ts";
import EmojiImport from "./commands/emoji/import.ts";
import EmojiList from "./commands/emoji/list.ts";
import FederationInstanceFetch from "./commands/federation/instance/fetch.ts";
import FederationUserFetch from "./commands/federation/user/fetch.ts";
import FederationUserFinger from "./commands/federation/user/finger.ts";
import IndexRebuild from "./commands/index/rebuild.ts";
import Start from "./commands/start.ts"; import Start from "./commands/start.ts";
import UserCreate from "./commands/user/create.ts";
import UserDelete from "./commands/user/delete.ts";
import UserList from "./commands/user/list.ts";
import UserRefetch from "./commands/user/refetch.ts";
import UserReset from "./commands/user/reset.ts";
await configureLoggers(); await configureLoggers();
// Use "explicit" oclif strategy to avoid issues with oclif's module resolver and bundling // Use "explicit" oclif strategy to avoid issues with oclif's module resolver and bundling
export const commands = { export const commands = {
"user:list": UserList, "user:list": (await import("./commands/user/list.ts")).default,
"user:delete": UserDelete, "user:delete": (await import("./commands/user/delete.ts")).default,
"user:create": UserCreate, "user:create": (await import("./commands/user/create.ts")).default,
"user:reset": UserReset, "user:reset": (await import("./commands/user/reset.ts")).default,
"user:refetch": UserRefetch, "user:refetch": (await import("./commands/user/refetch.ts")).default,
"emoji:add": EmojiAdd, "emoji:add": (await import("./commands/emoji/add.ts")).default,
"emoji:delete": EmojiDelete, "emoji:delete": (await import("./commands/emoji/delete.ts")).default,
"emoji:list": EmojiList, "emoji:list": (await import("./commands/emoji/list.ts")).default,
"emoji:import": EmojiImport, "emoji:import": (await import("./commands/emoji/import.ts")).default,
"index:rebuild": IndexRebuild, "index:rebuild": (await import("./commands/index/rebuild.ts")).default,
"federation:instance:fetch": FederationInstanceFetch, "federation:instance:fetch": (
"federation:user:finger": FederationUserFinger, await import("./commands/federation/instance/fetch.ts")
"federation:user:fetch": FederationUserFetch, ).default,
"federation:user:finger": (
await import("./commands/federation/user/finger.ts")
).default,
"federation:user:fetch": (
await import("./commands/federation/user/fetch.ts")
).default,
"generate-keys": (await import("./commands/generate-keys.ts")).default,
start: Start, start: Start,
}; };

View file

@ -314,7 +314,8 @@ description = "A Versia Server instance"
# URL to your instance banner # URL to your instance banner
# banner = "" # banner = ""
# Used for federation. If left empty or missing, the server will generate one for you. # Used for federation.
# Use the "generate-keys" CLI command to generate these keys
[instance.keys] [instance.keys]
public = "" public = ""
private = "" private = ""
@ -413,10 +414,10 @@ forced = false
# Overriden by the signups.registration setting # Overriden by the signups.registration setting
allow_registration = true allow_registration = true
# [plugins.config."@versia/openid".keys] [plugins.config."@versia/openid".keys]
# Run Versia Server with those values missing to generate a new key # Use the "generate-keys" CLI command to generate these keys
# public = "" public = ""
# private = "" private = ""
# The provider MUST support OpenID Connect with .well-known discovery # The provider MUST support OpenID Connect with .well-known discovery
# Most notably, GitHub does not support this # Most notably, GitHub does not support this

View file

@ -1,4 +1,5 @@
import { Hooks, Plugin } from "@versia/kit"; import { Hooks, Plugin } from "@versia/kit";
import chalk from "chalk";
import { z } from "zod"; import { z } from "zod";
import authorizeRoute from "./routes/authorize.ts"; import authorizeRoute from "./routes/authorize.ts";
import jwksRoute from "./routes/jwks.ts"; import jwksRoute from "./routes/jwks.ts";
@ -26,10 +27,7 @@ const plugin = new Plugin(
) )
.default([]), .default([]),
keys: z.object({ keys: z.object({
public: z public: z.string().transform(async (v) => {
.string()
.min(1)
.transform(async (v) => {
try { try {
return await crypto.subtle.importKey( return await crypto.subtle.importKey(
"spki", "spki",
@ -40,14 +38,11 @@ const plugin = new Plugin(
); );
} catch { } catch {
throw new Error( throw new Error(
"Public key at oidc.keys.public is invalid", `Public key at keys.public is invalid. Run the ${chalk.bold("generate-keys")} command to generate a new keypair`,
); );
} }
}), }),
private: z private: z.string().transform(async (v) => {
.string()
.min(1)
.transform(async (v) => {
try { try {
return await crypto.subtle.importKey( return await crypto.subtle.importKey(
"pkcs8", "pkcs8",
@ -58,7 +53,7 @@ const plugin = new Plugin(
); );
} catch { } catch {
throw new Error( throw new Error(
"Private key at oidc.keys.private is invalid", `Private key at keys.private is invalid. Run the ${chalk.bold("generate-keys")} command to generate a new keypair`,
); );
} }
}), }),

View file

@ -1,6 +1,5 @@
import { getLogger } from "@logtape/logtape"; import { getLogger } from "@logtape/logtape";
import chalk from "chalk"; import chalk from "chalk";
import { User } from "~/classes/database/user";
import type { Config } from "~/packages/config-manager"; import type { Config } from "~/packages/config-manager";
export const checkConfig = async (config: Config) => { export const checkConfig = async (config: Config) => {
@ -69,15 +68,8 @@ const checkFederationConfig = async (config: Config) => {
const logger = getLogger("server"); const logger = getLogger("server");
if (!(config.instance.keys.public && config.instance.keys.private)) { if (!(config.instance.keys.public && config.instance.keys.private)) {
logger.fatal`The federation keys are not set in the config`; logger.fatal`The federation keys are not set in the config at instance.keys.public and instance.keys.private`;
logger.fatal`Below are generated keys for you to copy in the config at instance.keys.public and instance.keys.private`; logger.fatal`You can generate a keypair using the CLI command ${chalk.bold("generate-keys")}`;
// Generate a key for them
const { public_key, private_key } = await User.generateKeys();
logger.fatal`Generated public key: ${chalk.gray(public_key)}`;
logger.fatal`Generated private key: ${chalk.gray(private_key)}`;
// Hang until Ctrl+C is pressed // Hang until Ctrl+C is pressed
await Bun.sleep(Number.POSITIVE_INFINITY); await Bun.sleep(Number.POSITIVE_INFINITY);
} }