@adminiumjs/manifest 0.3.39 → 0.3.40
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/forms.d.ts +111 -0
- package/dist/forms.d.ts.map +1 -0
- package/dist/forms.js +224 -0
- package/dist/forms.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/dist/mail-class.d.ts +11 -3
- package/dist/mail-class.d.ts.map +1 -1
- package/dist/mail-class.js +13 -1
- package/dist/mail-class.js.map +1 -1
- package/dist/mail-classes.d.ts +7 -0
- package/dist/mail-classes.d.ts.map +1 -1
- package/dist/mail-classes.js +6 -0
- package/dist/mail-classes.js.map +1 -1
- package/dist/outbox.d.ts +7 -0
- package/dist/outbox.d.ts.map +1 -1
- package/dist/outbox.js +66 -1
- package/dist/outbox.js.map +1 -1
- package/dist/public-access.d.ts +18 -0
- package/dist/public-access.d.ts.map +1 -1
- package/dist/public-access.js +72 -3
- package/dist/public-access.js.map +1 -1
- package/dist/schema.d.ts +83 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +79 -3
- package/dist/schema.js.map +1 -1
- package/dist/words.d.ts +1 -1
- package/dist/words.d.ts.map +1 -1
- package/dist/words.js +22 -0
- package/dist/words.js.map +1 -1
- package/package.json +2 -2
package/dist/public-access.js
CHANGED
|
@@ -31,12 +31,29 @@ import { publicConsentSchema } from './people.js';
|
|
|
31
31
|
import { kindOf, rulesOf } from './capacity.js';
|
|
32
32
|
import { formulaColumns } from './formula.js';
|
|
33
33
|
import { refSchema, scalarSchema, settingRefSchema, valueFits, } from './refs.js';
|
|
34
|
+
import { FORMS_BECAME_TABLE_MAX } from './forms.js';
|
|
34
35
|
import { momentIssues, momentOffsetSchema, plainMomentSchema, wallTimeSchema } from './refs.js';
|
|
35
36
|
import { conditionIssues, linkedConditionSchema, reachedOnlyByUndo, stateConditionSchema } from './states.js';
|
|
36
37
|
/** The fewest characters (its prefix apart) of a code that opens its own row (`unlockBy.self`). */
|
|
37
38
|
export const SELF_UNLOCK_MIN_LENGTH = 10;
|
|
38
39
|
/** The key every entry uses unless it names another. */
|
|
39
40
|
export const CUSTOMER_KEY = 'customer';
|
|
41
|
+
/**
|
|
42
|
+
* The reserved key name of the entries that open a table to FORMS: an answer
|
|
43
|
+
* to a form may become a new row of the table, through this entry's
|
|
44
|
+
* `writable` columns, `defaults` and `anonymous` limits. No browser key of
|
|
45
|
+
* this name is ever made, and the public API never serves such an entry: it
|
|
46
|
+
* is read by the forms door alone, so opening a table to forms does not open
|
|
47
|
+
* it to the public API.
|
|
48
|
+
*/
|
|
49
|
+
export const FORMS_KEY = 'forms';
|
|
50
|
+
/**
|
|
51
|
+
* What a forms entry may say. A form only ever creates a row (it never reads
|
|
52
|
+
* one back, changes one or signs anyone in), so every word about reading,
|
|
53
|
+
* claims, sessions, children, files and the entry's own confirmation mail is
|
|
54
|
+
* left out: forms send their own.
|
|
55
|
+
*/
|
|
56
|
+
export const FORMS_ENTRY_WORDS = ['table', 'methods', 'key', 'writable', 'writableValues', 'defaults', 'requires', 'agrees', 'anonymous', 'requireSetting', 'module'];
|
|
40
57
|
/** The longest a plain-text value may be unless its column says longer: a name. */
|
|
41
58
|
export const PLAIN_TEXT_MAX = 80;
|
|
42
59
|
/** The most digits a plain-text column may take in all: "table 12", "2 without onions", never a phone number. */
|
|
@@ -699,6 +716,9 @@ export const publicKeysSchema = z
|
|
|
699
716
|
.record(keyNameSchema, publicKeySchema)
|
|
700
717
|
.refine((keys) => !(CUSTOMER_KEY in keys) || Object.keys(keys[CUSTOMER_KEY] ?? {}).every((field) => field === 'peak'), {
|
|
701
718
|
message: '"customer" is the app\'s own key: only its peak is said here',
|
|
719
|
+
})
|
|
720
|
+
.refine((keys) => !(FORMS_KEY in keys), {
|
|
721
|
+
message: '"forms" is the name of the entries forms write through, and no browser key: name the key otherwise',
|
|
702
722
|
});
|
|
703
723
|
/**
|
|
704
724
|
* The own links of the rows a person of `identityTable` holds: each table a
|
|
@@ -765,6 +785,53 @@ export function resendAddressOf(outbox, kind, table, columnsOf) {
|
|
|
765
785
|
return null;
|
|
766
786
|
return { columns, ...(person === undefined ? {} : { person }) };
|
|
767
787
|
}
|
|
788
|
+
/** Whether anyone at all may read rows through an entry: no claim, no session, no person, no parent row it is read with. */
|
|
789
|
+
function readByAnyone(entry) {
|
|
790
|
+
return (entry.methods.includes('GET') &&
|
|
791
|
+
entry.kind !== 'availability' &&
|
|
792
|
+
entry.claim === undefined &&
|
|
793
|
+
entry.claimedBy === undefined &&
|
|
794
|
+
entry.identity === undefined &&
|
|
795
|
+
entry.visibleWith === undefined &&
|
|
796
|
+
entry.level === undefined);
|
|
797
|
+
}
|
|
798
|
+
/**
|
|
799
|
+
* A forms entry's own rules: it creates (POST) and nothing else, under the
|
|
800
|
+
* anonymous limits every stranger's create has, with the columns a form may
|
|
801
|
+
* fill named; one per table; and no entry on any key lets anyone read the
|
|
802
|
+
* rows strangers add through it.
|
|
803
|
+
*/
|
|
804
|
+
function formsEntryIssues(entry, i, entries, appKey) {
|
|
805
|
+
const out = [];
|
|
806
|
+
const at = (...rest) => ['publicAccess', i, ...rest];
|
|
807
|
+
// Each answer keeps the row it became by its table's stored name: it must fit an answers table, whole.
|
|
808
|
+
const stored = appKey === undefined ? 0 : appKey.length + 1 + entry.table.length;
|
|
809
|
+
if (stored > FORMS_BECAME_TABLE_MAX) {
|
|
810
|
+
out.push({ path: at('table'), message: `a form's answer names the row it became by "<app>:<table>", at most ${String(FORMS_BECAME_TABLE_MAX)} characters: "${appKey ?? ''}:${entry.table}" is ${String(stored)}` });
|
|
811
|
+
}
|
|
812
|
+
const allowed = FORMS_ENTRY_WORDS;
|
|
813
|
+
for (const word of Object.keys(entry)) {
|
|
814
|
+
if (!allowed.includes(word))
|
|
815
|
+
out.push({ path: at(word), message: `a forms entry only lets a form add a row: "${word}" is not said on it` });
|
|
816
|
+
}
|
|
817
|
+
if (entry.methods.length !== 1 || entry.methods[0] !== 'POST')
|
|
818
|
+
out.push({ path: at('methods'), message: 'a forms entry adds a row and does nothing else: its methods are ["POST"]' });
|
|
819
|
+
if (entry.anonymous === undefined)
|
|
820
|
+
out.push({ path: at('anonymous'), message: 'a form is sent by strangers: a forms entry says its anonymous limits' });
|
|
821
|
+
if ((entry.writable ?? []).length === 0)
|
|
822
|
+
out.push({ path: at('writable'), message: 'a forms entry names the columns a form may fill (writable)' });
|
|
823
|
+
if (entries.findIndex((other) => other.key === FORMS_KEY && other.table === entry.table) !== i) {
|
|
824
|
+
out.push({ path: at('table'), message: `"${entry.table}" is opened to forms by another entry already: one forms entry per table` });
|
|
825
|
+
}
|
|
826
|
+
const reader = entries.findIndex((other) => other !== entry && other.table === entry.table && readByAnyone(other));
|
|
827
|
+
if (reader !== -1) {
|
|
828
|
+
out.push({
|
|
829
|
+
path: at('table'),
|
|
830
|
+
message: `strangers add rows to "${entry.table}" through forms, and entry ${String(reader)} lets anyone read its rows: everyone could read what people typed into a form`,
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
return out;
|
|
834
|
+
}
|
|
768
835
|
/** Everything in `publicAccess` and `publicKeys` that names something undeclared, or breaks a rule. */
|
|
769
836
|
export function publicAccessIssues(entries, ctx) {
|
|
770
837
|
const out = [];
|
|
@@ -894,9 +961,11 @@ export function publicAccessIssues(entries, ctx) {
|
|
|
894
961
|
}
|
|
895
962
|
const column = (ref) => index.column(entry.table, ref);
|
|
896
963
|
const key = entry.key ?? CUSTOMER_KEY;
|
|
897
|
-
if (key !== CUSTOMER_KEY && ctx.publicKeys?.[key] === undefined) {
|
|
964
|
+
if (key !== CUSTOMER_KEY && key !== FORMS_KEY && ctx.publicKeys?.[key] === undefined) {
|
|
898
965
|
out.push({ path: at('key'), message: `"${key}" is not one of the app's publicKeys` });
|
|
899
966
|
}
|
|
967
|
+
if (key === FORMS_KEY)
|
|
968
|
+
out.push(...formsEntryIssues(entry, i, entries, ctx.appKey));
|
|
900
969
|
for (const list of ['select', 'writable']) {
|
|
901
970
|
for (const ref of entry[list] ?? []) {
|
|
902
971
|
if (!has(entry.table, ref))
|
|
@@ -906,8 +975,8 @@ export function publicAccessIssues(entries, ctx) {
|
|
|
906
975
|
// An entry anyone may call (no claim, no parent, no session asked) shows no personal data: the install refuses it too.
|
|
907
976
|
const sessionOnly = entry.level !== undefined && entry.claim === undefined && entry.claimedBy === undefined && entry.visibleWith === undefined;
|
|
908
977
|
const anyone = entry.claim === undefined && entry.visibleWith === undefined && (entry.claimedBy === undefined || entry.claimedBy.optional === true) && !sessionOnly;
|
|
909
|
-
// An availability entry shows no row, whatever it names.
|
|
910
|
-
if (anyone && entry.kind !== 'availability') {
|
|
978
|
+
// An availability entry shows no row, whatever it names; nor does a forms entry, which answers nobody with the row.
|
|
979
|
+
if (anyone && entry.kind !== 'availability' && key !== FORMS_KEY) {
|
|
911
980
|
// No select shows every column but a code Adminium makes and a secret, as the install reads it.
|
|
912
981
|
const shown = entry.select ??
|
|
913
982
|
table.columns.filter((c) => c.rules?.['code'] === undefined && c.rules?.['secret'] !== true).map((c) => c.ref);
|