@lamalibre/create-lamaste 4.0.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/scripts/lamaste-priv +21 -2
- package/src/lib/oidc-issuer-key.js +124 -0
- package/src/lib/relay-services.js +4 -0
- package/src/lib/service-config.js +16 -0
- 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/authentication.md +45 -1
- 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 +6 -5
- 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/overview.md +111 -105
- package/vendor/docs/04-api-reference/registry-and-oidc.md +207 -0
- 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 +6 -5
- package/vendor/docs/06-reference/config-files.md +103 -45
- 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/_index.json +5 -0
- package/vendor/docs/gatekeeper-design.md +8 -8
- package/vendor/docs/identity-directory-design.md +443 -0
- 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/bin/lamaste-server.js +10 -1
- package/vendor/server/package.json +4 -3
- package/vendor/server/src/commands/plugins.js +200 -158
- package/vendor/server/src/commands/registry-credential.js +142 -0
- package/vendor/server/src/panel-api.js +86 -39
- 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 +5 -2
- package/vendor/serverd/src/index.js +11 -0
- package/vendor/serverd/src/lib/authelia.js +11 -0
- package/vendor/serverd/src/lib/migrations/state/0005_plugin_hosting.sql +3 -0
- package/vendor/serverd/src/lib/plugin-oidc.js +233 -0
- package/vendor/serverd/src/lib/plugins.js +37 -5
- package/vendor/serverd/src/lib/registry-credential.js +145 -0
- package/vendor/serverd/src/routes/management/agents.js +81 -0
- package/vendor/serverd/src/routes/management/plugins.js +65 -2
- package/vendor/serverd/src/routes/management/registry-credential.js +170 -0
- package/vendor/serverd/src/routes/management/storage.js +10 -2
- package/vendor/serverd/src/routes/management/users.js +31 -0
- package/vendor/serverd/src/routes/management.js +2 -0
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
getPluginCapabilities,
|
|
9
9
|
} from '../../lib/plugins.js';
|
|
10
10
|
import { setPluginCapabilities, loadAgentRegistry } from '../../lib/mtls.js';
|
|
11
|
+
import { listPluginOidcClients, rotatePluginOidcClientSecret } from '../../lib/plugin-oidc.js';
|
|
11
12
|
import {
|
|
12
13
|
readPushInstallConfig,
|
|
13
14
|
readPushInstallSessions,
|
|
@@ -31,6 +32,14 @@ const PluginNameParamSchema = z.object({
|
|
|
31
32
|
.regex(/^[a-z0-9-]+$/, 'Plugin name must contain only lowercase letters, numbers, and hyphens'),
|
|
32
33
|
});
|
|
33
34
|
|
|
35
|
+
const OidcClientIdParamSchema = z.object({
|
|
36
|
+
clientId: z
|
|
37
|
+
.string()
|
|
38
|
+
.min(1)
|
|
39
|
+
.max(100)
|
|
40
|
+
.regex(/^[a-z0-9][a-z0-9-]*$/, 'Client id must be lowercase letters, numbers and hyphens'),
|
|
41
|
+
});
|
|
42
|
+
|
|
34
43
|
const InstallBodySchema = z.object({
|
|
35
44
|
packageName: z
|
|
36
45
|
.string()
|
|
@@ -188,10 +197,12 @@ export default async function pluginRoutes(fastify, _opts) {
|
|
|
188
197
|
const body = InstallBodySchema.parse(request.body);
|
|
189
198
|
|
|
190
199
|
try {
|
|
191
|
-
|
|
200
|
+
// `oidcClients` carries the secrets of confidential OIDC clients this
|
|
201
|
+
// install created — shown once, never stored in the registry row.
|
|
202
|
+
const { oidcClients, ...plugin } = await installPlugin(body.packageName, request.log);
|
|
192
203
|
// Refresh in-memory capability list so new plugin caps are valid immediately
|
|
193
204
|
setPluginCapabilities(await getPluginCapabilities());
|
|
194
|
-
return { ok: true, plugin };
|
|
205
|
+
return { ok: true, plugin, oidcClients };
|
|
195
206
|
} catch (err) {
|
|
196
207
|
const statusCode = err.statusCode || 500;
|
|
197
208
|
return reply.code(statusCode).send({
|
|
@@ -273,6 +284,58 @@ export default async function pluginRoutes(fastify, _opts) {
|
|
|
273
284
|
},
|
|
274
285
|
);
|
|
275
286
|
|
|
287
|
+
// ------------------------------------------------------------------
|
|
288
|
+
// GET /plugins/:name/oidc-clients — the OIDC clients a plugin declares
|
|
289
|
+
//
|
|
290
|
+
// Metadata only: a confidential client's secret is shown by the install
|
|
291
|
+
// or enable response that created it, and by rotate-secret — never here.
|
|
292
|
+
// ------------------------------------------------------------------
|
|
293
|
+
fastify.get(
|
|
294
|
+
'/plugins/:name/oidc-clients',
|
|
295
|
+
{
|
|
296
|
+
preHandler: fastify.requireRole(['admin']),
|
|
297
|
+
},
|
|
298
|
+
async (request, reply) => {
|
|
299
|
+
const params = PluginNameParamSchema.parse(request.params);
|
|
300
|
+
const registry = await readPlugins();
|
|
301
|
+
if (!registry.plugins.some((p) => p.name === params.name)) {
|
|
302
|
+
return reply.code(404).send({ error: `Plugin "${params.name}" not found` });
|
|
303
|
+
}
|
|
304
|
+
return { clients: await listPluginOidcClients(params.name) };
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// ------------------------------------------------------------------
|
|
309
|
+
// POST /plugins/:name/oidc-clients/:clientId/rotate-secret
|
|
310
|
+
//
|
|
311
|
+
// Replaces a confidential client's secret in the store and in Authelia.
|
|
312
|
+
// The new secret is in the response and nowhere else.
|
|
313
|
+
// ------------------------------------------------------------------
|
|
314
|
+
fastify.post(
|
|
315
|
+
'/plugins/:name/oidc-clients/:clientId/rotate-secret',
|
|
316
|
+
{
|
|
317
|
+
preHandler: fastify.requireRole(['admin']),
|
|
318
|
+
config: { rateLimit: { max: 10, timeWindow: '1 minute' } },
|
|
319
|
+
},
|
|
320
|
+
async (request, reply) => {
|
|
321
|
+
const params = PluginNameParamSchema.parse(request.params);
|
|
322
|
+
const { clientId } = OidcClientIdParamSchema.parse(request.params);
|
|
323
|
+
const registry = await readPlugins();
|
|
324
|
+
if (!registry.plugins.some((p) => p.name === params.name)) {
|
|
325
|
+
return reply.code(404).send({ error: `Plugin "${params.name}" not found` });
|
|
326
|
+
}
|
|
327
|
+
try {
|
|
328
|
+
const rotated = await rotatePluginOidcClientSecret(params.name, clientId, request.log);
|
|
329
|
+
return { ok: true, ...rotated };
|
|
330
|
+
} catch (err) {
|
|
331
|
+
const statusCode = err.statusCode || 500;
|
|
332
|
+
return reply.code(statusCode).send({
|
|
333
|
+
error: err.message || 'Failed to rotate the client secret',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
|
|
276
339
|
// ===========================================================================
|
|
277
340
|
// Push install routes
|
|
278
341
|
// ===========================================================================
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Admin management of the panel's registry (Feria) service credential.
|
|
3
|
+
*
|
|
4
|
+
* GET /api/registry-credential what is configured — never a token
|
|
5
|
+
* PUT /api/registry-credential store a credential after verifying the
|
|
6
|
+
* whole chain against the registry
|
|
7
|
+
* (admin scope required)
|
|
8
|
+
* DELETE /api/registry-credential remove it
|
|
9
|
+
*
|
|
10
|
+
* `auth.kind` is `token` (a pasted long-lived Feria token) or `oidc` (the
|
|
11
|
+
* panel's own `lamaste-server` client on Authelia: client-credentials grant,
|
|
12
|
+
* Feria login). For `oidc` the client is written to Authelia before the
|
|
13
|
+
* chain is verified; if verification fails it is removed again, and the
|
|
14
|
+
* error says what Feria's `auth.json` needs.
|
|
15
|
+
*
|
|
16
|
+
* With a credential in place, agents obtain short-lived read tokens through
|
|
17
|
+
* `GET /api/agents/me/registry-credential` for plugin installs.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { z } from 'zod';
|
|
21
|
+
import {
|
|
22
|
+
LAMASTE_SERVER_OIDC_CLIENT_ID,
|
|
23
|
+
RegistryCredentialError,
|
|
24
|
+
summariseRegistryCredential,
|
|
25
|
+
} from '@lamalibre/lamaste/server';
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
clearRegistryCredentialConfig,
|
|
29
|
+
readRegistryCredentialConfig,
|
|
30
|
+
verifyRegistryCredential,
|
|
31
|
+
writeRegistryCredentialConfig,
|
|
32
|
+
} from '../../lib/registry-credential.js';
|
|
33
|
+
import { syncOidcClients } from '../../lib/plugin-oidc.js';
|
|
34
|
+
|
|
35
|
+
const FeriaTokenSchema = z
|
|
36
|
+
.string()
|
|
37
|
+
.min(20)
|
|
38
|
+
.max(512)
|
|
39
|
+
.regex(/^feria_[A-Za-z0-9_-]+$/, 'token must be a Feria registry token');
|
|
40
|
+
|
|
41
|
+
const AuthSchema = z.discriminatedUnion('kind', [
|
|
42
|
+
z.object({ kind: z.literal('token'), token: FeriaTokenSchema }),
|
|
43
|
+
z.object({ kind: z.literal('oidc') }),
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
const PutSchema = z
|
|
47
|
+
.object({
|
|
48
|
+
registryUrl: z.string().url(),
|
|
49
|
+
scope: z
|
|
50
|
+
.string()
|
|
51
|
+
.regex(/^@[a-z0-9][a-z0-9-]*$/)
|
|
52
|
+
.optional(),
|
|
53
|
+
auth: AuthSchema.optional(),
|
|
54
|
+
/** The first release's shape — a token at the top level. */
|
|
55
|
+
token: FeriaTokenSchema.optional(),
|
|
56
|
+
})
|
|
57
|
+
.superRefine((body, ctx) => {
|
|
58
|
+
if (!body.auth && !body.token) {
|
|
59
|
+
ctx.addIssue({
|
|
60
|
+
code: z.ZodIssueCode.custom,
|
|
61
|
+
path: ['auth'],
|
|
62
|
+
message: 'auth is required: { kind: "token", token } or { kind: "oidc" }',
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/** The stored `auth` for a request body. */
|
|
68
|
+
function authOf(body) {
|
|
69
|
+
if (body.auth?.kind === 'oidc') return { kind: 'oidc', clientId: LAMASTE_SERVER_OIDC_CLIENT_ID };
|
|
70
|
+
if (body.auth?.kind === 'token') return { kind: 'token', token: body.auth.token };
|
|
71
|
+
return { kind: 'token', token: body.token };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export default async function registryCredentialRoutes(fastify) {
|
|
75
|
+
fastify.get(
|
|
76
|
+
'/registry-credential',
|
|
77
|
+
{ preHandler: fastify.requireRole(['admin']), config: { rateLimit: {} } },
|
|
78
|
+
async () => {
|
|
79
|
+
const config = await readRegistryCredentialConfig();
|
|
80
|
+
if (!config) return { configured: false };
|
|
81
|
+
return { configured: true, ...summariseRegistryCredential(config) };
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
fastify.put(
|
|
86
|
+
'/registry-credential',
|
|
87
|
+
{
|
|
88
|
+
preHandler: fastify.requireRole(['admin']),
|
|
89
|
+
config: { rateLimit: { max: 10, timeWindow: '1 minute' } },
|
|
90
|
+
},
|
|
91
|
+
async (request, reply) => {
|
|
92
|
+
const body = PutSchema.parse(request.body);
|
|
93
|
+
const auth = authOf(body);
|
|
94
|
+
const candidate = { registryUrl: body.registryUrl, auth };
|
|
95
|
+
|
|
96
|
+
// The panel's own client must exist in Authelia (with a secret in the
|
|
97
|
+
// store) before the chain can be exercised.
|
|
98
|
+
if (auth.kind === 'oidc') {
|
|
99
|
+
try {
|
|
100
|
+
const synced = await syncOidcClients(request.log, {
|
|
101
|
+
registryAuth: { registryUrl: body.registryUrl },
|
|
102
|
+
});
|
|
103
|
+
if (!synced) {
|
|
104
|
+
return reply.code(503).send({ error: 'Complete onboarding (set a domain) first' });
|
|
105
|
+
}
|
|
106
|
+
} catch (err) {
|
|
107
|
+
return reply.code(500).send({
|
|
108
|
+
error: `Could not register the ${LAMASTE_SERVER_OIDC_CLIENT_ID} client in Authelia: ${err.message}`,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
let verified;
|
|
114
|
+
try {
|
|
115
|
+
verified = await verifyRegistryCredential(candidate);
|
|
116
|
+
} catch (err) {
|
|
117
|
+
if (auth.kind === 'oidc') {
|
|
118
|
+
// Leave Authelia as the stored credential says — the candidate is
|
|
119
|
+
// not kept.
|
|
120
|
+
await syncOidcClients(request.log).catch((syncErr) =>
|
|
121
|
+
request.log.warn({ err: syncErr.message }, 'Failed to roll back the OIDC client'),
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
const status = err instanceof RegistryCredentialError ? err.statusCode : 502;
|
|
125
|
+
return reply.code(status).send({ error: err.message });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const config = {
|
|
129
|
+
provider: 'feria',
|
|
130
|
+
registryUrl: body.registryUrl,
|
|
131
|
+
scope: body.scope ?? '@lamalibre',
|
|
132
|
+
auth,
|
|
133
|
+
owner: verified.owner,
|
|
134
|
+
configuredAt: new Date().toISOString(),
|
|
135
|
+
};
|
|
136
|
+
await writeRegistryCredentialConfig(config);
|
|
137
|
+
// The stored credential now decides whether Authelia carries the client.
|
|
138
|
+
await syncOidcClients(request.log).catch((err) =>
|
|
139
|
+
request.log.warn(
|
|
140
|
+
{ err: err.message },
|
|
141
|
+
'Failed to sync OIDC clients after credential change',
|
|
142
|
+
),
|
|
143
|
+
);
|
|
144
|
+
request.log.info(
|
|
145
|
+
{ registryUrl: body.registryUrl, owner: verified.owner, auth: auth.kind },
|
|
146
|
+
'Registry credential configured',
|
|
147
|
+
);
|
|
148
|
+
return { configured: true, ...summariseRegistryCredential(config) };
|
|
149
|
+
},
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
fastify.delete(
|
|
153
|
+
'/registry-credential',
|
|
154
|
+
{ preHandler: fastify.requireRole(['admin']), config: { rateLimit: {} } },
|
|
155
|
+
async (request) => {
|
|
156
|
+
const removed = await clearRegistryCredentialConfig();
|
|
157
|
+
if (removed) {
|
|
158
|
+
request.log.info('Registry credential removed');
|
|
159
|
+
// Removes the lamaste-server client and its secret if it was in use.
|
|
160
|
+
await syncOidcClients(request.log).catch((err) =>
|
|
161
|
+
request.log.warn(
|
|
162
|
+
{ err: err.message },
|
|
163
|
+
'Failed to sync OIDC clients after credential removal',
|
|
164
|
+
),
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
return { configured: false, removed };
|
|
168
|
+
},
|
|
169
|
+
);
|
|
170
|
+
}
|
|
@@ -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);
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
writeTotpToDatabase,
|
|
10
10
|
} from '../../lib/authelia.js';
|
|
11
11
|
import { removeGrantsForUser } from '../../lib/user-access.js';
|
|
12
|
+
import { revokeUserRegistryTokens } from '../../lib/registry-credential.js';
|
|
12
13
|
import { bumpUserEpoch } from '../../lib/config.js';
|
|
13
14
|
import { gatekeeperRequest } from '../../lib/gatekeeper-client.js';
|
|
14
15
|
|
|
@@ -156,6 +157,7 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
156
157
|
}
|
|
157
158
|
|
|
158
159
|
const user = usersData.users[username];
|
|
160
|
+
const groupsBefore = Array.isArray(user.groups) ? [...user.groups] : [];
|
|
159
161
|
|
|
160
162
|
if (body.displayname !== undefined) {
|
|
161
163
|
user.displayname = body.displayname;
|
|
@@ -188,6 +190,23 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
188
190
|
request.log.warn(err, 'Failed to reload Authelia after user update');
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
// Registry scopes are granted at login from feria-* groups; losing a
|
|
194
|
+
// group must not leave a valid token behind. Best effort — the user
|
|
195
|
+
// update itself is already done.
|
|
196
|
+
const warnings = [];
|
|
197
|
+
if (body.groups !== undefined) {
|
|
198
|
+
try {
|
|
199
|
+
await revokeUserRegistryTokens(
|
|
200
|
+
username,
|
|
201
|
+
{ before: groupsBefore, after: user.groups || [] },
|
|
202
|
+
request.log,
|
|
203
|
+
);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
request.log.warn({ username, err: err.message }, 'Failed to revoke registry tokens');
|
|
206
|
+
warnings.push(`registry tokens were not revoked: ${err.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
191
210
|
return {
|
|
192
211
|
ok: true,
|
|
193
212
|
user: {
|
|
@@ -196,6 +215,7 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
196
215
|
email: user.email,
|
|
197
216
|
groups: user.groups || [],
|
|
198
217
|
},
|
|
218
|
+
...(warnings.length > 0 ? { warnings } : {}),
|
|
199
219
|
};
|
|
200
220
|
},
|
|
201
221
|
);
|
|
@@ -252,6 +272,17 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
252
272
|
// ---------------------------------------------------------------
|
|
253
273
|
const warnings = [];
|
|
254
274
|
|
|
275
|
+
// 0. Registry (Feria) tokens the user holds — their groups are gone.
|
|
276
|
+
try {
|
|
277
|
+
await revokeUserRegistryTokens(username, { deleted: true }, request.log);
|
|
278
|
+
} catch (err) {
|
|
279
|
+
request.log.warn(
|
|
280
|
+
{ username, err: err.message },
|
|
281
|
+
'Cascade: registry token revocation failed',
|
|
282
|
+
);
|
|
283
|
+
warnings.push(`registry tokens were not revoked: ${err.message}`);
|
|
284
|
+
}
|
|
285
|
+
|
|
255
286
|
// 1. Legacy user-plugin-access.json grants (pre-gatekeeper migration)
|
|
256
287
|
try {
|
|
257
288
|
const removed = await removeGrantsForUser(username, request.log);
|
|
@@ -14,6 +14,7 @@ import identityRoutes from './management/identity.js';
|
|
|
14
14
|
import storageRoutes from './management/storage.js';
|
|
15
15
|
import gatekeeperProxyRoutes from './management/gatekeeper-proxy.js';
|
|
16
16
|
import agentsRoutes from './management/agents.js';
|
|
17
|
+
import registryCredentialRoutes from './management/registry-credential.js';
|
|
17
18
|
|
|
18
19
|
export default async function managementRoutes(fastify, _opts) {
|
|
19
20
|
fastify.addHook('onRequest', managementOnly());
|
|
@@ -33,4 +34,5 @@ export default async function managementRoutes(fastify, _opts) {
|
|
|
33
34
|
await fastify.register(storageRoutes);
|
|
34
35
|
await fastify.register(gatekeeperProxyRoutes);
|
|
35
36
|
await fastify.register(agentsRoutes);
|
|
37
|
+
await fastify.register(registryCredentialRoutes);
|
|
36
38
|
}
|