import { sign } from "node:crypto" const internalNormalizePrivateKey = (privateKey: string): string => { const trimmedPrivateKey = privateKey.trim() if (trimmedPrivateKey.includes("BEGIN PRIVATE KEY")) { return trimmedPrivateKey } const privateKeyBody = trimmedPrivateKey.replaceAll(/\s+/g, "") const privateKeyLines = privateKeyBody.match(/.{1,64}/g)?.join("\n") ?? privateKeyBody return `-----BEGIN PRIVATE KEY-----\n${privateKeyLines}\n-----END PRIVATE KEY-----` } export interface SignJwtOptions { credentialId: string projectId: string privateKey: string /** * @description 过期时间,单位为秒,默认为 24 小时。 */ expiresIn?: number | undefined } /** * @see {@link https://dev.qweather.com/docs/configuration/authentication/#generate-ed25519-key} */ export const signJwt = (options: SignJwtOptions): string => { const { credentialId, projectId, privateKey, expiresIn = 86_400 } = options const header = { alg: "EdDSA", kid: credentialId, typ: "JWT", } const payload = { sub: projectId, iat: Math.floor(Date.now() / 1_000), exp: Math.floor(Date.now() / 1_000) + expiresIn, } // encode header and payload to base64url const headerBase64Url = Buffer.from(JSON.stringify(header)).toString("base64url") const payloadBase64Url = Buffer.from(JSON.stringify(payload)).toString("base64url") const unsignedSignature = `${headerBase64Url}.${payloadBase64Url}` // sign the unsigned signature with the private key using Ed25519 algorithm const signature = sign( null, Buffer.from(unsignedSignature), internalNormalizePrivateKey(privateKey), ) const signatureBase64Url = signature.toString("base64url") const jwt = `${unsignedSignature}.${signatureBase64Url}` return jwt }