Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 | 18x 7x 7x 6x 6x 6x 6x 6x 6x 6x 6x 4x 4x 4x 4x 4x 4x 4x 4x 4x 4x 1x 3x 3x 3x 3x 3x 3x 3x 3x 1x 2x | import { createHmac, timingSafeEqual } from "node:crypto";
import { CLIENT_CREDENTIALS_GRANT, TOKEN_EXPIRATION_SECONDS } from "./constants";
import { ExternalAccessTokenClaims, ExternalApiAuthConfig, ExternalApiClient } from "./types";
type JwtHeader = {
alg: "HS256";
typ: "JWT";
};
function toBase64Url(value: Buffer | string): string {
return Buffer.from(value).toString("base64url");
}
function parseJsonBase64Url<T>(value: string): T | null {
try {
return JSON.parse(Buffer.from(value, "base64url").toString("utf8")) as T;
} catch {
return null;
}
}
export function createExternalAccessToken(
config: ExternalApiAuthConfig,
client: ExternalApiClient,
): string {
const now = Math.floor(Date.now() / 1000);
const payload: ExternalAccessTokenClaims = {
iss: config.issuer,
sub: client.clientId,
client_id: client.clientId,
grant_type: CLIENT_CREDENTIALS_GRANT,
grants: client.grants,
token_use: "access",
iat: now,
exp: now + TOKEN_EXPIRATION_SECONDS,
};
const header: JwtHeader = {
alg: "HS256",
typ: "JWT",
};
const encodedHeader = toBase64Url(JSON.stringify(header));
const encodedPayload = toBase64Url(JSON.stringify(payload));
const signingInput = `${encodedHeader}.${encodedPayload}`;
const signature = createHmac("sha256", config.jwtSigningKey).update(signingInput).digest();
return `${signingInput}.${toBase64Url(signature)}`;
}
export function verifyExternalAccessToken(
token: string,
config: ExternalApiAuthConfig,
): ExternalAccessTokenClaims | null {
const parts = token.split(".");
Iif (parts.length !== 3) {
return null;
}
const [encodedHeader, encodedPayload, encodedSignature] = parts;
const header = parseJsonBase64Url<JwtHeader>(encodedHeader);
Iif (!header || header.alg !== "HS256" || header.typ !== "JWT") {
return null;
}
const expectedSignature = createHmac("sha256", config.jwtSigningKey)
.update(`${encodedHeader}.${encodedPayload}`)
.digest();
let providedSignature: Buffer;
try {
providedSignature = Buffer.from(encodedSignature, "base64url");
} catch {
return null;
}
Iif (expectedSignature.length !== providedSignature.length) {
return null;
}
if (!timingSafeEqual(expectedSignature, providedSignature)) {
return null;
}
const payload = parseJsonBase64Url<ExternalAccessTokenClaims>(encodedPayload);
Iif (!payload) {
return null;
}
Iif (
payload.iss !== config.issuer ||
payload.grant_type !== CLIENT_CREDENTIALS_GRANT ||
payload.token_use !== "access"
) {
return null;
}
Iif (
typeof payload.client_id !== "string" ||
payload.client_id.trim() === "" ||
typeof payload.sub !== "string" ||
payload.sub.trim() === "" ||
!Array.isArray(payload.grants) ||
payload.grants.some((grant) => typeof grant !== "string")
) {
return null;
}
Iif (typeof payload.exp !== "number" || typeof payload.iat !== "number") {
return null;
}
const now = Math.floor(Date.now() / 1000);
if (payload.exp <= now) {
return null;
}
return payload;
}
|