s3db.js 13.4.0 → 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} +38801 -32446
- package/dist/s3db.cjs.map +1 -0
- package/dist/s3db.es.js +38653 -32291
- package/dist/s3db.es.js.map +1 -1
- package/package.json +218 -22
- 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 +6 -2
- package/src/plugins/api/auth/basic-auth.js +40 -10
- 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 +510 -57
- 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 +119 -78
- package/src/plugins/api/routes/resource-routes.js +73 -30
- package/src/plugins/api/server.js +1139 -45
- package/src/plugins/api/utils/custom-routes.js +102 -0
- 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 +91 -12
- 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 +62 -2
- package/src/plugins/eventual-consistency/analytics.js +1 -0
- package/src/plugins/eventual-consistency/consolidation.js +2 -2
- package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
- package/src/plugins/eventual-consistency/install.js +2 -2
- 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 +65 -16
- 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 +584 -31
- 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/plugins/state-machine.plugin.js +57 -2
- 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
|
@@ -0,0 +1,1052 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Identity Provider Plugin - OAuth2/OIDC Authorization Server
|
|
3
|
+
*
|
|
4
|
+
* Provides complete OAuth2 + OpenID Connect server functionality:
|
|
5
|
+
* - RSA key management for token signing
|
|
6
|
+
* - OAuth2 grant types (authorization_code, client_credentials, refresh_token)
|
|
7
|
+
* - OIDC flows (id_token, userinfo endpoint)
|
|
8
|
+
* - Token introspection
|
|
9
|
+
* - Client registration
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* import { Database } from 's3db.js';
|
|
13
|
+
* import { IdentityPlugin } from 's3db.js/plugins/identity';
|
|
14
|
+
*
|
|
15
|
+
* const db = new Database({ connectionString: '...' });
|
|
16
|
+
* await db.connect();
|
|
17
|
+
*
|
|
18
|
+
* await db.usePlugin(new IdentityPlugin({
|
|
19
|
+
* port: 4000,
|
|
20
|
+
* issuer: 'http://localhost:4000',
|
|
21
|
+
* supportedScopes: ['openid', 'profile', 'email', 'read:api', 'write:api'],
|
|
22
|
+
* supportedGrantTypes: ['authorization_code', 'refresh_token', 'client_credentials'],
|
|
23
|
+
* accessTokenExpiry: '15m',
|
|
24
|
+
* idTokenExpiry: '15m',
|
|
25
|
+
* refreshTokenExpiry: '7d'
|
|
26
|
+
* }));
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { Plugin } from '../plugin.class.js';
|
|
30
|
+
import { requirePluginDependency } from '../concerns/plugin-dependencies.js';
|
|
31
|
+
import tryFn from '../../concerns/try-fn.js';
|
|
32
|
+
import { OAuth2Server } from './oauth2-server.js';
|
|
33
|
+
import {
|
|
34
|
+
BASE_USER_ATTRIBUTES,
|
|
35
|
+
BASE_TENANT_ATTRIBUTES,
|
|
36
|
+
BASE_CLIENT_ATTRIBUTES,
|
|
37
|
+
validateResourcesConfig,
|
|
38
|
+
mergeResourceConfig
|
|
39
|
+
} from './concerns/resource-schemas.js';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Identity Provider Plugin class
|
|
43
|
+
* @class
|
|
44
|
+
* @extends Plugin
|
|
45
|
+
*/
|
|
46
|
+
export class IdentityPlugin extends Plugin {
|
|
47
|
+
/**
|
|
48
|
+
* Create Identity Provider Plugin instance
|
|
49
|
+
* @param {Object} options - Plugin configuration
|
|
50
|
+
*/
|
|
51
|
+
constructor(options = {}) {
|
|
52
|
+
super(options);
|
|
53
|
+
|
|
54
|
+
// Validate required resources configuration
|
|
55
|
+
const resourcesValidation = validateResourcesConfig(options.resources);
|
|
56
|
+
if (!resourcesValidation.valid) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
'IdentityPlugin configuration error:\n' +
|
|
59
|
+
resourcesValidation.errors.join('\n')
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Validate resource configs (will throw if invalid)
|
|
64
|
+
mergeResourceConfig(
|
|
65
|
+
{ attributes: BASE_USER_ATTRIBUTES },
|
|
66
|
+
options.resources.users,
|
|
67
|
+
'users'
|
|
68
|
+
);
|
|
69
|
+
mergeResourceConfig(
|
|
70
|
+
{ attributes: BASE_TENANT_ATTRIBUTES },
|
|
71
|
+
options.resources.tenants,
|
|
72
|
+
'tenants'
|
|
73
|
+
);
|
|
74
|
+
mergeResourceConfig(
|
|
75
|
+
{ attributes: BASE_CLIENT_ATTRIBUTES },
|
|
76
|
+
options.resources.clients,
|
|
77
|
+
'clients'
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
this.config = {
|
|
81
|
+
// Server configuration
|
|
82
|
+
port: options.port || 4000,
|
|
83
|
+
host: options.host || '0.0.0.0',
|
|
84
|
+
verbose: options.verbose || false,
|
|
85
|
+
|
|
86
|
+
// OAuth2/OIDC configuration
|
|
87
|
+
issuer: options.issuer || `http://localhost:${options.port || 4000}`,
|
|
88
|
+
supportedScopes: options.supportedScopes || ['openid', 'profile', 'email', 'offline_access'],
|
|
89
|
+
supportedGrantTypes: options.supportedGrantTypes || ['authorization_code', 'client_credentials', 'refresh_token'],
|
|
90
|
+
supportedResponseTypes: options.supportedResponseTypes || ['code', 'token', 'id_token'],
|
|
91
|
+
|
|
92
|
+
// Token expiration
|
|
93
|
+
accessTokenExpiry: options.accessTokenExpiry || '15m',
|
|
94
|
+
idTokenExpiry: options.idTokenExpiry || '15m',
|
|
95
|
+
refreshTokenExpiry: options.refreshTokenExpiry || '7d',
|
|
96
|
+
authCodeExpiry: options.authCodeExpiry || '10m',
|
|
97
|
+
|
|
98
|
+
// Resource configuration (REQUIRED)
|
|
99
|
+
// User must declare: users, tenants, clients with full resource config
|
|
100
|
+
resources: {
|
|
101
|
+
users: {
|
|
102
|
+
userConfig: options.resources.users, // Store user's full config
|
|
103
|
+
mergedConfig: null // Will be populated in _createResources()
|
|
104
|
+
},
|
|
105
|
+
tenants: {
|
|
106
|
+
userConfig: options.resources.tenants,
|
|
107
|
+
mergedConfig: null
|
|
108
|
+
},
|
|
109
|
+
clients: {
|
|
110
|
+
userConfig: options.resources.clients,
|
|
111
|
+
mergedConfig: null
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
// CORS configuration
|
|
116
|
+
cors: {
|
|
117
|
+
enabled: options.cors?.enabled !== false, // Enabled by default for identity servers
|
|
118
|
+
origin: options.cors?.origin || '*',
|
|
119
|
+
methods: options.cors?.methods || ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
120
|
+
allowedHeaders: options.cors?.allowedHeaders || ['Content-Type', 'Authorization', 'X-API-Key'],
|
|
121
|
+
credentials: options.cors?.credentials !== false,
|
|
122
|
+
maxAge: options.cors?.maxAge || 86400
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
// Security headers
|
|
126
|
+
security: {
|
|
127
|
+
enabled: options.security?.enabled !== false,
|
|
128
|
+
contentSecurityPolicy: {
|
|
129
|
+
enabled: true,
|
|
130
|
+
directives: {
|
|
131
|
+
'default-src': ["'self'"],
|
|
132
|
+
'script-src': ["'self'", "'unsafe-inline'"],
|
|
133
|
+
'style-src': ["'self'", "'unsafe-inline'", 'https://unpkg.com'],
|
|
134
|
+
'img-src': ["'self'", 'data:', 'https:'],
|
|
135
|
+
'font-src': ["'self'", 'https://unpkg.com'],
|
|
136
|
+
...options.security?.contentSecurityPolicy?.directives
|
|
137
|
+
},
|
|
138
|
+
reportOnly: options.security?.contentSecurityPolicy?.reportOnly || false,
|
|
139
|
+
reportUri: options.security?.contentSecurityPolicy?.reportUri || null
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
|
|
143
|
+
// Logging
|
|
144
|
+
logging: {
|
|
145
|
+
enabled: options.logging?.enabled || false,
|
|
146
|
+
format: options.logging?.format || ':method :path :status :response-time ms'
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
// Session Management
|
|
150
|
+
session: {
|
|
151
|
+
sessionExpiry: options.session?.sessionExpiry || '24h',
|
|
152
|
+
cookieName: options.session?.cookieName || 's3db_session',
|
|
153
|
+
cookiePath: options.session?.cookiePath || '/',
|
|
154
|
+
cookieHttpOnly: options.session?.cookieHttpOnly !== false,
|
|
155
|
+
cookieSecure: options.session?.cookieSecure || false, // Set true in production with HTTPS
|
|
156
|
+
cookieSameSite: options.session?.cookieSameSite || 'Lax',
|
|
157
|
+
cleanupInterval: options.session?.cleanupInterval || 3600000, // 1 hour
|
|
158
|
+
enableCleanup: options.session?.enableCleanup !== false
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// Password Policy
|
|
162
|
+
passwordPolicy: {
|
|
163
|
+
minLength: options.passwordPolicy?.minLength || 8,
|
|
164
|
+
maxLength: options.passwordPolicy?.maxLength || 128,
|
|
165
|
+
requireUppercase: options.passwordPolicy?.requireUppercase !== false,
|
|
166
|
+
requireLowercase: options.passwordPolicy?.requireLowercase !== false,
|
|
167
|
+
requireNumbers: options.passwordPolicy?.requireNumbers !== false,
|
|
168
|
+
requireSymbols: options.passwordPolicy?.requireSymbols || false,
|
|
169
|
+
bcryptRounds: options.passwordPolicy?.bcryptRounds || 10
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
// Registration Configuration
|
|
173
|
+
registration: {
|
|
174
|
+
enabled: options.registration?.enabled !== false, // Enabled by default
|
|
175
|
+
requireEmailVerification: options.registration?.requireEmailVerification !== false, // Required by default
|
|
176
|
+
allowedDomains: options.registration?.allowedDomains || null, // null = allow all domains
|
|
177
|
+
blockedDomains: options.registration?.blockedDomains || [], // Block specific domains
|
|
178
|
+
customMessage: options.registration?.customMessage || null // Custom message when disabled
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
// UI Configuration (white-label customization)
|
|
182
|
+
ui: {
|
|
183
|
+
// Branding
|
|
184
|
+
title: options.ui?.title || 'S3DB Identity',
|
|
185
|
+
companyName: options.ui?.companyName || 'S3DB',
|
|
186
|
+
legalName: options.ui?.legalName || options.ui?.companyName || 'S3DB Corp',
|
|
187
|
+
tagline: options.ui?.tagline || 'Secure Identity & Access Management',
|
|
188
|
+
welcomeMessage: options.ui?.welcomeMessage || 'Welcome back!',
|
|
189
|
+
logoUrl: options.ui?.logoUrl || null,
|
|
190
|
+
logo: options.ui?.logo || null, // Deprecated, use logoUrl
|
|
191
|
+
favicon: options.ui?.favicon || null,
|
|
192
|
+
|
|
193
|
+
// Colors (11 options)
|
|
194
|
+
primaryColor: options.ui?.primaryColor || '#007bff',
|
|
195
|
+
secondaryColor: options.ui?.secondaryColor || '#6c757d',
|
|
196
|
+
successColor: options.ui?.successColor || '#28a745',
|
|
197
|
+
dangerColor: options.ui?.dangerColor || '#dc3545',
|
|
198
|
+
warningColor: options.ui?.warningColor || '#ffc107',
|
|
199
|
+
infoColor: options.ui?.infoColor || '#17a2b8',
|
|
200
|
+
textColor: options.ui?.textColor || '#212529',
|
|
201
|
+
textMuted: options.ui?.textMuted || '#6c757d',
|
|
202
|
+
backgroundColor: options.ui?.backgroundColor || '#ffffff',
|
|
203
|
+
backgroundLight: options.ui?.backgroundLight || '#f8f9fa',
|
|
204
|
+
borderColor: options.ui?.borderColor || '#dee2e6',
|
|
205
|
+
|
|
206
|
+
// Typography
|
|
207
|
+
fontFamily: options.ui?.fontFamily || '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
208
|
+
fontSize: options.ui?.fontSize || '16px',
|
|
209
|
+
|
|
210
|
+
// Layout
|
|
211
|
+
borderRadius: options.ui?.borderRadius || '0.375rem',
|
|
212
|
+
boxShadow: options.ui?.boxShadow || '0 0.125rem 0.25rem rgba(0, 0, 0, 0.075)',
|
|
213
|
+
|
|
214
|
+
// Company Info
|
|
215
|
+
footerText: options.ui?.footerText || null,
|
|
216
|
+
supportEmail: options.ui?.supportEmail || null,
|
|
217
|
+
privacyUrl: options.ui?.privacyUrl || '/privacy',
|
|
218
|
+
termsUrl: options.ui?.termsUrl || '/terms',
|
|
219
|
+
|
|
220
|
+
// Social Links
|
|
221
|
+
socialLinks: options.ui?.socialLinks || null,
|
|
222
|
+
|
|
223
|
+
// Custom CSS
|
|
224
|
+
customCSS: options.ui?.customCSS || null,
|
|
225
|
+
|
|
226
|
+
// Custom Pages (override default pages)
|
|
227
|
+
customPages: options.ui?.customPages || {},
|
|
228
|
+
|
|
229
|
+
// Base URL
|
|
230
|
+
baseUrl: options.ui?.baseUrl || `http://localhost:${options.port || 4000}`
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
// Email Configuration (SMTP)
|
|
234
|
+
email: {
|
|
235
|
+
enabled: options.email?.enabled !== false,
|
|
236
|
+
from: options.email?.from || 'noreply@s3db.identity',
|
|
237
|
+
replyTo: options.email?.replyTo || null,
|
|
238
|
+
smtp: {
|
|
239
|
+
host: options.email?.smtp?.host || 'localhost',
|
|
240
|
+
port: options.email?.smtp?.port || 587,
|
|
241
|
+
secure: options.email?.smtp?.secure || false,
|
|
242
|
+
auth: {
|
|
243
|
+
user: options.email?.smtp?.auth?.user || '',
|
|
244
|
+
pass: options.email?.smtp?.auth?.pass || ''
|
|
245
|
+
},
|
|
246
|
+
tls: {
|
|
247
|
+
rejectUnauthorized: options.email?.smtp?.tls?.rejectUnauthorized !== false
|
|
248
|
+
}
|
|
249
|
+
},
|
|
250
|
+
templates: {
|
|
251
|
+
baseUrl: options.email?.templates?.baseUrl || options.ui?.baseUrl || `http://localhost:${options.port || 4000}`,
|
|
252
|
+
brandName: options.email?.templates?.brandName || options.ui?.title || 'S3DB Identity',
|
|
253
|
+
brandLogo: options.email?.templates?.brandLogo || options.ui?.logo || null,
|
|
254
|
+
brandColor: options.email?.templates?.brandColor || options.ui?.primaryColor || '#007bff',
|
|
255
|
+
supportEmail: options.email?.templates?.supportEmail || options.email?.replyTo || null,
|
|
256
|
+
customFooter: options.email?.templates?.customFooter || null
|
|
257
|
+
}
|
|
258
|
+
},
|
|
259
|
+
|
|
260
|
+
// MFA Configuration (Multi-Factor Authentication)
|
|
261
|
+
mfa: {
|
|
262
|
+
enabled: options.mfa?.enabled || false, // Enable MFA/TOTP
|
|
263
|
+
required: options.mfa?.required || false, // Require MFA for all users
|
|
264
|
+
issuer: options.mfa?.issuer || options.ui?.title || 'S3DB Identity', // TOTP issuer name
|
|
265
|
+
algorithm: options.mfa?.algorithm || 'SHA1', // SHA1, SHA256, SHA512
|
|
266
|
+
digits: options.mfa?.digits || 6, // 6 or 8 digits
|
|
267
|
+
period: options.mfa?.period || 30, // 30 seconds
|
|
268
|
+
window: options.mfa?.window || 1, // Time window tolerance
|
|
269
|
+
backupCodesCount: options.mfa?.backupCodesCount || 10, // Number of backup codes
|
|
270
|
+
backupCodeLength: options.mfa?.backupCodeLength || 8 // Backup code length
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
// Audit Configuration (Compliance & Security Logging)
|
|
274
|
+
audit: {
|
|
275
|
+
enabled: options.audit?.enabled !== false, // Enable audit logging
|
|
276
|
+
includeData: options.audit?.includeData !== false, // Store before/after data
|
|
277
|
+
includePartitions: options.audit?.includePartitions !== false, // Track partition info
|
|
278
|
+
maxDataSize: options.audit?.maxDataSize || 10000, // Max bytes for data field
|
|
279
|
+
resources: options.audit?.resources || ['users', 'plg_oauth_clients'], // Resources to audit
|
|
280
|
+
events: options.audit?.events || [ // Custom events to audit
|
|
281
|
+
'login', 'logout', 'login_failed',
|
|
282
|
+
'account_locked', 'account_unlocked',
|
|
283
|
+
'ip_banned', 'ip_unbanned',
|
|
284
|
+
'password_reset_requested', 'password_changed',
|
|
285
|
+
'email_verified', 'user_created', 'user_deleted',
|
|
286
|
+
'mfa_enrolled', 'mfa_disabled', 'mfa_verified', 'mfa_failed'
|
|
287
|
+
]
|
|
288
|
+
},
|
|
289
|
+
|
|
290
|
+
// Account Lockout Configuration (Per-User Brute Force Protection)
|
|
291
|
+
accountLockout: {
|
|
292
|
+
enabled: options.accountLockout?.enabled !== false, // Enable account lockout
|
|
293
|
+
maxAttempts: options.accountLockout?.maxAttempts || 5, // Max failed attempts before lockout
|
|
294
|
+
lockoutDuration: options.accountLockout?.lockoutDuration || 900000, // Lockout duration (15 min)
|
|
295
|
+
resetOnSuccess: options.accountLockout?.resetOnSuccess !== false // Reset counter on successful login
|
|
296
|
+
},
|
|
297
|
+
|
|
298
|
+
// Failban Configuration (IP-Based Brute Force Protection)
|
|
299
|
+
failban: {
|
|
300
|
+
enabled: options.failban?.enabled !== false, // Enable failban protection
|
|
301
|
+
maxViolations: options.failban?.maxViolations || 5, // Max failed attempts before ban
|
|
302
|
+
violationWindow: options.failban?.violationWindow || 300000, // Time window for violations (5 min)
|
|
303
|
+
banDuration: options.failban?.banDuration || 900000, // Ban duration (15 min)
|
|
304
|
+
whitelist: options.failban?.whitelist || ['127.0.0.1', '::1'], // IPs to never ban
|
|
305
|
+
blacklist: options.failban?.blacklist || [], // IPs to always ban
|
|
306
|
+
persistViolations: options.failban?.persistViolations !== false, // Persist violations to DB
|
|
307
|
+
endpoints: {
|
|
308
|
+
login: options.failban?.endpoints?.login !== false, // Protect /oauth/authorize POST
|
|
309
|
+
token: options.failban?.endpoints?.token !== false, // Protect /oauth/token
|
|
310
|
+
register: options.failban?.endpoints?.register !== false // Protect /register
|
|
311
|
+
},
|
|
312
|
+
geo: {
|
|
313
|
+
enabled: options.failban?.geo?.enabled || false, // Enable GeoIP blocking
|
|
314
|
+
databasePath: options.failban?.geo?.databasePath || null, // Path to GeoLite2-Country.mmdb
|
|
315
|
+
allowedCountries: options.failban?.geo?.allowedCountries || [], // Whitelist countries (ISO codes)
|
|
316
|
+
blockedCountries: options.failban?.geo?.blockedCountries || [], // Blacklist countries (ISO codes)
|
|
317
|
+
blockUnknown: options.failban?.geo?.blockUnknown || false // Block IPs with unknown country
|
|
318
|
+
}
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
// Features (MVP - Phase 1)
|
|
322
|
+
features: {
|
|
323
|
+
// Endpoints (can be disabled individually)
|
|
324
|
+
discovery: options.features?.discovery !== false, // GET /.well-known/openid-configuration
|
|
325
|
+
jwks: options.features?.jwks !== false, // GET /.well-known/jwks.json
|
|
326
|
+
token: options.features?.token !== false, // POST /oauth/token
|
|
327
|
+
authorize: options.features?.authorize !== false, // GET/POST /oauth/authorize
|
|
328
|
+
userinfo: options.features?.userinfo !== false, // GET /oauth/userinfo
|
|
329
|
+
introspection: options.features?.introspection !== false, // POST /oauth/introspect
|
|
330
|
+
revocation: options.features?.revocation !== false, // POST /oauth/revoke
|
|
331
|
+
registration: options.features?.registration !== false, // POST /oauth/register (RFC 7591)
|
|
332
|
+
|
|
333
|
+
// Authorization Code Flow UI
|
|
334
|
+
builtInLoginUI: options.features?.builtInLoginUI !== false, // HTML login form
|
|
335
|
+
customLoginHandler: options.features?.customLoginHandler || null, // Custom UI handler
|
|
336
|
+
|
|
337
|
+
// PKCE (Proof Key for Code Exchange - RFC 7636)
|
|
338
|
+
pkce: {
|
|
339
|
+
enabled: options.features?.pkce?.enabled !== false, // PKCE support
|
|
340
|
+
required: options.features?.pkce?.required || false, // Force PKCE for public clients
|
|
341
|
+
methods: options.features?.pkce?.methods || ['S256', 'plain'] // Supported methods
|
|
342
|
+
},
|
|
343
|
+
|
|
344
|
+
// Refresh tokens
|
|
345
|
+
refreshTokens: options.features?.refreshTokens !== false, // Enable refresh tokens
|
|
346
|
+
refreshTokenRotation: options.features?.refreshTokenRotation || false, // Rotate on each use
|
|
347
|
+
revokeOldRefreshTokens: options.features?.revokeOldRefreshTokens !== false, // Revoke old tokens after rotation
|
|
348
|
+
|
|
349
|
+
// Future features (Phase 2 - commented for reference)
|
|
350
|
+
// admin: { enabled: false, apiKey: null, endpoints: {...} },
|
|
351
|
+
// consent: { enabled: false, skipForTrustedClients: true },
|
|
352
|
+
// mfa: { enabled: false, methods: ['totp', 'sms', 'email'] },
|
|
353
|
+
// emailVerification: { enabled: false, required: false },
|
|
354
|
+
// passwordPolicy: { enabled: false, minLength: 8, ... },
|
|
355
|
+
// webhooks: { enabled: false, endpoints: [], events: [] }
|
|
356
|
+
}
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
this.server = null;
|
|
360
|
+
this.oauth2Server = null;
|
|
361
|
+
this.sessionManager = null;
|
|
362
|
+
this.emailService = null;
|
|
363
|
+
this.failbanManager = null;
|
|
364
|
+
this.auditPlugin = null;
|
|
365
|
+
this.mfaManager = null;
|
|
366
|
+
|
|
367
|
+
// Internal plugin resources (prefixed with plg_)
|
|
368
|
+
this.oauth2KeysResource = null;
|
|
369
|
+
this.oauth2AuthCodesResource = null;
|
|
370
|
+
this.sessionsResource = null;
|
|
371
|
+
this.passwordResetTokensResource = null;
|
|
372
|
+
this.mfaDevicesResource = null;
|
|
373
|
+
|
|
374
|
+
// User-managed resources (user chooses names)
|
|
375
|
+
this.usersResource = null;
|
|
376
|
+
this.tenantsResource = null;
|
|
377
|
+
this.clientsResource = null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Validate plugin dependencies
|
|
382
|
+
* @private
|
|
383
|
+
*/
|
|
384
|
+
async _validateDependencies() {
|
|
385
|
+
await requirePluginDependency('identity-plugin', {
|
|
386
|
+
throwOnError: true,
|
|
387
|
+
checkVersions: true
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Install plugin
|
|
393
|
+
*/
|
|
394
|
+
async onInstall() {
|
|
395
|
+
if (this.config.verbose) {
|
|
396
|
+
console.log('[Identity Plugin] Installing...');
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Validate dependencies
|
|
400
|
+
try {
|
|
401
|
+
await this._validateDependencies();
|
|
402
|
+
} catch (err) {
|
|
403
|
+
console.error('[Identity Plugin] Dependency validation failed:', err.message);
|
|
404
|
+
throw err;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Create user-managed resources (users, tenants, clients) with merged attributes
|
|
408
|
+
await this._createUserManagedResources();
|
|
409
|
+
|
|
410
|
+
// Create OAuth2 internal resources (keys, auth_codes, sessions, etc.)
|
|
411
|
+
await this._createOAuth2Resources();
|
|
412
|
+
|
|
413
|
+
// Initialize OAuth2 Server
|
|
414
|
+
await this._initializeOAuth2Server();
|
|
415
|
+
|
|
416
|
+
// Initialize Session Manager
|
|
417
|
+
await this._initializeSessionManager();
|
|
418
|
+
|
|
419
|
+
// Initialize Email Service
|
|
420
|
+
await this._initializeEmailService();
|
|
421
|
+
|
|
422
|
+
// Initialize Failban Manager
|
|
423
|
+
await this._initializeFailbanManager();
|
|
424
|
+
|
|
425
|
+
// Initialize Audit Plugin
|
|
426
|
+
await this._initializeAuditPlugin();
|
|
427
|
+
|
|
428
|
+
// Initialize MFA Manager
|
|
429
|
+
await this._initializeMFAManager();
|
|
430
|
+
|
|
431
|
+
if (this.config.verbose) {
|
|
432
|
+
console.log('[Identity Plugin] Installed successfully');
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Create OAuth2 resources for authorization server
|
|
438
|
+
* @private
|
|
439
|
+
*/
|
|
440
|
+
async _createOAuth2Resources() {
|
|
441
|
+
// 1. OAuth Keys Resource (RSA keys for token signing)
|
|
442
|
+
const [okKeys, errKeys, keysResource] = await tryFn(() =>
|
|
443
|
+
this.database.createResource({
|
|
444
|
+
name: 'plg_oauth_keys',
|
|
445
|
+
attributes: {
|
|
446
|
+
kid: 'string|required',
|
|
447
|
+
publicKey: 'string|required',
|
|
448
|
+
privateKey: 'secret|required',
|
|
449
|
+
algorithm: 'string|default:RS256',
|
|
450
|
+
use: 'string|default:sig',
|
|
451
|
+
active: 'boolean|default:true',
|
|
452
|
+
createdAt: 'string|optional'
|
|
453
|
+
},
|
|
454
|
+
behavior: 'body-overflow',
|
|
455
|
+
timestamps: true,
|
|
456
|
+
createdBy: 'IdentityPlugin'
|
|
457
|
+
})
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
if (okKeys) {
|
|
461
|
+
this.oauth2KeysResource = keysResource;
|
|
462
|
+
if (this.config.verbose) {
|
|
463
|
+
console.log('[Identity Plugin] Created plg_oauth_keys resource');
|
|
464
|
+
}
|
|
465
|
+
} else if (this.database.resources.plg_oauth_keys) {
|
|
466
|
+
this.oauth2KeysResource = this.database.resources.plg_oauth_keys;
|
|
467
|
+
if (this.config.verbose) {
|
|
468
|
+
console.log('[Identity Plugin] Using existing plg_oauth_keys resource');
|
|
469
|
+
}
|
|
470
|
+
} else {
|
|
471
|
+
throw errKeys;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// 2. OAuth Authorization Codes Resource (authorization_code flow)
|
|
475
|
+
const [okCodes, errCodes, codesResource] = await tryFn(() =>
|
|
476
|
+
this.database.createResource({
|
|
477
|
+
name: 'plg_auth_codes',
|
|
478
|
+
attributes: {
|
|
479
|
+
code: 'string|required',
|
|
480
|
+
clientId: 'string|required',
|
|
481
|
+
userId: 'string|required',
|
|
482
|
+
redirectUri: 'string|required',
|
|
483
|
+
scope: 'string|optional',
|
|
484
|
+
expiresAt: 'string|required',
|
|
485
|
+
used: 'boolean|default:false',
|
|
486
|
+
codeChallenge: 'string|optional', // PKCE support
|
|
487
|
+
codeChallengeMethod: 'string|optional', // PKCE support
|
|
488
|
+
createdAt: 'string|optional'
|
|
489
|
+
},
|
|
490
|
+
behavior: 'body-overflow',
|
|
491
|
+
timestamps: true,
|
|
492
|
+
createdBy: 'IdentityPlugin'
|
|
493
|
+
})
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
if (okCodes) {
|
|
497
|
+
this.oauth2AuthCodesResource = codesResource;
|
|
498
|
+
if (this.config.verbose) {
|
|
499
|
+
console.log('[Identity Plugin] Created plg_auth_codes resource');
|
|
500
|
+
}
|
|
501
|
+
} else if (this.database.resources.plg_auth_codes) {
|
|
502
|
+
this.oauth2AuthCodesResource = this.database.resources.plg_auth_codes;
|
|
503
|
+
if (this.config.verbose) {
|
|
504
|
+
console.log('[Identity Plugin] Using existing plg_auth_codes resource');
|
|
505
|
+
}
|
|
506
|
+
} else {
|
|
507
|
+
throw errCodes;
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
// 3. Sessions Resource (user sessions for UI/admin)
|
|
511
|
+
const [okSessions, errSessions, sessionsResource] = await tryFn(() =>
|
|
512
|
+
this.database.createResource({
|
|
513
|
+
name: 'plg_sessions',
|
|
514
|
+
attributes: {
|
|
515
|
+
userId: 'string|required',
|
|
516
|
+
expiresAt: 'string|required',
|
|
517
|
+
ipAddress: 'ip4|optional',
|
|
518
|
+
userAgent: 'string|optional',
|
|
519
|
+
metadata: 'object|optional',
|
|
520
|
+
createdAt: 'string|optional'
|
|
521
|
+
},
|
|
522
|
+
behavior: 'body-overflow',
|
|
523
|
+
timestamps: true,
|
|
524
|
+
createdBy: 'IdentityPlugin'
|
|
525
|
+
})
|
|
526
|
+
);
|
|
527
|
+
|
|
528
|
+
if (okSessions) {
|
|
529
|
+
this.sessionsResource = sessionsResource;
|
|
530
|
+
if (this.config.verbose) {
|
|
531
|
+
console.log('[Identity Plugin] Created plg_sessions resource');
|
|
532
|
+
}
|
|
533
|
+
} else if (this.database.resources.plg_sessions) {
|
|
534
|
+
this.sessionsResource = this.database.resources.plg_sessions;
|
|
535
|
+
if (this.config.verbose) {
|
|
536
|
+
console.log('[Identity Plugin] Using existing plg_sessions resource');
|
|
537
|
+
}
|
|
538
|
+
} else {
|
|
539
|
+
throw errSessions;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// 4. Password Reset Tokens Resource (for password reset flow)
|
|
543
|
+
const [okResetTokens, errResetTokens, resetTokensResource] = await tryFn(() =>
|
|
544
|
+
this.database.createResource({
|
|
545
|
+
name: 'plg_password_reset_tokens',
|
|
546
|
+
attributes: {
|
|
547
|
+
userId: 'string|required',
|
|
548
|
+
token: 'string|required',
|
|
549
|
+
expiresAt: 'string|required',
|
|
550
|
+
used: 'boolean|default:false',
|
|
551
|
+
createdAt: 'string|optional'
|
|
552
|
+
},
|
|
553
|
+
behavior: 'body-overflow',
|
|
554
|
+
timestamps: true,
|
|
555
|
+
createdBy: 'IdentityPlugin'
|
|
556
|
+
})
|
|
557
|
+
);
|
|
558
|
+
|
|
559
|
+
if (okResetTokens) {
|
|
560
|
+
this.passwordResetTokensResource = resetTokensResource;
|
|
561
|
+
if (this.config.verbose) {
|
|
562
|
+
console.log('[Identity Plugin] Created plg_password_reset_tokens resource');
|
|
563
|
+
}
|
|
564
|
+
} else if (this.database.resources.plg_password_reset_tokens) {
|
|
565
|
+
this.passwordResetTokensResource = this.database.resources.plg_password_reset_tokens;
|
|
566
|
+
if (this.config.verbose) {
|
|
567
|
+
console.log('[Identity Plugin] Using existing plg_password_reset_tokens resource');
|
|
568
|
+
}
|
|
569
|
+
} else {
|
|
570
|
+
throw errResetTokens;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// 5. MFA Devices Resource (for multi-factor authentication)
|
|
574
|
+
if (this.config.mfa.enabled) {
|
|
575
|
+
const [okMFA, errMFA, mfaResource] = await tryFn(() =>
|
|
576
|
+
this.database.createResource({
|
|
577
|
+
name: 'plg_mfa_devices',
|
|
578
|
+
attributes: {
|
|
579
|
+
userId: 'string|required',
|
|
580
|
+
type: 'string|required', // 'totp', 'sms', 'email'
|
|
581
|
+
secret: 'secret|required', // TOTP secret (encrypted by S3DB)
|
|
582
|
+
verified: 'boolean|default:false',
|
|
583
|
+
backupCodes: 'array|items:string', // Hashed backup codes
|
|
584
|
+
enrolledAt: 'string',
|
|
585
|
+
lastUsedAt: 'string|optional',
|
|
586
|
+
deviceName: 'string|optional', // User-friendly name
|
|
587
|
+
metadata: 'object|optional'
|
|
588
|
+
},
|
|
589
|
+
behavior: 'body-overflow',
|
|
590
|
+
timestamps: true,
|
|
591
|
+
partitions: {
|
|
592
|
+
byUser: {
|
|
593
|
+
fields: { userId: 'string' }
|
|
594
|
+
}
|
|
595
|
+
},
|
|
596
|
+
createdBy: 'IdentityPlugin'
|
|
597
|
+
})
|
|
598
|
+
);
|
|
599
|
+
|
|
600
|
+
if (okMFA) {
|
|
601
|
+
this.mfaDevicesResource = mfaResource;
|
|
602
|
+
if (this.config.verbose) {
|
|
603
|
+
console.log('[Identity Plugin] Created plg_mfa_devices resource');
|
|
604
|
+
}
|
|
605
|
+
} else if (this.database.resources.plg_mfa_devices) {
|
|
606
|
+
this.mfaDevicesResource = this.database.resources.plg_mfa_devices;
|
|
607
|
+
if (this.config.verbose) {
|
|
608
|
+
console.log('[Identity Plugin] Using existing plg_mfa_devices resource');
|
|
609
|
+
}
|
|
610
|
+
} else {
|
|
611
|
+
console.warn('[Identity Plugin] MFA enabled but failed to create plg_mfa_devices resource:', errMFA?.message);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
/**
|
|
617
|
+
* Create user-managed resources (users, tenants, clients) with merged config
|
|
618
|
+
* @private
|
|
619
|
+
*/
|
|
620
|
+
async _createUserManagedResources() {
|
|
621
|
+
// 1. Create Users Resource
|
|
622
|
+
const usersConfig = this.config.resources.users;
|
|
623
|
+
|
|
624
|
+
// Base config for users
|
|
625
|
+
const usersBaseConfig = {
|
|
626
|
+
attributes: BASE_USER_ATTRIBUTES,
|
|
627
|
+
behavior: 'body-overflow',
|
|
628
|
+
timestamps: true
|
|
629
|
+
};
|
|
630
|
+
|
|
631
|
+
// Deep merge user config with base config
|
|
632
|
+
const usersMergedConfig = mergeResourceConfig(
|
|
633
|
+
usersBaseConfig,
|
|
634
|
+
usersConfig.userConfig,
|
|
635
|
+
'users'
|
|
636
|
+
);
|
|
637
|
+
|
|
638
|
+
// Store merged config for reference
|
|
639
|
+
usersConfig.mergedConfig = usersMergedConfig;
|
|
640
|
+
|
|
641
|
+
const [okUsers, errUsers, usersResource] = await tryFn(() =>
|
|
642
|
+
this.database.createResource(usersMergedConfig)
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
if (okUsers) {
|
|
646
|
+
this.usersResource = usersResource;
|
|
647
|
+
if (this.config.verbose) {
|
|
648
|
+
console.log(`[Identity Plugin] Created ${usersMergedConfig.name} resource with merged config`);
|
|
649
|
+
}
|
|
650
|
+
} else if (this.database.resources[usersMergedConfig.name]) {
|
|
651
|
+
this.usersResource = this.database.resources[usersMergedConfig.name];
|
|
652
|
+
if (this.config.verbose) {
|
|
653
|
+
console.log(`[Identity Plugin] Using existing ${usersMergedConfig.name} resource`);
|
|
654
|
+
}
|
|
655
|
+
} else {
|
|
656
|
+
throw errUsers;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// 2. Create Tenants Resource (multi-tenancy support)
|
|
660
|
+
const tenantsConfig = this.config.resources.tenants;
|
|
661
|
+
|
|
662
|
+
const tenantsBaseConfig = {
|
|
663
|
+
attributes: BASE_TENANT_ATTRIBUTES,
|
|
664
|
+
behavior: 'body-overflow',
|
|
665
|
+
timestamps: true
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
const tenantsMergedConfig = mergeResourceConfig(
|
|
669
|
+
tenantsBaseConfig,
|
|
670
|
+
tenantsConfig.userConfig,
|
|
671
|
+
'tenants'
|
|
672
|
+
);
|
|
673
|
+
|
|
674
|
+
tenantsConfig.mergedConfig = tenantsMergedConfig;
|
|
675
|
+
|
|
676
|
+
const [okTenants, errTenants, tenantsResource] = await tryFn(() =>
|
|
677
|
+
this.database.createResource(tenantsMergedConfig)
|
|
678
|
+
);
|
|
679
|
+
|
|
680
|
+
if (okTenants) {
|
|
681
|
+
this.tenantsResource = tenantsResource;
|
|
682
|
+
if (this.config.verbose) {
|
|
683
|
+
console.log(`[Identity Plugin] Created ${tenantsMergedConfig.name} resource with merged config`);
|
|
684
|
+
}
|
|
685
|
+
} else if (this.database.resources[tenantsMergedConfig.name]) {
|
|
686
|
+
this.tenantsResource = this.database.resources[tenantsMergedConfig.name];
|
|
687
|
+
if (this.config.verbose) {
|
|
688
|
+
console.log(`[Identity Plugin] Using existing ${tenantsMergedConfig.name} resource`);
|
|
689
|
+
}
|
|
690
|
+
} else {
|
|
691
|
+
throw errTenants;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// 3. Create OAuth2 Clients Resource
|
|
695
|
+
const clientsConfig = this.config.resources.clients;
|
|
696
|
+
|
|
697
|
+
const clientsBaseConfig = {
|
|
698
|
+
attributes: BASE_CLIENT_ATTRIBUTES,
|
|
699
|
+
behavior: 'body-overflow',
|
|
700
|
+
timestamps: true
|
|
701
|
+
};
|
|
702
|
+
|
|
703
|
+
const clientsMergedConfig = mergeResourceConfig(
|
|
704
|
+
clientsBaseConfig,
|
|
705
|
+
clientsConfig.userConfig,
|
|
706
|
+
'clients'
|
|
707
|
+
);
|
|
708
|
+
|
|
709
|
+
clientsConfig.mergedConfig = clientsMergedConfig;
|
|
710
|
+
|
|
711
|
+
const [okClients, errClients, clientsResource] = await tryFn(() =>
|
|
712
|
+
this.database.createResource(clientsMergedConfig)
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
if (okClients) {
|
|
716
|
+
this.clientsResource = clientsResource;
|
|
717
|
+
if (this.config.verbose) {
|
|
718
|
+
console.log(`[Identity Plugin] Created ${clientsMergedConfig.name} resource with merged config`);
|
|
719
|
+
}
|
|
720
|
+
} else if (this.database.resources[clientsMergedConfig.name]) {
|
|
721
|
+
this.clientsResource = this.database.resources[clientsMergedConfig.name];
|
|
722
|
+
if (this.config.verbose) {
|
|
723
|
+
console.log(`[Identity Plugin] Using existing ${clientsMergedConfig.name} resource`);
|
|
724
|
+
}
|
|
725
|
+
} else {
|
|
726
|
+
throw errClients;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Initialize OAuth2 Server instance
|
|
732
|
+
* @private
|
|
733
|
+
*/
|
|
734
|
+
async _initializeOAuth2Server() {
|
|
735
|
+
this.oauth2Server = new OAuth2Server({
|
|
736
|
+
issuer: this.config.issuer,
|
|
737
|
+
keyResource: this.oauth2KeysResource,
|
|
738
|
+
userResource: this.usersResource,
|
|
739
|
+
clientResource: this.clientsResource,
|
|
740
|
+
authCodeResource: this.oauth2AuthCodesResource,
|
|
741
|
+
supportedScopes: this.config.supportedScopes,
|
|
742
|
+
supportedGrantTypes: this.config.supportedGrantTypes,
|
|
743
|
+
supportedResponseTypes: this.config.supportedResponseTypes,
|
|
744
|
+
accessTokenExpiry: this.config.accessTokenExpiry,
|
|
745
|
+
idTokenExpiry: this.config.idTokenExpiry,
|
|
746
|
+
refreshTokenExpiry: this.config.refreshTokenExpiry,
|
|
747
|
+
authCodeExpiry: this.config.authCodeExpiry
|
|
748
|
+
});
|
|
749
|
+
|
|
750
|
+
await this.oauth2Server.initialize();
|
|
751
|
+
|
|
752
|
+
if (this.config.verbose) {
|
|
753
|
+
console.log('[Identity Plugin] OAuth2 Server initialized');
|
|
754
|
+
console.log(`[Identity Plugin] Issuer: ${this.config.issuer}`);
|
|
755
|
+
console.log(`[Identity Plugin] Supported scopes: ${this.config.supportedScopes.join(', ')}`);
|
|
756
|
+
console.log(`[Identity Plugin] Supported grant types: ${this.config.supportedGrantTypes.join(', ')}`);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* Initialize Session Manager
|
|
762
|
+
* @private
|
|
763
|
+
*/
|
|
764
|
+
async _initializeSessionManager() {
|
|
765
|
+
const { SessionManager } = await import('./session-manager.js');
|
|
766
|
+
|
|
767
|
+
this.sessionManager = new SessionManager({
|
|
768
|
+
sessionResource: this.sessionsResource,
|
|
769
|
+
config: this.config.session
|
|
770
|
+
});
|
|
771
|
+
|
|
772
|
+
if (this.config.verbose) {
|
|
773
|
+
console.log('[Identity Plugin] Session Manager initialized');
|
|
774
|
+
console.log(`[Identity Plugin] Session expiry: ${this.config.session.sessionExpiry}`);
|
|
775
|
+
console.log(`[Identity Plugin] Cookie name: ${this.config.session.cookieName}`);
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
/**
|
|
780
|
+
* Initialize email service
|
|
781
|
+
* @private
|
|
782
|
+
*/
|
|
783
|
+
async _initializeEmailService() {
|
|
784
|
+
const { EmailService } = await import('./email-service.js');
|
|
785
|
+
|
|
786
|
+
this.emailService = new EmailService({
|
|
787
|
+
enabled: this.config.email.enabled,
|
|
788
|
+
from: this.config.email.from,
|
|
789
|
+
replyTo: this.config.email.replyTo,
|
|
790
|
+
smtp: this.config.email.smtp,
|
|
791
|
+
templates: this.config.email.templates,
|
|
792
|
+
verbose: this.config.verbose
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
if (this.config.verbose) {
|
|
796
|
+
console.log('[Identity Plugin] Email Service initialized');
|
|
797
|
+
console.log(`[Identity Plugin] Email enabled: ${this.config.email.enabled}`);
|
|
798
|
+
if (this.config.email.enabled) {
|
|
799
|
+
console.log(`[Identity Plugin] SMTP host: ${this.config.email.smtp.host}:${this.config.email.smtp.port}`);
|
|
800
|
+
console.log(`[Identity Plugin] From address: ${this.config.email.from}`);
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
/**
|
|
806
|
+
* Initialize failban manager
|
|
807
|
+
* @private
|
|
808
|
+
*/
|
|
809
|
+
async _initializeFailbanManager() {
|
|
810
|
+
if (!this.config.failban.enabled) {
|
|
811
|
+
if (this.config.verbose) {
|
|
812
|
+
console.log('[Identity Plugin] Failban disabled');
|
|
813
|
+
}
|
|
814
|
+
return;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const { FailbanManager } = await import('../api/concerns/failban-manager.js');
|
|
818
|
+
|
|
819
|
+
this.failbanManager = new FailbanManager({
|
|
820
|
+
database: this.database,
|
|
821
|
+
enabled: this.config.failban.enabled,
|
|
822
|
+
maxViolations: this.config.failban.maxViolations,
|
|
823
|
+
violationWindow: this.config.failban.violationWindow,
|
|
824
|
+
banDuration: this.config.failban.banDuration,
|
|
825
|
+
whitelist: this.config.failban.whitelist,
|
|
826
|
+
blacklist: this.config.failban.blacklist,
|
|
827
|
+
persistViolations: this.config.failban.persistViolations,
|
|
828
|
+
verbose: this.config.verbose,
|
|
829
|
+
geo: this.config.failban.geo
|
|
830
|
+
});
|
|
831
|
+
|
|
832
|
+
await this.failbanManager.initialize();
|
|
833
|
+
|
|
834
|
+
if (this.config.verbose) {
|
|
835
|
+
console.log('[Identity Plugin] Failban Manager initialized');
|
|
836
|
+
console.log(`[Identity Plugin] Max violations: ${this.config.failban.maxViolations}`);
|
|
837
|
+
console.log(`[Identity Plugin] Violation window: ${this.config.failban.violationWindow}ms`);
|
|
838
|
+
console.log(`[Identity Plugin] Ban duration: ${this.config.failban.banDuration}ms`);
|
|
839
|
+
console.log(`[Identity Plugin] Protected endpoints: login=${this.config.failban.endpoints.login}, token=${this.config.failban.endpoints.token}, register=${this.config.failban.endpoints.register}`);
|
|
840
|
+
if (this.config.failban.geo.enabled) {
|
|
841
|
+
console.log(`[Identity Plugin] GeoIP enabled`);
|
|
842
|
+
console.log(`[Identity Plugin] Allowed countries: ${this.config.failban.geo.allowedCountries.join(', ') || 'all'}`);
|
|
843
|
+
console.log(`[Identity Plugin] Blocked countries: ${this.config.failban.geo.blockedCountries.join(', ') || 'none'}`);
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
/**
|
|
849
|
+
* Initialize audit plugin
|
|
850
|
+
* @private
|
|
851
|
+
*/
|
|
852
|
+
async _initializeAuditPlugin() {
|
|
853
|
+
if (!this.config.audit.enabled) {
|
|
854
|
+
if (this.config.verbose) {
|
|
855
|
+
console.log('[Identity Plugin] Audit logging disabled');
|
|
856
|
+
}
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
const { AuditPlugin } = await import('../audit.plugin.js');
|
|
861
|
+
|
|
862
|
+
this.auditPlugin = new AuditPlugin({
|
|
863
|
+
includeData: this.config.audit.includeData,
|
|
864
|
+
includePartitions: this.config.audit.includePartitions,
|
|
865
|
+
maxDataSize: this.config.audit.maxDataSize,
|
|
866
|
+
resources: this.config.audit.resources
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
await this.database.usePlugin(this.auditPlugin);
|
|
870
|
+
|
|
871
|
+
if (this.config.verbose) {
|
|
872
|
+
console.log('[Identity Plugin] Audit Plugin initialized');
|
|
873
|
+
console.log(`[Identity Plugin] Auditing resources: ${this.config.audit.resources.join(', ')}`);
|
|
874
|
+
console.log(`[Identity Plugin] Include data: ${this.config.audit.includeData}`);
|
|
875
|
+
console.log(`[Identity Plugin] Max data size: ${this.config.audit.maxDataSize} bytes`);
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Log custom audit event
|
|
881
|
+
* @param {string} event - Event name
|
|
882
|
+
* @param {Object} data - Event data
|
|
883
|
+
* @private
|
|
884
|
+
*/
|
|
885
|
+
async _logAuditEvent(event, data = {}) {
|
|
886
|
+
if (!this.config.audit.enabled || !this.auditPlugin) {
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (!this.config.audit.events.includes(event)) {
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
try {
|
|
895
|
+
await this.auditPlugin.logCustomEvent(event, data);
|
|
896
|
+
|
|
897
|
+
if (this.config.verbose) {
|
|
898
|
+
console.log(`[Audit] ${event}:`, JSON.stringify(data));
|
|
899
|
+
}
|
|
900
|
+
} catch (error) {
|
|
901
|
+
console.error(`[Audit] Failed to log event ${event}:`, error.message);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Initialize MFA Manager (Multi-Factor Authentication)
|
|
907
|
+
* @private
|
|
908
|
+
*/
|
|
909
|
+
async _initializeMFAManager() {
|
|
910
|
+
if (!this.config.mfa.enabled) {
|
|
911
|
+
if (this.config.verbose) {
|
|
912
|
+
console.log('[Identity Plugin] MFA disabled');
|
|
913
|
+
}
|
|
914
|
+
return;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
const { MFAManager } = await import('./concerns/mfa-manager.js');
|
|
918
|
+
|
|
919
|
+
this.mfaManager = new MFAManager({
|
|
920
|
+
issuer: this.config.mfa.issuer,
|
|
921
|
+
algorithm: this.config.mfa.algorithm,
|
|
922
|
+
digits: this.config.mfa.digits,
|
|
923
|
+
period: this.config.mfa.period,
|
|
924
|
+
window: this.config.mfa.window,
|
|
925
|
+
backupCodesCount: this.config.mfa.backupCodesCount,
|
|
926
|
+
backupCodeLength: this.config.mfa.backupCodeLength
|
|
927
|
+
});
|
|
928
|
+
|
|
929
|
+
await this.mfaManager.initialize();
|
|
930
|
+
|
|
931
|
+
if (this.config.verbose) {
|
|
932
|
+
console.log('[Identity Plugin] MFA Manager initialized');
|
|
933
|
+
console.log(`[Identity Plugin] Issuer: ${this.config.mfa.issuer}`);
|
|
934
|
+
console.log(`[Identity Plugin] Algorithm: ${this.config.mfa.algorithm}`);
|
|
935
|
+
console.log(`[Identity Plugin] Digits: ${this.config.mfa.digits}`);
|
|
936
|
+
console.log(`[Identity Plugin] Period: ${this.config.mfa.period}s`);
|
|
937
|
+
console.log(`[Identity Plugin] Required: ${this.config.mfa.required}`);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Start plugin
|
|
943
|
+
*/
|
|
944
|
+
async onStart() {
|
|
945
|
+
if (this.config.verbose) {
|
|
946
|
+
console.log('[Identity Plugin] Starting server...');
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Dynamic import of server (will create in next step)
|
|
950
|
+
const { IdentityServer } = await import('./server.js');
|
|
951
|
+
|
|
952
|
+
// Create server instance
|
|
953
|
+
this.server = new IdentityServer({
|
|
954
|
+
port: this.config.port,
|
|
955
|
+
host: this.config.host,
|
|
956
|
+
verbose: this.config.verbose,
|
|
957
|
+
issuer: this.config.issuer,
|
|
958
|
+
oauth2Server: this.oauth2Server,
|
|
959
|
+
sessionManager: this.sessionManager,
|
|
960
|
+
usersResource: this.usersResource,
|
|
961
|
+
identityPlugin: this,
|
|
962
|
+
failbanManager: this.failbanManager,
|
|
963
|
+
failbanConfig: this.config.failban,
|
|
964
|
+
accountLockoutConfig: this.config.accountLockout,
|
|
965
|
+
cors: this.config.cors,
|
|
966
|
+
security: this.config.security,
|
|
967
|
+
logging: this.config.logging
|
|
968
|
+
});
|
|
969
|
+
|
|
970
|
+
// Start server
|
|
971
|
+
await this.server.start();
|
|
972
|
+
|
|
973
|
+
this.emit('plugin.started', {
|
|
974
|
+
port: this.config.port,
|
|
975
|
+
host: this.config.host,
|
|
976
|
+
issuer: this.config.issuer
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
/**
|
|
981
|
+
* Stop plugin
|
|
982
|
+
*/
|
|
983
|
+
async onStop() {
|
|
984
|
+
if (this.config.verbose) {
|
|
985
|
+
console.log('[Identity Plugin] Stopping server...');
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
if (this.server) {
|
|
989
|
+
await this.server.stop();
|
|
990
|
+
this.server = null;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
// Stop session cleanup timer
|
|
994
|
+
if (this.sessionManager) {
|
|
995
|
+
this.sessionManager.stopCleanup();
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Close email service connection
|
|
999
|
+
if (this.emailService) {
|
|
1000
|
+
await this.emailService.close();
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
// Cleanup failban manager
|
|
1004
|
+
if (this.failbanManager) {
|
|
1005
|
+
await this.failbanManager.cleanup();
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
this.emit('plugin.stopped');
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
/**
|
|
1012
|
+
* Uninstall plugin
|
|
1013
|
+
*/
|
|
1014
|
+
async onUninstall(options = {}) {
|
|
1015
|
+
const { purgeData = false } = options;
|
|
1016
|
+
|
|
1017
|
+
// Stop server if running
|
|
1018
|
+
await this.onStop();
|
|
1019
|
+
|
|
1020
|
+
// Optionally delete OAuth2 resources
|
|
1021
|
+
if (purgeData) {
|
|
1022
|
+
const resourcesToDelete = ['plg_oauth_keys', 'plg_oauth_clients', 'plg_auth_codes'];
|
|
1023
|
+
|
|
1024
|
+
for (const resourceName of resourcesToDelete) {
|
|
1025
|
+
const [ok] = await tryFn(() => this.database.deleteResource(resourceName));
|
|
1026
|
+
if (ok && this.config.verbose) {
|
|
1027
|
+
console.log(`[Identity Plugin] Deleted ${resourceName} resource`);
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
if (this.config.verbose) {
|
|
1033
|
+
console.log('[Identity Plugin] Uninstalled successfully');
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
/**
|
|
1038
|
+
* Get server information
|
|
1039
|
+
* @returns {Object} Server info
|
|
1040
|
+
*/
|
|
1041
|
+
getServerInfo() {
|
|
1042
|
+
return this.server ? this.server.getInfo() : { isRunning: false };
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
/**
|
|
1046
|
+
* Get OAuth2 Server instance (for advanced usage)
|
|
1047
|
+
* @returns {OAuth2Server|null}
|
|
1048
|
+
*/
|
|
1049
|
+
getOAuth2Server() {
|
|
1050
|
+
return this.oauth2Server;
|
|
1051
|
+
}
|
|
1052
|
+
}
|