@aglyn/plugins-marketplace 1.10.0 → 1.12.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/package.json +14 -14
- package/src/lib/components/listing-content.component.js +78 -36
- package/src/lib/components/listing-content.component.js.map +1 -1
- package/src/lib/components/listing-detail-page.component.js +22 -9
- package/src/lib/components/listing-detail-page.component.js.map +1 -1
- package/src/lib/components/listing-review-status.component.d.ts +8 -0
- package/src/lib/components/listing-review-status.component.js +23 -3
- package/src/lib/components/listing-review-status.component.js.map +1 -1
- package/src/lib/components/listing-verification-request.component.d.ts +8 -0
- package/src/lib/components/listing-verification-request.component.js +12 -5
- package/src/lib/components/listing-verification-request.component.js.map +1 -1
- package/src/lib/components/marketplace-hub.component.js +2 -1
- package/src/lib/components/marketplace-hub.component.js.map +1 -1
- package/src/lib/components/org-seller-panel.component.d.ts +10 -0
- package/src/lib/components/org-seller-panel.component.js +26 -4
- package/src/lib/components/org-seller-panel.component.js.map +1 -1
- package/src/lib/components/starter-picker-shelf.component.js +13 -8
- package/src/lib/components/starter-picker-shelf.component.js.map +1 -1
- package/src/lib/declarations.d.ts +2 -1
- package/src/lib/declarations.js +5 -1
- package/src/lib/declarations.js.map +1 -1
- package/src/lib/model/marketplace-publisher-permissions.d.ts +26 -0
- package/src/lib/model/marketplace-publisher-permissions.js +64 -0
- package/src/lib/model/marketplace-publisher-permissions.js.map +1 -0
- package/src/lib/model/marketplace.js +2 -1
- package/src/lib/model/marketplace.js.map +1 -1
- package/src/lib/model/own-listing.d.ts +52 -0
- package/src/lib/model/own-listing.js +72 -0
- package/src/lib/model/own-listing.js.map +1 -0
- package/src/lib/model/starter-query.js +2 -0
- package/src/lib/model/starter-query.js.map +1 -1
- package/src/lib/server/connect.js +11 -5
- package/src/lib/server/connect.js.map +1 -1
- package/src/lib/server/install-dataset-schema.js +10 -2
- package/src/lib/server/install-dataset-schema.js.map +1 -1
- package/src/lib/server/install-email-starter.js +8 -2
- package/src/lib/server/install-email-starter.js.map +1 -1
- package/src/lib/server/install-email-template.js +8 -2
- package/src/lib/server/install-email-template.js.map +1 -1
- package/src/lib/server/install-layout.js +8 -2
- package/src/lib/server/install-layout.js.map +1 -1
- package/src/lib/server/install-plugin.js +24 -20
- package/src/lib/server/install-plugin.js.map +1 -1
- package/src/lib/server/install-template.js +8 -5
- package/src/lib/server/install-template.js.map +1 -1
- package/src/lib/server/install-theme.js +8 -2
- package/src/lib/server/install-theme.js.map +1 -1
- package/src/lib/server/install.js +10 -5
- package/src/lib/server/install.js.map +1 -1
- package/src/lib/server/listing-versions.js +24 -9
- package/src/lib/server/listing-versions.js.map +1 -1
- package/src/lib/server/own-listing.d.ts +46 -0
- package/src/lib/server/own-listing.js +52 -0
- package/src/lib/server/own-listing.js.map +1 -0
- package/src/lib/server/preview-image.d.ts +1 -1
- package/src/lib/server/preview-image.js +9 -8
- package/src/lib/server/preview-image.js.map +1 -1
- package/src/lib/server/publish-plugin.js +17 -19
- package/src/lib/server/publish-plugin.js.map +1 -1
- package/src/lib/server/publisher-permissions.d.ts +63 -0
- package/src/lib/server/publisher-permissions.js +51 -0
- package/src/lib/server/publisher-permissions.js.map +1 -0
- package/src/lib/server/publisher-profile-save.d.ts +4 -2
- package/src/lib/server/publisher-profile-save.js +12 -5
- package/src/lib/server/publisher-profile-save.js.map +1 -1
- package/src/lib/server/publisher-profile.d.ts +18 -6
- package/src/lib/server/publisher-profile.js +19 -8
- package/src/lib/server/publisher-profile.js.map +1 -1
- package/src/lib/server/reviews.js +6 -5
- package/src/lib/server/reviews.js.map +1 -1
- package/src/lib/server/sale-risk.d.ts +0 -6
- package/src/lib/server/sale-risk.js +0 -9
- package/src/lib/server/sale-risk.js.map +1 -1
- package/src/lib/server/sale.js +10 -9
- package/src/lib/server/sale.js.map +1 -1
- package/src/lib/server/site-starter-source.js +43 -17
- package/src/lib/server/site-starter-source.js.map +1 -1
- package/src/lib/server/update-artifact.js +11 -4
- package/src/lib/server/update-artifact.js.map +1 -1
- package/src/lib/server/verification-request.js +23 -7
- package/src/lib/server/verification-request.js.map +1 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/own-listing.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n isOwnListingInstall,\n isPublisherMemberElsewhere,\n landsInPublisherWorkspace,\n} from '../model/own-listing'\nimport { isPublisherMember } from './publisher-profile'\n\n/**\n * Whether this install is the publisher's own (AGL-2484, AGL-4047): the\n * person belongs to the workspace that publishes the listing AND the install\n * lands in that workspace. The one answer every install door, `update-artifact`\n * and the site starter source give to \"is this free because it is theirs\".\n *\n * `targetOrgId` is the workspace the install lands in, as the door resolved\n * it from the site (or the org it installs into), and the same workspace a\n * licence has to cover. Off the publisher's own sites the membership cannot\n * change the answer, so it is not read.\n */\nexport async function ownsListingHere(\n firestore: FirebaseFirestore.Firestore,\n facts: {\n /** The authenticated caller, never a value from the request body. */\n uid: string\n /** The listing's `profileId`: the workspace that publishes it. */\n publisherOrgId: unknown\n targetOrgId: unknown\n },\n): Promise<boolean> {\n const { uid, publisherOrgId, targetOrgId } = facts\n if (!landsInPublisherWorkspace(publisherOrgId, targetOrgId)) return false\n return isOwnListingInstall({\n publisherOrgId,\n targetOrgId,\n publisherMember: await isPublisherMember(firestore, uid, String(publisherOrgId)),\n })\n}\n\n/**\n * Whether the sale would refuse this person because they belong to the\n * publisher (AGL-3365, AGL-4047), asked before they press Buy so a surface can\n * say so. The refusal itself stays with `sale.ts`. On the publisher's own sites\n * the listing is free instead, so the membership is not read.\n */\nexport async function memberOfPublisherElsewhere(\n firestore: FirebaseFirestore.Firestore,\n facts: {\n /** The authenticated caller, never a value from the request body. */\n uid: string\n publisherOrgId: unknown\n targetOrgId: unknown\n },\n): Promise<boolean> {\n const { uid, publisherOrgId, targetOrgId } = facts\n if (landsInPublisherWorkspace(publisherOrgId, targetOrgId)) return false\n return isPublisherMemberElsewhere({\n publisherOrgId,\n targetOrgId,\n publisherMember: await isPublisherMember(firestore, uid, String(publisherOrgId ?? '')),\n })\n}\n"],"names":["isOwnListingInstall","isPublisherMemberElsewhere","landsInPublisherWorkspace","isPublisherMember","ownsListingHere","firestore","facts","uid","publisherOrgId","targetOrgId","publisherMember","String","memberOfPublisherElsewhere"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SACEA,mBAAmB,EACnBC,0BAA0B,EAC1BC,yBAAyB,QACpB,0BAAsB;AAC7B,SAASC,iBAAiB,QAAQ,yBAAqB;AAEvD;;;;;;;;;;CAUC,GACD,OAAO,eAAeC,gBACpBC,SAAsC,EACtCC,KAMC;IAED,MAAM,EAAEC,GAAG,EAAEC,cAAc,EAAEC,WAAW,EAAE,GAAGH;IAC7C,IAAI,CAACJ,0BAA0BM,gBAAgBC,cAAc,OAAO;IACpE,OAAOT,oBAAoB;QACzBQ;QACAC;QACAC,iBAAiB,MAAMP,kBAAkBE,WAAWE,KAAKI,OAAOH;IAClE;AACF;AAEA;;;;;CAKC,GACD,OAAO,eAAeI,2BACpBP,SAAsC,EACtCC,KAKC;IAED,MAAM,EAAEC,GAAG,EAAEC,cAAc,EAAEC,WAAW,EAAE,GAAGH;IAC7C,IAAIJ,0BAA0BM,gBAAgBC,cAAc,OAAO;IACnE,OAAOR,2BAA2B;QAChCO;QACAC;QACAC,iBAAiB,MAAMP,kBAAkBE,WAAWE,KAAKI,OAAOH,yBAAAA,iBAAkB;IACpF;AACF"}
|
|
@@ -24,7 +24,7 @@ import { type PluginApiHandler } from '@aglyn/aglyn/server';
|
|
|
24
24
|
*
|
|
25
25
|
* Lives in the plugin, like every other `marketplace/*` operation. It was the
|
|
26
26
|
* one that didn't: it sat in the console app and imported this plugin's
|
|
27
|
-
*
|
|
27
|
+
* publisher role helper across the `scope:app` → `aglyn:addons` boundary, which
|
|
28
28
|
* failed lint on main. Registered at the same path, so the URL clients call
|
|
29
29
|
* is unchanged — `/api/marketplace/preview-image` reaches the plugin catch-all
|
|
30
30
|
* with exactly the shape it had as a named app route.
|
|
@@ -19,7 +19,7 @@ import { isSvgUploadType, sanitizeSvgBuffer } from "@aglyn/aglyn/app-utils/sanit
|
|
|
19
19
|
import { inspectUploadBytes } from "@aglyn/aglyn/server";
|
|
20
20
|
import { firebaseAdmin, isImpersonationSession } from "@aglyn/tenant-data-admin";
|
|
21
21
|
import { randomUUID } from "crypto";
|
|
22
|
-
import {
|
|
22
|
+
import { manageListingsRefusal } from "./publisher-permissions.js";
|
|
23
23
|
import { recordPublisherAgreementAcceptance } from "./publisher-agreement-acceptance.js";
|
|
24
24
|
import { publisherAgreementConsentRefusal } from "../model/publisher-agreement.js";
|
|
25
25
|
import { isRefusedIdToken } from "@aglyn/tenant-data-admin/server/id-token-refusal";
|
|
@@ -35,7 +35,7 @@ const PREVIEW_PATH = (listingId)=>`marketplaceListings/${listingId}/preview`;
|
|
|
35
35
|
*
|
|
36
36
|
* Lives in the plugin, like every other `marketplace/*` operation. It was the
|
|
37
37
|
* one that didn't: it sat in the console app and imported this plugin's
|
|
38
|
-
*
|
|
38
|
+
* publisher role helper across the `scope:app` → `aglyn:addons` boundary, which
|
|
39
39
|
* failed lint on main. Registered at the same path, so the URL clients call
|
|
40
40
|
* is unchanged — `/api/marketplace/preview-image` reaches the plugin catch-all
|
|
41
41
|
* with exactly the shape it had as a named app route.
|
|
@@ -96,12 +96,13 @@ const PREVIEW_PATH = (listingId)=>`marketplaceListings/${listingId}/preview`;
|
|
|
96
96
|
// Listings are org-owned since AGL-652, so `profileId` holds an ORG id.
|
|
97
97
|
// Comparing it to a uid can never be true — it silently 403'd every
|
|
98
98
|
// publisher rather than erroring, which is why this survived the sweep.
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
99
|
+
// The picture is part of editing the listing, so it asks "Manage
|
|
100
|
+
// published listings" (AGL-4047), as the listing's own save does; staff
|
|
101
|
+
// were never let in here and still are not.
|
|
102
|
+
const keyRefusal = await manageListingsRefusal(decoded, listingSnapshot.get('profileId'), {
|
|
103
|
+
staffMayModerate: false
|
|
104
|
+
});
|
|
105
|
+
if (keyRefusal) return res.status(keyRefusal.status).json(keyRefusal.body);
|
|
105
106
|
// The agreement this save was sent with, recorded before the picture
|
|
106
107
|
// changes (AGL-4105). One record for whichever of the three ways in this
|
|
107
108
|
// is: removed, picked from the library, or uploaded.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/preview-image.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isFirstPartyMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport {\n isImageUploadContentType,\n normalizeImageContentType,\n} from '@aglyn/aglyn/app-utils/image-upload-types'\nimport {\n isSvgUploadType,\n sanitizeSvgBuffer,\n} from '@aglyn/aglyn/app-utils/sanitize-svg'\nimport { inspectUploadBytes, type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, isImpersonationSession } from '@aglyn/tenant-data-admin'\nimport { randomUUID } from 'crypto'\nimport { canActAsPublisher } from './publisher-profile'\nimport { recordPublisherAgreementAcceptance } from './publisher-agreement-acceptance'\nimport { publisherAgreementConsentRefusal } from '../model/publisher-agreement'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport type { DecodedIdToken } from 'firebase-admin/auth'\n\n// Base64 JSON payloads: 2MB of image encodes to ~2.7MB of body.\nconst MAX_FILE_BYTES = 2 * 1024 * 1024\n\nconst PREVIEW_PATH = (listingId: string) =>\n `marketplaceListings/${listingId}/preview`\n\n/**\n * Listing preview image (AGL-95): the publisher uploads one screenshot per\n * listing, shown on browse cards and the detail page. Storage rules deny\n * client writes, so the mutation runs here — Firebase ID token, listing\n * ownership, image-only, 2MB cap. POST replaces the image and sets\n * `previewImageUrl` on the listing; DELETE removes both.\n *\n * Lives in the plugin, like every other `marketplace/*` operation. It was the\n * one that didn't: it sat in the console app and imported this plugin's\n * `canActAsPublisher` across the `scope:app` → `aglyn:addons` boundary, which\n * failed lint on main. Registered at the same path, so the URL clients call\n * is unchanged — `/api/marketplace/preview-image` reaches the plugin catch-all\n * with exactly the shape it had as a named app route.\n *\n * The email-verified gate (AGL-479) is carried over deliberately: the plugin\n * API dispatcher does NOT apply it centrally, so dropping it here would have\n * quietly widened who can write listing images.\n */\nexport const previewImageHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST' && req.method !== 'DELETE') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const listingId = String(req.body?.listingId ?? '')\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n // The publisher agreement, agreed to on this save (AGL-4105).\n const agreementRefusal = publisherAgreementConsentRefusal(req.body?.agreement)\n if (agreementRefusal) {\n return res.status(agreementRefusal.status).json(agreementRefusal.body)\n }\n\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n let decoded: DecodedIdToken\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // Only a refused credential is the caller's 401; failing to check it is\n // ours and keeps the dispatcher's 5xx (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n try {\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return res.status(403).json({\n error: 'Verify your email to continue',\n reason: 'email-unverified',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listingSnapshot = await listingRef.get()\n if (!listingSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // Listings are org-owned since AGL-652, so `profileId` holds an ORG id.\n // Comparing it to a uid can never be true — it silently 403'd every\n // publisher rather than erroring, which is why this survived the sweep.\n const canEdit = await canActAsPublisher(\n firestore,\n decoded.uid,\n listingSnapshot.get('profileId') as string | undefined,\n )\n if (!canEdit) {\n return res.status(403).json({ error: 'Not your listing' })\n }\n // The agreement this save was sent with, recorded before the picture\n // changes (AGL-4105). One record for whichever of the three ways in this\n // is: removed, picked from the library, or uploaded.\n await recordPublisherAgreementAcceptance({\n firestore,\n request: req,\n orgId: String(listingSnapshot.get('profileId') ?? ''),\n uid: decoded.uid,\n staff: decoded['staff'] === true,\n action: req.method === 'DELETE' ? 'remove-preview-image' : 'set-preview-image',\n targetId: listingId,\n })\n const bucket = firebaseAdmin\n .app()\n .storage()\n .bucket(process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET)\n const file = bucket.file(PREVIEW_PATH(listingId))\n\n if (req.method === 'DELETE') {\n await file.delete().catch(() => undefined)\n await listingRef.set(\n {\n previewImageUrl: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n return res.status(200).json({ deleted: true })\n }\n\n // DAM-hosted asset (AGL-863): the publisher picked an existing asset from\n // the media library instead of uploading raw bytes. It already lives in\n // our storage/CDN, so point `previewImageUrl` at it directly — no re-upload\n // and no duplicate blob.\n //\n // The comment here used to claim parity with logo/screenshot, \"which\n // already accept any https URL\". That was true and was the bug (AGL-1701):\n // the preview image is the picture on the cross-org BROWSE grid and the\n // `og:image` an unauthenticated unfurler fetches, so \"any https URL\" made\n // a publisher-controlled host the recipient of every browser's IP. Parity\n // is restored in the other direction — this field now takes what the\n // sentence always implied it took, an asset that lives in our library.\n const providedUrl = String(req.body?.url ?? '').trim()\n if (providedUrl) {\n if (providedUrl.length > 2000 || !isFirstPartyMediaSrc(providedUrl)) {\n return res.status(400).json({\n error:\n 'Preview image must come from your media library — pick one with ' +\n 'Browse, or upload the file here',\n })\n }\n await listingRef.set(\n {\n previewImageUrl: providedUrl,\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n return res.status(200).json({ url: providedUrl })\n }\n\n const contentType = normalizeImageContentType(req.body?.contentType)\n const data = String(req.body?.data ?? '')\n // An ALLOWLIST rather than the `image/` prefix, for the same\n // reason as the DAM routes: the prefix tested a caller-supplied string,\n // so an unrecognised `image/…` label reached storage AND skipped the\n // signature check below, which can only compare bytes against a type it\n // knows.\n if (!isImageUploadContentType(contentType)) {\n return res\n .status(415)\n .json({ error: 'Only image uploads are supported' })\n }\n const buffer = Buffer.from(data, 'base64')\n if (!buffer.length || buffer.length > MAX_FILE_BYTES) {\n return res\n .status(413)\n .json({ error: 'File is empty or too large (2MB)' })\n }\n\n /**\n * STRUCTURAL inspection (AGL-1475). The FIFTH byte ingress, and the one\n * least like the other four.\n *\n * `contentType.startsWith('image/')` above is the entire type gate, and\n * it is taken verbatim from the request body — so `image/png` over a\n * Windows executable satisfied it. Worse than on the DAM routes: the URL\n * this mints points straight at `firebasestorage.googleapis.com` rather\n * than through `serveMediaCdn`, so the stored object never gets that\n * route's `nosniff` and per-type CSP either. And it is the picture on the\n * cross-org BROWSE grid and the `og:image` an unauthenticated unfurler\n * fetches — the same reasoning that hardened the URL branch in AGL-1701,\n * applied at last to the bytes branch.\n *\n * Structure only — not an antivirus scan. See `upload-inspection.ts`.\n */\n const refusal = inspectUploadBytes({\n bytes: buffer,\n contentType,\n fileName: String(req.body?.fileName ?? '') || null,\n })\n if (refusal) {\n return res.status(415).json({ error: refusal.message, code: refusal.code })\n }\n\n /**\n * SVG sanitization. Structural inspection cannot do this: an\n * SVG is text, it has no magic number, and a `<script>` in one is a\n * perfectly well-formed document.\n *\n * It matters more here than on the DAM routes, not less. Those serve\n * through `serveMediaCdn`, which stamps `nosniff` and a `sandbox` CSP on\n * every response and so contains a scripted SVG even if it is stored. The\n * URL this mints points straight at `firebasestorage.googleapis.com` and\n * gets neither header — and it is the `og:image` an unauthenticated\n * unfurler fetches and the picture on the cross-org browse grid, so the\n * audience is every other publisher's browser rather than the uploader's\n * own.\n */\n const bytes = isSvgUploadType(contentType)\n ? sanitizeSvgBuffer(buffer).buffer\n : buffer\n\n const token = randomUUID()\n await file.save(bytes, {\n contentType,\n metadata: { metadata: { firebaseStorageDownloadTokens: token } },\n })\n const url =\n `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/` +\n `${encodeURIComponent(PREVIEW_PATH(listingId))}` +\n `?alt=media&token=${token}`\n\n await listingRef.set(\n {\n previewImageUrl: url,\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n return res.status(200).json({ url })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Upload failed' })\n }\n}\n\nexport default previewImageHandler\n"],"names":["isFirstPartyMediaSrc","isImageUploadContentType","normalizeImageContentType","isSvgUploadType","sanitizeSvgBuffer","inspectUploadBytes","firebaseAdmin","isImpersonationSession","randomUUID","canActAsPublisher","recordPublisherAgreementAcceptance","publisherAgreementConsentRefusal","isRefusedIdToken","MAX_FILE_BYTES","PREVIEW_PATH","listingId","previewImageHandler","req","res","method","status","json","error","String","body","agreementRefusal","agreement","authorization","headers","idToken","startsWith","slice","length","undefined","decoded","app","auth","verifyIdToken","listingSnapshot","email_verified","reason","firestore","listingRef","collection","doc","get","exists","canEdit","uid","request","orgId","staff","action","targetId","bucket","storage","process","env","NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET","file","delete","catch","set","previewImageUrl","FieldValue","updatedAt","serverTimestamp","merge","deleted","providedUrl","url","trim","contentType","data","buffer","Buffer","from","refusal","bytes","fileName","message","code","token","save","metadata","firebaseStorageDownloadTokens","name","encodeURIComponent","console"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/preview-image.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { isFirstPartyMediaSrc } from '@aglyn/aglyn/app-utils/media-ref'\nimport {\n isImageUploadContentType,\n normalizeImageContentType,\n} from '@aglyn/aglyn/app-utils/image-upload-types'\nimport {\n isSvgUploadType,\n sanitizeSvgBuffer,\n} from '@aglyn/aglyn/app-utils/sanitize-svg'\nimport { inspectUploadBytes, type PluginApiHandler } from '@aglyn/aglyn/server'\nimport { firebaseAdmin, isImpersonationSession } from '@aglyn/tenant-data-admin'\nimport { randomUUID } from 'crypto'\nimport { manageListingsRefusal } from './publisher-permissions'\nimport { recordPublisherAgreementAcceptance } from './publisher-agreement-acceptance'\nimport { publisherAgreementConsentRefusal } from '../model/publisher-agreement'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport type { DecodedIdToken } from 'firebase-admin/auth'\n\n// Base64 JSON payloads: 2MB of image encodes to ~2.7MB of body.\nconst MAX_FILE_BYTES = 2 * 1024 * 1024\n\nconst PREVIEW_PATH = (listingId: string) =>\n `marketplaceListings/${listingId}/preview`\n\n/**\n * Listing preview image (AGL-95): the publisher uploads one screenshot per\n * listing, shown on browse cards and the detail page. Storage rules deny\n * client writes, so the mutation runs here — Firebase ID token, listing\n * ownership, image-only, 2MB cap. POST replaces the image and sets\n * `previewImageUrl` on the listing; DELETE removes both.\n *\n * Lives in the plugin, like every other `marketplace/*` operation. It was the\n * one that didn't: it sat in the console app and imported this plugin's\n * publisher role helper across the `scope:app` → `aglyn:addons` boundary, which\n * failed lint on main. Registered at the same path, so the URL clients call\n * is unchanged — `/api/marketplace/preview-image` reaches the plugin catch-all\n * with exactly the shape it had as a named app route.\n *\n * The email-verified gate (AGL-479) is carried over deliberately: the plugin\n * API dispatcher does NOT apply it centrally, so dropping it here would have\n * quietly widened who can write listing images.\n */\nexport const previewImageHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST' && req.method !== 'DELETE') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n const listingId = String(req.body?.listingId ?? '')\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n // The publisher agreement, agreed to on this save (AGL-4105).\n const agreementRefusal = publisherAgreementConsentRefusal(req.body?.agreement)\n if (agreementRefusal) {\n return res.status(agreementRefusal.status).json(agreementRefusal.body)\n }\n\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n let decoded: DecodedIdToken\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // Only a refused credential is the caller's 401; failing to check it is\n // ours and keeps the dispatcher's 5xx (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n try {\n if (!decoded.email_verified && !isImpersonationSession(decoded)) {\n return res.status(403).json({\n error: 'Verify your email to continue',\n reason: 'email-unverified',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listingSnapshot = await listingRef.get()\n if (!listingSnapshot.exists) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // Listings are org-owned since AGL-652, so `profileId` holds an ORG id.\n // Comparing it to a uid can never be true — it silently 403'd every\n // publisher rather than erroring, which is why this survived the sweep.\n // The picture is part of editing the listing, so it asks \"Manage\n // published listings\" (AGL-4047), as the listing's own save does; staff\n // were never let in here and still are not.\n const keyRefusal = await manageListingsRefusal(decoded, listingSnapshot.get('profileId'), {\n staffMayModerate: false,\n })\n if (keyRefusal) return res.status(keyRefusal.status).json(keyRefusal.body)\n // The agreement this save was sent with, recorded before the picture\n // changes (AGL-4105). One record for whichever of the three ways in this\n // is: removed, picked from the library, or uploaded.\n await recordPublisherAgreementAcceptance({\n firestore,\n request: req,\n orgId: String(listingSnapshot.get('profileId') ?? ''),\n uid: decoded.uid,\n staff: decoded['staff'] === true,\n action: req.method === 'DELETE' ? 'remove-preview-image' : 'set-preview-image',\n targetId: listingId,\n })\n const bucket = firebaseAdmin\n .app()\n .storage()\n .bucket(process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET)\n const file = bucket.file(PREVIEW_PATH(listingId))\n\n if (req.method === 'DELETE') {\n await file.delete().catch(() => undefined)\n await listingRef.set(\n {\n previewImageUrl: firebaseAdmin.firestore.FieldValue.delete(),\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n return res.status(200).json({ deleted: true })\n }\n\n // DAM-hosted asset (AGL-863): the publisher picked an existing asset from\n // the media library instead of uploading raw bytes. It already lives in\n // our storage/CDN, so point `previewImageUrl` at it directly — no re-upload\n // and no duplicate blob.\n //\n // The comment here used to claim parity with logo/screenshot, \"which\n // already accept any https URL\". That was true and was the bug (AGL-1701):\n // the preview image is the picture on the cross-org BROWSE grid and the\n // `og:image` an unauthenticated unfurler fetches, so \"any https URL\" made\n // a publisher-controlled host the recipient of every browser's IP. Parity\n // is restored in the other direction — this field now takes what the\n // sentence always implied it took, an asset that lives in our library.\n const providedUrl = String(req.body?.url ?? '').trim()\n if (providedUrl) {\n if (providedUrl.length > 2000 || !isFirstPartyMediaSrc(providedUrl)) {\n return res.status(400).json({\n error:\n 'Preview image must come from your media library — pick one with ' +\n 'Browse, or upload the file here',\n })\n }\n await listingRef.set(\n {\n previewImageUrl: providedUrl,\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n return res.status(200).json({ url: providedUrl })\n }\n\n const contentType = normalizeImageContentType(req.body?.contentType)\n const data = String(req.body?.data ?? '')\n // An ALLOWLIST rather than the `image/` prefix, for the same\n // reason as the DAM routes: the prefix tested a caller-supplied string,\n // so an unrecognised `image/…` label reached storage AND skipped the\n // signature check below, which can only compare bytes against a type it\n // knows.\n if (!isImageUploadContentType(contentType)) {\n return res\n .status(415)\n .json({ error: 'Only image uploads are supported' })\n }\n const buffer = Buffer.from(data, 'base64')\n if (!buffer.length || buffer.length > MAX_FILE_BYTES) {\n return res\n .status(413)\n .json({ error: 'File is empty or too large (2MB)' })\n }\n\n /**\n * STRUCTURAL inspection (AGL-1475). The FIFTH byte ingress, and the one\n * least like the other four.\n *\n * `contentType.startsWith('image/')` above is the entire type gate, and\n * it is taken verbatim from the request body — so `image/png` over a\n * Windows executable satisfied it. Worse than on the DAM routes: the URL\n * this mints points straight at `firebasestorage.googleapis.com` rather\n * than through `serveMediaCdn`, so the stored object never gets that\n * route's `nosniff` and per-type CSP either. And it is the picture on the\n * cross-org BROWSE grid and the `og:image` an unauthenticated unfurler\n * fetches — the same reasoning that hardened the URL branch in AGL-1701,\n * applied at last to the bytes branch.\n *\n * Structure only — not an antivirus scan. See `upload-inspection.ts`.\n */\n const refusal = inspectUploadBytes({\n bytes: buffer,\n contentType,\n fileName: String(req.body?.fileName ?? '') || null,\n })\n if (refusal) {\n return res.status(415).json({ error: refusal.message, code: refusal.code })\n }\n\n /**\n * SVG sanitization. Structural inspection cannot do this: an\n * SVG is text, it has no magic number, and a `<script>` in one is a\n * perfectly well-formed document.\n *\n * It matters more here than on the DAM routes, not less. Those serve\n * through `serveMediaCdn`, which stamps `nosniff` and a `sandbox` CSP on\n * every response and so contains a scripted SVG even if it is stored. The\n * URL this mints points straight at `firebasestorage.googleapis.com` and\n * gets neither header — and it is the `og:image` an unauthenticated\n * unfurler fetches and the picture on the cross-org browse grid, so the\n * audience is every other publisher's browser rather than the uploader's\n * own.\n */\n const bytes = isSvgUploadType(contentType)\n ? sanitizeSvgBuffer(buffer).buffer\n : buffer\n\n const token = randomUUID()\n await file.save(bytes, {\n contentType,\n metadata: { metadata: { firebaseStorageDownloadTokens: token } },\n })\n const url =\n `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/` +\n `${encodeURIComponent(PREVIEW_PATH(listingId))}` +\n `?alt=media&token=${token}`\n\n await listingRef.set(\n {\n previewImageUrl: url,\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n return res.status(200).json({ url })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Upload failed' })\n }\n}\n\nexport default previewImageHandler\n"],"names":["isFirstPartyMediaSrc","isImageUploadContentType","normalizeImageContentType","isSvgUploadType","sanitizeSvgBuffer","inspectUploadBytes","firebaseAdmin","isImpersonationSession","randomUUID","manageListingsRefusal","recordPublisherAgreementAcceptance","publisherAgreementConsentRefusal","isRefusedIdToken","MAX_FILE_BYTES","PREVIEW_PATH","listingId","previewImageHandler","req","res","method","status","json","error","String","body","agreementRefusal","agreement","authorization","headers","idToken","startsWith","slice","length","undefined","decoded","app","auth","verifyIdToken","listingSnapshot","email_verified","reason","firestore","listingRef","collection","doc","get","exists","keyRefusal","staffMayModerate","request","orgId","uid","staff","action","targetId","bucket","storage","process","env","NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET","file","delete","catch","set","previewImageUrl","FieldValue","updatedAt","serverTimestamp","merge","deleted","providedUrl","url","trim","contentType","data","buffer","Buffer","from","refusal","bytes","fileName","message","code","token","save","metadata","firebaseStorageDownloadTokens","name","encodeURIComponent","console"],"mappings":"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"}
|
|
@@ -19,7 +19,8 @@ import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_wi
|
|
|
19
19
|
import { checkEntitlement, checkPluginBundle, createResourceUid, MAX_PLUGIN_BUNDLE_BYTES, pluginArtifactPath, PLUGIN_VERIFIER_VERSION, validatePluginManifest } from "@aglyn/aglyn/server";
|
|
20
20
|
import { firebaseAdmin, getOrgForUser, notifyStaff } from "@aglyn/tenant-data-admin";
|
|
21
21
|
import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
|
|
22
|
-
import {
|
|
22
|
+
import { resolvePublisherProfile } from "./publisher-profile.js";
|
|
23
|
+
import { manageListingsRefusal } from "./publisher-permissions.js";
|
|
23
24
|
import { publishPreconditionRefusal } from "./publish-preconditions.js";
|
|
24
25
|
import { recordPublisherAgreementAcceptance } from "./publisher-agreement-acceptance.js";
|
|
25
26
|
import { listingSubmissionRefusal } from "./listing-screen.js";
|
|
@@ -99,12 +100,11 @@ const MAX_BUNDLE_BYTES = 5 * 1024 * 1024;
|
|
|
99
100
|
}
|
|
100
101
|
// Org-owned listings (AGL-652): a uid never equals an org id, so the
|
|
101
102
|
// old identity comparison would lock publishers out of their own listing.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
}
|
|
103
|
+
// "Manage published listings" (AGL-4047), the key the listing rules ask.
|
|
104
|
+
const keyRefusal = await manageListingsRefusal(decoded, listing.profileId, {
|
|
105
|
+
staffMayModerate: true
|
|
106
|
+
});
|
|
107
|
+
if (keyRefusal) return res.status(keyRefusal.status).json(keyRefusal.body);
|
|
108
108
|
// A site starter's category is its own `category`, one of the kinds of
|
|
109
109
|
// site or the one it has (AGL-4049); the first of `categories` is read the
|
|
110
110
|
// same way, as the native editors send it. Everything else keeps the
|
|
@@ -246,12 +246,11 @@ const MAX_BUNDLE_BYTES = 5 * 1024 * 1024;
|
|
|
246
246
|
error: 'Unknown listing'
|
|
247
247
|
});
|
|
248
248
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
}
|
|
249
|
+
// "Manage published listings" (AGL-4047), as editing the listing asks.
|
|
250
|
+
const keyRefusal = await manageListingsRefusal(decoded, listing.profileId, {
|
|
251
|
+
staffMayModerate: true
|
|
252
|
+
});
|
|
253
|
+
if (keyRefusal) return res.status(keyRefusal.status).json(keyRefusal.body);
|
|
255
254
|
if (visibility === 'public') {
|
|
256
255
|
const missing = missingPublicListingContent(listing);
|
|
257
256
|
if (missing.length) {
|
|
@@ -339,12 +338,11 @@ const MAX_BUNDLE_BYTES = 5 * 1024 * 1024;
|
|
|
339
338
|
if (!listing) return res.status(404).json({
|
|
340
339
|
error: 'Unknown listing'
|
|
341
340
|
});
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
}
|
|
341
|
+
// "Manage published listings" (AGL-4047), as editing the listing asks.
|
|
342
|
+
const keyRefusal = await manageListingsRefusal(decoded, listing.profileId, {
|
|
343
|
+
staffMayModerate: true
|
|
344
|
+
});
|
|
345
|
+
if (keyRefusal) return res.status(keyRefusal.status).json(keyRefusal.body);
|
|
348
346
|
// The agreement this save was sent with, recorded before it writes
|
|
349
347
|
// (AGL-4105).
|
|
350
348
|
await recordPublisherAgreementAcceptance({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/publish-plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { displayNameSearchFields } from '@aglyn/aglyn/app-utils/name-search'\nimport {\n checkEntitlement,\n checkPluginBundle,\n createResourceUid,\n MAX_PLUGIN_BUNDLE_BYTES,\n type PluginApiHandler,\n pluginArtifactPath,\n PLUGIN_VERIFIER_VERSION,\n validatePluginManifest,\n} from '@aglyn/aglyn/server'\nimport {\n firebaseAdmin,\n getOrgForUser,\n notifyStaff,\n} from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport {\n canActAsPublisher,\n resolvePublisherProfile,\n} from './publisher-profile'\nimport { publishPreconditionRefusal } from './publish-preconditions'\nimport { recordPublisherAgreementAcceptance } from './publisher-agreement-acceptance'\nimport { listingSubmissionRefusal } from './listing-screen'\nimport { refreshListingQueryFields } from './listing-query-fields'\nimport {\n claimPluginIdentity,\n componentNamespaceRefusal,\n pluginIdentityFor,\n} from './plugin-identity'\nimport {\n attestationLabels,\n missingAttestations,\n missingAttestationSubjects,\n PUBLISHER_ATTESTATION,\n} from '../model/publisher-attestation'\nimport { createHash } from 'crypto'\nimport {\n marketplacePriceRefusal,\n missingPublicListingContent,\n validateListingContent,\n} from '../model/marketplace'\nimport { publisherAgreementConsentRefusal } from '../model/publisher-agreement'\nimport { listingArtifactType } from '../model/listing-visibility'\nimport { siteStarterCategoryUpdate } from '../model/starter-category'\nimport { SITE_STARTER_ARTIFACT_TYPE } from '@aglyn/aglyn/app-utils/site-starter'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport type { DecodedIdToken } from 'firebase-admin/auth'\n\n// Base64 bundle bodies: a small JS plugin bundle, capped generously.\nconst MAX_BUNDLE_BYTES = 5 * 1024 * 1024\n\n/**\n * Publishes an executable plugin to the marketplace (AGL-45), per the AGL-43\n * artifact pipeline — relocated from the console app route into the\n * marketplace plugin (AGL-418); URL `/api/marketplace/publish-plugin` is\n * preserved through the dispatcher. Runs server-side so validation\n * (manifest schema, size caps) can't be bypassed: the bundle is\n * content-addressed (sha256) and written IMMUTABLY to the isolated\n * artifacts bucket, and the version entry records the hash the loader\n * verifies before executing. Env-gated on `PLUGIN_ARTIFACTS_BUCKET` —\n * 501 degrades like other platform infra. Requirements: publish\n * permission, marketplace profile, Pro plan; paid listings need completed\n * Stripe Connect onboarding.\n */\nconst updateListingContent: PluginApiHandler = async (req, res) => {\n const listingId = String(req.body?.listingId ?? '')\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n // The publisher agreement, agreed to on this save (AGL-4105).\n const agreementRefusal = publisherAgreementConsentRefusal(req.body?.agreement)\n if (agreementRefusal) {\n return res.status(agreementRefusal.status).json(agreementRefusal.body)\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n // The category is checked once the listing says what it is (AGL-4049): a\n // site starter is filed under a kind of site, every other listing under\n // the plugin taxonomy, so everything else is checked here.\n const { categories: sentCategories, category: sentCategory, ...content } =\n (req.body ?? {}) as Record<string, unknown>\n const verdict = validateListingContent(content)\n if (!verdict.ok) return res.status(400).json({ error: verdict.error })\n let decoded: DecodedIdToken\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // Only a refused credential is the caller's 401; failing to check it is\n // ours and keeps the dispatcher's 5xx (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listing = (await listingRef.get()).data()\n if (!listing || listing.deletedAt) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // Org-owned listings (AGL-652): a uid never equals an org id, so the\n // old identity comparison would lock publishers out of their own listing.\n const isPublisher = await canActAsPublisher(\n firestore,\n decoded.uid,\n listing.profileId,\n )\n if (!isPublisher && decoded['staff'] !== true) {\n return res.status(403).json({ error: 'Not your listing' })\n }\n // A site starter's category is its own `category`, one of the kinds of\n // site or the one it has (AGL-4049); the first of `categories` is read the\n // same way, as the native editors send it. Everything else keeps the\n // fixed taxonomy in `categories`.\n let categoryPatch: Record<string, unknown> = {}\n if (listingArtifactType(listing) === SITE_STARTER_ARTIFACT_TYPE) {\n const update = siteStarterCategoryUpdate(\n { category: sentCategory, categories: sentCategories },\n listing.category,\n )\n if (update.ok === false) return res.status(400).json({ error: update.error })\n if (update.category !== undefined) {\n categoryPatch = {\n category:\n update.category ?? firebaseAdmin.firestore.FieldValue.delete(),\n }\n }\n } else if (sentCategories !== undefined) {\n const categories = validateListingContent({ categories: sentCategories })\n if (!categories.ok) return res.status(400).json({ error: categories.error })\n categoryPatch = { categories: categories.content?.categories }\n }\n // Name and description are the whole first impression of a listing in\n // browse, and until AGL-793 they were the only listing-content fields with\n // no edit path — a typo in either could only be corrected by publishing a\n // fake new version, which signals \"there's an update\" to every installer.\n // Same caps the publish routes apply. An empty name is ignored rather than\n // accepted: a nameless listing is unrecognisable in the catalogue.\n const description = req.body?.description\n const displayName = req.body?.displayName\n const nextName =\n typeof displayName === 'string' ? displayName.trim().slice(0, 80) : ''\n // An edit is a submission too (AGL-3365): the listing is already public,\n // so what it is about to say is screened before it says it. The copy is\n // read as it will stand after the merge, not only the changed fields.\n const content = verdict.content ?? {}\n const screened = await listingSubmissionRefusal({\n publisherOrgId: String(listing.profileId ?? ''),\n content: {\n displayName: nextName || String(listing.displayName ?? ''),\n description:\n typeof description === 'string' ? description : String(listing.description ?? ''),\n readme: content.readme ?? (listing.readme as string | undefined) ?? null,\n urls: [\n content.homepageUrl ?? listing.homepageUrl,\n content.repositoryUrl ?? listing.repositoryUrl,\n ],\n },\n official: decoded['staff'] === true,\n })\n if (screened) return res.status(screened.status).json(screened.body)\n // The agreement this save was sent with, recorded before it writes\n // (AGL-4105).\n await recordPublisherAgreementAcceptance({\n firestore,\n request: req,\n orgId: String(listing.profileId ?? ''),\n uid: decoded.uid,\n staff: decoded['staff'] === true,\n action: 'update-listing',\n targetId: listingId,\n })\n await listingRef.set(\n {\n ...verdict.content,\n ...categoryPatch,\n ...(typeof description === 'string'\n ? { description: description.slice(0, 500) }\n : {}),\n ...(nextName\n ? { displayName: nextName, ...displayNameSearchFields(nextName) }\n : {}),\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n // A renamed listing is searched by its new name (AGL-3321).\n await refreshListingQueryFields(listingRef)\n return res.status(200).json({ ok: true })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Listing update failed' })\n }\n}\n\n/**\n * Flip a listing between private and public (AGL-968/994).\n *\n * Deliberately NOT a re-review. Per-version `reviewState` and the listing's\n * `reviewStatus` are left exactly as they are: approval is a statement about\n * specific bytes (AGL-966), and who may install them was never part of that\n * statement. Sending an already-approved bundle back through the queue\n * because its audience widened would only teach publishers to start public.\n *\n * Going public does require what a public listing needs — see\n * {@link missingPublicListingContent}. Going private is unconditional: it\n * only ever removes reach.\n */\nconst setListingVisibility: PluginApiHandler = async (req, res) => {\n const listingId = String(req.body?.listingId ?? '')\n const visibility = req.body?.visibility === 'private' ? 'private' : 'public'\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n // The publisher agreement, agreed to on this save (AGL-4105).\n const agreementRefusal = publisherAgreementConsentRefusal(req.body?.agreement)\n if (agreementRefusal) {\n return res.status(agreementRefusal.status).json(agreementRefusal.body)\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n let decoded: DecodedIdToken\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // Only a refused credential is the caller's 401; failing to check it is\n // ours and keeps the dispatcher's 5xx (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listing = (await listingRef.get()).data()\n if (!listing || listing.deletedAt) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n const isPublisher = await canActAsPublisher(\n firestore,\n decoded.uid,\n listing.profileId,\n )\n if (!isPublisher && decoded['staff'] !== true) {\n return res.status(403).json({ error: 'Not your listing' })\n }\n if (visibility === 'public') {\n const missing = missingPublicListingContent(listing)\n if (missing.length) {\n return res.status(400).json({\n error:\n 'A marketplace listing needs ' +\n missing.join(', ') +\n '. Add them in Edit, then publish it.',\n missing,\n })\n }\n }\n // The agreement this save was sent with, recorded before it writes\n // (AGL-4105).\n await recordPublisherAgreementAcceptance({\n firestore,\n request: req,\n orgId: String(listing.profileId ?? ''),\n uid: decoded.uid,\n staff: decoded['staff'] === true,\n action: 'set-visibility',\n targetId: listingId,\n })\n await listingRef.set(\n {\n visibility,\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n // Private leaves browse and public joins it, through the audience the\n // browse query reads (AGL-3321).\n await refreshListingQueryFields(listingRef)\n return res.status(200).json({ ok: true, visibility })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Visibility change failed' })\n }\n}\n\n/**\n * Unpublish or republish a listing (AGL-3321).\n *\n * This was the one client write to a listing: the seller panel set\n * `deletedAt` directly. Browse now queries a listing's AUDIENCE, which\n * follows from `deletedAt` among other fields, and a client write cannot\n * re-derive it — so the toggle moved here, beside `set-visibility`, and the\n * rules deny `deletedAt` to clients. Publisher-or-staff, like the other two\n * listing actions; unlike them it reaches a listing that is already\n * unpublished, since republishing is half of what it does.\n */\nconst setListingPublished: PluginApiHandler = async (req, res) => {\n const listingId = String(req.body?.listingId ?? '')\n const published = req.body?.published === true\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n // The publisher agreement, agreed to on this save (AGL-4105).\n const agreementRefusal = publisherAgreementConsentRefusal(req.body?.agreement)\n if (agreementRefusal) {\n return res.status(agreementRefusal.status).json(agreementRefusal.body)\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n let decoded: DecodedIdToken\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // Only a refused credential is the caller's 401; failing to check it is\n // ours and keeps the dispatcher's 5xx (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listing = (await listingRef.get()).data()\n if (!listing) return res.status(404).json({ error: 'Unknown listing' })\n const isPublisher = await canActAsPublisher(\n firestore,\n decoded.uid,\n listing.profileId,\n )\n if (!isPublisher && decoded['staff'] !== true) {\n return res.status(403).json({ error: 'Not your listing' })\n }\n // The agreement this save was sent with, recorded before it writes\n // (AGL-4105).\n await recordPublisherAgreementAcceptance({\n firestore,\n request: req,\n orgId: String(listing.profileId ?? ''),\n uid: decoded.uid,\n staff: decoded['staff'] === true,\n action: 'set-published',\n targetId: listingId,\n })\n await listingRef.set(\n {\n deletedAt: published\n ? null\n : firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n await refreshListingQueryFields(listingRef)\n return res.status(200).json({ ok: true, published })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Publishing change failed' })\n }\n}\n\nexport const publishPluginHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n // Content-only listing edits (AGL-430): publishers refresh the docs the\n // detail page renders (readme/screenshots/links/…) without shipping a\n // new bundle. Publisher-or-staff, validated exactly like publish.\n if (req.body?.action === 'update-listing') {\n return updateListingContent(req, res)\n }\n // Private ⇄ public (AGL-968/994), without shipping a new bundle.\n if (req.body?.action === 'set-visibility') {\n return setListingVisibility(req, res)\n }\n // Unpublish ⇄ republish (AGL-3321), which was a client write.\n if (req.body?.action === 'set-published') {\n return setListingPublished(req, res)\n }\n const body = req.body ?? {}\n const headers = req.headers as Partial<Record<string, string>>\n const displayName = String(body?.displayName ?? '').slice(0, 80)\n const description = String(body?.description ?? '').slice(0, 500)\n const category = String(body?.category ?? '').slice(0, 40)\n const changelog = String(body?.changelog ?? '').slice(0, 1000)\n const priceUsd = Math.round(Number(body?.priceUsd ?? 0)) || 0\n const bundleBase64 = String(body?.bundle ?? '')\n // Listing content (AGL-430): optional publisher docs for the detail page.\n const contentVerdict = validateListingContent(body ?? {})\n if (!contentVerdict.ok) {\n return res.status(400).json({ error: contentVerdict.error })\n }\n // The price floor and ceiling, from ONE validator (AGL-2343): a paid listing\n // under `MARKETPLACE_MIN_PRICE_USD` loses money on every sale, because\n // marketplace checkout is a destination charge whose Stripe fee is debited\n // from the platform's balance. Also enforced in `publishPreconditionRefusal`\n // below, so a door that drops this line is still covered.\n const priceRefusal = marketplacePriceRefusal(priceUsd)\n if (priceRefusal) {\n return res.status(400).json({ error: priceRefusal })\n }\n if (!displayName.trim() || !bundleBase64) {\n return res.status(400).json({ error: 'Missing displayName or bundle' })\n }\n\n const validation = validatePluginManifest(body?.manifest)\n if (validation.ok === false) {\n return res.status(422).json({ error: validation.error })\n }\n const manifest = validation.manifest\n\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n let decoded: DecodedIdToken\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // Only a refused credential is the caller's 401; failing to check it is\n // ours and keeps the dispatcher's 5xx (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n try {\n // Act on the org the client is in (AGL-868), not a guessed first-org — a\n // member can belong to several, and the plugin publishes under one of them.\n const requestedOrgId = String(body?.orgId ?? '').trim() || undefined\n const membership = await resolveOrgPermissions(decoded.uid, {\n orgId: requestedOrgId,\n })\n if (!membership.permissions.publishToMarketplace) {\n return res.status(403).json({\n error:\n 'Your organization role does not allow publishing to the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n\n // Plan gate rides the acting org's doc (AGL-238/868).\n const orgForUser = await getOrgForUser(\n decoded.uid,\n membership.orgId ?? requestedOrgId,\n )\n const org = orgForUser?.org ?? {}\n if (!checkEntitlement(org, 'marketplaceSelling')) {\n return res\n .status(403)\n .json({ error: 'Publishing to the marketplace requires a Pro plan' })\n }\n\n // Publish rate limit (AGL-437): a runaway or abusive publisher can't\n // flood the artifacts bucket/review queue — 20 publishes per UTC day.\n // Scoped to the publishing ORG (AGL-652): per-user it would multiply by\n // headcount, letting a big team flood the queue 20 publishes at a time.\n const dayKey = new Date().toISOString().slice(0, 10)\n if (!orgForUser?.orgId) {\n return res.status(409).json({ error: 'No publishing organization' })\n }\n const limiterRef = firestore\n .collection('publisherProfiles')\n .doc(orgForUser.orgId)\n .collection('meta')\n .doc('publishWindow')\n const allowed = await firestore.runTransaction(async (transaction) => {\n const window = (await transaction.get(limiterRef)).data() ?? {}\n const count = window.dayKey === dayKey ? Number(window.count ?? 0) : 0\n if (count >= 20) return false\n transaction.set(limiterRef, { dayKey, count: count + 1 })\n return true\n })\n if (!allowed) {\n return res\n .status(429)\n .json({ error: 'Daily publish limit reached — try again tomorrow' })\n }\n\n const publisher = await resolvePublisherProfile(firestore, orgForUser.orgId)\n // Profile, payouts AND the publisher agreement, in one gate\n // (AGL-2282) — see `publishPreconditionRefusal`.\n const refusal = publishPreconditionRefusal(publisher, {\n priceUsd,\n sells: 'plugins',\n agreement: body?.agreement,\n })\n if (refusal) return res.status(refusal.status).json(refusal.body)\n\n const bundle = Buffer.from(bundleBase64, 'base64')\n if (!bundle.length || bundle.length > MAX_BUNDLE_BYTES) {\n return res.status(413).json({ error: 'Bundle is empty or too large' })\n }\n // Static verification (AGL-426): the same checks as the local\n // `verify-plugin-bundle.mjs`, so a bundle that passes there publishes\n // here. Entry exports, self-containment, forbidden APIs, size.\n //\n // The manifest goes in too (AGL-964): the checker diffs the bundle's\n // network calls against the origins declared here, and a call to an\n // origin the manifest omits is refused at publish rather than silently\n // blocked by the CSP at runtime, where the publisher would meet it as a\n // mystery in production.\n //\n // And what register() registers is compared with the manifest's\n // `contributes` (AGL-3116), which a new version must carry whenever it\n // registers anything: the loaders place a plugin by that declaration\n // alone, so a registration it omits is a surface that never loads.\n const verification = checkPluginBundle(bundle.toString('utf8'), {\n maxBytes: MAX_PLUGIN_BUNDLE_BYTES,\n declaredNetwork: manifest.capabilities?.network ?? [],\n declaredContributions: manifest.contributes ?? null,\n requireContributions: true,\n })\n if (!verification.ok) {\n return res.status(422).json({\n error: 'Bundle failed verification',\n problems: verification.problems,\n })\n }\n const sha256 = createHash('sha256')\n .update(new Uint8Array(bundle))\n .digest('hex')\n\n // Isolated artifacts bucket (AGL-43 §3). Without it configured we\n // refuse rather than store executable code in the app bucket.\n const artifactsBucket = process.env.PLUGIN_ARTIFACTS_BUCKET\n if (!artifactsBucket) {\n return res.status(501).json({\n error:\n 'Plugin publishing is not configured (missing ' +\n 'PLUGIN_ARTIFACTS_BUCKET).',\n })\n }\n\n // One listing per publisher+plugin id: re-publishing bumps the version.\n const existing = await firestore\n .collection('marketplaceListings')\n .where('profileId', '==', publisher.orgId)\n .where('pluginId', '==', manifest.id)\n .limit(1)\n .get()\n const listingRef = existing.empty\n ? firestore.collection('marketplaceListings').doc(createResourceUid())\n : existing.docs[0].ref\n\n // The plugin's identity (AGL-3390): the namespace its elements are saved\n // under on every site that places one. A listing keeps the identity it\n // was first given, so renaming the publisher handle cannot orphan them.\n const frozenIdentity = existing.empty\n ? undefined\n : (existing.docs[0].get('identity') as string | undefined)\n const identity =\n frozenIdentity || pluginIdentityFor(publisher.handle, manifest.id)\n const namespaceRefusal = componentNamespaceRefusal(\n identity,\n manifest.contributes,\n )\n if (namespaceRefusal) {\n return res.status(422).json({ error: namespaceRefusal })\n }\n\n // Pre-submission attestation (AGL-969). Blocks the publish rather than\n // warning, because the whole point is that these answers exist BEFORE a\n // reviewer spends time on the bundle.\n //\n // Whether this is an update is decided here, from whether a listing\n // already exists — never from a client flag, which would make \"this is\n // my first version\" a way to skip the changelog item.\n const ticked = (\n Array.isArray(body?.attestation)\n ? body.attestation.map((id: unknown) => String(id))\n : []\n ).filter((id: string) =>\n PUBLISHER_ATTESTATION.some((item) => item.id === id),\n )\n const isUpdate = !existing.empty\n const missing = missingAttestations(ticked, isUpdate)\n if (missing.length) {\n return res.status(428).json({\n error:\n 'Confirm the pre-submission checklist before publishing: ' +\n attestationLabels(missing).join('; '),\n missingAttestations: missing,\n })\n }\n\n // An attestation needs something to be about (AGL-1076). `repository`\n // was confirmable in a form with no repository field, so listings\n // reached review carrying a signed claim that a URL we never collected\n // was public and matched — and the reviewer's first link went nowhere.\n //\n // Checked against the SUBMISSION, not the listing doc: a first publish\n // has no listing, and on an update the tick is a statement about what\n // this publish declares. `contentVerdict.content` is the normalized\n // value about to be stored, so the gate and the write cannot disagree.\n const missingSubjects = missingAttestationSubjects(\n contentVerdict.content as Record<string, unknown>,\n isUpdate,\n )\n if (missingSubjects.length) {\n return res.status(428).json({\n error:\n 'These are confirmed above but not filled in: ' +\n missingSubjects.map((subject) => subject.label).join('; '),\n missingAttestationSubjects: missingSubjects.map(\n (subject) => subject.field,\n ),\n })\n }\n\n // The phishing screen, before anything is stored or queued (AGL-3365).\n const screened = await listingSubmissionRefusal({\n publisherOrgId: publisher.orgId,\n content: {\n displayName,\n publisherName: publisher.displayName,\n description,\n changelog,\n readme: contentVerdict.content?.readme ?? null,\n urls: [contentVerdict.content?.homepageUrl, contentVerdict.content?.repositoryUrl],\n },\n official: decoded['staff'] === true,\n org,\n })\n if (screened) return res.status(screened.status).json(screened.body)\n\n // The agreement this version was sent with, recorded before it is stored\n // or listed (AGL-4105).\n await recordPublisherAgreementAcceptance({\n firestore,\n request: req,\n orgId: publisher.orgId,\n uid: decoded.uid,\n staff: decoded['staff'] === true,\n action: 'publish-plugin',\n targetId: listingRef.id,\n })\n\n // Claimed only once everything a publisher can fix has passed, so a\n // refused submission never takes an identity it then cannot use.\n const claimed = await claimPluginIdentity(firestore, {\n identity,\n listingId: listingRef.id,\n profileId: publisher.orgId,\n })\n if (!claimed) {\n return res.status(409).json({\n error: `The plugin identity \"${identity}\" belongs to another listing`,\n })\n }\n\n // Immutable content-addressed write — a new build is a new object, so a\n // consumer's pinned version can never be overwritten underneath it.\n const objectPath = pluginArtifactPath(\n listingRef.id,\n manifest.version,\n sha256,\n )\n const bucket = firebaseAdmin.app().storage().bucket(artifactsBucket)\n const file = bucket.file(objectPath)\n const [alreadyStored] = await file.exists()\n if (!alreadyStored) {\n await file.save(bundle, {\n contentType: 'application/javascript',\n metadata: {\n cacheControl: 'public, max-age=31536000, immutable',\n },\n })\n }\n\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n await listingRef.set(\n {\n type: 'plugin',\n artifactType: 'plugin',\n profileId: publisher.orgId,\n pluginId: manifest.id,\n identity,\n displayName: displayName.trim(),\n // The keys a listing is searched by (AGL-3321): the template gallery's\n // marketplace shelf asks `nameTokens` on its query.\n ...displayNameSearchFields(displayName.trim()),\n ...(description.trim() && { description: description.trim() }),\n ...(category.trim() && { category: category.trim() }),\n priceUsd,\n ...contentVerdict.content,\n latestVersion: manifest.version,\n // Mirror of `pluginVersions/{latestVersion}.reviewState`, for the\n // marketplace (AGL-1121). Browse reads listings directly and cannot\n // afford a version subdoc read per card, so the newest bytes' verdict\n // has to live here to be shown at all.\n //\n // Reset to 'pending' on EVERY publish, including a bump on a listing\n // that staff already verified. That is the whole point: the listing's\n // `reviewStatus` deliberately survives a version bump, so before this\n // the marketplace badged brand-new, unreviewed bytes as reviewed.\n latestVersionReviewState: 'pending',\n deletedAt: null,\n // Review queue (AGL-432): first publish enters as 'submitted';\n // version bumps keep whatever status staff granted — which is safe\n // now that installs resolve the newest APPROVED version rather than\n // `latestVersion` (AGL-966). A listed plugin stays listed on its\n // reviewed bytes while the new version waits in the queue.\n ...(existing.empty && { createdAt: now, reviewStatus: 'submitted' }),\n // Private plugins never reach the marketplace but take the same\n // review path (AGL-968). Only settable on first publish; changing\n // visibility later is a separate, deliberate action.\n ...(existing.empty && {\n visibility: req.body?.visibility === 'private' ? 'private' : 'public',\n }),\n updatedAt: now,\n },\n { merge: true },\n )\n // The lists this listing appears in query by fields derived from it (AGL-3321).\n await refreshListingQueryFields(listingRef)\n // Version snapshots are server-only; the loader reads sha256 + manifest\n // to verify integrity and stamp CSP before executing.\n await listingRef\n .collection('pluginVersions')\n .doc(manifest.version)\n .set({\n version: manifest.version,\n sha256,\n objectPath,\n manifest,\n // What the loaders hold these bytes to (AGL-3390): every component\n // they register is `<identity>.<role>` and names this as its plugin.\n identity,\n ...(changelog.trim() && { changelog: changelog.trim() }),\n // The repository as declared FOR THESE BYTES (AGL-1076). The listing\n // carries whatever the latest publish said, and a publisher may move\n // or rename a repo between versions — a reviewer looking at v1.0.2\n // needs the link that was attested against v1.0.2's sha256, which is\n // the same reasoning that pins the attestation itself.\n ...(contentVerdict.content?.repositoryUrl && {\n repositoryUrl: contentVerdict.content.repositoryUrl,\n }),\n publishedAt: now,\n // What the publisher stated about these bytes (AGL-969). Same shape\n // as the staff `reviewChecklist` and pinned to the same sha256, so\n // a republish under this version string re-asks rather than\n // inheriting an attestation made about code that has since changed.\n //\n // `by` is the uid that submitted, not the org: takedown and removal\n // are defensible because a named person said this on a date, and an\n // org id names nobody.\n publisherAttestation: Object.fromEntries(\n ticked.map((id: string) => [id, { by: decoded.uid, at: now, sha256 }]),\n ),\n // Every version starts UNREVIEWED (AGL-966). This doc is written\n // with .set() and no merge, so a republish of the same version\n // string also resets it — new bytes, new review, by construction.\n reviewState: 'pending',\n // Keep the verdict we just computed (AGL-962). The bundle is\n // immutable and content-addressed, so this result holds for as long\n // as the checker does — the review page reads it instead of\n // re-downloading a megabyte per view. `verifierVersion` is what\n // makes that safe: bump PLUGIN_VERIFIER_VERSION and every stored\n // verdict is recomputed on next read.\n verification: {\n ok: verification.ok,\n problems: verification.problems,\n sha256,\n verifierVersion: PLUGIN_VERIFIER_VERSION,\n checkedAt: now,\n },\n })\n\n // Tell staff a version is waiting (AGL-970). Nothing announced a\n // submission before this: reviewers had to notice a new row by\n // visiting the queue, and an UPDATE to an already-listed plugin\n // produced no row at all. Persistent per-user notifications, so it\n // survives a refresh and stays until someone acts on it.\n // The body names the plugin itself (AGL-3432): in the daily digest it\n // sits under a one-line title.\n await notifyStaff({\n type: 'marketplace.review',\n title: `${displayName.trim()} v${manifest.version} needs review`,\n body: existing.empty\n ? `${displayName.trim()} v${manifest.version} was submitted to the ` +\n 'marketplace and is waiting for review.'\n : `${displayName.trim()} v${manifest.version} was published and is ` +\n 'waiting for review. The previously approved version keeps ' +\n 'installing until this one is reviewed.',\n link: `/admin/plugin-reviews/${listingRef.id}`,\n }).catch(() => undefined)\n\n return res\n .status(200)\n .json({ listingId: listingRef.id, version: manifest.version, sha256 })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Publish failed' })\n }\n}\n"],"names":["displayNameSearchFields","checkEntitlement","checkPluginBundle","createResourceUid","MAX_PLUGIN_BUNDLE_BYTES","pluginArtifactPath","PLUGIN_VERIFIER_VERSION","validatePluginManifest","firebaseAdmin","getOrgForUser","notifyStaff","resolveOrgPermissions","canActAsPublisher","resolvePublisherProfile","publishPreconditionRefusal","recordPublisherAgreementAcceptance","listingSubmissionRefusal","refreshListingQueryFields","claimPluginIdentity","componentNamespaceRefusal","pluginIdentityFor","attestationLabels","missingAttestations","missingAttestationSubjects","PUBLISHER_ATTESTATION","createHash","marketplacePriceRefusal","missingPublicListingContent","validateListingContent","publisherAgreementConsentRefusal","listingArtifactType","siteStarterCategoryUpdate","SITE_STARTER_ARTIFACT_TYPE","isRefusedIdToken","MAX_BUNDLE_BYTES","updateListingContent","req","res","listingId","String","body","status","json","error","agreementRefusal","agreement","authorization","headers","idToken","startsWith","slice","length","undefined","categories","sentCategories","category","sentCategory","content","verdict","ok","decoded","app","auth","verifyIdToken","listing","firestore","listingRef","collection","doc","get","data","deletedAt","isPublisher","uid","profileId","categoryPatch","update","FieldValue","delete","description","displayName","nextName","trim","screened","publisherOrgId","readme","urls","homepageUrl","repositoryUrl","official","request","orgId","staff","action","targetId","set","updatedAt","serverTimestamp","merge","console","setListingVisibility","visibility","missing","join","setListingPublished","published","publishPluginHandler","method","changelog","priceUsd","Math","round","Number","bundleBase64","bundle","contentVerdict","priceRefusal","validation","manifest","membership","requestedOrgId","permissions","publishToMarketplace","orgForUser","org","dayKey","Date","toISOString","limiterRef","allowed","runTransaction","transaction","window","count","publisher","refusal","sells","Buffer","from","verification","toString","maxBytes","declaredNetwork","capabilities","network","declaredContributions","contributes","requireContributions","problems","sha256","Uint8Array","digest","artifactsBucket","process","env","PLUGIN_ARTIFACTS_BUCKET","existing","where","id","limit","empty","docs","ref","frozenIdentity","identity","handle","namespaceRefusal","ticked","Array","isArray","attestation","map","filter","some","item","isUpdate","missingSubjects","subject","label","field","publisherName","claimed","objectPath","version","bucket","storage","file","alreadyStored","exists","save","contentType","metadata","cacheControl","now","type","artifactType","pluginId","latestVersion","latestVersionReviewState","createdAt","reviewStatus","publishedAt","publisherAttestation","Object","fromEntries","by","at","reviewState","verifierVersion","checkedAt","title","link","catch"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketplace/src/lib/server/publish-plugin.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { displayNameSearchFields } from '@aglyn/aglyn/app-utils/name-search'\nimport {\n checkEntitlement,\n checkPluginBundle,\n createResourceUid,\n MAX_PLUGIN_BUNDLE_BYTES,\n type PluginApiHandler,\n pluginArtifactPath,\n PLUGIN_VERIFIER_VERSION,\n validatePluginManifest,\n} from '@aglyn/aglyn/server'\nimport {\n firebaseAdmin,\n getOrgForUser,\n notifyStaff,\n} from '@aglyn/tenant-data-admin'\nimport { resolveOrgPermissions } from '@aglyn/tenant-runtime/org-permissions'\nimport { resolvePublisherProfile } from './publisher-profile'\nimport { manageListingsRefusal } from './publisher-permissions'\nimport { publishPreconditionRefusal } from './publish-preconditions'\nimport { recordPublisherAgreementAcceptance } from './publisher-agreement-acceptance'\nimport { listingSubmissionRefusal } from './listing-screen'\nimport { refreshListingQueryFields } from './listing-query-fields'\nimport {\n claimPluginIdentity,\n componentNamespaceRefusal,\n pluginIdentityFor,\n} from './plugin-identity'\nimport {\n attestationLabels,\n missingAttestations,\n missingAttestationSubjects,\n PUBLISHER_ATTESTATION,\n} from '../model/publisher-attestation'\nimport { createHash } from 'crypto'\nimport {\n marketplacePriceRefusal,\n missingPublicListingContent,\n validateListingContent,\n} from '../model/marketplace'\nimport { publisherAgreementConsentRefusal } from '../model/publisher-agreement'\nimport { listingArtifactType } from '../model/listing-visibility'\nimport { siteStarterCategoryUpdate } from '../model/starter-category'\nimport { SITE_STARTER_ARTIFACT_TYPE } from '@aglyn/aglyn/app-utils/site-starter'\nimport { isRefusedIdToken } from '@aglyn/tenant-data-admin/server/id-token-refusal'\nimport type { DecodedIdToken } from 'firebase-admin/auth'\n\n// Base64 bundle bodies: a small JS plugin bundle, capped generously.\nconst MAX_BUNDLE_BYTES = 5 * 1024 * 1024\n\n/**\n * Publishes an executable plugin to the marketplace (AGL-45), per the AGL-43\n * artifact pipeline — relocated from the console app route into the\n * marketplace plugin (AGL-418); URL `/api/marketplace/publish-plugin` is\n * preserved through the dispatcher. Runs server-side so validation\n * (manifest schema, size caps) can't be bypassed: the bundle is\n * content-addressed (sha256) and written IMMUTABLY to the isolated\n * artifacts bucket, and the version entry records the hash the loader\n * verifies before executing. Env-gated on `PLUGIN_ARTIFACTS_BUCKET` —\n * 501 degrades like other platform infra. Requirements: publish\n * permission, marketplace profile, Pro plan; paid listings need completed\n * Stripe Connect onboarding.\n */\nconst updateListingContent: PluginApiHandler = async (req, res) => {\n const listingId = String(req.body?.listingId ?? '')\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n // The publisher agreement, agreed to on this save (AGL-4105).\n const agreementRefusal = publisherAgreementConsentRefusal(req.body?.agreement)\n if (agreementRefusal) {\n return res.status(agreementRefusal.status).json(agreementRefusal.body)\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n // The category is checked once the listing says what it is (AGL-4049): a\n // site starter is filed under a kind of site, every other listing under\n // the plugin taxonomy, so everything else is checked here.\n const { categories: sentCategories, category: sentCategory, ...content } =\n (req.body ?? {}) as Record<string, unknown>\n const verdict = validateListingContent(content)\n if (!verdict.ok) return res.status(400).json({ error: verdict.error })\n let decoded: DecodedIdToken\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // Only a refused credential is the caller's 401; failing to check it is\n // ours and keeps the dispatcher's 5xx (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listing = (await listingRef.get()).data()\n if (!listing || listing.deletedAt) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // Org-owned listings (AGL-652): a uid never equals an org id, so the\n // old identity comparison would lock publishers out of their own listing.\n // \"Manage published listings\" (AGL-4047), the key the listing rules ask.\n const keyRefusal = await manageListingsRefusal(decoded, listing.profileId, {\n staffMayModerate: true,\n })\n if (keyRefusal) return res.status(keyRefusal.status).json(keyRefusal.body)\n // A site starter's category is its own `category`, one of the kinds of\n // site or the one it has (AGL-4049); the first of `categories` is read the\n // same way, as the native editors send it. Everything else keeps the\n // fixed taxonomy in `categories`.\n let categoryPatch: Record<string, unknown> = {}\n if (listingArtifactType(listing) === SITE_STARTER_ARTIFACT_TYPE) {\n const update = siteStarterCategoryUpdate(\n { category: sentCategory, categories: sentCategories },\n listing.category,\n )\n if (update.ok === false) return res.status(400).json({ error: update.error })\n if (update.category !== undefined) {\n categoryPatch = {\n category:\n update.category ?? firebaseAdmin.firestore.FieldValue.delete(),\n }\n }\n } else if (sentCategories !== undefined) {\n const categories = validateListingContent({ categories: sentCategories })\n if (!categories.ok) return res.status(400).json({ error: categories.error })\n categoryPatch = { categories: categories.content?.categories }\n }\n // Name and description are the whole first impression of a listing in\n // browse, and until AGL-793 they were the only listing-content fields with\n // no edit path — a typo in either could only be corrected by publishing a\n // fake new version, which signals \"there's an update\" to every installer.\n // Same caps the publish routes apply. An empty name is ignored rather than\n // accepted: a nameless listing is unrecognisable in the catalogue.\n const description = req.body?.description\n const displayName = req.body?.displayName\n const nextName =\n typeof displayName === 'string' ? displayName.trim().slice(0, 80) : ''\n // An edit is a submission too (AGL-3365): the listing is already public,\n // so what it is about to say is screened before it says it. The copy is\n // read as it will stand after the merge, not only the changed fields.\n const content = verdict.content ?? {}\n const screened = await listingSubmissionRefusal({\n publisherOrgId: String(listing.profileId ?? ''),\n content: {\n displayName: nextName || String(listing.displayName ?? ''),\n description:\n typeof description === 'string' ? description : String(listing.description ?? ''),\n readme: content.readme ?? (listing.readme as string | undefined) ?? null,\n urls: [\n content.homepageUrl ?? listing.homepageUrl,\n content.repositoryUrl ?? listing.repositoryUrl,\n ],\n },\n official: decoded['staff'] === true,\n })\n if (screened) return res.status(screened.status).json(screened.body)\n // The agreement this save was sent with, recorded before it writes\n // (AGL-4105).\n await recordPublisherAgreementAcceptance({\n firestore,\n request: req,\n orgId: String(listing.profileId ?? ''),\n uid: decoded.uid,\n staff: decoded['staff'] === true,\n action: 'update-listing',\n targetId: listingId,\n })\n await listingRef.set(\n {\n ...verdict.content,\n ...categoryPatch,\n ...(typeof description === 'string'\n ? { description: description.slice(0, 500) }\n : {}),\n ...(nextName\n ? { displayName: nextName, ...displayNameSearchFields(nextName) }\n : {}),\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n // A renamed listing is searched by its new name (AGL-3321).\n await refreshListingQueryFields(listingRef)\n return res.status(200).json({ ok: true })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Listing update failed' })\n }\n}\n\n/**\n * Flip a listing between private and public (AGL-968/994).\n *\n * Deliberately NOT a re-review. Per-version `reviewState` and the listing's\n * `reviewStatus` are left exactly as they are: approval is a statement about\n * specific bytes (AGL-966), and who may install them was never part of that\n * statement. Sending an already-approved bundle back through the queue\n * because its audience widened would only teach publishers to start public.\n *\n * Going public does require what a public listing needs — see\n * {@link missingPublicListingContent}. Going private is unconditional: it\n * only ever removes reach.\n */\nconst setListingVisibility: PluginApiHandler = async (req, res) => {\n const listingId = String(req.body?.listingId ?? '')\n const visibility = req.body?.visibility === 'private' ? 'private' : 'public'\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n // The publisher agreement, agreed to on this save (AGL-4105).\n const agreementRefusal = publisherAgreementConsentRefusal(req.body?.agreement)\n if (agreementRefusal) {\n return res.status(agreementRefusal.status).json(agreementRefusal.body)\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n let decoded: DecodedIdToken\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // Only a refused credential is the caller's 401; failing to check it is\n // ours and keeps the dispatcher's 5xx (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listing = (await listingRef.get()).data()\n if (!listing || listing.deletedAt) {\n return res.status(404).json({ error: 'Unknown listing' })\n }\n // \"Manage published listings\" (AGL-4047), as editing the listing asks.\n const keyRefusal = await manageListingsRefusal(decoded, listing.profileId, {\n staffMayModerate: true,\n })\n if (keyRefusal) return res.status(keyRefusal.status).json(keyRefusal.body)\n if (visibility === 'public') {\n const missing = missingPublicListingContent(listing)\n if (missing.length) {\n return res.status(400).json({\n error:\n 'A marketplace listing needs ' +\n missing.join(', ') +\n '. Add them in Edit, then publish it.',\n missing,\n })\n }\n }\n // The agreement this save was sent with, recorded before it writes\n // (AGL-4105).\n await recordPublisherAgreementAcceptance({\n firestore,\n request: req,\n orgId: String(listing.profileId ?? ''),\n uid: decoded.uid,\n staff: decoded['staff'] === true,\n action: 'set-visibility',\n targetId: listingId,\n })\n await listingRef.set(\n {\n visibility,\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n // Private leaves browse and public joins it, through the audience the\n // browse query reads (AGL-3321).\n await refreshListingQueryFields(listingRef)\n return res.status(200).json({ ok: true, visibility })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Visibility change failed' })\n }\n}\n\n/**\n * Unpublish or republish a listing (AGL-3321).\n *\n * This was the one client write to a listing: the seller panel set\n * `deletedAt` directly. Browse now queries a listing's AUDIENCE, which\n * follows from `deletedAt` among other fields, and a client write cannot\n * re-derive it — so the toggle moved here, beside `set-visibility`, and the\n * rules deny `deletedAt` to clients. Publisher-or-staff, like the other two\n * listing actions; unlike them it reaches a listing that is already\n * unpublished, since republishing is half of what it does.\n */\nconst setListingPublished: PluginApiHandler = async (req, res) => {\n const listingId = String(req.body?.listingId ?? '')\n const published = req.body?.published === true\n if (!listingId) return res.status(400).json({ error: 'Missing listingId' })\n // The publisher agreement, agreed to on this save (AGL-4105).\n const agreementRefusal = publisherAgreementConsentRefusal(req.body?.agreement)\n if (agreementRefusal) {\n return res.status(agreementRefusal.status).json(agreementRefusal.body)\n }\n const authorization = String(req.headers.authorization ?? '')\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n let decoded: DecodedIdToken\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // Only a refused credential is the caller's 401; failing to check it is\n // ours and keeps the dispatcher's 5xx (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n try {\n const firestore = firebaseAdmin.app().firestore()\n const listingRef = firestore.collection('marketplaceListings').doc(listingId)\n const listing = (await listingRef.get()).data()\n if (!listing) return res.status(404).json({ error: 'Unknown listing' })\n // \"Manage published listings\" (AGL-4047), as editing the listing asks.\n const keyRefusal = await manageListingsRefusal(decoded, listing.profileId, {\n staffMayModerate: true,\n })\n if (keyRefusal) return res.status(keyRefusal.status).json(keyRefusal.body)\n // The agreement this save was sent with, recorded before it writes\n // (AGL-4105).\n await recordPublisherAgreementAcceptance({\n firestore,\n request: req,\n orgId: String(listing.profileId ?? ''),\n uid: decoded.uid,\n staff: decoded['staff'] === true,\n action: 'set-published',\n targetId: listingId,\n })\n await listingRef.set(\n {\n deletedAt: published\n ? null\n : firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\n await refreshListingQueryFields(listingRef)\n return res.status(200).json({ ok: true, published })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Publishing change failed' })\n }\n}\n\nexport const publishPluginHandler: PluginApiHandler = async (req, res) => {\n if (req.method !== 'POST') {\n return res.status(405).json({ error: 'Method not allowed' })\n }\n // Content-only listing edits (AGL-430): publishers refresh the docs the\n // detail page renders (readme/screenshots/links/…) without shipping a\n // new bundle. Publisher-or-staff, validated exactly like publish.\n if (req.body?.action === 'update-listing') {\n return updateListingContent(req, res)\n }\n // Private ⇄ public (AGL-968/994), without shipping a new bundle.\n if (req.body?.action === 'set-visibility') {\n return setListingVisibility(req, res)\n }\n // Unpublish ⇄ republish (AGL-3321), which was a client write.\n if (req.body?.action === 'set-published') {\n return setListingPublished(req, res)\n }\n const body = req.body ?? {}\n const headers = req.headers as Partial<Record<string, string>>\n const displayName = String(body?.displayName ?? '').slice(0, 80)\n const description = String(body?.description ?? '').slice(0, 500)\n const category = String(body?.category ?? '').slice(0, 40)\n const changelog = String(body?.changelog ?? '').slice(0, 1000)\n const priceUsd = Math.round(Number(body?.priceUsd ?? 0)) || 0\n const bundleBase64 = String(body?.bundle ?? '')\n // Listing content (AGL-430): optional publisher docs for the detail page.\n const contentVerdict = validateListingContent(body ?? {})\n if (!contentVerdict.ok) {\n return res.status(400).json({ error: contentVerdict.error })\n }\n // The price floor and ceiling, from ONE validator (AGL-2343): a paid listing\n // under `MARKETPLACE_MIN_PRICE_USD` loses money on every sale, because\n // marketplace checkout is a destination charge whose Stripe fee is debited\n // from the platform's balance. Also enforced in `publishPreconditionRefusal`\n // below, so a door that drops this line is still covered.\n const priceRefusal = marketplacePriceRefusal(priceUsd)\n if (priceRefusal) {\n return res.status(400).json({ error: priceRefusal })\n }\n if (!displayName.trim() || !bundleBase64) {\n return res.status(400).json({ error: 'Missing displayName or bundle' })\n }\n\n const validation = validatePluginManifest(body?.manifest)\n if (validation.ok === false) {\n return res.status(422).json({ error: validation.error })\n }\n const manifest = validation.manifest\n\n const authorization = headers.authorization ?? ''\n const idToken = authorization.startsWith('Bearer ')\n ? authorization.slice('Bearer '.length)\n : undefined\n if (!idToken) return res.status(401).json({ error: 'Unauthenticated' })\n\n let decoded: DecodedIdToken\n try {\n decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n } catch (error) {\n // Only a refused credential is the caller's 401; failing to check it is\n // ours and keeps the dispatcher's 5xx (AGL-2852).\n if (!isRefusedIdToken(error)) throw error\n return res.status(401).json({ error: 'Unauthenticated' })\n }\n try {\n // Act on the org the client is in (AGL-868), not a guessed first-org — a\n // member can belong to several, and the plugin publishes under one of them.\n const requestedOrgId = String(body?.orgId ?? '').trim() || undefined\n const membership = await resolveOrgPermissions(decoded.uid, {\n orgId: requestedOrgId,\n })\n if (!membership.permissions.publishToMarketplace) {\n return res.status(403).json({\n error:\n 'Your organization role does not allow publishing to the marketplace',\n })\n }\n const firestore = firebaseAdmin.app().firestore()\n\n // Plan gate rides the acting org's doc (AGL-238/868).\n const orgForUser = await getOrgForUser(\n decoded.uid,\n membership.orgId ?? requestedOrgId,\n )\n const org = orgForUser?.org ?? {}\n if (!checkEntitlement(org, 'marketplaceSelling')) {\n return res\n .status(403)\n .json({ error: 'Publishing to the marketplace requires a Pro plan' })\n }\n\n // Publish rate limit (AGL-437): a runaway or abusive publisher can't\n // flood the artifacts bucket/review queue — 20 publishes per UTC day.\n // Scoped to the publishing ORG (AGL-652): per-user it would multiply by\n // headcount, letting a big team flood the queue 20 publishes at a time.\n const dayKey = new Date().toISOString().slice(0, 10)\n if (!orgForUser?.orgId) {\n return res.status(409).json({ error: 'No publishing organization' })\n }\n const limiterRef = firestore\n .collection('publisherProfiles')\n .doc(orgForUser.orgId)\n .collection('meta')\n .doc('publishWindow')\n const allowed = await firestore.runTransaction(async (transaction) => {\n const window = (await transaction.get(limiterRef)).data() ?? {}\n const count = window.dayKey === dayKey ? Number(window.count ?? 0) : 0\n if (count >= 20) return false\n transaction.set(limiterRef, { dayKey, count: count + 1 })\n return true\n })\n if (!allowed) {\n return res\n .status(429)\n .json({ error: 'Daily publish limit reached — try again tomorrow' })\n }\n\n const publisher = await resolvePublisherProfile(firestore, orgForUser.orgId)\n // Profile, payouts AND the publisher agreement, in one gate\n // (AGL-2282) — see `publishPreconditionRefusal`.\n const refusal = publishPreconditionRefusal(publisher, {\n priceUsd,\n sells: 'plugins',\n agreement: body?.agreement,\n })\n if (refusal) return res.status(refusal.status).json(refusal.body)\n\n const bundle = Buffer.from(bundleBase64, 'base64')\n if (!bundle.length || bundle.length > MAX_BUNDLE_BYTES) {\n return res.status(413).json({ error: 'Bundle is empty or too large' })\n }\n // Static verification (AGL-426): the same checks as the local\n // `verify-plugin-bundle.mjs`, so a bundle that passes there publishes\n // here. Entry exports, self-containment, forbidden APIs, size.\n //\n // The manifest goes in too (AGL-964): the checker diffs the bundle's\n // network calls against the origins declared here, and a call to an\n // origin the manifest omits is refused at publish rather than silently\n // blocked by the CSP at runtime, where the publisher would meet it as a\n // mystery in production.\n //\n // And what register() registers is compared with the manifest's\n // `contributes` (AGL-3116), which a new version must carry whenever it\n // registers anything: the loaders place a plugin by that declaration\n // alone, so a registration it omits is a surface that never loads.\n const verification = checkPluginBundle(bundle.toString('utf8'), {\n maxBytes: MAX_PLUGIN_BUNDLE_BYTES,\n declaredNetwork: manifest.capabilities?.network ?? [],\n declaredContributions: manifest.contributes ?? null,\n requireContributions: true,\n })\n if (!verification.ok) {\n return res.status(422).json({\n error: 'Bundle failed verification',\n problems: verification.problems,\n })\n }\n const sha256 = createHash('sha256')\n .update(new Uint8Array(bundle))\n .digest('hex')\n\n // Isolated artifacts bucket (AGL-43 §3). Without it configured we\n // refuse rather than store executable code in the app bucket.\n const artifactsBucket = process.env.PLUGIN_ARTIFACTS_BUCKET\n if (!artifactsBucket) {\n return res.status(501).json({\n error:\n 'Plugin publishing is not configured (missing ' +\n 'PLUGIN_ARTIFACTS_BUCKET).',\n })\n }\n\n // One listing per publisher+plugin id: re-publishing bumps the version.\n const existing = await firestore\n .collection('marketplaceListings')\n .where('profileId', '==', publisher.orgId)\n .where('pluginId', '==', manifest.id)\n .limit(1)\n .get()\n const listingRef = existing.empty\n ? firestore.collection('marketplaceListings').doc(createResourceUid())\n : existing.docs[0].ref\n\n // The plugin's identity (AGL-3390): the namespace its elements are saved\n // under on every site that places one. A listing keeps the identity it\n // was first given, so renaming the publisher handle cannot orphan them.\n const frozenIdentity = existing.empty\n ? undefined\n : (existing.docs[0].get('identity') as string | undefined)\n const identity =\n frozenIdentity || pluginIdentityFor(publisher.handle, manifest.id)\n const namespaceRefusal = componentNamespaceRefusal(\n identity,\n manifest.contributes,\n )\n if (namespaceRefusal) {\n return res.status(422).json({ error: namespaceRefusal })\n }\n\n // Pre-submission attestation (AGL-969). Blocks the publish rather than\n // warning, because the whole point is that these answers exist BEFORE a\n // reviewer spends time on the bundle.\n //\n // Whether this is an update is decided here, from whether a listing\n // already exists — never from a client flag, which would make \"this is\n // my first version\" a way to skip the changelog item.\n const ticked = (\n Array.isArray(body?.attestation)\n ? body.attestation.map((id: unknown) => String(id))\n : []\n ).filter((id: string) =>\n PUBLISHER_ATTESTATION.some((item) => item.id === id),\n )\n const isUpdate = !existing.empty\n const missing = missingAttestations(ticked, isUpdate)\n if (missing.length) {\n return res.status(428).json({\n error:\n 'Confirm the pre-submission checklist before publishing: ' +\n attestationLabels(missing).join('; '),\n missingAttestations: missing,\n })\n }\n\n // An attestation needs something to be about (AGL-1076). `repository`\n // was confirmable in a form with no repository field, so listings\n // reached review carrying a signed claim that a URL we never collected\n // was public and matched — and the reviewer's first link went nowhere.\n //\n // Checked against the SUBMISSION, not the listing doc: a first publish\n // has no listing, and on an update the tick is a statement about what\n // this publish declares. `contentVerdict.content` is the normalized\n // value about to be stored, so the gate and the write cannot disagree.\n const missingSubjects = missingAttestationSubjects(\n contentVerdict.content as Record<string, unknown>,\n isUpdate,\n )\n if (missingSubjects.length) {\n return res.status(428).json({\n error:\n 'These are confirmed above but not filled in: ' +\n missingSubjects.map((subject) => subject.label).join('; '),\n missingAttestationSubjects: missingSubjects.map(\n (subject) => subject.field,\n ),\n })\n }\n\n // The phishing screen, before anything is stored or queued (AGL-3365).\n const screened = await listingSubmissionRefusal({\n publisherOrgId: publisher.orgId,\n content: {\n displayName,\n publisherName: publisher.displayName,\n description,\n changelog,\n readme: contentVerdict.content?.readme ?? null,\n urls: [contentVerdict.content?.homepageUrl, contentVerdict.content?.repositoryUrl],\n },\n official: decoded['staff'] === true,\n org,\n })\n if (screened) return res.status(screened.status).json(screened.body)\n\n // The agreement this version was sent with, recorded before it is stored\n // or listed (AGL-4105).\n await recordPublisherAgreementAcceptance({\n firestore,\n request: req,\n orgId: publisher.orgId,\n uid: decoded.uid,\n staff: decoded['staff'] === true,\n action: 'publish-plugin',\n targetId: listingRef.id,\n })\n\n // Claimed only once everything a publisher can fix has passed, so a\n // refused submission never takes an identity it then cannot use.\n const claimed = await claimPluginIdentity(firestore, {\n identity,\n listingId: listingRef.id,\n profileId: publisher.orgId,\n })\n if (!claimed) {\n return res.status(409).json({\n error: `The plugin identity \"${identity}\" belongs to another listing`,\n })\n }\n\n // Immutable content-addressed write — a new build is a new object, so a\n // consumer's pinned version can never be overwritten underneath it.\n const objectPath = pluginArtifactPath(\n listingRef.id,\n manifest.version,\n sha256,\n )\n const bucket = firebaseAdmin.app().storage().bucket(artifactsBucket)\n const file = bucket.file(objectPath)\n const [alreadyStored] = await file.exists()\n if (!alreadyStored) {\n await file.save(bundle, {\n contentType: 'application/javascript',\n metadata: {\n cacheControl: 'public, max-age=31536000, immutable',\n },\n })\n }\n\n const now = firebaseAdmin.firestore.FieldValue.serverTimestamp()\n await listingRef.set(\n {\n type: 'plugin',\n artifactType: 'plugin',\n profileId: publisher.orgId,\n pluginId: manifest.id,\n identity,\n displayName: displayName.trim(),\n // The keys a listing is searched by (AGL-3321): the template gallery's\n // marketplace shelf asks `nameTokens` on its query.\n ...displayNameSearchFields(displayName.trim()),\n ...(description.trim() && { description: description.trim() }),\n ...(category.trim() && { category: category.trim() }),\n priceUsd,\n ...contentVerdict.content,\n latestVersion: manifest.version,\n // Mirror of `pluginVersions/{latestVersion}.reviewState`, for the\n // marketplace (AGL-1121). Browse reads listings directly and cannot\n // afford a version subdoc read per card, so the newest bytes' verdict\n // has to live here to be shown at all.\n //\n // Reset to 'pending' on EVERY publish, including a bump on a listing\n // that staff already verified. That is the whole point: the listing's\n // `reviewStatus` deliberately survives a version bump, so before this\n // the marketplace badged brand-new, unreviewed bytes as reviewed.\n latestVersionReviewState: 'pending',\n deletedAt: null,\n // Review queue (AGL-432): first publish enters as 'submitted';\n // version bumps keep whatever status staff granted — which is safe\n // now that installs resolve the newest APPROVED version rather than\n // `latestVersion` (AGL-966). A listed plugin stays listed on its\n // reviewed bytes while the new version waits in the queue.\n ...(existing.empty && { createdAt: now, reviewStatus: 'submitted' }),\n // Private plugins never reach the marketplace but take the same\n // review path (AGL-968). Only settable on first publish; changing\n // visibility later is a separate, deliberate action.\n ...(existing.empty && {\n visibility: req.body?.visibility === 'private' ? 'private' : 'public',\n }),\n updatedAt: now,\n },\n { merge: true },\n )\n // The lists this listing appears in query by fields derived from it (AGL-3321).\n await refreshListingQueryFields(listingRef)\n // Version snapshots are server-only; the loader reads sha256 + manifest\n // to verify integrity and stamp CSP before executing.\n await listingRef\n .collection('pluginVersions')\n .doc(manifest.version)\n .set({\n version: manifest.version,\n sha256,\n objectPath,\n manifest,\n // What the loaders hold these bytes to (AGL-3390): every component\n // they register is `<identity>.<role>` and names this as its plugin.\n identity,\n ...(changelog.trim() && { changelog: changelog.trim() }),\n // The repository as declared FOR THESE BYTES (AGL-1076). The listing\n // carries whatever the latest publish said, and a publisher may move\n // or rename a repo between versions — a reviewer looking at v1.0.2\n // needs the link that was attested against v1.0.2's sha256, which is\n // the same reasoning that pins the attestation itself.\n ...(contentVerdict.content?.repositoryUrl && {\n repositoryUrl: contentVerdict.content.repositoryUrl,\n }),\n publishedAt: now,\n // What the publisher stated about these bytes (AGL-969). Same shape\n // as the staff `reviewChecklist` and pinned to the same sha256, so\n // a republish under this version string re-asks rather than\n // inheriting an attestation made about code that has since changed.\n //\n // `by` is the uid that submitted, not the org: takedown and removal\n // are defensible because a named person said this on a date, and an\n // org id names nobody.\n publisherAttestation: Object.fromEntries(\n ticked.map((id: string) => [id, { by: decoded.uid, at: now, sha256 }]),\n ),\n // Every version starts UNREVIEWED (AGL-966). This doc is written\n // with .set() and no merge, so a republish of the same version\n // string also resets it — new bytes, new review, by construction.\n reviewState: 'pending',\n // Keep the verdict we just computed (AGL-962). The bundle is\n // immutable and content-addressed, so this result holds for as long\n // as the checker does — the review page reads it instead of\n // re-downloading a megabyte per view. `verifierVersion` is what\n // makes that safe: bump PLUGIN_VERIFIER_VERSION and every stored\n // verdict is recomputed on next read.\n verification: {\n ok: verification.ok,\n problems: verification.problems,\n sha256,\n verifierVersion: PLUGIN_VERIFIER_VERSION,\n checkedAt: now,\n },\n })\n\n // Tell staff a version is waiting (AGL-970). Nothing announced a\n // submission before this: reviewers had to notice a new row by\n // visiting the queue, and an UPDATE to an already-listed plugin\n // produced no row at all. Persistent per-user notifications, so it\n // survives a refresh and stays until someone acts on it.\n // The body names the plugin itself (AGL-3432): in the daily digest it\n // sits under a one-line title.\n await notifyStaff({\n type: 'marketplace.review',\n title: `${displayName.trim()} v${manifest.version} needs review`,\n body: existing.empty\n ? `${displayName.trim()} v${manifest.version} was submitted to the ` +\n 'marketplace and is waiting for review.'\n : `${displayName.trim()} v${manifest.version} was published and is ` +\n 'waiting for review. The previously approved version keeps ' +\n 'installing until this one is reviewed.',\n link: `/admin/plugin-reviews/${listingRef.id}`,\n }).catch(() => undefined)\n\n return res\n .status(200)\n .json({ listingId: listingRef.id, version: manifest.version, sha256 })\n } catch (error) {\n console.error(error)\n return res.status(500).json({ error: 'Publish failed' })\n }\n}\n"],"names":["displayNameSearchFields","checkEntitlement","checkPluginBundle","createResourceUid","MAX_PLUGIN_BUNDLE_BYTES","pluginArtifactPath","PLUGIN_VERIFIER_VERSION","validatePluginManifest","firebaseAdmin","getOrgForUser","notifyStaff","resolveOrgPermissions","resolvePublisherProfile","manageListingsRefusal","publishPreconditionRefusal","recordPublisherAgreementAcceptance","listingSubmissionRefusal","refreshListingQueryFields","claimPluginIdentity","componentNamespaceRefusal","pluginIdentityFor","attestationLabels","missingAttestations","missingAttestationSubjects","PUBLISHER_ATTESTATION","createHash","marketplacePriceRefusal","missingPublicListingContent","validateListingContent","publisherAgreementConsentRefusal","listingArtifactType","siteStarterCategoryUpdate","SITE_STARTER_ARTIFACT_TYPE","isRefusedIdToken","MAX_BUNDLE_BYTES","updateListingContent","req","res","listingId","String","body","status","json","error","agreementRefusal","agreement","authorization","headers","idToken","startsWith","slice","length","undefined","categories","sentCategories","category","sentCategory","content","verdict","ok","decoded","app","auth","verifyIdToken","listing","firestore","listingRef","collection","doc","get","data","deletedAt","keyRefusal","profileId","staffMayModerate","categoryPatch","update","FieldValue","delete","description","displayName","nextName","trim","screened","publisherOrgId","readme","urls","homepageUrl","repositoryUrl","official","request","orgId","uid","staff","action","targetId","set","updatedAt","serverTimestamp","merge","console","setListingVisibility","visibility","missing","join","setListingPublished","published","publishPluginHandler","method","changelog","priceUsd","Math","round","Number","bundleBase64","bundle","contentVerdict","priceRefusal","validation","manifest","membership","requestedOrgId","permissions","publishToMarketplace","orgForUser","org","dayKey","Date","toISOString","limiterRef","allowed","runTransaction","transaction","window","count","publisher","refusal","sells","Buffer","from","verification","toString","maxBytes","declaredNetwork","capabilities","network","declaredContributions","contributes","requireContributions","problems","sha256","Uint8Array","digest","artifactsBucket","process","env","PLUGIN_ARTIFACTS_BUCKET","existing","where","id","limit","empty","docs","ref","frozenIdentity","identity","handle","namespaceRefusal","ticked","Array","isArray","attestation","map","filter","some","item","isUpdate","missingSubjects","subject","label","field","publisherName","claimed","objectPath","version","bucket","storage","file","alreadyStored","exists","save","contentType","metadata","cacheControl","now","type","artifactType","pluginId","latestVersion","latestVersionReviewState","createdAt","reviewStatus","publishedAt","publisherAttestation","Object","fromEntries","by","at","reviewState","verifierVersion","checkedAt","title","link","catch"],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type MarketplacePublisherPermission } from '../model/marketplace-publisher-permissions';
|
|
18
|
+
/**
|
|
19
|
+
* Where a member stands on one of the publisher keys of the workspace that
|
|
20
|
+
* publishes (AGL-4047):
|
|
21
|
+
*
|
|
22
|
+
* - `granted`: a member who holds it, by their role's default (owners and
|
|
23
|
+
* admins), their custom role or their own permissions;
|
|
24
|
+
* - `refused`: a member who does not;
|
|
25
|
+
* - `not-a-member`: someone with no membership in that workspace at all,
|
|
26
|
+
* for whom the listing or the profile is simply not theirs.
|
|
27
|
+
*
|
|
28
|
+
* The same answer `publisherManager()` and `listingManager()` give in the
|
|
29
|
+
* Firestore rules, read off the member's stamped permissions there, so the
|
|
30
|
+
* route and the client-direct write cannot disagree. Payouts, the Verified
|
|
31
|
+
* badge ask and the publisher's version history have no client-direct door to
|
|
32
|
+
* agree with: the rules freeze the payout fields and `verificationRequest`,
|
|
33
|
+
* and keep `pluginVersions` to staff, so their routes are the only way in.
|
|
34
|
+
*
|
|
35
|
+
* Whether someone belongs to the publishing workspace at all, with no key
|
|
36
|
+
* asked, is `isPublisherMember` (no self-purchase, no self-review, and, with
|
|
37
|
+
* the install landing in that workspace, free own installs: `ownsListingHere`).
|
|
38
|
+
*/
|
|
39
|
+
export type PublisherPermissionVerdict = 'granted' | 'refused' | 'not-a-member';
|
|
40
|
+
export declare function publisherPermissionVerdict(uid: string | null | undefined, publisherOrgId: string | null | undefined, permission: MarketplacePublisherPermission): Promise<PublisherPermissionVerdict>;
|
|
41
|
+
/** A door's refusal, shaped for `res.status(status).json(body)`. */
|
|
42
|
+
export interface PublisherDoorRefusal {
|
|
43
|
+
status: 403;
|
|
44
|
+
body: {
|
|
45
|
+
error: string;
|
|
46
|
+
reason?: 'permission';
|
|
47
|
+
permission?: MarketplacePublisherPermission;
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* The listing doors' gate (AGL-4047): editing a published listing, its
|
|
52
|
+
* picture, its visibility, and unpublishing or republishing it ask "Manage
|
|
53
|
+
* published listings" of a member of the workspace that published it. They
|
|
54
|
+
* asked for the owner or admin role, which is the key's default. Staff moderate any listing, as before. Someone outside the
|
|
55
|
+
* workspace hears the listing is not theirs, as before; a member without the
|
|
56
|
+
* key hears which permission they are missing.
|
|
57
|
+
*/
|
|
58
|
+
export declare function manageListingsRefusal(caller: {
|
|
59
|
+
uid: string;
|
|
60
|
+
staff?: unknown;
|
|
61
|
+
}, publisherOrgId: unknown, options: {
|
|
62
|
+
staffMayModerate: boolean;
|
|
63
|
+
}): Promise<PublisherDoorRefusal | null>;
|