A decentralized music tracking and discovery platform built on AT Protocol 🎵

refactor: remove unused jose-key.js script and clean up package.json

+1 -351
-349
apps/api/jose-key.js
··· 1 - "use strict"; 2 - Object.defineProperty(exports, "__esModule", { value: true }); 3 - exports.JoseKey = void 0; 4 - const jose_1 = require("jose"); 5 - const jwk_1 = require("@atproto/jwk"); 6 - const util_1 = require("./util"); 7 - const secp = require("@noble/secp256k1"); 8 - const crypto = require("node:crypto"); 9 - 10 - secp.utils.sha256Sync = (message) => { 11 - return new Uint8Array(crypto.createHash('sha256').update(message).digest()); 12 - }; 13 - secp.utils.hmacSha256Sync = (key, ...messages) => { 14 - const hmac = crypto.createHmac('sha256', key); 15 - messages.map(m => hmac.update(m)); 16 - return new Uint8Array(hmac.digest()); 17 - }; 18 - 19 - const { JOSEError } = jose_1.errors; 20 - 21 - function base64urlEncode(buffer) { 22 - return Buffer.from(buffer).toString('base64url'); 23 - } 24 - 25 - function base64urlDecode(str) { 26 - return new Uint8Array(Buffer.from(str, 'base64url')); 27 - } 28 - 29 - function sha256(data) { 30 - return secp.utils.sha256Sync(data); 31 - } 32 - 33 - async function signES256K(payload, header, privateKeyJwk) { 34 - const privateKeyBytes = base64urlDecode(privateKeyJwk.d); 35 - 36 - const encodedHeader = base64urlEncode( 37 - new TextEncoder().encode(JSON.stringify(header)) 38 - ); 39 - const encodedPayload = base64urlEncode( 40 - new TextEncoder().encode(JSON.stringify(payload)) 41 - ); 42 - 43 - const message = `${encodedHeader}.${encodedPayload}`; 44 - const messageHash = sha256(new TextEncoder().encode(message)); 45 - 46 - const signature = await secp.sign(messageHash, privateKeyBytes, { 47 - canonical: true, 48 - der: false 49 - }); 50 - const encodedSignature = base64urlEncode(signature); 51 - 52 - return `${message}.${encodedSignature}`; 53 - } 54 - 55 - async function verifyES256K(token, publicKeyJwk, options) { 56 - const [encodedHeader, encodedPayload, encodedSignature] = token.split('.'); 57 - 58 - if (!encodedHeader || !encodedPayload || !encodedSignature) { 59 - throw new Error('Invalid JWT format'); 60 - } 61 - 62 - const message = `${encodedHeader}.${encodedPayload}`; 63 - const messageHash = sha256(new TextEncoder().encode(message)); 64 - 65 - // Convert JWK to raw public key (uncompressed format: 0x04 + x + y) 66 - const x = base64urlDecode(publicKeyJwk.x); 67 - const y = base64urlDecode(publicKeyJwk.y); 68 - const publicKey = new Uint8Array([0x04, ...x, ...y]); 69 - 70 - const signature = base64urlDecode(encodedSignature); 71 - 72 - const isValid = secp.verify(signature, messageHash, publicKey); 73 - 74 - if (!isValid) { 75 - throw new Error('Invalid signature'); 76 - } 77 - 78 - // Parse header and payload 79 - const header = JSON.parse(new TextDecoder().decode(base64urlDecode(encodedHeader))); 80 - const payload = JSON.parse(new TextDecoder().decode(base64urlDecode(encodedPayload))); 81 - 82 - // Validate claims if options provided 83 - if (options) { 84 - const now = Math.floor(Date.now() / 1000); 85 - 86 - if (options.audience && payload.aud !== options.audience) { 87 - throw new Error('Invalid audience'); 88 - } 89 - 90 - if (options.issuer && payload.iss !== options.issuer) { 91 - throw new Error('Invalid issuer'); 92 - } 93 - 94 - if (payload.exp && payload.exp < now) { 95 - throw new Error('Token expired'); 96 - } 97 - 98 - if (payload.nbf && payload.nbf > now) { 99 - throw new Error('Token not yet valid'); 100 - } 101 - } 102 - 103 - return { 104 - protectedHeader: header, 105 - payload: payload, 106 - }; 107 - } 108 - 109 - function isES256K(alg) { 110 - return alg === 'ES256K'; 111 - } 112 - 113 - class JoseKey extends jwk_1.Key { 114 - /** 115 - * Some runtimes (e.g. Bun) require an `alg` second argument to be set when 116 - * invoking `importJWK`. In order to be compatible with these runtimes, we 117 - * provide the following method to ensure the `alg` is always set. We also 118 - * take the opportunity to ensure that the `alg` is compatible with this key. 119 - */ 120 - async getKeyObj(alg) { 121 - console.log('>> io le alg', alg); 122 - if (!this.algorithms.includes(alg)) { 123 - throw new jwk_1.JwkError(`Key cannot be used with algorithm "${alg}"`); 124 - } 125 - 126 - // For ES256K, return the JWK directly as we'll handle signing/verification separately 127 - if (isES256K(alg)) { 128 - return this.jwk; 129 - } 130 - 131 - try { 132 - return await (0, jose_1.importJWK)(this.jwk, alg.replaceAll('K', '')); 133 - } 134 - catch (cause) { 135 - throw new jwk_1.JwkError('Failed to import JWK', undefined, { cause }); 136 - } 137 - } 138 - 139 - async createJwt(header, payload) { 140 - try { 141 - const { kid } = header; 142 - if (kid && kid !== this.kid) { 143 - throw new jwk_1.JwtCreateError(`Invalid "kid" (${kid}) used to sign with key "${this.kid}"`); 144 - } 145 - 146 - const { alg } = header; 147 - if (!alg) { 148 - throw new jwk_1.JwtCreateError('Missing "alg" in JWT header'); 149 - } 150 - 151 - // Handle ES256K separately 152 - if (isES256K(alg)) { 153 - const fullHeader = { 154 - ...header, 155 - alg, 156 - kid: this.kid, 157 - }; 158 - return await signES256K(payload, fullHeader, this.jwk); 159 - } 160 - 161 - const keyObj = await this.getKeyObj(alg); 162 - const jwtBuilder = new jose_1.SignJWT(payload).setProtectedHeader({ 163 - ...header, 164 - alg, 165 - kid: this.kid, 166 - }); 167 - const signedJwt = await jwtBuilder.sign(keyObj); 168 - return signedJwt; 169 - } 170 - catch (cause) { 171 - if (cause instanceof JOSEError) { 172 - throw new jwk_1.JwtCreateError(cause.message, cause.code, { cause }); 173 - } 174 - else { 175 - throw jwk_1.JwtCreateError.from(cause); 176 - } 177 - } 178 - } 179 - 180 - async verifyJwt(token, options) { 181 - try { 182 - // Check if token uses ES256K by decoding header 183 - const headerB64 = token.split('.')[0]; 184 - const headerJson = JSON.parse( 185 - Buffer.from(headerB64, 'base64url').toString() 186 - ); 187 - 188 - // Handle ES256K separately 189 - if (isES256K(headerJson.alg)) { 190 - const result = await verifyES256K(token, this.jwk, options); 191 - 192 - const headerParsed = jwk_1.jwtHeaderSchema.safeParse(result.protectedHeader); 193 - if (!headerParsed.success) { 194 - throw new jwk_1.JwtVerifyError('Invalid JWT header', undefined, { 195 - cause: headerParsed.error, 196 - }); 197 - } 198 - 199 - const payloadParsed = jwk_1.jwtPayloadSchema.safeParse(result.payload); 200 - if (!payloadParsed.success) { 201 - throw new jwk_1.JwtVerifyError('Invalid JWT payload', undefined, { 202 - cause: payloadParsed.error, 203 - }); 204 - } 205 - 206 - return { 207 - protectedHeader: headerParsed.data, 208 - payload: payloadParsed.data, 209 - }; 210 - } 211 - 212 - const result = await (0, jose_1.jwtVerify)( 213 - token, 214 - async ({ alg }) => this.getKeyObj(alg), 215 - { ...options, algorithms: this.algorithms } 216 - ); 217 - 218 - const headerParsed = jwk_1.jwtHeaderSchema.safeParse(result.protectedHeader); 219 - if (!headerParsed.success) { 220 - throw new jwk_1.JwtVerifyError('Invalid JWT header', undefined, { 221 - cause: headerParsed.error, 222 - }); 223 - } 224 - 225 - const payloadParsed = jwk_1.jwtPayloadSchema.safeParse(result.payload); 226 - if (!payloadParsed.success) { 227 - throw new jwk_1.JwtVerifyError('Invalid JWT payload', undefined, { 228 - cause: payloadParsed.error, 229 - }); 230 - } 231 - 232 - return { 233 - protectedHeader: headerParsed.data, 234 - payload: payloadParsed.data, 235 - }; 236 - } 237 - catch (cause) { 238 - if (cause instanceof JOSEError) { 239 - throw new jwk_1.JwtVerifyError(cause.message, cause.code, { cause }); 240 - } 241 - else { 242 - throw jwk_1.JwtVerifyError.from(cause); 243 - } 244 - } 245 - } 246 - 247 - static async generateKeyPair(allowedAlgos, options) { 248 - if (allowedAlgos === undefined) allowedAlgos = ['ES256']; 249 - 250 - if (!allowedAlgos.length) { 251 - throw new jwk_1.JwkError('No algorithms provided for key generation'); 252 - } 253 - 254 - // Handle ES256K key generation 255 - if (allowedAlgos.includes('ES256K')) { 256 - const privateKey = secp.utils.randomPrivateKey(); 257 - const publicKey = secp.getPublicKey(privateKey, false); // uncompressed 258 - 259 - const x = publicKey.slice(1, 33); 260 - const y = publicKey.slice(33, 65); 261 - 262 - const privateJwk = { 263 - kty: 'EC', 264 - crv: 'secp256k1', 265 - x: base64urlEncode(x), 266 - y: base64urlEncode(y), 267 - d: base64urlEncode(privateKey), 268 - }; 269 - 270 - const publicJwk = { 271 - kty: 'EC', 272 - crv: 'secp256k1', 273 - x: base64urlEncode(x), 274 - y: base64urlEncode(y), 275 - }; 276 - 277 - return { 278 - privateKey: privateJwk, 279 - publicKey: publicJwk, 280 - }; 281 - } 282 - 283 - const errors = []; 284 - for (const alg of allowedAlgos) { 285 - try { 286 - return await (0, jose_1.generateKeyPair)(alg, options); 287 - } 288 - catch (err) { 289 - errors.push(err); 290 - } 291 - } 292 - throw new jwk_1.JwkError('Failed to generate key pair', undefined, { 293 - cause: new AggregateError(errors, 'None of the algorithms worked'), 294 - }); 295 - } 296 - 297 - static async generate(allowedAlgos, kid, options) { 298 - if (allowedAlgos === undefined) allowedAlgos = ['ES256']; 299 - 300 - const kp = await this.generateKeyPair(allowedAlgos, { 301 - ...options, 302 - extractable: true, 303 - }); 304 - return this.fromImportable(kp.privateKey, kid); 305 - } 306 - 307 - static async fromImportable(input, kid) { 308 - if (typeof input === 'string') { 309 - if (input.startsWith('-----')) { 310 - return this.fromPKCS8(input, '', kid); 311 - } 312 - if (input.startsWith('{')) { 313 - return this.fromJWK(input, kid); 314 - } 315 - throw new jwk_1.JwkError('Invalid input'); 316 - } 317 - if (typeof input === 'object') { 318 - if ('kty' in input || 'alg' in input) { 319 - return this.fromJWK(input, kid); 320 - } 321 - return this.fromKeyLike(input, kid); 322 - } 323 - throw new jwk_1.JwkError('Invalid input'); 324 - } 325 - 326 - static async fromKeyLike(keyLike, kid, alg) { 327 - const jwk = await (0, jose_1.exportJWK)(keyLike); 328 - if (alg) { 329 - if (!jwk.alg) jwk.alg = alg; 330 - else if (jwk.alg !== alg) throw new jwk_1.JwkError('Invalid "alg" in JWK'); 331 - } 332 - return this.fromJWK(jwk, kid); 333 - } 334 - 335 - static async fromPKCS8(pem, alg, kid) { 336 - const keyLike = await (0, jose_1.importPKCS8)(pem, alg, { extractable: true }); 337 - return this.fromKeyLike(keyLike, kid); 338 - } 339 - 340 - static async fromJWK(input, inputKid) { 341 - const jwk = typeof input === 'string' ? JSON.parse(input) : input; 342 - if (!jwk || typeof jwk !== 'object') throw new jwk_1.JwkError('Invalid JWK'); 343 - const kid = (0, util_1.either)(jwk.kid, inputKid); 344 - const use = jwk.use || 'sig'; 345 - return new JoseKey(jwk_1.jwkValidator.parse({ ...jwk, kid, use })); 346 - } 347 - } 348 - 349 - exports.JoseKey = JoseKey;
+1 -2
apps/api/package.json
··· 25 25 "format": "biome format src", 26 26 "lint": "biome lint src", 27 27 "feed": "bun ./src/scripts/feed.ts", 28 - "dedup": "bun ./src/scripts/dedup.ts", 29 - "postinstall": "cp jose-key.js ../../node_modules/.bun/@atproto+jwk-jose@0.1.5/node_modules/@atproto/jwk-jose/dist" 28 + "dedup": "bun ./src/scripts/dedup.ts" 30 29 }, 31 30 "dependencies": { 32 31 "@atproto/api": "^0.13.31",