@formancy/server 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{completers-CQQDS4qr.mjs → completers-DWLA1A-r.mjs} +49 -9
- package/dist/completers-DWLA1A-r.mjs.map +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/main.mjs +1 -1
- package/dist/main.mjs.map +1 -1
- package/package.json +4 -4
- package/dist/completers-CQQDS4qr.mjs.map +0 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
2
|
-
import { AUDIT_ACTIONS, auditedBy, authenticateApiKey, authenticateLocal, can, collectAbandonedFiles, completeBuilderRequest, createApiKey, createLocalUser, createSubmission, decodeSolution, exportCsv, formToFill, formVersion, healthOf, keepExamples, listForms, listSubmissions, listVersions, mintChallenge, offerUpload, publishForm, readExamples, replayDelivery, resolveForm, resumeDraft, saveDraft, screenUpload, setFormAccess, startDraft, verifySolution } from "@formancy/server-core";
|
|
2
|
+
import { AUDIT_ACTIONS, auditedBy, authenticateApiKey, authenticateLocal, can, collectAbandonedFiles, completeBuilderRequest, createApiKey, createLocalUser, createSubmission, decodeSolution, exportCsv, formToFill, formVersion, healthOf, keepExamples, listForms, listSubmissionPage, listSubmissions, listVersions, mintChallenge, offerUpload, publishForm, readExamples, replayDelivery, resolveForm, resumeDraft, saveDraft, screenUpload, setFormAccess, startDraft, summariseResponses, verifySolution } from "@formancy/server-core";
|
|
3
3
|
import { MODEL_REQUEST_KINDS } from "@formancy/builder-core";
|
|
4
4
|
import Fastify, { LogController } from "fastify";
|
|
5
5
|
import rateLimit from "@fastify/rate-limit";
|
|
6
6
|
import { hash, verify } from "@node-rs/argon2";
|
|
7
7
|
import { SignJWT, jwtVerify } from "jose";
|
|
8
8
|
import { METHODS, STATUS_CODES, validateHeaderName, validateHeaderValue } from "node:http";
|
|
9
|
-
import { and, desc, eq, inArray, isNull, lt, lte, max, ne, or } from "drizzle-orm";
|
|
9
|
+
import { and, desc, eq, inArray, isNull, lt, lte, max, ne, or, sql } from "drizzle-orm";
|
|
10
10
|
import { drizzle } from "drizzle-orm/postgres-js";
|
|
11
11
|
import { integer, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core";
|
|
12
12
|
import postgres from "postgres";
|
|
@@ -267,17 +267,18 @@ async function accessRoutes(app, { deps, requires, audit }) {
|
|
|
267
267
|
error: "invalid_access",
|
|
268
268
|
message: "allowedOrigins must be an array of strings, or absent for no allowlist."
|
|
269
269
|
});
|
|
270
|
+
const allowed = origins;
|
|
270
271
|
if (!(await setFormAccess(deps, {
|
|
271
272
|
path,
|
|
272
273
|
submit,
|
|
273
|
-
...
|
|
274
|
+
...allowed === void 0 ? {} : { allowedOrigins: allowed }
|
|
274
275
|
})).ok) return reply.code(404).send({ error: "unknown_form" });
|
|
275
276
|
await audit(request, {
|
|
276
277
|
action: "form.access.changed",
|
|
277
278
|
subject: path,
|
|
278
279
|
detail: {
|
|
279
280
|
submit,
|
|
280
|
-
allowedOrigins:
|
|
281
|
+
allowedOrigins: allowed === void 0 ? "unchanged" : allowed.join(" ")
|
|
281
282
|
}
|
|
282
283
|
});
|
|
283
284
|
return reply.code(204).send();
|
|
@@ -508,7 +509,7 @@ async function submissionRoutes(app, { deps, challengeSecret, limit, actorOf })
|
|
|
508
509
|
if (challengeSecret === void 0) return reply.code(404).send({ error: "challenge_not_enabled" });
|
|
509
510
|
const { path } = request.params;
|
|
510
511
|
if (await deps.storage.getFormByPath(path) === void 0) return reply.code(404).send({ error: "unknown_form" });
|
|
511
|
-
return reply.send(
|
|
512
|
+
return reply.send(mintChallenge({
|
|
512
513
|
secret: challengeSecret,
|
|
513
514
|
nowSeconds: Math.floor(new Date(deps.nowIso()).getTime() / 1e3)
|
|
514
515
|
}));
|
|
@@ -587,7 +588,7 @@ async function checkChallenge(deps, challengeSecret, request) {
|
|
|
587
588
|
message: "The challenge header is not base64 JSON."
|
|
588
589
|
};
|
|
589
590
|
const nowSeconds = Math.floor(new Date(deps.nowIso()).getTime() / 1e3);
|
|
590
|
-
const verified =
|
|
591
|
+
const verified = verifySolution(challengeSecret, solution, nowSeconds);
|
|
591
592
|
if (!verified.ok) return {
|
|
592
593
|
error: `challenge_${verified.reason}`,
|
|
593
594
|
message: "The challenge was not solved. Ask for a new one and try again."
|
|
@@ -1193,6 +1194,26 @@ async function createApp(storage, options) {
|
|
|
1193
1194
|
});
|
|
1194
1195
|
app.get("/f/:path/submissions", { preHandler: requires("submission.read") }, async (request, reply) => {
|
|
1195
1196
|
const { path } = request.params;
|
|
1197
|
+
const query = request.query ?? {};
|
|
1198
|
+
if (query.limit !== void 0 || query.after !== void 0) {
|
|
1199
|
+
const asked = typeof query.limit === "string" ? Number.parseInt(query.limit, 10) : 100;
|
|
1200
|
+
const after = query.after === void 0 ? void 0 : typeof query.after === "string" ? query.after : "";
|
|
1201
|
+
const page = await listSubmissionPage(deps, path, {
|
|
1202
|
+
limit: Number.isFinite(asked) ? asked : 100,
|
|
1203
|
+
after
|
|
1204
|
+
});
|
|
1205
|
+
if (page === void 0) return reply.code(404).send({ error: "unknown_form" });
|
|
1206
|
+
if (page.kind === "bad_cursor") return reply.code(400).send({ error: "bad_cursor" });
|
|
1207
|
+
await audit(request, {
|
|
1208
|
+
action: "submission.read",
|
|
1209
|
+
subject: path,
|
|
1210
|
+
detail: { count: page.submissions.length }
|
|
1211
|
+
});
|
|
1212
|
+
return reply.send({
|
|
1213
|
+
submissions: page.submissions,
|
|
1214
|
+
next: page.next
|
|
1215
|
+
});
|
|
1216
|
+
}
|
|
1196
1217
|
const listed = await listSubmissions(deps, path);
|
|
1197
1218
|
if (listed === void 0) return reply.code(404).send({ error: "unknown_form" });
|
|
1198
1219
|
await audit(request, {
|
|
@@ -1202,6 +1223,21 @@ async function createApp(storage, options) {
|
|
|
1202
1223
|
});
|
|
1203
1224
|
return reply.send({ submissions: listed });
|
|
1204
1225
|
});
|
|
1226
|
+
app.get("/f/:path/summary", { preHandler: requires("submission.read") }, async (request, reply) => {
|
|
1227
|
+
const { path } = request.params;
|
|
1228
|
+
const versions = await summariseResponses(deps, path);
|
|
1229
|
+
if (versions === void 0) return reply.code(404).send({ error: "unknown_form" });
|
|
1230
|
+
const count = versions.reduce((total, version) => total + version.responses, 0);
|
|
1231
|
+
await audit(request, {
|
|
1232
|
+
action: "submission.read",
|
|
1233
|
+
subject: path,
|
|
1234
|
+
detail: {
|
|
1235
|
+
count,
|
|
1236
|
+
summary: true
|
|
1237
|
+
}
|
|
1238
|
+
});
|
|
1239
|
+
return reply.send({ versions });
|
|
1240
|
+
});
|
|
1205
1241
|
app.get("/f/:path/submissions/export.csv", { preHandler: requires("submission.export") }, async (request, reply) => {
|
|
1206
1242
|
const { path } = request.params;
|
|
1207
1243
|
const csv = await exportCsv(deps, path);
|
|
@@ -1387,6 +1423,7 @@ async function bootstrapSchema(sql) {
|
|
|
1387
1423
|
data jsonb NOT NULL,
|
|
1388
1424
|
submitted_at timestamptz NOT NULL
|
|
1389
1425
|
)`;
|
|
1426
|
+
await sql`CREATE INDEX IF NOT EXISTS submissions_by_form ON submissions (form_id, submitted_at DESC, id DESC)`;
|
|
1390
1427
|
await sql`
|
|
1391
1428
|
CREATE TABLE IF NOT EXISTS webhooks (
|
|
1392
1429
|
id uuid PRIMARY KEY,
|
|
@@ -1638,8 +1675,8 @@ function toApiKeyRecord(row) {
|
|
|
1638
1675
|
}
|
|
1639
1676
|
//#endregion
|
|
1640
1677
|
//#region src/postgres-storage.ts
|
|
1641
|
-
function createPostgresStorage(sql) {
|
|
1642
|
-
const db = drizzle(sql);
|
|
1678
|
+
function createPostgresStorage(sql$1) {
|
|
1679
|
+
const db = drizzle(sql$1);
|
|
1643
1680
|
return {
|
|
1644
1681
|
...examplesStorage(db),
|
|
1645
1682
|
...identitiesStorage(db),
|
|
@@ -1873,6 +1910,9 @@ function createPostgresStorage(sql) {
|
|
|
1873
1910
|
async listSubmissionsByForm(formId) {
|
|
1874
1911
|
return (await db.select().from(submissions).where(eq(submissions.formId, formId)).orderBy(desc(submissions.submittedAt), desc(submissions.id))).map(toSubmissionRecord);
|
|
1875
1912
|
},
|
|
1913
|
+
async pageSubmissionsByForm(formId, limit, before) {
|
|
1914
|
+
return (await db.select().from(submissions).where(and(eq(submissions.formId, formId), before === void 0 ? void 0 : sql`(${submissions.submittedAt}, ${submissions.id}) < (${before.submittedAt}::timestamptz, ${before.id}::uuid)`)).orderBy(desc(submissions.submittedAt), desc(submissions.id)).limit(limit)).map(toSubmissionRecord);
|
|
1915
|
+
},
|
|
1876
1916
|
async listVersionsByForm(formId) {
|
|
1877
1917
|
return (await db.select().from(formVersions).where(eq(formVersions.formId, formId)).orderBy(desc(formVersions.version))).map((row) => ({
|
|
1878
1918
|
id: row.id,
|
|
@@ -2380,4 +2420,4 @@ function createCompleter(settings, fetch) {
|
|
|
2380
2420
|
//#endregion
|
|
2381
2421
|
export { SUBMISSION_TOKEN_HEADER as _, createLocalFileStore as a, createPostgresStorage as c, LOG_LEVELS as d, createServerLog as f, SCHEMA_HASH_HEADER as g, maxFileBytesFrom as h, startFileCollector as i, bootstrapSchema as l, workerLog as m, MODEL_PROVIDERS as n, sha256 as o, databaseNotices as p, modelSettings as r, createPostgresRateLimitStore as s, createCompleter as t, createApp as u };
|
|
2382
2422
|
|
|
2383
|
-
//# sourceMappingURL=completers-
|
|
2423
|
+
//# sourceMappingURL=completers-DWLA1A-r.mjs.map
|