@elaraai/e3-api-server 0.0.2-beta.11 → 0.0.2-beta.13
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 +144 -29
- package/dist/src/async-operation-state.d.ts +63 -0
- package/dist/src/async-operation-state.d.ts.map +1 -0
- package/dist/src/async-operation-state.js +193 -0
- package/dist/src/async-operation-state.js.map +1 -0
- package/dist/src/auth/device.d.ts +26 -0
- package/dist/src/auth/device.d.ts.map +1 -0
- package/dist/src/auth/device.js +227 -0
- package/dist/src/auth/device.js.map +1 -0
- package/dist/src/auth/discovery.d.ts +23 -0
- package/dist/src/auth/discovery.d.ts.map +1 -0
- package/dist/src/auth/discovery.js +40 -0
- package/dist/src/auth/discovery.js.map +1 -0
- package/dist/src/auth/index.d.ts +56 -0
- package/dist/src/auth/index.d.ts.map +1 -0
- package/dist/src/auth/index.js +69 -0
- package/dist/src/auth/index.js.map +1 -0
- package/dist/src/auth/keys.d.ts +55 -0
- package/dist/src/auth/keys.d.ts.map +1 -0
- package/dist/src/auth/keys.js +78 -0
- package/dist/src/auth/keys.js.map +1 -0
- package/dist/src/beast2.d.ts +15 -3
- package/dist/src/beast2.d.ts.map +1 -1
- package/dist/src/beast2.js +30 -4
- package/dist/src/beast2.js.map +1 -1
- package/dist/src/cli.js +58 -6
- package/dist/src/cli.js.map +1 -1
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +5 -2
- package/dist/src/errors.js.map +1 -1
- package/dist/src/execution-state.d.ts +54 -0
- package/dist/src/execution-state.d.ts.map +1 -0
- package/dist/src/execution-state.js +150 -0
- package/dist/src/execution-state.js.map +1 -0
- package/dist/src/handlers/dataflow.d.ts +49 -0
- package/dist/src/handlers/dataflow.d.ts.map +1 -0
- package/dist/src/handlers/dataflow.js +371 -0
- package/dist/src/handlers/dataflow.js.map +1 -0
- package/dist/src/handlers/datasets.d.ts +23 -0
- package/dist/src/handlers/datasets.d.ts.map +1 -0
- package/dist/src/handlers/datasets.js +113 -0
- package/dist/src/handlers/datasets.js.map +1 -0
- package/dist/src/handlers/index.d.ts +12 -0
- package/dist/src/handlers/index.d.ts.map +1 -0
- package/dist/src/handlers/index.js +12 -0
- package/dist/src/handlers/index.js.map +1 -0
- package/dist/src/handlers/packages.d.ts +26 -0
- package/dist/src/handlers/packages.d.ts.map +1 -0
- package/dist/src/handlers/packages.js +101 -0
- package/dist/src/handlers/packages.js.map +1 -0
- package/dist/src/handlers/repos.d.ts +11 -0
- package/dist/src/handlers/repos.d.ts.map +1 -0
- package/dist/src/handlers/repos.js +52 -0
- package/dist/src/handlers/repos.js.map +1 -0
- package/dist/src/handlers/repository.d.ts +35 -0
- package/dist/src/handlers/repository.d.ts.map +1 -0
- package/dist/src/handlers/repository.js +142 -0
- package/dist/src/handlers/repository.js.map +1 -0
- package/dist/src/handlers/tasks.d.ts +18 -0
- package/dist/src/handlers/tasks.d.ts.map +1 -0
- package/dist/src/handlers/tasks.js +134 -0
- package/dist/src/handlers/tasks.js.map +1 -0
- package/dist/src/handlers/workspaces.d.ts +34 -0
- package/dist/src/handlers/workspaces.d.ts.map +1 -0
- package/dist/src/handlers/workspaces.js +225 -0
- package/dist/src/handlers/workspaces.js.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/middleware/auth.d.ts +51 -0
- package/dist/src/middleware/auth.d.ts.map +1 -0
- package/dist/src/middleware/auth.js +158 -0
- package/dist/src/middleware/auth.js.map +1 -0
- package/dist/src/routes/datasets.d.ts +2 -1
- package/dist/src/routes/datasets.d.ts.map +1 -1
- package/dist/src/routes/datasets.js +50 -85
- package/dist/src/routes/datasets.js.map +1 -1
- package/dist/src/routes/executions.d.ts +2 -1
- package/dist/src/routes/executions.d.ts.map +1 -1
- package/dist/src/routes/executions.js +60 -286
- package/dist/src/routes/executions.js.map +1 -1
- package/dist/src/routes/index.d.ts +11 -0
- package/dist/src/routes/index.d.ts.map +1 -0
- package/dist/src/routes/index.js +11 -0
- package/dist/src/routes/index.js.map +1 -0
- package/dist/src/routes/packages.d.ts +2 -1
- package/dist/src/routes/packages.d.ts.map +1 -1
- package/dist/src/routes/packages.js +42 -105
- package/dist/src/routes/packages.js.map +1 -1
- package/dist/src/routes/repository.d.ts +2 -1
- package/dist/src/routes/repository.d.ts.map +1 -1
- package/dist/src/routes/repository.js +19 -54
- package/dist/src/routes/repository.js.map +1 -1
- package/dist/src/routes/tasks.d.ts +2 -1
- package/dist/src/routes/tasks.d.ts.map +1 -1
- package/dist/src/routes/tasks.js +22 -46
- package/dist/src/routes/tasks.js.map +1 -1
- package/dist/src/routes/workspaces.d.ts +2 -1
- package/dist/src/routes/workspaces.d.ts.map +1 -1
- package/dist/src/routes/workspaces.js +45 -116
- package/dist/src/routes/workspaces.js.map +1 -1
- package/dist/src/server.d.ts +24 -3
- package/dist/src/server.d.ts.map +1 -1
- package/dist/src/server.js +178 -19
- package/dist/src/server.js.map +1 -1
- package/dist/src/types.d.ts +486 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +209 -2
- package/dist/src/types.js.map +1 -1
- package/package.json +16 -4
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* OAuth2 Device Authorization Grant (RFC 8628) implementation.
|
|
7
|
+
*/
|
|
8
|
+
import * as crypto from 'node:crypto';
|
|
9
|
+
import { Hono } from 'hono';
|
|
10
|
+
import { signJwt } from './keys.js';
|
|
11
|
+
// In-memory pending authorizations
|
|
12
|
+
const pendingAuths = new Map();
|
|
13
|
+
// Clean up expired authorizations periodically
|
|
14
|
+
// Use unref() so this timer doesn't prevent process exit
|
|
15
|
+
const cleanupInterval = setInterval(() => {
|
|
16
|
+
const now = Date.now();
|
|
17
|
+
for (const [deviceCode, auth] of pendingAuths) {
|
|
18
|
+
if (auth.expiresAt < now) {
|
|
19
|
+
pendingAuths.delete(deviceCode);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}, 60000); // Every minute
|
|
23
|
+
cleanupInterval.unref();
|
|
24
|
+
/**
|
|
25
|
+
* Generate a random device code.
|
|
26
|
+
*/
|
|
27
|
+
function generateDeviceCode() {
|
|
28
|
+
return crypto.randomBytes(32).toString('hex');
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Generate a user-friendly code (e.g., ABCD-1234).
|
|
32
|
+
*/
|
|
33
|
+
function generateUserCode() {
|
|
34
|
+
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; // Exclude confusing chars
|
|
35
|
+
let code = '';
|
|
36
|
+
for (let i = 0; i < 8; i++) {
|
|
37
|
+
if (i === 4)
|
|
38
|
+
code += '-';
|
|
39
|
+
code += chars[crypto.randomInt(chars.length)];
|
|
40
|
+
}
|
|
41
|
+
return code;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create device flow routes.
|
|
45
|
+
*/
|
|
46
|
+
export function createDeviceRoutes(config) {
|
|
47
|
+
const app = new Hono();
|
|
48
|
+
// POST /oauth2/device_authorization - Start device flow
|
|
49
|
+
app.post('/oauth2/device_authorization', (c) => {
|
|
50
|
+
const deviceCode = generateDeviceCode();
|
|
51
|
+
const userCode = generateUserCode();
|
|
52
|
+
const expiresIn = 300; // 5 minutes
|
|
53
|
+
pendingAuths.set(deviceCode, {
|
|
54
|
+
userCode,
|
|
55
|
+
approved: config.autoApprove, // Auto-approve in CI mode
|
|
56
|
+
expiresAt: Date.now() + expiresIn * 1000,
|
|
57
|
+
});
|
|
58
|
+
return c.json({
|
|
59
|
+
device_code: deviceCode,
|
|
60
|
+
user_code: userCode,
|
|
61
|
+
verification_uri: `${config.baseUrl}/device`,
|
|
62
|
+
verification_uri_complete: `${config.baseUrl}/device?user_code=${userCode}`,
|
|
63
|
+
expires_in: expiresIn,
|
|
64
|
+
interval: 1, // Poll every 1 second (fast for dev)
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
// GET /device - HTML approval page
|
|
68
|
+
app.get('/device', (c) => {
|
|
69
|
+
const userCode = c.req.query('user_code') || '';
|
|
70
|
+
const autoApproveScript = config.autoApprove
|
|
71
|
+
? `<script>setTimeout(() => document.forms[0].submit(), 500);</script>`
|
|
72
|
+
: '';
|
|
73
|
+
return c.html(`<!DOCTYPE html>
|
|
74
|
+
<html>
|
|
75
|
+
<head>
|
|
76
|
+
<title>e3 Device Login</title>
|
|
77
|
+
<style>
|
|
78
|
+
body { font-family: system-ui, sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; }
|
|
79
|
+
h1 { color: #333; }
|
|
80
|
+
.code { font-size: 2em; font-family: monospace; letter-spacing: 0.1em; background: #f0f0f0; padding: 10px 20px; border-radius: 4px; }
|
|
81
|
+
button { background: #0066cc; color: white; border: none; padding: 12px 24px; font-size: 1em; border-radius: 4px; cursor: pointer; margin-top: 20px; }
|
|
82
|
+
button:hover { background: #0055aa; }
|
|
83
|
+
.auto { color: #666; font-style: italic; margin-top: 10px; }
|
|
84
|
+
</style>
|
|
85
|
+
</head>
|
|
86
|
+
<body>
|
|
87
|
+
<h1>e3 Device Login</h1>
|
|
88
|
+
<p>Confirm this code matches what's shown in your terminal:</p>
|
|
89
|
+
<div class="code">${userCode || '(no code)'}</div>
|
|
90
|
+
<form method="POST" action="/device/approve">
|
|
91
|
+
<input type="hidden" name="user_code" value="${userCode}">
|
|
92
|
+
<button type="submit">Approve</button>
|
|
93
|
+
</form>
|
|
94
|
+
${config.autoApprove ? '<p class="auto">Auto-approving in CI mode...</p>' : ''}
|
|
95
|
+
${autoApproveScript}
|
|
96
|
+
</body>
|
|
97
|
+
</html>`);
|
|
98
|
+
});
|
|
99
|
+
// POST /device/approve - Approve device code
|
|
100
|
+
app.post('/device/approve', async (c) => {
|
|
101
|
+
const body = await c.req.parseBody();
|
|
102
|
+
const userCode = String(body['user_code'] || '');
|
|
103
|
+
// Find the pending auth with this user code
|
|
104
|
+
for (const [_deviceCode, auth] of pendingAuths) {
|
|
105
|
+
if (auth.userCode === userCode && auth.expiresAt > Date.now()) {
|
|
106
|
+
auth.approved = true;
|
|
107
|
+
return c.html(`<!DOCTYPE html>
|
|
108
|
+
<html>
|
|
109
|
+
<head>
|
|
110
|
+
<title>e3 Device Login</title>
|
|
111
|
+
<style>
|
|
112
|
+
body { font-family: system-ui, sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; }
|
|
113
|
+
h1 { color: #22aa22; }
|
|
114
|
+
</style>
|
|
115
|
+
</head>
|
|
116
|
+
<body>
|
|
117
|
+
<h1>✓ Approved</h1>
|
|
118
|
+
<p>You can close this window and return to your terminal.</p>
|
|
119
|
+
</body>
|
|
120
|
+
</html>`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return c.html(`<!DOCTYPE html>
|
|
124
|
+
<html>
|
|
125
|
+
<head>
|
|
126
|
+
<title>e3 Device Login</title>
|
|
127
|
+
<style>
|
|
128
|
+
body { font-family: system-ui, sans-serif; max-width: 400px; margin: 50px auto; padding: 20px; }
|
|
129
|
+
h1 { color: #cc0000; }
|
|
130
|
+
</style>
|
|
131
|
+
</head>
|
|
132
|
+
<body>
|
|
133
|
+
<h1>✗ Invalid or Expired Code</h1>
|
|
134
|
+
<p>The code may have expired. Please try again.</p>
|
|
135
|
+
</body>
|
|
136
|
+
</html>`, 400);
|
|
137
|
+
});
|
|
138
|
+
// POST /oauth2/token - Exchange device code for tokens OR refresh tokens
|
|
139
|
+
app.post('/oauth2/token', async (c) => {
|
|
140
|
+
const body = await c.req.parseBody();
|
|
141
|
+
const grantType = String(body['grant_type'] || '');
|
|
142
|
+
// Device code grant
|
|
143
|
+
if (grantType === 'urn:ietf:params:oauth:grant-type:device_code') {
|
|
144
|
+
const deviceCode = String(body['device_code'] || '');
|
|
145
|
+
const auth = pendingAuths.get(deviceCode);
|
|
146
|
+
if (!auth || auth.expiresAt < Date.now()) {
|
|
147
|
+
return c.json({ error: 'expired_token' }, 400);
|
|
148
|
+
}
|
|
149
|
+
if (!auth.approved) {
|
|
150
|
+
return c.json({ error: 'authorization_pending' }, 400);
|
|
151
|
+
}
|
|
152
|
+
// Delete the pending auth
|
|
153
|
+
pendingAuths.delete(deviceCode);
|
|
154
|
+
// Generate tokens
|
|
155
|
+
const now = Math.floor(Date.now() / 1000);
|
|
156
|
+
const accessPayload = {
|
|
157
|
+
sub: 'dev-user',
|
|
158
|
+
iss: config.baseUrl,
|
|
159
|
+
aud: config.baseUrl,
|
|
160
|
+
iat: now,
|
|
161
|
+
nbf: now,
|
|
162
|
+
exp: now + config.accessTokenExpiry,
|
|
163
|
+
token_type: 'access',
|
|
164
|
+
};
|
|
165
|
+
const refreshPayload = {
|
|
166
|
+
sub: 'dev-user',
|
|
167
|
+
iss: config.baseUrl,
|
|
168
|
+
aud: config.baseUrl,
|
|
169
|
+
iat: now,
|
|
170
|
+
nbf: now,
|
|
171
|
+
exp: now + config.refreshTokenExpiry,
|
|
172
|
+
token_type: 'refresh',
|
|
173
|
+
};
|
|
174
|
+
return c.json({
|
|
175
|
+
access_token: signJwt(accessPayload, config.keys),
|
|
176
|
+
refresh_token: signJwt(refreshPayload, config.keys),
|
|
177
|
+
token_type: 'Bearer',
|
|
178
|
+
expires_in: config.accessTokenExpiry,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
// Refresh token grant
|
|
182
|
+
if (grantType === 'refresh_token') {
|
|
183
|
+
const refreshToken = String(body['refresh_token'] || '');
|
|
184
|
+
try {
|
|
185
|
+
// Import verifyJwt dynamically to avoid circular dependency
|
|
186
|
+
const { verifyJwt } = await import('./keys.js');
|
|
187
|
+
const payload = verifyJwt(refreshToken, config.keys);
|
|
188
|
+
if (payload.token_type !== 'refresh') {
|
|
189
|
+
return c.json({ error: 'invalid_grant', error_description: 'Not a refresh token' }, 400);
|
|
190
|
+
}
|
|
191
|
+
// Generate new access token
|
|
192
|
+
const now = Math.floor(Date.now() / 1000);
|
|
193
|
+
const accessPayload = {
|
|
194
|
+
sub: payload.sub,
|
|
195
|
+
iss: config.baseUrl,
|
|
196
|
+
aud: config.baseUrl,
|
|
197
|
+
iat: now,
|
|
198
|
+
nbf: now,
|
|
199
|
+
exp: now + config.accessTokenExpiry,
|
|
200
|
+
token_type: 'access',
|
|
201
|
+
};
|
|
202
|
+
// Also issue a new refresh token (rotation)
|
|
203
|
+
const newRefreshPayload = {
|
|
204
|
+
sub: payload.sub,
|
|
205
|
+
iss: config.baseUrl,
|
|
206
|
+
aud: config.baseUrl,
|
|
207
|
+
iat: now,
|
|
208
|
+
nbf: now,
|
|
209
|
+
exp: now + config.refreshTokenExpiry,
|
|
210
|
+
token_type: 'refresh',
|
|
211
|
+
};
|
|
212
|
+
return c.json({
|
|
213
|
+
access_token: signJwt(accessPayload, config.keys),
|
|
214
|
+
refresh_token: signJwt(newRefreshPayload, config.keys),
|
|
215
|
+
token_type: 'Bearer',
|
|
216
|
+
expires_in: config.accessTokenExpiry,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
catch {
|
|
220
|
+
return c.json({ error: 'invalid_grant', error_description: 'Invalid refresh token' }, 400);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return c.json({ error: 'unsupported_grant_type' }, 400);
|
|
224
|
+
});
|
|
225
|
+
return app;
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"device.js","sourceRoot":"","sources":["../../../src/auth/device.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* OIDC Discovery endpoint (/.well-known/openid-configuration).
|
|
7
|
+
*/
|
|
8
|
+
import { Hono } from 'hono';
|
|
9
|
+
import type { KeyPair } from './keys.js';
|
|
10
|
+
/**
|
|
11
|
+
* Discovery configuration.
|
|
12
|
+
*/
|
|
13
|
+
export interface DiscoveryConfig {
|
|
14
|
+
/** Server base URL (e.g., http://localhost:3000) */
|
|
15
|
+
baseUrl: string;
|
|
16
|
+
/** RSA keypair for JWKS */
|
|
17
|
+
keys: KeyPair;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Create discovery routes.
|
|
21
|
+
*/
|
|
22
|
+
export declare function createDiscoveryRoutes(config: DiscoveryConfig): Hono;
|
|
23
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","sourceRoot":"","sources":["../../../src/auth/discovery.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAGzC;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,2BAA2B;IAC3B,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CA6BnE"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* OIDC Discovery endpoint (/.well-known/openid-configuration).
|
|
7
|
+
*/
|
|
8
|
+
import { Hono } from 'hono';
|
|
9
|
+
import { publicKeyToJwk } from './keys.js';
|
|
10
|
+
/**
|
|
11
|
+
* Create discovery routes.
|
|
12
|
+
*/
|
|
13
|
+
export function createDiscoveryRoutes(config) {
|
|
14
|
+
const app = new Hono();
|
|
15
|
+
// GET /.well-known/openid-configuration - OIDC Discovery
|
|
16
|
+
app.get('/.well-known/openid-configuration', (c) => {
|
|
17
|
+
return c.json({
|
|
18
|
+
issuer: config.baseUrl,
|
|
19
|
+
device_authorization_endpoint: `${config.baseUrl}/oauth2/device_authorization`,
|
|
20
|
+
token_endpoint: `${config.baseUrl}/oauth2/token`,
|
|
21
|
+
jwks_uri: `${config.baseUrl}/.well-known/jwks.json`,
|
|
22
|
+
response_types_supported: ['token'],
|
|
23
|
+
grant_types_supported: [
|
|
24
|
+
'urn:ietf:params:oauth:grant-type:device_code',
|
|
25
|
+
'refresh_token',
|
|
26
|
+
],
|
|
27
|
+
subject_types_supported: ['public'],
|
|
28
|
+
id_token_signing_alg_values_supported: ['RS256'],
|
|
29
|
+
token_endpoint_auth_methods_supported: ['none'],
|
|
30
|
+
});
|
|
31
|
+
});
|
|
32
|
+
// GET /.well-known/jwks.json - JSON Web Key Set
|
|
33
|
+
app.get('/.well-known/jwks.json', (c) => {
|
|
34
|
+
return c.json({
|
|
35
|
+
keys: [publicKeyToJwk(config.keys)],
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
return app;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=discovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.js","sourceRoot":"","sources":["../../../src/auth/discovery.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAY3C;;GAEG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAuB;IAC3D,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,yDAAyD;IACzD,GAAG,CAAC,GAAG,CAAC,mCAAmC,EAAE,CAAC,CAAC,EAAE,EAAE;QACjD,OAAO,CAAC,CAAC,IAAI,CAAC;YACZ,MAAM,EAAE,MAAM,CAAC,OAAO;YACtB,6BAA6B,EAAE,GAAG,MAAM,CAAC,OAAO,8BAA8B;YAC9E,cAAc,EAAE,GAAG,MAAM,CAAC,OAAO,eAAe;YAChD,QAAQ,EAAE,GAAG,MAAM,CAAC,OAAO,wBAAwB;YACnD,wBAAwB,EAAE,CAAC,OAAO,CAAC;YACnC,qBAAqB,EAAE;gBACrB,8CAA8C;gBAC9C,eAAe;aAChB;YACD,uBAAuB,EAAE,CAAC,QAAQ,CAAC;YACnC,qCAAqC,EAAE,CAAC,OAAO,CAAC;YAChD,qCAAqC,EAAE,CAAC,MAAM,CAAC;SAChD,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,gDAAgD;IAChD,GAAG,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC,CAAC,EAAE,EAAE;QACtC,OAAO,CAAC,CAAC,IAAI,CAAC;YACZ,IAAI,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;SACpC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* OIDC authentication provider for e3-api-server.
|
|
7
|
+
*
|
|
8
|
+
* Provides OAuth2 Device Flow (RFC 8628) with JWT tokens,
|
|
9
|
+
* compatible with AWS Cognito for cloud deployment.
|
|
10
|
+
*/
|
|
11
|
+
import { Hono } from 'hono';
|
|
12
|
+
import { type KeyPair } from './keys.js';
|
|
13
|
+
export { generateKeyPair, verifyJwt, type KeyPair, type JwtPayload } from './keys.js';
|
|
14
|
+
/**
|
|
15
|
+
* Parse duration string to seconds.
|
|
16
|
+
* Supports: "5s", "15m", "1h", "24h", "90d"
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseDuration(duration: string): number;
|
|
19
|
+
/**
|
|
20
|
+
* OIDC provider configuration.
|
|
21
|
+
*/
|
|
22
|
+
export interface OidcConfig {
|
|
23
|
+
/** Server base URL (e.g., http://localhost:3000) */
|
|
24
|
+
baseUrl: string;
|
|
25
|
+
/** Access token expiry duration (default: "1h") */
|
|
26
|
+
tokenExpiry?: string;
|
|
27
|
+
/** Refresh token expiry duration (default: "90d") */
|
|
28
|
+
refreshTokenExpiry?: string;
|
|
29
|
+
/** Auto-approve device codes (for CI testing) */
|
|
30
|
+
autoApprove?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* OIDC provider instance.
|
|
34
|
+
*/
|
|
35
|
+
export interface OidcProvider {
|
|
36
|
+
/** Hono app with all auth routes */
|
|
37
|
+
routes: Hono;
|
|
38
|
+
/** RSA keypair (for validating tokens) */
|
|
39
|
+
keys: KeyPair;
|
|
40
|
+
/** Server base URL */
|
|
41
|
+
baseUrl: string;
|
|
42
|
+
/** Access token expiry in seconds */
|
|
43
|
+
accessTokenExpiry: number;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create an OIDC provider for the e3-api-server.
|
|
47
|
+
*
|
|
48
|
+
* This provides:
|
|
49
|
+
* - /.well-known/openid-configuration (discovery)
|
|
50
|
+
* - /.well-known/jwks.json (public keys)
|
|
51
|
+
* - /oauth2/device_authorization (start device flow)
|
|
52
|
+
* - /device (approval page)
|
|
53
|
+
* - /oauth2/token (exchange codes and refresh tokens)
|
|
54
|
+
*/
|
|
55
|
+
export declare function createOidcProvider(config: OidcConfig): OidcProvider;
|
|
56
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/auth/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAmB,KAAK,OAAO,EAAE,MAAM,WAAW,CAAC;AAI1D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,KAAK,OAAO,EAAE,KAAK,UAAU,EAAE,MAAM,WAAW,CAAC;AAEtF;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAgBtD;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,oDAAoD;IACpD,OAAO,EAAE,MAAM,CAAC;IAChB,mDAAmD;IACnD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,qDAAqD;IACrD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,iDAAiD;IACjD,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,oCAAoC;IACpC,MAAM,EAAE,IAAI,CAAC;IACb,0CAA0C;IAC1C,IAAI,EAAE,OAAO,CAAC;IACd,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,UAAU,GAAG,YAAY,CA4BnE"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* OIDC authentication provider for e3-api-server.
|
|
7
|
+
*
|
|
8
|
+
* Provides OAuth2 Device Flow (RFC 8628) with JWT tokens,
|
|
9
|
+
* compatible with AWS Cognito for cloud deployment.
|
|
10
|
+
*/
|
|
11
|
+
import { Hono } from 'hono';
|
|
12
|
+
import { generateKeyPair } from './keys.js';
|
|
13
|
+
import { createDeviceRoutes } from './device.js';
|
|
14
|
+
import { createDiscoveryRoutes } from './discovery.js';
|
|
15
|
+
export { generateKeyPair, verifyJwt } from './keys.js';
|
|
16
|
+
/**
|
|
17
|
+
* Parse duration string to seconds.
|
|
18
|
+
* Supports: "5s", "15m", "1h", "24h", "90d"
|
|
19
|
+
*/
|
|
20
|
+
export function parseDuration(duration) {
|
|
21
|
+
const match = duration.match(/^(\d+)(s|m|h|d)$/);
|
|
22
|
+
if (!match) {
|
|
23
|
+
throw new Error(`Invalid duration format: ${duration}. Use format like "5s", "15m", "1h", "90d"`);
|
|
24
|
+
}
|
|
25
|
+
const value = parseInt(match[1], 10);
|
|
26
|
+
const unit = match[2];
|
|
27
|
+
switch (unit) {
|
|
28
|
+
case 's': return value;
|
|
29
|
+
case 'm': return value * 60;
|
|
30
|
+
case 'h': return value * 60 * 60;
|
|
31
|
+
case 'd': return value * 60 * 60 * 24;
|
|
32
|
+
default: throw new Error(`Unknown duration unit: ${unit}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Create an OIDC provider for the e3-api-server.
|
|
37
|
+
*
|
|
38
|
+
* This provides:
|
|
39
|
+
* - /.well-known/openid-configuration (discovery)
|
|
40
|
+
* - /.well-known/jwks.json (public keys)
|
|
41
|
+
* - /oauth2/device_authorization (start device flow)
|
|
42
|
+
* - /device (approval page)
|
|
43
|
+
* - /oauth2/token (exchange codes and refresh tokens)
|
|
44
|
+
*/
|
|
45
|
+
export function createOidcProvider(config) {
|
|
46
|
+
const keys = generateKeyPair();
|
|
47
|
+
const accessTokenExpiry = parseDuration(config.tokenExpiry ?? '1h');
|
|
48
|
+
const refreshTokenExpiry = parseDuration(config.refreshTokenExpiry ?? '90d');
|
|
49
|
+
const autoApprove = config.autoApprove ?? process.env.E3_AUTH_AUTO_APPROVE === '1';
|
|
50
|
+
const deviceConfig = {
|
|
51
|
+
baseUrl: config.baseUrl,
|
|
52
|
+
keys,
|
|
53
|
+
accessTokenExpiry,
|
|
54
|
+
refreshTokenExpiry,
|
|
55
|
+
autoApprove,
|
|
56
|
+
};
|
|
57
|
+
const app = new Hono();
|
|
58
|
+
// Mount discovery routes (/.well-known/*)
|
|
59
|
+
app.route('/', createDiscoveryRoutes({ baseUrl: config.baseUrl, keys }));
|
|
60
|
+
// Mount device flow routes (/oauth2/*, /device)
|
|
61
|
+
app.route('/', createDeviceRoutes(deviceConfig));
|
|
62
|
+
return {
|
|
63
|
+
routes: app,
|
|
64
|
+
keys,
|
|
65
|
+
baseUrl: config.baseUrl,
|
|
66
|
+
accessTokenExpiry,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/auth/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAgB,MAAM,WAAW,CAAC;AAC1D,OAAO,EAAE,kBAAkB,EAAyB,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEvD,OAAO,EAAE,eAAe,EAAE,SAAS,EAAiC,MAAM,WAAW,CAAC;AAEtF;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAC;IACjD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,4BAA4B,QAAQ,4CAA4C,CAAC,CAAC;IACpG,CAAC;IAED,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IAEtB,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,GAAG,CAAC,CAAC,OAAO,KAAK,CAAC;QACvB,KAAK,GAAG,CAAC,CAAC,OAAO,KAAK,GAAG,EAAE,CAAC;QAC5B,KAAK,GAAG,CAAC,CAAC,OAAO,KAAK,GAAG,EAAE,GAAG,EAAE,CAAC;QACjC,KAAK,GAAG,CAAC,CAAC,OAAO,KAAK,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;QACtC,OAAO,CAAC,CAAC,MAAM,IAAI,KAAK,CAAC,0BAA0B,IAAI,EAAE,CAAC,CAAC;IAC7D,CAAC;AACH,CAAC;AA8BD;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAkB;IACnD,MAAM,IAAI,GAAG,eAAe,EAAE,CAAC;IAC/B,MAAM,iBAAiB,GAAG,aAAa,CAAC,MAAM,CAAC,WAAW,IAAI,IAAI,CAAC,CAAC;IACpE,MAAM,kBAAkB,GAAG,aAAa,CAAC,MAAM,CAAC,kBAAkB,IAAI,KAAK,CAAC,CAAC;IAC7E,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,KAAK,GAAG,CAAC;IAEnF,MAAM,YAAY,GAAqB;QACrC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,IAAI;QACJ,iBAAiB;QACjB,kBAAkB;QAClB,WAAW;KACZ,CAAC;IAEF,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IAEvB,0CAA0C;IAC1C,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,qBAAqB,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEzE,gDAAgD;IAChD,GAAG,CAAC,KAAK,CAAC,GAAG,EAAE,kBAAkB,CAAC,YAAY,CAAC,CAAC,CAAC;IAEjD,OAAO;QACL,MAAM,EAAE,GAAG;QACX,IAAI;QACJ,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,iBAAiB;KAClB,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* RSA keypair generation and JWT signing for OIDC provider.
|
|
7
|
+
*/
|
|
8
|
+
import * as crypto from 'node:crypto';
|
|
9
|
+
/**
|
|
10
|
+
* RSA keypair for JWT signing.
|
|
11
|
+
*/
|
|
12
|
+
export interface KeyPair {
|
|
13
|
+
privateKey: crypto.KeyObject;
|
|
14
|
+
publicKey: crypto.KeyObject;
|
|
15
|
+
/** Key ID for JWKS */
|
|
16
|
+
kid: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* JWT payload claims.
|
|
20
|
+
*/
|
|
21
|
+
export interface JwtPayload {
|
|
22
|
+
/** Subject - user identifier */
|
|
23
|
+
sub: string;
|
|
24
|
+
/** Issuer */
|
|
25
|
+
iss: string;
|
|
26
|
+
/** Audience */
|
|
27
|
+
aud: string;
|
|
28
|
+
/** Expiration time (Unix timestamp) */
|
|
29
|
+
exp: number;
|
|
30
|
+
/** Issued at time (Unix timestamp) */
|
|
31
|
+
iat: number;
|
|
32
|
+
/** Not before time (Unix timestamp) */
|
|
33
|
+
nbf: number;
|
|
34
|
+
/** Token type (access or refresh) */
|
|
35
|
+
token_type?: 'access' | 'refresh';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Generate an RSA keypair for JWT signing.
|
|
39
|
+
* Keys are generated in-memory on server startup.
|
|
40
|
+
*/
|
|
41
|
+
export declare function generateKeyPair(): KeyPair;
|
|
42
|
+
/**
|
|
43
|
+
* Sign a JWT payload with the private key.
|
|
44
|
+
*/
|
|
45
|
+
export declare function signJwt(payload: JwtPayload, keys: KeyPair): string;
|
|
46
|
+
/**
|
|
47
|
+
* Verify a JWT and return the payload.
|
|
48
|
+
* @throws Error if verification fails
|
|
49
|
+
*/
|
|
50
|
+
export declare function verifyJwt(token: string, keys: KeyPair): JwtPayload;
|
|
51
|
+
/**
|
|
52
|
+
* Export public key in JWK format for JWKS endpoint.
|
|
53
|
+
*/
|
|
54
|
+
export declare function publicKeyToJwk(keys: KeyPair): object;
|
|
55
|
+
//# sourceMappingURL=keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.d.ts","sourceRoot":"","sources":["../../../src/auth/keys.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;GAEG;AAEH,OAAO,KAAK,MAAM,MAAM,aAAa,CAAC;AAEtC;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,UAAU,EAAE,MAAM,CAAC,SAAS,CAAC;IAC7B,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC;IAC5B,sBAAsB;IACtB,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,aAAa;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,GAAG,EAAE,MAAM,CAAC;IACZ,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;CACnC;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,OAAO,CASzC;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,OAAO,GAAG,MAAM,CAgBlE;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,UAAU,CA4BlE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAQpD"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Elara AI Pty Ltd
|
|
3
|
+
* Licensed under BSL 1.1. See LICENSE for details.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* RSA keypair generation and JWT signing for OIDC provider.
|
|
7
|
+
*/
|
|
8
|
+
import * as crypto from 'node:crypto';
|
|
9
|
+
/**
|
|
10
|
+
* Generate an RSA keypair for JWT signing.
|
|
11
|
+
* Keys are generated in-memory on server startup.
|
|
12
|
+
*/
|
|
13
|
+
export function generateKeyPair() {
|
|
14
|
+
const { privateKey, publicKey } = crypto.generateKeyPairSync('rsa', {
|
|
15
|
+
modulusLength: 2048,
|
|
16
|
+
});
|
|
17
|
+
// Generate a random key ID
|
|
18
|
+
const kid = crypto.randomBytes(8).toString('hex');
|
|
19
|
+
return { privateKey, publicKey, kid };
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Sign a JWT payload with the private key.
|
|
23
|
+
*/
|
|
24
|
+
export function signJwt(payload, keys) {
|
|
25
|
+
const header = {
|
|
26
|
+
alg: 'RS256',
|
|
27
|
+
typ: 'JWT',
|
|
28
|
+
kid: keys.kid,
|
|
29
|
+
};
|
|
30
|
+
const headerB64 = Buffer.from(JSON.stringify(header)).toString('base64url');
|
|
31
|
+
const payloadB64 = Buffer.from(JSON.stringify(payload)).toString('base64url');
|
|
32
|
+
const sign = crypto.createSign('RSA-SHA256');
|
|
33
|
+
sign.update(`${headerB64}.${payloadB64}`);
|
|
34
|
+
const signature = sign.sign(keys.privateKey);
|
|
35
|
+
const signatureB64 = signature.toString('base64url');
|
|
36
|
+
return `${headerB64}.${payloadB64}.${signatureB64}`;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Verify a JWT and return the payload.
|
|
40
|
+
* @throws Error if verification fails
|
|
41
|
+
*/
|
|
42
|
+
export function verifyJwt(token, keys) {
|
|
43
|
+
const parts = token.split('.');
|
|
44
|
+
if (parts.length !== 3) {
|
|
45
|
+
throw new Error('Malformed JWT: expected 3 parts');
|
|
46
|
+
}
|
|
47
|
+
const [headerB64, payloadB64, signatureB64] = parts;
|
|
48
|
+
// Verify signature
|
|
49
|
+
const verify = crypto.createVerify('RSA-SHA256');
|
|
50
|
+
verify.update(`${headerB64}.${payloadB64}`);
|
|
51
|
+
const signature = Buffer.from(signatureB64, 'base64url');
|
|
52
|
+
if (!verify.verify(keys.publicKey, signature)) {
|
|
53
|
+
throw new Error('Invalid JWT signature');
|
|
54
|
+
}
|
|
55
|
+
// Decode and validate payload
|
|
56
|
+
const payload = JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
|
|
57
|
+
const now = Math.floor(Date.now() / 1000);
|
|
58
|
+
if (payload.exp !== undefined && payload.exp < now) {
|
|
59
|
+
throw new Error('Token expired');
|
|
60
|
+
}
|
|
61
|
+
if (payload.nbf !== undefined && payload.nbf > now) {
|
|
62
|
+
throw new Error('Token not yet valid');
|
|
63
|
+
}
|
|
64
|
+
return payload;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Export public key in JWK format for JWKS endpoint.
|
|
68
|
+
*/
|
|
69
|
+
export function publicKeyToJwk(keys) {
|
|
70
|
+
const publicKeyJwk = keys.publicKey.export({ format: 'jwk' });
|
|
71
|
+
return {
|
|
72
|
+
...publicKeyJwk,
|
|
73
|
+
kid: keys.kid,
|
|
74
|
+
use: 'sig',
|
|
75
|
+
alg: 'RS256',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keys.js","sourceRoot":"","sources":["../../../src/auth/keys.ts"],"names":[],"mappings":"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"}
|
package/dist/src/beast2.d.ts
CHANGED
|
@@ -6,15 +6,27 @@ import type { EastType, ValueTypeOf } from '@elaraai/east';
|
|
|
6
6
|
import type { Context } from 'hono';
|
|
7
7
|
import { type Error } from './types.js';
|
|
8
8
|
/**
|
|
9
|
-
* Decode BEAST2 request body.
|
|
9
|
+
* Decode BEAST2 request body from Hono context.
|
|
10
10
|
*/
|
|
11
11
|
export declare function decodeBody<T extends EastType>(c: Context, type: T): Promise<ValueTypeOf<T>>;
|
|
12
|
+
/**
|
|
13
|
+
* Decode raw BEAST2 bytes (for handlers that receive body directly).
|
|
14
|
+
*/
|
|
15
|
+
export declare function decodeBeast2<T extends EastType>(data: Uint8Array, type: T): ValueTypeOf<T>;
|
|
12
16
|
/**
|
|
13
17
|
* Send BEAST2 success response.
|
|
18
|
+
*
|
|
19
|
+
* Returns a web Response object that can be used by both Hono routes and Lambda handlers.
|
|
14
20
|
*/
|
|
15
|
-
export declare function sendSuccess<T extends EastType>(
|
|
21
|
+
export declare function sendSuccess<T extends EastType>(type: T, value: ValueTypeOf<T>): Response;
|
|
16
22
|
/**
|
|
17
23
|
* Send BEAST2 error response.
|
|
24
|
+
*
|
|
25
|
+
* Returns a web Response object that can be used by both Hono routes and Lambda handlers.
|
|
26
|
+
*/
|
|
27
|
+
export declare function sendError<T extends EastType>(type: T, error: Error): Response;
|
|
28
|
+
/**
|
|
29
|
+
* Send BEAST2 success response with custom HTTP status.
|
|
18
30
|
*/
|
|
19
|
-
export declare function
|
|
31
|
+
export declare function sendSuccessWithStatus<T extends EastType>(type: T, value: ValueTypeOf<T>, status: number): Response;
|
|
20
32
|
//# sourceMappingURL=beast2.d.ts.map
|
package/dist/src/beast2.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"beast2.d.ts","sourceRoot":"","sources":["../../src/beast2.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAgB,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAEtD;;GAEG;AACH,wBAAsB,UAAU,CAAC,CAAC,SAAS,QAAQ,EACjD,CAAC,EAAE,OAAO,EACV,IAAI,EAAE,CAAC,GACN,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"beast2.d.ts","sourceRoot":"","sources":["../../src/beast2.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC3D,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAgB,KAAK,KAAK,EAAE,MAAM,YAAY,CAAC;AAEtD;;GAEG;AACH,wBAAsB,UAAU,CAAC,CAAC,SAAS,QAAQ,EACjD,CAAC,EAAE,OAAO,EACV,IAAI,EAAE,CAAC,GACN,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAUzB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,CAAC,SAAS,QAAQ,EAC7C,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,CAAC,GACN,WAAW,CAAC,CAAC,CAAC,CAGhB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,QAAQ,EAC5C,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,GACpB,QAAQ,CAWV;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,CAAC,SAAS,QAAQ,EAC1C,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,KAAK,GACX,QAAQ,CAWV;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,CAAC,SAAS,QAAQ,EACtD,IAAI,EAAE,CAAC,EACP,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,EACrB,MAAM,EAAE,MAAM,GACb,QAAQ,CAWV"}
|