s3db.js 11.3.2 → 12.0.1
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 +102 -8
- package/dist/s3db.cjs.js +36945 -15510
- package/dist/s3db.cjs.js.map +1 -1
- package/dist/s3db.d.ts +66 -1
- package/dist/s3db.es.js +36914 -15534
- package/dist/s3db.es.js.map +1 -1
- package/mcp/entrypoint.js +58 -0
- package/mcp/tools/documentation.js +434 -0
- package/mcp/tools/index.js +4 -0
- package/package.json +35 -15
- package/src/behaviors/user-managed.js +13 -6
- package/src/client.class.js +79 -49
- package/src/concerns/base62.js +85 -0
- package/src/concerns/dictionary-encoding.js +294 -0
- package/src/concerns/geo-encoding.js +256 -0
- package/src/concerns/high-performance-inserter.js +34 -30
- package/src/concerns/ip.js +325 -0
- package/src/concerns/metadata-encoding.js +345 -66
- package/src/concerns/money.js +193 -0
- package/src/concerns/partition-queue.js +7 -4
- package/src/concerns/plugin-storage.js +97 -47
- package/src/database.class.js +76 -74
- package/src/errors.js +0 -4
- package/src/plugins/api/auth/api-key-auth.js +88 -0
- package/src/plugins/api/auth/basic-auth.js +154 -0
- package/src/plugins/api/auth/index.js +112 -0
- package/src/plugins/api/auth/jwt-auth.js +169 -0
- package/src/plugins/api/index.js +544 -0
- package/src/plugins/api/middlewares/index.js +15 -0
- package/src/plugins/api/middlewares/validator.js +185 -0
- package/src/plugins/api/routes/auth-routes.js +241 -0
- package/src/plugins/api/routes/resource-routes.js +304 -0
- package/src/plugins/api/server.js +354 -0
- package/src/plugins/api/utils/error-handler.js +147 -0
- package/src/plugins/api/utils/openapi-generator.js +1240 -0
- package/src/plugins/api/utils/response-formatter.js +218 -0
- package/src/plugins/backup/streaming-exporter.js +132 -0
- package/src/plugins/backup.plugin.js +103 -50
- package/src/plugins/cache/s3-cache.class.js +95 -47
- package/src/plugins/cache.plugin.js +107 -9
- package/src/plugins/concerns/plugin-dependencies.js +313 -0
- package/src/plugins/concerns/prometheus-formatter.js +255 -0
- package/src/plugins/consumers/rabbitmq-consumer.js +4 -0
- package/src/plugins/consumers/sqs-consumer.js +4 -0
- package/src/plugins/costs.plugin.js +255 -39
- package/src/plugins/eventual-consistency/helpers.js +15 -1
- package/src/plugins/geo.plugin.js +873 -0
- package/src/plugins/importer/index.js +1020 -0
- package/src/plugins/index.js +11 -0
- package/src/plugins/metrics.plugin.js +163 -4
- package/src/plugins/queue-consumer.plugin.js +6 -27
- package/src/plugins/relation.errors.js +139 -0
- package/src/plugins/relation.plugin.js +1242 -0
- package/src/plugins/replicator.plugin.js +2 -1
- package/src/plugins/replicators/bigquery-replicator.class.js +180 -8
- package/src/plugins/replicators/dynamodb-replicator.class.js +383 -0
- package/src/plugins/replicators/index.js +28 -3
- package/src/plugins/replicators/mongodb-replicator.class.js +391 -0
- package/src/plugins/replicators/mysql-replicator.class.js +558 -0
- package/src/plugins/replicators/planetscale-replicator.class.js +409 -0
- package/src/plugins/replicators/postgres-replicator.class.js +182 -7
- package/src/plugins/replicators/s3db-replicator.class.js +1 -12
- package/src/plugins/replicators/schema-sync.helper.js +601 -0
- package/src/plugins/replicators/sqs-replicator.class.js +11 -9
- package/src/plugins/replicators/turso-replicator.class.js +416 -0
- package/src/plugins/replicators/webhook-replicator.class.js +612 -0
- package/src/plugins/state-machine.plugin.js +122 -68
- package/src/plugins/tfstate/README.md +745 -0
- package/src/plugins/tfstate/base-driver.js +80 -0
- package/src/plugins/tfstate/errors.js +112 -0
- package/src/plugins/tfstate/filesystem-driver.js +129 -0
- package/src/plugins/tfstate/index.js +2660 -0
- package/src/plugins/tfstate/s3-driver.js +192 -0
- package/src/plugins/ttl.plugin.js +536 -0
- package/src/resource.class.js +315 -36
- package/src/s3db.d.ts +66 -1
- package/src/schema.class.js +366 -32
- package/SECURITY.md +0 -76
- package/src/partition-drivers/base-partition-driver.js +0 -106
- package/src/partition-drivers/index.js +0 -66
- package/src/partition-drivers/memory-partition-driver.js +0 -289
- package/src/partition-drivers/sqs-partition-driver.js +0 -337
- package/src/partition-drivers/sync-partition-driver.js +0 -38
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Authentication Factory - Create authentication middleware based on configuration
|
|
3
|
+
*
|
|
4
|
+
* Provides unified interface for multiple authentication methods
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { jwtAuth } from './jwt-auth.js';
|
|
8
|
+
import { apiKeyAuth } from './api-key-auth.js';
|
|
9
|
+
import { basicAuth } from './basic-auth.js';
|
|
10
|
+
import { unauthorized } from '../utils/response-formatter.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create authentication middleware that supports multiple auth methods
|
|
14
|
+
* @param {Object} options - Authentication options
|
|
15
|
+
* @param {Array<string>} options.methods - Allowed auth methods (['jwt', 'apiKey', 'basic'])
|
|
16
|
+
* @param {Object} options.jwt - JWT configuration
|
|
17
|
+
* @param {Object} options.apiKey - API Key configuration
|
|
18
|
+
* @param {Object} options.basic - Basic Auth configuration
|
|
19
|
+
* @param {Object} options.usersResource - Users resource
|
|
20
|
+
* @param {boolean} options.optional - If true, allows requests without auth
|
|
21
|
+
* @returns {Function} Hono middleware
|
|
22
|
+
*/
|
|
23
|
+
export function createAuthMiddleware(options = {}) {
|
|
24
|
+
const {
|
|
25
|
+
methods = [],
|
|
26
|
+
jwt: jwtConfig = {},
|
|
27
|
+
apiKey: apiKeyConfig = {},
|
|
28
|
+
basic: basicConfig = {},
|
|
29
|
+
usersResource,
|
|
30
|
+
optional = false
|
|
31
|
+
} = options;
|
|
32
|
+
|
|
33
|
+
// If no methods specified, allow all requests
|
|
34
|
+
if (methods.length === 0) {
|
|
35
|
+
return async (c, next) => await next();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Create individual auth middlewares
|
|
39
|
+
const middlewares = [];
|
|
40
|
+
|
|
41
|
+
if (methods.includes('jwt') && jwtConfig.secret) {
|
|
42
|
+
middlewares.push({
|
|
43
|
+
name: 'jwt',
|
|
44
|
+
middleware: jwtAuth({
|
|
45
|
+
secret: jwtConfig.secret,
|
|
46
|
+
usersResource,
|
|
47
|
+
optional: true // Check all methods before rejecting
|
|
48
|
+
})
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (methods.includes('apiKey') && usersResource) {
|
|
53
|
+
middlewares.push({
|
|
54
|
+
name: 'apiKey',
|
|
55
|
+
middleware: apiKeyAuth({
|
|
56
|
+
headerName: apiKeyConfig.headerName || 'X-API-Key',
|
|
57
|
+
usersResource,
|
|
58
|
+
optional: true // Check all methods before rejecting
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (methods.includes('basic') && usersResource) {
|
|
64
|
+
middlewares.push({
|
|
65
|
+
name: 'basic',
|
|
66
|
+
middleware: basicAuth({
|
|
67
|
+
realm: basicConfig.realm || 'API Access',
|
|
68
|
+
usersResource,
|
|
69
|
+
passphrase: basicConfig.passphrase || 'secret',
|
|
70
|
+
optional: true // Check all methods before rejecting
|
|
71
|
+
})
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Return combined middleware
|
|
76
|
+
return async (c, next) => {
|
|
77
|
+
// Try each auth method
|
|
78
|
+
for (const { name, middleware } of middlewares) {
|
|
79
|
+
// Create a temporary next that captures success
|
|
80
|
+
let authSuccess = false;
|
|
81
|
+
const tempNext = async () => {
|
|
82
|
+
authSuccess = true;
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// Try auth method
|
|
86
|
+
await middleware(c, tempNext);
|
|
87
|
+
|
|
88
|
+
// If auth succeeded, continue
|
|
89
|
+
if (authSuccess && c.get('user')) {
|
|
90
|
+
return await next();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// No auth method succeeded
|
|
95
|
+
if (optional) {
|
|
96
|
+
return await next();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Require authentication
|
|
100
|
+
const response = unauthorized(
|
|
101
|
+
`Authentication required. Supported methods: ${methods.join(', ')}`
|
|
102
|
+
);
|
|
103
|
+
return c.json(response, response._status);
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default {
|
|
108
|
+
createAuthMiddleware,
|
|
109
|
+
jwtAuth,
|
|
110
|
+
apiKeyAuth,
|
|
111
|
+
basicAuth
|
|
112
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Authentication - JSON Web Token authentication middleware
|
|
3
|
+
*
|
|
4
|
+
* Provides stateless authentication using JWT tokens
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { createHash } from 'crypto';
|
|
8
|
+
import { unauthorized } from '../utils/response-formatter.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create JWT token (simple implementation without external dependencies)
|
|
12
|
+
* Note: In production, use 'jsonwebtoken' package for better security
|
|
13
|
+
* @param {Object} payload - Token payload
|
|
14
|
+
* @param {string} secret - JWT secret
|
|
15
|
+
* @param {string} expiresIn - Token expiration (e.g., '7d')
|
|
16
|
+
* @returns {string} JWT token
|
|
17
|
+
*/
|
|
18
|
+
export function createToken(payload, secret, expiresIn = '7d') {
|
|
19
|
+
// Parse expiresIn
|
|
20
|
+
const match = expiresIn.match(/^(\d+)([smhd])$/);
|
|
21
|
+
if (!match) {
|
|
22
|
+
throw new Error('Invalid expiresIn format. Use: 60s, 30m, 24h, 7d');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const [, value, unit] = match;
|
|
26
|
+
const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
27
|
+
const expiresInSeconds = parseInt(value) * multipliers[unit];
|
|
28
|
+
|
|
29
|
+
const header = { alg: 'HS256', typ: 'JWT' };
|
|
30
|
+
const now = Math.floor(Date.now() / 1000);
|
|
31
|
+
|
|
32
|
+
const data = {
|
|
33
|
+
...payload,
|
|
34
|
+
iat: now,
|
|
35
|
+
exp: now + expiresInSeconds
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Encode
|
|
39
|
+
const encodedHeader = Buffer.from(JSON.stringify(header)).toString('base64url');
|
|
40
|
+
const encodedPayload = Buffer.from(JSON.stringify(data)).toString('base64url');
|
|
41
|
+
|
|
42
|
+
// Sign
|
|
43
|
+
const signature = createHash('sha256')
|
|
44
|
+
.update(`${encodedHeader}.${encodedPayload}.${secret}`)
|
|
45
|
+
.digest('base64url');
|
|
46
|
+
|
|
47
|
+
return `${encodedHeader}.${encodedPayload}.${signature}`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Verify JWT token
|
|
52
|
+
* @param {string} token - JWT token
|
|
53
|
+
* @param {string} secret - JWT secret
|
|
54
|
+
* @returns {Object|null} Decoded payload or null if invalid
|
|
55
|
+
*/
|
|
56
|
+
export function verifyToken(token, secret) {
|
|
57
|
+
try {
|
|
58
|
+
const [encodedHeader, encodedPayload, signature] = token.split('.');
|
|
59
|
+
|
|
60
|
+
if (!encodedHeader || !encodedPayload || !signature) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Verify signature
|
|
65
|
+
const expectedSignature = createHash('sha256')
|
|
66
|
+
.update(`${encodedHeader}.${encodedPayload}.${secret}`)
|
|
67
|
+
.digest('base64url');
|
|
68
|
+
|
|
69
|
+
if (signature !== expectedSignature) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Decode payload
|
|
74
|
+
const payload = JSON.parse(Buffer.from(encodedPayload, 'base64url').toString());
|
|
75
|
+
|
|
76
|
+
// Check expiration
|
|
77
|
+
const now = Math.floor(Date.now() / 1000);
|
|
78
|
+
if (payload.exp && payload.exp < now) {
|
|
79
|
+
return null; // Expired
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return payload;
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create JWT authentication middleware
|
|
90
|
+
* @param {Object} options - JWT options
|
|
91
|
+
* @param {string} options.secret - JWT secret key
|
|
92
|
+
* @param {Object} options.usersResource - Users resource for user lookup
|
|
93
|
+
* @param {boolean} options.optional - If true, allows requests without auth
|
|
94
|
+
* @returns {Function} Hono middleware
|
|
95
|
+
*/
|
|
96
|
+
export function jwtAuth(options = {}) {
|
|
97
|
+
const { secret, usersResource, optional = false } = options;
|
|
98
|
+
|
|
99
|
+
if (!secret) {
|
|
100
|
+
throw new Error('JWT secret is required');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return async (c, next) => {
|
|
104
|
+
const authHeader = c.req.header('authorization');
|
|
105
|
+
|
|
106
|
+
if (!authHeader) {
|
|
107
|
+
if (optional) {
|
|
108
|
+
return await next();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const response = unauthorized('No authorization header provided');
|
|
112
|
+
return c.json(response, response._status);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Extract token from "Bearer <token>"
|
|
116
|
+
const match = authHeader.match(/^Bearer\s+(.+)$/i);
|
|
117
|
+
if (!match) {
|
|
118
|
+
const response = unauthorized('Invalid authorization header format. Use: Bearer <token>');
|
|
119
|
+
return c.json(response, response._status);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const token = match[1];
|
|
123
|
+
|
|
124
|
+
// Verify token
|
|
125
|
+
const payload = verifyToken(token, secret);
|
|
126
|
+
|
|
127
|
+
if (!payload) {
|
|
128
|
+
const response = unauthorized('Invalid or expired token');
|
|
129
|
+
return c.json(response, response._status);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Optionally load user from database
|
|
133
|
+
if (usersResource && payload.userId) {
|
|
134
|
+
try {
|
|
135
|
+
const user = await usersResource.get(payload.userId);
|
|
136
|
+
|
|
137
|
+
if (!user) {
|
|
138
|
+
const response = unauthorized('User not found');
|
|
139
|
+
return c.json(response, response._status);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (!user.active) {
|
|
143
|
+
const response = unauthorized('User account is inactive');
|
|
144
|
+
return c.json(response, response._status);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Store user in context
|
|
148
|
+
c.set('user', user);
|
|
149
|
+
c.set('authMethod', 'jwt');
|
|
150
|
+
} catch (err) {
|
|
151
|
+
console.error('[JWT Auth] Error loading user:', err);
|
|
152
|
+
const response = unauthorized('Authentication error');
|
|
153
|
+
return c.json(response, response._status);
|
|
154
|
+
}
|
|
155
|
+
} else {
|
|
156
|
+
// Store payload as user
|
|
157
|
+
c.set('user', payload);
|
|
158
|
+
c.set('authMethod', 'jwt');
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
await next();
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export default {
|
|
166
|
+
createToken,
|
|
167
|
+
verifyToken,
|
|
168
|
+
jwtAuth
|
|
169
|
+
};
|