@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.
138 lines • 4.63 kB
JavaScript
import { base64url } from 'multiformats/bases/base64';
import { ZodIssueCode } from 'zod';
export const isDefined = (i) => i !== undefined;
export 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;
};
/* eslint-disable @typescript-eslint/no-unused-vars -- `v` is used at runtime in the returned type guards; v8 false-positive */
export function matchesAny(value) {
return value == null
? (v) => true
: Array.isArray(value)
? (v) => value.includes(v)
: (v) => v === value;
}
/* eslint-enable @typescript-eslint/no-unused-vars */
/**
* Decorator to cache the result of a getter on a class instance.
*/
export const cachedGetter = (target, _context) => {
return function () {
const value = target.call(this);
Object.defineProperty(this, target.name, {
get: () => value,
enumerable: true,
configurable: true,
});
return value;
};
};
const decoder = new TextDecoder();
export function parseB64uJson(input) {
const inputBytes = base64url.baseDecode(input);
const json = decoder.decode(inputBytes);
return JSON.parse(json);
}
/**
* @example
* ```ts
* // jwtSchema will only allow base64url chars & "." (dot)
* const jwtSchema = z.string().superRefine(jwtCharsRefinement)
* ```
*/
export 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: ZodIssueCode.custom,
message: `Invalid character "${invalidChar}" in JWT at position ${i}`,
});
}
}
};
/**
* @example
* ```ts
* const jwtSchema = z.string().superRefine(segmentedStringRefinementFactory(3))
* type Jwt = z.infer<typeof jwtSchema> // `${string}.${string}.${string}`
* ```
*/
export 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: 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: ZodIssueCode.custom,
message: `${errorPrefix}: expected ${count} segments, got ${i + 1}`,
});
return false;
}
if (nextDot - currentStart < minPartLength) {
ctx.addIssue({
code: ZodIssueCode.custom,
message: `${errorPrefix}: segment ${i + 1} is too short`,
});
return false;
}
currentStart = nextDot + 1;
}
if (data.indexOf('.', currentStart) !== -1) {
ctx.addIssue({
code: ZodIssueCode.custom,
message: `${errorPrefix}: too many segments`,
});
return false;
}
if (data.length - currentStart < minPartLength) {
ctx.addIssue({
code: ZodIssueCode.custom,
message: `${errorPrefix}: last segment is too short`,
});
return false;
}
return true;
};
};
export function isLastOccurrence(v, i, arr) {
return arr.indexOf(v, i + 1) === -1;
}
//# sourceMappingURL=util.js.map