UNPKG

studiocms

Version:

Astro Native CMS for AstroDB. Built from the ground up by the Astro community.

125 lines (124 loc) 5.07 kB
import fs from "node:fs"; import { StudioCMSColorway, StudioCMSColorwayBg, StudioCMSColorwayInfoBg } from "@withstudiocms/cli-kit/colors"; import { boxen, label } from "@withstudiocms/cli-kit/messages"; import { intro, log, outro, spinner } from "@withstudiocms/effect/clack"; import { SignJWT } from "jose"; import { importPKCS8 } from "jose/key/import"; import { Cli, Effect, genLogger } from "../../../effect.js"; import { genContext } from "../../utils/context.js"; import { dateAdd } from "../../utils/dateAdd.js"; import { logger } from "../../utils/logger.js"; const OneYear = 31556926; const keyFile = Cli.Args.text({ name: "keyfile" }).pipe( Cli.Args.withDescription( "a relative path (e.g., `../keys/libsql.pem`) from the current directory to your private key file (.pem)" ) ); const expire = Cli.Options.integer("exp").pipe( Cli.Options.withAlias("e"), Cli.Options.optional, Cli.Options.withDefault(OneYear), Cli.Options.withDescription("Expiry date in seconds (>=0) from issued at (iat) time") ); const debug = Cli.Options.boolean("debug").pipe( Cli.Options.optional, Cli.Options.withDefault(false), Cli.Options.withDescription("Enable debug mode") ); const convertJwtToBase64Url = (jwtToken) => Effect.try(() => Buffer.from(jwtToken).toString("base64url")); const genJWT = Cli.Command.make( "gen-jwt", { keyFile, expire, debug }, ({ expire: expire2, keyFile: keyFile2, debug: _debug }) => genLogger("studiocms/cli/crypto/genJWT")(function* () { let exp; if (typeof expire2 !== "number") { exp = yield* expire2; } else { exp = Number(expire2); } let debug2; if (typeof _debug !== "boolean") { debug2 = yield* _debug; } else { debug2 = _debug; } if (Number.isNaN(exp)) { return yield* Effect.fail(new Error(`Expiration must be a valid number, received: ${exp}`)); } if (exp < 0) { return yield* Effect.fail(new Error("Expiration must be greater than 0")); } if (debug2) { logger.debug("Debug mode enabled"); logger.debug(`Key file: ${keyFile2}`); logger.debug(`Expiration: ${exp}`); } if (debug2) logger.debug("Getting context"); const context = yield* genContext; const { chalk } = context; if (debug2) logger.debug("Init complete, starting..."); yield* intro(label("StudioCMS Crypto: Generate JWT", StudioCMSColorwayBg, chalk.bold)); const spin = yield* spinner(); try { yield* spin.start("Getting Key from keyFile"); if (debug2) logger.debug(`Key file path: ${keyFile2}`); if (debug2) logger.debug(`Key file exists: ${fs.existsSync(keyFile2)}`); if (debug2) logger.debug(`Key file is a file: ${fs.statSync(keyFile2).isFile()}`); const keyString = yield* Effect.try(() => fs.readFileSync(keyFile2, "utf8")); if (debug2) logger.debug(`Key string: ${keyString}`); if (!keyString) { yield* spin.stop("Key not found, or invalid"); return yield* Effect.fail(new Error("Key not found, or invalid")); } const alg = "EdDSA"; let privateKey; try { privateKey = yield* Effect.tryPromise(() => importPKCS8(keyString, alg)); } catch (_e) { yield* spin.stop("Invalid or unsupported private key"); return yield* Effect.fail(new Error("Invalid or unsupported private key")); } const NOW = /* @__PURE__ */ new Date(); const expirationDate = yield* dateAdd(NOW, "second", exp); const jwt = yield* Effect.tryPromise( () => new SignJWT({ sub: "libsql-client" }).setProtectedHeader({ alg }).setIssuedAt(NOW).setExpirationTime(expirationDate).setIssuer("admin").sign(privateKey) ); const base64UrlJwt = yield* convertJwtToBase64Url(jwt); yield* spin.stop("Token Generated."); yield* log.success( boxen(chalk.bold(`${label("Token Generated!", StudioCMSColorwayInfoBg, chalk.bold)}`), { ln1: "Your new Token has been generated successfully:", ln3: `Token: ${chalk.magenta(jwt)}`, ln5: `Base64Url Token: ${chalk.blue(base64UrlJwt)}` }) ); yield* outro( `${label("You can now use this token where needed.", StudioCMSColorwayBg, chalk.bold)} Stuck? Join us on Discord at ${StudioCMSColorway.bold.underline("https://chat.studiocms.dev")}` ); } catch (err) { if (err instanceof Error) { if (err.message.includes("ENOENT")) { yield* log.error("Key file not found: Please check the file path and try again."); } else if (err.message.includes("permission")) { yield* log.error("Permission denied: Cannot read the key file."); } else { yield* log.error(`Error generating JWT: ${err.message}`); } } else { yield* log.error(`Unexpected error generating JWT: ${err}`); } return yield* Effect.fail(new Error("JWT ERROR: Unknown")); } }) ).pipe(Cli.Command.withDescription("Generate a JWT token from a keyfile")); export { OneYear, debug, expire, genJWT, keyFile };