@aglyn/plugins-marketplace 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +51 -0
- package/src/index.d.ts +19 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/artifact-update-dialog.component.d.ts +27 -0
- package/src/lib/components/artifact-update-dialog.component.js +244 -0
- package/src/lib/components/artifact-update-dialog.component.js.map +1 -0
- package/src/lib/components/host-plugins-card.component.d.ts +23 -0
- package/src/lib/components/host-plugins-card.component.js +566 -0
- package/src/lib/components/host-plugins-card.component.js.map +1 -0
- package/src/lib/components/listing-content.component.d.ts +57 -0
- package/src/lib/components/listing-content.component.js +1880 -0
- package/src/lib/components/listing-content.component.js.map +1 -0
- package/src/lib/components/listing-image.component.d.ts +46 -0
- package/src/lib/components/listing-image.component.js +38 -0
- package/src/lib/components/listing-image.component.js.map +1 -0
- package/src/lib/components/listing-reviews.component.d.ts +22 -0
- package/src/lib/components/listing-reviews.component.js +419 -0
- package/src/lib/components/listing-reviews.component.js.map +1 -0
- package/src/lib/components/marketplace-browse.component.d.ts +38 -0
- package/src/lib/components/marketplace-browse.component.js +802 -0
- package/src/lib/components/marketplace-browse.component.js.map +1 -0
- package/src/lib/components/plugin-site-set-panel.component.d.ts +24 -0
- package/src/lib/components/plugin-site-set-panel.component.js +137 -0
- package/src/lib/components/plugin-site-set-panel.component.js.map +1 -0
- package/src/lib/components/plugin-site-set.component.d.ts +43 -0
- package/src/lib/components/plugin-site-set.component.js +425 -0
- package/src/lib/components/plugin-site-set.component.js.map +1 -0
- package/src/lib/components/rating-input.component.d.ts +3 -0
- package/src/lib/components/rating-input.component.js +47 -0
- package/src/lib/components/rating-input.component.js.map +1 -0
- package/src/lib/components/report-target.component.d.ts +36 -0
- package/src/lib/components/report-target.component.js +181 -0
- package/src/lib/components/report-target.component.js.map +1 -0
- package/src/lib/components/uninstall-impact-dialog.component.d.ts +40 -0
- package/src/lib/components/uninstall-impact-dialog.component.js +244 -0
- package/src/lib/components/uninstall-impact-dialog.component.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +8 -0
- package/src/lib/constants/bundle-common.js +9 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/hooks/use-artifact-update.d.ts +41 -0
- package/src/lib/hooks/use-artifact-update.js +169 -0
- package/src/lib/hooks/use-artifact-update.js.map +1 -0
- package/src/lib/hooks/use-marketplace-actions.d.ts +37 -0
- package/src/lib/hooks/use-marketplace-actions.js +523 -0
- package/src/lib/hooks/use-marketplace-actions.js.map +1 -0
- package/src/lib/model/artifact-merge.d.ts +154 -0
- package/src/lib/model/artifact-merge.js +228 -0
- package/src/lib/model/artifact-merge.js.map +1 -0
- package/src/lib/model/index.d.ts +43 -0
- package/src/lib/model/index.js +41 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/marketplace-props.d.ts +57 -0
- package/src/lib/model/marketplace-props.js +441 -0
- package/src/lib/model/marketplace-props.js.map +1 -0
- package/src/lib/model/marketplace.d.ts +833 -0
- package/src/lib/model/marketplace.js +929 -0
- package/src/lib/model/marketplace.js.map +1 -0
- package/src/lib/model/rating-field.d.ts +24 -0
- package/src/lib/model/rating-field.js +33 -0
- package/src/lib/model/rating-field.js.map +1 -0
- package/src/lib/plugin.d.ts +33 -0
- package/src/lib/plugin.js +86 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server/billing-webhook.d.ts +25 -0
- package/src/lib/server/billing-webhook.js +1062 -0
- package/src/lib/server/billing-webhook.js.map +1 -0
- package/src/lib/server/checkout.d.ts +76 -0
- package/src/lib/server/checkout.js +477 -0
- package/src/lib/server/checkout.js.map +1 -0
- package/src/lib/server/connect.d.ts +24 -0
- package/src/lib/server/connect.js +205 -0
- package/src/lib/server/connect.js.map +1 -0
- package/src/lib/server/install-dataset-schema.d.ts +32 -0
- package/src/lib/server/install-dataset-schema.js +255 -0
- package/src/lib/server/install-dataset-schema.js.map +1 -0
- package/src/lib/server/install-email-starter.d.ts +65 -0
- package/src/lib/server/install-email-starter.js +279 -0
- package/src/lib/server/install-email-starter.js.map +1 -0
- package/src/lib/server/install-email-template.d.ts +34 -0
- package/src/lib/server/install-email-template.js +252 -0
- package/src/lib/server/install-email-template.js.map +1 -0
- package/src/lib/server/install-layout.d.ts +29 -0
- package/src/lib/server/install-layout.js +323 -0
- package/src/lib/server/install-layout.js.map +1 -0
- package/src/lib/server/install-pin-counts.d.ts +173 -0
- package/src/lib/server/install-pin-counts.js +248 -0
- package/src/lib/server/install-pin-counts.js.map +1 -0
- package/src/lib/server/install-plugin.d.ts +18 -0
- package/src/lib/server/install-plugin.js +405 -0
- package/src/lib/server/install-plugin.js.map +1 -0
- package/src/lib/server/install-template.d.ts +37 -0
- package/src/lib/server/install-template.js +348 -0
- package/src/lib/server/install-template.js.map +1 -0
- package/src/lib/server/install-theme.d.ts +42 -0
- package/src/lib/server/install-theme.js +313 -0
- package/src/lib/server/install-theme.js.map +1 -0
- package/src/lib/server/install.d.ts +27 -0
- package/src/lib/server/install.js +276 -0
- package/src/lib/server/install.js.map +1 -0
- package/src/lib/server/listing-versions.d.ts +26 -0
- package/src/lib/server/listing-versions.js +378 -0
- package/src/lib/server/listing-versions.js.map +1 -0
- package/src/lib/server/preview-image.d.ts +37 -0
- package/src/lib/server/preview-image.js +216 -0
- package/src/lib/server/preview-image.js.map +1 -0
- package/src/lib/server/provenance.d.ts +100 -0
- package/src/lib/server/provenance.js +118 -0
- package/src/lib/server/provenance.js.map +1 -0
- package/src/lib/server/publish-dataset-schema.d.ts +31 -0
- package/src/lib/server/publish-dataset-schema.js +202 -0
- package/src/lib/server/publish-dataset-schema.js.map +1 -0
- package/src/lib/server/publish-email-starter.d.ts +44 -0
- package/src/lib/server/publish-email-starter.js +231 -0
- package/src/lib/server/publish-email-starter.js.map +1 -0
- package/src/lib/server/publish-email-template.d.ts +33 -0
- package/src/lib/server/publish-email-template.js +211 -0
- package/src/lib/server/publish-email-template.js.map +1 -0
- package/src/lib/server/publish-layout.d.ts +33 -0
- package/src/lib/server/publish-layout.js +243 -0
- package/src/lib/server/publish-layout.js.map +1 -0
- package/src/lib/server/publish-plugin.d.ts +18 -0
- package/src/lib/server/publish-plugin.js +485 -0
- package/src/lib/server/publish-plugin.js.map +1 -0
- package/src/lib/server/publish-preconditions.d.ts +86 -0
- package/src/lib/server/publish-preconditions.js +160 -0
- package/src/lib/server/publish-preconditions.js.map +1 -0
- package/src/lib/server/publish-template.d.ts +27 -0
- package/src/lib/server/publish-template.js +215 -0
- package/src/lib/server/publish-template.js.map +1 -0
- package/src/lib/server/publish-theme.d.ts +38 -0
- package/src/lib/server/publish-theme.js +186 -0
- package/src/lib/server/publish-theme.js.map +1 -0
- package/src/lib/server/publish.d.ts +26 -0
- package/src/lib/server/publish.js +204 -0
- package/src/lib/server/publish.js.map +1 -0
- package/src/lib/server/publisher-profile-save.d.ts +30 -0
- package/src/lib/server/publisher-profile-save.js +226 -0
- package/src/lib/server/publisher-profile-save.js.map +1 -0
- package/src/lib/server/publisher-profile.d.ts +86 -0
- package/src/lib/server/publisher-profile.js +116 -0
- package/src/lib/server/publisher-profile.js.map +1 -0
- package/src/lib/server/purchase-entitlement.d.ts +83 -0
- package/src/lib/server/purchase-entitlement.js +118 -0
- package/src/lib/server/purchase-entitlement.js.map +1 -0
- package/src/lib/server/report.d.ts +32 -0
- package/src/lib/server/report.js +101 -0
- package/src/lib/server/report.js.map +1 -0
- package/src/lib/server/reviews.d.ts +41 -0
- package/src/lib/server/reviews.js +240 -0
- package/src/lib/server/reviews.js.map +1 -0
- package/src/lib/server/update-artifact.d.ts +19 -0
- package/src/lib/server/update-artifact.js +569 -0
- package/src/lib/server/update-artifact.js.map +1 -0
- package/src/lib/server/verification-request.d.ts +31 -0
- package/src/lib/server/verification-request.js +173 -0
- package/src/lib/server/verification-request.js.map +1 -0
- package/src/lib/server/version-stats.d.ts +202 -0
- package/src/lib/server/version-stats.js +305 -0
- package/src/lib/server/version-stats.js.map +1 -0
- package/src/lib/server.d.ts +30 -0
- package/src/lib/server.js +95 -0
- package/src/lib/server.js.map +1 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
/**
|
|
3
|
+
* @license
|
|
4
|
+
* Copyright 2026 Aglyn LLC
|
|
5
|
+
*
|
|
6
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
* you may not use this file except in compliance with the License.
|
|
8
|
+
* You may obtain a copy of the License at
|
|
9
|
+
*
|
|
10
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
*
|
|
12
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
* See the License for the specific language governing permissions and
|
|
16
|
+
* limitations under the License.
|
|
17
|
+
*/ import { checkEntitlement, checkPluginBundle, createResourceUid, MAX_PLUGIN_BUNDLE_BYTES, pluginArtifactPath, PLUGIN_VERIFIER_VERSION, validatePluginManifest } from "@aglyn/aglyn/server";
|
|
18
|
+
import { firebaseAdmin, getOrgForUser, notifyStaff } from "@aglyn/tenant-data-admin";
|
|
19
|
+
import { resolveOrgPermissions } from "@aglyn/tenant-runtime/org-permissions";
|
|
20
|
+
import { canActAsPublisher, resolvePublisherProfile } from "./publisher-profile.js";
|
|
21
|
+
import { publishPreconditionRefusal } from "./publish-preconditions.js";
|
|
22
|
+
import { attestationLabels, missingAttestations, missingAttestationSubjects, PUBLISHER_ATTESTATION } from "@aglyn/aglyn/app-utils/publisher-attestation";
|
|
23
|
+
import { createHash } from "crypto";
|
|
24
|
+
import { marketplacePriceRefusal, missingPublicListingContent, validateListingContent } from "../model/marketplace.js";
|
|
25
|
+
// Base64 bundle bodies: a small JS plugin bundle, capped generously.
|
|
26
|
+
const MAX_BUNDLE_BYTES = 5 * 1024 * 1024;
|
|
27
|
+
/**
|
|
28
|
+
* Publishes an executable plugin to the marketplace (AGL-45), per the AGL-43
|
|
29
|
+
* artifact pipeline — relocated from the console app route into the
|
|
30
|
+
* marketplace plugin (AGL-418); URL `/api/marketplace/publish-plugin` is
|
|
31
|
+
* preserved through the dispatcher. Runs server-side so validation
|
|
32
|
+
* (manifest schema, size caps) can't be bypassed: the bundle is
|
|
33
|
+
* content-addressed (sha256) and written IMMUTABLY to the isolated
|
|
34
|
+
* artifacts bucket, and the version entry records the hash the loader
|
|
35
|
+
* verifies before executing. Env-gated on `PLUGIN_ARTIFACTS_BUCKET` —
|
|
36
|
+
* 501 degrades like other platform infra. Requirements: publish
|
|
37
|
+
* permission, marketplace profile, Pro plan; paid listings need completed
|
|
38
|
+
* Stripe Connect onboarding.
|
|
39
|
+
*/ const updateListingContent = async (req, res)=>{
|
|
40
|
+
var _ref, _req_headers_authorization, _req_body;
|
|
41
|
+
var _req_body1;
|
|
42
|
+
const listingId = String((_ref = (_req_body1 = req.body) == null ? void 0 : _req_body1.listingId) != null ? _ref : '');
|
|
43
|
+
if (!listingId) return res.status(400).json({
|
|
44
|
+
error: 'Missing listingId'
|
|
45
|
+
});
|
|
46
|
+
const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
|
|
47
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
|
|
48
|
+
if (!idToken) return res.status(401).json({
|
|
49
|
+
error: 'Unauthenticated'
|
|
50
|
+
});
|
|
51
|
+
const verdict = validateListingContent((_req_body = req.body) != null ? _req_body : {});
|
|
52
|
+
if (!verdict.ok) return res.status(400).json({
|
|
53
|
+
error: verdict.error
|
|
54
|
+
});
|
|
55
|
+
try {
|
|
56
|
+
var _req_body2, _req_body3;
|
|
57
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
58
|
+
const listingRef = firebaseAdmin.app().firestore().collection('marketplaceListings').doc(listingId);
|
|
59
|
+
const listing = (await listingRef.get()).data();
|
|
60
|
+
if (!listing || listing.deletedAt) {
|
|
61
|
+
return res.status(404).json({
|
|
62
|
+
error: 'Unknown listing'
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// Org-owned listings (AGL-652): a uid never equals an org id, so the
|
|
66
|
+
// old identity comparison would lock publishers out of their own listing.
|
|
67
|
+
const isPublisher = await canActAsPublisher(firebaseAdmin.app().firestore(), decoded.uid, listing.profileId);
|
|
68
|
+
if (!isPublisher && decoded['staff'] !== true) {
|
|
69
|
+
return res.status(403).json({
|
|
70
|
+
error: 'Not your listing'
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
// Name and description are the whole first impression of a listing in
|
|
74
|
+
// browse, and until AGL-793 they were the only listing-content fields with
|
|
75
|
+
// no edit path — a typo in either could only be corrected by publishing a
|
|
76
|
+
// fake new version, which signals "there's an update" to every installer.
|
|
77
|
+
// Same caps the publish routes apply. An empty name is ignored rather than
|
|
78
|
+
// accepted: a nameless listing is unrecognisable in the catalogue.
|
|
79
|
+
const description = (_req_body2 = req.body) == null ? void 0 : _req_body2.description;
|
|
80
|
+
const displayName = (_req_body3 = req.body) == null ? void 0 : _req_body3.displayName;
|
|
81
|
+
const nextName = typeof displayName === 'string' ? displayName.trim().slice(0, 80) : '';
|
|
82
|
+
await listingRef.set(_extends({}, verdict.content, typeof description === 'string' ? {
|
|
83
|
+
description: description.slice(0, 500)
|
|
84
|
+
} : {}, nextName ? {
|
|
85
|
+
displayName: nextName
|
|
86
|
+
} : {}, {
|
|
87
|
+
updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
|
|
88
|
+
}), {
|
|
89
|
+
merge: true
|
|
90
|
+
});
|
|
91
|
+
return res.status(200).json({
|
|
92
|
+
ok: true
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.error(error);
|
|
96
|
+
return res.status(500).json({
|
|
97
|
+
error: 'Listing update failed'
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Flip a listing between private and public (AGL-968/994).
|
|
103
|
+
*
|
|
104
|
+
* Deliberately NOT a re-review. Per-version `reviewState` and the listing's
|
|
105
|
+
* `reviewStatus` are left exactly as they are: approval is a statement about
|
|
106
|
+
* specific bytes (AGL-966), and who may install them was never part of that
|
|
107
|
+
* statement. Sending an already-approved bundle back through the queue
|
|
108
|
+
* because its audience widened would only teach publishers to start public.
|
|
109
|
+
*
|
|
110
|
+
* Going public does require what a public listing needs — see
|
|
111
|
+
* {@link missingPublicListingContent}. Going private is unconditional: it
|
|
112
|
+
* only ever removes reach.
|
|
113
|
+
*/ const setListingVisibility = async (req, res)=>{
|
|
114
|
+
var _ref, _req_headers_authorization;
|
|
115
|
+
var _req_body, _req_body1;
|
|
116
|
+
const listingId = String((_ref = (_req_body = req.body) == null ? void 0 : _req_body.listingId) != null ? _ref : '');
|
|
117
|
+
const visibility = ((_req_body1 = req.body) == null ? void 0 : _req_body1.visibility) === 'private' ? 'private' : 'public';
|
|
118
|
+
if (!listingId) return res.status(400).json({
|
|
119
|
+
error: 'Missing listingId'
|
|
120
|
+
});
|
|
121
|
+
const authorization = String((_req_headers_authorization = req.headers.authorization) != null ? _req_headers_authorization : '');
|
|
122
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
|
|
123
|
+
if (!idToken) return res.status(401).json({
|
|
124
|
+
error: 'Unauthenticated'
|
|
125
|
+
});
|
|
126
|
+
try {
|
|
127
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
128
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
129
|
+
const listingRef = firestore.collection('marketplaceListings').doc(listingId);
|
|
130
|
+
const listing = (await listingRef.get()).data();
|
|
131
|
+
if (!listing || listing.deletedAt) {
|
|
132
|
+
return res.status(404).json({
|
|
133
|
+
error: 'Unknown listing'
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
const isPublisher = await canActAsPublisher(firestore, decoded.uid, listing.profileId);
|
|
137
|
+
if (!isPublisher && decoded['staff'] !== true) {
|
|
138
|
+
return res.status(403).json({
|
|
139
|
+
error: 'Not your listing'
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
if (visibility === 'public') {
|
|
143
|
+
const missing = missingPublicListingContent(listing);
|
|
144
|
+
if (missing.length) {
|
|
145
|
+
return res.status(400).json({
|
|
146
|
+
error: 'A marketplace listing needs ' + missing.join(', ') + '. Add them in Edit, then publish it.',
|
|
147
|
+
missing
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
await listingRef.set({
|
|
152
|
+
visibility,
|
|
153
|
+
updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp()
|
|
154
|
+
}, {
|
|
155
|
+
merge: true
|
|
156
|
+
});
|
|
157
|
+
return res.status(200).json({
|
|
158
|
+
ok: true,
|
|
159
|
+
visibility
|
|
160
|
+
});
|
|
161
|
+
} catch (error) {
|
|
162
|
+
console.error(error);
|
|
163
|
+
return res.status(500).json({
|
|
164
|
+
error: 'Visibility change failed'
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
export const publishPluginHandler = async (req, res)=>{
|
|
169
|
+
var _req_body, _ref, _ref1, _ref2, _ref3, _ref4, _ref5, _headers_authorization;
|
|
170
|
+
var _req_body1, _req_body2;
|
|
171
|
+
if (req.method !== 'POST') {
|
|
172
|
+
return res.status(405).json({
|
|
173
|
+
error: 'Method not allowed'
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// Content-only listing edits (AGL-430): publishers refresh the docs the
|
|
177
|
+
// detail page renders (readme/screenshots/links/…) without shipping a
|
|
178
|
+
// new bundle. Publisher-or-staff, validated exactly like publish.
|
|
179
|
+
if (((_req_body1 = req.body) == null ? void 0 : _req_body1.action) === 'update-listing') {
|
|
180
|
+
return updateListingContent(req, res);
|
|
181
|
+
}
|
|
182
|
+
// Private ⇄ public (AGL-968/994), without shipping a new bundle.
|
|
183
|
+
if (((_req_body2 = req.body) == null ? void 0 : _req_body2.action) === 'set-visibility') {
|
|
184
|
+
return setListingVisibility(req, res);
|
|
185
|
+
}
|
|
186
|
+
const body = (_req_body = req.body) != null ? _req_body : {};
|
|
187
|
+
const headers = req.headers;
|
|
188
|
+
const displayName = String((_ref = body == null ? void 0 : body.displayName) != null ? _ref : '').slice(0, 80);
|
|
189
|
+
const description = String((_ref1 = body == null ? void 0 : body.description) != null ? _ref1 : '').slice(0, 500);
|
|
190
|
+
const category = String((_ref2 = body == null ? void 0 : body.category) != null ? _ref2 : '').slice(0, 40);
|
|
191
|
+
const changelog = String((_ref3 = body == null ? void 0 : body.changelog) != null ? _ref3 : '').slice(0, 1000);
|
|
192
|
+
const priceUsd = Math.round(Number((_ref4 = body == null ? void 0 : body.priceUsd) != null ? _ref4 : 0)) || 0;
|
|
193
|
+
const bundleBase64 = String((_ref5 = body == null ? void 0 : body.bundle) != null ? _ref5 : '');
|
|
194
|
+
// Listing content (AGL-430): optional publisher docs for the detail page.
|
|
195
|
+
const contentVerdict = validateListingContent(body != null ? body : {});
|
|
196
|
+
if (!contentVerdict.ok) {
|
|
197
|
+
return res.status(400).json({
|
|
198
|
+
error: contentVerdict.error
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
// The price floor and ceiling, from ONE validator (AGL-2343): a paid listing
|
|
202
|
+
// under `MARKETPLACE_MIN_PRICE_USD` loses money on every sale, because
|
|
203
|
+
// marketplace checkout is a destination charge whose Stripe fee is debited
|
|
204
|
+
// from the platform's balance. Also enforced in `publishPreconditionRefusal`
|
|
205
|
+
// below, so a door that drops this line is still covered.
|
|
206
|
+
const priceRefusal = marketplacePriceRefusal(priceUsd);
|
|
207
|
+
if (priceRefusal) {
|
|
208
|
+
return res.status(400).json({
|
|
209
|
+
error: priceRefusal
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (!displayName.trim() || !bundleBase64) {
|
|
213
|
+
return res.status(400).json({
|
|
214
|
+
error: 'Missing displayName or bundle'
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
const validation = validatePluginManifest(body == null ? void 0 : body.manifest);
|
|
218
|
+
if (validation.ok === false) {
|
|
219
|
+
return res.status(422).json({
|
|
220
|
+
error: validation.error
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
const manifest = validation.manifest;
|
|
224
|
+
const authorization = (_headers_authorization = headers.authorization) != null ? _headers_authorization : '';
|
|
225
|
+
const idToken = authorization.startsWith('Bearer ') ? authorization.slice('Bearer '.length) : undefined;
|
|
226
|
+
if (!idToken) return res.status(401).json({
|
|
227
|
+
error: 'Unauthenticated'
|
|
228
|
+
});
|
|
229
|
+
try {
|
|
230
|
+
var _ref6, _membership_orgId, _ref7, _ref8, _manifest_contributes;
|
|
231
|
+
var _manifest_capabilities, _req_body3, _contentVerdict_content;
|
|
232
|
+
const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken);
|
|
233
|
+
// Act on the org the client is in (AGL-868), not a guessed first-org — a
|
|
234
|
+
// member can belong to several, and the plugin publishes under one of them.
|
|
235
|
+
const requestedOrgId = String((_ref6 = body == null ? void 0 : body.orgId) != null ? _ref6 : '').trim() || undefined;
|
|
236
|
+
const membership = await resolveOrgPermissions(decoded.uid, {
|
|
237
|
+
orgId: requestedOrgId
|
|
238
|
+
});
|
|
239
|
+
if (!membership.permissions.publishToMarketplace) {
|
|
240
|
+
return res.status(403).json({
|
|
241
|
+
error: 'Your organization role does not allow publishing to the marketplace'
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
const firestore = firebaseAdmin.app().firestore();
|
|
245
|
+
// Plan gate rides the acting org's doc (AGL-238/868).
|
|
246
|
+
const orgForUser = await getOrgForUser(decoded.uid, (_membership_orgId = membership.orgId) != null ? _membership_orgId : requestedOrgId);
|
|
247
|
+
const org = (_ref7 = orgForUser == null ? void 0 : orgForUser.org) != null ? _ref7 : {};
|
|
248
|
+
if (!checkEntitlement(org, 'marketplaceSelling')) {
|
|
249
|
+
return res.status(403).json({
|
|
250
|
+
error: 'Publishing to the marketplace requires a Pro plan'
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
// Publish rate limit (AGL-437): a runaway or abusive publisher can't
|
|
254
|
+
// flood the artifacts bucket/review queue — 20 publishes per UTC day.
|
|
255
|
+
// Scoped to the publishing ORG (AGL-652): per-user it would multiply by
|
|
256
|
+
// headcount, letting a big team flood the queue 20 publishes at a time.
|
|
257
|
+
const dayKey = new Date().toISOString().slice(0, 10);
|
|
258
|
+
if (!(orgForUser == null ? void 0 : orgForUser.orgId)) {
|
|
259
|
+
return res.status(409).json({
|
|
260
|
+
error: 'No publishing organization'
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
const limiterRef = firestore.collection('publisherProfiles').doc(orgForUser.orgId).collection('meta').doc('publishWindow');
|
|
264
|
+
const allowed = await firestore.runTransaction(async (transaction)=>{
|
|
265
|
+
var _data, _window_count;
|
|
266
|
+
const window = (_data = (await transaction.get(limiterRef)).data()) != null ? _data : {};
|
|
267
|
+
const count = window.dayKey === dayKey ? Number((_window_count = window.count) != null ? _window_count : 0) : 0;
|
|
268
|
+
if (count >= 20) return false;
|
|
269
|
+
transaction.set(limiterRef, {
|
|
270
|
+
dayKey,
|
|
271
|
+
count: count + 1
|
|
272
|
+
});
|
|
273
|
+
return true;
|
|
274
|
+
});
|
|
275
|
+
if (!allowed) {
|
|
276
|
+
return res.status(429).json({
|
|
277
|
+
error: 'Daily publish limit reached — try again tomorrow'
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
const publisher = await resolvePublisherProfile(firestore, orgForUser.orgId);
|
|
281
|
+
// Profile, payouts AND the publisher agreement, in one gate
|
|
282
|
+
// (AGL-2282) — see `publishPreconditionRefusal`.
|
|
283
|
+
const refusal = publishPreconditionRefusal(publisher, {
|
|
284
|
+
priceUsd,
|
|
285
|
+
sells: 'plugins'
|
|
286
|
+
});
|
|
287
|
+
if (refusal) return res.status(refusal.status).json(refusal.body);
|
|
288
|
+
const bundle = Buffer.from(bundleBase64, 'base64');
|
|
289
|
+
if (!bundle.length || bundle.length > MAX_BUNDLE_BYTES) {
|
|
290
|
+
return res.status(413).json({
|
|
291
|
+
error: 'Bundle is empty or too large'
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
// Static verification (AGL-426): the same checks as the local
|
|
295
|
+
// `verify-plugin-bundle.mjs`, so a bundle that passes there publishes
|
|
296
|
+
// here. Entry exports, self-containment, forbidden APIs, size.
|
|
297
|
+
//
|
|
298
|
+
// The manifest goes in too (AGL-964): the checker diffs the bundle's
|
|
299
|
+
// network calls against the origins declared here, and a call to an
|
|
300
|
+
// origin the manifest omits is refused at publish rather than silently
|
|
301
|
+
// blocked by the CSP at runtime, where the publisher would meet it as a
|
|
302
|
+
// mystery in production.
|
|
303
|
+
//
|
|
304
|
+
// And what register() registers is compared with the manifest's
|
|
305
|
+
// `contributes` (AGL-3116), which a new version must carry whenever it
|
|
306
|
+
// registers anything: the loaders place a plugin by that declaration
|
|
307
|
+
// alone, so a registration it omits is a surface that never loads.
|
|
308
|
+
const verification = checkPluginBundle(bundle.toString('utf8'), {
|
|
309
|
+
maxBytes: MAX_PLUGIN_BUNDLE_BYTES,
|
|
310
|
+
declaredNetwork: (_ref8 = (_manifest_capabilities = manifest.capabilities) == null ? void 0 : _manifest_capabilities.network) != null ? _ref8 : [],
|
|
311
|
+
declaredContributions: (_manifest_contributes = manifest.contributes) != null ? _manifest_contributes : null,
|
|
312
|
+
requireContributions: true
|
|
313
|
+
});
|
|
314
|
+
if (!verification.ok) {
|
|
315
|
+
return res.status(422).json({
|
|
316
|
+
error: 'Bundle failed verification',
|
|
317
|
+
problems: verification.problems
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
const sha256 = createHash('sha256').update(new Uint8Array(bundle)).digest('hex');
|
|
321
|
+
// Isolated artifacts bucket (AGL-43 §3). Without it configured we
|
|
322
|
+
// refuse rather than store executable code in the app bucket.
|
|
323
|
+
const artifactsBucket = process.env.PLUGIN_ARTIFACTS_BUCKET;
|
|
324
|
+
if (!artifactsBucket) {
|
|
325
|
+
return res.status(501).json({
|
|
326
|
+
error: 'Plugin publishing is not configured (missing ' + 'PLUGIN_ARTIFACTS_BUCKET).'
|
|
327
|
+
});
|
|
328
|
+
}
|
|
329
|
+
// One listing per publisher+plugin id: re-publishing bumps the version.
|
|
330
|
+
const existing = await firestore.collection('marketplaceListings').where('profileId', '==', publisher.orgId).where('pluginId', '==', manifest.id).limit(1).get();
|
|
331
|
+
const listingRef = existing.empty ? firestore.collection('marketplaceListings').doc(createResourceUid()) : existing.docs[0].ref;
|
|
332
|
+
// Pre-submission attestation (AGL-969). Blocks the publish rather than
|
|
333
|
+
// warning, because the whole point is that these answers exist BEFORE a
|
|
334
|
+
// reviewer spends time on the bundle.
|
|
335
|
+
//
|
|
336
|
+
// Whether this is an update is decided here, from whether a listing
|
|
337
|
+
// already exists — never from a client flag, which would make "this is
|
|
338
|
+
// my first version" a way to skip the changelog item.
|
|
339
|
+
const ticked = (Array.isArray(body == null ? void 0 : body.attestation) ? body.attestation.map((id)=>String(id)) : []).filter((id)=>PUBLISHER_ATTESTATION.some((item)=>item.id === id));
|
|
340
|
+
const isUpdate = !existing.empty;
|
|
341
|
+
const missing = missingAttestations(ticked, isUpdate);
|
|
342
|
+
if (missing.length) {
|
|
343
|
+
return res.status(428).json({
|
|
344
|
+
error: 'Confirm the pre-submission checklist before publishing: ' + attestationLabels(missing).join('; '),
|
|
345
|
+
missingAttestations: missing
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
// An attestation needs something to be about (AGL-1076). `repository`
|
|
349
|
+
// was confirmable in a form with no repository field, so listings
|
|
350
|
+
// reached review carrying a signed claim that a URL we never collected
|
|
351
|
+
// was public and matched — and the reviewer's first link went nowhere.
|
|
352
|
+
//
|
|
353
|
+
// Checked against the SUBMISSION, not the listing doc: a first publish
|
|
354
|
+
// has no listing, and on an update the tick is a statement about what
|
|
355
|
+
// this publish declares. `contentVerdict.content` is the normalized
|
|
356
|
+
// value about to be stored, so the gate and the write cannot disagree.
|
|
357
|
+
const missingSubjects = missingAttestationSubjects(contentVerdict.content, isUpdate);
|
|
358
|
+
if (missingSubjects.length) {
|
|
359
|
+
return res.status(428).json({
|
|
360
|
+
error: 'These are confirmed above but not filled in: ' + missingSubjects.map((subject)=>subject.label).join('; '),
|
|
361
|
+
missingAttestationSubjects: missingSubjects.map((subject)=>subject.field)
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
// Immutable content-addressed write — a new build is a new object, so a
|
|
365
|
+
// consumer's pinned version can never be overwritten underneath it.
|
|
366
|
+
const objectPath = pluginArtifactPath(listingRef.id, manifest.version, sha256);
|
|
367
|
+
const bucket = firebaseAdmin.app().storage().bucket(artifactsBucket);
|
|
368
|
+
const file = bucket.file(objectPath);
|
|
369
|
+
const [alreadyStored] = await file.exists();
|
|
370
|
+
if (!alreadyStored) {
|
|
371
|
+
await file.save(bundle, {
|
|
372
|
+
contentType: 'application/javascript',
|
|
373
|
+
metadata: {
|
|
374
|
+
cacheControl: 'public, max-age=31536000, immutable'
|
|
375
|
+
}
|
|
376
|
+
});
|
|
377
|
+
}
|
|
378
|
+
const now = firebaseAdmin.firestore.FieldValue.serverTimestamp();
|
|
379
|
+
await listingRef.set(_extends({
|
|
380
|
+
type: 'plugin',
|
|
381
|
+
artifactType: 'plugin',
|
|
382
|
+
profileId: publisher.orgId,
|
|
383
|
+
pluginId: manifest.id,
|
|
384
|
+
displayName: displayName.trim()
|
|
385
|
+
}, description.trim() && {
|
|
386
|
+
description: description.trim()
|
|
387
|
+
}, category.trim() && {
|
|
388
|
+
category: category.trim()
|
|
389
|
+
}, {
|
|
390
|
+
priceUsd
|
|
391
|
+
}, contentVerdict.content, {
|
|
392
|
+
latestVersion: manifest.version,
|
|
393
|
+
// Mirror of `pluginVersions/{latestVersion}.reviewState`, for the
|
|
394
|
+
// marketplace (AGL-1121). Browse reads listings directly and cannot
|
|
395
|
+
// afford a version subdoc read per card, so the newest bytes' verdict
|
|
396
|
+
// has to live here to be shown at all.
|
|
397
|
+
//
|
|
398
|
+
// Reset to 'pending' on EVERY publish, including a bump on a listing
|
|
399
|
+
// that staff already verified. That is the whole point: the listing's
|
|
400
|
+
// `reviewStatus` deliberately survives a version bump, so before this
|
|
401
|
+
// the marketplace badged brand-new, unreviewed bytes as reviewed.
|
|
402
|
+
latestVersionReviewState: 'pending',
|
|
403
|
+
deletedAt: null
|
|
404
|
+
}, existing.empty && {
|
|
405
|
+
createdAt: now,
|
|
406
|
+
reviewStatus: 'submitted'
|
|
407
|
+
}, existing.empty && {
|
|
408
|
+
visibility: ((_req_body3 = req.body) == null ? void 0 : _req_body3.visibility) === 'private' ? 'private' : 'public'
|
|
409
|
+
}, {
|
|
410
|
+
updatedAt: now
|
|
411
|
+
}), {
|
|
412
|
+
merge: true
|
|
413
|
+
});
|
|
414
|
+
// Version snapshots are server-only; the loader reads sha256 + manifest
|
|
415
|
+
// to verify integrity and stamp CSP before executing.
|
|
416
|
+
await listingRef.collection('pluginVersions').doc(manifest.version).set(_extends({
|
|
417
|
+
version: manifest.version,
|
|
418
|
+
sha256,
|
|
419
|
+
objectPath,
|
|
420
|
+
manifest
|
|
421
|
+
}, changelog.trim() && {
|
|
422
|
+
changelog: changelog.trim()
|
|
423
|
+
}, ((_contentVerdict_content = contentVerdict.content) == null ? void 0 : _contentVerdict_content.repositoryUrl) && {
|
|
424
|
+
repositoryUrl: contentVerdict.content.repositoryUrl
|
|
425
|
+
}, {
|
|
426
|
+
publishedAt: now,
|
|
427
|
+
// What the publisher stated about these bytes (AGL-969). Same shape
|
|
428
|
+
// as the staff `reviewChecklist` and pinned to the same sha256, so
|
|
429
|
+
// a republish under this version string re-asks rather than
|
|
430
|
+
// inheriting an attestation made about code that has since changed.
|
|
431
|
+
//
|
|
432
|
+
// `by` is the uid that submitted, not the org: takedown and removal
|
|
433
|
+
// are defensible because a named person said this on a date, and an
|
|
434
|
+
// org id names nobody.
|
|
435
|
+
publisherAttestation: Object.fromEntries(ticked.map((id)=>[
|
|
436
|
+
id,
|
|
437
|
+
{
|
|
438
|
+
by: decoded.uid,
|
|
439
|
+
at: now,
|
|
440
|
+
sha256
|
|
441
|
+
}
|
|
442
|
+
])),
|
|
443
|
+
// Every version starts UNREVIEWED (AGL-966). This doc is written
|
|
444
|
+
// with .set() and no merge, so a republish of the same version
|
|
445
|
+
// string also resets it — new bytes, new review, by construction.
|
|
446
|
+
reviewState: 'pending',
|
|
447
|
+
// Keep the verdict we just computed (AGL-962). The bundle is
|
|
448
|
+
// immutable and content-addressed, so this result holds for as long
|
|
449
|
+
// as the checker does — the review page reads it instead of
|
|
450
|
+
// re-downloading a megabyte per view. `verifierVersion` is what
|
|
451
|
+
// makes that safe: bump PLUGIN_VERIFIER_VERSION and every stored
|
|
452
|
+
// verdict is recomputed on next read.
|
|
453
|
+
verification: {
|
|
454
|
+
ok: verification.ok,
|
|
455
|
+
problems: verification.problems,
|
|
456
|
+
sha256,
|
|
457
|
+
verifierVersion: PLUGIN_VERIFIER_VERSION,
|
|
458
|
+
checkedAt: now
|
|
459
|
+
}
|
|
460
|
+
}));
|
|
461
|
+
// Tell staff a version is waiting (AGL-970). Nothing announced a
|
|
462
|
+
// submission before this: reviewers had to notice a new row by
|
|
463
|
+
// visiting the queue, and an UPDATE to an already-listed plugin
|
|
464
|
+
// produced no row at all. Persistent per-user notifications, so it
|
|
465
|
+
// survives a refresh and stays until someone acts on it.
|
|
466
|
+
await notifyStaff({
|
|
467
|
+
type: 'marketplace.review',
|
|
468
|
+
title: `${displayName.trim()} v${manifest.version} needs review`,
|
|
469
|
+
body: existing.empty ? 'A new plugin was submitted to the marketplace.' : 'A new version was published. The previously approved version ' + 'keeps installing until this one is reviewed.',
|
|
470
|
+
link: `/admin/plugin-reviews/${listingRef.id}`
|
|
471
|
+
}).catch(()=>undefined);
|
|
472
|
+
return res.status(200).json({
|
|
473
|
+
listingId: listingRef.id,
|
|
474
|
+
version: manifest.version,
|
|
475
|
+
sha256
|
|
476
|
+
});
|
|
477
|
+
} catch (error) {
|
|
478
|
+
console.error(error);
|
|
479
|
+
return res.status(500).json({
|
|
480
|
+
error: 'Publish failed'
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
//# sourceMappingURL=publish-plugin.js.map
|
|
@@ -0,0 +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 {\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 {\n attestationLabels,\n missingAttestations,\n missingAttestationSubjects,\n PUBLISHER_ATTESTATION,\n} from '@aglyn/aglyn/app-utils/publisher-attestation'\nimport { createHash } from 'crypto'\nimport {\n marketplacePriceRefusal,\n missingPublicListingContent,\n validateListingContent,\n} from '../model/marketplace'\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 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 const verdict = validateListingContent(req.body ?? {})\n if (!verdict.ok) return res.status(400).json({ error: verdict.error })\n try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\n const listingRef = firebaseAdmin\n .app()\n .firestore()\n .collection('marketplaceListings')\n .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 firebaseAdmin.app().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 // 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 await listingRef.set(\n {\n ...verdict.content,\n ...(typeof description === 'string'\n ? { description: description.slice(0, 500) }\n : {}),\n ...(nextName ? { displayName: nextName } : {}),\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\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 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 try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\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 await listingRef.set(\n {\n visibility,\n updatedAt: firebaseAdmin.firestore.FieldValue.serverTimestamp(),\n },\n { merge: true },\n )\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\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 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 try {\n const decoded = await firebaseAdmin.app().auth().verifyIdToken(idToken)\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 })\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 // 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 // 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 displayName: 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 // 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 ...(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 await notifyStaff({\n type: 'marketplace.review',\n title: `${displayName.trim()} v${manifest.version} needs review`,\n body: existing.empty\n ? 'A new plugin was submitted to the marketplace.'\n : 'A new version was published. The previously approved version ' +\n 'keeps 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":["checkEntitlement","checkPluginBundle","createResourceUid","MAX_PLUGIN_BUNDLE_BYTES","pluginArtifactPath","PLUGIN_VERIFIER_VERSION","validatePluginManifest","firebaseAdmin","getOrgForUser","notifyStaff","resolveOrgPermissions","canActAsPublisher","resolvePublisherProfile","publishPreconditionRefusal","attestationLabels","missingAttestations","missingAttestationSubjects","PUBLISHER_ATTESTATION","createHash","marketplacePriceRefusal","missingPublicListingContent","validateListingContent","MAX_BUNDLE_BYTES","updateListingContent","req","res","listingId","String","body","status","json","error","authorization","headers","idToken","startsWith","slice","length","undefined","verdict","ok","decoded","app","auth","verifyIdToken","listingRef","firestore","collection","doc","listing","get","data","deletedAt","isPublisher","uid","profileId","description","displayName","nextName","trim","set","content","updatedAt","FieldValue","serverTimestamp","merge","console","setListingVisibility","visibility","missing","join","publishPluginHandler","method","action","category","changelog","priceUsd","Math","round","Number","bundleBase64","bundle","contentVerdict","priceRefusal","validation","manifest","membership","requestedOrgId","orgId","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","update","Uint8Array","digest","artifactsBucket","process","env","PLUGIN_ARTIFACTS_BUCKET","existing","where","id","limit","empty","docs","ref","ticked","Array","isArray","attestation","map","filter","some","item","isUpdate","missingSubjects","subject","label","field","objectPath","version","bucket","storage","file","alreadyStored","exists","save","contentType","metadata","cacheControl","now","type","artifactType","pluginId","latestVersion","latestVersionReviewState","createdAt","reviewStatus","repositoryUrl","publishedAt","publisherAttestation","Object","fromEntries","by","at","reviewState","verifierVersion","checkedAt","title","link","catch"],"mappings":"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"}
|