s3db.js 13.5.1 → 13.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -10
- package/dist/{s3db.cjs.js → s3db.cjs} +30323 -24958
- package/dist/s3db.cjs.map +1 -0
- package/dist/s3db.es.js +24026 -18654
- package/dist/s3db.es.js.map +1 -1
- package/package.json +216 -20
- package/src/concerns/id.js +90 -6
- package/src/concerns/index.js +2 -1
- package/src/concerns/password-hashing.js +150 -0
- package/src/database.class.js +4 -0
- package/src/plugins/api/auth/basic-auth.js +23 -1
- package/src/plugins/api/auth/index.js +49 -3
- package/src/plugins/api/auth/oauth2-auth.js +171 -0
- package/src/plugins/api/auth/oidc-auth.js +789 -0
- package/src/plugins/api/auth/oidc-client.js +462 -0
- package/src/plugins/api/auth/path-auth-matcher.js +284 -0
- package/src/plugins/api/concerns/event-emitter.js +134 -0
- package/src/plugins/api/concerns/failban-manager.js +651 -0
- package/src/plugins/api/concerns/guards-helpers.js +402 -0
- package/src/plugins/api/concerns/metrics-collector.js +346 -0
- package/src/plugins/api/index.js +503 -54
- package/src/plugins/api/middlewares/failban.js +305 -0
- package/src/plugins/api/middlewares/rate-limit.js +301 -0
- package/src/plugins/api/middlewares/request-id.js +74 -0
- package/src/plugins/api/middlewares/security-headers.js +120 -0
- package/src/plugins/api/middlewares/session-tracking.js +194 -0
- package/src/plugins/api/routes/auth-routes.js +23 -3
- package/src/plugins/api/routes/resource-routes.js +71 -29
- package/src/plugins/api/server.js +1017 -94
- package/src/plugins/api/utils/guards.js +213 -0
- package/src/plugins/api/utils/mime-types.js +154 -0
- package/src/plugins/api/utils/openapi-generator.js +44 -11
- package/src/plugins/api/utils/path-matcher.js +173 -0
- package/src/plugins/api/utils/static-filesystem.js +262 -0
- package/src/plugins/api/utils/static-s3.js +231 -0
- package/src/plugins/api/utils/template-engine.js +188 -0
- package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
- package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
- package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
- package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
- package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
- package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
- package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
- package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
- package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
- package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
- package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
- package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
- package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
- package/src/plugins/cloud-inventory/index.js +20 -0
- package/src/plugins/cloud-inventory/registry.js +146 -0
- package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
- package/src/plugins/cloud-inventory.plugin.js +1333 -0
- package/src/plugins/concerns/plugin-dependencies.js +61 -1
- package/src/plugins/eventual-consistency/analytics.js +1 -0
- package/src/plugins/identity/README.md +335 -0
- package/src/plugins/identity/concerns/mfa-manager.js +204 -0
- package/src/plugins/identity/concerns/password.js +138 -0
- package/src/plugins/identity/concerns/resource-schemas.js +273 -0
- package/src/plugins/identity/concerns/token-generator.js +172 -0
- package/src/plugins/identity/email-service.js +422 -0
- package/src/plugins/identity/index.js +1052 -0
- package/src/plugins/identity/oauth2-server.js +1033 -0
- package/src/plugins/identity/oidc-discovery.js +285 -0
- package/src/plugins/identity/rsa-keys.js +323 -0
- package/src/plugins/identity/server.js +500 -0
- package/src/plugins/identity/session-manager.js +453 -0
- package/src/plugins/identity/ui/layouts/base.js +251 -0
- package/src/plugins/identity/ui/middleware.js +135 -0
- package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
- package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
- package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
- package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
- package/src/plugins/identity/ui/pages/admin/users.js +263 -0
- package/src/plugins/identity/ui/pages/consent.js +262 -0
- package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
- package/src/plugins/identity/ui/pages/login.js +144 -0
- package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
- package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
- package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
- package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
- package/src/plugins/identity/ui/pages/profile.js +361 -0
- package/src/plugins/identity/ui/pages/register.js +226 -0
- package/src/plugins/identity/ui/pages/reset-password.js +128 -0
- package/src/plugins/identity/ui/pages/verify-email.js +172 -0
- package/src/plugins/identity/ui/routes.js +2541 -0
- package/src/plugins/identity/ui/styles/main.css +465 -0
- package/src/plugins/index.js +4 -1
- package/src/plugins/ml/base-model.class.js +32 -7
- package/src/plugins/ml/classification-model.class.js +1 -1
- package/src/plugins/ml/timeseries-model.class.js +3 -1
- package/src/plugins/ml.plugin.js +124 -32
- package/src/plugins/shared/error-handler.js +147 -0
- package/src/plugins/shared/index.js +9 -0
- package/src/plugins/shared/middlewares/compression.js +117 -0
- package/src/plugins/shared/middlewares/cors.js +49 -0
- package/src/plugins/shared/middlewares/index.js +11 -0
- package/src/plugins/shared/middlewares/logging.js +54 -0
- package/src/plugins/shared/middlewares/rate-limit.js +73 -0
- package/src/plugins/shared/middlewares/security.js +158 -0
- package/src/plugins/shared/response-formatter.js +264 -0
- package/src/resource.class.js +140 -12
- package/src/schema.class.js +30 -1
- package/src/validator.class.js +57 -6
- package/dist/s3db.cjs.js.map +0 -1
|
@@ -7,17 +7,23 @@
|
|
|
7
7
|
import { jwtAuth } from './jwt-auth.js';
|
|
8
8
|
import { apiKeyAuth } from './api-key-auth.js';
|
|
9
9
|
import { basicAuth } from './basic-auth.js';
|
|
10
|
+
import { createOAuth2Handler } from './oauth2-auth.js';
|
|
11
|
+
import { OIDCClient } from './oidc-client.js';
|
|
10
12
|
import { unauthorized } from '../utils/response-formatter.js';
|
|
11
13
|
|
|
12
14
|
/**
|
|
13
15
|
* Create authentication middleware that supports multiple auth methods
|
|
14
16
|
* @param {Object} options - Authentication options
|
|
15
|
-
* @param {Array<string>} options.methods - Allowed auth methods (['jwt', 'apiKey', 'basic'])
|
|
17
|
+
* @param {Array<string>} options.methods - Allowed auth methods (['jwt', 'apiKey', 'basic', 'oauth2'])
|
|
16
18
|
* @param {Object} options.jwt - JWT configuration
|
|
17
19
|
* @param {Object} options.apiKey - API Key configuration
|
|
18
20
|
* @param {Object} options.basic - Basic Auth configuration
|
|
21
|
+
* @param {Object} options.oauth2 - OAuth2 configuration
|
|
22
|
+
* @param {Function} options.oidc - OIDC middleware (already configured)
|
|
19
23
|
* @param {Object} options.usersResource - Users resource
|
|
20
24
|
* @param {boolean} options.optional - If true, allows requests without auth
|
|
25
|
+
* @param {string} options.strategy - Auth strategy: 'any' (default, OR logic) or 'priority' (waterfall with explicit order)
|
|
26
|
+
* @param {Object} options.priorities - Priority map for 'priority' strategy { jwt: 1, oidc: 2, basic: 3 }
|
|
21
27
|
* @returns {Function} Hono middleware
|
|
22
28
|
*/
|
|
23
29
|
export function createAuthMiddleware(options = {}) {
|
|
@@ -26,8 +32,12 @@ export function createAuthMiddleware(options = {}) {
|
|
|
26
32
|
jwt: jwtConfig = {},
|
|
27
33
|
apiKey: apiKeyConfig = {},
|
|
28
34
|
basic: basicConfig = {},
|
|
35
|
+
oauth2: oauth2Config = {},
|
|
36
|
+
oidc: oidcMiddleware = null,
|
|
29
37
|
usersResource,
|
|
30
|
-
optional = false
|
|
38
|
+
optional = false,
|
|
39
|
+
strategy = 'any',
|
|
40
|
+
priorities = {}
|
|
31
41
|
} = options;
|
|
32
42
|
|
|
33
43
|
// If no methods specified, allow all requests
|
|
@@ -72,6 +82,38 @@ export function createAuthMiddleware(options = {}) {
|
|
|
72
82
|
});
|
|
73
83
|
}
|
|
74
84
|
|
|
85
|
+
if (methods.includes('oauth2') && oauth2Config.issuer) {
|
|
86
|
+
const oauth2Handler = createOAuth2Handler(oauth2Config, usersResource);
|
|
87
|
+
middlewares.push({
|
|
88
|
+
name: 'oauth2',
|
|
89
|
+
middleware: async (c, next) => {
|
|
90
|
+
const user = await oauth2Handler(c);
|
|
91
|
+
if (user) {
|
|
92
|
+
c.set('user', user);
|
|
93
|
+
return await next();
|
|
94
|
+
}
|
|
95
|
+
// No user, try next method
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// OIDC middleware (session-based authentication)
|
|
101
|
+
if (oidcMiddleware) {
|
|
102
|
+
middlewares.push({
|
|
103
|
+
name: 'oidc',
|
|
104
|
+
middleware: oidcMiddleware
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Sort middlewares by priority if strategy is 'priority'
|
|
109
|
+
if (strategy === 'priority' && Object.keys(priorities).length > 0) {
|
|
110
|
+
middlewares.sort((a, b) => {
|
|
111
|
+
const priorityA = priorities[a.name] || 999; // Unspecified = lowest priority
|
|
112
|
+
const priorityB = priorities[b.name] || 999;
|
|
113
|
+
return priorityA - priorityB; // Lower number = higher priority
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
75
117
|
// Return combined middleware
|
|
76
118
|
return async (c, next) => {
|
|
77
119
|
// Try each auth method
|
|
@@ -104,9 +146,13 @@ export function createAuthMiddleware(options = {}) {
|
|
|
104
146
|
};
|
|
105
147
|
}
|
|
106
148
|
|
|
149
|
+
export { OIDCClient };
|
|
150
|
+
|
|
107
151
|
export default {
|
|
108
152
|
createAuthMiddleware,
|
|
109
153
|
jwtAuth,
|
|
110
154
|
apiKeyAuth,
|
|
111
|
-
basicAuth
|
|
155
|
+
basicAuth,
|
|
156
|
+
createOAuth2Handler,
|
|
157
|
+
OIDCClient
|
|
112
158
|
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth2/OIDC Authentication Driver (Resource Server)
|
|
3
|
+
*
|
|
4
|
+
* Validates JWT access tokens issued by an OAuth2/OIDC Authorization Server.
|
|
5
|
+
* Fetches public keys from JWKS endpoint and verifies token signatures.
|
|
6
|
+
*
|
|
7
|
+
* Use this driver when your application acts as a Resource Server
|
|
8
|
+
* consuming tokens from an external Authorization Server (SSO).
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* {
|
|
12
|
+
* driver: 'oauth2',
|
|
13
|
+
* config: {
|
|
14
|
+
* issuer: 'http://localhost:4000',
|
|
15
|
+
* jwksUri: 'http://localhost:4000/.well-known/jwks.json',
|
|
16
|
+
* audience: 'my-api',
|
|
17
|
+
* algorithms: ['RS256'],
|
|
18
|
+
* cacheTTL: 3600000 // 1 hour
|
|
19
|
+
* }
|
|
20
|
+
* }
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
24
|
+
|
|
25
|
+
// Cache for JWKS (avoids fetching on every request)
|
|
26
|
+
const jwksCache = new Map();
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create OAuth2 authentication handler
|
|
30
|
+
* @param {Object} config - OAuth2 configuration
|
|
31
|
+
* @param {Object} usersResource - s3db.js users resource
|
|
32
|
+
* @returns {Function} Hono middleware
|
|
33
|
+
*/
|
|
34
|
+
export function createOAuth2Handler(config, usersResource) {
|
|
35
|
+
const {
|
|
36
|
+
issuer,
|
|
37
|
+
jwksUri,
|
|
38
|
+
audience = null,
|
|
39
|
+
algorithms = ['RS256', 'ES256'],
|
|
40
|
+
cacheTTL = 3600000, // 1 hour
|
|
41
|
+
clockTolerance = 60, // 60 seconds tolerance for exp/nbf
|
|
42
|
+
validateScopes = true,
|
|
43
|
+
fetchUserInfo = true
|
|
44
|
+
} = config;
|
|
45
|
+
|
|
46
|
+
if (!issuer) {
|
|
47
|
+
throw new Error('[OAuth2 Auth] Missing required config: issuer');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Construct JWKS URI from issuer if not provided
|
|
51
|
+
const finalJwksUri = jwksUri || `${issuer}/.well-known/jwks.json`;
|
|
52
|
+
|
|
53
|
+
// Get or create JWKS fetcher (cached)
|
|
54
|
+
const getJWKS = () => {
|
|
55
|
+
const cacheKey = finalJwksUri;
|
|
56
|
+
|
|
57
|
+
if (jwksCache.has(cacheKey)) {
|
|
58
|
+
const cached = jwksCache.get(cacheKey);
|
|
59
|
+
if (Date.now() - cached.timestamp < cacheTTL) {
|
|
60
|
+
return cached.jwks;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Create remote JWKS fetcher
|
|
65
|
+
const jwks = createRemoteJWKSet(new URL(finalJwksUri), {
|
|
66
|
+
cooldownDuration: 30000, // 30 seconds cooldown between fetches
|
|
67
|
+
cacheMaxAge: cacheTTL
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
jwksCache.set(cacheKey, {
|
|
71
|
+
jwks,
|
|
72
|
+
timestamp: Date.now()
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
return jwks;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* OAuth2 authentication middleware
|
|
80
|
+
*/
|
|
81
|
+
return async (c) => {
|
|
82
|
+
// Extract token from Authorization header
|
|
83
|
+
const authHeader = c.req.header('authorization') || c.req.header('Authorization');
|
|
84
|
+
|
|
85
|
+
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
|
86
|
+
return null; // No OAuth2 token, try next auth method
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const token = authHeader.substring(7); // Remove 'Bearer ' prefix
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// Verify JWT token with remote JWKS
|
|
93
|
+
const jwks = getJWKS();
|
|
94
|
+
|
|
95
|
+
const verifyOptions = {
|
|
96
|
+
issuer,
|
|
97
|
+
algorithms,
|
|
98
|
+
clockTolerance
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
if (audience) {
|
|
102
|
+
verifyOptions.audience = audience;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const { payload } = await jwtVerify(token, jwks, verifyOptions);
|
|
106
|
+
|
|
107
|
+
// Extract user info from token claims
|
|
108
|
+
const userId = payload.sub; // Subject (user ID)
|
|
109
|
+
const email = payload.email || null;
|
|
110
|
+
const username = payload.preferred_username || payload.username || email;
|
|
111
|
+
const scopes = payload.scope ? payload.scope.split(' ') : (payload.scopes || []);
|
|
112
|
+
const role = payload.role || 'user';
|
|
113
|
+
|
|
114
|
+
// Optionally fetch full user info from database
|
|
115
|
+
let user = null;
|
|
116
|
+
|
|
117
|
+
if (fetchUserInfo && userId && usersResource) {
|
|
118
|
+
try {
|
|
119
|
+
// Try to find user by ID
|
|
120
|
+
user = await usersResource.get(userId).catch(() => null);
|
|
121
|
+
|
|
122
|
+
// If not found by ID, try by email
|
|
123
|
+
if (!user && email) {
|
|
124
|
+
const users = await usersResource.query({ email }, { limit: 1 });
|
|
125
|
+
user = users[0] || null;
|
|
126
|
+
}
|
|
127
|
+
} catch (err) {
|
|
128
|
+
// User not found in local database, use token claims only
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// If user found in database, merge with token claims
|
|
133
|
+
if (user) {
|
|
134
|
+
return {
|
|
135
|
+
...user,
|
|
136
|
+
scopes: user.scopes || scopes, // Prefer database scopes
|
|
137
|
+
role: user.role || role,
|
|
138
|
+
tokenClaims: payload // Include full token claims
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// User not in database, create virtual user from token
|
|
143
|
+
return {
|
|
144
|
+
id: userId,
|
|
145
|
+
username: username || userId,
|
|
146
|
+
email,
|
|
147
|
+
role,
|
|
148
|
+
scopes,
|
|
149
|
+
active: true,
|
|
150
|
+
tokenClaims: payload,
|
|
151
|
+
isVirtual: true // Flag to indicate user is not in local database
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
} catch (err) {
|
|
155
|
+
// Token verification failed
|
|
156
|
+
if (config.verbose) {
|
|
157
|
+
console.error('[OAuth2 Auth] Token verification failed:', err.message);
|
|
158
|
+
}
|
|
159
|
+
return null; // Invalid token, try next auth method
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Clear JWKS cache (useful for testing or when keys are rotated)
|
|
166
|
+
*/
|
|
167
|
+
export function clearJWKSCache() {
|
|
168
|
+
jwksCache.clear();
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default createOAuth2Handler;
|