@bongos/core 1.19.829 → 1.19.831
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/.bongos-core.json +27 -17
- package/docker-compose.yml +5 -2
- package/docs/module-api-changelog.md +4 -0
- package/docs/recipes/self-host.md +7 -1
- package/package-lock.json +2 -2
- package/package.json +1 -1
- package/src/bongos/auth-admission.js +16 -0
- package/src/bongos/auth.js +2 -1
- package/src/bongos/db-kernel.js +9 -0
- package/src/bongos/routes/auth.js +14 -1
- package/src/module-api.js +1 -1
- package/tests/compose_demo_credentials.mjs +30 -0
- package/tests/first_admin_unset_signin.mjs +80 -0
package/.bongos-core.json
CHANGED
|
@@ -2,22 +2,22 @@
|
|
|
2
2
|
"artifact": "bongos-core",
|
|
3
3
|
"manifest_schema": 1,
|
|
4
4
|
"generator": "scripts/gds/package-core.js",
|
|
5
|
-
"core_version": "1.19.
|
|
6
|
-
"core_contract": "1.19.
|
|
7
|
-
"source_commit": "
|
|
5
|
+
"core_version": "1.19.831",
|
|
6
|
+
"core_contract": "1.19.831",
|
|
7
|
+
"source_commit": "30cc1cbe10b2f1a3d1cb907ee320d18c78e23eb0",
|
|
8
8
|
"source_ref": "HEAD",
|
|
9
|
-
"built_at": "2026-09-
|
|
9
|
+
"built_at": "2026-09-17T15:15:18.923Z",
|
|
10
10
|
"redaction": {
|
|
11
11
|
"model": "docs-redacted+functional-verbatim",
|
|
12
12
|
"docs_redacted": 491,
|
|
13
13
|
"agent_docs_stubbed": 26,
|
|
14
|
-
"functional_verbatim":
|
|
14
|
+
"functional_verbatim": 2244,
|
|
15
15
|
"rules": 3,
|
|
16
16
|
"gate_literals": 3,
|
|
17
17
|
"gate": "passed"
|
|
18
18
|
},
|
|
19
|
-
"file_count":
|
|
20
|
-
"tree_sha256": "
|
|
19
|
+
"file_count": 2761,
|
|
20
|
+
"tree_sha256": "484014065f907c382c6fdbaadc2ae5bc3ff3af2f1b79a02119f7acf9d3ccd2fc",
|
|
21
21
|
"files": [
|
|
22
22
|
{
|
|
23
23
|
"path": ".claude/skills/ask-for-help/SKILL.md",
|
|
@@ -407,7 +407,7 @@
|
|
|
407
407
|
{
|
|
408
408
|
"path": "docker-compose.yml",
|
|
409
409
|
"mode": "0000644",
|
|
410
|
-
"sha256": "
|
|
410
|
+
"sha256": "098a2645a43876141b6c711362c2a93e134d1de7533cb6e17de9e80ccfb1327e"
|
|
411
411
|
},
|
|
412
412
|
{
|
|
413
413
|
"path": "docker-entrypoint.sh",
|
|
@@ -2552,7 +2552,7 @@
|
|
|
2552
2552
|
{
|
|
2553
2553
|
"path": "docs/module-api-changelog.md",
|
|
2554
2554
|
"mode": "0000644",
|
|
2555
|
-
"sha256": "
|
|
2555
|
+
"sha256": "eb4a8912413adbed5f2677ea334c86aeaac50dab4acc03e618dcff866286d505"
|
|
2556
2556
|
},
|
|
2557
2557
|
{
|
|
2558
2558
|
"path": "docs/modules-contract.md",
|
|
@@ -2782,7 +2782,7 @@
|
|
|
2782
2782
|
{
|
|
2783
2783
|
"path": "docs/recipes/self-host.md",
|
|
2784
2784
|
"mode": "0000644",
|
|
2785
|
-
"sha256": "
|
|
2785
|
+
"sha256": "2108b55f6e3264af35c2db885b5a98ff8cc9a5c5c68881d3f5ce8037db1bf159"
|
|
2786
2786
|
},
|
|
2787
2787
|
{
|
|
2788
2788
|
"path": "docs/recipes/server-bring-up.md",
|
|
@@ -7837,12 +7837,12 @@
|
|
|
7837
7837
|
{
|
|
7838
7838
|
"path": "package-lock.json",
|
|
7839
7839
|
"mode": "0000644",
|
|
7840
|
-
"sha256": "
|
|
7840
|
+
"sha256": "9ef226bf93c85c85d63babd32469fb3a3f4a69fdf2dc94f57d80c3b54b3e9c0d"
|
|
7841
7841
|
},
|
|
7842
7842
|
{
|
|
7843
7843
|
"path": "package.json",
|
|
7844
7844
|
"mode": "0000644",
|
|
7845
|
-
"sha256": "
|
|
7845
|
+
"sha256": "3a966e21858e712dbb9e8bedc875e4bc92095f1c4489b471221e51b42d701aed"
|
|
7846
7846
|
},
|
|
7847
7847
|
{
|
|
7848
7848
|
"path": "public-docs/index.html",
|
|
@@ -9467,7 +9467,7 @@
|
|
|
9467
9467
|
{
|
|
9468
9468
|
"path": "src/bongos/auth-admission.js",
|
|
9469
9469
|
"mode": "0000644",
|
|
9470
|
-
"sha256": "
|
|
9470
|
+
"sha256": "2d11e7b745a0476fa0643fd997c2e17a49709bae1ef93a7c4636f7efd18c7ab0"
|
|
9471
9471
|
},
|
|
9472
9472
|
{
|
|
9473
9473
|
"path": "src/bongos/auth-config.js",
|
|
@@ -9492,7 +9492,7 @@
|
|
|
9492
9492
|
{
|
|
9493
9493
|
"path": "src/bongos/auth.js",
|
|
9494
9494
|
"mode": "0000644",
|
|
9495
|
-
"sha256": "
|
|
9495
|
+
"sha256": "7e9b3c48e6d4bc3e0370e6ff4dba799691abeb02181d8910d0807651656f7d7e"
|
|
9496
9496
|
},
|
|
9497
9497
|
{
|
|
9498
9498
|
"path": "src/bongos/cascade-dispatch.js",
|
|
@@ -9507,7 +9507,7 @@
|
|
|
9507
9507
|
{
|
|
9508
9508
|
"path": "src/bongos/db-kernel.js",
|
|
9509
9509
|
"mode": "0000644",
|
|
9510
|
-
"sha256": "
|
|
9510
|
+
"sha256": "1fffc39fb9b70cd009d7d239729a9ff329cd41ca1f35b50b7c9e6b89aa83624e"
|
|
9511
9511
|
},
|
|
9512
9512
|
{
|
|
9513
9513
|
"path": "src/bongos/db.js",
|
|
@@ -9652,7 +9652,7 @@
|
|
|
9652
9652
|
{
|
|
9653
9653
|
"path": "src/bongos/routes/auth.js",
|
|
9654
9654
|
"mode": "0000644",
|
|
9655
|
-
"sha256": "
|
|
9655
|
+
"sha256": "66abf424a565cdcc4de85350774d91e93be826a3b01a23ad75fee1feb7977a3c"
|
|
9656
9656
|
},
|
|
9657
9657
|
{
|
|
9658
9658
|
"path": "src/bongos/routes/backup.js",
|
|
@@ -9762,7 +9762,7 @@
|
|
|
9762
9762
|
{
|
|
9763
9763
|
"path": "src/module-api.js",
|
|
9764
9764
|
"mode": "0000644",
|
|
9765
|
-
"sha256": "
|
|
9765
|
+
"sha256": "0e198cb1212d9a60178cb27ffed61166f60e097d3bed2afb564c9960b10c81e7"
|
|
9766
9766
|
},
|
|
9767
9767
|
{
|
|
9768
9768
|
"path": "src/module-loader/catalog.js",
|
|
@@ -10544,6 +10544,11 @@
|
|
|
10544
10544
|
"mode": "0000644",
|
|
10545
10545
|
"sha256": "3f9e3ea6d2b88cd6592ba7b0575908eb020a072e6ccdb8cdf908bfba65b5191c"
|
|
10546
10546
|
},
|
|
10547
|
+
{
|
|
10548
|
+
"path": "tests/compose_demo_credentials.mjs",
|
|
10549
|
+
"mode": "0000644",
|
|
10550
|
+
"sha256": "1d625746b022de4f4e23ce071e276ce5758e048f5997fce690ac8e301b6c6721"
|
|
10551
|
+
},
|
|
10547
10552
|
{
|
|
10548
10553
|
"path": "tests/conductor.mjs",
|
|
10549
10554
|
"mode": "0000644",
|
|
@@ -10969,6 +10974,11 @@
|
|
|
10969
10974
|
"mode": "0000644",
|
|
10970
10975
|
"sha256": "8006f4696e6bae21b84c9e8ec25cb7a7d34c5899f187d2ac59b2a6dcbb6f92d1"
|
|
10971
10976
|
},
|
|
10977
|
+
{
|
|
10978
|
+
"path": "tests/first_admin_unset_signin.mjs",
|
|
10979
|
+
"mode": "0000644",
|
|
10980
|
+
"sha256": "88b3d0c7fb278e560850f440ec6721655efb62bcafcd983deb7e9ddb25103b65"
|
|
10981
|
+
},
|
|
10972
10982
|
{
|
|
10973
10983
|
"path": "tests/first_run_engine_check.mjs",
|
|
10974
10984
|
"mode": "0000644",
|
package/docker-compose.yml
CHANGED
|
@@ -14,7 +14,10 @@ services:
|
|
|
14
14
|
image: postgres:16-alpine
|
|
15
15
|
environment:
|
|
16
16
|
POSTGRES_USER: cloudbongos
|
|
17
|
-
|
|
17
|
+
# Under 8 characters on purpose: the leak scan treats any 8+ character value of a
|
|
18
|
+
# secret-named variable as a live credential, and this file ships in the core
|
|
19
|
+
# (task 1002717). The db port is not published; this value never leaves the network.
|
|
20
|
+
POSTGRES_PASSWORD: demo-pw
|
|
18
21
|
POSTGRES_DB: cloudbongos
|
|
19
22
|
volumes:
|
|
20
23
|
- cloudbongos-db:/var/lib/postgresql/data
|
|
@@ -35,7 +38,7 @@ services:
|
|
|
35
38
|
PGHOST: db
|
|
36
39
|
PGPORT: "5432"
|
|
37
40
|
PGUSER: cloudbongos
|
|
38
|
-
PGPASSWORD:
|
|
41
|
+
PGPASSWORD: demo-pw
|
|
39
42
|
PGDATABASE: cloudbongos
|
|
40
43
|
# Bind to all interfaces inside the container so the published port works.
|
|
41
44
|
HOST: 0.0.0.0
|
|
@@ -2132,5 +2132,9 @@ is load-bearing: the script throws rather than guess if it is missing, and
|
|
|
2132
2132
|
landed since 1.19.827 with no explicit bump. run 35231300810. (task 1002620)
|
|
2133
2133
|
1.19.829 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2134
2134
|
landed since 1.19.828 with no explicit bump. run 35234300466. (task 1002620)
|
|
2135
|
+
1.19.830 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2136
|
+
landed since 1.19.829 with no explicit bump. run 35235005422. (task 1002620)
|
|
2137
|
+
1.19.831 — CI auto-patch (publish-on-merge, ADR 0161): carrier for merges
|
|
2138
|
+
landed since 1.19.830 with no explicit bump. run 35238952391. (task 1002620)
|
|
2135
2139
|
---------------------------------------------------------------------------
|
|
2136
2140
|
```
|
|
@@ -18,6 +18,10 @@ That builds the image, starts Postgres, applies the build-system migrations (gam
|
|
|
18
18
|
|
|
19
19
|
To stop and wipe: `docker compose down -v` (the `-v` drops the `cloudbongos-db` volume).
|
|
20
20
|
|
|
21
|
+
**Before your first sign-in, set `BONGOS_FIRST_ADMIN` to your GitHub login** (in `docker-compose.override.yml`, below). A new instance has no owner to approve anyone, so without it every sign-in is refused, yours included, and the page says so.
|
|
22
|
+
|
|
23
|
+
**Upgrading from a compose file older than task 1002717?** The demo database password changed from `cloudbongos` to `demo-pw`, and an existing volume keeps the old one. Either wipe it (`docker compose down -v`) or keep your data and update the password in place: `docker compose exec db psql -U cloudbongos -c "ALTER USER cloudbongos PASSWORD 'demo-pw'"`.
|
|
24
|
+
|
|
21
25
|
## Branding / configuring your instance
|
|
22
26
|
|
|
23
27
|
The image is intentionally vanilla. To make it yours, supply your own config at runtime by mounting it over the build:
|
|
@@ -35,7 +39,7 @@ Author `config/branding.json` from the neutral starter — see [the branding con
|
|
|
35
39
|
|
|
36
40
|
**Or scaffold it with `bongos init`** ([task #1204](https://example.com/builders#/task/1204)): a short interview (or `bongos init --from spec.json` for a hands-free / CI run) that writes both config files, **seeds your first version + its done-when goal**, and **files the human-only kickoff steps** (register a GitHub OAuth app, first owner sign-in, repo binding, admission) as real tracked tasks — so a fresh instance has a claimable queue on day one instead of an empty hall. It never invents your branding (it points you at the fill prompt for that). Preview the whole plan with `bongos init --dry-run`.
|
|
37
41
|
|
|
38
|
-
**Production hosting, optional (the `provisioning` module — [ADR 0111](../adr/0111-instance-hosting-provisioning-module.md) §3).** If you enable the `provisioning` module and answer the interview's hosting questions (shape — co-tenant `$0` or a dedicated droplet — bring-your-own domain, credential ref, tier), init turns those answers into a real **provisioning request** (a queued `provision` intent the control-plane runner drains) instead of only filing a manual "production topology" task. The two irreducibly-human legs — the GitHub OAuth app and the first owner sign-in — stay guided/manual. If the module isn't enabled on the target instance, init falls back to the manual production-topology task, so nothing is silently skipped.
|
|
42
|
+
**Production hosting, optional (the `provisioning` module — [ADR 0111](../adr/0111-instance-hosting-provisioning-module.md) §3).** If you enable the `provisioning` module and answer the interview's hosting questions (shape — co-tenant `$0` or a dedicated droplet — bring-your-own domain, credential ref, tier), init turns those answers into a real **provisioning request** (a queued `provision` intent the control-plane runner drains) instead of only filing a manual "production topology" task. **A queued request does nothing until a runner drains it:** on the control plane that is a systemd timer running `node scripts/gds/provision.js run-intents --apply`. This Docker image cannot run that today (no git in the image, systemd and a DigitalOcean token assumed; task 1002718), so on a compose self-host a new project waits indefinitely. The two irreducibly-human legs — the GitHub OAuth app and the first owner sign-in — stay guided/manual. If the module isn't enabled on the target instance, init falls back to the manual production-topology task, so nothing is silently skipped.
|
|
39
43
|
|
|
40
44
|
## Configuration surface
|
|
41
45
|
|
|
@@ -46,6 +50,8 @@ Author `config/branding.json` from the neutral starter — see [the branding con
|
|
|
46
50
|
| `MEDUSA_GDS_ONLY` | `1` = hard force-skip the OTB **instance-seed** migrations (`migrations/instance/`) — belt-and-suspenders over the default. A vanilla instance never wants OTB's versions/goals/criteria (task 1704 / [ADR 0105](../adr/0105-instance-seed-migrations-out-of-core.md)). | `1` |
|
|
47
51
|
| `GDS_APPLY_INSTANCE_SEEDS` | `1` = opt IN to applying the bundled OTB instance-seed migrations. Default (unset) = **skip** — so a self-host never inherits OTB scope-truth. Only the owning instance sets this, and only when rebuilding its DB from scratch (DR). | unset |
|
|
48
52
|
| `BONGOS_PUBLIC_ORIGIN` | public origin for OAuth callbacks etc. | `http://localhost:3000` |
|
|
53
|
+
| `BONGOS_FIRST_ADMIN` | GitHub login seated as the first Archon at their first sign-in. Unset, nobody can sign in, because nobody exists to approve them. | unset |
|
|
54
|
+
| `BONGOS_BUILDERS_ORIGIN` | origin the builders' hall is served on. The vanilla pack uses the public origin's host, which makes the hall the whole site: pages that only live on the apex, such as `/projects` and `/create`, return 404. Set a separate host (e.g. `http://builders.localhost:3000`) when you enable `provisioning`. | same host as the public origin |
|
|
49
55
|
|
|
50
56
|
(The env prefix is the instance's `envPrefix` from its branding pack — `BONGOS` for vanilla.)
|
|
51
57
|
|
package/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.831",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@bongos/core",
|
|
9
|
-
"version": "1.19.
|
|
9
|
+
"version": "1.19.831",
|
|
10
10
|
"license": "AGPL-3.0-or-later",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"express": "^4.21.2",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bongos/core",
|
|
3
|
-
"version": "1.19.
|
|
3
|
+
"version": "1.19.831",
|
|
4
4
|
"description": "Cloud Bongos — the AI-first build platform core (GDS + platform surfaces + module system), installed as a versioned dependency (ADR 0108).",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"main": "src/platform-server.js",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
const {
|
|
17
17
|
upsertBuilder,
|
|
18
18
|
seatFirstAdminIfUnclaimed,
|
|
19
|
+
archonExists,
|
|
19
20
|
createSession,
|
|
20
21
|
lookupSession,
|
|
21
22
|
getBuilderById,
|
|
@@ -308,6 +309,20 @@ async function webAdmissionStatus(login) {
|
|
|
308
309
|
return { admitted, expectedSoon: admitted || state.pending || isFirstAdmin };
|
|
309
310
|
}
|
|
310
311
|
|
|
312
|
+
// instanceNeedsFirstAdmin — true while no Archon exists AND no firstAdmin is
|
|
313
|
+
// configured: the vanilla self-host, where every sign-in (the operator's own
|
|
314
|
+
// included) is refused and the generic page told them to ask an owner who does not
|
|
315
|
+
// exist (task 1002717). A DB error answers false, leaving the generic page.
|
|
316
|
+
async function instanceNeedsFirstAdmin() {
|
|
317
|
+
if (firstAdminLogin()) return false;
|
|
318
|
+
try {
|
|
319
|
+
return !(await archonExists());
|
|
320
|
+
} catch (err) {
|
|
321
|
+
log.error('[auth] instanceNeedsFirstAdmin', err);
|
|
322
|
+
return false;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
311
326
|
// First-admin bootstrap (R62 / task 1201, ADR 0062). The configured firstAdmin
|
|
312
327
|
// login — an install-time env override (<PREFIX>_FIRST_ADMIN, incl. legacy) wins,
|
|
313
328
|
// else branding.firstAdmin. Empty/unset (the vanilla starter) means "no one is
|
|
@@ -456,6 +471,7 @@ async function readApplicantProfileFromHub(githubId) {
|
|
|
456
471
|
module.exports = {
|
|
457
472
|
firstAdminLogin,
|
|
458
473
|
firstTimeAdmissionBlocked,
|
|
474
|
+
instanceNeedsFirstAdmin,
|
|
459
475
|
readApplicantProfileFromHub,
|
|
460
476
|
// Re-exported, not defined here (task 1003579 moved it to project-door.js with
|
|
461
477
|
// the two knobs it composes against). auth.js, the doorway's api.joinabilityMode
|
package/src/bongos/auth.js
CHANGED
|
@@ -55,7 +55,7 @@ const ic = require('../instance-config');
|
|
|
55
55
|
const { branding, userAgent } = require('../branding');
|
|
56
56
|
|
|
57
57
|
const { SCOPE, authConfigured, grantedScopeCoversRepos, idpConfigured, idpEnabled, loadIdpConfig, scopeForWebFlow } = require('./auth-config.js');
|
|
58
|
-
const { firstAdminLogin, firstTimeAdmissionBlocked, joinabilityMode, maybeSeatFirstAdmin, membershipKind, openEnrollmentEnabled, readApplicantProfileFromHub, webAdmissionStatus } = require('./auth-admission.js');
|
|
58
|
+
const { firstAdminLogin, firstTimeAdmissionBlocked, instanceNeedsFirstAdmin, joinabilityMode, maybeSeatFirstAdmin, membershipKind, openEnrollmentEnabled, readApplicantProfileFromHub, webAdmissionStatus } = require('./auth-admission.js');
|
|
59
59
|
const { buildAuthorizeUrl, completeWebFlow, pollDeviceFlow, startDeviceFlow } = require('./auth-github.js');
|
|
60
60
|
const { buildIdpAuthorizeUrl, completeDeviceExchange, completeIdpFlow, idpAuthorizeUrl, loadHubPublicKey, logoutTokenJtiSeenAndRecord, verifyHubAssertion, verifyHubLogoutToken } = require('./auth-idp.js');
|
|
61
61
|
const { addDiscordGuildMember, buildDiscordAuthorizeUrl, discordAuthorizeUrl, discordBotConfigured, discordConfigured, exchangeDiscordCode, fetchDiscordUser, isValidDiscordUserId, loadDiscordBotToken, loadDiscordCreds, removeDiscordGuildMember } = require('./auth-discord.js');
|
|
@@ -895,6 +895,7 @@ module.exports = {
|
|
|
895
895
|
joinabilityMode,
|
|
896
896
|
readApplicantProfileFromHub,
|
|
897
897
|
firstTimeAdmissionBlocked,
|
|
898
|
+
instanceNeedsFirstAdmin,
|
|
898
899
|
webAdmissionStatus,
|
|
899
900
|
firstAdminLogin,
|
|
900
901
|
maybeSeatFirstAdmin,
|
package/src/bongos/db-kernel.js
CHANGED
|
@@ -212,6 +212,14 @@ async function seatFirstAdminIfUnclaimed(builderId, login) {
|
|
|
212
212
|
return rowCount > 0;
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
+
// archonExists — has anyone been seated as Archon yet? The same predicate the seat
|
|
216
|
+
// above guards on, so "no owner yet" is exactly the window in which a configured
|
|
217
|
+
// firstAdmin would still be seated (task 1002717).
|
|
218
|
+
async function archonExists() {
|
|
219
|
+
const { rows } = await pool.query(`SELECT EXISTS (SELECT 1 FROM builders WHERE rank = 'archon') AS has`);
|
|
220
|
+
return !!(rows[0] && rows[0].has);
|
|
221
|
+
}
|
|
222
|
+
|
|
215
223
|
// getBuilderByGithubId — minimal identity read by stable GitHub numeric id (login
|
|
216
224
|
// can change; github_id is the stable key). Backs the approval-gated web sign-in.
|
|
217
225
|
async function getBuilderByGithubId(githubId) {
|
|
@@ -378,6 +386,7 @@ module.exports = {
|
|
|
378
386
|
SESSION_TTL_MS,
|
|
379
387
|
upsertBuilder,
|
|
380
388
|
seatFirstAdminIfUnclaimed,
|
|
389
|
+
archonExists,
|
|
381
390
|
getBuilderByGithubId,
|
|
382
391
|
hasInvitedAccessRequest,
|
|
383
392
|
accessStateByLogin,
|
|
@@ -40,7 +40,7 @@ const { pairStore } = require('../app-pair');
|
|
|
40
40
|
const { publicOrigin, parseCookie, validateOrRespond } = require('./_helpers');
|
|
41
41
|
const { accountExistenceReadRateLimit } = require('../middleware/rate-limit');
|
|
42
42
|
const { branding } = require('../../branding');
|
|
43
|
-
const { resolveEnv } = require('../../instance-config');
|
|
43
|
+
const { resolveEnv, envPrefix } = require('../../instance-config');
|
|
44
44
|
const seams = require('../../module-seams');
|
|
45
45
|
// task 1003208: structured logging — was console.*. A core file logs through
|
|
46
46
|
// the kernel logger directly; only MODULES go via api.logger (the doorway).
|
|
@@ -738,6 +738,19 @@ module.exports = function buildAuthRouter() {
|
|
|
738
738
|
if (expectedSoon) {
|
|
739
739
|
return res.status(202).type('text/html').send(renderAccessPendingPage(err.login, recoveryStartHref(req)));
|
|
740
740
|
}
|
|
741
|
+
// task 1002717: with no owner and no FIRST_ADMIN, everyone is refused, the
|
|
742
|
+
// operator included. Name the fix instead of pointing at an owner who does not exist.
|
|
743
|
+
if (await auth.instanceNeedsFirstAdmin()) {
|
|
744
|
+
return res.status(403).type('text/html').send(renderAuthErrorPage(
|
|
745
|
+
'This instance has no owner yet',
|
|
746
|
+
`Nobody can sign in until an owner is configured. If you run this instance, set ${envPrefix()}_FIRST_ADMIN to your GitHub login, restart it, and sign in again. That account becomes the first Archon and can approve everyone after it.`,
|
|
747
|
+
{
|
|
748
|
+
subtitle: 'A new instance needs its owner set before anyone can sign in.',
|
|
749
|
+
startHref: recoveryStartHref(req),
|
|
750
|
+
note: 'The self-host recipe (docs/recipes/self-host.md) lists this setting.',
|
|
751
|
+
},
|
|
752
|
+
));
|
|
753
|
+
}
|
|
741
754
|
// The founder-shaped branch (only reachable with open enrollment OFF):
|
|
742
755
|
// a stranger who was on their way to create a project is told the
|
|
743
756
|
// approval brings them back to it — the return cookie says where.
|
package/src/module-api.js
CHANGED
|
@@ -71,7 +71,7 @@ const { responsibilityFor, ROLE_RESPONSIBILITIES } = require('./role-responsibil
|
|
|
71
71
|
// there. scripts/gds/bump-version.js still rewrites the literal below; it appends
|
|
72
72
|
// the entry to that file. Look for a version's history there, not here.
|
|
73
73
|
// ---------------------------------------------------------------------------
|
|
74
|
-
const CORE_VERSION = '1.19.
|
|
74
|
+
const CORE_VERSION = '1.19.831'; // CI auto-patch carrier (ADR 0161); changelog: docs/module-api-changelog.md
|
|
75
75
|
|
|
76
76
|
// A namespaced logger so a module's log lines are attributable + consistent.
|
|
77
77
|
// Usage: const log = api.logger('dev-box'); log.info('mounted');
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// tests/compose_demo_credentials.mjs — the self-host demo credentials never read as a live secret (task 1002717).
|
|
2
|
+
//
|
|
3
|
+
// docker-compose.yml used PGPASSWORD=cloudbongos. The env-secret leak scan
|
|
4
|
+
// (collectEnvSecrets) treats any secret-named env value of 8+ characters as a live
|
|
5
|
+
// credential, and package-core refuses to build when a functional file contains one.
|
|
6
|
+
// Inside the compose container that meant every file mentioning the brand name, and
|
|
7
|
+
// docker-compose.yml itself ships in the core, so any long demo value trips it.
|
|
8
|
+
//
|
|
9
|
+
// Run: node --test tests/compose_demo_credentials.mjs
|
|
10
|
+
|
|
11
|
+
import assert from 'node:assert/strict';
|
|
12
|
+
import { test } from 'node:test';
|
|
13
|
+
import fs from 'node:fs';
|
|
14
|
+
import { createRequire } from 'node:module';
|
|
15
|
+
|
|
16
|
+
const require = createRequire(import.meta.url);
|
|
17
|
+
const { collectEnvSecrets } = require('../modules/security/secret-scrub.js');
|
|
18
|
+
|
|
19
|
+
const compose = fs.readFileSync(new URL('../docker-compose.yml', import.meta.url), 'utf8');
|
|
20
|
+
const env = {};
|
|
21
|
+
for (const m of compose.matchAll(/^\s+([A-Z][A-Z0-9_]*):\s*"?([^"\r\n#]*?)"?\s*$/gm)) env[m[1]] = m[2];
|
|
22
|
+
|
|
23
|
+
test('the compose file declares the database password for both services', () => {
|
|
24
|
+
assert.ok(env.POSTGRES_PASSWORD, 'db service password');
|
|
25
|
+
assert.equal(env.PGPASSWORD, env.POSTGRES_PASSWORD, 'the app connects with the password the db was created with');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
test('no compose env value is collected as a live secret', () => {
|
|
29
|
+
assert.deepEqual(collectEnvSecrets(env), []);
|
|
30
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// tests/first_admin_unset_signin.mjs — a fresh instance with no owner says so (task 1002717).
|
|
2
|
+
//
|
|
3
|
+
// A vanilla docker-compose self-host configures no FIRST_ADMIN, so the very first
|
|
4
|
+
// sign-in (the operator's own) is refused, and the page it lands on said "ask the
|
|
5
|
+
// owner to confirm your request" on an instance that has no owner. The callback
|
|
6
|
+
// now asks auth.instanceNeedsFirstAdmin() first and, when it is true, names the fix.
|
|
7
|
+
//
|
|
8
|
+
// The stub goes into require.cache BEFORE auth.js loads: auth-admission destructures
|
|
9
|
+
// its db-kernel bindings at require time.
|
|
10
|
+
//
|
|
11
|
+
// Run: node --test tests/first_admin_unset_signin.mjs
|
|
12
|
+
|
|
13
|
+
import assert from 'node:assert/strict';
|
|
14
|
+
import { test } from 'node:test';
|
|
15
|
+
import fs from 'node:fs';
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
17
|
+
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
|
|
20
|
+
const kernelPath = require.resolve('../src/bongos/db-kernel.js');
|
|
21
|
+
const realKernel = require(kernelPath);
|
|
22
|
+
let archon = false;
|
|
23
|
+
let dbThrows = false;
|
|
24
|
+
require.cache[kernelPath].exports = {
|
|
25
|
+
...realKernel,
|
|
26
|
+
archonExists: async () => {
|
|
27
|
+
if (dbThrows) throw new Error('simulated DB outage');
|
|
28
|
+
return archon;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Any prefix resolveEnv reads — the instance's own and the legacy spellings.
|
|
33
|
+
for (const k of Object.keys(process.env)) if (k.endsWith('_FIRST_ADMIN')) delete process.env[k];
|
|
34
|
+
|
|
35
|
+
const auth = require('../src/bongos/auth.js');
|
|
36
|
+
|
|
37
|
+
test('precondition: this pack configures no firstAdmin', () => {
|
|
38
|
+
assert.equal(auth.firstAdminLogin(), null);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('no owner and no FIRST_ADMIN: the instance needs one', async () => {
|
|
42
|
+
archon = false;
|
|
43
|
+
assert.equal(await auth.instanceNeedsFirstAdmin(), true);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('an Archon exists: an established instance never shows the no-owner page', async () => {
|
|
47
|
+
archon = true;
|
|
48
|
+
assert.equal(await auth.instanceNeedsFirstAdmin(), false);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
test('FIRST_ADMIN is configured: the bootstrap will seat them, so no', async () => {
|
|
52
|
+
archon = false;
|
|
53
|
+
process.env.OTB_FIRST_ADMIN = 'founding-owner';
|
|
54
|
+
try {
|
|
55
|
+
assert.equal(await auth.instanceNeedsFirstAdmin(), false);
|
|
56
|
+
} finally {
|
|
57
|
+
delete process.env.OTB_FIRST_ADMIN;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('a DB error answers false, leaving the generic page', async () => {
|
|
62
|
+
dbThrows = true;
|
|
63
|
+
try {
|
|
64
|
+
assert.equal(await auth.instanceNeedsFirstAdmin(), false);
|
|
65
|
+
} finally {
|
|
66
|
+
dbThrows = false;
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('the callback checks for a missing owner before the generic not-approved pages', () => {
|
|
71
|
+
const src = fs.readFileSync(new URL('../src/bongos/routes/auth.js', import.meta.url), 'utf8').replace(/\r\n/g, '\n');
|
|
72
|
+
const waiting = src.indexOf('if (expectedSoon) {');
|
|
73
|
+
const noOwner = src.indexOf('if (await auth.instanceNeedsFirstAdmin()) {');
|
|
74
|
+
const inviteOnly = src.indexOf("if (effectiveJoinDoor() !== 'apply') {");
|
|
75
|
+
assert.ok(waiting > 0 && noOwner > 0 && inviteOnly > 0, 'all three branches present');
|
|
76
|
+
assert.ok(waiting < noOwner && noOwner < inviteOnly, 'waiting page, then no-owner page, then the refusals');
|
|
77
|
+
const branch = src.slice(noOwner, inviteOnly);
|
|
78
|
+
assert.match(branch, /'This instance has no owner yet'/);
|
|
79
|
+
assert.match(branch, /\$\{envPrefix\(\)\}_FIRST_ADMIN/, 'names the setting under this instance\'s own env prefix');
|
|
80
|
+
});
|