@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
JavaScript
;
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
});