78 lines
2.5 KiB
JavaScript
78 lines
2.5 KiB
JavaScript
// src/utils/jwt/jwt.ts
|
|
import { encodeBase64Url, decodeBase64Url } from "../../utils/encode.js";
|
|
import { AlgorithmTypes } from "./jwa.js";
|
|
import { signing, verifying } from "./jws.js";
|
|
import { JwtHeaderInvalid } from "./types.js";
|
|
import {
|
|
JwtTokenInvalid,
|
|
JwtTokenNotBefore,
|
|
JwtTokenExpired,
|
|
JwtTokenSignatureMismatched,
|
|
JwtTokenIssuedAt
|
|
} from "./types.js";
|
|
import { utf8Decoder, utf8Encoder } from "./utf8.js";
|
|
var encodeJwtPart = (part) => encodeBase64Url(utf8Encoder.encode(JSON.stringify(part))).replace(/=/g, "");
|
|
var encodeSignaturePart = (buf) => encodeBase64Url(buf).replace(/=/g, "");
|
|
var decodeJwtPart = (part) => JSON.parse(utf8Decoder.decode(decodeBase64Url(part)));
|
|
function isTokenHeader(obj) {
|
|
return typeof obj === "object" && obj !== null && "alg" in obj && Object.values(AlgorithmTypes).includes(obj.alg) && (!("typ" in obj) || obj.typ === "JWT");
|
|
}
|
|
var sign = async (payload, privateKey, alg = "HS256") => {
|
|
const encodedPayload = encodeJwtPart(payload);
|
|
const encodedHeader = encodeJwtPart({ alg, typ: "JWT" });
|
|
const partialToken = `${encodedHeader}.${encodedPayload}`;
|
|
const signaturePart = await signing(privateKey, alg, utf8Encoder.encode(partialToken));
|
|
const signature = encodeSignaturePart(signaturePart);
|
|
return `${partialToken}.${signature}`;
|
|
};
|
|
var verify = async (token, publicKey, alg = "HS256") => {
|
|
const tokenParts = token.split(".");
|
|
if (tokenParts.length !== 3) {
|
|
throw new JwtTokenInvalid(token);
|
|
}
|
|
const { header, payload } = decode(token);
|
|
if (!isTokenHeader(header)) {
|
|
throw new JwtHeaderInvalid(header);
|
|
}
|
|
const now = Math.floor(Date.now() / 1e3);
|
|
if (payload.nbf && payload.nbf > now) {
|
|
throw new JwtTokenNotBefore(token);
|
|
}
|
|
if (payload.exp && payload.exp <= now) {
|
|
throw new JwtTokenExpired(token);
|
|
}
|
|
if (payload.iat && now < payload.iat) {
|
|
throw new JwtTokenIssuedAt(now, payload.iat);
|
|
}
|
|
const headerPayload = token.substring(0, token.lastIndexOf("."));
|
|
const verified = await verifying(
|
|
publicKey,
|
|
alg,
|
|
decodeBase64Url(tokenParts[2]),
|
|
utf8Encoder.encode(headerPayload)
|
|
);
|
|
if (!verified) {
|
|
throw new JwtTokenSignatureMismatched(token);
|
|
}
|
|
return payload;
|
|
};
|
|
var decode = (token) => {
|
|
try {
|
|
const [h, p] = token.split(".");
|
|
const header = decodeJwtPart(h);
|
|
const payload = decodeJwtPart(p);
|
|
return {
|
|
header,
|
|
payload
|
|
};
|
|
} catch (e) {
|
|
throw new JwtTokenInvalid(token);
|
|
}
|
|
};
|
|
export {
|
|
decode,
|
|
isTokenHeader,
|
|
sign,
|
|
verify
|
|
};
|