@pellux/goodvibes-sdk 0.18.36 → 0.18.37
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/_internal/platform/runtime/bootstrap-services.d.ts +14 -0
- package/dist/_internal/platform/runtime/bootstrap-services.d.ts.map +1 -1
- package/dist/_internal/platform/runtime/bootstrap-services.js +2 -2
- package/dist/_internal/platform/security/user-auth.d.ts.map +1 -1
- package/dist/_internal/platform/security/user-auth.js +55 -0
- package/dist/_internal/platform/version.js +1 -1
- package/package.json +1 -1
|
@@ -22,6 +22,20 @@ interface ServiceFactories {
|
|
|
22
22
|
startupTimeoutMs?: number;
|
|
23
23
|
probeDaemonPortInUse?: (host: string, port: number) => Promise<boolean>;
|
|
24
24
|
probeHttpListenerPortInUse?: (host: string, port: number) => Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* Shared bearer token the daemon should accept on inbound HTTP requests.
|
|
27
|
+
* When set, `daemon.enable()` registers this token and requests carrying
|
|
28
|
+
* `Authorization: Bearer <token>` authenticate without a login session.
|
|
29
|
+
* Surfaces that generate companion-app pairing tokens should pass the token
|
|
30
|
+
* here so the embedded daemon accepts scanned QR credentials.
|
|
31
|
+
*/
|
|
32
|
+
sharedDaemonToken?: string;
|
|
33
|
+
/**
|
|
34
|
+
* Shared bearer token for the HTTP listener (webhook-style surfaces).
|
|
35
|
+
* Independent from `sharedDaemonToken`; different surfaces may issue
|
|
36
|
+
* different tokens, or both may share the same bearer.
|
|
37
|
+
*/
|
|
38
|
+
sharedHttpListenerToken?: string;
|
|
25
39
|
}
|
|
26
40
|
export interface HostServicesHandle {
|
|
27
41
|
readonly daemonServer: DaemonService | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bootstrap-services.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/runtime/bootstrap-services.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAOrD,UAAU,aAAa;IACrB,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,OAAO,6BAA6B,EAAE,uBAAuB,EAAE,CAAC;CACvH;AAED,UAAU,mBAAmB;IAC3B,MAAM,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACnE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,UAAU,gBAAgB;IACxB,kBAAkB,CAAC,EAAE,CACnB,UAAU,EAAE,eAAe,EAC3B,QAAQ,EAAE,eAAe,CAAC,sBAAsB,CAAC,EACjD,eAAe,EAAE,eAAe,KAC7B,aAAa,CAAC;IACnB,kBAAkB,CAAC,EAAE,CACnB,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,eAAe,CAAC,sBAAsB,CAAC,EACjD,aAAa,EAAE,eAAe,CAAC,eAAe,CAAC,KAC5C,mBAAmB,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxE,0BAA0B,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"bootstrap-services.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/runtime/bootstrap-services.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAC7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAOrD,UAAU,aAAa;IACrB,MAAM,CAAC,MAAM,EAAE;QAAE,MAAM,EAAE,OAAO,CAAA;KAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAC7D,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,OAAO,6BAA6B,EAAE,uBAAuB,EAAE,CAAC;CACvH;AAED,UAAU,mBAAmB;IAC3B,MAAM,CAAC,MAAM,EAAE;QAAE,YAAY,EAAE,OAAO,CAAA;KAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACnE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,UAAU,gBAAgB;IACxB,kBAAkB,CAAC,EAAE,CACnB,UAAU,EAAE,eAAe,EAC3B,QAAQ,EAAE,eAAe,CAAC,sBAAsB,CAAC,EACjD,eAAe,EAAE,eAAe,KAC7B,aAAa,CAAC;IACnB,kBAAkB,CAAC,EAAE,CACnB,cAAc,EAAE,cAAc,EAC9B,QAAQ,EAAE,eAAe,CAAC,sBAAsB,CAAC,EACjD,aAAa,EAAE,eAAe,CAAC,eAAe,CAAC,KAC5C,mBAAmB,CAAC;IACzB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oBAAoB,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IACxE,0BAA0B,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC9E;;;;;;OAMG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B;;;;OAIG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,YAAY,EAAE,aAAa,GAAG,IAAI,CAAC;IAC5C,QAAQ,CAAC,YAAY,EAAE,mBAAmB,GAAG,IAAI,CAAC;IAClD,4BAA4B,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,OAAO,6BAA6B,EAAE,uBAAuB,EAAE,CAAC;IACtH,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,CACD,GAAG,EACC,eAAe,GACf,qBAAqB,GACrB,mBAAmB,GACnB,mBAAmB,GACnB,mBAAmB,GACnB,mBAAmB,GACtB,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;CAC9B;AAmDD,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,UAAU,EAAE,eAAe,EAC3B,cAAc,EAAE,cAAc,EAC9B,SAAS,EAAE,gBAAgB,YAAK,EAChC,eAAe,EAAE,eAAe,GAC/B,OAAO,CAAC,kBAAkB,CAAC,CAoF7B;AAED,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CAAC;AACxD,MAAM,MAAM,sBAAsB,GAAG,kBAAkB,CAAC;AACxD,eAAO,MAAM,qBAAqB,0BAAoB,CAAC"}
|
|
@@ -70,7 +70,7 @@ export async function startHostServices(config, runtimeBus, hookDispatcher, fact
|
|
|
70
70
|
}
|
|
71
71
|
else {
|
|
72
72
|
daemonServer = createDaemonServer(runtimeBus, sharedUserAuth, runtimeServices);
|
|
73
|
-
daemonServer.enable({ daemon: true });
|
|
73
|
+
daemonServer.enable({ daemon: true }, factories.sharedDaemonToken);
|
|
74
74
|
try {
|
|
75
75
|
const service = daemonServer;
|
|
76
76
|
const result = await startWithTimeout('Daemon server', () => service.start(), startupTimeoutMs, () => service.stop());
|
|
@@ -99,7 +99,7 @@ export async function startHostServices(config, runtimeBus, hookDispatcher, fact
|
|
|
99
99
|
}
|
|
100
100
|
else {
|
|
101
101
|
httpListener = createHttpListener(hookDispatcher, sharedUserAuth, runtimeServices.configManager);
|
|
102
|
-
httpListener.enable({ httpListener: true });
|
|
102
|
+
httpListener.enable({ httpListener: true }, factories.sharedHttpListenerToken);
|
|
103
103
|
try {
|
|
104
104
|
const service = httpListener;
|
|
105
105
|
const result = await startWithTimeout('HTTP listener', () => service.start(), startupTimeoutMs, () => service.stop());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user-auth.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/security/user-auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"user-auth.d.ts","sourceRoot":"","sources":["../../../../src/_internal/platform/security/user-auth.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,UAAU,cAAc;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,QAAQ,EAAE,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAUD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;CACnC;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,uBAAuB,EAAE,MAAM,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,0BAA0B,EAAE,OAAO,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAC1C,QAAQ,CAAC,QAAQ,EAAE,SAAS,iBAAiB,EAAE,CAAC;CACjD;AAwJD,qBAAa,eAAe;IAC1B,OAAO,CAAC,KAAK,CAA+B;IAC5C,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAAS;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAU;gBAE3B,MAAM,EAAE,cAAc;IAqBlC,MAAM,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAI7C,YAAY,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI;IAMjE,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI;IAShD,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW;IAY5C,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI;IAUlD,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;IAIrC,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM;IAW/C,SAAS,IAAI,cAAc,EAAE;IAS7B,YAAY,IAAI,iBAAiB,EAAE;IAOnC,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,SAAS,MAAM,EAAc,GAAG,cAAc;IAejG,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO;IAarC,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI;IAgB5D,OAAO,IAAI,iBAAiB;IAc5B,4BAA4B,IAAI,OAAO;IAMvC,0BAA0B,IAAI,MAAM;IAIpC,OAAO,CAAC,oBAAoB;IAS5B,OAAO,CAAC,OAAO;CAIhB"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { randomBytes, scryptSync, timingSafeEqual } from 'crypto';
|
|
2
2
|
import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
|
+
import { logger } from '../utils/logger.js';
|
|
4
5
|
const DEFAULT_SESSION_TTL_MS = 3_600_000;
|
|
5
6
|
const SCRYPT_KEY_LENGTH = 64;
|
|
6
7
|
function toBase64(value) {
|
|
@@ -85,6 +86,54 @@ function loadOrBootstrapUsers(filePath, credentialPath) {
|
|
|
85
86
|
writeBootstrapCredentialFile(credentialPath, username, initialPassword);
|
|
86
87
|
return users;
|
|
87
88
|
}
|
|
89
|
+
function readBootstrapCredentialFile(filePath) {
|
|
90
|
+
try {
|
|
91
|
+
if (!existsSync(filePath))
|
|
92
|
+
return null;
|
|
93
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
94
|
+
let username;
|
|
95
|
+
let password;
|
|
96
|
+
for (const rawLine of raw.split('\n')) {
|
|
97
|
+
const line = rawLine.trim();
|
|
98
|
+
if (line.startsWith('username='))
|
|
99
|
+
username = line.slice('username='.length);
|
|
100
|
+
else if (line.startsWith('password='))
|
|
101
|
+
password = line.slice('password='.length);
|
|
102
|
+
}
|
|
103
|
+
if (!username || !password)
|
|
104
|
+
return null;
|
|
105
|
+
return { username, password };
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return null;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Detects when the bootstrap credential file has drifted from the persisted
|
|
113
|
+
* user store — typically after someone manually edits `auth-bootstrap.txt`
|
|
114
|
+
* without rotating the password through the UserAuthManager. The daemon's
|
|
115
|
+
* /login route will reject the edited password because the hash in
|
|
116
|
+
* `auth-users.json` no longer matches. Left silent, this wastes hours of
|
|
117
|
+
* debugging; loudly warning is cheap.
|
|
118
|
+
*/
|
|
119
|
+
function detectBootstrapCredentialDrift(users, credentialPath, userStorePath) {
|
|
120
|
+
const credential = readBootstrapCredentialFile(credentialPath);
|
|
121
|
+
if (!credential)
|
|
122
|
+
return;
|
|
123
|
+
const storedUser = users.find((user) => user.username === credential.username);
|
|
124
|
+
if (!storedUser) {
|
|
125
|
+
logger.warn('Bootstrap credential file references a user not present in the auth store; /login with this username will fail', { credentialPath, userStorePath, username: credential.username });
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!verifyPassword(credential.password, storedUser.passwordHash)) {
|
|
129
|
+
logger.warn('Bootstrap credential file password does not match the stored hash; /login with this password will fail. Rotate the password via UserAuthManager.rotatePassword() or regenerate the credential by deleting both files so they are re-created in sync.', {
|
|
130
|
+
credentialPath,
|
|
131
|
+
userStorePath,
|
|
132
|
+
username: credential.username,
|
|
133
|
+
hint: 'Manual edits to auth-bootstrap.txt do not update auth-users.json. The bootstrap file is an output, not an input.',
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
88
137
|
export class UserAuthManager {
|
|
89
138
|
users = new Map();
|
|
90
139
|
sessions = new Map();
|
|
@@ -105,6 +154,12 @@ export class UserAuthManager {
|
|
|
105
154
|
for (const user of seedUsers) {
|
|
106
155
|
this.users.set(user.username, user);
|
|
107
156
|
}
|
|
157
|
+
// Only run drift detection when we own the file-backed store — test
|
|
158
|
+
// configs that pass explicit `users` opt out of filesystem bootstrap and
|
|
159
|
+
// should not trigger spurious warnings.
|
|
160
|
+
if (this.persistUsers) {
|
|
161
|
+
detectBootstrapCredentialDrift(seedUsers, this.bootstrapCredentialPath, this.userStorePath);
|
|
162
|
+
}
|
|
108
163
|
}
|
|
109
164
|
static hashPassword(password) {
|
|
110
165
|
return hashPassword(password);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
let version = '0.18.
|
|
3
|
+
let version = '0.18.37';
|
|
4
4
|
try {
|
|
5
5
|
const pkg = JSON.parse(readFileSync(join(import.meta.dir, '..', '..', 'package.json'), 'utf-8'));
|
|
6
6
|
version = pkg.version ?? version;
|