@lamalibre/create-lamaste 4.5.0 → 4.7.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/bundle-vendor.js +20 -3
- package/scripts/lamaste-priv +56 -5
- package/scripts/pin-release.js +98 -0
- package/src/index.js +8 -1
- package/src/lib/env.js +19 -0
- package/src/lib/service-config.js +7 -0
- package/src/tasks/redeploy.js +8 -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 +6 -2
- package/vendor/docs/01-concepts/authorization.md +4 -0
- package/vendor/docs/01-concepts/certificates.md +3 -3
- package/vendor/docs/01-concepts/dns-and-domains.md +9 -0
- package/vendor/docs/01-concepts/mtls.md +2 -2
- package/vendor/docs/01-concepts/security-model.md +28 -3
- 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 +5 -5
- 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/managing-users.md +5 -11
- package/vendor/docs/02-guides/onboarding.md +2 -2
- package/vendor/docs/02-guides/second-domain.md +82 -0
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +2 -2
- package/vendor/docs/02-guides/static-sites.md +4 -0
- 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/nginx-configuration.md +80 -12
- 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 +3 -3
- package/vendor/docs/03-architecture/system-overview.md +1 -1
- package/vendor/docs/04-api-reference/agent-hosted-plugins.md +116 -37
- package/vendor/docs/04-api-reference/app-host.md +3 -3
- package/vendor/docs/04-api-reference/domains.md +192 -0
- package/vendor/docs/04-api-reference/gatekeeper.md +54 -8
- package/vendor/docs/04-api-reference/lifecycle-hooks.md +4 -4
- package/vendor/docs/04-api-reference/onboarding.md +1 -1
- package/vendor/docs/04-api-reference/overview.md +9 -0
- package/vendor/docs/04-api-reference/registry-and-oidc.md +26 -13
- package/vendor/docs/04-api-reference/sites.md +9 -0
- package/vendor/docs/04-api-reference/tunnels.md +35 -28
- package/vendor/docs/04-api-reference/users.md +41 -29
- 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 +15 -6
- package/vendor/docs/06-reference/config-files.md +19 -15
- 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 +105 -19
- package/vendor/docs/external-members-design.md +816 -0
- package/vendor/docs/gatekeeper-design.md +8 -8
- package/vendor/docs/identity-directory-design.md +3 -3
- package/vendor/docs/multi-domain-relay-design.md +836 -0
- package/vendor/docs/package.json +1 -1
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +27 -27
- package/vendor/gatekeeper/dist/index.d.ts +3 -3
- package/vendor/gatekeeper/dist/index.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/index.js +2 -2
- package/vendor/gatekeeper/dist/index.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/authz.d.ts +9 -0
- package/vendor/gatekeeper/dist/lib/authz.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/authz.js +31 -0
- package/vendor/gatekeeper/dist/lib/authz.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/constants.d.ts +5 -2
- package/vendor/gatekeeper/dist/lib/constants.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/constants.js +5 -2
- package/vendor/gatekeeper/dist/lib/constants.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/grants.js +20 -13
- package/vendor/gatekeeper/dist/lib/grants.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0004_unconsume_access_grants.sql +8 -0
- package/vendor/gatekeeper/dist/lib/secret.d.ts +6 -0
- package/vendor/gatekeeper/dist/lib/secret.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/secret.js +12 -0
- package/vendor/gatekeeper/dist/lib/secret.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/templates.d.ts +20 -0
- package/vendor/gatekeeper/dist/lib/templates.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/templates.js +80 -14
- package/vendor/gatekeeper/dist/lib/templates.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/tunnels.d.ts +3 -6
- package/vendor/gatekeeper/dist/lib/tunnels.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/lib/tunnels.js +4 -16
- package/vendor/gatekeeper/dist/lib/tunnels.js.map +1 -1
- package/vendor/gatekeeper/dist/lib/types.d.ts +0 -19
- package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.d.ts +6 -1
- package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/index.js +56 -11
- package/vendor/gatekeeper/dist/server/index.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/authz.js +325 -55
- package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -1
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js +5 -2
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -1
- package/vendor/gatekeeper/package.json +2 -2
- package/vendor/server/bin/lamaste-server.js +7 -0
- package/vendor/server/package.json +2 -2
- package/vendor/server/src/commands/domains.js +214 -0
- package/vendor/server/src/commands/plugin-identity.js +2 -16
- package/vendor/server/src/commands/plugin-web-surface.js +269 -0
- package/vendor/server/src/commands/plugins.js +175 -14
- package/vendor/server/src/commands/registry-credential.js +10 -3
- package/vendor/server/src/commands/sites.js +18 -1
- package/vendor/server/src/commands/status.js +19 -0
- package/vendor/server/src/commands/tunnels.js +4 -1
- package/vendor/server/src/commands/users.js +8 -19
- package/vendor/server/src/domains.js +92 -0
- package/vendor/server/src/fail.js +21 -0
- package/vendor/server/src/oidc-client-grants.js +63 -0
- package/vendor/server/src/panel-api.js +15 -0
- package/vendor/server/src/plugin-approval.js +80 -1
- package/vendor/server/src/plugin-web-surface.js +307 -0
- package/vendor/server-ui/dist/assets/index-B8Tv_ocF.js +84 -0
- package/vendor/server-ui/dist/assets/index-Cb-ADsbB.css +1 -0
- package/vendor/server-ui/dist/index.html +2 -2
- package/vendor/serverd/package.json +2 -2
- package/vendor/serverd/src/index.js +50 -19
- package/vendor/serverd/src/lib/app-error.js +24 -0
- package/vendor/serverd/src/lib/audience.js +12 -3
- package/vendor/serverd/src/lib/authelia.js +341 -35
- package/vendor/serverd/src/lib/chisel-runtime.js +18 -6
- package/vendor/serverd/src/lib/chisel.js +10 -2
- package/vendor/serverd/src/lib/directory.js +67 -52
- package/vendor/serverd/src/lib/domains.js +1792 -0
- package/vendor/serverd/src/lib/gatekeeper-client.js +34 -7
- package/vendor/serverd/src/lib/hook-pipelines.js +8 -3
- package/vendor/serverd/src/lib/identity-sync.js +35 -0
- package/vendor/serverd/src/lib/migrations/state/0015_plugin_web_surface.sql +14 -0
- package/vendor/serverd/src/lib/migrations/state/0016_domains.sql +53 -0
- package/vendor/serverd/src/lib/migrations/state/0017_membership_kind.sql +15 -0
- package/vendor/serverd/src/lib/migrations/state/0018_domain_group_keys.sql +23 -0
- package/vendor/serverd/src/lib/nginx.js +576 -131
- package/vendor/serverd/src/lib/owned-groups.js +326 -0
- package/vendor/serverd/src/lib/plugin-domain.js +86 -0
- package/vendor/serverd/src/lib/plugin-hooks.js +40 -11
- package/vendor/serverd/src/lib/plugin-lifecycle.js +129 -5
- package/vendor/serverd/src/lib/plugin-oidc.js +144 -42
- package/vendor/serverd/src/lib/plugin-packages.js +74 -2
- package/vendor/serverd/src/lib/plugins.js +82 -9
- package/vendor/serverd/src/lib/registry-credential.js +39 -6
- package/vendor/serverd/src/lib/services.js +9 -1
- package/vendor/serverd/src/lib/state.js +29 -0
- package/vendor/serverd/src/lib/tunnel-deps.js +2 -4
- package/vendor/serverd/src/lib/user-surface.js +77 -31
- package/vendor/serverd/src/lib/users-lock.js +23 -0
- package/vendor/serverd/src/lib/vhost-reconcile.js +151 -41
- package/vendor/serverd/src/lib/web-surface.js +783 -0
- package/vendor/serverd/src/middleware/user-access-session.js +11 -4
- package/vendor/serverd/src/routes/invite.js +15 -5
- package/vendor/serverd/src/routes/management/domains.js +154 -0
- package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +44 -8
- package/vendor/serverd/src/routes/management/identity.js +21 -7
- package/vendor/serverd/src/routes/management/invitations.js +33 -4
- package/vendor/serverd/src/routes/management/plugin-web-surface.js +151 -0
- package/vendor/serverd/src/routes/management/plugins.js +6 -2
- package/vendor/serverd/src/routes/management/registry-credential.js +86 -57
- package/vendor/serverd/src/routes/management/sites.js +188 -71
- package/vendor/serverd/src/routes/management/tunnels.js +37 -22
- package/vendor/serverd/src/routes/management/users.js +184 -64
- package/vendor/serverd/src/routes/management.js +4 -0
- package/vendor/serverd/src/routes/onboarding/dns.js +3 -17
- package/vendor/serverd/src/routes/onboarding/provision.js +22 -13
- package/vendor/serverd/src/routes/user-access.js +13 -1
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0001_groups.sql +0 -7
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0002_access_grants.sql +0 -14
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0003_access_request_log.sql +0 -9
- package/vendor/server-ui/dist/assets/index-D-fer5ef.css +0 -1
- package/vendor/server-ui/dist/assets/index-DSaDPJaw.js +0 -84
|
@@ -22,16 +22,20 @@ import {
|
|
|
22
22
|
updateTunnel,
|
|
23
23
|
TUNNEL_MAX_BODY_MB,
|
|
24
24
|
grantedTunnelsFor,
|
|
25
|
+
hostnameHolder,
|
|
25
26
|
TunnelError,
|
|
26
27
|
} from '@lamalibre/lamaste/server';
|
|
27
28
|
import {
|
|
28
29
|
AGENT_LABEL_REGEX,
|
|
30
|
+
DomainNameSchema,
|
|
29
31
|
INTERNAL_PLUGIN_PORT_RANGE,
|
|
30
32
|
PLUGIN_AGENT_CN_PREFIX,
|
|
31
33
|
RESERVED_TUNNEL_PORTS,
|
|
32
34
|
isInternalPluginPort,
|
|
33
35
|
} from '@lamalibre/lamaste';
|
|
34
36
|
import { getConfig } from '../../lib/config.js';
|
|
37
|
+
import { primaryDomainName, resolveActiveDomain } from '../../lib/domains.js';
|
|
38
|
+
import { sendFailure } from '../../lib/app-error.js';
|
|
35
39
|
import { readTunnels, readSites } from '../../lib/state.js';
|
|
36
40
|
import { getAgentHostFactsHash, loadAgentRegistry } from '../../lib/mtls.js';
|
|
37
41
|
import { getChiselCredentialIssuedAt } from '../../lib/chisel-users.js';
|
|
@@ -147,6 +151,9 @@ const CreateTunnelSchema = z
|
|
|
147
151
|
agentLabel: AgentLabelSchema.optional(),
|
|
148
152
|
accessMode: AccessModeSchema.optional().default('restricted'),
|
|
149
153
|
maxBodySizeMb: MaxBodySizeSchema.optional(),
|
|
154
|
+
// The base domain the hostname is under (an active one; the primary
|
|
155
|
+
// when absent). An agent panel is always on the primary.
|
|
156
|
+
domain: DomainNameSchema.optional(),
|
|
150
157
|
})
|
|
151
158
|
.refine((d) => d.type !== 'plugin' || (d.pluginName && d.agentLabel), {
|
|
152
159
|
message: 'pluginName and agentLabel are required for plugin tunnels',
|
|
@@ -220,10 +227,15 @@ function maxBodyCeiling(request) {
|
|
|
220
227
|
return request.certRole === 'admin' ? TUNNEL_MAX_BODY_MB.max : TUNNEL_MAX_BODY_MB.agentMax;
|
|
221
228
|
}
|
|
222
229
|
|
|
223
|
-
/**
|
|
230
|
+
/**
|
|
231
|
+
* Core `createTunnel`'s `hostTaken`: the static site (or one of its
|
|
232
|
+
* aliases) that already answers on `fqdn` — two vhosts with one
|
|
233
|
+
* server_name make nginx pick silently. Asked under the tunnel lock, which
|
|
234
|
+
* every workflow giving a site a hostname holds too.
|
|
235
|
+
*/
|
|
224
236
|
async function siteServing(fqdn) {
|
|
225
|
-
const
|
|
226
|
-
return
|
|
237
|
+
const holder = hostnameHolder(fqdn, { sites: await readSites() });
|
|
238
|
+
return holder ? `by a static site ('${holder.name}')` : null;
|
|
227
239
|
}
|
|
228
240
|
|
|
229
241
|
/**
|
|
@@ -336,6 +348,7 @@ export default async function tunnelRoutes(fastify, _opts) {
|
|
|
336
348
|
tunnels: carried.map((t) => ({
|
|
337
349
|
port: t.port,
|
|
338
350
|
subdomain: t.subdomain,
|
|
351
|
+
fqdn: t.fqdn,
|
|
339
352
|
})),
|
|
340
353
|
// The agent-hosted plugins this agent hosts: the agent fetches a
|
|
341
354
|
// plugin's config when it does not hold the secret issued at
|
|
@@ -495,15 +508,16 @@ export default async function tunnelRoutes(fastify, _opts) {
|
|
|
495
508
|
error: 'Domain and email must be configured before creating tunnels',
|
|
496
509
|
});
|
|
497
510
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
511
|
+
let domain;
|
|
512
|
+
try {
|
|
513
|
+
domain = await resolveActiveDomain(body.domain);
|
|
514
|
+
} catch (err) {
|
|
515
|
+
return sendFailure(request, reply, err, 'Failed to read the domains');
|
|
516
|
+
}
|
|
517
|
+
if (type === 'panel' && domain !== (await primaryDomainName())) {
|
|
518
|
+
return reply
|
|
519
|
+
.code(400)
|
|
520
|
+
.send({ error: 'An agent panel is served on the primary domain only' });
|
|
507
521
|
}
|
|
508
522
|
|
|
509
523
|
try {
|
|
@@ -516,17 +530,25 @@ export default async function tunnelRoutes(fastify, _opts) {
|
|
|
516
530
|
pluginName,
|
|
517
531
|
agentLabel,
|
|
518
532
|
maxBodySizeMb,
|
|
519
|
-
domain
|
|
533
|
+
domain,
|
|
534
|
+
// Under the tunnel lock: the domain may have been removed since.
|
|
535
|
+
assertDomainActive: async (d) => {
|
|
536
|
+
await resolveActiveDomain(d);
|
|
537
|
+
},
|
|
520
538
|
email: config.email,
|
|
521
539
|
nginx: nginxDeps,
|
|
522
540
|
certbot: certbotDeps,
|
|
523
541
|
chisel: chiselDeps,
|
|
524
542
|
state: stateDeps,
|
|
525
543
|
agents: agentDeps,
|
|
544
|
+
hostTaken: siteServing,
|
|
526
545
|
logger: request.log,
|
|
527
546
|
});
|
|
528
547
|
return reply.code(201).send({ ok: true, tunnel });
|
|
529
548
|
} catch (err) {
|
|
549
|
+
if (Number.isInteger(err.statusCode)) {
|
|
550
|
+
return sendFailure(request, reply, err, 'Failed to create tunnel');
|
|
551
|
+
}
|
|
530
552
|
if (err instanceof TunnelError) {
|
|
531
553
|
const status = tunnelErrorStatus(err.code);
|
|
532
554
|
return reply.code(status).send({
|
|
@@ -592,7 +614,6 @@ export default async function tunnelRoutes(fastify, _opts) {
|
|
|
592
614
|
agentLabel: body.agentLabel,
|
|
593
615
|
accessMode: body.accessMode,
|
|
594
616
|
maxBodySizeMb: body.maxBodySizeMb,
|
|
595
|
-
domain: config.domain,
|
|
596
617
|
email: config.email,
|
|
597
618
|
nginx: nginxDeps,
|
|
598
619
|
certbot: certbotDeps,
|
|
@@ -600,6 +621,7 @@ export default async function tunnelRoutes(fastify, _opts) {
|
|
|
600
621
|
state: stateDeps,
|
|
601
622
|
agents: agentDeps,
|
|
602
623
|
userSurface: userSurfaceDeps,
|
|
624
|
+
hostTaken: siteServing,
|
|
603
625
|
logger: request.log,
|
|
604
626
|
authorize: authorizeFor(request, 'change'),
|
|
605
627
|
});
|
|
@@ -748,14 +770,6 @@ export default async function tunnelRoutes(fastify, _opts) {
|
|
|
748
770
|
});
|
|
749
771
|
}
|
|
750
772
|
|
|
751
|
-
const fqdn = `${subdomain}.${config.domain}`;
|
|
752
|
-
if (await siteServing(fqdn)) {
|
|
753
|
-
return reply.code(400).send({
|
|
754
|
-
error: 'Failed to expose agent panel',
|
|
755
|
-
details: `Domain '${fqdn}' is already served by a static site`,
|
|
756
|
-
});
|
|
757
|
-
}
|
|
758
|
-
|
|
759
773
|
// Check if a panel tunnel already exists for this agent
|
|
760
774
|
const existing = await readTunnels();
|
|
761
775
|
const existingPanel = existing.find((t) => t.type === 'panel' && t.subdomain === subdomain);
|
|
@@ -780,6 +794,7 @@ export default async function tunnelRoutes(fastify, _opts) {
|
|
|
780
794
|
chisel: chiselDeps,
|
|
781
795
|
state: stateDeps,
|
|
782
796
|
agents: agentDeps,
|
|
797
|
+
hostTaken: siteServing,
|
|
783
798
|
logger: request.log,
|
|
784
799
|
});
|
|
785
800
|
return reply.code(201).send({ ok: true, tunnel });
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
USERNAME_MAX_LENGTH,
|
|
4
|
+
USERNAME_MIN_LENGTH,
|
|
5
|
+
USERNAME_REGEX,
|
|
6
|
+
isLamasteOwnedGroup,
|
|
7
|
+
} from '@lamalibre/lamaste';
|
|
3
8
|
import {
|
|
4
9
|
readUsers,
|
|
5
10
|
readUsersRaw,
|
|
@@ -9,7 +14,7 @@ import {
|
|
|
9
14
|
writeTotpToDatabase,
|
|
10
15
|
} from '../../lib/authelia.js';
|
|
11
16
|
import { revokeUserRegistryTokens } from '../../lib/registry-credential.js';
|
|
12
|
-
import { httpError } from '../../lib/app-error.js';
|
|
17
|
+
import { httpError, sendFailure } from '../../lib/app-error.js';
|
|
13
18
|
import { actorFromRequest } from '../../lib/actor.js';
|
|
14
19
|
import { recordAuditBestEffort } from '../../lib/audit.js';
|
|
15
20
|
import {
|
|
@@ -21,6 +26,19 @@ import {
|
|
|
21
26
|
getDirectoryUser,
|
|
22
27
|
usersFileError,
|
|
23
28
|
} from '../../lib/directory.js';
|
|
29
|
+
import {
|
|
30
|
+
MembershipInputSchema,
|
|
31
|
+
adminGroups,
|
|
32
|
+
adminGroupsOf,
|
|
33
|
+
assertAssignableGroups,
|
|
34
|
+
domainNames,
|
|
35
|
+
domainsByUser,
|
|
36
|
+
domainsForNewUser,
|
|
37
|
+
domainsOfUser,
|
|
38
|
+
externalMemberGroupsError,
|
|
39
|
+
normalizeMemberships,
|
|
40
|
+
setUserDomains,
|
|
41
|
+
} from '../../lib/domains.js';
|
|
24
42
|
|
|
25
43
|
const CreateUserSchema = z.object({
|
|
26
44
|
username: z
|
|
@@ -34,7 +52,11 @@ const CreateUserSchema = z.object({
|
|
|
34
52
|
displayname: z.string().min(1).max(100),
|
|
35
53
|
email: z.string().email(),
|
|
36
54
|
password: z.string().min(8).max(128),
|
|
37
|
-
groups: z.array(z.string()).optional().default([]),
|
|
55
|
+
groups: z.array(z.string()).max(100).optional().default([]),
|
|
56
|
+
// The domains the user is a member of — a name (a member) or
|
|
57
|
+
// `{ name, kind }` — required while the relay has two or more; with one,
|
|
58
|
+
// the user becomes its member.
|
|
59
|
+
domains: z.array(MembershipInputSchema).max(50).optional(),
|
|
38
60
|
});
|
|
39
61
|
|
|
40
62
|
const UsernameParamSchema = z.object({
|
|
@@ -50,10 +72,14 @@ const UpdateUserSchema = z
|
|
|
50
72
|
displayname: z.string().min(1).max(100).optional(),
|
|
51
73
|
email: z.string().email().optional(),
|
|
52
74
|
password: z.string().min(8).max(128).optional(),
|
|
53
|
-
groups: z.array(z.string()).optional(),
|
|
75
|
+
groups: z.array(z.string()).max(100).optional(),
|
|
54
76
|
// Authelia's `disabled: true`: the user cannot sign in, every plugin
|
|
55
77
|
// audience shows them disabled, and the user surface refuses them.
|
|
56
78
|
disabled: z.boolean().optional(),
|
|
79
|
+
// The domains the user is a member of afterwards — exactly these (the
|
|
80
|
+
// difference applied in one transaction, each membership audited): a
|
|
81
|
+
// name (a member) or `{ name, kind }`.
|
|
82
|
+
domains: z.array(MembershipInputSchema).max(50).optional(),
|
|
57
83
|
})
|
|
58
84
|
.refine((data) => Object.keys(data).length > 0, {
|
|
59
85
|
message: 'At least one field must be provided',
|
|
@@ -76,17 +102,28 @@ async function readUsersOrFail() {
|
|
|
76
102
|
}
|
|
77
103
|
}
|
|
78
104
|
|
|
79
|
-
async function writeUsersOrFail(usersData) {
|
|
105
|
+
async function writeUsersOrFail(usersData, opts) {
|
|
80
106
|
try {
|
|
81
|
-
await writeUsers(usersData);
|
|
107
|
+
await writeUsers(usersData, opts);
|
|
82
108
|
} catch (err) {
|
|
83
109
|
throw httpError('Failed to update user database', 500, { cause: err });
|
|
84
110
|
}
|
|
85
111
|
}
|
|
86
112
|
|
|
87
|
-
/**
|
|
88
|
-
|
|
89
|
-
|
|
113
|
+
/**
|
|
114
|
+
* The groups an administrator gave a user as a sorted list — the groups
|
|
115
|
+
* Lamaste owns left out (users.yml may hold none). `domains`: every domain
|
|
116
|
+
* record.
|
|
117
|
+
*/
|
|
118
|
+
function groupsOf(entry, domains) {
|
|
119
|
+
return adminGroups(entry?.groups, domains).sort();
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** The groups Lamaste owns that a users.yml entry holds — kept by every edit. */
|
|
123
|
+
function ownedGroupsOf(entry, domains) {
|
|
124
|
+
return Array.isArray(entry?.groups)
|
|
125
|
+
? entry.groups.filter((g) => isLamasteOwnedGroup(g, domains))
|
|
126
|
+
: [];
|
|
90
127
|
}
|
|
91
128
|
|
|
92
129
|
/** Whether two lists hold the same strings. */
|
|
@@ -101,22 +138,6 @@ function isEnabledAdmin(entry) {
|
|
|
101
138
|
);
|
|
102
139
|
}
|
|
103
140
|
|
|
104
|
-
/**
|
|
105
|
-
* Answer a refusal a users change threw (an error with `statusCode`: its
|
|
106
|
-
* message and `code`), or a 500 for anything else — logged either way when
|
|
107
|
-
* it is the panel's own failure.
|
|
108
|
-
*/
|
|
109
|
-
function sendFailure(request, reply, err, fallback) {
|
|
110
|
-
if (Number.isInteger(err.statusCode)) {
|
|
111
|
-
if (err.statusCode >= 500) request.log.error(err.cause ?? err, err.message);
|
|
112
|
-
return reply
|
|
113
|
-
.code(err.statusCode)
|
|
114
|
-
.send({ error: err.message, ...(err.code ? { code: err.code } : {}) });
|
|
115
|
-
}
|
|
116
|
-
request.log.error(err, fallback);
|
|
117
|
-
return reply.code(500).send({ error: fallback });
|
|
118
|
-
}
|
|
119
|
-
|
|
120
141
|
export default async function usersRoutes(fastify, _opts) {
|
|
121
142
|
// GET /api/users — list all users (no sensitive fields): users.yml's, with
|
|
122
143
|
// each one's directory id (null until the directory has seen them, within
|
|
@@ -130,13 +151,17 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
130
151
|
try {
|
|
131
152
|
const users = await readUsers();
|
|
132
153
|
const directory = await directoryUsersByUsername();
|
|
154
|
+
const memberships = await domainsByUser();
|
|
155
|
+
const domains = await domainNames();
|
|
133
156
|
const listed = users
|
|
134
157
|
.map((user) => {
|
|
135
158
|
const known = directory.get(user.username);
|
|
136
159
|
return {
|
|
137
160
|
...user,
|
|
161
|
+
groups: groupsOf(user, domains),
|
|
138
162
|
userId: known?.userId ?? null,
|
|
139
163
|
status: known?.status ?? (user.disabled ? 'disabled' : 'active'),
|
|
164
|
+
domains: (known && memberships.get(known.userId)) ?? [],
|
|
140
165
|
};
|
|
141
166
|
})
|
|
142
167
|
.sort((a, b) => a.username.localeCompare(b.username));
|
|
@@ -160,6 +185,17 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
160
185
|
async (request, reply) => {
|
|
161
186
|
const body = CreateUserSchema.parse(request.body);
|
|
162
187
|
|
|
188
|
+
let domains;
|
|
189
|
+
try {
|
|
190
|
+
assertAssignableGroups(body.groups);
|
|
191
|
+
domains = await domainsForNewUser(body.domains, { required: true });
|
|
192
|
+
if (body.groups.length > 0 && domains.some((m) => m.kind === 'external')) {
|
|
193
|
+
throw externalMemberGroupsError(400);
|
|
194
|
+
}
|
|
195
|
+
} catch (err) {
|
|
196
|
+
return sendFailure(request, reply, err, 'Failed to read the domains');
|
|
197
|
+
}
|
|
198
|
+
|
|
163
199
|
try {
|
|
164
200
|
// Refused before a password is hashed; checked again under the lock.
|
|
165
201
|
if ((await readUsersOrFail()).users[body.username]) {
|
|
@@ -198,6 +234,7 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
198
234
|
// A removed user's grants, groups and second factors under the
|
|
199
235
|
// username go first; a username with an OIDC identity is refused.
|
|
200
236
|
creates: [body.username],
|
|
237
|
+
domains: { [body.username]: domains },
|
|
201
238
|
expect: (users) => users[body.username]?.password === hash,
|
|
202
239
|
},
|
|
203
240
|
));
|
|
@@ -217,6 +254,7 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
217
254
|
disabled: false,
|
|
218
255
|
userId: created?.userId ?? null,
|
|
219
256
|
status: 'active',
|
|
257
|
+
domains: created ? await domainsOfUser(created.userId) : [],
|
|
220
258
|
},
|
|
221
259
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
222
260
|
});
|
|
@@ -248,68 +286,149 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
248
286
|
}
|
|
249
287
|
}
|
|
250
288
|
|
|
289
|
+
const domains = await domainNames();
|
|
290
|
+
|
|
251
291
|
/** Whether users.yml's entry holds every field this request set. */
|
|
252
292
|
const intended = (entry) =>
|
|
253
293
|
entry !== undefined &&
|
|
254
294
|
entry !== null &&
|
|
255
295
|
(body.displayname === undefined || entry.displayname === body.displayname) &&
|
|
256
296
|
(body.email === undefined || entry.email === body.email) &&
|
|
257
|
-
(body.groups === undefined ||
|
|
297
|
+
(body.groups === undefined ||
|
|
298
|
+
sameList(groupsOf(entry, domains), [...body.groups].sort())) &&
|
|
258
299
|
(passwordHash === undefined || entry.password === passwordHash) &&
|
|
259
300
|
(body.disabled === undefined || (entry.disabled === true) === body.disabled);
|
|
260
301
|
|
|
302
|
+
const { domains: memberOf, ...fields } = body;
|
|
303
|
+
const memberships = memberOf === undefined ? undefined : normalizeMemberships(memberOf);
|
|
304
|
+
const known = await findDirectoryUser(username);
|
|
305
|
+
const userId = known?.userId ?? null;
|
|
306
|
+
if (memberships !== undefined && !userId) {
|
|
307
|
+
return reply.code(409).send({
|
|
308
|
+
error: 'The directory has not seen this user yet — try again within a minute',
|
|
309
|
+
code: 'user_not_in_directory',
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
const current = userId ? await domainsOfUser(userId) : [];
|
|
313
|
+
const external = (memberships ?? current).some((m) => m.kind === 'external');
|
|
314
|
+
// Refused before anything is written: groups kept or given to a user
|
|
315
|
+
// who is (or becomes) an external member. Checked again under the
|
|
316
|
+
// users lock, by each step.
|
|
317
|
+
if (external) {
|
|
318
|
+
let groups = body.groups;
|
|
319
|
+
if (groups === undefined) {
|
|
320
|
+
try {
|
|
321
|
+
groups = await adminGroupsOf(username);
|
|
322
|
+
} catch (err) {
|
|
323
|
+
return sendFailure(request, reply, err, 'Failed to read user database');
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
if (groups.length > 0) {
|
|
327
|
+
return sendFailure(request, reply, externalMemberGroupsError(409), 'Refused');
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
// A user leaving every external membership with groups given in the
|
|
331
|
+
// same request: the memberships first, then the groups (each step is
|
|
332
|
+
// checked against the other under the users lock).
|
|
333
|
+
const membershipsFirst =
|
|
334
|
+
memberships !== undefined && !external && current.some((m) => m.kind === 'external');
|
|
335
|
+
|
|
336
|
+
const warnings = [];
|
|
337
|
+
const changeMemberships = async () => {
|
|
338
|
+
if (memberships === undefined) return;
|
|
339
|
+
const membership = await setUserDomains(userId, memberships, {
|
|
340
|
+
actor: actorFromRequest(request),
|
|
341
|
+
logger: request.log,
|
|
342
|
+
});
|
|
343
|
+
warnings.push(...(membership.warnings ?? []));
|
|
344
|
+
};
|
|
345
|
+
if (membershipsFirst) {
|
|
346
|
+
try {
|
|
347
|
+
await changeMemberships();
|
|
348
|
+
} catch (err) {
|
|
349
|
+
return sendFailure(request, reply, err, 'Failed to change the memberships');
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
261
353
|
let outcome;
|
|
262
354
|
let before = null;
|
|
263
355
|
try {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
356
|
+
// Only memberships change: users.yml is not written, the user is read.
|
|
357
|
+
if (Object.keys(fields).length === 0) {
|
|
358
|
+
const usersData = await readUsersOrFail();
|
|
359
|
+
const user = usersData.users[username];
|
|
360
|
+
if (!user) throw httpError('User not found', 404);
|
|
361
|
+
before = { groups: groupsOf(user, domains), disabled: user.disabled === true };
|
|
362
|
+
outcome = { result: user, warnings: [], persisted: true };
|
|
363
|
+
} else
|
|
364
|
+
outcome = await changeUsers(
|
|
365
|
+
async () => {
|
|
366
|
+
const usersData = await readUsersOrFail();
|
|
367
|
+
const user = usersData.users[username];
|
|
368
|
+
if (!user) throw httpError('User not found', 404);
|
|
369
|
+
// As it was before this request (a second attempt finds it changed already).
|
|
370
|
+
before ??= { groups: groupsOf(user, domains), disabled: user.disabled === true };
|
|
371
|
+
if (body.groups !== undefined) {
|
|
372
|
+
assertAssignableGroups(body.groups, before.groups);
|
|
373
|
+
// Checked under the users lock, which a change of kind takes too.
|
|
374
|
+
if (
|
|
375
|
+
body.groups.length > 0 &&
|
|
376
|
+
userId &&
|
|
377
|
+
(await domainsOfUser(userId)).some((m) => m.kind === 'external')
|
|
378
|
+
) {
|
|
379
|
+
throw externalMemberGroupsError(409);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
if (body.disabled === true && isEnabledAdmin(user)) {
|
|
383
|
+
const enabledAdmins = Object.values(usersData.users).filter(isEnabledAdmin).length;
|
|
384
|
+
if (enabledAdmins <= 1) {
|
|
385
|
+
throw httpError('Cannot disable the last enabled administrator', 400, {
|
|
386
|
+
code: 'last_admin',
|
|
387
|
+
});
|
|
388
|
+
}
|
|
277
389
|
}
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
if (body.displayname !== undefined) user.displayname = body.displayname;
|
|
281
|
-
if (body.email !== undefined) user.email = body.email;
|
|
282
|
-
if (body.groups !== undefined) user.groups = body.groups;
|
|
283
|
-
if (passwordHash !== undefined) user.password = passwordHash;
|
|
284
|
-
if (body.disabled === true) user.disabled = true;
|
|
285
|
-
if (body.disabled === false) delete user.disabled;
|
|
286
390
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
391
|
+
if (body.displayname !== undefined) user.displayname = body.displayname;
|
|
392
|
+
if (body.email !== undefined) user.email = body.email;
|
|
393
|
+
if (body.groups !== undefined) {
|
|
394
|
+
user.groups = [...body.groups, ...ownedGroupsOf(user, domains)];
|
|
395
|
+
}
|
|
396
|
+
if (passwordHash !== undefined) user.password = passwordHash;
|
|
397
|
+
if (body.disabled === true) user.disabled = true;
|
|
398
|
+
if (body.disabled === false) delete user.disabled;
|
|
399
|
+
|
|
400
|
+
await writeUsersOrFail(usersData);
|
|
401
|
+
return user;
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
actor: actorFromRequest(request),
|
|
405
|
+
logger: request.log,
|
|
406
|
+
// Authelia's own password reset may rewrite users.yml under the
|
|
407
|
+
// change: it is checked after the rendering, and made once more.
|
|
408
|
+
expect: (users) => intended(users[username]),
|
|
409
|
+
},
|
|
410
|
+
);
|
|
298
411
|
} catch (err) {
|
|
299
412
|
return sendFailure(request, reply, err, 'Failed to update user database');
|
|
300
413
|
}
|
|
301
414
|
|
|
302
415
|
const user = outcome.result;
|
|
303
416
|
const disabled = user.disabled === true;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
417
|
+
warnings.push(...outcome.warnings);
|
|
418
|
+
|
|
419
|
+
if (!membershipsFirst) {
|
|
420
|
+
try {
|
|
421
|
+
await changeMemberships();
|
|
422
|
+
} catch (err) {
|
|
423
|
+
return sendFailure(request, reply, err, 'Failed to change the memberships');
|
|
424
|
+
}
|
|
425
|
+
}
|
|
307
426
|
|
|
308
427
|
// Disabling is offboarding, which the change did as the directory saw
|
|
309
428
|
// the user become disabled (`changeUsers`): sessions ended, registry
|
|
310
429
|
// tokens revoked. Registry scopes are granted at login from feria-*
|
|
311
430
|
// groups too: losing one must not leave a valid token behind either.
|
|
312
|
-
const groupsAfter = groupsOf(user);
|
|
431
|
+
const groupsAfter = groupsOf(user, domains);
|
|
313
432
|
const groupsChanged = body.groups !== undefined && !sameList(before.groups, groupsAfter);
|
|
314
433
|
if (groupsChanged && !(disabled && !before.disabled)) {
|
|
315
434
|
try {
|
|
@@ -348,10 +467,11 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
348
467
|
username,
|
|
349
468
|
displayname: user.displayname,
|
|
350
469
|
email: user.email,
|
|
351
|
-
groups:
|
|
470
|
+
groups: groupsAfter,
|
|
352
471
|
disabled,
|
|
353
472
|
userId,
|
|
354
473
|
status: disabled ? 'disabled' : 'active',
|
|
474
|
+
domains: userId ? await domainsOfUser(userId) : [],
|
|
355
475
|
},
|
|
356
476
|
...(outcome.persisted ? {} : { persisted: false }),
|
|
357
477
|
...(warnings.length > 0 ? { warnings } : {}),
|
|
@@ -386,7 +506,7 @@ export default async function usersRoutes(fastify, _opts) {
|
|
|
386
506
|
throw httpError('Cannot delete the last user', 400);
|
|
387
507
|
}
|
|
388
508
|
delete usersData.users[username];
|
|
389
|
-
await writeUsersOrFail(usersData);
|
|
509
|
+
await writeUsersOrFail(usersData, { deleting: [username] });
|
|
390
510
|
},
|
|
391
511
|
{
|
|
392
512
|
actor,
|
|
@@ -13,12 +13,14 @@ import ticketRoutes from './management/tickets.js';
|
|
|
13
13
|
import identityRoutes from './management/identity.js';
|
|
14
14
|
import identityDirectoryRoutes from './management/identity-directory.js';
|
|
15
15
|
import pluginIdentityRoutes from './management/plugin-identity.js';
|
|
16
|
+
import pluginWebSurfaceRoutes from './management/plugin-web-surface.js';
|
|
16
17
|
import storageRoutes from './management/storage.js';
|
|
17
18
|
import gatekeeperProxyRoutes from './management/gatekeeper-proxy.js';
|
|
18
19
|
import agentsRoutes from './management/agents.js';
|
|
19
20
|
import registryCredentialRoutes from './management/registry-credential.js';
|
|
20
21
|
import auditRoutes from './management/audit.js';
|
|
21
22
|
import agentPanelAuthzRoutes from './management/agent-panel-authz.js';
|
|
23
|
+
import domainRoutes from './management/domains.js';
|
|
22
24
|
|
|
23
25
|
export default async function managementRoutes(fastify, _opts) {
|
|
24
26
|
fastify.addHook('onRequest', managementOnly());
|
|
@@ -37,10 +39,12 @@ export default async function managementRoutes(fastify, _opts) {
|
|
|
37
39
|
await fastify.register(identityRoutes);
|
|
38
40
|
await fastify.register(identityDirectoryRoutes);
|
|
39
41
|
await fastify.register(pluginIdentityRoutes);
|
|
42
|
+
await fastify.register(pluginWebSurfaceRoutes);
|
|
40
43
|
await fastify.register(storageRoutes);
|
|
41
44
|
await fastify.register(gatekeeperProxyRoutes);
|
|
42
45
|
await fastify.register(agentsRoutes);
|
|
43
46
|
await fastify.register(registryCredentialRoutes);
|
|
44
47
|
await fastify.register(auditRoutes);
|
|
45
48
|
await fastify.register(agentPanelAuthzRoutes);
|
|
49
|
+
await fastify.register(domainRoutes);
|
|
46
50
|
}
|
|
@@ -1,21 +1,7 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { CORE_SUBDOMAINS, DNS_WILDCARD_PROBE_LABEL } from '@lamalibre/lamaste';
|
|
2
|
+
import { resolveHostA as resolveA } from '@lamalibre/lamaste/server';
|
|
3
3
|
import { getConfig, updateConfig } from '../../lib/config.js';
|
|
4
4
|
|
|
5
|
-
/**
|
|
6
|
-
* Resolve A records for a hostname, returning an empty array on expected DNS errors.
|
|
7
|
-
*/
|
|
8
|
-
async function resolveA(hostname) {
|
|
9
|
-
try {
|
|
10
|
-
return await dns.resolve4(hostname);
|
|
11
|
-
} catch (err) {
|
|
12
|
-
if (err.code === 'ENOTFOUND' || err.code === 'ENODATA' || err.code === 'ETIMEOUT') {
|
|
13
|
-
return [];
|
|
14
|
-
}
|
|
15
|
-
throw err;
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
|
|
19
5
|
/**
|
|
20
6
|
* Build a human-readable diagnostic message for the DNS verification result.
|
|
21
7
|
*/
|
|
@@ -69,7 +55,7 @@ export default async function dnsRoute(fastify, _opts) {
|
|
|
69
55
|
const [coreResolved, apexResolvedIps, wildcardResolvedIps] = await Promise.all([
|
|
70
56
|
Promise.all(coreNames.map((name) => resolveA(name))),
|
|
71
57
|
resolveA(domain),
|
|
72
|
-
resolveA(
|
|
58
|
+
resolveA(`${DNS_WILDCARD_PROBE_LABEL}.${domain}`),
|
|
73
59
|
]);
|
|
74
60
|
|
|
75
61
|
const records = coreNames.map((name, i) => {
|
|
@@ -12,6 +12,7 @@ import { migrateChiselCredentialsIfNeeded } from '../../lib/chisel-users.js';
|
|
|
12
12
|
import { loadAgentRegistry } from '../../lib/mtls.js';
|
|
13
13
|
import { setChiselRunningPinnedRelease } from '../../lib/chisel-runtime.js';
|
|
14
14
|
import { changeUsers } from '../../lib/directory.js';
|
|
15
|
+
import { ensurePrimaryDomain, syncDomainAccess } from '../../lib/domains.js';
|
|
15
16
|
|
|
16
17
|
// Module-level state for provisioning
|
|
17
18
|
const emitter = new EventEmitter();
|
|
@@ -115,24 +116,16 @@ async function runProvisioning(log) {
|
|
|
115
116
|
'Authelia is not installed. Run `npx @lamalibre/create-lamaste` on the server to repair the installation.',
|
|
116
117
|
);
|
|
117
118
|
}
|
|
118
|
-
emitProgress('install-authelia', 'running', 'Writing configuration...');
|
|
119
|
-
|
|
120
|
-
const secrets = {
|
|
121
|
-
jwtSecret: crypto.randomBytes(32).toString('hex'),
|
|
122
|
-
sessionSecret: crypto.randomBytes(32).toString('hex'),
|
|
123
|
-
storageEncryptionKey: crypto.randomBytes(32).toString('hex'),
|
|
124
|
-
};
|
|
125
|
-
await authelia.writeAutheliaConfig(domain, secrets);
|
|
126
|
-
|
|
127
119
|
emitProgress('install-authelia', 'running', 'Creating admin user...');
|
|
128
120
|
adminPassword = crypto.randomBytes(16).toString('base64url');
|
|
129
121
|
// Through the users lock, and into the user directory — which reconciles
|
|
130
|
-
// with users.yml from here on.
|
|
122
|
+
// with users.yml from here on. The domain is rendered below, once it
|
|
123
|
+
// exists.
|
|
131
124
|
const created = await changeUsers(() => authelia.createUser('admin', adminPassword), {
|
|
132
125
|
actor: 'lamaste',
|
|
133
126
|
logger: log,
|
|
134
127
|
creates: ['admin'],
|
|
135
|
-
|
|
128
|
+
renderDomains: false,
|
|
136
129
|
// Authelia's storage does not exist before its first start.
|
|
137
130
|
autheliaStorage: false,
|
|
138
131
|
});
|
|
@@ -140,6 +133,21 @@ async function runProvisioning(log) {
|
|
|
140
133
|
log.warn({ step: warning.step }, warning.message);
|
|
141
134
|
}
|
|
142
135
|
|
|
136
|
+
// The primary domain with the administrator as its member — rendered
|
|
137
|
+
// into users.yml's groups and Gatekeeper's projection before Authelia
|
|
138
|
+
// first starts, so its first configuration already admits them (the
|
|
139
|
+
// configuration it is rendered from is the one written here: nothing to
|
|
140
|
+
// restart). The panel renders every later change.
|
|
141
|
+
emitProgress('install-authelia', 'running', 'Writing configuration...');
|
|
142
|
+
await ensurePrimaryDomain({ logger: log });
|
|
143
|
+
const secrets = {
|
|
144
|
+
jwtSecret: crypto.randomBytes(32).toString('hex'),
|
|
145
|
+
sessionSecret: crypto.randomBytes(32).toString('hex'),
|
|
146
|
+
storageEncryptionKey: crypto.randomBytes(32).toString('hex'),
|
|
147
|
+
};
|
|
148
|
+
await authelia.writeAutheliaConfig(domain, secrets);
|
|
149
|
+
await syncDomainAccess({ logger: log });
|
|
150
|
+
|
|
143
151
|
emitProgress('install-authelia', 'running', 'Starting Authelia service...');
|
|
144
152
|
await authelia.startAuthelia();
|
|
145
153
|
emitProgress('install-authelia', 'done', 'Authelia configured and running');
|
|
@@ -156,7 +164,7 @@ async function runProvisioning(log) {
|
|
|
156
164
|
await nginx.writePanelVhost(domain);
|
|
157
165
|
|
|
158
166
|
emitProgress('configure-nginx', 'running', 'Writing auth vhost...');
|
|
159
|
-
await nginx.writeAuthVhost(domain);
|
|
167
|
+
await nginx.writeAuthVhost(domain, { primary: true });
|
|
160
168
|
|
|
161
169
|
emitProgress('configure-nginx', 'running', 'Writing tunnel vhost...');
|
|
162
170
|
await nginx.writeTunnelVhost(domain);
|
|
@@ -166,7 +174,6 @@ async function runProvisioning(log) {
|
|
|
166
174
|
|
|
167
175
|
emitProgress('configure-nginx', 'running', 'Enabling sites...');
|
|
168
176
|
await nginx.enableSite('lamalibre-lamaste-panel-domain');
|
|
169
|
-
await nginx.enableSite('lamalibre-lamaste-auth');
|
|
170
177
|
await nginx.enableSite('lamalibre-lamaste-tunnel');
|
|
171
178
|
|
|
172
179
|
emitProgress('configure-nginx', 'running', 'Testing nginx configuration...');
|
|
@@ -217,6 +224,8 @@ async function runProvisioning(log) {
|
|
|
217
224
|
// Step 6: Finalize
|
|
218
225
|
emitProgress('finalize', 'running', 'Updating configuration...');
|
|
219
226
|
await updateConfig({ onboarding: { status: 'COMPLETED' } });
|
|
227
|
+
// Gatekeeper's projection of the domain (Authelia holds it already).
|
|
228
|
+
await syncDomainAccess({ logger: log });
|
|
220
229
|
|
|
221
230
|
const result = {
|
|
222
231
|
adminUsername: 'admin',
|