diff --git a/cli/commands/generate-keys.ts b/cli/commands/generate-keys.ts new file mode 100644 index 00000000..3ad75c3b --- /dev/null +++ b/cli/commands/generate-keys.ts @@ -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 { + static override args = {}; + + static override description = "Generates keys to use in Versia Server"; + + static override flags = {}; + + public async run(): Promise { + 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)}`); + } +} diff --git a/cli/index.ts b/cli/index.ts index a1115a8b..e93c4a32 100644 --- a/cli/index.ts +++ b/cli/index.ts @@ -1,37 +1,31 @@ import { configureLoggers } from "@/loggers"; 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 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(); // Use "explicit" oclif strategy to avoid issues with oclif's module resolver and bundling export const commands = { - "user:list": UserList, - "user:delete": UserDelete, - "user:create": UserCreate, - "user:reset": UserReset, - "user:refetch": UserRefetch, - "emoji:add": EmojiAdd, - "emoji:delete": EmojiDelete, - "emoji:list": EmojiList, - "emoji:import": EmojiImport, - "index:rebuild": IndexRebuild, - "federation:instance:fetch": FederationInstanceFetch, - "federation:user:finger": FederationUserFinger, - "federation:user:fetch": FederationUserFetch, + "user:list": (await import("./commands/user/list.ts")).default, + "user:delete": (await import("./commands/user/delete.ts")).default, + "user:create": (await import("./commands/user/create.ts")).default, + "user:reset": (await import("./commands/user/reset.ts")).default, + "user:refetch": (await import("./commands/user/refetch.ts")).default, + "emoji:add": (await import("./commands/emoji/add.ts")).default, + "emoji:delete": (await import("./commands/emoji/delete.ts")).default, + "emoji:list": (await import("./commands/emoji/list.ts")).default, + "emoji:import": (await import("./commands/emoji/import.ts")).default, + "index:rebuild": (await import("./commands/index/rebuild.ts")).default, + "federation:instance:fetch": ( + await import("./commands/federation/instance/fetch.ts") + ).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, }; diff --git a/config/config.example.toml b/config/config.example.toml index 682b773f..fb4a47fd 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -314,7 +314,8 @@ description = "A Versia Server instance" # URL to your instance 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] public = "" private = "" @@ -413,10 +414,10 @@ forced = false # Overriden by the signups.registration setting allow_registration = true -# [plugins.config."@versia/openid".keys] -# Run Versia Server with those values missing to generate a new key -# public = "" -# private = "" +[plugins.config."@versia/openid".keys] +# Use the "generate-keys" CLI command to generate these keys +public = "" +private = "" # The provider MUST support OpenID Connect with .well-known discovery # Most notably, GitHub does not support this diff --git a/plugins/openid/index.ts b/plugins/openid/index.ts index 31710488..1665e32d 100644 --- a/plugins/openid/index.ts +++ b/plugins/openid/index.ts @@ -1,4 +1,5 @@ import { Hooks, Plugin } from "@versia/kit"; +import chalk from "chalk"; import { z } from "zod"; import authorizeRoute from "./routes/authorize.ts"; import jwksRoute from "./routes/jwks.ts"; @@ -26,42 +27,36 @@ const plugin = new Plugin( ) .default([]), keys: z.object({ - public: z - .string() - .min(1) - .transform(async (v) => { - try { - return await crypto.subtle.importKey( - "spki", - Buffer.from(v, "base64"), - "Ed25519", - true, - ["verify"], - ); - } catch { - throw new Error( - "Public key at oidc.keys.public is invalid", - ); - } - }), - private: z - .string() - .min(1) - .transform(async (v) => { - try { - return await crypto.subtle.importKey( - "pkcs8", - Buffer.from(v, "base64"), - "Ed25519", - true, - ["sign"], - ); - } catch { - throw new Error( - "Private key at oidc.keys.private is invalid", - ); - } - }), + public: z.string().transform(async (v) => { + try { + return await crypto.subtle.importKey( + "spki", + Buffer.from(v, "base64"), + "Ed25519", + true, + ["verify"], + ); + } catch { + throw new Error( + `Public key at keys.public is invalid. Run the ${chalk.bold("generate-keys")} command to generate a new keypair`, + ); + } + }), + private: z.string().transform(async (v) => { + try { + return await crypto.subtle.importKey( + "pkcs8", + Buffer.from(v, "base64"), + "Ed25519", + true, + ["sign"], + ); + } catch { + throw new Error( + `Private key at keys.private is invalid. Run the ${chalk.bold("generate-keys")} command to generate a new keypair`, + ); + } + }), }), }), ); diff --git a/utils/init.ts b/utils/init.ts index 3a1fa502..2749f83b 100644 --- a/utils/init.ts +++ b/utils/init.ts @@ -1,6 +1,5 @@ import { getLogger } from "@logtape/logtape"; import chalk from "chalk"; -import { User } from "~/classes/database/user"; import type { Config } from "~/packages/config-manager"; export const checkConfig = async (config: Config) => { @@ -69,15 +68,8 @@ const checkFederationConfig = async (config: Config) => { const logger = getLogger("server"); if (!(config.instance.keys.public && config.instance.keys.private)) { - logger.fatal`The federation keys are not set in the config`; - logger.fatal`Below are generated keys for you to copy in the config at instance.keys.public and instance.keys.private`; - - // 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)}`; - + logger.fatal`The federation keys are not set 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")}`; // Hang until Ctrl+C is pressed await Bun.sleep(Number.POSITIVE_INFINITY); }