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.
Files changed (118) hide show
  1. package/README.md +74 -77
  2. package/assets/logo.svg +36 -39
  3. package/dist/auth/api-keys.d.ts +7 -0
  4. package/dist/auth/api-keys.d.ts.map +1 -0
  5. package/dist/auth/api-keys.js +12 -0
  6. package/dist/auth/api-keys.js.map +1 -0
  7. package/dist/auth/index.d.ts +4 -0
  8. package/dist/auth/index.d.ts.map +1 -0
  9. package/dist/auth/index.js +4 -0
  10. package/dist/auth/index.js.map +1 -0
  11. package/dist/auth/middleware.d.ts +11 -0
  12. package/dist/auth/middleware.d.ts.map +1 -0
  13. package/dist/auth/middleware.js +46 -0
  14. package/dist/auth/middleware.js.map +1 -0
  15. package/dist/auth/passwords.d.ts +3 -0
  16. package/dist/auth/passwords.d.ts.map +1 -0
  17. package/dist/auth/passwords.js +33 -0
  18. package/dist/auth/passwords.js.map +1 -0
  19. package/dist/cli/commands/admin.d.ts +13 -0
  20. package/dist/cli/commands/admin.d.ts.map +1 -0
  21. package/dist/cli/commands/admin.js +261 -0
  22. package/dist/cli/commands/admin.js.map +1 -0
  23. package/dist/cli/commands/auth.d.ts +4 -0
  24. package/dist/cli/commands/auth.d.ts.map +1 -0
  25. package/dist/cli/commands/auth.js +77 -0
  26. package/dist/cli/commands/auth.js.map +1 -0
  27. package/dist/cli/commands/context.d.ts +5 -0
  28. package/dist/cli/commands/context.d.ts.map +1 -0
  29. package/dist/cli/commands/context.js +190 -0
  30. package/dist/cli/commands/context.js.map +1 -0
  31. package/dist/cli/commands/sync.d.ts +2 -0
  32. package/dist/cli/commands/sync.d.ts.map +1 -0
  33. package/dist/cli/commands/sync.js +58 -0
  34. package/dist/cli/commands/sync.js.map +1 -0
  35. package/dist/cli/index.js +75 -1
  36. package/dist/cli/index.js.map +1 -1
  37. package/dist/cli/ui/banner.js +2 -2
  38. package/dist/config/credentials.d.ts +10 -0
  39. package/dist/config/credentials.d.ts.map +1 -0
  40. package/dist/config/credentials.js +33 -0
  41. package/dist/config/credentials.js.map +1 -0
  42. package/dist/config/defaults.d.ts.map +1 -1
  43. package/dist/config/defaults.js +2 -0
  44. package/dist/config/defaults.js.map +1 -1
  45. package/dist/constants.d.ts +5 -0
  46. package/dist/constants.d.ts.map +1 -0
  47. package/dist/constants.js +5 -0
  48. package/dist/constants.js.map +1 -0
  49. package/dist/context/chunker.d.ts +2 -0
  50. package/dist/context/chunker.d.ts.map +1 -0
  51. package/dist/context/chunker.js +81 -0
  52. package/dist/context/chunker.js.map +1 -0
  53. package/dist/context/index.d.ts +26 -0
  54. package/dist/context/index.d.ts.map +1 -0
  55. package/dist/context/index.js +97 -0
  56. package/dist/context/index.js.map +1 -0
  57. package/dist/core/registry.d.ts +3 -1
  58. package/dist/core/registry.d.ts.map +1 -1
  59. package/dist/core/registry.js +8 -6
  60. package/dist/core/registry.js.map +1 -1
  61. package/dist/core/secrets.d.ts +4 -0
  62. package/dist/core/secrets.d.ts.map +1 -0
  63. package/dist/core/secrets.js +40 -0
  64. package/dist/core/secrets.js.map +1 -0
  65. package/dist/core/server-manager.js +1 -1
  66. package/dist/dashboard/assets/index-BNOtcUPs.js +257 -0
  67. package/dist/dashboard/assets/index-Duwp34GW.css +1 -0
  68. package/dist/dashboard/index.html +14 -0
  69. package/dist/index.d.ts +13 -2
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +18 -1
  72. package/dist/index.js.map +1 -1
  73. package/dist/integrations/github.d.ts +5 -0
  74. package/dist/integrations/github.d.ts.map +1 -0
  75. package/dist/integrations/github.js +63 -0
  76. package/dist/integrations/github.js.map +1 -0
  77. package/dist/integrations/slack.d.ts +21 -0
  78. package/dist/integrations/slack.d.ts.map +1 -0
  79. package/dist/integrations/slack.js +61 -0
  80. package/dist/integrations/slack.js.map +1 -0
  81. package/dist/monitoring/tracker.d.ts +31 -0
  82. package/dist/monitoring/tracker.d.ts.map +1 -0
  83. package/dist/monitoring/tracker.js +86 -0
  84. package/dist/monitoring/tracker.js.map +1 -0
  85. package/dist/server/app.d.ts.map +1 -1
  86. package/dist/server/app.js +46 -0
  87. package/dist/server/app.js.map +1 -1
  88. package/dist/server/mcp-gateway.js +1 -1
  89. package/dist/server/routes/auth.d.ts +4 -0
  90. package/dist/server/routes/auth.d.ts.map +1 -0
  91. package/dist/server/routes/auth.js +117 -0
  92. package/dist/server/routes/auth.js.map +1 -0
  93. package/dist/server/routes/context.d.ts +4 -0
  94. package/dist/server/routes/context.d.ts.map +1 -0
  95. package/dist/server/routes/context.js +107 -0
  96. package/dist/server/routes/context.js.map +1 -0
  97. package/dist/server/routes/health.js +1 -1
  98. package/dist/server/routes/stats.d.ts +4 -0
  99. package/dist/server/routes/stats.d.ts.map +1 -0
  100. package/dist/server/routes/stats.js +97 -0
  101. package/dist/server/routes/stats.js.map +1 -0
  102. package/dist/server/routes/team-servers.d.ts +4 -0
  103. package/dist/server/routes/team-servers.d.ts.map +1 -0
  104. package/dist/server/routes/team-servers.js +108 -0
  105. package/dist/server/routes/team-servers.js.map +1 -0
  106. package/dist/server/routes/webhooks.d.ts +4 -0
  107. package/dist/server/routes/webhooks.d.ts.map +1 -0
  108. package/dist/server/routes/webhooks.js +77 -0
  109. package/dist/server/routes/webhooks.js.map +1 -0
  110. package/dist/storage/database.d.ts +10 -3
  111. package/dist/storage/database.d.ts.map +1 -1
  112. package/dist/storage/database.js +258 -117
  113. package/dist/storage/database.js.map +1 -1
  114. package/dist/types/index.d.ts +102 -0
  115. package/dist/types/index.d.ts.map +1 -1
  116. package/dist/types/index.js +8 -0
  117. package/dist/types/index.js.map +1 -1
  118. 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>Local MCP Tool Registry & Discovery Hub</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** aggregates, discovers, and proxies tools across multiple MCP (Model Context Protocol) servers from a single hub.
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
- - **Real MCP Protocol** — Connects to any MCP server via stdio, SSE, or Streamable HTTP
21
- - **Full-Text Search** — SQLite FTS5-powered tool discovery across all servers
22
- - **Beautiful CLI** — Interactive setup, gradient banners, color-coded tables
23
- - **HTTP API** — Fastify REST API for managing servers and tools
24
- - **Persistent** — SQLite database survives restarts
25
- - **MCP Gateway** — Acts as an MCP server itself for LLM clients
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 globally
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
- ## Add MCP Servers
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
- # GitHub
47
- opalserve server add --name github --stdio "npx -y @modelcontextprotocol/server-github" --env GITHUB_TOKEN=ghp_xxx
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
- ## Discover Tools
68
+ ## Team Mode
54
69
 
55
70
  ```bash
56
- # List all tools
57
- opalserve tools list
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
- # Full-text search
60
- opalserve tools search "create issue"
78
+ # On each developer's machine:
79
+ opalserve login
80
+ opalserve sync
61
81
 
62
- # Check server health
63
- opalserve health
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
- ## Use as MCP Gateway
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
- Point your MCP client (Claude Desktop, Cursor, etc.) at OpalServe to access all tools from one connection:
100
+ ## CLI Commands
69
101
 
70
- ```json
71
- {
72
- "mcpServers": {
73
- "opalserve": {
74
- "command": "npx",
75
- "args": ["-y", "opalserve", "start", "--mcp"]
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 documentation at [adityaidev.github.io/opalserve](https://adityaidev.github.io/opalserve)
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="opal-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
3
+ <linearGradient id="hub-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
4
4
  <stop offset="0%" stop-color="#F59E0B"/>
5
- <stop offset="35%" stop-color="#F97316"/>
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="opal-shine" x1="20%" y1="0%" x2="80%" y2="100%">
10
- <stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.4"/>
11
- <stop offset="50%" stop-color="#FFFFFF" stop-opacity="0.1"/>
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="facet-light" x1="0%" y1="0%" x2="100%" y2="100%">
15
- <stop offset="0%" stop-color="#FFFFFF" stop-opacity="0.25"/>
16
- <stop offset="100%" stop-color="#FFFFFF" stop-opacity="0.05"/>
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
- <!-- Outer gem shape hexagonal prism -->
21
- <polygon
22
- points="256,32 432,128 432,320 256,480 80,320 80,128"
23
- fill="url(#opal-gradient)"
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
- <!-- Top facet highlight -->
27
- <polygon
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
- <!-- Left facet -->
33
- <polygon
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
- <!-- Inner refraction lines -->
40
- <line x1="256" y1="192" x2="256" y2="380" stroke="#FFFFFF" stroke-opacity="0.15" stroke-width="2"/>
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
- <!-- Center sparkle -->
47
- <circle cx="256" cy="220" r="6" fill="#FFFFFF" opacity="0.6"/>
48
- <circle cx="256" cy="220" r="12" fill="#FFFFFF" opacity="0.15"/>
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
- <!-- Small accent sparkles -->
51
- <circle cx="200" cy="260" r="3" fill="#FFFFFF" opacity="0.3"/>
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,7 @@
1
+ export declare function generateApiKey(): {
2
+ key: string;
3
+ keyHash: string;
4
+ keyPrefix: string;
5
+ };
6
+ export declare function hashApiKey(key: string): string;
7
+ //# sourceMappingURL=api-keys.d.ts.map
@@ -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,4 @@
1
+ export { hashPassword, verifyPassword } from './passwords.js';
2
+ export { generateApiKey, hashApiKey } from './api-keys.js';
3
+ export { createAuthMiddleware, registerAuthDecorator } from './middleware.js';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -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,4 @@
1
+ export { hashPassword, verifyPassword } from './passwords.js';
2
+ export { generateApiKey, hashApiKey } from './api-keys.js';
3
+ export { createAuthMiddleware, registerAuthDecorator } from './middleware.js';
4
+ //# sourceMappingURL=index.js.map
@@ -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,3 @@
1
+ export declare function hashPassword(password: string): Promise<string>;
2
+ export declare function verifyPassword(password: string, stored: string): Promise<boolean>;
3
+ //# sourceMappingURL=passwords.d.ts.map
@@ -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"}