@kilterset/auth0-actions-testing
Version:
Test and develop Auth0 Actions or Okta CIC Actions locally.
266 lines • 14.9 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const node_test_1 = __importDefault(require("node:test"));
const node_assert_1 = require("node:assert");
const api_1 = require("../../mock/api");
const mock_1 = require("../../mock");
const hs256_1 = require("../../jwt/hs256");
(0, node_test_1.default)("Post Challenge API", (t) => __awaiter(void 0, void 0, void 0, function* () {
yield t.test("access", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { implementation: api, state } = (0, api_1.postChallenge)();
(0, node_assert_1.strictEqual)(api.access.deny("Only cool kids allowed"), api);
(0, node_assert_1.deepStrictEqual)(state.access.denied, {
reason: "Only cool kids allowed",
});
}));
yield t.test("authentication", (t) => __awaiter(void 0, void 0, void 0, function* () {
yield t.test("challenge", (t) => __awaiter(void 0, void 0, void 0, function* () {
yield t.test("is false by default", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { implementation: api, state } = (0, api_1.postChallenge)();
(0, node_assert_1.strictEqual)(state.authentication.challenge, false);
}));
yield t.test("can be set with a default", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { implementation: api, state } = (0, api_1.postChallenge)();
(0, node_assert_1.strictEqual)(api.authentication.challengeWith({ type: "otp" }), undefined);
(0, node_assert_1.deepStrictEqual)(state.authentication.challenge, {
default: { type: "otp" },
allOptions: [{ type: "otp" }],
});
}));
yield t.test("can be set with a default and alternatives", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { implementation: api, state } = (0, api_1.postChallenge)();
(0, node_assert_1.strictEqual)(api.authentication.challengeWith({
type: "phone",
options: { preferredMethod: "voice" },
}, {
additionalFactors: [
{ type: "email" },
{ type: "webauthn-roaming" },
],
}), undefined);
(0, node_assert_1.deepStrictEqual)(state.authentication.challenge, {
default: {
type: "phone",
options: { preferredMethod: "voice" },
},
allOptions: [
{
type: "phone",
options: { preferredMethod: "voice" },
},
{ type: "email" },
{ type: "webauthn-roaming" },
],
});
}));
yield t.test("can be set with options and no default", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { implementation: api, state } = (0, api_1.postChallenge)();
const factors = [{ type: "email" }, { type: "webauthn-roaming" }];
(0, node_assert_1.strictEqual)(api.authentication.challengeWithAny(factors), undefined);
(0, node_assert_1.deepStrictEqual)(state.authentication.challenge, {
default: undefined,
allOptions: factors,
});
}));
}));
}));
yield t.test("redirect", (t) => __awaiter(void 0, void 0, void 0, function* () {
yield t.test("sendUserTo", (t) => __awaiter(void 0, void 0, void 0, function* () {
yield t.test("simple redirect", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { implementation: api, state } = (0, api_1.postChallenge)();
(0, node_assert_1.strictEqual)(api.redirect.sendUserTo("https://example.com/r"), api);
const { redirect } = state;
(0, node_assert_1.ok)(redirect, "redirect not set");
(0, node_assert_1.deepStrictEqual)(redirect.queryParams, {}, "query should be empty");
(0, node_assert_1.strictEqual)(redirect.url.href, "https://example.com/r", "url mismatch");
}));
yield t.test("redirect with consolidated GET parameters", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { implementation: api, state } = (0, api_1.postChallenge)();
(0, node_assert_1.strictEqual)(api.redirect.sendUserTo("https://example.com?bread=rye", {
query: { filling: "cheese", spread: "butter" },
}), api);
const { redirect } = state;
(0, node_assert_1.ok)(redirect, "redirect not set");
(0, node_assert_1.deepStrictEqual)(redirect.queryParams, { bread: "rye", filling: "cheese", spread: "butter" }, "unexpected query");
(0, node_assert_1.strictEqual)(redirect.url.href, "https://example.com/?bread=rye&filling=cheese&spread=butter", "url mismatch");
}));
}));
yield t.test("encodeToken", (t) => __awaiter(void 0, void 0, void 0, function* () {
const now = new Date("2024-01-01T00:00:00.000Z");
const nowUnixTimestamp = now.getTime() / 1000;
const { implementation: api } = (0, api_1.postChallenge)({
request: (0, mock_1.request)({
hostname: "example.com",
ip: "d666:171e:e7a4:1aa3:359a:a317:9f53:ee97",
}),
user: (0, mock_1.user)({ user_id: "auth0|8150" }),
now,
});
const token = api.redirect.encodeToken({
expiresInSeconds: 42,
payload: { foo: "bar" },
secret: "shh",
});
const [header, payload, signature] = token.split(".");
const decodedHeader = JSON.parse(atob(header));
const decodedPayload = JSON.parse(atob(payload));
(0, node_assert_1.deepStrictEqual)(decodedHeader, { alg: "HS256", typ: "JWT" }, "unexpected JWT header");
(0, node_assert_1.deepStrictEqual)(decodedPayload, {
iss: "example.com",
iat: nowUnixTimestamp,
exp: nowUnixTimestamp + 42,
sub: "auth0|8150",
ip: "d666:171e:e7a4:1aa3:359a:a317:9f53:ee97",
foo: "bar",
}, "unexpected claims");
(0, node_assert_1.strictEqual)(signature, "ZlLKLk7uJzDjD0nt2a08QiWMY1EPnhFIuc8WsSZPBvQ", "invalid signature");
}));
yield t.test("validateToken", (t) => __awaiter(void 0, void 0, void 0, function* () {
const VALID_SECRET = "shh";
const TOKEN_PAYLOAD = {
sub: "auth0|7321",
iss: "myapp.com",
iat: 1711509300,
exp: 1711509800,
state: "opaque-random-state",
foo: "bar",
};
const VALID_STATE = TOKEN_PAYLOAD.state;
const VALID_TOKEN = (0, hs256_1.encodeHS256JWT)({
secret: VALID_SECRET,
claims: TOKEN_PAYLOAD,
});
const VALID_CONTEXT = {
now: TOKEN_PAYLOAD.iat * 1000,
user: (0, mock_1.user)({ user_id: TOKEN_PAYLOAD.sub }),
request: (0, mock_1.request)({
method: "POST",
body: {
session_token: VALID_TOKEN,
state: VALID_STATE,
},
}),
};
yield t.test("decodes a valid POST token", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { now, user } = VALID_CONTEXT;
const { implementation: api } = (0, api_1.postChallenge)({
now,
user,
request: (0, mock_1.request)({
method: "POST",
body: {
session_token: VALID_TOKEN,
state: VALID_STATE,
},
}),
});
const payload = api.redirect.validateToken({ secret: VALID_SECRET });
(0, node_assert_1.deepStrictEqual)(payload, TOKEN_PAYLOAD, "unexpected payload");
}));
yield t.test("decodes a valid GET token", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { now, user } = VALID_CONTEXT;
const { implementation: api } = (0, api_1.postChallenge)({
now,
user,
request: (0, mock_1.request)({
method: "GET",
query: {
session_token: VALID_TOKEN,
state: VALID_STATE,
},
}),
});
const payload = api.redirect.validateToken({ secret: VALID_SECRET });
(0, node_assert_1.deepStrictEqual)(payload, TOKEN_PAYLOAD, "unexpected payload");
}));
yield t.test("can use an alternative parameter", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { now, user } = VALID_CONTEXT;
const { implementation: api } = (0, api_1.postChallenge)({
now,
user,
request: (0, mock_1.request)({
method: "POST",
body: {
a_token_param: VALID_TOKEN,
state: VALID_STATE,
},
}),
});
const payload = api.redirect.validateToken({
secret: VALID_SECRET,
tokenParameterName: "a_token_param",
});
(0, node_assert_1.deepStrictEqual)(payload, TOKEN_PAYLOAD, "unexpected payload");
}));
yield t.test("throws error if state is mismatched", (t) => __awaiter(void 0, void 0, void 0, function* () {
const context = structuredClone(VALID_CONTEXT);
context.request.body.state = "mismatched-from-token-value";
const { implementation: api } = (0, api_1.postChallenge)(context);
(0, node_assert_1.throws)(() => api.redirect.validateToken({ secret: VALID_SECRET }), /state in the token/i);
}));
yield t.test("throws error if param is missing", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { implementation: api } = (0, api_1.postChallenge)(VALID_CONTEXT);
(0, node_assert_1.throws)(() => api.redirect.validateToken({
secret: VALID_SECRET,
tokenParameterName: "a_token_param",
}), /no parameter called 'a_token_param'/i);
}));
yield t.test("throws error if expired", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { user, request } = VALID_CONTEXT;
const { implementation: api } = (0, api_1.postChallenge)({
now: TOKEN_PAYLOAD.exp * 1000, // exactly the expiry time
user,
request,
});
(0, node_assert_1.throws)(() => api.redirect.validateToken({ secret: VALID_SECRET }), /expired/i);
}));
yield t.test("throws error if signature is invalid", (t) => __awaiter(void 0, void 0, void 0, function* () {
const context = structuredClone(VALID_CONTEXT);
context.request.body.session_token = VALID_TOKEN.replace(/\.[^.]+$/, ".badsignature");
const { implementation: api } = (0, api_1.postChallenge)(context);
(0, node_assert_1.throws)(() => api.redirect.validateToken({ secret: VALID_SECRET }), /signature/i);
}));
for (const claim of ["sub", "iss", "iat", "exp"]) {
yield t.test(`throws error if ${claim} is missing`, (t) => __awaiter(void 0, void 0, void 0, function* () {
const context = structuredClone(VALID_CONTEXT);
const claims = structuredClone(TOKEN_PAYLOAD);
(0, node_assert_1.ok)(claim in claims, "claim not in payload");
delete claims[claim];
context.request.body.session_token = (0, hs256_1.encodeHS256JWT)({
secret: VALID_SECRET,
claims,
});
const { implementation: api } = (0, api_1.postChallenge)(context);
(0, node_assert_1.throws)(() => api.redirect.validateToken({ secret: VALID_SECRET }), /missing or invalid standard claims/i);
}));
}
}));
}));
yield t.test("cache", (t) => __awaiter(void 0, void 0, void 0, function* () {
yield t.test("can set cache", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { implementation: api, state } = (0, api_1.postChallenge)();
(0, node_assert_1.strictEqual)(api.cache.set("location", "Ōtautahi").type, "success");
(0, node_assert_1.deepStrictEqual)(state.cache.get("location"), "Ōtautahi");
}));
yield t.test("can get cache", (t) => __awaiter(void 0, void 0, void 0, function* () {
const { implementation: api, state } = (0, api_1.postChallenge)({
cache: { location: "Ōtautahi" },
});
(0, node_assert_1.strictEqual)(state.cache.get("location"), "Ōtautahi");
(0, node_assert_1.strictEqual)(state.cache.get("nonexistent"), undefined);
}));
}));
}));
//# sourceMappingURL=post-challenge.test.js.map