summaryrefslogtreecommitdiff
path: root/src/account
diff options
context:
space:
mode:
authorJustZvan <justzvan@justzvan.xyz>2026-02-06 12:16:40 +0100
committerJustZvan <justzvan@justzvan.xyz>2026-02-06 12:16:40 +0100
commite904e9634548e47d611bdcbb88d7b180b927fd5f (patch)
tree21aa5be08fc5b22585508c0263ee5ea4effcc593 /src/account
feat: initial commit!
Diffstat (limited to 'src/account')
-rw-r--r--src/account/jwt.ts91
1 files changed, 91 insertions, 0 deletions
diff --git a/src/account/jwt.ts b/src/account/jwt.ts
new file mode 100644
index 0000000..21d49f8
--- /dev/null
+++ b/src/account/jwt.ts
@@ -0,0 +1,91 @@
+import * as jose from "jose";
+import { logger } from "../lib/pino";
+
+const privateKey = process.env.JWT_PRIVATE_KEY;
+const publicKey = process.env.JWT_PUBLIC_KEY;
+
+/**
+ * Creates a signed JWT token with the provided payload.
+ * Tokens are set to expire in 1000 years (effectively never).
+ */
+export async function signJwt(
+ payload: Record<string, unknown>,
+ audience: string,
+) {
+ try {
+ if (!privateKey) {
+ logger.error("JWT_PRIVATE_KEY environment variable not set");
+ throw new Error("JWT private key not configured");
+ }
+
+ if (!payload || typeof payload !== "object") {
+ logger.error({ payload }, "Invalid payload for JWT signing");
+ throw new Error("Invalid JWT payload");
+ }
+
+ if (!audience || typeof audience !== "string") {
+ logger.error({ audience }, "Invalid audience for JWT signing");
+ throw new Error("Invalid JWT audience");
+ }
+
+ const privateKeyJose = await jose.importPKCS8(privateKey, "RS256");
+
+ const jwt = await new jose.SignJWT(payload)
+ .setProtectedHeader({ alg: "RS256" })
+ .setIssuedAt()
+ .setIssuer("urn:lajosh:buddy")
+ .setAudience(audience)
+ .setExpirationTime("1000years")
+ .sign(privateKeyJose);
+
+ logger.debug(
+ { audience, payloadType: payload.type },
+ "JWT signed successfully",
+ );
+ return jwt;
+ } catch (e) {
+ logger.error(
+ { error: e, audience, payloadType: payload?.type },
+ "Failed to sign JWT",
+ );
+ throw e;
+ }
+}
+
+/**
+ * Verifies a JWT token and returns the payload if valid.
+ * Checks signature, issuer, and audience claims.
+ */
+export async function verifyJwt(token: string, audience: string) {
+ try {
+ if (!publicKey) {
+ logger.error("JWT_PUBLIC_KEY environment variable not set");
+ throw new Error("JWT public key not configured");
+ }
+
+ if (!token || typeof token !== "string") {
+ logger.warn("Invalid token for JWT verification");
+ throw new Error("Invalid token");
+ }
+
+ if (!audience || typeof audience !== "string") {
+ logger.error({ audience }, "Invalid audience for JWT verification");
+ throw new Error("Invalid JWT audience");
+ }
+
+ const publicKeyJose = await jose.importSPKI(publicKey, "RS256");
+ const { payload } = await jose.jwtVerify(token, publicKeyJose, {
+ issuer: "urn:lajosh:buddy",
+ audience: audience,
+ });
+
+ logger.debug(
+ { audience, payloadType: payload.type as string },
+ "JWT verified successfully",
+ );
+ return payload;
+ } catch (e) {
+ logger.warn({ error: e, audience }, "JWT verification failed");
+ throw e;
+ }
+}