feat(server): implement HTTP auth and page management functionality
- Introduced new HTTP authentication and page management APIs, allowing for the creation, retrieval, updating, and deletion of HTTP auth profiles and pages. - Added validation for auth and page fields to ensure proper configuration and error handling. - Implemented a mechanism for resolving auth credentials from various sources, including inline definitions, KV store, and secrets. - Enhanced workflow validation to include checks for HTTP triggers, ensuring proper auth and response configurations. - Updated the web interface to include new routes for managing HTTP auth profiles and pages, improving user experience and accessibility.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
import {
|
||||
assertAuthName,
|
||||
assertAuthType,
|
||||
listHttpAuths,
|
||||
getHttpAuthById,
|
||||
getHttpAuthByName,
|
||||
upsertHttpAuth,
|
||||
deleteHttpAuth,
|
||||
} from "../../http-auths-store.js";
|
||||
import { getHttpPageByName } from "../../http-pages-store.js";
|
||||
|
||||
/**
|
||||
* @param {import("fastify").FastifyInstance} fastify
|
||||
*/
|
||||
export default async function httpAuthsPlugin(fastify) {
|
||||
fastify.get("/http-auths", async () => {
|
||||
return { auths: await listHttpAuths() };
|
||||
});
|
||||
|
||||
fastify.get("/http-auths/:name", async (req, reply) => {
|
||||
const { name } = /** @type {{ name: string }} */ (req.params);
|
||||
try {
|
||||
assertAuthName(name);
|
||||
} catch (err) {
|
||||
return reply.code(err.statusCode ?? 400).send({ error: err.message });
|
||||
}
|
||||
const auth = await getHttpAuthByName(name);
|
||||
if (!auth) {
|
||||
return reply.code(404).send({ error: "auth not found" });
|
||||
}
|
||||
return { auth };
|
||||
});
|
||||
|
||||
fastify.put("/http-auths", async (req, reply) => {
|
||||
const body = /** @type {{
|
||||
name?: string,
|
||||
type?: string,
|
||||
config?: unknown,
|
||||
unauthorized_status?: number | null,
|
||||
unauthorized_response?: string | null,
|
||||
}} */ (req.body ?? {});
|
||||
try {
|
||||
assertAuthName(String(body.name ?? ""));
|
||||
assertAuthType(body.type);
|
||||
if (
|
||||
body.unauthorized_response != null &&
|
||||
String(body.unauthorized_response).length > 0
|
||||
) {
|
||||
const page = await getHttpPageByName(String(body.unauthorized_response));
|
||||
if (!page) {
|
||||
return reply
|
||||
.code(400)
|
||||
.send({ error: `unknown response page "${body.unauthorized_response}"` });
|
||||
}
|
||||
}
|
||||
const auth = await upsertHttpAuth({
|
||||
name: String(body.name),
|
||||
type: String(body.type),
|
||||
config: body.config,
|
||||
unauthorized_status: body.unauthorized_status,
|
||||
unauthorized_response: body.unauthorized_response,
|
||||
});
|
||||
return reply.send({ auth });
|
||||
} catch (err) {
|
||||
return reply.code(err.statusCode ?? 500).send({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
fastify.delete("/http-auths/:id", async (req, reply) => {
|
||||
const { id } = /** @type {{ id: string }} */ (req.params);
|
||||
const existing = await getHttpAuthById(id);
|
||||
if (!existing) {
|
||||
return reply.code(404).send({ error: "auth not found" });
|
||||
}
|
||||
await deleteHttpAuth(id);
|
||||
return { ok: true };
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import {
|
||||
assertPageName,
|
||||
assertMime,
|
||||
assertHttpStatus,
|
||||
listHttpPages,
|
||||
getHttpPageById,
|
||||
getHttpPageByName,
|
||||
upsertHttpPage,
|
||||
deleteHttpPage,
|
||||
} from "../../http-pages-store.js";
|
||||
|
||||
/**
|
||||
* @param {import("fastify").FastifyInstance} fastify
|
||||
*/
|
||||
export default async function httpPagesPlugin(fastify) {
|
||||
fastify.get("/http-pages", async () => {
|
||||
return { pages: await listHttpPages() };
|
||||
});
|
||||
|
||||
fastify.get("/http-pages/:name", async (req, reply) => {
|
||||
const { name } = /** @type {{ name: string }} */ (req.params);
|
||||
try {
|
||||
assertPageName(name);
|
||||
} catch (err) {
|
||||
return reply.code(err.statusCode ?? 400).send({ error: err.message });
|
||||
}
|
||||
const page = await getHttpPageByName(name);
|
||||
if (!page) {
|
||||
return reply.code(404).send({ error: "page not found" });
|
||||
}
|
||||
return { page };
|
||||
});
|
||||
|
||||
fastify.put("/http-pages", async (req, reply) => {
|
||||
const body = /** @type {{
|
||||
name?: string,
|
||||
content?: string,
|
||||
mime?: string,
|
||||
status?: number,
|
||||
}} */ (req.body ?? {});
|
||||
try {
|
||||
assertPageName(String(body.name ?? ""));
|
||||
assertMime(body.mime);
|
||||
assertHttpStatus(body.status, 200);
|
||||
if (typeof body.content !== "string") {
|
||||
return reply.code(400).send({ error: "content must be a string" });
|
||||
}
|
||||
const page = await upsertHttpPage({
|
||||
name: String(body.name),
|
||||
content: body.content,
|
||||
mime: String(body.mime),
|
||||
status: body.status,
|
||||
});
|
||||
return reply.send({ page });
|
||||
} catch (err) {
|
||||
return reply.code(err.statusCode ?? 500).send({ error: err.message });
|
||||
}
|
||||
});
|
||||
|
||||
fastify.delete("/http-pages/:id", async (req, reply) => {
|
||||
const { id } = /** @type {{ id: string }} */ (req.params);
|
||||
const existing = await getHttpPageById(id);
|
||||
if (!existing) {
|
||||
return reply.code(404).send({ error: "page not found" });
|
||||
}
|
||||
await deleteHttpPage(id);
|
||||
return { ok: true };
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,10 @@ import {
|
||||
namespacedPath,
|
||||
parseScriptStep,
|
||||
} from "../../workflow-parse.js";
|
||||
import {
|
||||
authLabel,
|
||||
validateWorkflowHttpTriggers,
|
||||
} from "../../workflow-http-validate.js";
|
||||
|
||||
function triggerSummary(owner, workflow) {
|
||||
if (!workflow || typeof workflow !== "object") return [];
|
||||
@@ -17,6 +21,7 @@ function triggerSummary(owner, workflow) {
|
||||
method: isHttp ? String(t?.method ?? "POST").toUpperCase() : t?.method ?? null,
|
||||
path: isHttp && t?.path != null ? namespacedPath(owner, t.path) : t?.path ?? null,
|
||||
schedule: t?.schedule ?? null,
|
||||
auth: isHttp ? authLabel(t?.auth) : null,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -180,6 +185,13 @@ export default function workflowsPluginFactory(registry) {
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
try {
|
||||
await validateWorkflowHttpTriggers(parsed);
|
||||
} catch (err) {
|
||||
return reply.code(err.statusCode ?? 400).send({
|
||||
error: err instanceof Error ? err.message : String(err),
|
||||
});
|
||||
}
|
||||
const existed = fsStore.readWorkflowYaml(owner, file) != null;
|
||||
fsStore.writeWorkflowYaml(owner, file, body.content);
|
||||
const registered = fsStore.readRegisters(owner);
|
||||
|
||||
Reference in New Issue
Block a user