@aglyn/tenant-data-admin 1.0.0-beta.218 → 1.0.0-beta.219
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/package.json +7 -7
- package/src/lib/server/api-v1-description.d.ts +112 -0
- package/src/lib/server/api-v1-description.js +126 -0
- package/src/lib/server/api-v1-description.js.map +1 -0
- package/src/lib/server/api-v1-kit.d.ts +156 -0
- package/src/lib/server/api-v1-kit.js +191 -0
- package/src/lib/server/api-v1-kit.js.map +1 -0
- package/src/lib/server/api-v1-resources.d.ts +144 -0
- package/src/lib/server/api-v1-resources.js +126 -0
- package/src/lib/server/api-v1-resources.js.map +1 -0
- package/src/lib/server/consent-group-change.d.ts +2 -1
- package/src/lib/server/consent-group-change.js +41 -16
- package/src/lib/server/consent-group-change.js.map +1 -1
- package/src/lib/server/contact-email-index.d.ts +22 -0
- package/src/lib/server/contact-email-index.js +108 -0
- package/src/lib/server/contact-email-index.js.map +1 -1
- package/src/lib/server/crm-email-activity.d.ts +1 -1
- package/src/lib/server/crm-email-activity.js +1 -1
- package/src/lib/server/crm-email-activity.js.map +1 -1
- package/src/lib/server/crm-inbound-email.d.ts +23 -0
- package/src/lib/server/crm-inbound-email.js +1 -16
- package/src/lib/server/crm-inbound-email.js.map +1 -1
- package/src/lib/server/crm-records.d.ts +9 -1
- package/src/lib/server/crm-records.js +19 -2
- package/src/lib/server/crm-records.js.map +1 -1
- package/src/lib/server/cron-auth.d.ts +44 -0
- package/src/lib/server/cron-auth.js +76 -0
- package/src/lib/server/cron-auth.js.map +1 -0
- package/src/lib/server/dataset-live-pages.js +6 -6
- package/src/lib/server/dataset-live-pages.js.map +1 -1
- package/src/lib/server/duplicate-resource.d.ts +26 -0
- package/src/lib/server/duplicate-resource.js +47 -25
- package/src/lib/server/duplicate-resource.js.map +1 -1
- package/src/lib/server/dynamic-list-materialize.js +4 -1
- package/src/lib/server/dynamic-list-materialize.js.map +1 -1
- package/src/lib/server/email-suppression.d.ts +11 -0
- package/src/lib/server/email-suppression.js +36 -1
- package/src/lib/server/email-suppression.js.map +1 -1
- package/src/lib/server/email-unsubscribe-link.d.ts +8 -5
- package/src/lib/server/email-unsubscribe-link.js +7 -7
- package/src/lib/server/email-unsubscribe-link.js.map +1 -1
- package/src/lib/server/erase.d.ts +19 -33
- package/src/lib/server/erase.js +37 -162
- package/src/lib/server/erase.js.map +1 -1
- package/src/lib/server/host-email-tokens.js +1 -1
- package/src/lib/server/host-email-tokens.js.map +1 -1
- package/src/lib/server/host-memberships.js +3 -3
- package/src/lib/server/host-memberships.js.map +1 -1
- package/src/lib/server/member-email-aliases.d.ts +1 -1
- package/src/lib/server/member-email-aliases.js +1 -1
- package/src/lib/server/member-email-aliases.js.map +1 -1
- package/src/lib/server/new-account-announcement.d.ts +62 -0
- package/src/lib/server/new-account-announcement.js +77 -0
- package/src/lib/server/new-account-announcement.js.map +1 -0
- package/src/lib/server/notifications.d.ts +8 -2
- package/src/lib/server/notifications.js +11 -3
- package/src/lib/server/notifications.js.map +1 -1
- package/src/lib/server/{connect-payout-failure.d.ts → payment-provider-stripe-connect-payouts.d.ts} +3 -0
- package/src/lib/server/{connect-payout-failure.js → payment-provider-stripe-connect-payouts.js} +1 -1
- package/src/lib/server/payment-provider-stripe-connect-payouts.js.map +1 -0
- package/src/lib/server/{connect-account-status.d.ts → payment-provider-stripe-connect-status.d.ts} +3 -0
- package/src/lib/server/{connect-account-status.js → payment-provider-stripe-connect-status.js} +1 -1
- package/src/lib/server/payment-provider-stripe-connect-status.js.map +1 -0
- package/src/lib/server/{stripe-account-mode.d.ts → payment-provider-stripe-connect.d.ts} +45 -70
- package/src/lib/server/{stripe-account-mode.js → payment-provider-stripe-connect.js} +78 -52
- package/src/lib/server/payment-provider-stripe-connect.js.map +1 -0
- package/src/lib/server/payment-provider.d.ts +173 -0
- package/src/lib/server/payment-provider.js +70 -0
- package/src/lib/server/payment-provider.js.map +1 -0
- package/src/lib/server/personal-data-export.js +1 -1
- package/src/lib/server/personal-data-export.js.map +1 -1
- package/src/lib/server/rate-limit-store.js +13 -3
- package/src/lib/server/rate-limit-store.js.map +1 -1
- package/src/lib/server/realm-plugins.js +8 -3
- package/src/lib/server/realm-plugins.js.map +1 -1
- package/src/lib/server/retained-refusals.d.ts +142 -0
- package/src/lib/server/retained-refusals.js +325 -0
- package/src/lib/server/retained-refusals.js.map +1 -0
- package/src/lib/server/test-firestore-queries.d.ts +7 -0
- package/src/lib/server/test-firestore-queries.js +70 -27
- package/src/lib/server/test-firestore-queries.js.map +1 -1
- package/src/lib/server/upsert-contact.d.ts +46 -1
- package/src/lib/server/upsert-contact.js +85 -18
- package/src/lib/server/upsert-contact.js.map +1 -1
- package/src/tenancy.d.ts +4 -5
- package/src/tenancy.js +11 -6
- package/src/tenancy.js.map +1 -1
- package/src/lib/server/connect-account-status.js.map +0 -1
- package/src/lib/server/connect-payout-failure.js.map +0 -1
- package/src/lib/server/contact-lifecycle-floor.d.ts +0 -89
- package/src/lib/server/contact-lifecycle-floor.js +0 -91
- package/src/lib/server/contact-lifecycle-floor.js.map +0 -1
- package/src/lib/server/contact-merge.d.ts +0 -55
- package/src/lib/server/contact-merge.js +0 -288
- package/src/lib/server/contact-merge.js.map +0 -1
- package/src/lib/server/form-dataset-binding-token.d.ts +0 -35
- package/src/lib/server/form-dataset-binding-token.js +0 -107
- package/src/lib/server/form-dataset-binding-token.js.map +0 -1
- package/src/lib/server/stripe-account-mode.js.map +0 -1
|
@@ -86,15 +86,6 @@ export declare function eraseHost(hostId: string, options?: EraseHostOptions): P
|
|
|
86
86
|
* Authorization belongs to the caller — this only walks the tree.
|
|
87
87
|
*/
|
|
88
88
|
export declare function eraseSubtree(path: readonly [string, string, string, string]): Promise<void>;
|
|
89
|
-
/**
|
|
90
|
-
* What an erasure did with `publisherProfiles/{orgId}` (AGL-1970).
|
|
91
|
-
*
|
|
92
|
-
* Three outcomes rather than a boolean, because the third one is a decision
|
|
93
|
-
* the audit row has to be able to state: a listing the erased org published
|
|
94
|
-
* can outlive it, and a listing whose publisher document is simply gone is
|
|
95
|
-
* unattributable in a way nobody chose.
|
|
96
|
-
*/
|
|
97
|
-
export type PublisherProfileDisposition = 'absent' | 'deleted' | 'tombstoned';
|
|
98
89
|
/**
|
|
99
90
|
* The two things an OPERATOR needs that a served caller does not (AGL-1481).
|
|
100
91
|
*
|
|
@@ -180,10 +171,13 @@ export interface EraseOrgResult {
|
|
|
180
171
|
* each eraser's count of what it would do.
|
|
181
172
|
*/
|
|
182
173
|
plugins?: Record<string, PluginOrgErasureReport | null>;
|
|
183
|
-
/**
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
174
|
+
/**
|
|
175
|
+
* Rows destroyed from each plugin's top-level collection keyed by the org
|
|
176
|
+
* (AGL-3080), keyed by collection: a connected mailbox's grant (AGL-2974),
|
|
177
|
+
* a sequence email's tracking links (AGL-3297). Outside the org path, and
|
|
178
|
+
* declared by their owners rather than named here.
|
|
179
|
+
*/
|
|
180
|
+
pluginCollections?: Record<string, number>;
|
|
187
181
|
/** Public SSO routing docs destroyed (AGL-1448) — outside the org path. */
|
|
188
182
|
ssoDomains?: number;
|
|
189
183
|
/** Custom console domains released (AGL-1448) — outside the org path. */
|
|
@@ -209,17 +203,6 @@ export interface EraseOrgResult {
|
|
|
209
203
|
* same number as one that reached it and found nothing.
|
|
210
204
|
*/
|
|
211
205
|
hostMemberships?: number | null;
|
|
212
|
-
/** Marketplace handle reservations released (AGL-1970) — id-keyed, public. */
|
|
213
|
-
publisherHandles?: number;
|
|
214
|
-
/** What became of the org's public publisher profile (AGL-1970). */
|
|
215
|
-
publisherProfile?: PublisherProfileDisposition;
|
|
216
|
-
/**
|
|
217
|
-
* Marketplace listings that OUTLIVE this erasure and still name the org
|
|
218
|
-
* (AGL-1970/AGL-1448 Tier 3). Reported rather than deleted: an erasure that
|
|
219
|
-
* leaves something standing has to say so, and this is the number that makes
|
|
220
|
-
* the parked listing-survival decision cost something measurable.
|
|
221
|
-
*/
|
|
222
|
-
listingsRetained?: number;
|
|
223
206
|
/**
|
|
224
207
|
* Support threads destroyed, each with its `messages` subtree (AGL-1971) —
|
|
225
208
|
* the highest-PII-density collection outside the org path.
|
|
@@ -262,15 +245,18 @@ export interface EraseOrgResult {
|
|
|
262
245
|
* 2. Nothing is written. This step used to persist a final JSON export of
|
|
263
246
|
* the org and host trees to `erasures/{orgId}/…` and abort if it could
|
|
264
247
|
* not — see the note below for why it is gone (AGL-1443).
|
|
265
|
-
* 3.
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
*
|
|
248
|
+
* 3. The plugins' erasers first (AGL-2978), while everything they may need
|
|
249
|
+
* to act through still exists — a REQUIRED one (the marketplace's, which
|
|
250
|
+
* erases the org's public publisher identity, AGL-1970) fails the step
|
|
251
|
+
* rather than recording its share as undone. Then revoke the org's API
|
|
252
|
+
* credentials (AGL-1444), its public routing — SSO domains and custom
|
|
253
|
+
* console domains (AGL-1448) — and sweep every plugin collection keyed
|
|
254
|
+
* by the org that its owner declared (a connected mailbox's OAuth grant,
|
|
255
|
+
* AGL-2974). All of them are top-level collections keyed by something
|
|
256
|
+
* other than a path under the org, so the org-tree delete cannot reach
|
|
257
|
+
* them; doing them before the content delete also closes the mid-erasure
|
|
258
|
+
* window, in which a credential, a domain or a public publisher page
|
|
259
|
+
* still resolves to a half-deleted workspace.
|
|
274
260
|
* 4. Delete each host (eraseHost), org-level Storage, the Stripe customer
|
|
275
261
|
* AND its local reverse index, member back-references, then the org tree
|
|
276
262
|
* and every slug the org ever held — tombstones included (AGL-1448).
|
package/src/lib/server/erase.js
CHANGED
|
@@ -26,7 +26,7 @@ import { resolveAccountAddresses } from "./account-addresses.js";
|
|
|
26
26
|
import { EMAIL_IDENTITY_INDEX_COLLECTION } from "./account-emails.js";
|
|
27
27
|
import { removeOrgMember } from "./organizations.js";
|
|
28
28
|
import { runPluginUserErasers } from "@aglyn/aglyn/plugin-manager/plugin-user-erasure";
|
|
29
|
-
import { runPluginOrgErasers } from "@aglyn/aglyn/plugin-manager/plugin-org-erasure";
|
|
29
|
+
import { PLUGIN_ORG_KEYED_COLLECTIONS, runPluginOrgErasers } from "@aglyn/aglyn/plugin-manager/plugin-org-erasure";
|
|
30
30
|
import { isBillingSubscription } from "@aglyn/aglyn/server";
|
|
31
31
|
// The module path rather than the barrel: this module's specs substitute the
|
|
32
32
|
// barrel, and which sites a consent group still needs is the rule itself.
|
|
@@ -55,31 +55,6 @@ import { addAdminAudit } from "./admin-audit-write.js";
|
|
|
55
55
|
* emulator spec asserts the two spellings still agree by reading that file,
|
|
56
56
|
* so a rename fails a test rather than silently un-wiring this sweep.
|
|
57
57
|
*/ const SUPPLIER_DELIVERY_COLLECTION = 'supplierDeliveries';
|
|
58
|
-
/**
|
|
59
|
-
* `outreachMailboxCredentials` — the OAuth grant behind each mailbox a rep
|
|
60
|
-
* connects to Outreach, keyed by the mailbox id and carrying `orgId` as a
|
|
61
|
-
* FIELD.
|
|
62
|
-
*
|
|
63
|
-
* The literal rather than an import, for `SUPPLIER_DELIVERY_COLLECTION`'s
|
|
64
|
-
* reason: the collection belongs to `libs/plugins/outreach`
|
|
65
|
-
* (`OUTREACH_COLLECTIONS.mailboxCredentials` in
|
|
66
|
-
* `src/lib/model/outreach.types.ts`), and this `scope:data` library may not
|
|
67
|
-
* import a plugin. The plugin's own spec asserts the two spellings agree, so
|
|
68
|
-
* a rename fails a test rather than silently un-wiring the sweep.
|
|
69
|
-
*
|
|
70
|
-
* A static sweep here, not a hook the plugin registers, because an erasure
|
|
71
|
-
* cannot rely on the plugin being present. An org that switched Outreach off
|
|
72
|
-
* still holds the tokens its reps granted, and the process running the
|
|
73
|
-
* erasure — the cron, the operator script, a replay — may never have loaded
|
|
74
|
-
* the plugin's bundle at all. A credential's lifetime cannot depend on which
|
|
75
|
-
* bundles an erasing process happened to load.
|
|
76
|
-
*/ const OUTREACH_MAILBOX_CREDENTIALS_COLLECTION = 'outreachMailboxCredentials';
|
|
77
|
-
/**
|
|
78
|
-
* `outreachLinks` — Outreach's short tracking links (AGL-3297), one document
|
|
79
|
-
* per link in a sent sequence email, keyed by the link's random id and
|
|
80
|
-
* carrying `orgId` as a FIELD. A literal for the reason above, held to the
|
|
81
|
-
* plugin's spelling by the same spec.
|
|
82
|
-
*/ const OUTREACH_LINKS_COLLECTION = 'outreachLinks';
|
|
83
58
|
/**
|
|
84
59
|
* Destroy the site's DEAD-LETTERED supplier deliveries (AGL-1448).
|
|
85
60
|
*
|
|
@@ -331,27 +306,21 @@ export async function eraseHost(hostId, options = {}) {
|
|
|
331
306
|
return deleteDocsByOrgId('apiKeys', orgId, dryRun);
|
|
332
307
|
}
|
|
333
308
|
/**
|
|
334
|
-
*
|
|
335
|
-
*
|
|
336
|
-
*
|
|
337
|
-
*
|
|
338
|
-
*
|
|
339
|
-
*
|
|
340
|
-
*
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
* holds every other workspace's connected mailboxes too.
|
|
350
|
-
*
|
|
351
|
-
* Returns the number destroyed, for the audit row — an erasure trail that
|
|
352
|
-
* understates what it removed is the one record that has to be right.
|
|
353
|
-
*/ async function eraseOrgOutreachMailboxCredentials(orgId, dryRun = false) {
|
|
354
|
-
return deleteDocsByOrgId(OUTREACH_MAILBOX_CREDENTIALS_COLLECTION, orgId, dryRun);
|
|
309
|
+
* Sweep every top-level plugin collection keyed by this org (AGL-3080).
|
|
310
|
+
*
|
|
311
|
+
* The collections are the plugins' own, DECLARED by each owner in
|
|
312
|
+
* `plugins.config.json` (`orgKeyedCollections`) and compiled into core, so the
|
|
313
|
+
* sweep names none of them and runs in every process whether or not the
|
|
314
|
+
* owner is loaded. Sequential and field-bounded like every other org-keyed
|
|
315
|
+
* sweep: each of these collections holds every other workspace's rows too.
|
|
316
|
+
*
|
|
317
|
+
* Returns the count destroyed per collection, for the audit row.
|
|
318
|
+
*/ async function erasePluginOrgKeyedCollections(orgId, dryRun = false) {
|
|
319
|
+
const counts = {};
|
|
320
|
+
for (const { name, orgField } of PLUGIN_ORG_KEYED_COLLECTIONS){
|
|
321
|
+
counts[name] = await deleteDocsByOrgId(name, orgId, dryRun, orgField);
|
|
322
|
+
}
|
|
323
|
+
return counts;
|
|
355
324
|
}
|
|
356
325
|
/**
|
|
357
326
|
* Delete every document in a TOP-LEVEL collection that names this org in its
|
|
@@ -390,10 +359,10 @@ export async function eraseHost(hostId, options = {}) {
|
|
|
390
359
|
* including the tax a `mode: 'stripe'` store collects under AGLYN's own
|
|
391
360
|
* Texas registration, which Aglyn must be able to account for long after the
|
|
392
361
|
* merchant has gone.
|
|
393
|
-
*/ async function deleteDocsByOrgId(collection, orgId, dryRun = false) {
|
|
362
|
+
*/ async function deleteDocsByOrgId(collection, orgId, dryRun = false, orgField = 'orgId') {
|
|
394
363
|
if (!orgId) return 0;
|
|
395
364
|
const firestore = firebaseAdmin.app().firestore();
|
|
396
|
-
const rows = await firestore.collection(collection).where(
|
|
365
|
+
const rows = await firestore.collection(collection).where(orgField, '==', orgId).get();
|
|
397
366
|
if (dryRun) return rows.size;
|
|
398
367
|
for(let index = 0; index < rows.docs.length; index += 400){
|
|
399
368
|
const batch = firestore.batch();
|
|
@@ -645,99 +614,6 @@ export async function eraseHost(hostId, options = {}) {
|
|
|
645
614
|
return null;
|
|
646
615
|
}
|
|
647
616
|
}
|
|
648
|
-
/**
|
|
649
|
-
* Drop the org's PUBLIC marketplace identity — the profile and every handle
|
|
650
|
-
* it reserved (AGL-1970).
|
|
651
|
-
*
|
|
652
|
-
* Both survived every erasure until now, and both are `allow read: if true`
|
|
653
|
-
* (`cloud/firebase-firestore.rules`, `match /publisherProfiles/{orgId}`). This
|
|
654
|
-
* is the AGL-1448 shape with one twist that is the whole reason it was missed:
|
|
655
|
-
* `publisherProfiles` is keyed by the org id **as the document id**, so it is
|
|
656
|
-
* invisible to `deleteDocsByOrgId` *and* to a reader scanning this file for
|
|
657
|
-
* the field-keyed sweep list. Nothing prompts you to notice it.
|
|
658
|
-
*
|
|
659
|
-
* What survived is not cosmetic. `stripeAccountId` is a **payout
|
|
660
|
-
* destination** — written server-side only after Connect onboarding/KYC and
|
|
661
|
-
* trusted by every checkout path — so an erased org stayed world-readable with
|
|
662
|
-
* a live payment-account identifier attached to a dead identity. That is the
|
|
663
|
-
* `stripeCustomers` correlation AGL-1448 removed, pointing the other way.
|
|
664
|
-
*
|
|
665
|
-
* `publisherHandles` is the easy half and goes unconditionally: it carries
|
|
666
|
-
* `orgId` as a FIELD, so `deleteDocsByOrgId` fits exactly, and it is a **live
|
|
667
|
-
* reservation** — `claimPublisherHandle` refuses a handle another org's row
|
|
668
|
-
* names, so a ghost holds a marketplace name against a real customer with no
|
|
669
|
-
* way to appeal. That is the `ssoDomains` argument verbatim, and absence is
|
|
670
|
-
* again the only state that both stops the read and releases the name. Rename
|
|
671
|
-
* tombstones (`{ orgId, movedTo }`) carry the same `orgId` and go with it, and
|
|
672
|
-
* the field bound is what makes that safe: re-claiming a tombstone FULL-
|
|
673
|
-
* REPLACES it with the new owner's `{ orgId }`, so a handle this org renamed
|
|
674
|
-
* away from and somebody else has since taken does not match the query.
|
|
675
|
-
*
|
|
676
|
-
* **The profile is the half with a genuine tension, and it is resolved here
|
|
677
|
-
* rather than deferred.** `marketplaceListings` outlives an erasure — that is
|
|
678
|
-
* AGL-1448's parked Tier 3 product decision, an erased org's listing being
|
|
679
|
-
* something buyers paid for — so deleting the publisher document outright can
|
|
680
|
-
* leave a listing attributed to nothing. So:
|
|
681
|
-
*
|
|
682
|
-
* - **No surviving listing** → `recursiveDelete` the profile. `deleted`.
|
|
683
|
-
* `recursiveDelete` and not `delete`: `publish-plugin` keeps its daily
|
|
684
|
-
* publish-rate window at `publisherProfiles/{orgId}/meta/publishWindow`,
|
|
685
|
-
* and a document delete would orphan it under a path with no owner.
|
|
686
|
-
* - **A listing survives** → the same `recursiveDelete`, then a minimal
|
|
687
|
-
* `{ erased: true, erasedAt }` in its place. `tombstoned`. The tombstone
|
|
688
|
-
* carries no `handle`, no `displayName`, no `bio`/`avatarUrl`/`website`,
|
|
689
|
-
* no `publisherAgreement` and — the point — no `stripeAccountId`. It is
|
|
690
|
-
* "an internal record that the erasure happened", which is exactly what
|
|
691
|
-
* the Privacy Policy §5 sentence reserves; every byte the sentence calls
|
|
692
|
-
* content is gone either way. Readers already handle it: with no `handle`,
|
|
693
|
-
* `resolvePublisherProfile` returns `null`, which is the pre-existing
|
|
694
|
-
* "this org has no profile" path.
|
|
695
|
-
*
|
|
696
|
-
* The count of surviving listings is returned either way, so an erasure that
|
|
697
|
-
* left something standing SAYS SO in its audit row instead of reporting a
|
|
698
|
-
* clean success — the Tier 3 decision is still open, and an erasure is the
|
|
699
|
-
* one place its cost is measurable.
|
|
700
|
-
*/ async function eraseOrgPublisherIdentity(orgId, dryRun = false) {
|
|
701
|
-
const firestore = firebaseAdmin.app().firestore();
|
|
702
|
-
const handles = await deleteDocsByOrgId('publisherHandles', orgId, dryRun);
|
|
703
|
-
const profileRef = firestore.collection('publisherProfiles').doc(orgId);
|
|
704
|
-
const [profileSnapshot, listings] = await Promise.all([
|
|
705
|
-
profileRef.get(),
|
|
706
|
-
// `profileId` is the listing's publishing-org id (AGL-652) — the same
|
|
707
|
-
// value as this profile's document id, under the older field name.
|
|
708
|
-
firestore.collection('marketplaceListings').where('profileId', '==', orgId).get()
|
|
709
|
-
]);
|
|
710
|
-
const listingsRetained = listings.size;
|
|
711
|
-
if (!profileSnapshot.exists) {
|
|
712
|
-
return {
|
|
713
|
-
handles,
|
|
714
|
-
profile: 'absent',
|
|
715
|
-
listingsRetained
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
if (dryRun) {
|
|
719
|
-
return {
|
|
720
|
-
handles,
|
|
721
|
-
profile: listingsRetained ? 'tombstoned' : 'deleted',
|
|
722
|
-
listingsRetained
|
|
723
|
-
};
|
|
724
|
-
}
|
|
725
|
-
await firestore.recursiveDelete(profileRef);
|
|
726
|
-
if (!listingsRetained) return {
|
|
727
|
-
handles,
|
|
728
|
-
profile: 'deleted',
|
|
729
|
-
listingsRetained
|
|
730
|
-
};
|
|
731
|
-
await profileRef.set({
|
|
732
|
-
erased: true,
|
|
733
|
-
erasedAt: FieldValue.serverTimestamp()
|
|
734
|
-
});
|
|
735
|
-
return {
|
|
736
|
-
handles,
|
|
737
|
-
profile: 'tombstoned',
|
|
738
|
-
listingsRetained
|
|
739
|
-
};
|
|
740
|
-
}
|
|
741
617
|
/**
|
|
742
618
|
* Destroy every support ticket the org opened, and the messages under each
|
|
743
619
|
* (AGL-1971).
|
|
@@ -864,15 +740,18 @@ export async function eraseHost(hostId, options = {}) {
|
|
|
864
740
|
* 2. Nothing is written. This step used to persist a final JSON export of
|
|
865
741
|
* the org and host trees to `erasures/{orgId}/…` and abort if it could
|
|
866
742
|
* not — see the note below for why it is gone (AGL-1443).
|
|
867
|
-
* 3.
|
|
868
|
-
*
|
|
869
|
-
*
|
|
870
|
-
*
|
|
871
|
-
*
|
|
872
|
-
*
|
|
873
|
-
*
|
|
874
|
-
*
|
|
875
|
-
*
|
|
743
|
+
* 3. The plugins' erasers first (AGL-2978), while everything they may need
|
|
744
|
+
* to act through still exists — a REQUIRED one (the marketplace's, which
|
|
745
|
+
* erases the org's public publisher identity, AGL-1970) fails the step
|
|
746
|
+
* rather than recording its share as undone. Then revoke the org's API
|
|
747
|
+
* credentials (AGL-1444), its public routing — SSO domains and custom
|
|
748
|
+
* console domains (AGL-1448) — and sweep every plugin collection keyed
|
|
749
|
+
* by the org that its owner declared (a connected mailbox's OAuth grant,
|
|
750
|
+
* AGL-2974). All of them are top-level collections keyed by something
|
|
751
|
+
* other than a path under the org, so the org-tree delete cannot reach
|
|
752
|
+
* them; doing them before the content delete also closes the mid-erasure
|
|
753
|
+
* window, in which a credential, a domain or a public publisher page
|
|
754
|
+
* still resolves to a half-deleted workspace.
|
|
876
755
|
* 4. Delete each host (eraseHost), org-level Storage, the Stripe customer
|
|
877
756
|
* AND its local reverse index, member back-references, then the org tree
|
|
878
757
|
* and every slug the org ever held — tombstones included (AGL-1448).
|
|
@@ -986,20 +865,16 @@ export async function eraseHost(hostId, options = {}) {
|
|
|
986
865
|
// pass the org gate and reach a half-deleted workspace. Revoking first
|
|
987
866
|
// closes that window as well as the permanent one.
|
|
988
867
|
progress.apiKeys = await eraseOrgApiKeys(orgId, dryRun);
|
|
989
|
-
|
|
990
|
-
|
|
868
|
+
// The plugins' top-level collections keyed by this org (AGL-3080) — a
|
|
869
|
+
// connected mailbox's OAuth grant, a sequence email's tracking links.
|
|
870
|
+
// Declared by the owning plugin and swept HERE, statically, not by its
|
|
871
|
+
// eraser: an erasure cannot rely on a plugin being loaded, and a grant
|
|
872
|
+
// that can still send as a person must not outlive the workspace it was
|
|
873
|
+
// given to because an erasing process never loaded that plugin's bundle.
|
|
874
|
+
progress.pluginCollections = await erasePluginOrgKeyedCollections(orgId, dryRun);
|
|
991
875
|
progress.ssoDomains = await eraseOrgSsoDomains(orgId, dryRun);
|
|
992
876
|
progress.consoleDomains = await releaseOrgConsoleDomains(orgId, dryRun);
|
|
993
877
|
progress.apiIdempotency = await eraseOrgIdempotencyKeys(orgId, dryRun);
|
|
994
|
-
// The org's PUBLIC marketplace identity (AGL-1970). Here rather than with
|
|
995
|
-
// the Stripe step, though it carries a payout id, because `publisherHandles`
|
|
996
|
-
// is a live name reservation and belongs with the other reservations this
|
|
997
|
-
// step releases — and because the profile is world-readable, so the sooner
|
|
998
|
-
// it stops resolving the smaller the mid-erasure window.
|
|
999
|
-
const publisher = await eraseOrgPublisherIdentity(orgId, dryRun);
|
|
1000
|
-
progress.publisherHandles = publisher.handles;
|
|
1001
|
-
progress.publisherProfile = publisher.profile;
|
|
1002
|
-
progress.listingsRetained = publisher.listingsRetained;
|
|
1003
878
|
// Support threads (AGL-1971). Its own step because it is the only sweep
|
|
1004
879
|
// here that walks a subtree per row, so it is the one most likely to be
|
|
1005
880
|
// where a large workspace's erasure runs out of time — and a failure that
|