UNPKG

@sd-jwt/jwt-status-list

Version:

Implementation based on https://datatracker.ietf.org/doc/draft-ietf-oauth-status-list/

208 lines (204 loc) 6.42 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { StatusList: () => StatusList, createHeaderAndPayload: () => createHeaderAndPayload, getListFromStatusListJWT: () => getListFromStatusListJWT, getStatusListFromJWT: () => getStatusListFromJWT }); module.exports = __toCommonJS(index_exports); // src/status-list.ts var import_utils = require("@sd-jwt/utils"); var import_pako = require("pako"); var StatusList = class _StatusList { /** * Create a new StatusListManager instance. * @param statusList * @param bitsPerStatus */ constructor(statusList, bitsPerStatus) { if (![1, 2, 4, 8].includes(bitsPerStatus)) { throw new Error("bitsPerStatus must be 1, 2, 4, or 8"); } for (let i = 0; i < statusList.length; i++) { if (statusList[i] > 2 ** bitsPerStatus) { throw Error( `Status value out of range at index ${i} with value ${statusList[i]}` ); } } this._statusList = statusList; this.bitsPerStatus = bitsPerStatus; this.totalStatuses = statusList.length; } /** * Get the status list. */ get statusList() { return this._statusList; } /** * Get the number of statuses. * @returns */ getBitsPerStatus() { return this.bitsPerStatus; } /** * Get the status at a specific index. * @param index */ getStatus(index) { if (index < 0 || index >= this.totalStatuses) { throw new Error("Index out of bounds"); } return this._statusList[index]; } /** * Set the status at a specific index. * @param index * @param value */ setStatus(index, value) { if (index < 0 || index >= this.totalStatuses) { throw new Error("Index out of bounds"); } this._statusList[index] = value; } /** * Compress the status list. */ compressStatusList() { const byteArray = this.encodeStatusList(); const compressed = (0, import_pako.deflate)(byteArray, { level: 9 }); return (0, import_utils.uint8ArrayToBase64Url)(compressed); } /** * Decompress the compressed status list and return a new StatusList instance. * @param compressed * @param bitsPerStatus */ static decompressStatusList(compressed, bitsPerStatus) { const decoded = (0, import_utils.base64UrlToUint8Array)(compressed); try { const decompressed = (0, import_pako.inflate)(decoded); const statusList = _StatusList.decodeStatusList( decompressed, bitsPerStatus ); return new _StatusList(statusList, bitsPerStatus); } catch (err) { throw new Error(`Decompression failed: ${err}`); } } /** * Encode the status list into a byte array. * @returns **/ encodeStatusList() { const numBits = this.bitsPerStatus; const numBytes = Math.ceil(this.totalStatuses * numBits / 8); const byteArray = new Uint8Array(numBytes); let byteIndex = 0; let bitIndex = 0; let currentByte = ""; for (let i = 0; i < this.totalStatuses; i++) { const status = this._statusList[i]; currentByte = status.toString(2).padStart(numBits, "0") + currentByte; bitIndex += numBits; if (bitIndex >= 8 || i === this.totalStatuses - 1) { if (i === this.totalStatuses - 1 && bitIndex % 8 !== 0) { currentByte = currentByte.padStart(8, "0"); } byteArray[byteIndex] = Number.parseInt(currentByte, 2); currentByte = ""; bitIndex = 0; byteIndex++; } } return byteArray; } /** * Decode the byte array into a status list. * @param byteArray * @param bitsPerStatus * @returns */ static decodeStatusList(byteArray, bitsPerStatus) { const numBits = bitsPerStatus; const totalStatuses = byteArray.length * 8 / numBits; const statusList = new Array(totalStatuses); let bitIndex = 0; for (let i = 0; i < totalStatuses; i++) { const byte = byteArray[Math.floor(i * numBits / 8)]; let byteString = byte.toString(2); if (byteString.length < 8) { byteString = "0".repeat(8 - byteString.length) + byteString; } const status = byteString.slice(bitIndex, bitIndex + numBits); const group = Math.floor(i / (8 / numBits)); const indexInGroup = i % (8 / numBits); const position = group * (8 / numBits) + (8 / numBits + -1 - indexInGroup); statusList[position] = Number.parseInt(status, 2); bitIndex = (bitIndex + numBits) % 8; } return statusList; } }; // src/status-list-jwt.ts var import_utils2 = require("@sd-jwt/utils"); function decodeJwt(jwt) { const parts = jwt.split("."); return JSON.parse((0, import_utils2.base64urlDecode)(parts[1])); } function createHeaderAndPayload(list, payload, header) { if (!payload.iss) { throw new Error("iss field is required"); } if (!payload.sub) { throw new Error("sub field is required"); } if (!payload.iat) { throw new Error("iat field is required"); } header.typ = "statuslist+jwt"; payload.status_list = { bits: list.getBitsPerStatus(), lst: list.compressStatusList() }; return { header, payload }; } function getListFromStatusListJWT(jwt) { const payload = decodeJwt(jwt); const statusList = payload.status_list; return StatusList.decompressStatusList(statusList.lst, statusList.bits); } function getStatusListFromJWT(jwt) { const payload = decodeJwt(jwt); return payload.status.status_list; } // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { StatusList, createHeaderAndPayload, getListFromStatusListJWT, getStatusListFromJWT });