100 lines · 3.0 KB · TypeScript
RawDownload
import { decodeJwt, importPKCS8, importSPKI, jwtVerify, SignJWT, type JWTPayload } from "jose";import * as DateTime from "effect/DateTime";import * as Effect from "effect/Effect";import * as Schema from "effect/Schema"; export const RELAY_LINK_PROOF_TYP = "t3-env-link+jwt";export const RELAY_MINT_REQUEST_TYP = "t3-cloud-mint+jwt";export const RELAY_HEALTH_REQUEST_TYP = "t3-cloud-health+jwt";export const RELAY_MINT_RESPONSE_TYP = "t3-env-mint+jwt";export const RELAY_HEALTH_RESPONSE_TYP = "t3-env-health+jwt";export const RELAY_ACTIVITY_PUBLISH_TYP = "t3-env-activity+jwt"; export class RelayJwtSignError extends Schema.TaggedErrorClass<RelayJwtSignError>()(  "RelayJwtSignError",  {    typ: Schema.String,    cause: Schema.Defect(),  },) {  override get message(): string {    return `Failed to sign relay JWT (${this.typ}).`;  }} export class RelayJwtVerifyError extends Schema.TaggedErrorClass<RelayJwtVerifyError>()(  "RelayJwtVerifyError",  {    typ: Schema.String,    issuer: Schema.String,    audience: Schema.String,    cause: Schema.Defect(),  },) {  override get message(): string {    return `Failed to verify relay JWT (${this.typ}).`;  }} export const RelayJwtError = Schema.Union([RelayJwtSignError, RelayJwtVerifyError]);export type RelayJwtError = typeof RelayJwtError.Type; export function normalizeRelayIssuer(value: string): string {  return value.trim().replace(/\/+$/gu, "");} export function decodeRelayJwt(token: string): JWTPayload {  return decodeJwt(token);} function normalizePem(value: string): string {  return value.replace(/\\n/gu, "\n").trim();} export function signRelayJwt(input: {  readonly privateKey: string;  readonly typ: string;  readonly payload: JWTPayload;}): Effect.Effect<string, RelayJwtError> {  return Effect.tryPromise({    try: async () => {      const key = await importPKCS8(normalizePem(input.privateKey), "EdDSA");      return new SignJWT(input.payload)        .setProtectedHeader({ alg: "EdDSA", typ: input.typ })        .sign(key);    },    catch: (cause) => new RelayJwtSignError({ typ: input.typ, cause }),  });} export function verifyRelayJwt(input: {  readonly publicKey: string;  readonly token: string;  readonly typ: string;  readonly issuer: string;  readonly audience: string;  readonly nowEpochSeconds: number;}): Effect.Effect<JWTPayload, RelayJwtError> {  return Effect.tryPromise({    try: async () => {      const key = await importSPKI(normalizePem(input.publicKey), "EdDSA");      const verified = await jwtVerify(input.token, key, {        algorithms: ["EdDSA"],        typ: input.typ,        issuer: input.issuer,        audience: input.audience,        maxTokenAge: "5 minutes",        clockTolerance: 60,        currentDate: DateTime.toDate(DateTime.makeUnsafe(input.nowEpochSeconds * 1_000)),      });      return verified.payload;    },    catch: (cause) =>      new RelayJwtVerifyError({        typ: input.typ,        issuer: input.issuer,        audience: input.audience,        cause,      }),  });}