@exulu/backend 1.48.2 → 1.49.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/dist/index.cjs +351 -42
- package/dist/index.d.cts +96 -1
- package/dist/index.d.ts +96 -1
- package/dist/index.js +340 -38
- package/ee/{markdown.ts → chunking/markdown.ts} +2 -2
- package/ee/python/README.md +295 -0
- package/ee/python/documents/processing/README.md +155 -0
- package/ee/{documents → python/documents}/processing/doc_processor.ts +25 -17
- package/ee/{documents/processing/pdf_to_markdown.py → python/documents/processing/document_to_markdown.py} +3 -10
- package/ee/python/setup.sh +180 -0
- package/package.json +14 -3
- package/scripts/postinstall.cjs +149 -0
- package/.agents/skills/mintlify/SKILL.md +0 -347
- package/.editorconfig +0 -15
- package/.eslintrc.json +0 -52
- package/.github/workflows/release-backend.yml +0 -38
- package/.husky/commit-msg +0 -1
- package/.jscpd.json +0 -18
- package/.mcp.json +0 -25
- package/.nvmrc +0 -1
- package/.prettierignore +0 -5
- package/.prettierrc.json +0 -12
- package/CHANGELOG.md +0 -8
- package/SECURITY.md +0 -5
- package/commitlint.config.js +0 -4
- package/devops/documentation/patch-older-releases.md +0 -42
- package/ee/documents/processing/build_pdf_processor.sh +0 -35
- package/ee/documents/processing/chunk_markdown.py +0 -263
- package/ee/documents/processing/pdf_processor.spec +0 -115
- package/eslint.config.js +0 -88
- package/jest.config.ts +0 -25
- package/mintlify-docs/.mintignore +0 -7
- package/mintlify-docs/AGENTS.md +0 -33
- package/mintlify-docs/CLAUDE.MD +0 -50
- package/mintlify-docs/CONTRIBUTING.md +0 -32
- package/mintlify-docs/LICENSE +0 -21
- package/mintlify-docs/README.md +0 -55
- package/mintlify-docs/ai-tools/claude-code.mdx +0 -43
- package/mintlify-docs/ai-tools/cursor.mdx +0 -39
- package/mintlify-docs/ai-tools/windsurf.mdx +0 -39
- package/mintlify-docs/api-reference/core-types/agent-types.mdx +0 -110
- package/mintlify-docs/api-reference/core-types/analytics-types.mdx +0 -95
- package/mintlify-docs/api-reference/core-types/configuration-types.mdx +0 -83
- package/mintlify-docs/api-reference/core-types/evaluation-types.mdx +0 -106
- package/mintlify-docs/api-reference/core-types/job-types.mdx +0 -135
- package/mintlify-docs/api-reference/core-types/overview.mdx +0 -73
- package/mintlify-docs/api-reference/core-types/prompt-types.mdx +0 -102
- package/mintlify-docs/api-reference/core-types/rbac-types.mdx +0 -163
- package/mintlify-docs/api-reference/core-types/session-types.mdx +0 -77
- package/mintlify-docs/api-reference/core-types/user-management.mdx +0 -112
- package/mintlify-docs/api-reference/core-types/workflow-types.mdx +0 -88
- package/mintlify-docs/api-reference/core-types.mdx +0 -585
- package/mintlify-docs/api-reference/dynamic-types.mdx +0 -851
- package/mintlify-docs/api-reference/endpoint/create.mdx +0 -4
- package/mintlify-docs/api-reference/endpoint/delete.mdx +0 -4
- package/mintlify-docs/api-reference/endpoint/get.mdx +0 -4
- package/mintlify-docs/api-reference/endpoint/webhook.mdx +0 -4
- package/mintlify-docs/api-reference/introduction.mdx +0 -661
- package/mintlify-docs/api-reference/mutations.mdx +0 -1012
- package/mintlify-docs/api-reference/openapi.json +0 -217
- package/mintlify-docs/api-reference/queries.mdx +0 -1154
- package/mintlify-docs/backend/introduction.mdx +0 -218
- package/mintlify-docs/changelog.mdx +0 -387
- package/mintlify-docs/community-edition.mdx +0 -304
- package/mintlify-docs/core/exulu-agent/api-reference.mdx +0 -894
- package/mintlify-docs/core/exulu-agent/configuration.mdx +0 -690
- package/mintlify-docs/core/exulu-agent/introduction.mdx +0 -552
- package/mintlify-docs/core/exulu-app/api-reference.mdx +0 -481
- package/mintlify-docs/core/exulu-app/configuration.mdx +0 -319
- package/mintlify-docs/core/exulu-app/introduction.mdx +0 -117
- package/mintlify-docs/core/exulu-authentication.mdx +0 -810
- package/mintlify-docs/core/exulu-chunkers/api-reference.mdx +0 -1011
- package/mintlify-docs/core/exulu-chunkers/configuration.mdx +0 -596
- package/mintlify-docs/core/exulu-chunkers/introduction.mdx +0 -403
- package/mintlify-docs/core/exulu-context/api-reference.mdx +0 -911
- package/mintlify-docs/core/exulu-context/configuration.mdx +0 -648
- package/mintlify-docs/core/exulu-context/introduction.mdx +0 -394
- package/mintlify-docs/core/exulu-database.mdx +0 -811
- package/mintlify-docs/core/exulu-default-agents.mdx +0 -545
- package/mintlify-docs/core/exulu-eval/api-reference.mdx +0 -772
- package/mintlify-docs/core/exulu-eval/configuration.mdx +0 -680
- package/mintlify-docs/core/exulu-eval/introduction.mdx +0 -459
- package/mintlify-docs/core/exulu-logging.mdx +0 -464
- package/mintlify-docs/core/exulu-otel.mdx +0 -670
- package/mintlify-docs/core/exulu-queues/api-reference.mdx +0 -648
- package/mintlify-docs/core/exulu-queues/configuration.mdx +0 -650
- package/mintlify-docs/core/exulu-queues/introduction.mdx +0 -474
- package/mintlify-docs/core/exulu-reranker/api-reference.mdx +0 -630
- package/mintlify-docs/core/exulu-reranker/configuration.mdx +0 -663
- package/mintlify-docs/core/exulu-reranker/introduction.mdx +0 -516
- package/mintlify-docs/core/exulu-tool/api-reference.mdx +0 -723
- package/mintlify-docs/core/exulu-tool/configuration.mdx +0 -805
- package/mintlify-docs/core/exulu-tool/introduction.mdx +0 -539
- package/mintlify-docs/core/exulu-variables/api-reference.mdx +0 -699
- package/mintlify-docs/core/exulu-variables/configuration.mdx +0 -736
- package/mintlify-docs/core/exulu-variables/introduction.mdx +0 -511
- package/mintlify-docs/development.mdx +0 -94
- package/mintlify-docs/docs.json +0 -248
- package/mintlify-docs/enterprise-edition.mdx +0 -538
- package/mintlify-docs/essentials/code.mdx +0 -35
- package/mintlify-docs/essentials/images.mdx +0 -59
- package/mintlify-docs/essentials/markdown.mdx +0 -88
- package/mintlify-docs/essentials/navigation.mdx +0 -87
- package/mintlify-docs/essentials/reusable-snippets.mdx +0 -110
- package/mintlify-docs/essentials/settings.mdx +0 -318
- package/mintlify-docs/favicon.svg +0 -3
- package/mintlify-docs/frontend/introduction.mdx +0 -39
- package/mintlify-docs/getting-started.mdx +0 -267
- package/mintlify-docs/guides/custom-agent.mdx +0 -608
- package/mintlify-docs/guides/first-agent.mdx +0 -315
- package/mintlify-docs/images/admin_ui.png +0 -0
- package/mintlify-docs/images/contexts.png +0 -0
- package/mintlify-docs/images/create_agents.png +0 -0
- package/mintlify-docs/images/evals.png +0 -0
- package/mintlify-docs/images/graphql.png +0 -0
- package/mintlify-docs/images/graphql_api.png +0 -0
- package/mintlify-docs/images/hero-dark.png +0 -0
- package/mintlify-docs/images/hero-light.png +0 -0
- package/mintlify-docs/images/hero.png +0 -0
- package/mintlify-docs/images/knowledge_sources.png +0 -0
- package/mintlify-docs/images/mcp.png +0 -0
- package/mintlify-docs/images/scaling.png +0 -0
- package/mintlify-docs/index.mdx +0 -411
- package/mintlify-docs/logo/dark.svg +0 -9
- package/mintlify-docs/logo/light.svg +0 -9
- package/mintlify-docs/partners.mdx +0 -558
- package/mintlify-docs/products.mdx +0 -77
- package/mintlify-docs/snippets/snippet-intro.mdx +0 -4
- package/mintlify-docs/styles.css +0 -207
- package/ngrok.bash +0 -1
- package/ngrok.md +0 -6
- package/ngrok.yml +0 -10
- package/release.config.cjs +0 -15
- package/skills-lock.json +0 -10
- package/types/context-processor.ts +0 -45
- package/types/enums/eval-types.ts +0 -5
- package/types/enums/field-types.ts +0 -1
- package/types/enums/jobs.ts +0 -11
- package/types/enums/statistics.ts +0 -13
- package/types/exulu-table-definition.ts +0 -79
- package/types/file-types.ts +0 -18
- package/types/models/agent-session.ts +0 -27
- package/types/models/agent.ts +0 -68
- package/types/models/context.ts +0 -53
- package/types/models/embedding.ts +0 -17
- package/types/models/eval-run.ts +0 -40
- package/types/models/exulu-agent-tool-config.ts +0 -11
- package/types/models/item.ts +0 -21
- package/types/models/job.ts +0 -8
- package/types/models/project.ts +0 -16
- package/types/models/rate-limiter-rules.ts +0 -7
- package/types/models/test-case.ts +0 -25
- package/types/models/tool.ts +0 -9
- package/types/models/user-role.ts +0 -12
- package/types/models/user.ts +0 -20
- package/types/models/variable.ts +0 -8
- package/types/models/vector-methods.ts +0 -7
- package/types/provider-config.ts +0 -21
- package/types/queue-config.ts +0 -16
- package/types/rbac-rights-modes.ts +0 -1
- package/types/statistics.ts +0 -20
- package/types/workflow.ts +0 -31
- /package/ee/{documents → python/documents}/THIRD_PARTY_LICENSES/docling.txt +0 -0
- /package/ee/{documents/processing → python}/requirements.txt +0 -0
|
@@ -1,810 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
title: "ExuluAuthentication"
|
|
3
|
-
description: "Authentication function for API keys, auth tokens, and internal service communication"
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
## Overview
|
|
7
|
-
|
|
8
|
-
`ExuluAuthentication` is an authentication function that verifies users through three methods: API keys, session tokens (NextAuth/JWT), or internal service keys. It returns authenticated user information with role-based permissions.
|
|
9
|
-
|
|
10
|
-
## Authentication methods
|
|
11
|
-
|
|
12
|
-
<CardGroup cols={3}>
|
|
13
|
-
<Card title="API Keys" icon="key">
|
|
14
|
-
For programmatic access to Exulu APIs
|
|
15
|
-
</Card>
|
|
16
|
-
<Card title="Session Tokens" icon="shield">
|
|
17
|
-
For web application authentication via NextAuth
|
|
18
|
-
</Card>
|
|
19
|
-
<Card title="Internal Keys" icon="lock">
|
|
20
|
-
For secure service-to-service communication
|
|
21
|
-
</Card>
|
|
22
|
-
</CardGroup>
|
|
23
|
-
|
|
24
|
-
## What is ExuluAuthentication?
|
|
25
|
-
|
|
26
|
-
ExuluAuthentication provides a unified authentication interface that:
|
|
27
|
-
|
|
28
|
-
- **Validates API keys**: Securely compares bcrypt-hashed API keys
|
|
29
|
-
- **Verifies session tokens**: Validates NextAuth JWT tokens from web applications
|
|
30
|
-
- **Authorizes internal services**: Allows trusted service-to-service communication
|
|
31
|
-
- **Retrieves user data**: Returns user information with role permissions
|
|
32
|
-
- **Updates usage tracking**: Records last API key usage timestamp
|
|
33
|
-
|
|
34
|
-
## Quick start
|
|
35
|
-
|
|
36
|
-
```typescript
|
|
37
|
-
import { ExuluAuthentication, postgresClient } from "@exulu/backend";
|
|
38
|
-
|
|
39
|
-
const { db } = await postgresClient();
|
|
40
|
-
|
|
41
|
-
// Authenticate with API key
|
|
42
|
-
const result = await ExuluAuthentication.authenticate({
|
|
43
|
-
apikey: "exl_abc123.../my-key-name",
|
|
44
|
-
db
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
if (result.error) {
|
|
48
|
-
console.error(`Auth failed: ${result.message}`);
|
|
49
|
-
// Handle error (return 401, etc.)
|
|
50
|
-
} else {
|
|
51
|
-
console.log(`Authenticated as: ${result.user?.email}`);
|
|
52
|
-
// Proceed with authenticated request
|
|
53
|
-
}
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
## Authentication methods
|
|
57
|
-
|
|
58
|
-
### 1. API Key authentication
|
|
59
|
-
|
|
60
|
-
API keys are used for programmatic access to Exulu APIs.
|
|
61
|
-
|
|
62
|
-
**Format:** `{hashed_key}/{key_name}`
|
|
63
|
-
|
|
64
|
-
**Example:** `exl_abc123def456.../production-api-key`
|
|
65
|
-
|
|
66
|
-
```typescript
|
|
67
|
-
import { ExuluAuthentication, postgresClient } from "@exulu/backend";
|
|
68
|
-
|
|
69
|
-
const { db } = await postgresClient();
|
|
70
|
-
|
|
71
|
-
const result = await ExuluAuthentication.authenticate({
|
|
72
|
-
apikey: "exl_abc123def456.../production-api-key",
|
|
73
|
-
db
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
if (result.error) {
|
|
77
|
-
return { status: result.code, message: result.message };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const user = result.user;
|
|
81
|
-
console.log(`Authenticated: ${user?.email}`);
|
|
82
|
-
console.log(`Role: ${user?.role.name}`);
|
|
83
|
-
console.log(`Permissions:`, user?.role);
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
**How it works:**
|
|
87
|
-
|
|
88
|
-
1. Extracts key name from format: `{key}/{name}`
|
|
89
|
-
2. Queries database for API users with matching key name
|
|
90
|
-
3. Compares hashed portion using bcrypt
|
|
91
|
-
4. Updates `last_used` timestamp on successful match
|
|
92
|
-
5. Returns user with role information
|
|
93
|
-
|
|
94
|
-
**API key structure:**
|
|
95
|
-
|
|
96
|
-
- **Before `/`**: Hashed key value (bcrypt)
|
|
97
|
-
- **After `/`**: Human-readable key name
|
|
98
|
-
|
|
99
|
-
### 2. Session token authentication
|
|
100
|
-
|
|
101
|
-
Session tokens are JWT tokens issued by NextAuth for web application authentication.
|
|
102
|
-
|
|
103
|
-
```typescript
|
|
104
|
-
import { ExuluAuthentication, postgresClient } from "@exulu/backend";
|
|
105
|
-
|
|
106
|
-
const { db } = await postgresClient();
|
|
107
|
-
|
|
108
|
-
// Token from NextAuth session
|
|
109
|
-
const authToken = {
|
|
110
|
-
email: "user@example.com",
|
|
111
|
-
name: "John Doe",
|
|
112
|
-
// ... other session data
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const result = await ExuluAuthentication.authenticate({
|
|
116
|
-
authtoken: authToken,
|
|
117
|
-
db
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
if (result.error) {
|
|
121
|
-
return { status: 401, message: "Unauthorized" };
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const user = result.user;
|
|
125
|
-
console.log(`User: ${user?.firstname} ${user?.lastname}`);
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
**How it works:**
|
|
129
|
-
|
|
130
|
-
1. Extracts email from session token
|
|
131
|
-
2. Queries database for user by email
|
|
132
|
-
3. Loads user's role information
|
|
133
|
-
4. Returns user object with role
|
|
134
|
-
|
|
135
|
-
**Usage in Express middleware:**
|
|
136
|
-
|
|
137
|
-
```typescript
|
|
138
|
-
import { ExuluAuthentication, postgresClient } from "@exulu/backend";
|
|
139
|
-
import { getToken } from "next-auth/jwt";
|
|
140
|
-
import express from "express";
|
|
141
|
-
|
|
142
|
-
const app = express();
|
|
143
|
-
|
|
144
|
-
app.use(async (req, res, next) => {
|
|
145
|
-
const token = await getToken({ req });
|
|
146
|
-
|
|
147
|
-
const { db } = await postgresClient();
|
|
148
|
-
const result = await ExuluAuthentication.authenticate({
|
|
149
|
-
authtoken: token,
|
|
150
|
-
db
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
if (result.error) {
|
|
154
|
-
return res.status(result.code || 401).json({
|
|
155
|
-
error: result.message
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
req.user = result.user;
|
|
160
|
-
next();
|
|
161
|
-
});
|
|
162
|
-
```
|
|
163
|
-
|
|
164
|
-
### 3. Internal key authentication
|
|
165
|
-
|
|
166
|
-
Internal keys enable secure communication between internal Exulu services (e.g., backend and file upload service).
|
|
167
|
-
|
|
168
|
-
**Environment variable:** `INTERNAL_SECRET`
|
|
169
|
-
|
|
170
|
-
```typescript
|
|
171
|
-
import { ExuluAuthentication, postgresClient } from "@exulu/backend";
|
|
172
|
-
|
|
173
|
-
const { db } = await postgresClient();
|
|
174
|
-
|
|
175
|
-
const result = await ExuluAuthentication.authenticate({
|
|
176
|
-
internalkey: process.env.INTERNAL_SECRET,
|
|
177
|
-
db
|
|
178
|
-
});
|
|
179
|
-
|
|
180
|
-
if (result.error) {
|
|
181
|
-
throw new Error("Internal authentication failed");
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Returns a synthetic "internal" user
|
|
185
|
-
const user = result.user;
|
|
186
|
-
console.log(user?.email); // "internal@exulu.com"
|
|
187
|
-
console.log(user?.role.name); // "Internal"
|
|
188
|
-
```
|
|
189
|
-
|
|
190
|
-
**How it works:**
|
|
191
|
-
|
|
192
|
-
1. Checks if `INTERNAL_SECRET` environment variable is set
|
|
193
|
-
2. Compares provided internal key with `INTERNAL_SECRET`
|
|
194
|
-
3. Returns a synthetic "internal" user with read-only role permissions
|
|
195
|
-
|
|
196
|
-
**Synthetic internal user:**
|
|
197
|
-
|
|
198
|
-
```typescript
|
|
199
|
-
{
|
|
200
|
-
type: "api",
|
|
201
|
-
id: 192837465,
|
|
202
|
-
email: "internal@exulu.com",
|
|
203
|
-
firstname: "API",
|
|
204
|
-
lastname: "User",
|
|
205
|
-
role: {
|
|
206
|
-
id: "internal",
|
|
207
|
-
name: "Internal",
|
|
208
|
-
agents: "read",
|
|
209
|
-
workflows: "read",
|
|
210
|
-
variables: "read",
|
|
211
|
-
users: "read",
|
|
212
|
-
evals: "read"
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
**Use case:**
|
|
218
|
-
|
|
219
|
-
When the backend and file upload service (Uppy) run on different networks or environments, internal key authentication allows them to communicate securely without requiring user credentials.
|
|
220
|
-
|
|
221
|
-
## Function signature
|
|
222
|
-
|
|
223
|
-
```typescript
|
|
224
|
-
ExuluAuthentication.authenticate({
|
|
225
|
-
apikey?: string;
|
|
226
|
-
authtoken?: any;
|
|
227
|
-
internalkey?: string;
|
|
228
|
-
db: Knex;
|
|
229
|
-
}): Promise<{
|
|
230
|
-
error: boolean;
|
|
231
|
-
message?: string;
|
|
232
|
-
code?: number;
|
|
233
|
-
user?: User;
|
|
234
|
-
}>
|
|
235
|
-
```
|
|
236
|
-
|
|
237
|
-
### Parameters
|
|
238
|
-
|
|
239
|
-
<ParamField path="apikey" type="string">
|
|
240
|
-
API key in format `{hashed_key}/{key_name}`
|
|
241
|
-
</ParamField>
|
|
242
|
-
|
|
243
|
-
<ParamField path="authtoken" type="any">
|
|
244
|
-
NextAuth session token object (must contain `email` field)
|
|
245
|
-
</ParamField>
|
|
246
|
-
|
|
247
|
-
<ParamField path="internalkey" type="string">
|
|
248
|
-
Internal service key matching `INTERNAL_SECRET` environment variable
|
|
249
|
-
</ParamField>
|
|
250
|
-
|
|
251
|
-
<ParamField path="db" type="Knex" required>
|
|
252
|
-
Knex database connection instance
|
|
253
|
-
</ParamField>
|
|
254
|
-
|
|
255
|
-
### Return value
|
|
256
|
-
|
|
257
|
-
<ResponseField name="error" type="boolean">
|
|
258
|
-
Whether authentication failed
|
|
259
|
-
</ResponseField>
|
|
260
|
-
|
|
261
|
-
<ResponseField name="message" type="string">
|
|
262
|
-
Error message (only present if `error: true`)
|
|
263
|
-
</ResponseField>
|
|
264
|
-
|
|
265
|
-
<ResponseField name="code" type="number">
|
|
266
|
-
HTTP status code (200 for success, 401 for failure)
|
|
267
|
-
</ResponseField>
|
|
268
|
-
|
|
269
|
-
<ResponseField name="user" type="User">
|
|
270
|
-
Authenticated user object with role information
|
|
271
|
-
```typescript
|
|
272
|
-
{
|
|
273
|
-
id: number;
|
|
274
|
-
firstname?: string;
|
|
275
|
-
lastname?: string;
|
|
276
|
-
email: string;
|
|
277
|
-
type?: "api" | "user";
|
|
278
|
-
role: {
|
|
279
|
-
id: string;
|
|
280
|
-
name: string;
|
|
281
|
-
agents: "read" | "write";
|
|
282
|
-
evals: "read" | "write";
|
|
283
|
-
workflows: "read" | "write";
|
|
284
|
-
variables: "read" | "write";
|
|
285
|
-
users: "read" | "write";
|
|
286
|
-
};
|
|
287
|
-
}
|
|
288
|
-
```
|
|
289
|
-
</ResponseField>
|
|
290
|
-
|
|
291
|
-
## Usage patterns
|
|
292
|
-
|
|
293
|
-
### Express middleware
|
|
294
|
-
|
|
295
|
-
```typescript
|
|
296
|
-
import { ExuluAuthentication, postgresClient } from "@exulu/backend";
|
|
297
|
-
import express from "express";
|
|
298
|
-
|
|
299
|
-
const app = express();
|
|
300
|
-
|
|
301
|
-
// Authentication middleware
|
|
302
|
-
app.use(async (req, res, next) => {
|
|
303
|
-
const { db } = await postgresClient();
|
|
304
|
-
|
|
305
|
-
// Extract credentials from headers
|
|
306
|
-
const apiKey = req.headers["x-api-key"] as string;
|
|
307
|
-
const internalKey = req.headers["internal"] as string;
|
|
308
|
-
const authHeader = req.headers["authorization"];
|
|
309
|
-
|
|
310
|
-
// Session token from Bearer token
|
|
311
|
-
let authToken;
|
|
312
|
-
if (authHeader?.startsWith("Bearer ")) {
|
|
313
|
-
const token = authHeader.substring(7);
|
|
314
|
-
authToken = await decodeJWT(token); // Decode JWT
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
const result = await ExuluAuthentication.authenticate({
|
|
318
|
-
apikey: apiKey,
|
|
319
|
-
authtoken: authToken,
|
|
320
|
-
internalkey: internalKey,
|
|
321
|
-
db
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
if (result.error) {
|
|
325
|
-
return res.status(result.code || 401).json({
|
|
326
|
-
error: result.message
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// Attach user to request
|
|
331
|
-
req.user = result.user;
|
|
332
|
-
next();
|
|
333
|
-
});
|
|
334
|
-
|
|
335
|
-
// Protected route
|
|
336
|
-
app.get("/api/agents", async (req, res) => {
|
|
337
|
-
// User is available from middleware
|
|
338
|
-
const user = req.user;
|
|
339
|
-
|
|
340
|
-
if (user.role.agents !== "read" && user.role.agents !== "write") {
|
|
341
|
-
return res.status(403).json({ error: "Forbidden" });
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// Fetch agents
|
|
345
|
-
const agents = await db.from("agents").select("*");
|
|
346
|
-
res.json(agents);
|
|
347
|
-
});
|
|
348
|
-
```
|
|
349
|
-
|
|
350
|
-
### Role-based access control
|
|
351
|
-
|
|
352
|
-
```typescript
|
|
353
|
-
import { ExuluAuthentication, postgresClient } from "@exulu/backend";
|
|
354
|
-
|
|
355
|
-
async function checkPermission(
|
|
356
|
-
apiKey: string,
|
|
357
|
-
resource: "agents" | "evals" | "workflows" | "variables" | "users",
|
|
358
|
-
requiredPermission: "read" | "write"
|
|
359
|
-
) {
|
|
360
|
-
const { db } = await postgresClient();
|
|
361
|
-
|
|
362
|
-
const result = await ExuluAuthentication.authenticate({ apikey: apiKey, db });
|
|
363
|
-
|
|
364
|
-
if (result.error) {
|
|
365
|
-
throw new Error(`Authentication failed: ${result.message}`);
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
const userPermission = result.user?.role[resource];
|
|
369
|
-
|
|
370
|
-
if (userPermission !== requiredPermission && userPermission !== "write") {
|
|
371
|
-
throw new Error(
|
|
372
|
-
`Insufficient permissions. User has ${userPermission} but ${requiredPermission} is required.`
|
|
373
|
-
);
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return result.user;
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
// Use
|
|
380
|
-
try {
|
|
381
|
-
const user = await checkPermission(apiKey, "agents", "write");
|
|
382
|
-
// Proceed with agent creation
|
|
383
|
-
} catch (error) {
|
|
384
|
-
console.error(error.message);
|
|
385
|
-
// Return 403 Forbidden
|
|
386
|
-
}
|
|
387
|
-
```
|
|
388
|
-
|
|
389
|
-
### Multi-method authentication
|
|
390
|
-
|
|
391
|
-
Support multiple authentication methods in the same endpoint:
|
|
392
|
-
|
|
393
|
-
```typescript
|
|
394
|
-
import { ExuluAuthentication, postgresClient } from "@exulu/backend";
|
|
395
|
-
import { getToken } from "next-auth/jwt";
|
|
396
|
-
|
|
397
|
-
async function authenticateRequest(req: Request) {
|
|
398
|
-
const { db } = await postgresClient();
|
|
399
|
-
|
|
400
|
-
// Try API key
|
|
401
|
-
const apiKey = req.headers.get("x-api-key");
|
|
402
|
-
if (apiKey) {
|
|
403
|
-
return await ExuluAuthentication.authenticate({ apikey: apiKey, db });
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
// Try session token
|
|
407
|
-
const token = await getToken({ req });
|
|
408
|
-
if (token) {
|
|
409
|
-
return await ExuluAuthentication.authenticate({ authtoken: token, db });
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
// Try internal key
|
|
413
|
-
const internalKey = req.headers.get("internal");
|
|
414
|
-
if (internalKey) {
|
|
415
|
-
return await ExuluAuthentication.authenticate({ internalkey: internalKey, db });
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
// No credentials provided
|
|
419
|
-
return {
|
|
420
|
-
error: true,
|
|
421
|
-
message: "No authentication credentials provided",
|
|
422
|
-
code: 401
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
// Use in API route
|
|
427
|
-
export async function GET(req: Request) {
|
|
428
|
-
const authResult = await authenticateRequest(req);
|
|
429
|
-
|
|
430
|
-
if (authResult.error) {
|
|
431
|
-
return new Response(
|
|
432
|
-
JSON.stringify({ error: authResult.message }),
|
|
433
|
-
{ status: authResult.code }
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
const user = authResult.user;
|
|
438
|
-
// Process request with authenticated user
|
|
439
|
-
}
|
|
440
|
-
```
|
|
441
|
-
|
|
442
|
-
### Caching authenticated users
|
|
443
|
-
|
|
444
|
-
For high-traffic APIs, cache user lookups:
|
|
445
|
-
|
|
446
|
-
```typescript
|
|
447
|
-
import { ExuluAuthentication, postgresClient } from "@exulu/backend";
|
|
448
|
-
|
|
449
|
-
class AuthCache {
|
|
450
|
-
private cache = new Map<string, { user: User; expires: number }>();
|
|
451
|
-
private ttl = 5 * 60 * 1000; // 5 minutes
|
|
452
|
-
|
|
453
|
-
async authenticate(apiKey: string) {
|
|
454
|
-
const cached = this.cache.get(apiKey);
|
|
455
|
-
|
|
456
|
-
if (cached && cached.expires > Date.now()) {
|
|
457
|
-
return { error: false, code: 200, user: cached.user };
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
const { db } = await postgresClient();
|
|
461
|
-
const result = await ExuluAuthentication.authenticate({ apikey: apiKey, db });
|
|
462
|
-
|
|
463
|
-
if (!result.error && result.user) {
|
|
464
|
-
this.cache.set(apiKey, {
|
|
465
|
-
user: result.user,
|
|
466
|
-
expires: Date.now() + this.ttl
|
|
467
|
-
});
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
return result;
|
|
471
|
-
}
|
|
472
|
-
|
|
473
|
-
invalidate(apiKey: string) {
|
|
474
|
-
this.cache.delete(apiKey);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
clear() {
|
|
478
|
-
this.cache.clear();
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
const authCache = new AuthCache();
|
|
483
|
-
|
|
484
|
-
// Use
|
|
485
|
-
const result = await authCache.authenticate(apiKey);
|
|
486
|
-
```
|
|
487
|
-
|
|
488
|
-
## Generating API keys
|
|
489
|
-
|
|
490
|
-
API keys must be generated and stored in the database:
|
|
491
|
-
|
|
492
|
-
```typescript
|
|
493
|
-
import bcrypt from "bcryptjs";
|
|
494
|
-
import { randomBytes } from "crypto";
|
|
495
|
-
import { postgresClient } from "@exulu/backend";
|
|
496
|
-
|
|
497
|
-
async function generateApiKey(name: string, email: string) {
|
|
498
|
-
const { db } = await postgresClient();
|
|
499
|
-
|
|
500
|
-
// Generate random key
|
|
501
|
-
const keyValue = `exl_${randomBytes(32).toString("hex")}`;
|
|
502
|
-
|
|
503
|
-
// Hash the key
|
|
504
|
-
const hashedKey = await bcrypt.hash(keyValue, 10);
|
|
505
|
-
|
|
506
|
-
// Create API key string
|
|
507
|
-
const apiKey = `${hashedKey}/${name}`;
|
|
508
|
-
|
|
509
|
-
// Create API user
|
|
510
|
-
await db.into("users").insert({
|
|
511
|
-
email,
|
|
512
|
-
type: "api",
|
|
513
|
-
apikey: apiKey,
|
|
514
|
-
role: "api_user", // Role ID
|
|
515
|
-
created_at: new Date()
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
// Return the unhashed key (only time user sees it)
|
|
519
|
-
return `${keyValue}/${name}`;
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
// Generate
|
|
523
|
-
const apiKey = await generateApiKey("production-key", "api@example.com");
|
|
524
|
-
console.log("API Key (save this!):", apiKey);
|
|
525
|
-
// exl_abc123def456.../production-key
|
|
526
|
-
```
|
|
527
|
-
|
|
528
|
-
<Warning>
|
|
529
|
-
The unhashed API key is only available at generation time. Store it securely, as it cannot be retrieved later.
|
|
530
|
-
</Warning>
|
|
531
|
-
|
|
532
|
-
## Database schema
|
|
533
|
-
|
|
534
|
-
### Users table
|
|
535
|
-
|
|
536
|
-
```sql
|
|
537
|
-
CREATE TABLE users (
|
|
538
|
-
id SERIAL PRIMARY KEY,
|
|
539
|
-
firstname VARCHAR(255),
|
|
540
|
-
lastname VARCHAR(255),
|
|
541
|
-
email VARCHAR(255) UNIQUE NOT NULL,
|
|
542
|
-
type VARCHAR(10), -- 'api' or 'user'
|
|
543
|
-
apikey TEXT, -- Format: {hashed_key}/{key_name}
|
|
544
|
-
role VARCHAR(255), -- Role ID
|
|
545
|
-
last_used TIMESTAMP,
|
|
546
|
-
created_at TIMESTAMP DEFAULT NOW(),
|
|
547
|
-
updated_at TIMESTAMP DEFAULT NOW()
|
|
548
|
-
);
|
|
549
|
-
```
|
|
550
|
-
|
|
551
|
-
### Roles table
|
|
552
|
-
|
|
553
|
-
```sql
|
|
554
|
-
CREATE TABLE roles (
|
|
555
|
-
id VARCHAR(255) PRIMARY KEY,
|
|
556
|
-
name VARCHAR(255) NOT NULL,
|
|
557
|
-
agents VARCHAR(10), -- 'read' or 'write'
|
|
558
|
-
evals VARCHAR(10),
|
|
559
|
-
workflows VARCHAR(10),
|
|
560
|
-
variables VARCHAR(10),
|
|
561
|
-
users VARCHAR(10)
|
|
562
|
-
);
|
|
563
|
-
```
|
|
564
|
-
|
|
565
|
-
### Example role
|
|
566
|
-
|
|
567
|
-
```sql
|
|
568
|
-
INSERT INTO roles (id, name, agents, evals, workflows, variables, users)
|
|
569
|
-
VALUES (
|
|
570
|
-
'api_user',
|
|
571
|
-
'API User',
|
|
572
|
-
'write',
|
|
573
|
-
'write',
|
|
574
|
-
'read',
|
|
575
|
-
'read',
|
|
576
|
-
'read'
|
|
577
|
-
);
|
|
578
|
-
```
|
|
579
|
-
|
|
580
|
-
## Error handling
|
|
581
|
-
|
|
582
|
-
ExuluAuthentication returns different error messages for different failure scenarios:
|
|
583
|
-
|
|
584
|
-
### API key errors
|
|
585
|
-
|
|
586
|
-
```typescript
|
|
587
|
-
// No API key name
|
|
588
|
-
{
|
|
589
|
-
error: true,
|
|
590
|
-
message: "Provided api key does not include postfix with key name ({key}/{name}).",
|
|
591
|
-
code: 401
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// Invalid format
|
|
595
|
-
{
|
|
596
|
-
error: true,
|
|
597
|
-
message: "Provided api key is not in the correct format.",
|
|
598
|
-
code: 401
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// No API users in database
|
|
602
|
-
{
|
|
603
|
-
error: true,
|
|
604
|
-
message: "No API users found.",
|
|
605
|
-
code: 401
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
// No matching key
|
|
609
|
-
{
|
|
610
|
-
error: true,
|
|
611
|
-
message: "No matching api key found.",
|
|
612
|
-
code: 401
|
|
613
|
-
}
|
|
614
|
-
```
|
|
615
|
-
|
|
616
|
-
### Session token errors
|
|
617
|
-
|
|
618
|
-
```typescript
|
|
619
|
-
// No email in token
|
|
620
|
-
{
|
|
621
|
-
error: true,
|
|
622
|
-
message: "No email provided in session {...}",
|
|
623
|
-
code: 401
|
|
624
|
-
}
|
|
625
|
-
|
|
626
|
-
// User not found
|
|
627
|
-
{
|
|
628
|
-
error: true,
|
|
629
|
-
message: "No user found for email: user@example.com",
|
|
630
|
-
code: 401
|
|
631
|
-
}
|
|
632
|
-
|
|
633
|
-
// Invalid token
|
|
634
|
-
{
|
|
635
|
-
error: true,
|
|
636
|
-
message: "Invalid token.",
|
|
637
|
-
code: 401
|
|
638
|
-
}
|
|
639
|
-
```
|
|
640
|
-
|
|
641
|
-
### Internal key errors
|
|
642
|
-
|
|
643
|
-
```typescript
|
|
644
|
-
// INTERNAL_SECRET not set
|
|
645
|
-
{
|
|
646
|
-
error: true,
|
|
647
|
-
message: 'Header "internal" provided, but no INTERNAL_SECRET was provided in the environment variables.',
|
|
648
|
-
code: 401
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
// Key mismatch
|
|
652
|
-
{
|
|
653
|
-
error: true,
|
|
654
|
-
message: "Internal key was provided in header but did not match the INTERNAL_SECRET environment variable.",
|
|
655
|
-
code: 401
|
|
656
|
-
}
|
|
657
|
-
```
|
|
658
|
-
|
|
659
|
-
### General error
|
|
660
|
-
|
|
661
|
-
```typescript
|
|
662
|
-
// No credentials
|
|
663
|
-
{
|
|
664
|
-
error: true,
|
|
665
|
-
message: "Either an api key or authorization key must be provided.",
|
|
666
|
-
code: 401
|
|
667
|
-
}
|
|
668
|
-
```
|
|
669
|
-
|
|
670
|
-
## Security best practices
|
|
671
|
-
|
|
672
|
-
<Tip>
|
|
673
|
-
**API key rotation**: Regularly rotate API keys and revoke old ones by deleting the user record.
|
|
674
|
-
</Tip>
|
|
675
|
-
|
|
676
|
-
<Note>
|
|
677
|
-
**HTTPS only**: Always use HTTPS in production to protect API keys and tokens in transit.
|
|
678
|
-
</Note>
|
|
679
|
-
|
|
680
|
-
<Warning>
|
|
681
|
-
**Never log keys**: Never log API keys or internal secrets in application logs or error messages.
|
|
682
|
-
</Warning>
|
|
683
|
-
|
|
684
|
-
<Info>
|
|
685
|
-
**Rate limiting**: Implement rate limiting per API key to prevent abuse.
|
|
686
|
-
</Info>
|
|
687
|
-
|
|
688
|
-
## Common patterns
|
|
689
|
-
|
|
690
|
-
### Permission checking helper
|
|
691
|
-
|
|
692
|
-
```typescript
|
|
693
|
-
function hasPermission(
|
|
694
|
-
user: User,
|
|
695
|
-
resource: keyof User["role"],
|
|
696
|
-
requiredLevel: "read" | "write"
|
|
697
|
-
): boolean {
|
|
698
|
-
const userPermission = user.role[resource];
|
|
699
|
-
|
|
700
|
-
if (requiredLevel === "read") {
|
|
701
|
-
return userPermission === "read" || userPermission === "write";
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
return userPermission === "write";
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
// Use
|
|
708
|
-
if (!hasPermission(user, "agents", "write")) {
|
|
709
|
-
return res.status(403).json({ error: "Forbidden" });
|
|
710
|
-
}
|
|
711
|
-
```
|
|
712
|
-
|
|
713
|
-
### Audit logging
|
|
714
|
-
|
|
715
|
-
```typescript
|
|
716
|
-
async function logAuthAttempt(
|
|
717
|
-
method: "apikey" | "authtoken" | "internalkey",
|
|
718
|
-
success: boolean,
|
|
719
|
-
userId?: number,
|
|
720
|
-
error?: string
|
|
721
|
-
) {
|
|
722
|
-
const { db } = await postgresClient();
|
|
723
|
-
|
|
724
|
-
await db.into("auth_logs").insert({
|
|
725
|
-
method,
|
|
726
|
-
success,
|
|
727
|
-
user_id: userId,
|
|
728
|
-
error_message: error,
|
|
729
|
-
timestamp: new Date()
|
|
730
|
-
});
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
// Use
|
|
734
|
-
const result = await ExuluAuthentication({ apikey, db });
|
|
735
|
-
|
|
736
|
-
await logAuthAttempt(
|
|
737
|
-
"apikey",
|
|
738
|
-
!result.error,
|
|
739
|
-
result.user?.id,
|
|
740
|
-
result.message
|
|
741
|
-
);
|
|
742
|
-
```
|
|
743
|
-
|
|
744
|
-
### API key revocation
|
|
745
|
-
|
|
746
|
-
```typescript
|
|
747
|
-
async function revokeApiKey(userId: number) {
|
|
748
|
-
const { db } = await postgresClient();
|
|
749
|
-
|
|
750
|
-
await db
|
|
751
|
-
.from("users")
|
|
752
|
-
.where({ id: userId, type: "api" })
|
|
753
|
-
.delete();
|
|
754
|
-
|
|
755
|
-
console.log(`Revoked API key for user ${userId}`);
|
|
756
|
-
}
|
|
757
|
-
```
|
|
758
|
-
|
|
759
|
-
## Integration with ExuluApp
|
|
760
|
-
|
|
761
|
-
Use ExuluAuthentication to protect ExuluApp API endpoints:
|
|
762
|
-
|
|
763
|
-
```typescript
|
|
764
|
-
import { ExuluApp, ExuluAuthentication, postgresClient } from "@exulu/backend";
|
|
765
|
-
|
|
766
|
-
const app = new ExuluApp();
|
|
767
|
-
|
|
768
|
-
await app.create({
|
|
769
|
-
config: {
|
|
770
|
-
express: {
|
|
771
|
-
enabled: true,
|
|
772
|
-
middleware: async (expressApp) => {
|
|
773
|
-
// Add authentication middleware
|
|
774
|
-
expressApp.use(async (req, res, next) => {
|
|
775
|
-
const { db } = await postgresClient();
|
|
776
|
-
const apiKey = req.headers["x-api-key"] as string;
|
|
777
|
-
|
|
778
|
-
const result = await ExuluAuthentication.authenticate({ apikey: apiKey, db });
|
|
779
|
-
|
|
780
|
-
if (result.error) {
|
|
781
|
-
return res.status(401).json({ error: "Unauthorized" });
|
|
782
|
-
}
|
|
783
|
-
|
|
784
|
-
req.user = result.user;
|
|
785
|
-
next();
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
},
|
|
790
|
-
contexts: {},
|
|
791
|
-
agents: {}
|
|
792
|
-
});
|
|
793
|
-
```
|
|
794
|
-
|
|
795
|
-
## Next steps
|
|
796
|
-
|
|
797
|
-
<CardGroup cols={2}>
|
|
798
|
-
<Card title="ExuluApp" icon="box" href="/core/exulu-app/introduction">
|
|
799
|
-
Integrate authentication with ExuluApp
|
|
800
|
-
</Card>
|
|
801
|
-
<Card title="ExuluVariables" icon="key" href="/core/exulu-variables/introduction">
|
|
802
|
-
Store INTERNAL_SECRET securely
|
|
803
|
-
</Card>
|
|
804
|
-
<Card title="ExuluProvider" icon="robot" href="/core/exulu-agent/introduction">
|
|
805
|
-
Protect provider endpoints with authentication
|
|
806
|
-
</Card>
|
|
807
|
-
<Card title="API Reference" icon="book" href="/api-reference/introduction">
|
|
808
|
-
View API documentation
|
|
809
|
-
</Card>
|
|
810
|
-
</CardGroup>
|