@powerhousedao/registry 6.2.2-dev.1 → 6.2.2-dev.11
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/dist/cli.d.mts +4 -0
- package/dist/cli.d.mts.map +1 -1
- package/dist/cli.mjs +292 -84
- package/dist/cli.mjs.map +1 -1
- package/dist/plugins/package.json +1 -0
- package/dist/plugins/verdaccio-registry-auth.js +241 -0
- package/package.json +9 -3
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
//#region \0rolldown/runtime.js
|
|
6
|
+
var __create = Object.create;
|
|
7
|
+
var __defProp = Object.defineProperty;
|
|
8
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
9
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
10
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
11
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
14
|
+
key = keys[i];
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
16
|
+
get: ((k) => from[k]).bind(null, key),
|
|
17
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
//#endregion
|
|
27
|
+
let _verdaccio_core = require("@verdaccio/core");
|
|
28
|
+
let bcryptjs = require("bcryptjs");
|
|
29
|
+
bcryptjs = __toESM(bcryptjs);
|
|
30
|
+
let pg = require("pg");
|
|
31
|
+
let _renown_sdk = require("@renown/sdk");
|
|
32
|
+
//#region src/auth/pg-store.ts
|
|
33
|
+
/** Build a real Postgres pool from a connection string. */
|
|
34
|
+
function createPgPool(databaseUrl) {
|
|
35
|
+
return new pg.Pool({ connectionString: databaseUrl });
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Postgres-backed AuthStore. Two small tables:
|
|
39
|
+
* - registry_users(username PK, password_hash, created_at)
|
|
40
|
+
* - registry_package_owners(package_name PK, owners text[], claimed_at)
|
|
41
|
+
*
|
|
42
|
+
* Ownership claim is race-free: `INSERT ... ON CONFLICT DO NOTHING` means the
|
|
43
|
+
* first publisher wins atomically; the follow-up read returns the actual
|
|
44
|
+
* owners so a losing racer is denied.
|
|
45
|
+
*
|
|
46
|
+
* Takes an already-built `pg.Pool` (tests inject a pg-mem pool cast to Pool).
|
|
47
|
+
*/
|
|
48
|
+
function createPgStore(pool) {
|
|
49
|
+
let initialized = null;
|
|
50
|
+
return {
|
|
51
|
+
init() {
|
|
52
|
+
initialized ??= (async () => {
|
|
53
|
+
await pool.query(`
|
|
54
|
+
CREATE TABLE IF NOT EXISTS registry_users (
|
|
55
|
+
username text PRIMARY KEY,
|
|
56
|
+
password_hash text NOT NULL,
|
|
57
|
+
created_at timestamptz NOT NULL DEFAULT now()
|
|
58
|
+
)`);
|
|
59
|
+
await pool.query(`
|
|
60
|
+
CREATE TABLE IF NOT EXISTS registry_package_owners (
|
|
61
|
+
package_name text PRIMARY KEY,
|
|
62
|
+
owners text[] NOT NULL,
|
|
63
|
+
claimed_at timestamptz NOT NULL DEFAULT now()
|
|
64
|
+
)`);
|
|
65
|
+
})();
|
|
66
|
+
return initialized;
|
|
67
|
+
},
|
|
68
|
+
async getUser(username) {
|
|
69
|
+
const row = (await pool.query("SELECT password_hash FROM registry_users WHERE username = $1", [username])).rows[0];
|
|
70
|
+
return row ? { passwordHash: row.password_hash } : null;
|
|
71
|
+
},
|
|
72
|
+
async createUser(username, passwordHash) {
|
|
73
|
+
try {
|
|
74
|
+
await pool.query("INSERT INTO registry_users (username, password_hash) VALUES ($1, $2)", [username, passwordHash]);
|
|
75
|
+
return true;
|
|
76
|
+
} catch (err) {
|
|
77
|
+
if (err.code === "23505") return false;
|
|
78
|
+
throw err;
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
async getOwners(pkg) {
|
|
82
|
+
return (await pool.query("SELECT owners FROM registry_package_owners WHERE package_name = $1", [pkg])).rows[0]?.owners ?? null;
|
|
83
|
+
},
|
|
84
|
+
async getOwnersFor(pkgs) {
|
|
85
|
+
if (pkgs.length === 0) return {};
|
|
86
|
+
const placeholders = pkgs.map((_, i) => `$${i + 1}`).join(",");
|
|
87
|
+
const res = await pool.query(`SELECT package_name, owners FROM registry_package_owners WHERE package_name IN (${placeholders})`, pkgs);
|
|
88
|
+
const out = {};
|
|
89
|
+
for (const row of res.rows) out[row.package_name] = row.owners;
|
|
90
|
+
return out;
|
|
91
|
+
},
|
|
92
|
+
async claimOwner(pkg, username) {
|
|
93
|
+
await pool.query(`INSERT INTO registry_package_owners (package_name, owners)
|
|
94
|
+
VALUES ($1, ARRAY[$2]::text[])
|
|
95
|
+
ON CONFLICT (package_name) DO NOTHING`, [pkg, username]);
|
|
96
|
+
return (await pool.query("SELECT owners FROM registry_package_owners WHERE package_name = $1", [pkg])).rows[0]?.owners ?? [];
|
|
97
|
+
},
|
|
98
|
+
close() {
|
|
99
|
+
return pool.end();
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
104
|
+
//#region src/auth/store-handoff.ts
|
|
105
|
+
const REGISTRY_KEY = Symbol.for("@powerhousedao/registry:auth-store-handoff");
|
|
106
|
+
function registry() {
|
|
107
|
+
const g = globalThis;
|
|
108
|
+
return g[REGISTRY_KEY] ??= /* @__PURE__ */ new Map();
|
|
109
|
+
}
|
|
110
|
+
/** Resolve a stashed store by token (undefined if unknown). */
|
|
111
|
+
function takeAuthStore(token) {
|
|
112
|
+
return registry().get(token)?.store;
|
|
113
|
+
}
|
|
114
|
+
/** Record that the plugin fully constructed with this token's store. */
|
|
115
|
+
function markStoreLoaded(token) {
|
|
116
|
+
const entry = registry().get(token);
|
|
117
|
+
if (entry) entry.loaded = true;
|
|
118
|
+
}
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region src/auth/renown-verifier.ts
|
|
121
|
+
function createRenownVerifier(config) {
|
|
122
|
+
const ttl = config.cacheTtlMs ?? 6e4;
|
|
123
|
+
const maxEntries = 1e3;
|
|
124
|
+
const cache = /* @__PURE__ */ new Map();
|
|
125
|
+
return async (token) => {
|
|
126
|
+
if (ttl > 0) {
|
|
127
|
+
const cached = cache.get(token);
|
|
128
|
+
if (cached) {
|
|
129
|
+
if (cached.expiresAt > Date.now()) return cached.did;
|
|
130
|
+
cache.delete(token);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const did = (await (0, _renown_sdk.verifyAuthCredential)(token, {
|
|
134
|
+
audience: config.publicUrl,
|
|
135
|
+
renownUrl: config.renownUrl
|
|
136
|
+
}))?.did;
|
|
137
|
+
if (ttl > 0 && did) {
|
|
138
|
+
cache.set(token, {
|
|
139
|
+
did,
|
|
140
|
+
expiresAt: Date.now() + ttl
|
|
141
|
+
});
|
|
142
|
+
if (cache.size > maxEntries) {
|
|
143
|
+
const oldest = cache.keys().next().value;
|
|
144
|
+
if (oldest !== void 0) cache.delete(oldest);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return did;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
function renownApiJwtMiddleware(verify, helpers) {
|
|
151
|
+
return (req, res, next) => {
|
|
152
|
+
const anon = helpers.createAnonymousRemoteUser();
|
|
153
|
+
req.remote_user = anon;
|
|
154
|
+
res.locals.remote_user = anon;
|
|
155
|
+
const header = req.headers.authorization;
|
|
156
|
+
const token = header && /^Bearer /i.test(header) ? header.slice(7).trim() : void 0;
|
|
157
|
+
if (!token) {
|
|
158
|
+
next();
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
req.pause();
|
|
162
|
+
verify(token).then((did) => {
|
|
163
|
+
if (did) {
|
|
164
|
+
const user = helpers.createRemoteUser(did, ["renown"]);
|
|
165
|
+
req.remote_user = user;
|
|
166
|
+
res.locals.remote_user = user;
|
|
167
|
+
}
|
|
168
|
+
}).catch(() => void 0).finally(() => {
|
|
169
|
+
req.resume();
|
|
170
|
+
next();
|
|
171
|
+
});
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/auth/registry-auth-plugin.ts
|
|
176
|
+
const BCRYPT_ROUNDS = 10;
|
|
177
|
+
function internal(err) {
|
|
178
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
179
|
+
return _verdaccio_core.errorUtils.getInternalError(msg);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Pure plugin factory over an injected store — the unit-testable core.
|
|
183
|
+
* `registryAuthPlugin` below wires it to a real Postgres store.
|
|
184
|
+
*/
|
|
185
|
+
function createRegistryAuthPlugin(store, renownVerifier) {
|
|
186
|
+
const ready = store.init();
|
|
187
|
+
return {
|
|
188
|
+
authenticate(user, password, cb) {
|
|
189
|
+
ready.then(() => store.getUser(user)).then((rec) => {
|
|
190
|
+
if (!rec) return cb(null, false);
|
|
191
|
+
if (!bcryptjs.default.compareSync(password, rec.passwordHash)) return cb(_verdaccio_core.errorUtils.getUnauthorized("bad username or password"));
|
|
192
|
+
return cb(null, [user]);
|
|
193
|
+
}).catch((err) => cb(internal(err)));
|
|
194
|
+
},
|
|
195
|
+
adduser(user, password, cb) {
|
|
196
|
+
ready.then(() => store.createUser(user, bcryptjs.default.hashSync(password, BCRYPT_ROUNDS))).then((created) => {
|
|
197
|
+
if (!created) return cb(_verdaccio_core.errorUtils.getConflict("username already registered"));
|
|
198
|
+
return cb(null, true);
|
|
199
|
+
}).catch((err) => cb(internal(err)));
|
|
200
|
+
},
|
|
201
|
+
allow_access(_user, _pkg, cb) {
|
|
202
|
+
cb(null, true);
|
|
203
|
+
},
|
|
204
|
+
allow_publish(user, pkg, cb) {
|
|
205
|
+
const name = pkg.name ?? "";
|
|
206
|
+
const username = user.name;
|
|
207
|
+
if (!username) return cb(_verdaccio_core.errorUtils.getForbidden("authentication required to publish"));
|
|
208
|
+
ready.then(() => store.claimOwner(name, username)).then((owners) => {
|
|
209
|
+
if (owners.includes(username)) return cb(null, true);
|
|
210
|
+
return cb(_verdaccio_core.errorUtils.getForbidden(`not authorized to publish "${name}" (owned by another user)`));
|
|
211
|
+
}).catch((err) => cb(internal(err)));
|
|
212
|
+
},
|
|
213
|
+
allow_unpublish(user, pkg, cb) {
|
|
214
|
+
const name = pkg.name ?? "";
|
|
215
|
+
const username = user.name;
|
|
216
|
+
if (!username) return cb(_verdaccio_core.errorUtils.getForbidden("authentication required to unpublish"));
|
|
217
|
+
ready.then(() => store.getOwners(name)).then((owners) => {
|
|
218
|
+
if (owners && owners.includes(username)) return cb(null, true);
|
|
219
|
+
return cb(_verdaccio_core.errorUtils.getForbidden(`not authorized to unpublish "${name}"`));
|
|
220
|
+
}).catch((err) => cb(internal(err)));
|
|
221
|
+
},
|
|
222
|
+
...renownVerifier ? { apiJWTmiddleware(helpers) {
|
|
223
|
+
return renownApiJwtMiddleware(renownVerifier, helpers);
|
|
224
|
+
} } : {}
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
/** Verdaccio plugin entry. The loader calls this factory (or `new`s the
|
|
228
|
+
* default export — both return the plugin object). */
|
|
229
|
+
function registryAuthPlugin(config) {
|
|
230
|
+
const plugin = createRegistryAuthPlugin((config.storeToken ? takeAuthStore(config.storeToken) : void 0) ?? createPgStore(createPgPool(config.databaseUrl ?? (() => {
|
|
231
|
+
throw new Error("registry-auth plugin requires a databaseUrl (or a store token)");
|
|
232
|
+
})())), config.publicUrl ? createRenownVerifier({
|
|
233
|
+
publicUrl: config.publicUrl,
|
|
234
|
+
renownUrl: config.renownUrl
|
|
235
|
+
}) : void 0);
|
|
236
|
+
if (config.storeToken) markStoreLoaded(config.storeToken);
|
|
237
|
+
return plugin;
|
|
238
|
+
}
|
|
239
|
+
//#endregion
|
|
240
|
+
exports.createRegistryAuthPlugin = createRegistryAuthPlugin;
|
|
241
|
+
exports.default = registryAuthPlugin;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powerhousedao/registry",
|
|
3
|
-
"version": "6.2.2-dev.
|
|
3
|
+
"version": "6.2.2-dev.11",
|
|
4
4
|
"description": "Powerhouse package registry — resolves and manages document model package dependencies within the Powerhouse ecosystem.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -29,21 +29,27 @@
|
|
|
29
29
|
"author": "",
|
|
30
30
|
"license": "ISC",
|
|
31
31
|
"dependencies": {
|
|
32
|
+
"@verdaccio/core": "8.0.0-next-8.37",
|
|
32
33
|
"@verdaccio/signature": "8.0.0-next-8.29",
|
|
34
|
+
"bcryptjs": "^2.4.3",
|
|
33
35
|
"cmd-ts": "0.15.0",
|
|
34
36
|
"express": "^4.22.1",
|
|
35
37
|
"find-up": "^8.0.0",
|
|
38
|
+
"pg": "8.18.0",
|
|
36
39
|
"tar": "^7.5.11",
|
|
37
40
|
"verdaccio": "^6.5.0",
|
|
38
41
|
"verdaccio-aws-s3-storage": "^10.4.0",
|
|
39
|
-
"@powerhousedao/shared": "6.2.2-dev.
|
|
40
|
-
"@renown/sdk": "6.2.2-dev.
|
|
42
|
+
"@powerhousedao/shared": "6.2.2-dev.11",
|
|
43
|
+
"@renown/sdk": "6.2.2-dev.11"
|
|
41
44
|
},
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"tsdown": "0.21.1",
|
|
47
|
+
"@types/bcryptjs": "^2.4.6",
|
|
44
48
|
"@types/express": "^4.17.21",
|
|
45
49
|
"@types/node": "25.2.3",
|
|
50
|
+
"@types/pg": "8.16.0",
|
|
46
51
|
"@types/supertest": "^6.0.2",
|
|
52
|
+
"pg-mem": "^3.0.14",
|
|
47
53
|
"supertest": "^7.1.0",
|
|
48
54
|
"vitest": "4.1.1"
|
|
49
55
|
},
|