studiocms
Version:
Astro Native CMS for AstroDB. Built from the ground up by the Astro community.
125 lines (124 loc) • 5.07 kB
JavaScript
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
};