@jwilleke/ngdpbase-types 5.32.0 → 5.34.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/src/managers/BackupManager.js +3 -3
- package/lib/src/managers/CommentManager.d.ts +25 -0
- package/lib/src/managers/DatabaseManager.js +1 -1
- package/lib/src/managers/FootnoteManager.d.ts +27 -0
- package/lib/src/managers/IdentityManager.d.ts +37 -0
- package/lib/src/managers/NotificationManager.d.ts +36 -4
- package/lib/src/managers/OrganizationManager.d.ts +4 -3
- package/lib/src/managers/RoleManager.d.ts +44 -2
- package/lib/src/providers/databaseMigrations.js +14 -0
- package/lib/src/types/Comment.d.ts +1 -0
- package/lib/src/types/Organization.d.ts +11 -8
- package/lib/src/utils/auditEvents.d.ts +7 -2
- package/lib/src/utils/auditEvents.js +3 -2
- package/lib/src/utils/username.d.ts +16 -1
- package/package.json +1 -1
|
@@ -1200,7 +1200,7 @@ class BackupManager extends BaseManager {
|
|
|
1200
1200
|
async notifyAdmins(level, title, message) {
|
|
1201
1201
|
try {
|
|
1202
1202
|
await this.engine.getManager('NotificationManager')
|
|
1203
|
-
?.
|
|
1203
|
+
?.notifyAdmins({ level, title, message });
|
|
1204
1204
|
}
|
|
1205
1205
|
catch (err) {
|
|
1206
1206
|
logger.error(`❌ The notification '${title}' could not be sent:`, err);
|
|
@@ -2059,8 +2059,8 @@ class BackupManager extends BaseManager {
|
|
|
2059
2059
|
catch (err) {
|
|
2060
2060
|
jobs.registerJob(def);
|
|
2061
2061
|
logger.error(`❌ Automatic backups are ON but NOT scheduled: ${err.message}. Fix the setting in Admin → Backup.`);
|
|
2062
|
-
void this.engine.getManager('NotificationManager')?.
|
|
2063
|
-
|
|
2062
|
+
void this.engine.getManager('NotificationManager')?.notifyAdmins({
|
|
2063
|
+
level: 'error', title: 'Automatic backups are not running',
|
|
2064
2064
|
message: `${err.message}. Fix the setting in Admin → Backup.`
|
|
2065
2065
|
}).catch(() => undefined);
|
|
2066
2066
|
}
|
|
@@ -10,6 +10,31 @@ export default class CommentManager extends BaseManager {
|
|
|
10
10
|
private readonly outsideCheck;
|
|
11
11
|
constructor(engine: WikiEngine);
|
|
12
12
|
initialize(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Once, at start-up (#2041): every public comment whose `author` is still a
|
|
15
|
+
* username gets `IdentityManager.convertReference`'s answer, one write per
|
|
16
|
+
* comment file, under the system context: that user's identity `@id`, or
|
|
17
|
+
* `orphan:<name>` when the name has no account (nobody's from then on: a
|
|
18
|
+
* later account with that name never takes it over, and only an
|
|
19
|
+
* administrator can delete it). `@id`s, orphans, `anonymous` and the system
|
|
20
|
+
* principal are left alone, so a second start writes nothing. Orphans are
|
|
21
|
+
* reported, counts only: a log line and the admin dashboard's
|
|
22
|
+
* needs-attention list. When the identity store cannot answer, the whole
|
|
23
|
+
* pass is postponed to the next start and reported the same way. A vault
|
|
24
|
+
* page's comments are in its vault, which start-up does not open; they keep
|
|
25
|
+
* their username, and {@link isAuthor} still matches it.
|
|
26
|
+
* `authorDisplayName` and `deletedBy` are not touched.
|
|
27
|
+
*/
|
|
28
|
+
private convertAuthorsToIdentityIds;
|
|
29
|
+
/** The identity store could not answer: nothing more is converted this start, and the next start tries again (#2041). */
|
|
30
|
+
private postponeConversion;
|
|
31
|
+
/**
|
|
32
|
+
* Whether `ctx` wrote `comment` (#2041): its `author` is the caller's
|
|
33
|
+
* identity `@id`, or, on a comment written before and not yet converted (a
|
|
34
|
+
* vault page's), the caller's username. An orphan (`orphan:<name>`) is
|
|
35
|
+
* nobody's and never matches.
|
|
36
|
+
*/
|
|
37
|
+
isAuthor(comment: Pick<PageComment, 'author'>, ctx: ActorContext): Promise<boolean>;
|
|
13
38
|
/** `ngdpbase.comments.storagedir`, resolved, or the default when there is no configuration (a test). */
|
|
14
39
|
private configuredDir;
|
|
15
40
|
/**
|
|
@@ -713,7 +713,7 @@ class DatabaseManager extends BaseManager {
|
|
|
713
713
|
async notify(level, title, message) {
|
|
714
714
|
try {
|
|
715
715
|
await this.engine.getManager('NotificationManager')
|
|
716
|
-
?.
|
|
716
|
+
?.notifyAdmins({ level, title, message });
|
|
717
717
|
}
|
|
718
718
|
catch (err) {
|
|
719
719
|
logger.error(`[DatabaseManager] The notification '${title}' could not be sent (#1723):`, err);
|
|
@@ -7,6 +7,7 @@ export interface PageFootnote {
|
|
|
7
7
|
display: string;
|
|
8
8
|
url: string;
|
|
9
9
|
note: string;
|
|
10
|
+
/** Who added it: their identity `@id` (#2041); a username on a note written before and not yet converted (a vault page's), or one with no account. */
|
|
10
11
|
createdBy: string;
|
|
11
12
|
createdAt: string;
|
|
12
13
|
}
|
|
@@ -17,6 +18,32 @@ export default class FootnoteManager extends BaseManager {
|
|
|
17
18
|
private readonly outsideCheck;
|
|
18
19
|
constructor(engine: WikiEngine);
|
|
19
20
|
initialize(): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Once, at start-up (#2041): every public footnote whose `createdBy` is
|
|
23
|
+
* still a username gets `IdentityManager.convertReference`'s answer, one
|
|
24
|
+
* write per page file, under the system context: that user's identity
|
|
25
|
+
* `@id`, or `orphan:<name>` when the name has no account (nobody's from
|
|
26
|
+
* then on: a later account with that name never takes it over, and only an
|
|
27
|
+
* administrator can delete it). `@id`s, orphans, `anonymous` and the system
|
|
28
|
+
* principal are left alone, so a second start writes nothing. Orphans are
|
|
29
|
+
* reported, counts only (the legacy `migrated` placeholder is `orphan:migrated`
|
|
30
|
+
* and only logged at info, not counted: it has no recorded creator): a log line and the admin dashboard's
|
|
31
|
+
* needs-attention list. When the identity store cannot answer, the whole
|
|
32
|
+
* pass is postponed to the next start and reported the same way. A vault
|
|
33
|
+
* page's footnotes are in its vault, which start-up does not open; they
|
|
34
|
+
* keep their username, and {@link isCreator} still matches it.
|
|
35
|
+
*/
|
|
36
|
+
private convertCreatorsToIdentityIds;
|
|
37
|
+
/** The identity store could not answer: nothing more is converted this start, and the next start tries again (#2041). */
|
|
38
|
+
private postponeConversion;
|
|
39
|
+
/**
|
|
40
|
+
* Whether `ctx` added `footnote` (#2041): its `createdBy` is the caller's
|
|
41
|
+
* identity `@id`, or, on a footnote written before and not yet converted
|
|
42
|
+
* (a vault page's), the caller's username. An orphan (`orphan:<name>`)
|
|
43
|
+
* is nobody's and never matches; neither does the legacy `migrated`
|
|
44
|
+
* placeholder (no recorded creator), on a footnote not yet converted.
|
|
45
|
+
*/
|
|
46
|
+
isCreator(footnote: Pick<PageFootnote, 'createdBy'>, ctx: ActorContext): Promise<boolean>;
|
|
20
47
|
/** `ngdpbase.footnotes.storagedir`, resolved, or the default when there is no configuration (a test). */
|
|
21
48
|
private configuredDir;
|
|
22
49
|
/**
|
|
@@ -272,6 +272,43 @@ declare class IdentityManager extends BaseManager {
|
|
|
272
272
|
* (an administrator links it, {@link linkAccount}).
|
|
273
273
|
*/
|
|
274
274
|
getByUserIdentifier(ctx: ActorContext, username: string): Promise<Person | null>;
|
|
275
|
+
/**
|
|
276
|
+
* The `@id` of the user `username`'s identity record, or null (#2041): null
|
|
277
|
+
* when there is no account by that name, and null when the identity store
|
|
278
|
+
* cannot answer (degraded, unreadable), logged without the name. The door
|
|
279
|
+
* for a write or a record that refers to a user: it then keeps working as
|
|
280
|
+
* it does for a name with no account, and never fails because the identity
|
|
281
|
+
* store is down. Read as {@link getByUserIdentifier}, which a caller that
|
|
282
|
+
* must tell "no account" from "cannot answer" uses instead (a start-up
|
|
283
|
+
* conversion, with {@link canAnswer}). A refusal still throws.
|
|
284
|
+
*/
|
|
285
|
+
identityIdOf(ctx: ActorContext, username: string): Promise<string | null>;
|
|
286
|
+
/**
|
|
287
|
+
* What a stored reference to a user should become, for a start-up
|
|
288
|
+
* conversion (#2041). `stored` is what a record holds today:
|
|
289
|
+
*
|
|
290
|
+
* - an `@id` (`urn:`), an orphan (`orphan:<name>`), `anonymous`, the
|
|
291
|
+
* system principal or an empty value: `kept` (`orphan` when it is an
|
|
292
|
+
* orphan, so the caller can count them);
|
|
293
|
+
* - a username with an account: that identity's `@id`, `converted`;
|
|
294
|
+
* - a username with no account: `orphan:<name>`, `orphaned`. The record is
|
|
295
|
+
* then nobody's: a later account with the same name never takes it over,
|
|
296
|
+
* and no "is it mine" check matches it.
|
|
297
|
+
*
|
|
298
|
+
* Throws when the identity store cannot answer (and so does nothing to the
|
|
299
|
+
* record): the caller postpones its whole pass to the next start, rather
|
|
300
|
+
* than orphan a name it could not look up. Ask {@link canAnswer} first.
|
|
301
|
+
*/
|
|
302
|
+
convertReference(ctx: ActorContext, stored: string): Promise<{
|
|
303
|
+
value: string;
|
|
304
|
+
outcome: 'kept' | 'orphan' | 'converted' | 'orphaned';
|
|
305
|
+
}>;
|
|
306
|
+
/**
|
|
307
|
+
* Whether the identity store can answer lookups now (#2041): false when it
|
|
308
|
+
* started degraded (no provider). A start-up conversion asks this first and
|
|
309
|
+
* postpones the whole pass when it is false.
|
|
310
|
+
*/
|
|
311
|
+
canAnswer(): boolean;
|
|
275
312
|
private requireProvider;
|
|
276
313
|
private getMetrics;
|
|
277
314
|
private normalizeProviderName;
|
|
@@ -10,9 +10,11 @@ export interface Notification {
|
|
|
10
10
|
title: string;
|
|
11
11
|
message: string;
|
|
12
12
|
level: 'info' | 'warning' | 'error' | 'success';
|
|
13
|
+
/** Who it is for, by identity `@id` (#2041); empty means every user. A name with no account is kept as given. */
|
|
13
14
|
targetUsers: string[];
|
|
14
15
|
createdAt: Date;
|
|
15
16
|
expiresAt: Date | null;
|
|
17
|
+
/** Who dismissed it, by identity `@id` (#2041); a name with no account as given. */
|
|
16
18
|
dismissedBy: string[];
|
|
17
19
|
}
|
|
18
20
|
/**
|
|
@@ -23,6 +25,7 @@ export interface NotificationInput {
|
|
|
23
25
|
title?: string;
|
|
24
26
|
message?: string;
|
|
25
27
|
level?: 'info' | 'warning' | 'error' | 'success';
|
|
28
|
+
/** Usernames or identity `@id`s; stored as `@id`s (#2041). Empty or absent: every user. */
|
|
26
29
|
targetUsers?: string[];
|
|
27
30
|
expiresAt?: Date | null;
|
|
28
31
|
/**
|
|
@@ -32,6 +35,8 @@ export interface NotificationInput {
|
|
|
32
35
|
*/
|
|
33
36
|
escalate?: boolean;
|
|
34
37
|
}
|
|
38
|
+
/** A notice for administrators: the type and the audience are not the caller's to choose. */
|
|
39
|
+
export type AdminNoticeInput = Omit<NotificationInput, 'type' | 'targetUsers'>;
|
|
35
40
|
/**
|
|
36
41
|
* Notification statistics
|
|
37
42
|
*/
|
|
@@ -92,6 +97,21 @@ declare class NotificationManager extends BaseManager {
|
|
|
92
97
|
* Load notifications from storage
|
|
93
98
|
*/
|
|
94
99
|
private loadNotifications;
|
|
100
|
+
/**
|
|
101
|
+
* Once, at start-up (#2041): every stored `targetUsers` and `dismissedBy`
|
|
102
|
+
* entry that is still a username gets `IdentityManager.convertReference`'s
|
|
103
|
+
* answer, under the system context, and the file is written once: that
|
|
104
|
+
* user's identity `@id`, or `orphan:<name>` when the name has no account
|
|
105
|
+
* (it then reaches nobody: a later account with that name is never
|
|
106
|
+
* addressed by it). `@id`s, orphans, `anonymous` and the system principal
|
|
107
|
+
* are left alone, so a second start writes nothing. Orphans are reported,
|
|
108
|
+
* counts only: a log line and the admin dashboard's needs-attention list.
|
|
109
|
+
* When the identity store cannot answer, nothing is changed and the pass is
|
|
110
|
+
* postponed to the next start, reported the same way.
|
|
111
|
+
*/
|
|
112
|
+
private convertRecipientsToIdentityIds;
|
|
113
|
+
/** The identity store could not answer: nothing is converted this start, and the next start tries again (#2041). */
|
|
114
|
+
private postponeConversion;
|
|
95
115
|
/**
|
|
96
116
|
* Save notifications to storage
|
|
97
117
|
*/
|
|
@@ -115,6 +135,16 @@ declare class NotificationManager extends BaseManager {
|
|
|
115
135
|
* @returns false when there is no such notification
|
|
116
136
|
*/
|
|
117
137
|
updateNotificationMessage(notificationId: string, message: string): Promise<boolean>;
|
|
138
|
+
/**
|
|
139
|
+
* The one way to tell administrators something (#2040): a `system`
|
|
140
|
+
* notification targeted at the users holding `admin-system`. A notification
|
|
141
|
+
* with no targetUsers reaches every user, so no admin notice is created any
|
|
142
|
+
* other way. Nobody holds admin-system: the log line stands alone and null is
|
|
143
|
+
* returned.
|
|
144
|
+
*
|
|
145
|
+
* @returns the notification id, or null when there was nobody to tell
|
|
146
|
+
*/
|
|
147
|
+
notifyAdmins(input: AdminNoticeInput): Promise<string | null>;
|
|
118
148
|
/**
|
|
119
149
|
* Add a notification (alias for createNotification for backward compatibility)
|
|
120
150
|
* @param {NotificationInput} notification - Notification object
|
|
@@ -122,12 +152,14 @@ declare class NotificationManager extends BaseManager {
|
|
|
122
152
|
*/
|
|
123
153
|
addNotification(notification: NotificationInput): Promise<string>;
|
|
124
154
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
155
|
+
* The notifications for the caller: addressed to every user, or to them by
|
|
156
|
+
* identity `@id` (#2041), or by username on one with no account; never one
|
|
157
|
+
* they dismissed.
|
|
158
|
+
* @param ctx - Who is asking: the request's subject, forwarded
|
|
127
159
|
* @param {boolean} includeExpired - Include expired notifications
|
|
128
|
-
* @returns
|
|
160
|
+
* @returns Array of notifications
|
|
129
161
|
*/
|
|
130
|
-
getUserNotifications(
|
|
162
|
+
getUserNotifications(ctx: ActorContext, includeExpired?: boolean): Promise<Notification[]>;
|
|
131
163
|
/**
|
|
132
164
|
* Dismiss a notification for the caller.
|
|
133
165
|
*
|
|
@@ -120,9 +120,10 @@ declare class OrganizationManager extends BaseManager {
|
|
|
120
120
|
affiliation: PersonAffiliation;
|
|
121
121
|
}>>;
|
|
122
122
|
/**
|
|
123
|
-
* Create an organization. `name` is required
|
|
124
|
-
*
|
|
125
|
-
*
|
|
123
|
+
* Create an organization. `name` is required. Its `@id` is always a new
|
|
124
|
+
* `urn:uuid:` (#2041): the web address stays the ordinary `url` field, and
|
|
125
|
+
* an `@id` the caller gives is ignored. `filename` is for the install
|
|
126
|
+
* anchor (seeding); other records take one from the url or name.
|
|
126
127
|
*/
|
|
127
128
|
create(ctx: ActorContext, org: OrganizationInput, filename?: string): Promise<Organization>;
|
|
128
129
|
update(ctx: ActorContext, id: string, patch: OrganizationUpdate): Promise<Organization | null>;
|
|
@@ -4,6 +4,8 @@ import type { Role, RoleUpdate } from '../types/Role.js';
|
|
|
4
4
|
import type { RoleProvider } from '../types/RoleProvider.js';
|
|
5
5
|
import { type ActorContext } from '../context/ActorContext.js';
|
|
6
6
|
import type { JobContext } from '../context/JobContext.js';
|
|
7
|
+
/** The title of the one notification for contacts found holding roles at start-up (#2040). */
|
|
8
|
+
export declare const CONTACT_MEMBERS_TITLE = "Contacts are listed as role members";
|
|
7
9
|
declare class RoleManager extends BaseManager {
|
|
8
10
|
readonly description = "Canonical OrganizationRole records (#617 follow-up)";
|
|
9
11
|
private provider;
|
|
@@ -142,6 +144,11 @@ declare class RoleManager extends BaseManager {
|
|
|
142
144
|
* IdentityManager joins an account to an existing person, the roles the
|
|
143
145
|
* account's person held go with the account. Returns how many records
|
|
144
146
|
* changed. IdentityManager's join records the change. All or nothing (#2015).
|
|
147
|
+
*
|
|
148
|
+
* #2040: the roles go only with credentials. The join gives the account
|
|
149
|
+
* (`fromPersonId`'s credentials) to `toPersonId`, which is still a contact
|
|
150
|
+
* while the roles move, so the check is that `fromPersonId` is a user: a
|
|
151
|
+
* contact's memberships never move to anyone.
|
|
145
152
|
*/
|
|
146
153
|
moveMemberships(fromPersonId: string, toPersonId: string): Promise<number>;
|
|
147
154
|
/**
|
|
@@ -160,7 +167,12 @@ declare class RoleManager extends BaseManager {
|
|
|
160
167
|
* be left without the role it was given, nor keep one it was meant to lose.
|
|
161
168
|
*/
|
|
162
169
|
private membershipTarget;
|
|
163
|
-
/**
|
|
170
|
+
/**
|
|
171
|
+
* One member change and its inverse. An add is checked: the member must be a
|
|
172
|
+
* user (#2040), judged by `credentialsOf` when the credentials come from
|
|
173
|
+
* another identity in the same change (a join). An undo puts back what was
|
|
174
|
+
* there and is not checked.
|
|
175
|
+
*/
|
|
164
176
|
private memberChange;
|
|
165
177
|
/**
|
|
166
178
|
* Make each change in turn (#2015). When one fails, undo the ones made, in
|
|
@@ -175,7 +187,7 @@ declare class RoleManager extends BaseManager {
|
|
|
175
187
|
* unless the caller sends one for a whole batch. Returns the error to throw.
|
|
176
188
|
*/
|
|
177
189
|
private roleFailure;
|
|
178
|
-
/** An error notification for
|
|
190
|
+
/** An error notification for the users holding `admin-system`; a notification that cannot be sent is logged, never thrown. */
|
|
179
191
|
private reportToAdmins;
|
|
180
192
|
/** #2015: the membership queue's key for one role record. */
|
|
181
193
|
private membershipKey;
|
|
@@ -186,6 +198,11 @@ declare class RoleManager extends BaseManager {
|
|
|
186
198
|
* record is created in `createIn` for an add; with no `createIn` an add to a
|
|
187
199
|
* missing record throws, and a removal from one changes nothing. False,
|
|
188
200
|
* writing nothing, when the person already is (or is not) a member.
|
|
201
|
+
*
|
|
202
|
+
* `mustBeUser` (#2040): the identity whose credentials an add needs, or null
|
|
203
|
+
* for an undo, which puts back what was there. Only a user holds a role; a
|
|
204
|
+
* contact is refused with {@link RoleMemberRefusal} before anything is
|
|
205
|
+
* written, the role record included.
|
|
189
206
|
*/
|
|
190
207
|
private setMember;
|
|
191
208
|
/** assignRole / removeRole: the change, recorded as a user-edit whether it happened or not (#1204, #2015). */
|
|
@@ -199,6 +216,31 @@ declare class RoleManager extends BaseManager {
|
|
|
199
216
|
* permits is the policies, resolved at the moment of each decision.
|
|
200
217
|
*/
|
|
201
218
|
private getOrCreateRoleRecord;
|
|
219
|
+
/**
|
|
220
|
+
* Why the identity `personId` may not hold a role, or null when it may
|
|
221
|
+
* (#2040). Only a user holds a role: an identity with credentials, whose
|
|
222
|
+
* identity record's `identifier` is the username of an account that exists.
|
|
223
|
+
* A contact's identifier is `person:<uuid>` and it has no account.
|
|
224
|
+
* Read under the system context: this is RoleManager's own rule.
|
|
225
|
+
*/
|
|
226
|
+
private notAUserReason;
|
|
227
|
+
/**
|
|
228
|
+
* Check the role records for members that are contacts (#2040), once at
|
|
229
|
+
* start-up when every manager is up. Nothing is dropped or rewritten: each
|
|
230
|
+
* role holding contacts is named, with how many, in a needs-attention entry
|
|
231
|
+
* on the dashboard and a log line, and administrators get one notification
|
|
232
|
+
* (not a second while the first is still active). A contact cannot sign in,
|
|
233
|
+
* so such a membership gives nothing; an administrator decides what to do.
|
|
234
|
+
*
|
|
235
|
+
* @returns role name → how many of its members are contacts
|
|
236
|
+
*/
|
|
237
|
+
reportContactMembersAtBoot(): Promise<Record<string, number>>;
|
|
238
|
+
/**
|
|
239
|
+
* One error notification for the users holding `admin-system`, unless one
|
|
240
|
+
* with this title is still active. Nobody to tell: the log line stands
|
|
241
|
+
* alone, since an empty target list would tell every user.
|
|
242
|
+
*/
|
|
243
|
+
private notifyAdminsOnce;
|
|
202
244
|
/** The account must exist; membership of a missing account is refused. */
|
|
203
245
|
private requireAccount;
|
|
204
246
|
/** #1204: a role assigned or removed is a user-edit; what the account may do changed. */
|
|
@@ -117,6 +117,20 @@ const DATABASE_MIGRATIONS = [
|
|
|
117
117
|
db.exec('CREATE INDEX review_requests_by_status ON review_requests (status, filed_at)');
|
|
118
118
|
db.exec('CREATE INDEX review_requests_by_decided ON review_requests (decided_at)');
|
|
119
119
|
}
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
// #2041: a row names the owner of its resource by identity @id in
|
|
123
|
+
// `metadata.owner` and by username in `metadata.ownerUser`, so the rows
|
|
124
|
+
// about an account can still be listed after the account (and its
|
|
125
|
+
// identity record) is deleted. This column is the copy of `ownerUser`
|
|
126
|
+
// for the index; rows written before it are null, and their `owner` is
|
|
127
|
+
// the username anyway.
|
|
128
|
+
id: '20261026120000',
|
|
129
|
+
description: 'audit log: audit_events.owner_user',
|
|
130
|
+
up: (db) => {
|
|
131
|
+
db.exec('ALTER TABLE audit_events ADD COLUMN owner_user TEXT');
|
|
132
|
+
db.exec('CREATE INDEX audit_events_by_owner_user ON audit_events (owner_user, timestamp)');
|
|
133
|
+
}
|
|
120
134
|
}
|
|
121
135
|
];
|
|
122
136
|
export default DATABASE_MIGRATIONS;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface PageComment {
|
|
2
2
|
id: string;
|
|
3
3
|
pageUuid: string;
|
|
4
|
+
/** Who wrote it: their identity `@id` (#2041); a username on a comment written before and not yet converted (a vault page's), or one with no account. */
|
|
4
5
|
author: string;
|
|
5
6
|
authorDisplayName: string;
|
|
6
7
|
content: string;
|
|
@@ -5,9 +5,12 @@
|
|
|
5
5
|
* org under `ngdpbase.application.organization.storagedir`. The install's
|
|
6
6
|
* anchor org is identified by `ngdpbase.application.organization.file`.
|
|
7
7
|
*
|
|
8
|
-
* `@id` is the organization's
|
|
9
|
-
*
|
|
10
|
-
*
|
|
8
|
+
* `@id` is a `urn:uuid:<v4>` made with the record, the organization's id for
|
|
9
|
+
* life (#2041, identity model step 6). Its web address is the ordinary `url`
|
|
10
|
+
* field, never the id. Organizations made before #2041 keep the `@id` they
|
|
11
|
+
* were given (often their URL); nothing refers to an organization by URL as
|
|
12
|
+
* its id from then on. Use the schema.org `identifier` field for any legacy
|
|
13
|
+
* slug compatibility.
|
|
11
14
|
*
|
|
12
15
|
* @see https://schema.org/Organization
|
|
13
16
|
*/
|
|
@@ -61,13 +64,13 @@ export type ContactPointEntry = ContactPoint | PostalAddress;
|
|
|
61
64
|
export interface Organization {
|
|
62
65
|
'@context': 'https://schema.org';
|
|
63
66
|
'@type': 'Organization';
|
|
64
|
-
/**
|
|
67
|
+
/** The organization's id for life: `urn:uuid:<v4>` (#2041); an organization made before #2041 keeps its own, often its URL */
|
|
65
68
|
'@id': string;
|
|
66
69
|
/** Human-readable name */
|
|
67
70
|
name: string;
|
|
68
71
|
/** Legal/registered name if different from `name` */
|
|
69
72
|
legalName?: string;
|
|
70
|
-
/** Public web
|
|
73
|
+
/** Public web address: an ordinary field, never the id (#2041) */
|
|
71
74
|
url?: string;
|
|
72
75
|
/** Logo URL */
|
|
73
76
|
logo?: string;
|
|
@@ -87,10 +90,10 @@ export interface Organization {
|
|
|
87
90
|
[key: string]: unknown;
|
|
88
91
|
}
|
|
89
92
|
/**
|
|
90
|
-
* What a caller gives `OrganizationManager.create` (#1930): the fields
|
|
91
|
-
* `@id`
|
|
93
|
+
* What a caller gives `OrganizationManager.create` (#1930): the fields. The
|
|
94
|
+
* `@id` is never the caller's: `create` makes a new `urn:uuid:` (#2041).
|
|
92
95
|
*/
|
|
93
|
-
export type OrganizationInput = Partial<Organization
|
|
96
|
+
export type OrganizationInput = Partial<Omit<Organization, '@id'>> & {
|
|
94
97
|
name: string;
|
|
95
98
|
};
|
|
96
99
|
/**
|
|
@@ -126,8 +126,10 @@ export interface TokenInput extends CommonInput {
|
|
|
126
126
|
op: TokenOp;
|
|
127
127
|
/** Token id — the handle every later question about this credential uses. */
|
|
128
128
|
id: string;
|
|
129
|
-
/** Whose token it is, which is not always who acted
|
|
129
|
+
/** Whose token it is, which is not always who acted (an admin may revoke): their identity @id (#2041), or a name with no account as given. */
|
|
130
130
|
owner: string;
|
|
131
|
+
/** The token owner's username, written beside `owner` (#2041) so the rows still list after the account is deleted. */
|
|
132
|
+
ownerUser?: string;
|
|
131
133
|
/** Operator-supplied label. Optional; a token need not be named. */
|
|
132
134
|
name?: string | null;
|
|
133
135
|
/** Only meaningful for `mint`; ignored otherwise. */
|
|
@@ -149,11 +151,14 @@ export type AttachmentDetail = {
|
|
|
149
151
|
filename: string;
|
|
150
152
|
pageName?: string | null;
|
|
151
153
|
sizeBytes?: number | null;
|
|
152
|
-
}
|
|
154
|
+
}
|
|
155
|
+
/** A sealed store's file: `owner` is the store owner's identity @id in the record, `ownerUser` their username beside it (#2041). */
|
|
156
|
+
| {
|
|
153
157
|
attachmentId: string;
|
|
154
158
|
sealedStore: {
|
|
155
159
|
owner: string;
|
|
156
160
|
store: string;
|
|
161
|
+
ownerUser?: string;
|
|
157
162
|
};
|
|
158
163
|
};
|
|
159
164
|
export type AttachmentInput = CommonInput & {
|
|
@@ -148,7 +148,7 @@ export function buildAttachmentAuditEvent(input) {
|
|
|
148
148
|
const { op, attachmentId, viaToken } = input;
|
|
149
149
|
const who = whoOf(input);
|
|
150
150
|
const metadata = 'sealedStore' in input
|
|
151
|
-
? { attachmentId, owner: input.sealedStore.owner, store: input.sealedStore.store, sealed: true, ...tokenMetadata(viaToken), ...who.metadata }
|
|
151
|
+
? { attachmentId, owner: input.sealedStore.owner, ...(input.sealedStore.ownerUser ? { ownerUser: input.sealedStore.ownerUser } : {}), store: input.sealedStore.store, sealed: true, ...tokenMetadata(viaToken), ...who.metadata }
|
|
152
152
|
: { attachmentId, filename: input.filename, ...tokenMetadata(viaToken), ...who.metadata };
|
|
153
153
|
if (!('sealedStore' in input)) {
|
|
154
154
|
if (input.pageName)
|
|
@@ -273,11 +273,12 @@ export async function recordAuditEvent(sink, event, onError) {
|
|
|
273
273
|
* every event is useless as a filter.
|
|
274
274
|
*/
|
|
275
275
|
export function buildTokenAuditEvent(input) {
|
|
276
|
-
const { op, id, owner, name, scopes, expiresAt, previousExpiresAt, revokedBy, viaToken } = input;
|
|
276
|
+
const { op, id, owner, ownerUser, name, scopes, expiresAt, previousExpiresAt, revokedBy, viaToken } = input;
|
|
277
277
|
const who = whoOf(input);
|
|
278
278
|
const metadata = {
|
|
279
279
|
id,
|
|
280
280
|
owner,
|
|
281
|
+
...(ownerUser ? { ownerUser } : {}),
|
|
281
282
|
name: name ?? null,
|
|
282
283
|
...tokenMetadata(viaToken),
|
|
283
284
|
...who.metadata
|
|
@@ -55,5 +55,20 @@ export declare const RESERVED_USERNAMES: readonly string[];
|
|
|
55
55
|
* identifier can never be, or later become, someone's username.
|
|
56
56
|
*/
|
|
57
57
|
export declare const ACCOUNTLESS_PERSON_PREFIX = "person:";
|
|
58
|
-
/**
|
|
58
|
+
/**
|
|
59
|
+
* The prefix of an identity's `@id` (#2041): `urn:uuid:<v4>`, and older
|
|
60
|
+
* organizations' `urn:…` ids. No username may start with it, so a stored
|
|
61
|
+
* reference is always told apart: a value starting `urn:` is an `@id`,
|
|
62
|
+
* anything else a username not yet converted.
|
|
63
|
+
*/
|
|
64
|
+
export declare const IDENTITY_ID_PREFIX = "urn:";
|
|
65
|
+
/**
|
|
66
|
+
* The prefix a start-up conversion gives a stored username that has no
|
|
67
|
+
* account (#2041): `orphan:<name>`. The record is then nobody's: a later
|
|
68
|
+
* account with the same name never takes it over, no "is it mine" check
|
|
69
|
+
* matches it, and only an administrator can act on it. No username may
|
|
70
|
+
* start with it.
|
|
71
|
+
*/
|
|
72
|
+
export declare const ORPHAN_PREFIX = "orphan:";
|
|
73
|
+
/** True when the name belongs to a principal rather than a user, or has the contact, `@id` or orphan prefix. */
|
|
59
74
|
export declare function isReservedUsername(username: unknown): boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jwilleke/ngdpbase-types",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.34.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",
|