@lamalibre/create-lamaste 4.1.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/vendor/docs/00-introduction/how-it-works.md +2 -2
- package/vendor/docs/00-introduction/quickstart.md +5 -5
- package/vendor/docs/00-introduction/what-is-lamaste.md +5 -5
- package/vendor/docs/01-concepts/certificates.md +3 -3
- package/vendor/docs/01-concepts/mtls.md +2 -2
- package/vendor/docs/01-concepts/security-model.md +1 -1
- package/vendor/docs/01-concepts/tickets.md +2 -2
- package/vendor/docs/01-concepts/tunneling.md +4 -4
- package/vendor/docs/02-guides/agent-setup.md +3 -3
- package/vendor/docs/02-guides/certificate-management.md +2 -2
- package/vendor/docs/02-guides/cloud-provisioning.md +3 -3
- package/vendor/docs/02-guides/desktop-app-setup.md +10 -10
- package/vendor/docs/02-guides/installation.md +3 -3
- package/vendor/docs/02-guides/onboarding.md +2 -2
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
- package/vendor/docs/03-architecture/installer-flow.md +2 -2
- package/vendor/docs/03-architecture/installer.md +4 -4
- package/vendor/docs/03-architecture/management-flow.md +1 -1
- package/vendor/docs/03-architecture/overview.md +5 -5
- package/vendor/docs/03-architecture/panel-client.md +6 -6
- package/vendor/docs/03-architecture/panel-server.md +2 -2
- package/vendor/docs/03-architecture/state-management.md +2 -2
- package/vendor/docs/03-architecture/system-overview.md +1 -1
- package/vendor/docs/04-api-reference/onboarding.md +1 -1
- package/vendor/docs/04-api-reference/registry-and-oidc.md +26 -1
- package/vendor/docs/05-operations/backup-and-restore.md +1 -1
- package/vendor/docs/05-operations/uninstalling.md +1 -1
- package/vendor/docs/05-operations/upgrades.md +5 -5
- package/vendor/docs/06-reference/config-files.md +1 -1
- package/vendor/docs/06-reference/glossary.md +3 -3
- package/vendor/docs/06-reference/installer-flags.md +13 -13
- package/vendor/docs/06-reference/ports-and-services.md +1 -1
- package/vendor/docs/06-reference/troubleshooting.md +8 -8
- package/vendor/docs/gatekeeper-design.md +8 -8
- package/vendor/docs/identity-directory-design.md +2 -2
- package/vendor/docs/package.json +1 -1
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
- package/vendor/gatekeeper/package.json +1 -1
- package/vendor/server/package.json +2 -2
- package/vendor/server/src/commands/plugins.js +123 -168
- package/vendor/server/src/panel-api.js +4 -2
- package/vendor/server-ui/dist/assets/{index-B0nWuUyL.js → index-B5Wp8lu-.js} +26 -26
- package/vendor/server-ui/dist/index.html +1 -1
- package/vendor/serverd/package.json +2 -2
- package/vendor/serverd/src/lib/migrations/state/0005_plugin_hosting.sql +3 -0
- package/vendor/serverd/src/lib/plugin-oidc.js +33 -16
- package/vendor/serverd/src/lib/plugins.js +11 -3
- package/vendor/serverd/src/routes/management/storage.js +10 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>Lamaste</title>
|
|
7
|
-
<script type="module" crossorigin src="/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/assets/index-B5Wp8lu-.js"></script>
|
|
8
8
|
<link rel="stylesheet" crossorigin href="/assets/index-BqWWFlTU.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body style="background-color: #09090b">
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lamalibre/lamaste-serverd",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0",
|
|
4
4
|
"description": "Lamaste server daemon — REST API and plugin host",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
6
6
|
"publishConfig": {
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"@fastify/rate-limit": "^10.3.0",
|
|
30
30
|
"@fastify/static": "^9.1.1",
|
|
31
31
|
"@fastify/websocket": "^11.0.0",
|
|
32
|
-
"@lamalibre/lamaste": "^4.
|
|
32
|
+
"@lamalibre/lamaste": "^4.2.0",
|
|
33
33
|
"bcryptjs": "^3.0.3",
|
|
34
34
|
"execa": "^9.6.1",
|
|
35
35
|
"fastify": "^5.8.4",
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Two sources declare clients on the relay's Authelia:
|
|
5
5
|
*
|
|
6
|
-
* - every installed
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* - every installed plugin may declare them in its manifest (`oidcClients`,
|
|
7
|
+
* see the core `ManifestSchema`); only the clients of enabled plugins are
|
|
8
|
+
* served. Hosting does not matter: an agent-hosted plugin (`hosting:
|
|
9
|
+
* "agent"`, never imported by the panel) registers its clients exactly as
|
|
10
|
+
* a server plugin does — the manifest is all this reads;
|
|
9
11
|
* - the panel itself declares the confidential `lamaste-server` client
|
|
10
12
|
* when the registry credential uses `auth.kind: 'oidc'`.
|
|
11
13
|
*
|
|
@@ -94,6 +96,30 @@ async function collectAllClients(overrides = {}) {
|
|
|
94
96
|
return [...(await collectPluginOidcClients()), ...lamasteOwnClient(registryAuth)];
|
|
95
97
|
}
|
|
96
98
|
|
|
99
|
+
/**
|
|
100
|
+
* What Authelia should serve and which secrets the store should hold, from
|
|
101
|
+
* the stored state: every installed plugin's clients (an installed plugin
|
|
102
|
+
* keeps its confidential-client secret while disabled), the clients of
|
|
103
|
+
* enabled plugins served, and the panel's own scopes when its client is.
|
|
104
|
+
* Pure with respect to Authelia — nothing is written.
|
|
105
|
+
*
|
|
106
|
+
* @param {{ registryAuth?: { registryUrl: string } | null }} [overrides] see collectAllClients
|
|
107
|
+
* @returns {Promise<{
|
|
108
|
+
* confidential: Array<{ id: string, plugin: string }>,
|
|
109
|
+
* served: Array<import('@lamalibre/lamaste/server').AutheliaOidcClient>,
|
|
110
|
+
* scopes: Record<string, { claims: readonly string[] }>,
|
|
111
|
+
* }>}
|
|
112
|
+
*/
|
|
113
|
+
export async function planOidcClients(overrides = {}) {
|
|
114
|
+
const all = await collectAllClients(overrides);
|
|
115
|
+
const served = all.filter((c) => c.enabled).map(({ enabled: _enabled, ...client }) => client);
|
|
116
|
+
return {
|
|
117
|
+
confidential: all.filter((c) => c.confidential).map((c) => ({ id: c.id, plugin: c.plugin })),
|
|
118
|
+
served,
|
|
119
|
+
scopes: served.some((c) => c.plugin === 'lamaste') ? LAMASTE_REGISTRY_OIDC_SCOPES : {},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
97
123
|
/**
|
|
98
124
|
* Sync Authelia's OIDC clients and the secret store with the plugins and
|
|
99
125
|
* the registry credential. Returns what was done, or `null` when the panel
|
|
@@ -115,11 +141,8 @@ export async function syncOidcClients(logger, overrides = {}) {
|
|
|
115
141
|
return null;
|
|
116
142
|
}
|
|
117
143
|
|
|
118
|
-
const
|
|
119
|
-
const reconciled = await reconcileOidcClientSecrets(
|
|
120
|
-
STATE_DIR,
|
|
121
|
-
all.filter((c) => c.confidential).map((c) => ({ id: c.id, plugin: c.plugin })),
|
|
122
|
-
);
|
|
144
|
+
const { confidential, served, scopes } = await planOidcClients(overrides);
|
|
145
|
+
const reconciled = await reconcileOidcClientSecrets(STATE_DIR, confidential);
|
|
123
146
|
if (reconciled.created.length > 0 || reconciled.removed.length > 0) {
|
|
124
147
|
logger.info(
|
|
125
148
|
{ created: reconciled.created.map((c) => c.id), removed: reconciled.removed },
|
|
@@ -127,8 +150,6 @@ export async function syncOidcClients(logger, overrides = {}) {
|
|
|
127
150
|
);
|
|
128
151
|
}
|
|
129
152
|
|
|
130
|
-
const served = all.filter((c) => c.enabled).map(({ enabled: _enabled, ...client }) => client);
|
|
131
|
-
const scopes = served.some((c) => c.plugin === 'lamaste') ? LAMASTE_REGISTRY_OIDC_SCOPES : {};
|
|
132
153
|
const result = await updateOidcProvider(domain, served, {
|
|
133
154
|
secretDigests: reconciled.digests,
|
|
134
155
|
scopes,
|
|
@@ -199,12 +220,8 @@ export async function rotatePluginOidcClientSecret(pluginName, clientId, logger)
|
|
|
199
220
|
const digests = await readOidcClientSecretDigests(STATE_DIR);
|
|
200
221
|
if (client.enabled) {
|
|
201
222
|
const domain = getConfig().domain;
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
await updateOidcProvider(domain, served, {
|
|
205
|
-
secretDigests: digests,
|
|
206
|
-
scopes: served.some((c) => c.plugin === 'lamaste') ? LAMASTE_REGISTRY_OIDC_SCOPES : {},
|
|
207
|
-
});
|
|
223
|
+
const { served, scopes } = await planOidcClients();
|
|
224
|
+
await updateOidcProvider(domain, served, { secretDigests: digests, scopes });
|
|
208
225
|
}
|
|
209
226
|
logger.info({ plugin: pluginName, clientId }, 'OIDC client secret rotated');
|
|
210
227
|
return {
|
|
@@ -14,6 +14,7 @@ import path from 'node:path';
|
|
|
14
14
|
import { z } from 'zod';
|
|
15
15
|
import { execa } from 'execa';
|
|
16
16
|
import {
|
|
17
|
+
DEFAULT_PLUGIN_HOSTING,
|
|
17
18
|
RESERVED_API_PREFIXES,
|
|
18
19
|
RESERVED_NAV_LABELS,
|
|
19
20
|
derivePluginRoute,
|
|
@@ -60,8 +61,8 @@ async function getStmts() {
|
|
|
60
61
|
insert: db.prepare(`
|
|
61
62
|
INSERT INTO plugins
|
|
62
63
|
(name, display_name, package_name, version, description, capabilities,
|
|
63
|
-
packages, panel, config, modes, status, installed_at, enabled_at)
|
|
64
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
64
|
+
packages, panel, config, modes, hosting, status, installed_at, enabled_at)
|
|
65
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
65
66
|
`),
|
|
66
67
|
deleteByName: db.prepare('DELETE FROM plugins WHERE name = ?'),
|
|
67
68
|
deleteAll: db.prepare('DELETE FROM plugins'),
|
|
@@ -86,7 +87,10 @@ async function getStmts() {
|
|
|
86
87
|
// JSON-encoded TEXT in storage. They round-trip through JSON.parse/stringify
|
|
87
88
|
// so callers see the same shape the JSON-backed module returned — including
|
|
88
89
|
// `undefined` for optional fields (mirroring the JSON object layout where
|
|
89
|
-
// missing keys are absent rather than null).
|
|
90
|
+
// missing keys are absent rather than null). `hosting` is plain TEXT
|
|
91
|
+
// ('server' | 'agent'; rows from before 4.2 default to 'server'): an
|
|
92
|
+
// agent-hosted plugin is installed (npm, manifest) like any other, but the
|
|
93
|
+
// plugin host never imports it — see `pluginHostPlugin`.
|
|
90
94
|
|
|
91
95
|
function rowToPlugin(row) {
|
|
92
96
|
const entry = {
|
|
@@ -94,6 +98,7 @@ function rowToPlugin(row) {
|
|
|
94
98
|
packageName: row.package_name,
|
|
95
99
|
version: row.version,
|
|
96
100
|
capabilities: row.capabilities ? JSON.parse(row.capabilities) : [],
|
|
101
|
+
hosting: row.hosting,
|
|
97
102
|
status: row.status,
|
|
98
103
|
installedAt: row.installed_at,
|
|
99
104
|
};
|
|
@@ -155,6 +160,7 @@ export async function writePlugins(data) {
|
|
|
155
160
|
nullableJson(p.panel),
|
|
156
161
|
nullableJson(p.config),
|
|
157
162
|
nullableJson(p.modes),
|
|
163
|
+
p.hosting ?? DEFAULT_PLUGIN_HOSTING,
|
|
158
164
|
p.status,
|
|
159
165
|
p.installedAt,
|
|
160
166
|
nullableString(p.enabledAt),
|
|
@@ -359,6 +365,7 @@ export function installPlugin(packageName, logger) {
|
|
|
359
365
|
panel: manifest.panel,
|
|
360
366
|
config: manifest.config,
|
|
361
367
|
modes: manifest.modes,
|
|
368
|
+
hosting: manifest.hosting,
|
|
362
369
|
status: 'disabled',
|
|
363
370
|
installedAt: new Date().toISOString(),
|
|
364
371
|
};
|
|
@@ -376,6 +383,7 @@ export function installPlugin(packageName, logger) {
|
|
|
376
383
|
nullableJson(entry.panel),
|
|
377
384
|
nullableJson(entry.config),
|
|
378
385
|
nullableJson(entry.modes),
|
|
386
|
+
entry.hosting,
|
|
379
387
|
entry.status,
|
|
380
388
|
entry.installedAt,
|
|
381
389
|
null,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { isAgentHostedPlugin } from '@lamalibre/lamaste';
|
|
2
3
|
import {
|
|
3
4
|
registerStorageServer,
|
|
4
5
|
removeStorageServer,
|
|
@@ -118,12 +119,19 @@ export default async function storageRoutes(fastify, _opts) {
|
|
|
118
119
|
|
|
119
120
|
// Validate plugin exists in registry
|
|
120
121
|
const registry = await readPlugins();
|
|
121
|
-
const
|
|
122
|
-
if (!
|
|
122
|
+
const plugin = registry.plugins.find((p) => p.name === body.pluginName);
|
|
123
|
+
if (!plugin) {
|
|
123
124
|
return reply.code(404).send({
|
|
124
125
|
error: `Plugin "${body.pluginName}" not found in registry`,
|
|
125
126
|
});
|
|
126
127
|
}
|
|
128
|
+
// A binding is handed to the plugin's server code when the panel
|
|
129
|
+
// mounts it; an agent-hosted plugin has none to receive it.
|
|
130
|
+
if (isAgentHostedPlugin(plugin)) {
|
|
131
|
+
return reply.code(400).send({
|
|
132
|
+
error: `Plugin "${body.pluginName}" is agent-hosted — storage binds to server-hosted plugins only`,
|
|
133
|
+
});
|
|
134
|
+
}
|
|
127
135
|
|
|
128
136
|
try {
|
|
129
137
|
const binding = await bindPluginStorage(body.pluginName, body.storageServerId);
|