@lamalibre/create-lamaste 4.0.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/LICENSE.md +118 -0
- package/README.md +114 -0
- package/bin/create-lamaste.js +15 -0
- package/package.json +54 -0
- package/scripts/bundle-vendor.js +110 -0
- package/scripts/lamaste-cert-info +62 -0
- package/scripts/lamaste-certbot +96 -0
- package/scripts/lamaste-priv +693 -0
- package/src/index.js +646 -0
- package/src/lib/accounts.js +86 -0
- package/src/lib/binaries.js +137 -0
- package/src/lib/cert-help-page.js +160 -0
- package/src/lib/env.js +132 -0
- package/src/lib/http-redirect.js +38 -0
- package/src/lib/ownership.js +231 -0
- package/src/lib/relay-services.js +103 -0
- package/src/lib/secrets.js +19 -0
- package/src/lib/service-config.js +311 -0
- package/src/lib/summary.js +131 -0
- package/src/lib/wrappers.js +63 -0
- package/src/tasks/gatekeeper.js +233 -0
- package/src/tasks/harden.js +293 -0
- package/src/tasks/mtls.js +193 -0
- package/src/tasks/nginx.js +265 -0
- package/src/tasks/node.js +110 -0
- package/src/tasks/panel.js +408 -0
- package/src/tasks/redeploy.js +438 -0
- package/vendor/docs/00-introduction/how-it-works.md +447 -0
- package/vendor/docs/00-introduction/quickstart.md +472 -0
- package/vendor/docs/00-introduction/what-is-lamaste.md +112 -0
- package/vendor/docs/01-concepts/authentication.md +464 -0
- package/vendor/docs/01-concepts/authorization.md +242 -0
- package/vendor/docs/01-concepts/certificates.md +452 -0
- package/vendor/docs/01-concepts/dns-and-domains.md +343 -0
- package/vendor/docs/01-concepts/mtls.md +490 -0
- package/vendor/docs/01-concepts/nginx-reverse-proxy.md +540 -0
- package/vendor/docs/01-concepts/security-model.md +600 -0
- package/vendor/docs/01-concepts/tickets.md +414 -0
- package/vendor/docs/01-concepts/tunneling.md +367 -0
- package/vendor/docs/02-guides/agent-setup.md +194 -0
- package/vendor/docs/02-guides/certificate-management.md +341 -0
- package/vendor/docs/02-guides/cloud-provisioning.md +406 -0
- package/vendor/docs/02-guides/desktop-app-setup.md +226 -0
- package/vendor/docs/02-guides/disaster-recovery.md +464 -0
- package/vendor/docs/02-guides/first-tunnel.md +230 -0
- package/vendor/docs/02-guides/installation.md +393 -0
- package/vendor/docs/02-guides/managing-users.md +255 -0
- package/vendor/docs/02-guides/onboarding.md +244 -0
- package/vendor/docs/02-guides/standalone-plugin-with-tickets.md +375 -0
- package/vendor/docs/02-guides/static-sites.md +364 -0
- package/vendor/docs/03-architecture/e2e-three-vm-sequences.md +457 -0
- package/vendor/docs/03-architecture/installer-flow.md +151 -0
- package/vendor/docs/03-architecture/installer.md +445 -0
- package/vendor/docs/03-architecture/management-flow.md +288 -0
- package/vendor/docs/03-architecture/nginx-configuration.md +830 -0
- package/vendor/docs/03-architecture/onboarding-flow.md +164 -0
- package/vendor/docs/03-architecture/overview.md +541 -0
- package/vendor/docs/03-architecture/panel-client.md +416 -0
- package/vendor/docs/03-architecture/panel-server.md +695 -0
- package/vendor/docs/03-architecture/state-management.md +529 -0
- package/vendor/docs/03-architecture/system-overview.md +167 -0
- package/vendor/docs/04-api-reference/certificates.md +1141 -0
- package/vendor/docs/04-api-reference/gatekeeper.md +592 -0
- package/vendor/docs/04-api-reference/onboarding.md +473 -0
- package/vendor/docs/04-api-reference/overview.md +404 -0
- package/vendor/docs/04-api-reference/services.md +267 -0
- package/vendor/docs/04-api-reference/settings.md +343 -0
- package/vendor/docs/04-api-reference/sites.md +716 -0
- package/vendor/docs/04-api-reference/system.md +268 -0
- package/vendor/docs/04-api-reference/tickets.md +618 -0
- package/vendor/docs/04-api-reference/tunnels.md +417 -0
- package/vendor/docs/04-api-reference/users.md +333 -0
- package/vendor/docs/05-operations/backup-and-restore.md +342 -0
- package/vendor/docs/05-operations/monitoring.md +285 -0
- package/vendor/docs/05-operations/uninstalling.md +344 -0
- package/vendor/docs/05-operations/upgrades.md +270 -0
- package/vendor/docs/06-reference/config-files.md +1080 -0
- package/vendor/docs/06-reference/glossary.md +528 -0
- package/vendor/docs/06-reference/installer-flags.md +260 -0
- package/vendor/docs/06-reference/ports-and-services.md +244 -0
- package/vendor/docs/06-reference/troubleshooting.md +665 -0
- package/vendor/docs/_index.json +233 -0
- package/vendor/docs/gatekeeper-design.md +953 -0
- package/vendor/docs/package.json +15 -0
- package/vendor/docs/todo/lib-cli-desktop-refactor.md +641 -0
- package/vendor/gatekeeper/dist/boot.d.ts +1 -0
- package/vendor/gatekeeper/dist/boot.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/boot.js +17 -0
- package/vendor/gatekeeper/dist/boot.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/access.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/access.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/access.js +33 -0
- package/vendor/gatekeeper/dist/cli/commands/access.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.js +110 -0
- package/vendor/gatekeeper/dist/cli/commands/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.d.ts +2 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.js +98 -0
- package/vendor/gatekeeper/dist/cli/commands/groups.js.map +1 -0
- package/vendor/gatekeeper/dist/cli/index.d.ts +3 -0
- package/vendor/gatekeeper/dist/cli/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/cli/index.js +45 -0
- package/vendor/gatekeeper/dist/cli/index.js.map +1 -0
- package/vendor/gatekeeper/dist/index.d.ts +9 -0
- package/vendor/gatekeeper/dist/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/index.js +13 -0
- package/vendor/gatekeeper/dist/index.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/authz.d.ts +16 -0
- package/vendor/gatekeeper/dist/lib/authz.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/authz.js +42 -0
- package/vendor/gatekeeper/dist/lib/authz.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.d.ts +15 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.js +19 -0
- package/vendor/gatekeeper/dist/lib/cache-bust.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/constants.d.ts +82 -0
- package/vendor/gatekeeper/dist/lib/constants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/constants.js +79 -0
- package/vendor/gatekeeper/dist/lib/constants.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts +32 -0
- package/vendor/gatekeeper/dist/lib/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/grants.js +314 -0
- package/vendor/gatekeeper/dist/lib/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts +10 -0
- package/vendor/gatekeeper/dist/lib/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/groups.js +247 -0
- package/vendor/gatekeeper/dist/lib/groups.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/migration.d.ts +17 -0
- package/vendor/gatekeeper/dist/lib/migration.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/migration.js +79 -0
- package/vendor/gatekeeper/dist/lib/migration.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0001_groups.sql +7 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0002_access_grants.sql +14 -0
- package/vendor/gatekeeper/dist/lib/migrations/gatekeeper/0003_access_request_log.sql +9 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0001_groups.sql +7 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0002_access_grants.sql +14 -0
- package/vendor/gatekeeper/dist/lib/migrations/migrations/gatekeeper/0003_access_request_log.sql +9 -0
- package/vendor/gatekeeper/dist/lib/state-db.d.ts +16 -0
- package/vendor/gatekeeper/dist/lib/state-db.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/state-db.js +154 -0
- package/vendor/gatekeeper/dist/lib/state-db.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/templates.d.ts +21 -0
- package/vendor/gatekeeper/dist/lib/templates.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/templates.js +151 -0
- package/vendor/gatekeeper/dist/lib/templates.js.map +1 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts +178 -0
- package/vendor/gatekeeper/dist/lib/types.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/lib/types.js +2 -0
- package/vendor/gatekeeper/dist/lib/types.js.map +1 -0
- package/vendor/gatekeeper/dist/server/index.d.ts +15 -0
- package/vendor/gatekeeper/dist/server/index.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/index.js +251 -0
- package/vendor/gatekeeper/dist/server/index.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.d.ts +7 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.js +17 -0
- package/vendor/gatekeeper/dist/server/routes/access-request.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/authz.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/authz.js +279 -0
- package/vendor/gatekeeper/dist/server/routes/authz.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts +13 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js +249 -0
- package/vendor/gatekeeper/dist/server/routes/diagnostic.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/grants.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/grants.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/grants.js +112 -0
- package/vendor/gatekeeper/dist/server/routes/grants.js.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/groups.d.ts +3 -0
- package/vendor/gatekeeper/dist/server/routes/groups.d.ts.map +1 -0
- package/vendor/gatekeeper/dist/server/routes/groups.js +139 -0
- package/vendor/gatekeeper/dist/server/routes/groups.js.map +1 -0
- package/vendor/gatekeeper/package.json +49 -0
- package/vendor/server/bin/lamaste-server.js +204 -0
- package/vendor/server/package.json +31 -0
- package/vendor/server/src/commands/certs.js +224 -0
- package/vendor/server/src/commands/chisel.js +132 -0
- package/vendor/server/src/commands/logs.js +49 -0
- package/vendor/server/src/commands/plugins.js +321 -0
- package/vendor/server/src/commands/reset-admin.js +478 -0
- package/vendor/server/src/commands/restart.js +94 -0
- package/vendor/server/src/commands/sites.js +257 -0
- package/vendor/server/src/commands/status.js +158 -0
- package/vendor/server/src/commands/tunnels.js +365 -0
- package/vendor/server/src/commands/uninstall.js +326 -0
- package/vendor/server/src/config.js +38 -0
- package/vendor/server/src/exec.js +26 -0
- package/vendor/server/src/logger.js +39 -0
- package/vendor/server/src/ndjson.js +56 -0
- package/vendor/server/src/panel-api.js +85 -0
- package/vendor/server/src/state.js +45 -0
- package/vendor/server-ui/dist/assets/index-B0nWuUyL.js +257 -0
- package/vendor/server-ui/dist/assets/index-BqWWFlTU.css +1 -0
- package/vendor/server-ui/dist/index.html +13 -0
- package/vendor/serverd/package.json +44 -0
- package/vendor/serverd/src/boot.js +13 -0
- package/vendor/serverd/src/index.js +191 -0
- package/vendor/serverd/src/lib/app-error.js +14 -0
- package/vendor/serverd/src/lib/authelia.js +85 -0
- package/vendor/serverd/src/lib/certbot.js +74 -0
- package/vendor/serverd/src/lib/chisel-args.js +6 -0
- package/vendor/serverd/src/lib/chisel-reconcile.js +232 -0
- package/vendor/serverd/src/lib/chisel-runtime.js +46 -0
- package/vendor/serverd/src/lib/chisel-users.js +109 -0
- package/vendor/serverd/src/lib/chisel.js +81 -0
- package/vendor/serverd/src/lib/config.js +190 -0
- package/vendor/serverd/src/lib/constants.js +8 -0
- package/vendor/serverd/src/lib/csr-signing.js +331 -0
- package/vendor/serverd/src/lib/enrollment.js +302 -0
- package/vendor/serverd/src/lib/files.js +19 -0
- package/vendor/serverd/src/lib/gatekeeper-client.js +135 -0
- package/vendor/serverd/src/lib/invite-page.js +281 -0
- package/vendor/serverd/src/lib/ip-utils.js +79 -0
- package/vendor/serverd/src/lib/migrations/state/0001_enrollment_tokens.sql +17 -0
- package/vendor/serverd/src/lib/migrations/state/0002_tickets.sql +70 -0
- package/vendor/serverd/src/lib/migrations/state/0003_plugins.sql +15 -0
- package/vendor/serverd/src/lib/migrations/state/0004_agents.sql +19 -0
- package/vendor/serverd/src/lib/mtls.js +795 -0
- package/vendor/serverd/src/lib/nginx.js +1090 -0
- package/vendor/serverd/src/lib/pki-paths.js +41 -0
- package/vendor/serverd/src/lib/pki-sign.js +103 -0
- package/vendor/serverd/src/lib/plugins.js +538 -0
- package/vendor/serverd/src/lib/push-install.js +390 -0
- package/vendor/serverd/src/lib/revocation.js +112 -0
- package/vendor/serverd/src/lib/services.js +131 -0
- package/vendor/serverd/src/lib/session.js +151 -0
- package/vendor/serverd/src/lib/state-db.js +130 -0
- package/vendor/serverd/src/lib/state.js +140 -0
- package/vendor/serverd/src/lib/storage.js +63 -0
- package/vendor/serverd/src/lib/system-stats.js +58 -0
- package/vendor/serverd/src/lib/tickets.js +1556 -0
- package/vendor/serverd/src/lib/totp.js +76 -0
- package/vendor/serverd/src/lib/tunnel-deps.js +41 -0
- package/vendor/serverd/src/lib/user-access-session.js +142 -0
- package/vendor/serverd/src/lib/user-access.js +52 -0
- package/vendor/serverd/src/middleware/errors.js +63 -0
- package/vendor/serverd/src/middleware/mtls.js +120 -0
- package/vendor/serverd/src/middleware/onboarding-guard.js +30 -0
- package/vendor/serverd/src/middleware/role-guard.js +58 -0
- package/vendor/serverd/src/middleware/twofa-session.js +65 -0
- package/vendor/serverd/src/middleware/user-access-session.js +52 -0
- package/vendor/serverd/src/routes/enrollment.js +120 -0
- package/vendor/serverd/src/routes/health.js +22 -0
- package/vendor/serverd/src/routes/invite.js +163 -0
- package/vendor/serverd/src/routes/management/agents.js +182 -0
- package/vendor/serverd/src/routes/management/certs.js +911 -0
- package/vendor/serverd/src/routes/management/gatekeeper-proxy.js +160 -0
- package/vendor/serverd/src/routes/management/identity.js +107 -0
- package/vendor/serverd/src/routes/management/invitations.js +166 -0
- package/vendor/serverd/src/routes/management/logs.js +218 -0
- package/vendor/serverd/src/routes/management/plugins.js +560 -0
- package/vendor/serverd/src/routes/management/services.js +63 -0
- package/vendor/serverd/src/routes/management/settings.js +325 -0
- package/vendor/serverd/src/routes/management/sites.js +522 -0
- package/vendor/serverd/src/routes/management/storage.js +192 -0
- package/vendor/serverd/src/routes/management/system.js +201 -0
- package/vendor/serverd/src/routes/management/tickets.js +457 -0
- package/vendor/serverd/src/routes/management/tunnels.js +709 -0
- package/vendor/serverd/src/routes/management/users.js +377 -0
- package/vendor/serverd/src/routes/management.js +36 -0
- package/vendor/serverd/src/routes/onboarding/dns.js +101 -0
- package/vendor/serverd/src/routes/onboarding/domain.js +42 -0
- package/vendor/serverd/src/routes/onboarding/index.js +18 -0
- package/vendor/serverd/src/routes/onboarding/provision.js +327 -0
- package/vendor/serverd/src/routes/onboarding/status.js +12 -0
- package/vendor/serverd/src/routes/plugin-router.js +79 -0
- package/vendor/serverd/src/routes/user-access.js +389 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny gatekeeper admin-API client for callers other than the
|
|
3
|
+
* /api/gatekeeper/* proxy routes.
|
|
4
|
+
*
|
|
5
|
+
* Reads the shared secret on first use (cached for the process lifetime)
|
|
6
|
+
* and forwards a request to http://127.0.0.1:9294/api/...
|
|
7
|
+
*
|
|
8
|
+
* Used by the user-delete route to cascade-revoke grants belonging to a
|
|
9
|
+
* deleted Authelia user, and by other internal callers that need to talk
|
|
10
|
+
* to gatekeeper without going through the panel's own mTLS layer.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFile } from 'node:fs/promises';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { getConfigPath } from './config.js';
|
|
16
|
+
|
|
17
|
+
const GATEKEEPER_BASE = 'http://127.0.0.1:9294';
|
|
18
|
+
let cachedSecret = null;
|
|
19
|
+
let secretLoaded = false;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Load the gatekeeper API secret. Supports both the new JSON envelope
|
|
23
|
+
* (`gatekeeper-secret.json`) written by the gatekeeper service and the
|
|
24
|
+
* legacy plain-text file (`gatekeeper-secret`). Returns null if neither
|
|
25
|
+
* exists — callers must treat this as "gatekeeper not installed".
|
|
26
|
+
*/
|
|
27
|
+
async function loadSecret() {
|
|
28
|
+
if (secretLoaded) return cachedSecret;
|
|
29
|
+
secretLoaded = true;
|
|
30
|
+
|
|
31
|
+
const configDir = path.dirname(getConfigPath());
|
|
32
|
+
|
|
33
|
+
// Prefer the JSON envelope written by recent gatekeeper builds
|
|
34
|
+
try {
|
|
35
|
+
const raw = await readFile(path.join(configDir, 'gatekeeper-secret.json'), 'utf-8');
|
|
36
|
+
const parsed = JSON.parse(raw);
|
|
37
|
+
if (parsed && typeof parsed.secret === 'string' && parsed.secret.length > 0) {
|
|
38
|
+
cachedSecret = parsed.secret;
|
|
39
|
+
return cachedSecret;
|
|
40
|
+
}
|
|
41
|
+
} catch {
|
|
42
|
+
// Fall through to legacy
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Legacy plain-text fallback
|
|
46
|
+
try {
|
|
47
|
+
const raw = (await readFile(path.join(configDir, 'gatekeeper-secret'), 'utf-8')).trim();
|
|
48
|
+
if (raw.length > 0) {
|
|
49
|
+
cachedSecret = raw;
|
|
50
|
+
return cachedSecret;
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
// No secret available
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
cachedSecret = null;
|
|
57
|
+
return cachedSecret;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Result envelope.
|
|
62
|
+
*
|
|
63
|
+
* @typedef {{ ok: true, statusCode: number, data: unknown }
|
|
64
|
+
* | { ok: false, statusCode: number, error: string, reason: 'unreachable' | 'no-secret' | 'http-error' }
|
|
65
|
+
* } GatekeeperResult
|
|
66
|
+
*/
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Forward a request to the gatekeeper service.
|
|
70
|
+
*
|
|
71
|
+
* Network failures (ECONNREFUSED, etc.) and missing-secret conditions
|
|
72
|
+
* are reported as `{ ok: false, reason: ... }` rather than thrown — the
|
|
73
|
+
* panel server treats gatekeeper as best-effort during cascade operations.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} method
|
|
76
|
+
* @param {string} apiPath - e.g. "/api/grants?principalType=user&principalId=alice"
|
|
77
|
+
* @param {unknown} [body]
|
|
78
|
+
* @returns {Promise<GatekeeperResult>}
|
|
79
|
+
*/
|
|
80
|
+
export async function gatekeeperRequest(method, apiPath, body) {
|
|
81
|
+
const secret = await loadSecret();
|
|
82
|
+
if (!secret) {
|
|
83
|
+
return {
|
|
84
|
+
ok: false,
|
|
85
|
+
statusCode: 503,
|
|
86
|
+
error: 'gatekeeper secret not available',
|
|
87
|
+
reason: 'no-secret',
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const url = `${GATEKEEPER_BASE}${apiPath}`;
|
|
92
|
+
// Only set Content-Type when we actually have a body. Fastify rejects
|
|
93
|
+
// `Content-Type: application/json` with an empty body (e.g. DELETE) as
|
|
94
|
+
// 400 Bad Request.
|
|
95
|
+
const headers = { 'X-Gatekeeper-Secret': secret };
|
|
96
|
+
const options = { method, headers };
|
|
97
|
+
if (body !== undefined && body !== null) {
|
|
98
|
+
headers['Content-Type'] = 'application/json';
|
|
99
|
+
options.body = typeof body === 'string' ? body : JSON.stringify(body);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let response;
|
|
103
|
+
try {
|
|
104
|
+
response = await fetch(url, options);
|
|
105
|
+
} catch (err) {
|
|
106
|
+
return {
|
|
107
|
+
ok: false,
|
|
108
|
+
statusCode: 503,
|
|
109
|
+
error: `gatekeeper unreachable: ${err.message}`,
|
|
110
|
+
reason: 'unreachable',
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const text = await response.text();
|
|
115
|
+
let data;
|
|
116
|
+
try {
|
|
117
|
+
data = text.length > 0 ? JSON.parse(text) : null;
|
|
118
|
+
} catch {
|
|
119
|
+
data = { raw: text };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (response.status >= 400) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
statusCode: response.status,
|
|
126
|
+
error:
|
|
127
|
+
data && typeof data === 'object' && 'error' in data && typeof data.error === 'string'
|
|
128
|
+
? data.error
|
|
129
|
+
: `gatekeeper returned HTTP ${response.status}`,
|
|
130
|
+
reason: 'http-error',
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return { ok: true, statusCode: response.status, data };
|
|
135
|
+
}
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { mkdir, open, rename, rm } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const INVITE_DIR = '/var/www/lamaste/invite';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate and write the invitation acceptance HTML page. The web root
|
|
9
|
+
* belongs to the lamaste user (group www-data, setgid directories), so no
|
|
10
|
+
* privileges are needed; the file is 0640 and nginx reads it via the group.
|
|
11
|
+
*/
|
|
12
|
+
export async function writeInvitePage() {
|
|
13
|
+
const html = generateInvitePageHtml();
|
|
14
|
+
|
|
15
|
+
await mkdir(INVITE_DIR, { recursive: true, mode: 0o750 });
|
|
16
|
+
|
|
17
|
+
const dest = path.join(INVITE_DIR, 'index.html');
|
|
18
|
+
const tmp = path.join(INVITE_DIR, `.index-${crypto.randomBytes(8).toString('hex')}.html`);
|
|
19
|
+
try {
|
|
20
|
+
const fh = await open(tmp, 'wx', 0o640);
|
|
21
|
+
try {
|
|
22
|
+
await fh.writeFile(html, 'utf-8');
|
|
23
|
+
await fh.chmod(0o640);
|
|
24
|
+
await fh.sync();
|
|
25
|
+
} finally {
|
|
26
|
+
await fh.close();
|
|
27
|
+
}
|
|
28
|
+
await rename(tmp, dest);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
await rm(tmp, { force: true }).catch(() => {});
|
|
31
|
+
throw err;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function generateInvitePageHtml() {
|
|
36
|
+
return `<!DOCTYPE html>
|
|
37
|
+
<html lang="en">
|
|
38
|
+
<head>
|
|
39
|
+
<meta charset="utf-8">
|
|
40
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
41
|
+
<title>Lamaste — Accept Invitation</title>
|
|
42
|
+
<style>
|
|
43
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
44
|
+
body {
|
|
45
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
|
46
|
+
background: #09090b; color: #a1a1aa;
|
|
47
|
+
min-height: 100vh; display: flex; align-items: center; justify-content: center;
|
|
48
|
+
padding: 1.5rem;
|
|
49
|
+
}
|
|
50
|
+
.card {
|
|
51
|
+
background: #18181b; border: 1px solid #27272a; border-radius: 0.75rem;
|
|
52
|
+
max-width: 480px; width: 100%; padding: 2.5rem;
|
|
53
|
+
}
|
|
54
|
+
.icon { text-align: center; font-size: 3rem; margin-bottom: 1rem; }
|
|
55
|
+
h1 { color: #f4f4f5; font-size: 1.25rem; text-align: center; margin-bottom: 0.5rem; }
|
|
56
|
+
.subtitle { text-align: center; margin-bottom: 2rem; font-size: 0.875rem; }
|
|
57
|
+
.field { margin-bottom: 1.25rem; }
|
|
58
|
+
.field label {
|
|
59
|
+
display: block; font-size: 0.8125rem; color: #a1a1aa;
|
|
60
|
+
margin-bottom: 0.375rem; font-weight: 600;
|
|
61
|
+
}
|
|
62
|
+
.field input {
|
|
63
|
+
width: 100%; padding: 0.625rem 0.875rem;
|
|
64
|
+
background: #09090b; border: 1px solid #27272a; border-radius: 0.5rem;
|
|
65
|
+
color: #f4f4f5; font-family: inherit; font-size: 0.875rem;
|
|
66
|
+
outline: none; transition: border-color 0.15s;
|
|
67
|
+
}
|
|
68
|
+
.field input:focus { border-color: #22d3ee; }
|
|
69
|
+
.field input:disabled { color: #71717a; cursor: not-allowed; }
|
|
70
|
+
.field .hint { font-size: 0.75rem; color: #52525b; margin-top: 0.25rem; }
|
|
71
|
+
.error-msg {
|
|
72
|
+
background: #1c0f0f; border: 1px solid #7f1d1d; border-radius: 0.5rem;
|
|
73
|
+
padding: 0.75rem 1rem; margin-bottom: 1.25rem;
|
|
74
|
+
font-size: 0.8125rem; color: #fca5a5;
|
|
75
|
+
}
|
|
76
|
+
.success-card { text-align: center; }
|
|
77
|
+
.success-card h1 { color: #4ade80; margin-bottom: 1rem; }
|
|
78
|
+
.success-card p { margin-bottom: 1.5rem; font-size: 0.875rem; line-height: 1.6; }
|
|
79
|
+
.btn {
|
|
80
|
+
display: block; width: 100%;
|
|
81
|
+
background: #22d3ee; color: #09090b; font-weight: 700;
|
|
82
|
+
border: none; border-radius: 0.5rem; padding: 0.75rem;
|
|
83
|
+
font-size: 0.875rem; cursor: pointer; font-family: inherit;
|
|
84
|
+
transition: background-color 0.15s;
|
|
85
|
+
}
|
|
86
|
+
.btn:hover { background: #06b6d4; }
|
|
87
|
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
88
|
+
.btn-outline {
|
|
89
|
+
background: transparent; border: 1px solid #27272a; color: #a1a1aa;
|
|
90
|
+
}
|
|
91
|
+
.btn-outline:hover { border-color: #22d3ee; color: #f4f4f5; }
|
|
92
|
+
.loading {
|
|
93
|
+
display: flex; align-items: center; justify-content: center; gap: 0.5rem;
|
|
94
|
+
padding: 3rem 0; font-size: 0.875rem;
|
|
95
|
+
}
|
|
96
|
+
.spinner {
|
|
97
|
+
width: 1.25rem; height: 1.25rem;
|
|
98
|
+
border: 2px solid #27272a; border-top-color: #22d3ee;
|
|
99
|
+
border-radius: 50%; animation: spin 0.6s linear infinite;
|
|
100
|
+
}
|
|
101
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
102
|
+
.detail-row {
|
|
103
|
+
display: flex; justify-content: space-between; align-items: center;
|
|
104
|
+
padding: 0.5rem 0; border-bottom: 1px solid #27272a;
|
|
105
|
+
font-size: 0.8125rem;
|
|
106
|
+
}
|
|
107
|
+
.detail-row:last-child { border-bottom: none; }
|
|
108
|
+
.detail-label { color: #71717a; }
|
|
109
|
+
.detail-value { color: #e4e4e7; font-weight: 500; }
|
|
110
|
+
.details-box {
|
|
111
|
+
background: #09090b; border: 1px solid #27272a; border-radius: 0.5rem;
|
|
112
|
+
padding: 0.75rem 1rem; margin-bottom: 1.5rem;
|
|
113
|
+
}
|
|
114
|
+
</style>
|
|
115
|
+
</head>
|
|
116
|
+
<body>
|
|
117
|
+
<div class="card" id="app">
|
|
118
|
+
<div class="loading">
|
|
119
|
+
<div class="spinner"></div>
|
|
120
|
+
Loading invitation...
|
|
121
|
+
</div>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<script>
|
|
125
|
+
(function() {
|
|
126
|
+
var app = document.getElementById('app');
|
|
127
|
+
var pathParts = window.location.pathname.split('/');
|
|
128
|
+
var token = pathParts[pathParts.length - 1] || pathParts[pathParts.length - 2];
|
|
129
|
+
|
|
130
|
+
if (!token || token === 'invite') {
|
|
131
|
+
showError('Invalid invitation link.');
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Fetch invitation details
|
|
136
|
+
fetch('/api/invite/' + encodeURIComponent(token))
|
|
137
|
+
.then(function(res) {
|
|
138
|
+
if (res.ok) return res.json();
|
|
139
|
+
return res.json().then(function(data) { throw new Error(data.error || 'Invalid invitation'); });
|
|
140
|
+
})
|
|
141
|
+
.then(function(data) {
|
|
142
|
+
showForm(data);
|
|
143
|
+
})
|
|
144
|
+
.catch(function(err) {
|
|
145
|
+
showError(err.message);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
function showError(message) {
|
|
149
|
+
app.innerHTML =
|
|
150
|
+
'<div class="icon">\\u26A0\\uFE0F</div>' +
|
|
151
|
+
'<h1>Invitation Unavailable</h1>' +
|
|
152
|
+
'<p class="subtitle">' + escapeHtml(message) + '</p>';
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function showForm(data) {
|
|
156
|
+
var expiresDate = new Date(data.expiresAt);
|
|
157
|
+
var expiresStr = expiresDate.toLocaleDateString(undefined, {
|
|
158
|
+
year: 'numeric', month: 'long', day: 'numeric',
|
|
159
|
+
hour: '2-digit', minute: '2-digit'
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
app.innerHTML =
|
|
163
|
+
'<div class="icon">\\u2709\\uFE0F</div>' +
|
|
164
|
+
'<h1>You\\u2019ve Been Invited</h1>' +
|
|
165
|
+
'<p class="subtitle">Set your password to create your account.</p>' +
|
|
166
|
+
'<div class="details-box">' +
|
|
167
|
+
'<div class="detail-row">' +
|
|
168
|
+
'<span class="detail-label">Username</span>' +
|
|
169
|
+
'<span class="detail-value">' + escapeHtml(data.username) + '</span>' +
|
|
170
|
+
'</div>' +
|
|
171
|
+
'<div class="detail-row">' +
|
|
172
|
+
'<span class="detail-label">Email</span>' +
|
|
173
|
+
'<span class="detail-value">' + escapeHtml(data.email) + '</span>' +
|
|
174
|
+
'</div>' +
|
|
175
|
+
'<div class="detail-row">' +
|
|
176
|
+
'<span class="detail-label">Expires</span>' +
|
|
177
|
+
'<span class="detail-value">' + escapeHtml(expiresStr) + '</span>' +
|
|
178
|
+
'</div>' +
|
|
179
|
+
'</div>' +
|
|
180
|
+
'<div id="form-error"></div>' +
|
|
181
|
+
'<form id="accept-form">' +
|
|
182
|
+
'<div class="field">' +
|
|
183
|
+
'<label for="password">Password</label>' +
|
|
184
|
+
'<input type="password" id="password" placeholder="Minimum 8 characters" required minlength="8" maxlength="128">' +
|
|
185
|
+
'</div>' +
|
|
186
|
+
'<div class="field">' +
|
|
187
|
+
'<label for="confirm-password">Confirm Password</label>' +
|
|
188
|
+
'<input type="password" id="confirm-password" placeholder="Re-enter your password" required>' +
|
|
189
|
+
'</div>' +
|
|
190
|
+
'<button type="submit" class="btn" id="submit-btn">Create Account</button>' +
|
|
191
|
+
'</form>';
|
|
192
|
+
|
|
193
|
+
document.getElementById('accept-form').addEventListener('submit', function(e) {
|
|
194
|
+
e.preventDefault();
|
|
195
|
+
var password = document.getElementById('password').value;
|
|
196
|
+
var confirm = document.getElementById('confirm-password').value;
|
|
197
|
+
var errorDiv = document.getElementById('form-error');
|
|
198
|
+
var submitBtn = document.getElementById('submit-btn');
|
|
199
|
+
|
|
200
|
+
errorDiv.innerHTML = '';
|
|
201
|
+
|
|
202
|
+
if (password.length < 8) {
|
|
203
|
+
errorDiv.innerHTML = '<div class="error-msg">Password must be at least 8 characters.</div>';
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (password !== confirm) {
|
|
207
|
+
errorDiv.innerHTML = '<div class="error-msg">Passwords do not match.</div>';
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
submitBtn.disabled = true;
|
|
212
|
+
submitBtn.textContent = 'Creating account...';
|
|
213
|
+
|
|
214
|
+
fetch('/api/invite/' + encodeURIComponent(token) + '/accept', {
|
|
215
|
+
method: 'POST',
|
|
216
|
+
headers: { 'Content-Type': 'application/json' },
|
|
217
|
+
body: JSON.stringify({ password: password })
|
|
218
|
+
})
|
|
219
|
+
.then(function(res) {
|
|
220
|
+
if (res.ok) return res.json();
|
|
221
|
+
return res.json().then(function(d) { throw new Error(d.error || 'Failed to create account'); });
|
|
222
|
+
})
|
|
223
|
+
.then(function(result) {
|
|
224
|
+
showSuccess(result);
|
|
225
|
+
})
|
|
226
|
+
.catch(function(err) {
|
|
227
|
+
errorDiv.innerHTML = '<div class="error-msg">' + escapeHtml(err.message) + '</div>';
|
|
228
|
+
submitBtn.disabled = false;
|
|
229
|
+
submitBtn.textContent = 'Create Account';
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function showSuccess(result) {
|
|
235
|
+
app.className = 'card success-card';
|
|
236
|
+
var loginBtn = result.loginUrl
|
|
237
|
+
? '<a href="' + escapeHtml(result.loginUrl) + '" class="btn" style="text-decoration:none;text-align:center;display:block;margin-top:1rem">Go to Login</a>'
|
|
238
|
+
: '';
|
|
239
|
+
|
|
240
|
+
if (result.totpUri) {
|
|
241
|
+
app.innerHTML =
|
|
242
|
+
'<div class="icon">\\u2705</div>' +
|
|
243
|
+
'<h1>Account Created</h1>' +
|
|
244
|
+
'<p>Your account <strong style="color:#22d3ee">' + escapeHtml(result.username) + '</strong> has been created.</p>' +
|
|
245
|
+
'<p style="margin-bottom:1.5rem">Scan this QR code with your authenticator app (Google Authenticator, Authy, etc.) to set up two-factor authentication.</p>' +
|
|
246
|
+
'<div id="qr-container" style="display:flex;justify-content:center;margin-bottom:1.5rem;background:#fff;border-radius:0.5rem;padding:1rem;max-width:220px;margin-left:auto;margin-right:auto"></div>' +
|
|
247
|
+
'<p style="font-size:0.75rem;color:#52525b;margin-bottom:1rem;word-break:break-all"><strong style="color:#a1a1aa">Manual entry:</strong> ' + escapeHtml(result.totpUri.match(/secret=([^&]+)/)?.[1] || '') + '</p>' +
|
|
248
|
+
'<div class="error-msg" style="background:#0f1c1c;border-color:#134e4a;color:#5eead4;text-align:center">\\u26A0\\uFE0F Save this before navigating away — it cannot be shown again.</div>' +
|
|
249
|
+
loginBtn;
|
|
250
|
+
|
|
251
|
+
// Load QR code library and render
|
|
252
|
+
var script = document.createElement('script');
|
|
253
|
+
script.src = 'https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js';
|
|
254
|
+
script.onload = function() {
|
|
255
|
+
var qr = qrcode(0, 'M');
|
|
256
|
+
qr.addData(result.totpUri);
|
|
257
|
+
qr.make();
|
|
258
|
+
var container = document.getElementById('qr-container');
|
|
259
|
+
if (container) container.innerHTML = qr.createSvgTag(4, 0);
|
|
260
|
+
};
|
|
261
|
+
document.head.appendChild(script);
|
|
262
|
+
} else {
|
|
263
|
+
app.innerHTML =
|
|
264
|
+
'<div class="icon">\\u2705</div>' +
|
|
265
|
+
'<h1>Account Created</h1>' +
|
|
266
|
+
'<p>Your account <strong style="color:#22d3ee">' + escapeHtml(result.username) + '</strong> has been created successfully.</p>' +
|
|
267
|
+
'<p>Contact your administrator to set up two-factor authentication.</p>' +
|
|
268
|
+
loginBtn;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function escapeHtml(str) {
|
|
273
|
+
var div = document.createElement('div');
|
|
274
|
+
div.appendChild(document.createTextNode(str));
|
|
275
|
+
return div.innerHTML;
|
|
276
|
+
}
|
|
277
|
+
})();
|
|
278
|
+
</script>
|
|
279
|
+
</body>
|
|
280
|
+
</html>`;
|
|
281
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* IP address matching utilities — allow/deny list evaluation with CIDR support.
|
|
3
|
+
* Used by push-install policy enforcement.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Check whether an IP is allowed given allow and deny lists.
|
|
8
|
+
* Deny list takes precedence. Empty allow list means all IPs allowed.
|
|
9
|
+
* @param {string} ip
|
|
10
|
+
* @param {string[]} allowedIps
|
|
11
|
+
* @param {string[]} deniedIps
|
|
12
|
+
* @returns {boolean}
|
|
13
|
+
*/
|
|
14
|
+
export function isIpAllowed(ip, allowedIps, deniedIps) {
|
|
15
|
+
// Check deny list first (takes precedence)
|
|
16
|
+
if (deniedIps.length > 0 && matchesAny(ip, deniedIps)) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Empty allow list means all IPs allowed
|
|
21
|
+
if (allowedIps.length === 0) {
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check allow list
|
|
26
|
+
return matchesAny(ip, allowedIps);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Strip the ::ffff: prefix from IPv4-mapped IPv6 addresses so that
|
|
31
|
+
* comparisons work consistently against plain IPv4 entries.
|
|
32
|
+
*/
|
|
33
|
+
function normalizeIp(ip) {
|
|
34
|
+
if (ip.startsWith('::ffff:')) {
|
|
35
|
+
return ip.slice(7);
|
|
36
|
+
}
|
|
37
|
+
return ip;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check if an IP matches any entry in a list of IPs/CIDRs.
|
|
42
|
+
*/
|
|
43
|
+
function matchesAny(ip, list) {
|
|
44
|
+
const normalized = normalizeIp(ip);
|
|
45
|
+
for (const entry of list) {
|
|
46
|
+
const normalizedEntry = normalizeIp(entry);
|
|
47
|
+
if (normalizedEntry.includes('/')) {
|
|
48
|
+
if (ipInCidr(normalized, normalizedEntry)) return true;
|
|
49
|
+
} else {
|
|
50
|
+
if (normalized === normalizedEntry) return true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Check if an IPv4 address is within a CIDR range.
|
|
58
|
+
*/
|
|
59
|
+
function ipInCidr(ip, cidr) {
|
|
60
|
+
const [range, bitsStr] = cidr.split('/');
|
|
61
|
+
const bits = parseInt(bitsStr, 10);
|
|
62
|
+
if (bits < 0 || bits > 32) return false;
|
|
63
|
+
const mask = bits === 0 ? 0 : ~(2 ** (32 - bits) - 1);
|
|
64
|
+
const ipNum = ipToNum(ip);
|
|
65
|
+
const rangeNum = ipToNum(range);
|
|
66
|
+
if (ipNum === null || rangeNum === null) return false;
|
|
67
|
+
return (ipNum & mask) === (rangeNum & mask);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Convert an IPv4 address string to a 32-bit number.
|
|
72
|
+
*/
|
|
73
|
+
function ipToNum(ip) {
|
|
74
|
+
const parts = ip.split('.');
|
|
75
|
+
if (parts.length !== 4) return null;
|
|
76
|
+
const nums = parts.map(Number);
|
|
77
|
+
if (nums.some((n) => isNaN(n) || n < 0 || n > 255)) return null;
|
|
78
|
+
return ((nums[0] << 24) | (nums[1] << 16) | (nums[2] << 8) | nums[3]) >>> 0;
|
|
79
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
CREATE TABLE enrollment_tokens (
|
|
2
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
3
|
+
token TEXT NOT NULL,
|
|
4
|
+
label TEXT NOT NULL,
|
|
5
|
+
capabilities TEXT NOT NULL,
|
|
6
|
+
allowed_sites TEXT NOT NULL,
|
|
7
|
+
type TEXT,
|
|
8
|
+
delegated_by TEXT,
|
|
9
|
+
scope TEXT,
|
|
10
|
+
created_at TEXT NOT NULL,
|
|
11
|
+
expires_at TEXT NOT NULL,
|
|
12
|
+
used INTEGER NOT NULL DEFAULT 0,
|
|
13
|
+
used_at TEXT
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX idx_enrollment_label_active ON enrollment_tokens(label, used);
|
|
17
|
+
CREATE INDEX idx_enrollment_created_at ON enrollment_tokens(created_at);
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
CREATE TABLE tickets (
|
|
2
|
+
index_hash TEXT PRIMARY KEY,
|
|
3
|
+
id TEXT NOT NULL,
|
|
4
|
+
scope TEXT NOT NULL,
|
|
5
|
+
instance_id TEXT NOT NULL,
|
|
6
|
+
source TEXT NOT NULL,
|
|
7
|
+
target TEXT NOT NULL,
|
|
8
|
+
created_at TEXT NOT NULL,
|
|
9
|
+
expires_at TEXT NOT NULL,
|
|
10
|
+
used INTEGER NOT NULL DEFAULT 0,
|
|
11
|
+
used_at TEXT,
|
|
12
|
+
session_id TEXT,
|
|
13
|
+
transport TEXT NOT NULL
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE INDEX idx_tickets_target_active ON tickets(target, used);
|
|
17
|
+
CREATE INDEX idx_tickets_instance_id ON tickets(instance_id);
|
|
18
|
+
CREATE INDEX idx_tickets_created_at ON tickets(created_at);
|
|
19
|
+
|
|
20
|
+
CREATE TABLE ticket_scopes (
|
|
21
|
+
name TEXT PRIMARY KEY,
|
|
22
|
+
version TEXT NOT NULL,
|
|
23
|
+
description TEXT NOT NULL,
|
|
24
|
+
scopes TEXT NOT NULL,
|
|
25
|
+
transport TEXT NOT NULL,
|
|
26
|
+
hooks TEXT NOT NULL DEFAULT '{}',
|
|
27
|
+
installed_at TEXT NOT NULL
|
|
28
|
+
);
|
|
29
|
+
|
|
30
|
+
CREATE TABLE ticket_instances (
|
|
31
|
+
instance_id TEXT PRIMARY KEY,
|
|
32
|
+
scope TEXT NOT NULL,
|
|
33
|
+
agent_label TEXT NOT NULL,
|
|
34
|
+
registered_at TEXT NOT NULL,
|
|
35
|
+
last_heartbeat TEXT NOT NULL,
|
|
36
|
+
status TEXT NOT NULL,
|
|
37
|
+
transport TEXT NOT NULL
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
CREATE INDEX idx_ticket_instances_scope_agent ON ticket_instances(scope, agent_label);
|
|
41
|
+
CREATE INDEX idx_ticket_instances_status ON ticket_instances(status);
|
|
42
|
+
|
|
43
|
+
CREATE TABLE ticket_assignments (
|
|
44
|
+
agent_label TEXT NOT NULL,
|
|
45
|
+
instance_scope TEXT NOT NULL,
|
|
46
|
+
assigned_at TEXT NOT NULL,
|
|
47
|
+
assigned_by TEXT NOT NULL DEFAULT 'admin',
|
|
48
|
+
PRIMARY KEY (agent_label, instance_scope)
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
CREATE INDEX idx_ticket_assignments_instance_scope ON ticket_assignments(instance_scope);
|
|
52
|
+
|
|
53
|
+
CREATE TABLE ticket_sessions (
|
|
54
|
+
session_id TEXT PRIMARY KEY,
|
|
55
|
+
ticket_id TEXT NOT NULL,
|
|
56
|
+
scope TEXT NOT NULL,
|
|
57
|
+
instance_id TEXT NOT NULL,
|
|
58
|
+
source TEXT NOT NULL,
|
|
59
|
+
target TEXT NOT NULL,
|
|
60
|
+
created_at TEXT NOT NULL,
|
|
61
|
+
last_activity_at TEXT NOT NULL,
|
|
62
|
+
status TEXT NOT NULL,
|
|
63
|
+
reconnect_grace_seconds INTEGER NOT NULL DEFAULT 60,
|
|
64
|
+
terminated_by TEXT,
|
|
65
|
+
terminated_at TEXT
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
CREATE INDEX idx_sessions_target_active ON ticket_sessions(target, status);
|
|
69
|
+
CREATE INDEX idx_sessions_instance_id ON ticket_sessions(instance_id);
|
|
70
|
+
CREATE INDEX idx_sessions_last_activity ON ticket_sessions(last_activity_at);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
CREATE TABLE plugins (
|
|
2
|
+
name TEXT PRIMARY KEY,
|
|
3
|
+
display_name TEXT,
|
|
4
|
+
package_name TEXT NOT NULL,
|
|
5
|
+
version TEXT NOT NULL,
|
|
6
|
+
description TEXT,
|
|
7
|
+
capabilities TEXT NOT NULL DEFAULT '[]',
|
|
8
|
+
packages TEXT,
|
|
9
|
+
panel TEXT,
|
|
10
|
+
config TEXT,
|
|
11
|
+
modes TEXT,
|
|
12
|
+
status TEXT NOT NULL,
|
|
13
|
+
installed_at TEXT NOT NULL,
|
|
14
|
+
enabled_at TEXT
|
|
15
|
+
);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
CREATE TABLE agents (
|
|
2
|
+
label TEXT PRIMARY KEY,
|
|
3
|
+
serial TEXT NOT NULL,
|
|
4
|
+
capabilities TEXT NOT NULL DEFAULT '[]',
|
|
5
|
+
allowed_sites TEXT NOT NULL DEFAULT '[]',
|
|
6
|
+
enrollment_method TEXT NOT NULL DEFAULT 'p12',
|
|
7
|
+
delegated_by TEXT,
|
|
8
|
+
created_at TEXT NOT NULL,
|
|
9
|
+
expires_at TEXT NOT NULL,
|
|
10
|
+
revoked INTEGER NOT NULL DEFAULT 0,
|
|
11
|
+
revoked_at TEXT
|
|
12
|
+
);
|
|
13
|
+
CREATE INDEX idx_agents_serial ON agents(serial);
|
|
14
|
+
|
|
15
|
+
CREATE TABLE revoked_certs (
|
|
16
|
+
serial TEXT PRIMARY KEY,
|
|
17
|
+
label TEXT NOT NULL,
|
|
18
|
+
revoked_at TEXT NOT NULL
|
|
19
|
+
);
|