#!/usr/bin/env node const crypto = require('crypto') function b64urlDecode(s) { s = String(s || '') s = s.replace(/-/g, '+').replace(/_/g, '/') while (s.length % 4) s += '=' return Buffer.from(s, 'base64').toString('utf8') } function b64urlEncode(buf) { return buf.toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_') } function verifyHS256(token, secret) { const parts = token.split('.') if (parts.length !== 3) return { ok: false, reason: 'bad token format' } const [h64, p64, s64] = parts const signingInput = `${h64}.${p64}` const expected = b64urlEncode(crypto.createHmac('sha256', secret).update(signingInput).digest()) return { ok: expected === s64, expected, actual: s64 } } function usage() { console.log('Usage: node server/verify_supa_token.js [secret]') console.log('Or set env vars: TOKEN and SECRET and run without args') } const token = process.argv[2] || process.env.TOKEN const secret = process.argv[3] || process.env.SECRET || process.env.JWT_SECRET || process.env.SERVICE_ROLE_KEY || process.env.SUPA_KEY if (!token) { usage() process.exit(2) } if (!secret) { console.error('No secret provided. Pass as second arg or set SECRET/JWT_SECRET/SERVICE_ROLE_KEY env var') process.exit(2) } try { const parts = token.split('.') const header = JSON.parse(b64urlDecode(parts[0])) const payload = JSON.parse(b64urlDecode(parts[1])) console.log('Header:') console.log(JSON.stringify(header, null, 2)) console.log('Payload:') console.log(JSON.stringify(payload, null, 2)) if ((header.alg || '').toUpperCase() !== 'HS256') { console.warn('Warning: token alg is not HS256; this verifier only checks HS256 signatures') } const res = verifyHS256(token, secret) if (res.ok) { console.log('\nSignature: VALID (HMAC-SHA256)') } else { console.log('\nSignature: INVALID') console.log('expected:', res.expected) console.log('actual :', res.actual) } } catch (e) { console.error('verify error', e && e.message ? e.message : e) process.exit(1) }