@atproto/jwk
Version:
A library for working with JSON Web Keys (JWKs) in TypeScript. This is meant to be extended by environment-specific libraries like @atproto/jwk-jose.
143 lines • 4.93 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.segmentedStringRefinementFactory = exports.jwtCharsRefinement = exports.parseB64uJson = exports.cachedGetter = exports.matchesAny = exports.preferredOrderCmp = exports.isDefined = void 0;
const base64_1 = require("multiformats/bases/base64");
const zod_1 = require("zod");
const isDefined = (i) => i !== undefined;
exports.isDefined = isDefined;
const preferredOrderCmp = (order) => (a, b) => {
const aIdx = order.indexOf(a);
const bIdx = order.indexOf(b);
if (aIdx === bIdx)
return 0;
if (aIdx === -1)
return 1;
if (bIdx === -1)
return -1;
return aIdx - bIdx;
};
exports.preferredOrderCmp = preferredOrderCmp;
function matchesAny(value) {
return value == null
? (v) => true
: Array.isArray(value)
? (v) => value.includes(v)
: (v) => v === value;
}
exports.matchesAny = matchesAny;
/**
* Decorator to cache the result of a getter on a class instance.
*/
const cachedGetter = (target, _context) => {
return function () {
const value = target.call(this);
Object.defineProperty(this, target.name, {
get: () => value,
enumerable: true,
configurable: true,
});
return value;
};
};
exports.cachedGetter = cachedGetter;
const decoder = new TextDecoder();
function parseB64uJson(input) {
const inputBytes = base64_1.base64url.baseDecode(input);
const json = decoder.decode(inputBytes);
return JSON.parse(json);
}
exports.parseB64uJson = parseB64uJson;
/**
* @example
* ```ts
* // jwtSchema will only allow base64url chars & "." (dot)
* const jwtSchema = z.string().superRefine(jwtCharsRefinement)
* ```
*/
const jwtCharsRefinement = (data, ctx) => {
// Note: this is a hot path, let's avoid using a RegExp
let char;
for (let i = 0; i < data.length; i++) {
char = data.charCodeAt(i);
if (
// Base64 URL encoding (most frequent)
(65 <= char && char <= 90) || // A-Z
(97 <= char && char <= 122) || // a-z
(48 <= char && char <= 57) || // 0-9
char === 45 || // -
char === 95 || // _
// Boundary (least frequent, check last)
char === 46 // .
) {
// continue
}
else {
// Invalid char might be a surrogate pair
const invalidChar = String.fromCodePoint(data.codePointAt(i));
return ctx.addIssue({
code: zod_1.ZodIssueCode.custom,
message: `Invalid character "${invalidChar}" in JWT at position ${i}`,
});
}
}
};
exports.jwtCharsRefinement = jwtCharsRefinement;
/**
* @example
* ```ts
* const jwtSchema = z.string().superRefine(segmentedStringRefinementFactory(3))
* type Jwt = z.infer<typeof jwtSchema> // `${string}.${string}.${string}`
* ```
*/
const segmentedStringRefinementFactory = (count, minPartLength = 2) => {
if (!Number.isFinite(count) || count < 1 || (count | 0) !== count) {
throw new TypeError(`Count must be a natural number (got ${count})`);
}
const minTotalLength = count * minPartLength + (count - 1);
const errorPrefix = `Invalid JWT format`;
return (data, ctx) => {
if (data.length < minTotalLength) {
ctx.addIssue({
code: zod_1.ZodIssueCode.custom,
message: `${errorPrefix}: too short`,
});
return false;
}
let currentStart = 0;
for (let i = 0; i < count - 1; i++) {
const nextDot = data.indexOf('.', currentStart);
if (nextDot === -1) {
ctx.addIssue({
code: zod_1.ZodIssueCode.custom,
message: `${errorPrefix}: expected ${count} segments, got ${i + 1}`,
});
return false;
}
if (nextDot - currentStart < minPartLength) {
ctx.addIssue({
code: zod_1.ZodIssueCode.custom,
message: `${errorPrefix}: segment ${i + 1} is too short`,
});
return false;
}
currentStart = nextDot + 1;
}
if (data.indexOf('.', currentStart) !== -1) {
ctx.addIssue({
code: zod_1.ZodIssueCode.custom,
message: `${errorPrefix}: too many segments`,
});
return false;
}
if (data.length - currentStart < minPartLength) {
ctx.addIssue({
code: zod_1.ZodIssueCode.custom,
message: `${errorPrefix}: last segment is too short`,
});
return false;
}
return true;
};
};
exports.segmentedStringRefinementFactory = segmentedStringRefinementFactory;
//# sourceMappingURL=util.js.map