opalserve 2.0.0 → 3.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/README.md +74 -77
- package/assets/logo.svg +36 -39
- package/dist/auth/api-keys.d.ts +7 -0
- package/dist/auth/api-keys.d.ts.map +1 -0
- package/dist/auth/api-keys.js +12 -0
- package/dist/auth/api-keys.js.map +1 -0
- package/dist/auth/index.d.ts +4 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +4 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/middleware.d.ts +11 -0
- package/dist/auth/middleware.d.ts.map +1 -0
- package/dist/auth/middleware.js +46 -0
- package/dist/auth/middleware.js.map +1 -0
- package/dist/auth/passwords.d.ts +3 -0
- package/dist/auth/passwords.d.ts.map +1 -0
- package/dist/auth/passwords.js +33 -0
- package/dist/auth/passwords.js.map +1 -0
- package/dist/cli/commands/admin.d.ts +13 -0
- package/dist/cli/commands/admin.d.ts.map +1 -0
- package/dist/cli/commands/admin.js +261 -0
- package/dist/cli/commands/admin.js.map +1 -0
- package/dist/cli/commands/auth.d.ts +4 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/auth.js +77 -0
- package/dist/cli/commands/auth.js.map +1 -0
- package/dist/cli/commands/context.d.ts +5 -0
- package/dist/cli/commands/context.d.ts.map +1 -0
- package/dist/cli/commands/context.js +190 -0
- package/dist/cli/commands/context.js.map +1 -0
- package/dist/cli/commands/sync.d.ts +2 -0
- package/dist/cli/commands/sync.d.ts.map +1 -0
- package/dist/cli/commands/sync.js +58 -0
- package/dist/cli/commands/sync.js.map +1 -0
- package/dist/cli/index.js +75 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/ui/banner.js +2 -2
- package/dist/config/credentials.d.ts +10 -0
- package/dist/config/credentials.d.ts.map +1 -0
- package/dist/config/credentials.js +33 -0
- package/dist/config/credentials.js.map +1 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +2 -0
- package/dist/config/defaults.js.map +1 -1
- package/dist/constants.d.ts +5 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +5 -0
- package/dist/constants.js.map +1 -0
- package/dist/context/chunker.d.ts +2 -0
- package/dist/context/chunker.d.ts.map +1 -0
- package/dist/context/chunker.js +81 -0
- package/dist/context/chunker.js.map +1 -0
- package/dist/context/index.d.ts +26 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +97 -0
- package/dist/context/index.js.map +1 -0
- package/dist/core/registry.d.ts +3 -1
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +8 -6
- package/dist/core/registry.js.map +1 -1
- package/dist/core/secrets.d.ts +4 -0
- package/dist/core/secrets.d.ts.map +1 -0
- package/dist/core/secrets.js +40 -0
- package/dist/core/secrets.js.map +1 -0
- package/dist/core/server-manager.js +1 -1
- package/dist/dashboard/assets/index-BNOtcUPs.js +257 -0
- package/dist/dashboard/assets/index-Duwp34GW.css +1 -0
- package/dist/dashboard/index.html +14 -0
- package/dist/index.d.ts +13 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/dist/integrations/github.d.ts +5 -0
- package/dist/integrations/github.d.ts.map +1 -0
- package/dist/integrations/github.js +63 -0
- package/dist/integrations/github.js.map +1 -0
- package/dist/integrations/slack.d.ts +21 -0
- package/dist/integrations/slack.d.ts.map +1 -0
- package/dist/integrations/slack.js +61 -0
- package/dist/integrations/slack.js.map +1 -0
- package/dist/monitoring/tracker.d.ts +31 -0
- package/dist/monitoring/tracker.d.ts.map +1 -0
- package/dist/monitoring/tracker.js +86 -0
- package/dist/monitoring/tracker.js.map +1 -0
- package/dist/server/app.d.ts.map +1 -1
- package/dist/server/app.js +46 -0
- package/dist/server/app.js.map +1 -1
- package/dist/server/mcp-gateway.js +1 -1
- package/dist/server/routes/auth.d.ts +4 -0
- package/dist/server/routes/auth.d.ts.map +1 -0
- package/dist/server/routes/auth.js +117 -0
- package/dist/server/routes/auth.js.map +1 -0
- package/dist/server/routes/context.d.ts +4 -0
- package/dist/server/routes/context.d.ts.map +1 -0
- package/dist/server/routes/context.js +107 -0
- package/dist/server/routes/context.js.map +1 -0
- package/dist/server/routes/health.js +1 -1
- package/dist/server/routes/stats.d.ts +4 -0
- package/dist/server/routes/stats.d.ts.map +1 -0
- package/dist/server/routes/stats.js +97 -0
- package/dist/server/routes/stats.js.map +1 -0
- package/dist/server/routes/team-servers.d.ts +4 -0
- package/dist/server/routes/team-servers.d.ts.map +1 -0
- package/dist/server/routes/team-servers.js +108 -0
- package/dist/server/routes/team-servers.js.map +1 -0
- package/dist/server/routes/webhooks.d.ts +4 -0
- package/dist/server/routes/webhooks.d.ts.map +1 -0
- package/dist/server/routes/webhooks.js +77 -0
- package/dist/server/routes/webhooks.js.map +1 -0
- package/dist/storage/database.d.ts +10 -3
- package/dist/storage/database.d.ts.map +1 -1
- package/dist/storage/database.js +258 -117
- package/dist/storage/database.js.map +1 -1
- package/dist/types/index.d.ts +102 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +8 -0
- package/dist/types/index.js.map +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
5
|
<p align="center">
|
|
6
|
-
<strong>
|
|
6
|
+
<strong>The control plane for your team's AI tools</strong>
|
|
7
7
|
</p>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
@@ -15,19 +15,41 @@
|
|
|
15
15
|
|
|
16
16
|
---
|
|
17
17
|
|
|
18
|
-
**OpalServe**
|
|
18
|
+
**OpalServe** is a team platform for managing AI coding tools. Share MCP servers, knowledge bases, and usage analytics across your engineering team — whether they use Claude Code, Cursor, Codex, or any MCP-compatible tool.
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
- **Team MCP Server Registry** — Admin registers servers once, every developer gets them via `opalserve sync`
|
|
23
|
+
- **Shared Knowledge Base** — Upload architecture docs, coding standards, API specs — your team's AI tools query it automatically via MCP
|
|
24
|
+
- **Usage Analytics Dashboard** — React SPA showing tool calls, token usage, active users, error rates per developer
|
|
25
|
+
- **Auth & Access Control** — User accounts, API keys, rate limits, tool permissions per role
|
|
26
|
+
- **GitHub + Slack Integration** — Webhooks auto-update context, slash commands for search
|
|
27
|
+
- **MCP Gateway** — OpalServe itself is an MCP server — connect any AI client to access all tools from one endpoint
|
|
28
|
+
- **Beautiful CLI** — Interactive setup, gradient banners, color-coded tables, 15+ commands
|
|
29
|
+
- **100% Open Source** — MIT licensed, self-host for free
|
|
30
|
+
|
|
31
|
+
## Architecture
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
┌──────────────────────────┐
|
|
35
|
+
│ OpalServe Team Server │
|
|
36
|
+
│ │
|
|
37
|
+
│ Team MCP Server Registry │
|
|
38
|
+
│ Shared Knowledge Base │
|
|
39
|
+
│ Usage Analytics │
|
|
40
|
+
│ React Admin Dashboard │
|
|
41
|
+
│ Auth / API Keys │
|
|
42
|
+
└────────────┬──────────────┘
|
|
43
|
+
│ HTTPS API
|
|
44
|
+
┌────────────────┼────────────────┐
|
|
45
|
+
│ │ │
|
|
46
|
+
Dev A (Claude) Dev B (Cursor) Dev C (Codex)
|
|
47
|
+
```
|
|
26
48
|
|
|
27
49
|
## Quick Start
|
|
28
50
|
|
|
29
51
|
```bash
|
|
30
|
-
# Install
|
|
52
|
+
# Install
|
|
31
53
|
npm install -g opalserve
|
|
32
54
|
|
|
33
55
|
# Interactive setup
|
|
@@ -35,47 +57,60 @@ opalserve init
|
|
|
35
57
|
|
|
36
58
|
# Start the registry
|
|
37
59
|
opalserve start
|
|
38
|
-
```
|
|
39
60
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
# Filesystem access
|
|
61
|
+
# Add an MCP server
|
|
44
62
|
opalserve server add --name files --stdio "npx -y @modelcontextprotocol/server-filesystem ."
|
|
45
63
|
|
|
46
|
-
#
|
|
47
|
-
opalserve
|
|
48
|
-
|
|
49
|
-
# Any MCP server
|
|
50
|
-
opalserve server add --name my-server --url https://mcp.example.com/sse
|
|
64
|
+
# Search tools
|
|
65
|
+
opalserve tools search "read file"
|
|
51
66
|
```
|
|
52
67
|
|
|
53
|
-
##
|
|
68
|
+
## Team Mode
|
|
54
69
|
|
|
55
70
|
```bash
|
|
56
|
-
#
|
|
57
|
-
opalserve
|
|
71
|
+
# Initialize team server
|
|
72
|
+
opalserve admin init
|
|
73
|
+
|
|
74
|
+
# Start in team mode
|
|
75
|
+
# (set mode: "team-server" in opalserve.config.json)
|
|
76
|
+
opalserve start
|
|
58
77
|
|
|
59
|
-
#
|
|
60
|
-
opalserve
|
|
78
|
+
# On each developer's machine:
|
|
79
|
+
opalserve login
|
|
80
|
+
opalserve sync
|
|
61
81
|
|
|
62
|
-
#
|
|
63
|
-
opalserve
|
|
82
|
+
# Upload shared docs
|
|
83
|
+
opalserve context add ./docs/architecture.md
|
|
84
|
+
opalserve context add ./docs/api-spec.md
|
|
85
|
+
|
|
86
|
+
# Search team knowledge
|
|
87
|
+
opalserve context search "authentication flow"
|
|
64
88
|
```
|
|
65
89
|
|
|
66
|
-
##
|
|
90
|
+
## Admin Dashboard
|
|
91
|
+
|
|
92
|
+
Start the server and visit `http://localhost:3456/dashboard` for:
|
|
93
|
+
- Usage overview with charts
|
|
94
|
+
- Server status monitoring
|
|
95
|
+
- User management
|
|
96
|
+
- Tool browser with search
|
|
97
|
+
- Knowledge base management
|
|
98
|
+
- API key + integration settings
|
|
67
99
|
|
|
68
|
-
|
|
100
|
+
## CLI Commands
|
|
69
101
|
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
102
|
+
```
|
|
103
|
+
opalserve init Setup wizard
|
|
104
|
+
opalserve start Start server
|
|
105
|
+
opalserve status Show status
|
|
106
|
+
opalserve login / logout / whoami Auth commands
|
|
107
|
+
opalserve sync Pull team configs
|
|
108
|
+
opalserve server list|add|remove Server management
|
|
109
|
+
opalserve tools list|search Tool discovery
|
|
110
|
+
opalserve context add|list|search Knowledge base
|
|
111
|
+
opalserve health Health checks
|
|
112
|
+
opalserve admin init|stats|users Team admin
|
|
113
|
+
opalserve admin limits|permissions Access control
|
|
79
114
|
```
|
|
80
115
|
|
|
81
116
|
## Library Usage
|
|
@@ -91,53 +126,15 @@ const registry = await OpalServeRegistry.create({
|
|
|
91
126
|
});
|
|
92
127
|
|
|
93
128
|
await registry.start();
|
|
94
|
-
|
|
95
129
|
const tools = registry.listTools();
|
|
96
130
|
const results = registry.searchTools('read file');
|
|
97
|
-
const result = await registry.callTool('my-files:read_file', { path: './README.md' });
|
|
98
|
-
|
|
99
131
|
await registry.stop();
|
|
100
132
|
```
|
|
101
133
|
|
|
102
|
-
## HTTP API
|
|
103
|
-
|
|
104
|
-
Start the server with `opalserve start`, then:
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
# Health check
|
|
108
|
-
curl http://localhost:3456/api/v1/health
|
|
109
|
-
|
|
110
|
-
# List tools
|
|
111
|
-
curl http://localhost:3456/api/v1/tools
|
|
112
|
-
|
|
113
|
-
# Search tools
|
|
114
|
-
curl "http://localhost:3456/api/v1/tools/search?q=read+file"
|
|
115
|
-
|
|
116
|
-
# Call a tool
|
|
117
|
-
curl -X POST http://localhost:3456/api/v1/tools/files%3Aread_file/call \
|
|
118
|
-
-H "Content-Type: application/json" \
|
|
119
|
-
-d '{"path": "./README.md"}'
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
## Architecture
|
|
123
|
-
|
|
124
|
-
```
|
|
125
|
-
[Claude/Cursor] <--MCP--> [OpalServe Gateway] <--MCP--> [Server A]
|
|
126
|
-
| <--MCP--> [Server B]
|
|
127
|
-
[HTTP API] <--MCP--> [Server C]
|
|
128
|
-
[SQLite DB]
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
OpalServe is simultaneously:
|
|
132
|
-
- An **MCP Client** to each backend server
|
|
133
|
-
- An **MCP Server** exposing aggregated tools
|
|
134
|
-
- An **HTTP API** for management
|
|
135
|
-
- A **CLI** for humans
|
|
136
|
-
|
|
137
134
|
## Documentation
|
|
138
135
|
|
|
139
|
-
Full
|
|
136
|
+
Full docs at [opalserve.vercel.app](https://opalserve.vercel.app)
|
|
140
137
|
|
|
141
138
|
## License
|
|
142
139
|
|
|
143
|
-
MIT
|
|
140
|
+
MIT — 100% open source, free to self-host
|
package/assets/logo.svg
CHANGED
|
@@ -1,54 +1,51 @@
|
|
|
1
1
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" fill="none">
|
|
2
2
|
<defs>
|
|
3
|
-
<linearGradient id="
|
|
3
|
+
<linearGradient id="hub-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
4
4
|
<stop offset="0%" stop-color="#F59E0B"/>
|
|
5
|
-
<stop offset="
|
|
6
|
-
<stop offset="65%" stop-color="#EF4444"/>
|
|
5
|
+
<stop offset="50%" stop-color="#F97316"/>
|
|
7
6
|
<stop offset="100%" stop-color="#8B5CF6"/>
|
|
8
7
|
</linearGradient>
|
|
9
|
-
<linearGradient id="
|
|
10
|
-
<stop offset="0%" stop-color="#
|
|
11
|
-
<stop offset="
|
|
12
|
-
<stop offset="100%" stop-color="#FFFFFF" stop-opacity="0"/>
|
|
8
|
+
<linearGradient id="line-gradient-top" x1="256" y1="256" x2="256" y2="80" gradientUnits="userSpaceOnUse">
|
|
9
|
+
<stop offset="0%" stop-color="#F97316"/>
|
|
10
|
+
<stop offset="100%" stop-color="#F59E0B"/>
|
|
13
11
|
</linearGradient>
|
|
14
|
-
<linearGradient id="
|
|
15
|
-
<stop offset="0%" stop-color="#
|
|
16
|
-
<stop offset="100%" stop-color="#
|
|
12
|
+
<linearGradient id="line-gradient-right" x1="256" y1="256" x2="432" y2="316" gradientUnits="userSpaceOnUse">
|
|
13
|
+
<stop offset="0%" stop-color="#F97316"/>
|
|
14
|
+
<stop offset="100%" stop-color="#8B5CF6"/>
|
|
15
|
+
</linearGradient>
|
|
16
|
+
<linearGradient id="line-gradient-bottom" x1="256" y1="256" x2="256" y2="432" gradientUnits="userSpaceOnUse">
|
|
17
|
+
<stop offset="0%" stop-color="#F97316"/>
|
|
18
|
+
<stop offset="100%" stop-color="#8B5CF6"/>
|
|
19
|
+
</linearGradient>
|
|
20
|
+
<linearGradient id="line-gradient-left" x1="256" y1="256" x2="80" y2="196" gradientUnits="userSpaceOnUse">
|
|
21
|
+
<stop offset="0%" stop-color="#F97316"/>
|
|
22
|
+
<stop offset="100%" stop-color="#F59E0B"/>
|
|
17
23
|
</linearGradient>
|
|
18
24
|
</defs>
|
|
19
25
|
|
|
20
|
-
<!--
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
/>
|
|
26
|
+
<!-- Connection lines from hub to satellites -->
|
|
27
|
+
<line x1="256" y1="256" x2="256" y2="88" stroke="url(#line-gradient-top)" stroke-width="6" stroke-linecap="round" opacity="0.6"/>
|
|
28
|
+
<line x1="256" y1="256" x2="424" y2="316" stroke="url(#line-gradient-right)" stroke-width="6" stroke-linecap="round" opacity="0.6"/>
|
|
29
|
+
<line x1="256" y1="256" x2="256" y2="424" stroke="url(#line-gradient-bottom)" stroke-width="6" stroke-linecap="round" opacity="0.6"/>
|
|
30
|
+
<line x1="256" y1="256" x2="88" y2="196" stroke="url(#line-gradient-left)" stroke-width="6" stroke-linecap="round" opacity="0.6"/>
|
|
31
|
+
|
|
32
|
+
<!-- Satellite nodes -->
|
|
33
|
+
<circle cx="256" cy="88" r="28" fill="#F59E0B" opacity="0.5"/>
|
|
34
|
+
<circle cx="256" cy="88" r="18" fill="#F59E0B" opacity="0.85"/>
|
|
25
35
|
|
|
26
|
-
|
|
27
|
-
<
|
|
28
|
-
points="256,32 432,128 256,192 80,128"
|
|
29
|
-
fill="url(#opal-shine)"
|
|
30
|
-
/>
|
|
36
|
+
<circle cx="424" cy="316" r="28" fill="#8B5CF6" opacity="0.5"/>
|
|
37
|
+
<circle cx="424" cy="316" r="18" fill="#8B5CF6" opacity="0.85"/>
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
<
|
|
34
|
-
points="80,128 256,192 256,380 80,320"
|
|
35
|
-
fill="url(#facet-light)"
|
|
36
|
-
opacity="0.3"
|
|
37
|
-
/>
|
|
39
|
+
<circle cx="256" cy="424" r="28" fill="#8B5CF6" opacity="0.5"/>
|
|
40
|
+
<circle cx="256" cy="424" r="18" fill="#A855F7" opacity="0.85"/>
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
<
|
|
41
|
-
<line x1="256" y1="192" x2="160" y2="260" stroke="#FFFFFF" stroke-opacity="0.12" stroke-width="1.5"/>
|
|
42
|
-
<line x1="256" y1="192" x2="352" y2="260" stroke="#FFFFFF" stroke-opacity="0.12" stroke-width="1.5"/>
|
|
43
|
-
<line x1="256" y1="192" x2="180" y2="340" stroke="#FFFFFF" stroke-opacity="0.08" stroke-width="1"/>
|
|
44
|
-
<line x1="256" y1="192" x2="332" y2="340" stroke="#FFFFFF" stroke-opacity="0.08" stroke-width="1"/>
|
|
42
|
+
<circle cx="88" cy="196" r="28" fill="#F97316" opacity="0.5"/>
|
|
43
|
+
<circle cx="88" cy="196" r="18" fill="#F97316" opacity="0.85"/>
|
|
45
44
|
|
|
46
|
-
<!--
|
|
47
|
-
<circle cx="256" cy="
|
|
48
|
-
<circle cx="256" cy="
|
|
45
|
+
<!-- Central hub node -->
|
|
46
|
+
<circle cx="256" cy="256" r="64" fill="url(#hub-gradient)" opacity="0.2"/>
|
|
47
|
+
<circle cx="256" cy="256" r="48" fill="url(#hub-gradient)"/>
|
|
49
48
|
|
|
50
|
-
<!--
|
|
51
|
-
<circle cx="
|
|
52
|
-
<circle cx="310" cy="280" r="2.5" fill="#FFFFFF" opacity="0.25"/>
|
|
53
|
-
<circle cx="240" cy="320" r="2" fill="#FFFFFF" opacity="0.2"/>
|
|
49
|
+
<!-- Hub inner highlight -->
|
|
50
|
+
<circle cx="246" cy="246" r="20" fill="#FFFFFF" opacity="0.15"/>
|
|
54
51
|
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.d.ts","sourceRoot":"","sources":["../../src/auth/api-keys.ts"],"names":[],"mappings":"AAEA,wBAAgB,cAAc,IAAI;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAMpF;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE9C"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { randomBytes, createHash } from 'node:crypto';
|
|
2
|
+
export function generateApiKey() {
|
|
3
|
+
const random = randomBytes(16).toString('hex'); // 32 hex chars
|
|
4
|
+
const key = `opal_${random}`;
|
|
5
|
+
const keyHash = hashApiKey(key);
|
|
6
|
+
const keyPrefix = key.slice(0, 8);
|
|
7
|
+
return { key, keyHash, keyPrefix };
|
|
8
|
+
}
|
|
9
|
+
export function hashApiKey(key) {
|
|
10
|
+
return createHash('sha256').update(key).digest('hex');
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=api-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api-keys.js","sourceRoot":"","sources":["../../src/auth/api-keys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEtD,MAAM,UAAU,cAAc;IAC5B,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,eAAe;IAC/D,MAAM,GAAG,GAAG,QAAQ,MAAM,EAAE,CAAC;IAC7B,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAChC,MAAM,SAAS,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IAClC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AACrC,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify';
|
|
2
|
+
import type { Database } from '../storage/database.js';
|
|
3
|
+
import type { User } from '../types/index.js';
|
|
4
|
+
declare module 'fastify' {
|
|
5
|
+
interface FastifyRequest {
|
|
6
|
+
user?: User;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export declare function createAuthMiddleware(db: Database): (request: FastifyRequest, reply: FastifyReply) => Promise<void>;
|
|
10
|
+
export declare function registerAuthDecorator(app: FastifyInstance): void;
|
|
11
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAG9C,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,cAAc;QACtB,IAAI,CAAC,EAAE,IAAI,CAAC;KACb;CACF;AAED,wBAAgB,oBAAoB,CAAC,EAAE,EAAE,QAAQ,IAChB,SAAS,cAAc,EAAE,OAAO,YAAY,KAAG,OAAO,CAAC,IAAI,CAAC,CAsD5F;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,eAAe,GAAG,IAAI,CAEhE"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { hashApiKey } from './api-keys.js';
|
|
2
|
+
export function createAuthMiddleware(db) {
|
|
3
|
+
return async function authHook(request, reply) {
|
|
4
|
+
// In local mode, skip auth entirely
|
|
5
|
+
const mode = process.env.OPALSERVE_MODE || 'local';
|
|
6
|
+
if (mode === 'local') {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const authHeader = request.headers.authorization;
|
|
10
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
11
|
+
reply.code(401).send({ ok: false, error: 'Missing or invalid Authorization header' });
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
const key = authHeader.slice(7);
|
|
15
|
+
const keyHash = hashApiKey(key);
|
|
16
|
+
const row = db.get('SELECT user_id, expires_at FROM api_keys WHERE key_hash = ?', [keyHash]);
|
|
17
|
+
if (!row) {
|
|
18
|
+
reply.code(401).send({ ok: false, error: 'Invalid API key' });
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Check expiry
|
|
22
|
+
if (row.expires_at && new Date(row.expires_at) < new Date()) {
|
|
23
|
+
reply.code(401).send({ ok: false, error: 'API key has expired' });
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
// Update last_used_at
|
|
27
|
+
db.run('UPDATE api_keys SET last_used_at = datetime("now") WHERE key_hash = ?', [keyHash]);
|
|
28
|
+
// Load user
|
|
29
|
+
const user = db.get('SELECT id, email, display_name, created_at, updated_at FROM users WHERE id = ?', [row.user_id]);
|
|
30
|
+
if (!user) {
|
|
31
|
+
reply.code(401).send({ ok: false, error: 'User not found' });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
request.user = {
|
|
35
|
+
id: user.id,
|
|
36
|
+
email: user.email,
|
|
37
|
+
displayName: user.display_name,
|
|
38
|
+
createdAt: user.created_at,
|
|
39
|
+
updatedAt: user.updated_at,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export function registerAuthDecorator(app) {
|
|
44
|
+
app.decorateRequest('user', undefined);
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/auth/middleware.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAQ3C,MAAM,UAAU,oBAAoB,CAAC,EAAY;IAC/C,OAAO,KAAK,UAAU,QAAQ,CAAC,OAAuB,EAAE,KAAmB;QACzE,oCAAoC;QACpC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,IAAI,OAAO,CAAC;QACnD,IAAI,IAAI,KAAK,OAAO,EAAE,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC;QACjD,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACrD,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,yCAAyC,EAAE,CAAC,CAAC;YACtF,OAAO;QACT,CAAC;QAED,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAChC,MAAM,OAAO,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QAEhC,MAAM,GAAG,GAAG,EAAE,CAAC,GAAG,CAChB,6DAA6D,EAC7D,CAAC,OAAO,CAAC,CACV,CAAC;QAEF,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,iBAAiB,EAAE,CAAC,CAAC;YAC9D,OAAO;QACT,CAAC;QAED,eAAe;QACf,IAAI,GAAG,CAAC,UAAU,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;YAC5D,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,qBAAqB,EAAE,CAAC,CAAC;YAClE,OAAO;QACT,CAAC;QAED,sBAAsB;QACtB,EAAE,CAAC,GAAG,CAAC,uEAAuE,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;QAE3F,YAAY;QACZ,MAAM,IAAI,GAAG,EAAE,CAAC,GAAG,CACjB,gFAAgF,EAChF,CAAC,GAAG,CAAC,OAAO,CAAC,CACd,CAAC;QAEF,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;YAC7D,OAAO;QACT,CAAC;QAED,OAAO,CAAC,IAAI,GAAG;YACb,EAAE,EAAE,IAAI,CAAC,EAAE;YACX,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,WAAW,EAAE,IAAI,CAAC,YAAY;YAC9B,SAAS,EAAE,IAAI,CAAC,UAAU;YAC1B,SAAS,EAAE,IAAI,CAAC,UAAU;SAC3B,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,GAAoB;IACxD,GAAG,CAAC,eAAe,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;AACzC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passwords.d.ts","sourceRoot":"","sources":["../../src/auth/passwords.ts"],"names":[],"mappings":"AAKA,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CASpE;AAED,wBAAsB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAevF"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { randomBytes, scrypt, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
const SALT_LENGTH = 32;
|
|
3
|
+
const KEY_LENGTH = 64;
|
|
4
|
+
export async function hashPassword(password) {
|
|
5
|
+
const salt = randomBytes(SALT_LENGTH).toString('hex');
|
|
6
|
+
const hash = await new Promise((resolve, reject) => {
|
|
7
|
+
scrypt(password, salt, KEY_LENGTH, (err, derivedKey) => {
|
|
8
|
+
if (err)
|
|
9
|
+
reject(err);
|
|
10
|
+
else
|
|
11
|
+
resolve(derivedKey.toString('hex'));
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
return `${salt}:${hash}`;
|
|
15
|
+
}
|
|
16
|
+
export async function verifyPassword(password, stored) {
|
|
17
|
+
const [salt, storedHash] = stored.split(':');
|
|
18
|
+
if (!salt || !storedHash)
|
|
19
|
+
return false;
|
|
20
|
+
const hash = await new Promise((resolve, reject) => {
|
|
21
|
+
scrypt(password, salt, KEY_LENGTH, (err, derivedKey) => {
|
|
22
|
+
if (err)
|
|
23
|
+
reject(err);
|
|
24
|
+
else
|
|
25
|
+
resolve(derivedKey);
|
|
26
|
+
});
|
|
27
|
+
});
|
|
28
|
+
const storedBuffer = Buffer.from(storedHash, 'hex');
|
|
29
|
+
if (hash.length !== storedBuffer.length)
|
|
30
|
+
return false;
|
|
31
|
+
return timingSafeEqual(hash, storedBuffer);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=passwords.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passwords.js","sourceRoot":"","sources":["../../src/auth/passwords.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,WAAW,GAAG,EAAE,CAAC;AACvB,MAAM,UAAU,GAAG,EAAE,CAAC;AAEtB,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,QAAgB;IACjD,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IACtD,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACzD,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE;YACrD,IAAI,GAAG;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBAChB,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IACH,OAAO,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,QAAgB,EAAE,MAAc;IACnE,MAAM,CAAC,IAAI,EAAE,UAAU,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,IAAI,IAAI,CAAC,UAAU;QAAE,OAAO,KAAK,CAAC;IAEvC,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,CAAS,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACzD,MAAM,CAAC,QAAQ,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,GAAG,EAAE,UAAU,EAAE,EAAE;YACrD,IAAI,GAAG;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBAChB,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3B,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;IACpD,IAAI,IAAI,CAAC,MAAM,KAAK,YAAY,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IAEtD,OAAO,eAAe,CAAC,IAAI,EAAE,YAAY,CAAC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare function adminInitCommand(): Promise<void>;
|
|
2
|
+
export declare function adminStatsCommand(): Promise<void>;
|
|
3
|
+
export declare function adminUsersCommand(): Promise<void>;
|
|
4
|
+
export declare function adminInviteCommand(email: string): Promise<void>;
|
|
5
|
+
export declare function adminLimitsCommand(options: {
|
|
6
|
+
set?: string;
|
|
7
|
+
list?: boolean;
|
|
8
|
+
}): Promise<void>;
|
|
9
|
+
export declare function adminPermissionsCommand(options: {
|
|
10
|
+
set?: string;
|
|
11
|
+
list?: boolean;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
//# sourceMappingURL=admin.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/admin.ts"],"names":[],"mappings":"AAQA,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CA2DtD;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CAyCvD;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,IAAI,CAAC,CA4CvD;AAED,wBAAsB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA6DrE;AAED,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAkEjG;AAED,wBAAsB,uBAAuB,CAAC,OAAO,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA+DtG"}
|