@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.
- package/lib/packages/ngdpbase-types/index.d.ts +1 -1
- package/lib/src/context/ApiContext.d.ts +3 -2
- package/lib/src/context/ApiContext.js +4 -2
- package/lib/src/ice/fileHandling.d.ts +6 -1
- package/lib/src/ice/givenName.d.ts +19 -0
- package/lib/src/ice/runExport.d.ts +41 -0
- package/lib/src/ice/runImport.d.ts +37 -8
- package/lib/src/ice/shims/csv.d.ts +24 -0
- package/lib/src/ice/shims/export.d.ts +23 -0
- package/lib/src/ice/shims/index.d.ts +3 -1
- package/lib/src/ice/shims/svg.d.ts +27 -0
- package/lib/src/ice/shims/takeout.d.ts +83 -0
- package/lib/src/ice/types.d.ts +301 -5
- package/lib/src/ice/vet.d.ts +19 -0
- package/lib/src/managers/AttachmentManager.d.ts +100 -36
- package/lib/src/managers/AuthManager.d.ts +152 -12
- package/lib/src/managers/AuthManager.js +3830 -0
- package/lib/src/managers/BackgroundJobManager.d.ts +25 -0
- package/lib/src/managers/BackupManager.d.ts +24 -0
- package/lib/src/managers/BackupManager.js +48 -1
- package/lib/src/managers/ExportManager.d.ts +16 -0
- package/lib/src/managers/ExportManager.js +25 -0
- package/lib/src/managers/FileManager.d.ts +182 -0
- package/lib/src/managers/ImportManager.d.ts +279 -16
- package/lib/src/managers/MediaManager.d.ts +2 -0
- package/lib/src/managers/PageManager.d.ts +53 -0
- package/lib/src/managers/SearchManager.d.ts +4 -0
- package/lib/src/managers/SharedFolderManager.d.ts +320 -0
- package/lib/src/managers/ValidationManager.d.ts +5 -2
- package/lib/src/middleware/csrf.js +99 -0
- package/lib/src/providers/AgentTokenAuthProvider.js +46 -0
- package/lib/src/providers/AuthentikBearerAuthProvider.js +176 -0
- package/lib/src/providers/BaseCredentialsProvider.js +21 -0
- package/lib/src/providers/BaseMediaProvider.d.ts +4 -0
- package/lib/src/providers/BasePageProvider.d.ts +8 -0
- package/lib/src/providers/CloudflareAccessAuthProvider.js +160 -0
- package/lib/src/providers/FileCredentialsProvider.js +188 -0
- package/lib/src/providers/FileSystemMediaProvider.d.ts +6 -0
- package/lib/src/providers/MagicLinkAuthProvider.js +333 -0
- package/lib/src/providers/OidcAuthProvider.js +350 -0
- package/lib/src/providers/OidcBearerAuthProvider.js +27 -0
- package/lib/src/providers/PasskeyAuthProvider.js +147 -0
- package/lib/src/providers/PasswordAuthProvider.js +44 -0
- package/lib/src/security/PolicyDecisionPoint.d.ts +3 -2
- package/lib/src/security/pageActions.js +43 -0
- package/lib/src/security/permissions.generated.d.ts +7 -3
- package/lib/src/types/File.d.ts +90 -0
- package/lib/src/types/Provider.d.ts +15 -0
- package/lib/src/types/express.d.ts +7 -1
- package/lib/src/types/guards.js +366 -0
- package/lib/src/types/linkedom.d.ts +2 -0
- package/lib/src/utils/atomicWrite.js +129 -0
- package/lib/src/utils/auditEventNames.generated.d.ts +12 -0
- package/lib/src/utils/auditEventNames.generated.js +12 -0
- package/lib/src/utils/auditEvents.d.ts +15 -0
- package/lib/src/utils/auditEvents.js +17 -1
- package/lib/src/utils/bip39English.js +2 -0
- package/lib/src/utils/browserDescription.js +28 -0
- package/lib/src/utils/delegation.js +79 -0
- package/lib/src/utils/deriveUsername.js +38 -0
- package/lib/src/utils/emailForLog.js +28 -0
- package/lib/src/utils/fileMigrationPlan.d.ts +81 -0
- package/lib/src/utils/instanceEnvBackup.d.ts +6 -0
- package/lib/src/utils/instanceEnvBackup.js +13 -0
- package/lib/src/utils/ipLocation.js +106 -0
- package/lib/src/utils/magicLinkDeviceState.js +97 -0
- package/lib/src/utils/pageUrl.d.ts +1 -0
- package/lib/src/utils/passwordHash.js +150 -0
- package/lib/src/utils/permissionCatalog.js +102 -0
- package/lib/src/utils/privateStoreAccess.d.ts +25 -0
- package/lib/src/utils/privateStoreAccess.js +22 -1
- package/lib/src/utils/privateStoreCatalogs.js +57 -0
- package/lib/src/utils/privateStoreCrypto.js +266 -0
- package/lib/src/utils/privateStoreDoor.d.ts +10 -0
- package/lib/src/utils/privateStoreDoor.js +298 -0
- package/lib/src/utils/privateStoreImport.d.ts +0 -10
- package/lib/src/utils/privateStoreMeta.js +123 -0
- package/lib/src/utils/privateStorePath.d.ts +37 -1
- package/lib/src/utils/privateStorePath.js +60 -3
- package/lib/src/utils/privateStoreTakeout.js +149 -0
- package/lib/src/utils/privateStoreUnlock.d.ts +3 -0
- package/lib/src/utils/privateStoreUnlock.js +499 -0
- package/lib/src/utils/recoveryWordsFile.d.ts +54 -0
- package/lib/src/utils/safeRedirect.js +55 -0
- package/lib/src/utils/sessionAccount.js +55 -0
- package/lib/src/utils/sessionEnd.d.ts +1 -0
- package/lib/src/utils/sessionEnd.js +42 -0
- package/lib/src/utils/sessionGeneration.js +29 -0
- package/lib/src/utils/sessionIdle.d.ts +4 -0
- package/lib/src/utils/sessionIdle.js +72 -0
- package/lib/src/utils/sessionLimits.js +72 -0
- package/lib/src/utils/sessionSecurity.js +122 -0
- package/lib/src/utils/shareLockbox.js +101 -0
- package/lib/src/utils/sharedFolderAccess.d.ts +57 -0
- package/lib/src/utils/sharedFolderAccess.js +80 -0
- package/lib/src/utils/userCreateError.js +82 -0
- package/lib/src/utils/username.d.ts +2 -0
- package/lib/src/utils/username.js +105 -0
- 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.
|
|
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",
|