Skip to content

Commit ef9e6f7

Browse files
barckcodeclaude
andcommitted
feat: add authentication documentation and update installer for local auth
- Add full authentication docs page (EN + ES) covering auth modes, organizations, RBAC, invites, password management, WebSocket auth, and security details - Update install.sh to default to AUTH_PROVIDER=local with auto-generated JWT_SECRET and SETTINGS_ENCRYPTION_KEY using openssl rand - Add auth environment variables to docker-compose.yml, .env.example, and configuration docs (AUTH_PROVIDER, JWT_SECRET, SETTINGS_ENCRYPTION_KEY, MULTI_TENANT, JWT_ACCESS_EXPIRATION, JWT_REFRESH_EXPIRATION) - Update architecture docs to mention auth/authorization layer and multi-tenant data isolation in the API Server section - Update quick-start guide with new registration step and auth token generation in the manual install flow - Add Authentication link to docs sidebar, docs index, and i18n translations Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 106a75e commit ef9e6f7

File tree

14 files changed

+746
-20
lines changed

14 files changed

+746
-20
lines changed

.env.example

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,22 @@
11
# Required: Generate a secure random token for NATS authentication
22
NATS_AUTH_TOKEN=change-me-to-a-secure-token
33

4+
# Required: Secret for signing JWT tokens (minimum 32 characters)
5+
JWT_SECRET=change-me-to-a-secure-random-string-at-least-32-chars
6+
7+
# Required: Key for encrypting sensitive data at rest (API keys, invite tokens)
8+
SETTINGS_ENCRYPTION_KEY=change-me-to-a-secure-random-string
9+
10+
# Authentication provider: "local" (email/password) or "noop" (no auth)
11+
AUTH_PROVIDER=local
12+
13+
# Multi-tenancy: false = single organization, true = unlimited organizations
14+
MULTI_TENANT=false
15+
16+
# Optional: JWT token expiration times
17+
# JWT_ACCESS_EXPIRATION=24h
18+
# JWT_REFRESH_EXPIRATION=7d
19+
420
# Optional: Customize ports (defaults shown)
521
# API_PORT=3000
622
# FRONTEND_PORT=8080

docker-compose.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,12 @@ services:
1919
- NATS_AUTH_TOKEN=${NATS_AUTH_TOKEN}
2020
- DATABASE_PATH=/data/agentcrew.db
2121
- AGENT_IMAGE=${AGENT_IMAGE:-ghcr.io/helmcode/agent_crew_agent:0.3.0}
22+
- SETTINGS_ENCRYPTION_KEY=${SETTINGS_ENCRYPTION_KEY}
23+
- AUTH_PROVIDER=${AUTH_PROVIDER:-local}
24+
- JWT_SECRET=${JWT_SECRET}
25+
- JWT_ACCESS_EXPIRATION=${JWT_ACCESS_EXPIRATION:-24h}
26+
- JWT_REFRESH_EXPIRATION=${JWT_REFRESH_EXPIRATION:-7d}
27+
- MULTI_TENANT=${MULTI_TENANT:-false}
2228
volumes:
2329
- /var/run/docker.sock:/var/run/docker.sock
2430
- agentcrew_db:/data

web/public/install.sh

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,12 +66,18 @@ check_dependencies() {
6666
if ! command -v curl &>/dev/null; then
6767
fail "curl is not installed. Please install curl and try again."
6868
fi
69+
70+
if ! command -v openssl &>/dev/null; then
71+
fail "openssl is not installed. Please install openssl and try again."
72+
fi
6973
}
7074

7175
# ── Install ─────────────────────────────────────────────────────────
7276
install() {
73-
# Generate NATS auth token
74-
NATS_TOKEN=$(openssl rand -hex 16 2>/dev/null || head -c 32 /dev/urandom | od -An -tx1 | tr -d ' \n')
77+
# Generate secure tokens
78+
NATS_TOKEN=$(openssl rand -base64 24)
79+
JWT_SECRET=$(openssl rand -base64 48)
80+
ENCRYPTION_KEY=$(openssl rand -base64 32)
7581

7682
# Create install directory
7783
if [[ -d "$INSTALL_DIR" ]]; then
@@ -93,18 +99,30 @@ install() {
9399
curl -fsSL -o "$INSTALL_DIR/docker-compose.yml" "$REPO_BASE/docker-compose.yml"
94100
success "docker-compose.yml downloaded"
95101

96-
# Create .env file (preserve existing to keep NATS token)
102+
# Create .env file (preserve existing to keep tokens)
97103
if [[ -f "$INSTALL_DIR/.env" ]]; then
98104
info "Existing .env found, keeping current configuration."
99105
else
100106
info "Generating configuration..."
101107
{
102108
echo "# AgentCrew configuration — generated by install.sh"
109+
echo ""
110+
echo "# Internal messaging token (auto-generated)"
103111
echo "NATS_AUTH_TOKEN=$NATS_TOKEN"
112+
echo ""
113+
echo "# Authentication"
114+
echo "AUTH_PROVIDER=local"
115+
echo "JWT_SECRET=$JWT_SECRET"
116+
echo "SETTINGS_ENCRYPTION_KEY=$ENCRYPTION_KEY"
117+
echo "MULTI_TENANT=false"
118+
echo ""
119+
echo "# Optional overrides"
104120
echo "# API_PORT=3000"
105121
echo "# FRONTEND_PORT=8080"
122+
echo "# JWT_ACCESS_EXPIRATION=24h"
123+
echo "# JWT_REFRESH_EXPIRATION=7d"
106124
} > "$INSTALL_DIR/.env"
107-
success ".env created with secure NATS token"
125+
success ".env created with secure tokens"
108126
fi
109127

110128
# Start the stack
@@ -137,7 +155,10 @@ summary() {
137155
echo ""
138156
echo -e " ${BOLD}Open:${NC} http://localhost:8080"
139157
echo ""
140-
echo -e " ${YELLOW}Next step: go to Settings (gear icon) and add your"
158+
echo -e " ${YELLOW}First time? Create your account at the registration page."
159+
echo -e " The first user becomes the organization admin.${NC}"
160+
echo ""
161+
echo -e " ${YELLOW}Then go to Settings (gear icon) and add your"
141162
echo -e " Anthropic API key or OAuth token to start deploying agents.${NC}"
142163
echo ""
143164
echo -e " ${BOLD}Manage:${NC}"

web/src/layouts/DocsLayout.astro

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const sidebarSections: NavSection[] = [
5555
{ href: `${base}/docs/webhooks`, label: t('docs.webhooks') },
5656
{ href: `${base}/docs/post-actions`, label: t('docs.postActions') },
5757
{ href: `${base}/docs/mcp`, label: t('docs.mcp') },
58+
{ href: `${base}/docs/authentication`, label: t('docs.authentication') },
5859
{ href: `${base}/docs/architecture`, label: t('docs.architecture') },
5960
],
6061
},

web/src/pages/docs/architecture.astro

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,12 @@ import DocsLayout from '../../layouts/DocsLayout.astro';
6868
</p>
6969

7070
<ul>
71+
<li><strong>Authentication and authorization:</strong> Built-in auth layer with JWT tokens, user management, organizations, and role-based access control (admin/member). See <a href="/docs/authentication">Authentication</a>.</li>
7172
<li>REST API endpoints for CRUD operations on teams, agents, and skills.</li>
7273
<li>Application settings management (API keys, configuration).</li>
7374
<li>Docker runtime management, including creating networks, volumes, and containers for each team.</li>
7475
<li>NATS message routing between the frontend and agent containers.</li>
76+
<li>Multi-tenant data isolation: all queries are scoped to the user's organization.</li>
7577
<li>SQLite database access for persistent storage.</li>
7678
</ul>
7779

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
---
2+
import DocsLayout from '../../layouts/DocsLayout.astro';
3+
---
4+
5+
<DocsLayout title="Authentication | AgentCrew" lang="en">
6+
<div class="docs-prose">
7+
<h1>Authentication</h1>
8+
9+
<p>
10+
AgentCrew includes a built-in authentication system with user management,
11+
organizations, invite-based onboarding, and role-based access control.
12+
Authentication is configured via the <code>AUTH_PROVIDER</code> environment
13+
variable and supports two modes: <strong>local</strong> (default) and
14+
<strong>noop</strong>.
15+
</p>
16+
17+
<h2>Authentication Modes</h2>
18+
19+
<h3>Local Authentication (default)</h3>
20+
21+
<p>
22+
Set <code>AUTH_PROVIDER=local</code> to enable email/password authentication
23+
with JWT tokens. This is the default mode when using the installer.
24+
</p>
25+
26+
<ul>
27+
<li>Users register with email, password, name, and organization name.</li>
28+
<li>Passwords are hashed with <strong>bcrypt</strong> (cost factor 12).</li>
29+
<li>JWT access tokens (default 24h) and refresh tokens (default 7d) are issued on login.</li>
30+
<li>Tokens are signed with <strong>HMAC-SHA256</strong> using the <code>JWT_SECRET</code> environment variable.</li>
31+
<li>The frontend automatically refreshes expired access tokens using the refresh token.</li>
32+
</ul>
33+
34+
<p>
35+
Required environment variables for local mode:
36+
</p>
37+
38+
<table>
39+
<thead>
40+
<tr>
41+
<th>Variable</th>
42+
<th>Description</th>
43+
</tr>
44+
</thead>
45+
<tbody>
46+
<tr>
47+
<td><code>JWT_SECRET</code></td>
48+
<td>Secret for signing JWT tokens. Minimum 32 characters. Generate with <code>openssl rand -base64 48</code>.</td>
49+
</tr>
50+
<tr>
51+
<td><code>SETTINGS_ENCRYPTION_KEY</code></td>
52+
<td>Key for encrypting sensitive data (API keys, invite tokens) at rest using AES-256-GCM. Generate with <code>openssl rand -base64 32</code>.</td>
53+
</tr>
54+
</tbody>
55+
</table>
56+
57+
<h3>Noop Authentication (no auth)</h3>
58+
59+
<p>
60+
Set <code>AUTH_PROVIDER=noop</code> to disable authentication entirely. In
61+
this mode:
62+
</p>
63+
64+
<ul>
65+
<li>No login screen is shown.</li>
66+
<li>A default organization and user are created automatically.</li>
67+
<li>All API requests are accepted without tokens.</li>
68+
<li>Ideal for local development, testing, or single-user self-hosted setups where security is handled at the network level.</li>
69+
</ul>
70+
71+
<blockquote>
72+
<strong>Note:</strong> Switching from <code>noop</code> to <code>local</code>
73+
is seamless. Existing data (teams, agents, settings) is automatically
74+
associated with the default organization. You will just need to create
75+
your first user account.
76+
</blockquote>
77+
78+
<h2>Organizations</h2>
79+
80+
<p>
81+
Every user belongs to an organization. Organizations provide <strong>data
82+
isolation</strong>: teams, agents, schedules, webhooks, settings, and all
83+
other resources are scoped to the organization. Users in one organization
84+
cannot see or access resources from another.
85+
</p>
86+
87+
<h3>Single-Tenant Mode (default)</h3>
88+
89+
<p>
90+
With <code>MULTI_TENANT=false</code> (the default), only one organization
91+
can exist. The first user who registers creates the organization and becomes
92+
its admin. Additional users can only join via invite.
93+
</p>
94+
95+
<h3>Multi-Tenant Mode</h3>
96+
97+
<p>
98+
With <code>MULTI_TENANT=true</code>, anyone can register and create a new
99+
organization. Each organization is fully isolated with its own users,
100+
teams, settings, and data.
101+
</p>
102+
103+
<h2>User Roles</h2>
104+
105+
<p>
106+
AgentCrew uses a simple two-role model:
107+
</p>
108+
109+
<table>
110+
<thead>
111+
<tr>
112+
<th>Role</th>
113+
<th>Capabilities</th>
114+
</tr>
115+
</thead>
116+
<tbody>
117+
<tr>
118+
<td><strong>Admin</strong></td>
119+
<td>Full access. Can manage organization settings, invite users, change roles, reset passwords, and remove members. The first user (organization creator) is always an admin and cannot be downgraded.</td>
120+
</tr>
121+
<tr>
122+
<td><strong>Member</strong></td>
123+
<td>Can create and manage teams, agents, schedules, webhooks, and settings. Cannot manage users or organization settings.</td>
124+
</tr>
125+
</tbody>
126+
</table>
127+
128+
<blockquote>
129+
<strong>Safety:</strong> The system enforces that at least one admin always
130+
exists. The organization owner cannot be removed or downgraded.
131+
</blockquote>
132+
133+
<h2>First-Time Setup</h2>
134+
135+
<ol>
136+
<li>
137+
<strong>Install AgentCrew</strong> using the
138+
<a href="/docs/quick-start">Quick Start</a> guide. The installer
139+
generates all required secrets automatically.
140+
</li>
141+
<li>
142+
<strong>Open the UI</strong> at <code>http://localhost:8080</code>. You
143+
will see the registration page.
144+
</li>
145+
<li>
146+
<strong>Create your account</strong> with your name, email, password, and
147+
organization name. This account becomes the organization admin.
148+
</li>
149+
<li>
150+
<strong>Add API keys</strong> in Settings to start deploying agent teams.
151+
</li>
152+
</ol>
153+
154+
<h2>Inviting Users</h2>
155+
156+
<p>
157+
Admins can invite new users from the <strong>Organization Settings</strong>
158+
page:
159+
</p>
160+
161+
<ol>
162+
<li>Go to <strong>Organization Settings</strong> in the sidebar.</li>
163+
<li>In the <strong>Invitations</strong> section, enter the email address and click <strong>Invite</strong>.</li>
164+
<li>Copy the invite link from the listing and share it with the user.</li>
165+
<li>The invited user opens the link, fills in their name and password, and joins the organization.</li>
166+
</ol>
167+
168+
<p>
169+
Invitations expire after <strong>7 days</strong>. Admins can cancel pending
170+
invitations at any time. The system prevents duplicate invites: you cannot
171+
invite an email that is already a member or has a pending invitation.
172+
</p>
173+
174+
<h2>Password Management</h2>
175+
176+
<h3>Changing Your Password</h3>
177+
178+
<p>
179+
Users can change their own password from their <strong>Profile</strong>
180+
page. They must enter their current password and the new one. Passwords
181+
must be at least 8 characters and include an uppercase letter, a lowercase
182+
letter, and a digit.
183+
</p>
184+
185+
<h3>Admin Password Reset</h3>
186+
187+
<p>
188+
Admins can reset any member's password from the Organization Settings page.
189+
When reset, a temporary password is generated and displayed. The user must
190+
change it on their next login (a forced password change screen is shown).
191+
</p>
192+
193+
<h2>WebSocket Authentication</h2>
194+
195+
<p>
196+
Real-time chat connections use WebSocket. Since WebSocket does not support
197+
custom headers, the JWT token is passed as a query parameter:
198+
</p>
199+
200+
<pre><code class="language-text">ws://localhost:8080/api/ws/team/&lt;team-id&gt;?token=&lt;jwt-access-token&gt;</code></pre>
201+
202+
<p>
203+
The API server validates the token before upgrading the connection. In
204+
<code>noop</code> mode, no token is required.
205+
</p>
206+
207+
<h2>Security Details</h2>
208+
209+
<ul>
210+
<li><strong>Password hashing:</strong> bcrypt with cost factor 12.</li>
211+
<li><strong>JWT signing:</strong> HMAC-SHA256. The <code>none</code> algorithm is explicitly blocked.</li>
212+
<li><strong>Token types:</strong> Access and refresh tokens include a <code>type</code> claim to prevent cross-use.</li>
213+
<li><strong>Invite tokens:</strong> 32 bytes from <code>crypto/rand</code>. A SHA-256 hash is stored in the database; the raw token is encrypted with AES-256-GCM for admin retrieval.</li>
214+
<li><strong>Sensitive data at rest:</strong> API keys and settings values are encrypted with AES-256-GCM using <code>SETTINGS_ENCRYPTION_KEY</code>.</li>
215+
<li><strong>Email normalization:</strong> All emails are lowercased and trimmed to prevent case-sensitivity issues.</li>
216+
<li><strong>Data isolation:</strong> All database queries are scoped to the user's organization ID, enforced at the middleware level.</li>
217+
</ul>
218+
219+
<h2>Disabling Authentication</h2>
220+
221+
<p>
222+
To run AgentCrew without authentication (useful for local development or
223+
trusted networks):
224+
</p>
225+
226+
<pre><code class="language-bash"># In your .env file
227+
AUTH_PROVIDER=noop</code></pre>
228+
229+
<p>
230+
In noop mode, <code>JWT_SECRET</code> and <code>SETTINGS_ENCRYPTION_KEY</code>
231+
are not required. All API endpoints accept unauthenticated requests, and a
232+
default user and organization are created automatically.
233+
</p>
234+
235+
<h2>Next Steps</h2>
236+
237+
<ul>
238+
<li>
239+
<a href="/docs/configuration">Configuration</a>: Full list of
240+
environment variables including auth settings.
241+
</li>
242+
<li>
243+
<a href="/docs/architecture">Architecture</a>: How the auth layer
244+
integrates with the API server and frontend.
245+
</li>
246+
<li>
247+
<a href="/docs/quick-start">Quick Start</a>: Get AgentCrew running
248+
with authentication in one command.
249+
</li>
250+
</ul>
251+
</div>
252+
</DocsLayout>

0 commit comments

Comments
 (0)