@lamalibre/portlama-agent 1.0.22 → 1.0.24

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lamalibre/portlama-agent",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "Tunnel agent for Portlama — manages Chisel tunnel client as a system service",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -21,7 +21,7 @@ import { agentDataDir, agentPluginsFile, agentPluginsDir } from './platform.js';
21
21
  const RESERVED_NAMES = [
22
22
  'health', 'onboarding', 'invite', 'enroll', 'tunnels', 'sites', 'system',
23
23
  'services', 'logs', 'users', 'certs', 'invitations', 'plugins', 'tickets',
24
- 'settings', 'identity', 'storage', 'agents',
24
+ 'settings', 'identity', 'storage', 'agents', 'user-access',
25
25
  ];
26
26
 
27
27
  // --- Promise-chain mutex (serialises registry modifications) ---
@@ -7,7 +7,8 @@ import { localDir, localPluginsFile, localPluginsDir } from './platform.js';
7
7
  // Reserved names that cannot be used as plugin names (matches panel-server constants).
8
8
  const RESERVED_NAMES = [
9
9
  'health', 'onboarding', 'invite', 'enroll', 'tunnels', 'sites', 'system',
10
- 'services', 'logs', 'users', 'certs', 'invitations', 'plugins', 'tickets', 'settings',
10
+ 'services', 'logs', 'users', 'certs', 'invitations', 'plugins', 'tickets',
11
+ 'settings', 'identity', 'storage', 'agents', 'user-access',
11
12
  ];
12
13
 
13
14
  // --- Promise-chain mutex (serialises registry modifications) ---
@@ -67,8 +67,15 @@ export async function startPanelServer(label, { port = 9393 } = {}) {
67
67
  // Allow health check without auth
68
68
  if (request.url === '/api/health') return;
69
69
 
70
- // Static assets don't need API-level auth (nginx already verified mTLS)
71
- if (!request.url.startsWith('/api')) return;
70
+ // Plugin bundles are intentionally public (loaded via <script> tag)
71
+ if (request.url.startsWith('/plugin-bundles/')) return;
72
+
73
+ // Auth is required for /api/* management routes AND all plugin routes
74
+ // (/<pluginName>/..., including non-/api/ sub-paths). Static assets (SPA
75
+ // files served by fastify-static from root) don't need auth.
76
+ const needsAuth = request.url.startsWith('/api') ||
77
+ /^\/[a-z0-9-]+\//.test(request.url);
78
+ if (!needsAuth) return;
72
79
 
73
80
  const verify = request.headers['x-ssl-client-verify'];
74
81
  if (verify !== 'SUCCESS') {
@@ -85,6 +92,35 @@ export async function startPanelServer(label, { port = 9393 } = {}) {
85
92
  request.certRole = 'agent';
86
93
  return;
87
94
  }
95
+
96
+ // Authelia-authenticated user (plugin tunnel access).
97
+ // When nginx routes through an Authelia-protected plugin vhost,
98
+ // Remote-User is set after successful Authelia forward auth. The header
99
+ // is cleared then re-injected by nginx — cannot be forged from outside.
100
+ // Port 9393 binds 127.0.0.1, so external traffic only arrives via nginx.
101
+ //
102
+ // Authelia users are restricted to plugin routes only (/{pluginName}/...).
103
+ // They must NOT access agent management API (/api/*) — those require
104
+ // mTLS (admin/agent cert) or localhost origin (desktop app).
105
+ const remoteUser = request.headers['remote-user'];
106
+ if (remoteUser) {
107
+ // Validate username format (defense-in-depth against header injection)
108
+ if (!/^[a-z0-9_.-]+$/i.test(remoteUser)) {
109
+ return reply.code(403).send({ error: 'Invalid user identity' });
110
+ }
111
+
112
+ // Block access to /api/* management endpoints — Authelia users
113
+ // may only access plugin routes (/{pluginName}/api/...)
114
+ if (request.url.startsWith('/api')) {
115
+ return reply.code(403).send({ error: 'Management API requires certificate authentication' });
116
+ }
117
+
118
+ request.certCN = `user:${remoteUser}`;
119
+ request.certRole = 'user';
120
+ request.autheliaUser = remoteUser;
121
+ return;
122
+ }
123
+
88
124
  return reply.code(403).send({ error: 'Valid mTLS certificate required' });
89
125
  }
90
126