mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-29 06:35:24 +00:00
chore: lock the plugin boundary and remove 2.9.0 leftovers
This commit is contained in:
1 parent
6489b62ece
commit
2961297989
399 files changed
+3964
-5627
No files matched your search
@@ -1,7 +1,6 @@
|
||||
/**
|
||||
* The 2.8 login URLs identity providers and 2.8 clients still call. They
|
||||
* forward to whatever replaced them and, with the method's plugin gone, sign
|
||||
* nobody in.
|
||||
* The 2.8 login URLs 2.8 clients still call. They forward to the login
|
||||
* pipeline and, with the method's plugin gone, sign nobody in.
|
||||
*/
|
||||
|
||||
import http from "node:http";
|
||||
@@ -64,43 +63,6 @@ function registerSso() {
|
||||
});
|
||||
}
|
||||
|
||||
describe("the old identity provider URLs", () => {
|
||||
it("forwards the callback to the sso plugin, query intact", async () => {
|
||||
const response = await get("/users/oidc/callback?code=abc&state=xyz");
|
||||
expect(response.status).toBe(308);
|
||||
expect(response.headers.get("location")).toBe(
|
||||
"/plugin-api/sso/callback?code=abc&state=xyz",
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards a form-posted callback with a 308 so the body is resent", async () => {
|
||||
const response = await fetch(`${base}/users/oidc/callback`, {
|
||||
method: "POST",
|
||||
redirect: "manual",
|
||||
});
|
||||
expect(response.status).toBe(308);
|
||||
});
|
||||
|
||||
it("forwards back-channel logout to the sso plugin", async () => {
|
||||
const response = await fetch(`${base}/users/oidc/backchannel-logout`, {
|
||||
method: "POST",
|
||||
redirect: "manual",
|
||||
});
|
||||
expect(response.status).toBe(308);
|
||||
expect(response.headers.get("location")).toMatch(
|
||||
/\/plugin-api\/sso\/backchannel-logout$/,
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards the public config to the sso plugin", async () => {
|
||||
const response = await get("/users/oidc-config");
|
||||
expect(response.status).toBe(307);
|
||||
expect(response.headers.get("location")).toMatch(
|
||||
/\/plugin-api\/sso\/config$/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the old client routes", () => {
|
||||
it("starts an SSO login through the oidc method", async () => {
|
||||
registerSso();
|
||||
|
||||
@@ -1,116 +0,0 @@
|
||||
/**
|
||||
* The OPKSSH, Step CA and Vault redirect URIs identity providers were registered with before
|
||||
* 2.9 must keep reaching the plugin's callback, query intact, and the Termix
|
||||
* ID resolver URLs servers fetch must keep reaching the plugin's resolver.
|
||||
*/
|
||||
|
||||
import http from "node:http";
|
||||
import type { AddressInfo } from "node:net";
|
||||
import express from "express";
|
||||
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
||||
|
||||
const { registerHostAuthCompatRoutes, registerTermixIdCompatRoutes } =
|
||||
await import("../../../database/routes/host-compat-routes.js");
|
||||
|
||||
let server: http.Server;
|
||||
let base: string;
|
||||
|
||||
beforeEach(async () => {
|
||||
const router = express.Router();
|
||||
registerHostAuthCompatRoutes(router);
|
||||
const app = express();
|
||||
app.use("/host", router);
|
||||
const termixId = express.Router();
|
||||
registerTermixIdCompatRoutes(termixId);
|
||||
app.use("/termix-id", termixId);
|
||||
server = http.createServer(app);
|
||||
await new Promise<void>((resolve) => server.listen(0, resolve));
|
||||
base = `http://127.0.0.1:${(server.address() as AddressInfo).port}`;
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
await new Promise<void>((resolve) => server.close(() => resolve()));
|
||||
});
|
||||
|
||||
describe("the old OPKSSH callback", () => {
|
||||
it("forwards to the plugin's callback with the query", async () => {
|
||||
const response = await fetch(
|
||||
`${base}/host/opkssh-callback?code=abc&state=xyz`,
|
||||
{ redirect: "manual" },
|
||||
);
|
||||
expect(response.status).toBe(307);
|
||||
expect(response.headers.get("location")).toBe(
|
||||
"/plugin-api/opkssh/callback?code=abc&state=xyz",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps a subpath", async () => {
|
||||
const response = await fetch(`${base}/host/opkssh-callback/req-1/done`, {
|
||||
redirect: "manual",
|
||||
});
|
||||
expect(response.headers.get("location")).toBe(
|
||||
"/plugin-api/opkssh/callback/req-1/done",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the old Step CA callback", () => {
|
||||
it("forwards to the plugin's callback with the query", async () => {
|
||||
const response = await fetch(
|
||||
`${base}/host/step-ca-callback?code=abc&state=xyz`,
|
||||
{ redirect: "manual" },
|
||||
);
|
||||
expect(response.status).toBe(307);
|
||||
expect(response.headers.get("location")).toBe(
|
||||
"/plugin-api/step-ca/callback?code=abc&state=xyz",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the old Vault callback", () => {
|
||||
it("forwards to the plugin's callback with the query", async () => {
|
||||
const { registerVaultCompatRoutes } =
|
||||
await import("../../../database/routes/host-compat-routes.js");
|
||||
const router = express.Router();
|
||||
registerVaultCompatRoutes(router);
|
||||
const app = express();
|
||||
app.use("/vault", router);
|
||||
const vaultServer = http.createServer(app);
|
||||
await new Promise<void>((resolve) => vaultServer.listen(0, resolve));
|
||||
const port = (vaultServer.address() as AddressInfo).port;
|
||||
try {
|
||||
const response = await fetch(
|
||||
`http://127.0.0.1:${port}/vault/oidc/callback?code=abc&state=xyz`,
|
||||
{ redirect: "manual" },
|
||||
);
|
||||
expect(response.status).toBe(307);
|
||||
expect(response.headers.get("location")).toBe(
|
||||
"/plugin-api/vault/oidc/callback?code=abc&state=xyz",
|
||||
);
|
||||
} finally {
|
||||
await new Promise<void>((resolve) => vaultServer.close(() => resolve()));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("the old Termix ID resolver URLs", () => {
|
||||
it.each([
|
||||
["/termix-id/u/alice", "/plugin-api/termix-identity/u/alice"],
|
||||
[
|
||||
"/termix-id/u/alice/ED25519",
|
||||
"/plugin-api/termix-identity/u/alice/ED25519",
|
||||
],
|
||||
["/termix-id/u/alice/ca", "/plugin-api/termix-identity/u/alice/ca"],
|
||||
])("permanently redirects %s", async (path, target) => {
|
||||
const response = await fetch(`${base}${path}`, { redirect: "manual" });
|
||||
expect(response.status).toBe(308);
|
||||
expect(response.headers.get("location")).toBe(target);
|
||||
});
|
||||
|
||||
it("does not forward the old management routes", async () => {
|
||||
const response = await fetch(`${base}/termix-id/me`, {
|
||||
redirect: "manual",
|
||||
});
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
});
|
||||
@@ -1,6 +1,8 @@
|
||||
/**
|
||||
* contributes.http.legacyPaths: an old URL under "/<plugin id>/" reaches the
|
||||
* plugin's router with that prefix removed, and nothing else is caught.
|
||||
* contributes.http.legacyRedirects: an old URL anywhere redirects to the
|
||||
* plugin's own URL, keeping the subpath and the query.
|
||||
*/
|
||||
|
||||
import express from "express";
|
||||
@@ -16,10 +18,20 @@ vi.mock("../../../plugins/http.js", () => ({
|
||||
const { mountPluginLegacyPaths } =
|
||||
await import("../../../database/routes/plugin-api-routes.js");
|
||||
|
||||
function appWith(active: Array<{ id: string; legacyPaths: string[] }>) {
|
||||
type Active = Parameters<typeof mountPluginLegacyPaths>[1] extends () => Array<
|
||||
infer T
|
||||
>
|
||||
? T
|
||||
: never;
|
||||
|
||||
function appWith(
|
||||
active: Array<Omit<Active, "legacyRedirects"> & Partial<Active>>,
|
||||
) {
|
||||
const app = express();
|
||||
app.get("/core/route", (_req, res) => res.json({ from: "core" }));
|
||||
mountPluginLegacyPaths(app, () => active);
|
||||
mountPluginLegacyPaths(app, () =>
|
||||
active.map((entry) => ({ legacyRedirects: [], ...entry })),
|
||||
);
|
||||
app.use((_req, res) => res.status(404).json({ from: "fallthrough" }));
|
||||
return app;
|
||||
}
|
||||
@@ -58,3 +70,37 @@ describe("mountPluginLegacyPaths", () => {
|
||||
expect(response.status).toBe(404);
|
||||
});
|
||||
});
|
||||
|
||||
describe("legacy redirects", () => {
|
||||
const app = appWith([
|
||||
{
|
||||
id: "idp",
|
||||
legacyPaths: [],
|
||||
legacyRedirects: [
|
||||
{ from: "/host/idp-callback", to: "/callback" },
|
||||
{ from: "/old-id/u", to: "/u", status: 308 },
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
it("redirects to the plugin's URL with the query", async () => {
|
||||
const response = await request(app).get(
|
||||
"/host/idp-callback?code=abc&state=xyz",
|
||||
);
|
||||
expect(response.status).toBe(307);
|
||||
expect(response.headers.location).toBe(
|
||||
"/plugin-api/idp/callback?code=abc&state=xyz",
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps a subpath and honours a permanent status", async () => {
|
||||
const response = await request(app).get("/old-id/u/alice/ca");
|
||||
expect(response.status).toBe(308);
|
||||
expect(response.headers.location).toBe("/plugin-api/idp/u/alice/ca");
|
||||
});
|
||||
|
||||
it("does not catch a path that only shares a prefix", async () => {
|
||||
const response = await request(app).get("/host/idp-callbacks");
|
||||
expect(response.body).toEqual({ from: "fallthrough" });
|
||||
});
|
||||
});
|
||||
@@ -35,7 +35,7 @@ const state = vi.hoisted(() => ({
|
||||
active: new Set<string>(),
|
||||
}));
|
||||
|
||||
vi.mock("../../../plugins/boot-migrations.js", () => ({
|
||||
vi.mock("../../../upgrade/boot-migrations.js", () => ({
|
||||
runPluginDataMigrations: async () => {},
|
||||
}));
|
||||
|
||||
|
||||
Reference in new issue
Block a user