@jwilleke/ngdpbase-types 5.39.0 → 5.41.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.
Files changed (99) hide show
  1. package/lib/packages/ngdpbase-types/index.d.ts +1 -1
  2. package/lib/src/context/ApiContext.d.ts +3 -2
  3. package/lib/src/context/ApiContext.js +4 -2
  4. package/lib/src/ice/fileHandling.d.ts +6 -1
  5. package/lib/src/ice/givenName.d.ts +19 -0
  6. package/lib/src/ice/runExport.d.ts +41 -0
  7. package/lib/src/ice/runImport.d.ts +37 -8
  8. package/lib/src/ice/shims/csv.d.ts +24 -0
  9. package/lib/src/ice/shims/export.d.ts +23 -0
  10. package/lib/src/ice/shims/index.d.ts +3 -1
  11. package/lib/src/ice/shims/svg.d.ts +27 -0
  12. package/lib/src/ice/shims/takeout.d.ts +83 -0
  13. package/lib/src/ice/types.d.ts +301 -5
  14. package/lib/src/ice/vet.d.ts +19 -0
  15. package/lib/src/managers/AttachmentManager.d.ts +100 -36
  16. package/lib/src/managers/AuthManager.d.ts +152 -12
  17. package/lib/src/managers/AuthManager.js +3830 -0
  18. package/lib/src/managers/BackgroundJobManager.d.ts +25 -0
  19. package/lib/src/managers/BackupManager.d.ts +24 -0
  20. package/lib/src/managers/BackupManager.js +48 -1
  21. package/lib/src/managers/ExportManager.d.ts +16 -0
  22. package/lib/src/managers/ExportManager.js +25 -0
  23. package/lib/src/managers/FileManager.d.ts +182 -0
  24. package/lib/src/managers/ImportManager.d.ts +279 -16
  25. package/lib/src/managers/MediaManager.d.ts +2 -0
  26. package/lib/src/managers/PageManager.d.ts +53 -0
  27. package/lib/src/managers/SearchManager.d.ts +4 -0
  28. package/lib/src/managers/SharedFolderManager.d.ts +320 -0
  29. package/lib/src/managers/ValidationManager.d.ts +5 -2
  30. package/lib/src/middleware/csrf.js +99 -0
  31. package/lib/src/providers/AgentTokenAuthProvider.js +46 -0
  32. package/lib/src/providers/AuthentikBearerAuthProvider.js +176 -0
  33. package/lib/src/providers/BaseCredentialsProvider.js +21 -0
  34. package/lib/src/providers/BaseMediaProvider.d.ts +4 -0
  35. package/lib/src/providers/BasePageProvider.d.ts +8 -0
  36. package/lib/src/providers/CloudflareAccessAuthProvider.js +160 -0
  37. package/lib/src/providers/FileCredentialsProvider.js +188 -0
  38. package/lib/src/providers/FileSystemMediaProvider.d.ts +6 -0
  39. package/lib/src/providers/MagicLinkAuthProvider.js +333 -0
  40. package/lib/src/providers/OidcAuthProvider.js +350 -0
  41. package/lib/src/providers/OidcBearerAuthProvider.js +27 -0
  42. package/lib/src/providers/PasskeyAuthProvider.js +147 -0
  43. package/lib/src/providers/PasswordAuthProvider.js +44 -0
  44. package/lib/src/security/PolicyDecisionPoint.d.ts +3 -2
  45. package/lib/src/security/pageActions.js +43 -0
  46. package/lib/src/security/permissions.generated.d.ts +7 -3
  47. package/lib/src/types/File.d.ts +90 -0
  48. package/lib/src/types/Provider.d.ts +15 -0
  49. package/lib/src/types/express.d.ts +7 -1
  50. package/lib/src/types/guards.js +366 -0
  51. package/lib/src/types/linkedom.d.ts +2 -0
  52. package/lib/src/utils/atomicWrite.js +129 -0
  53. package/lib/src/utils/auditEventNames.generated.d.ts +12 -0
  54. package/lib/src/utils/auditEventNames.generated.js +12 -0
  55. package/lib/src/utils/auditEvents.d.ts +15 -0
  56. package/lib/src/utils/auditEvents.js +17 -1
  57. package/lib/src/utils/bip39English.js +2 -0
  58. package/lib/src/utils/browserDescription.js +28 -0
  59. package/lib/src/utils/delegation.js +79 -0
  60. package/lib/src/utils/deriveUsername.js +38 -0
  61. package/lib/src/utils/emailForLog.js +28 -0
  62. package/lib/src/utils/fileMigrationPlan.d.ts +81 -0
  63. package/lib/src/utils/instanceEnvBackup.d.ts +6 -0
  64. package/lib/src/utils/instanceEnvBackup.js +13 -0
  65. package/lib/src/utils/ipLocation.js +106 -0
  66. package/lib/src/utils/magicLinkDeviceState.js +97 -0
  67. package/lib/src/utils/pageUrl.d.ts +1 -0
  68. package/lib/src/utils/passwordHash.js +150 -0
  69. package/lib/src/utils/permissionCatalog.js +102 -0
  70. package/lib/src/utils/privateStoreAccess.d.ts +25 -0
  71. package/lib/src/utils/privateStoreAccess.js +22 -1
  72. package/lib/src/utils/privateStoreCatalogs.js +57 -0
  73. package/lib/src/utils/privateStoreCrypto.js +266 -0
  74. package/lib/src/utils/privateStoreDoor.d.ts +10 -0
  75. package/lib/src/utils/privateStoreDoor.js +298 -0
  76. package/lib/src/utils/privateStoreImport.d.ts +0 -10
  77. package/lib/src/utils/privateStoreMeta.js +123 -0
  78. package/lib/src/utils/privateStorePath.d.ts +37 -1
  79. package/lib/src/utils/privateStorePath.js +60 -3
  80. package/lib/src/utils/privateStoreTakeout.js +149 -0
  81. package/lib/src/utils/privateStoreUnlock.d.ts +3 -0
  82. package/lib/src/utils/privateStoreUnlock.js +499 -0
  83. package/lib/src/utils/recoveryWordsFile.d.ts +54 -0
  84. package/lib/src/utils/safeRedirect.js +55 -0
  85. package/lib/src/utils/sessionAccount.js +55 -0
  86. package/lib/src/utils/sessionEnd.d.ts +1 -0
  87. package/lib/src/utils/sessionEnd.js +42 -0
  88. package/lib/src/utils/sessionGeneration.js +29 -0
  89. package/lib/src/utils/sessionIdle.d.ts +4 -0
  90. package/lib/src/utils/sessionIdle.js +72 -0
  91. package/lib/src/utils/sessionLimits.js +72 -0
  92. package/lib/src/utils/sessionSecurity.js +122 -0
  93. package/lib/src/utils/shareLockbox.js +101 -0
  94. package/lib/src/utils/sharedFolderAccess.d.ts +57 -0
  95. package/lib/src/utils/sharedFolderAccess.js +80 -0
  96. package/lib/src/utils/userCreateError.js +82 -0
  97. package/lib/src/utils/username.d.ts +2 -0
  98. package/lib/src/utils/username.js +105 -0
  99. package/package.json +2 -1
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Who may act in a shared folder, and how its key reaches the store I/O
3
+ * (#1775, #2125; docs/planning/shared-folders.md, "Access is by permission").
4
+ *
5
+ * A shared folder is the container `SHARED_FOLDER_OWNER`'s vault
6
+ * (`utils/privateStorePath.ts`), so the page and file doors reach it as they
7
+ * reach any vault. Two things differ, and both are here:
8
+ *
9
+ * - __Who may act__: never the vault owner rule (`mayActInPrivateContainer`
10
+ * refuses everyone for this container). Every read asks `shared-folder-read`
11
+ * and every add, edit or delete asks `shared-folder-file`, of the PDP, with
12
+ * the caller's context and the folder as the policy resource
13
+ * (`shared-folder`, `shared/{folder}`). A folder lists no roles. A share link
14
+ * never gets in, and asks nothing (no share links into folders).
15
+ * - __The key__: the server holds every folder's key (SharedFolderManager,
16
+ * decision 1). It reaches `dekFor` only for a context the PDP has just
17
+ * permitted on that folder, remembered against that context object
18
+ * ({@link openedFor}). A request's subject is built afresh for every
19
+ * request, so a grant removed ends access on the next request (decision 5);
20
+ * a context nothing asked about — a boot job, a link, another request — gets
21
+ * no key, and a sealed folder reads as nothing to it.
22
+ *
23
+ * Nothing here decides an allow or a deny itself: the PDP does (P2).
24
+ */
25
+ import type { ActorContext } from '../context/ActorContext.js';
26
+ import type { VaultDecider } from './privateStoreAccess.js';
27
+ /** Open a shared folder: its pages, their history and its files. */
28
+ export declare const SHARED_FOLDER_READ = "shared-folder-read";
29
+ /** Add, edit and delete a shared folder's pages and files. */
30
+ export declare const SHARED_FOLDER_FILE = "shared-folder-file";
31
+ /** The policy resource type a folder is named by (`ngdpbase.access.resource-types`). */
32
+ export declare const SHARED_FOLDER_RESOURCE_TYPE = "shared-folder";
33
+ /** The policy resource of folder `folder`: `shared/{folder}`, as a policy's pattern names it. */
34
+ export declare function sharedFolderResource(folder: string): string;
35
+ /**
36
+ * The folder permission an action on a folder's page or file asks:
37
+ * `shared-folder-read` for a read, `shared-folder-file` for a write, and null
38
+ * for anything else — sharing it by link, exporting it, raw edits — which is
39
+ * refused in a folder.
40
+ */
41
+ export declare function sharedFolderPermissionFor(action: string): string | null;
42
+ /**
43
+ * May `ctx` do `action` in shared folder `folder`? The PDP decides
44
+ * `shared-folder-read` or `shared-folder-file` ({@link sharedFolderPermissionFor})
45
+ * with the folder as the resource. A share link is refused without asking. A
46
+ * permit opens the folder's key to this context ({@link openedFor}).
47
+ */
48
+ export declare function permitsInSharedFolder(pdp: VaultDecider | null | undefined, ctx: ActorContext, action: string, folder: string): Promise<boolean>;
49
+ /** Whether the PDP permitted `ctx` on `folder` (read or file): what hands it the folder's key. */
50
+ export declare function openedFor(ctx: ActorContext | undefined, folder: string): boolean;
51
+ /** SharedFolderManager's folder keys, unwrapped with the server key; null to stop handing them out. */
52
+ export declare function setSharedFolderKeySource(source: ((folder: string) => Buffer | undefined) | null): void;
53
+ /**
54
+ * Folder `folder`'s key for `ctx`, for the store I/O (`dekFor`): only when
55
+ * the PDP permitted this context on the folder. A copy; the caller may zero it.
56
+ */
57
+ export declare function sharedFolderKeyFor(ctx: ActorContext | undefined, folder: string): Buffer | undefined;
@@ -0,0 +1,80 @@
1
+ import { permissionForPageAction } from '../security/pageActions.js';
2
+ import { isValidStoreId, SHARED_URL_SEGMENT } from './privateStorePath.js';
3
+ /** Open a shared folder: its pages, their history and its files. */
4
+ export const SHARED_FOLDER_READ = 'shared-folder-read';
5
+ /** Add, edit and delete a shared folder's pages and files. */
6
+ export const SHARED_FOLDER_FILE = 'shared-folder-file';
7
+ /** The policy resource type a folder is named by (`ngdpbase.access.resource-types`). */
8
+ export const SHARED_FOLDER_RESOURCE_TYPE = 'shared-folder';
9
+ /** The policy resource of folder `folder`: `shared/{folder}`, as a policy's pattern names it. */
10
+ export function sharedFolderResource(folder) {
11
+ return `${SHARED_URL_SEGMENT}/${folder}`;
12
+ }
13
+ /** What reads a folder: a page, its history, a file. */
14
+ const READS = new Set(['page-read', 'asset-read', 'media-read', 'history', 'diff', SHARED_FOLDER_READ]);
15
+ /** What writes one: a page or a file added, changed or removed. */
16
+ const WRITES = new Set(['page-create', 'page-edit', 'page-rename', 'page-delete', 'save', 'asset-upload', 'asset-edit', 'asset-delete', SHARED_FOLDER_FILE]);
17
+ /**
18
+ * The folder permission an action on a folder's page or file asks:
19
+ * `shared-folder-read` for a read, `shared-folder-file` for a write, and null
20
+ * for anything else — sharing it by link, exporting it, raw edits — which is
21
+ * refused in a folder.
22
+ */
23
+ export function sharedFolderPermissionFor(action) {
24
+ const asked = permissionForPageAction(action);
25
+ if (READS.has(asked))
26
+ return SHARED_FOLDER_READ;
27
+ if (WRITES.has(asked))
28
+ return SHARED_FOLDER_FILE;
29
+ return null;
30
+ }
31
+ /** Which folders each context was permitted on, by the context object: one request's subject. */
32
+ const opened = new WeakMap();
33
+ /**
34
+ * May `ctx` do `action` in shared folder `folder`? The PDP decides
35
+ * `shared-folder-read` or `shared-folder-file` ({@link sharedFolderPermissionFor})
36
+ * with the folder as the resource. A share link is refused without asking. A
37
+ * permit opens the folder's key to this context ({@link openedFor}).
38
+ */
39
+ export async function permitsInSharedFolder(pdp, ctx, action, folder) {
40
+ const permission = sharedFolderPermissionFor(action);
41
+ if (!pdp || !ctx || typeof ctx !== 'object' || !permission || !isValidStoreId(folder))
42
+ return false;
43
+ if ('viaShare' in ctx && ctx.viaShare)
44
+ return false;
45
+ const resource = sharedFolderResource(folder);
46
+ const { permit } = await pdp.decide(ctx, {
47
+ action: permission,
48
+ resource: { type: SHARED_FOLDER_RESOURCE_TYPE, id: resource },
49
+ attributes: { [SHARED_FOLDER_RESOURCE_TYPE]: resource }
50
+ });
51
+ if (permit) {
52
+ const folders = opened.get(ctx) ?? new Set();
53
+ folders.add(folder);
54
+ opened.set(ctx, folders);
55
+ }
56
+ return permit;
57
+ }
58
+ /** Whether the PDP permitted `ctx` on `folder` (read or file): what hands it the folder's key. */
59
+ export function openedFor(ctx, folder) {
60
+ if (!ctx || typeof ctx !== 'object')
61
+ return false;
62
+ if ('viaShare' in ctx && ctx.viaShare)
63
+ return false;
64
+ return opened.get(ctx)?.has(folder) === true;
65
+ }
66
+ /** Where folder keys come from: SharedFolderManager, which sets it at start. */
67
+ let keySource = null;
68
+ /** SharedFolderManager's folder keys, unwrapped with the server key; null to stop handing them out. */
69
+ export function setSharedFolderKeySource(source) {
70
+ keySource = source;
71
+ }
72
+ /**
73
+ * Folder `folder`'s key for `ctx`, for the store I/O (`dekFor`): only when
74
+ * the PDP permitted this context on the folder. A copy; the caller may zero it.
75
+ */
76
+ export function sharedFolderKeyFor(ctx, folder) {
77
+ if (!keySource || !openedFor(ctx, folder))
78
+ return undefined;
79
+ return keySource(folder);
80
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Typed user-creation failures and the safe messages registration may show (#1086).
3
+ *
4
+ * `POST /register` used to disclose every username on the instance to an
5
+ * unauthenticated visitor. Two independent defects produced that:
6
+ *
7
+ * 1. `UserManager.createUser` built its duplicate-username error by joining
8
+ * `getAllUsernames()` into the message.
9
+ * 2. `processRegister` forwarded `getErrorMessage(err)` straight into a
10
+ * redirect, which the register page rendered.
11
+ *
12
+ * The second is the one that matters. Fixing only the message would leave the
13
+ * route one internal `throw` away from the same exposure, because it treats
14
+ * arbitrary exception text as user-facing copy — and every layer beneath it
15
+ * can throw.
16
+ *
17
+ * So failures now carry a machine-readable `reason`, and the route maps that to
18
+ * a small closed set of messages. Anything unrecognised — a disk error, a
19
+ * provider bug, a future exception nobody anticipated — becomes the generic
20
+ * failure. That default is what makes this hold for errors that do not exist
21
+ * yet.
22
+ *
23
+ * The codebase is careful about this elsewhere, which is what made the
24
+ * registration path look like an oversight rather than a policy: login returns
25
+ * a uniform "Invalid username or password", `requestMagicLink` always redirects
26
+ * with `magic=sent` whether or not the address exists, and agent-token revoke
27
+ * returns 404 rather than 403 to avoid confirming that a token exists.
28
+ */
29
+ /**
30
+ * A user-creation failure with a reason a caller can branch on.
31
+ *
32
+ * The `message` is for logs and may contain internal detail; it must never be
33
+ * shown to a caller. `safeRegistrationMessage` is the only thing that decides
34
+ * what a visitor sees.
35
+ */
36
+ export class UserCreateError extends Error {
37
+ reason;
38
+ constructor(reason, message) {
39
+ super(message);
40
+ this.name = 'UserCreateError';
41
+ this.reason = reason;
42
+ }
43
+ }
44
+ /** Shown when the cause is unknown, or known but not safe to describe. */
45
+ export const GENERIC_REGISTRATION_FAILURE = 'Registration failed. Please try again.';
46
+ /**
47
+ * Messages registration may show, keyed by reason.
48
+ *
49
+ * Both reveal only what registration structurally cannot hide — that *this*
50
+ * value is unavailable, which the visitor must be told in order to pick
51
+ * another. Neither reveals anything about what else exists.
52
+ *
53
+ * `display-name-conflict` is deliberately vague about the cause: internally it
54
+ * means the name collides with an existing page, and saying so would tell an
55
+ * anonymous visitor whether a given page exists, including a private one.
56
+ */
57
+ const SAFE_MESSAGES = {
58
+ 'username-taken': 'That username is not available. Please choose another.',
59
+ 'display-name-conflict': 'That display name is not available. Please choose another.',
60
+ // #2073: plain, and without saying which account holds the address.
61
+ 'admin-email-shared': 'That email address cannot be used. Please use another.',
62
+ 'email-unconfirmed': 'Your email is set when you click the link we send to it.'
63
+ // No email-taken reason: since #2073 two accounts may share an email (#1748's refusal reversed).
64
+ };
65
+ /**
66
+ * Map a thrown value to a message that is safe to show an unauthenticated
67
+ * visitor.
68
+ *
69
+ * Deliberately conservative: only a real `UserCreateError` carrying a known
70
+ * reason produces a specific message. A plain object shaped like one is not
71
+ * trusted, because only the code that constructs a `UserCreateError` has
72
+ * decided the reason is accurate.
73
+ *
74
+ * Callers must still log the original error — this discards detail on purpose,
75
+ * and the detail is what makes a real failure diagnosable.
76
+ */
77
+ export function safeRegistrationMessage(err) {
78
+ if (err instanceof UserCreateError) {
79
+ return SAFE_MESSAGES[err.reason] ?? GENERIC_REGISTRATION_FAILURE;
80
+ }
81
+ return GENERIC_REGISTRATION_FAILURE;
82
+ }
@@ -44,6 +44,8 @@ export declare function sameUsername(a: unknown, b: unknown): boolean;
44
44
  * holding that name would collide with it wherever the name is compared.
45
45
  * `asserted` was the subject removed in #1435; it is reserved so the name
46
46
  * cannot be taken in the window before anyone reconsiders the concept.
47
+ * `shared-folders` owns the organization's shared folders (#2125): an account
48
+ * by that name would be the owner of every folder by the vault owner rule.
47
49
  *
48
50
  * The system principal is NOT listed here: its name comes from `.env` and is
49
51
  * checked separately by `isSystemPrincipal`, because an operator chooses it.
@@ -0,0 +1,105 @@
1
+ 'use strict';
2
+ import { SHARED_FOLDER_OWNER } from './privateStorePath.js';
3
+ /**
4
+ * One spelling of a username, for comparison (#1436).
5
+ *
6
+ * A username is an identifier, not prose: `jim`, `Jim` and `" jim "` are one
7
+ * account, and every comparison has to agree about that. Before this they did
8
+ * not. `FileUserProvider` keyed its map on the raw string, so those three were
9
+ * three separate accounts and `createUser`'s duplicate check did not see the
10
+ * case variants — an account could be registered whose name differed from an
11
+ * existing one only in case, which is the classic lookalike.
12
+ *
13
+ * The same split ran through the code the other way. `getAnonymousUser()`
14
+ * emits `'Anonymous'` while roughly ten sites compare against `'anonymous'`,
15
+ * so those guards never fired for a real visitor. They failed closed, which is
16
+ * luck rather than design — it is the same defect as #1433, where a viewer was
17
+ * written to the cache key under one spelling and read back under another.
18
+ *
19
+ * __Normalize to compare; keep the original to display.__ The stored record
20
+ * keeps whatever the person typed, because that is their name; the index and
21
+ * every equality test use this.
22
+ */
23
+ /**
24
+ * The comparison form of a username: trimmed, lower-cased.
25
+ *
26
+ * Lower-case rather than upper: `toLowerCase()` is what the existing checks
27
+ * already used (`isSystemPrincipal`, the sitemap subject list), so this agrees
28
+ * with them rather than introducing a third convention.
29
+ *
30
+ * A non-string is normalized to the empty string rather than throwing, because
31
+ * callers pass values straight from a request body — and empty is already the
32
+ * "no username" case every caller handles.
33
+ */
34
+ export function normalizeUsername(username) {
35
+ return typeof username === 'string' ? username.trim().toLowerCase() : '';
36
+ }
37
+ /**
38
+ * True when two usernames name the same account.
39
+ *
40
+ * Exists so a comparison reads as the question it is asking, and so no call
41
+ * site re-implements the rule by hand — which is how the spellings diverged.
42
+ */
43
+ export function sameUsername(a, b) {
44
+ const left = normalizeUsername(a);
45
+ return left !== '' && left === normalizeUsername(b);
46
+ }
47
+ /**
48
+ * Names no person may register.
49
+ *
50
+ * `anonymous` is the principal for "nobody authenticated" — a real account
51
+ * holding that name would collide with it wherever the name is compared.
52
+ * `asserted` was the subject removed in #1435; it is reserved so the name
53
+ * cannot be taken in the window before anyone reconsiders the concept.
54
+ * `shared-folders` owns the organization's shared folders (#2125): an account
55
+ * by that name would be the owner of every folder by the vault owner rule.
56
+ *
57
+ * The system principal is NOT listed here: its name comes from `.env` and is
58
+ * checked separately by `isSystemPrincipal`, because an operator chooses it.
59
+ */
60
+ export const RESERVED_USERNAMES = ['anonymous', 'asserted', SHARED_FOLDER_OWNER];
61
+ /**
62
+ * The prefix of a person's identifier when the person has no account (#1930):
63
+ * `person:<uuid>`. No username may start with it, so an account-less person's
64
+ * identifier can never be, or later become, someone's username.
65
+ */
66
+ export const ACCOUNTLESS_PERSON_PREFIX = 'person:';
67
+ /**
68
+ * The prefix of an identity's `@id` (#2041): `urn:uuid:<v4>`, and older
69
+ * organizations' `urn:…` ids. No username may start with it, so a stored
70
+ * reference is always told apart: a value starting `urn:` is an `@id`,
71
+ * anything else a username not yet converted.
72
+ */
73
+ export const IDENTITY_ID_PREFIX = 'urn:';
74
+ /**
75
+ * The prefix a start-up conversion gives a stored username that has no
76
+ * account (#2041): `orphan:<name>`. The record is then nobody's: a later
77
+ * account with the same name never takes it over, no "is it mine" check
78
+ * matches it, and only an administrator can act on it. No username may
79
+ * start with it.
80
+ */
81
+ export const ORPHAN_PREFIX = 'orphan:';
82
+ /**
83
+ * Values older records hold where no user was recorded (#2096, #2097): the
84
+ * footnote migration's `migrated`, the editor fallback `unknown` (and the
85
+ * attachment pool's `Unknown`), the JSPWiki import's `import`. Never a
86
+ * username: a start-up conversion never looks them up (an account that later
87
+ * takes one of these names must not gain the records), stores them as
88
+ * `orphan:<value>` and does not count them as needing attention.
89
+ */
90
+ export const NO_RECORDED_USER = ['migrated', 'unknown', 'import'];
91
+ /** True when `value` is one of {@link NO_RECORDED_USER}, bare or as its orphan form. */
92
+ export function isNoRecordedUser(value) {
93
+ const name = normalizeUsername(value);
94
+ return NO_RECORDED_USER.includes(name.startsWith(ORPHAN_PREFIX) ? name.slice(ORPHAN_PREFIX.length) : name);
95
+ }
96
+ /**
97
+ * True when the name belongs to a principal rather than a user, is a "no user
98
+ * recorded" value ({@link NO_RECORDED_USER}, #2097: an account by that name
99
+ * would take over the records that hold it), or has the contact, `@id` or
100
+ * orphan prefix.
101
+ */
102
+ export function isReservedUsername(username) {
103
+ const name = normalizeUsername(username);
104
+ return RESERVED_USERNAMES.includes(name) || NO_RECORDED_USER.includes(name) || name.startsWith(ACCOUNTLESS_PERSON_PREFIX) || name.startsWith(IDENTITY_ID_PREFIX) || name.startsWith(ORPHAN_PREFIX);
105
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jwilleke/ngdpbase-types",
3
- "version": "5.39.0",
3
+ "version": "5.41.0",
4
4
  "description": "TypeScript types for ngdpbase add-ons, and a test kit of core's own values. A dev dependency, versioned with ngdpbase.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Jim Willeke",
@@ -51,6 +51,7 @@
51
51
  "linkedom": "^0.18.14",
52
52
  "markdown-it": "^14.3.3",
53
53
  "minisearch": "7.2.0",
54
+ "mmdb-lib": "^3.0.3",
54
55
  "multer": "^2.4.0",
55
56
  "rrule-temporal": "2.2.8",
56
57
  "tar": "^7.5.22",