s3db.js 13.5.1 → 13.6.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 +89 -19
- package/dist/{s3db.cjs.js → s3db.cjs} +29780 -24384
- package/dist/s3db.cjs.map +1 -0
- package/dist/s3db.es.js +24263 -18860
- package/dist/s3db.es.js.map +1 -1
- package/package.json +227 -21
- 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/concerns/opengraph-helper.js +116 -0
- package/src/plugins/api/concerns/state-machine.js +288 -0
- package/src/plugins/api/index.js +514 -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 +262 -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/plugins/tfstate/README.md +126 -126
- 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
|
@@ -107,6 +107,60 @@ export const PLUGIN_DEPENDENCIES = {
|
|
|
107
107
|
description: 'Swagger UI integration for Hono',
|
|
108
108
|
installCommand: 'pnpm add @hono/swagger-ui',
|
|
109
109
|
npmUrl: 'https://www.npmjs.com/package/@hono/swagger-ui'
|
|
110
|
+
},
|
|
111
|
+
'jose': {
|
|
112
|
+
version: '^5.0.0 || ^6.0.0',
|
|
113
|
+
description: 'Universal JOSE and JWE implementation (for OAuth2 token validation)',
|
|
114
|
+
installCommand: 'pnpm add jose',
|
|
115
|
+
npmUrl: 'https://www.npmjs.com/package/jose'
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
'identity-plugin': {
|
|
120
|
+
name: 'Identity Provider Plugin',
|
|
121
|
+
docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/identity.md',
|
|
122
|
+
dependencies: {
|
|
123
|
+
'hono': {
|
|
124
|
+
version: '^4.0.0',
|
|
125
|
+
description: 'Ultra-light HTTP server framework',
|
|
126
|
+
installCommand: 'pnpm add hono',
|
|
127
|
+
npmUrl: 'https://www.npmjs.com/package/hono'
|
|
128
|
+
},
|
|
129
|
+
'@hono/node-server': {
|
|
130
|
+
version: '^1.0.0',
|
|
131
|
+
description: 'Node.js adapter for Hono',
|
|
132
|
+
installCommand: 'pnpm add @hono/node-server',
|
|
133
|
+
npmUrl: 'https://www.npmjs.com/package/@hono/node-server'
|
|
134
|
+
},
|
|
135
|
+
'jose': {
|
|
136
|
+
version: '^5.0.0 || ^6.0.0',
|
|
137
|
+
description: 'Universal JOSE and JWE implementation (for RSA key generation and JWT signing)',
|
|
138
|
+
installCommand: 'pnpm add jose',
|
|
139
|
+
npmUrl: 'https://www.npmjs.com/package/jose'
|
|
140
|
+
},
|
|
141
|
+
'bcrypt': {
|
|
142
|
+
version: '^5.1.0 || ^6.0.0',
|
|
143
|
+
description: 'Secure password hashing library',
|
|
144
|
+
installCommand: 'pnpm add bcrypt',
|
|
145
|
+
npmUrl: 'https://www.npmjs.com/package/bcrypt'
|
|
146
|
+
},
|
|
147
|
+
'nodemailer': {
|
|
148
|
+
version: '^6.9.0 || ^7.0.0',
|
|
149
|
+
description: 'Email sending library for password reset and verification',
|
|
150
|
+
installCommand: 'pnpm add nodemailer',
|
|
151
|
+
npmUrl: 'https://www.npmjs.com/package/nodemailer'
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
'cloud-inventory-plugin': {
|
|
156
|
+
name: 'Cloud Inventory Plugin',
|
|
157
|
+
docsUrl: 'https://github.com/forattini-dev/s3db.js/blob/main/docs/plugins/cloud-inventory.md',
|
|
158
|
+
dependencies: {
|
|
159
|
+
'node-cron': {
|
|
160
|
+
version: '^4.0.0',
|
|
161
|
+
description: 'Cron scheduler for automated discovery',
|
|
162
|
+
installCommand: 'pnpm add -D node-cron',
|
|
163
|
+
npmUrl: 'https://www.npmjs.com/package/node-cron'
|
|
110
164
|
}
|
|
111
165
|
}
|
|
112
166
|
},
|
|
@@ -127,12 +181,18 @@ export const PLUGIN_DEPENDENCIES = {
|
|
|
127
181
|
/**
|
|
128
182
|
* Simple semver comparison for major version checking
|
|
129
183
|
* @param {string} actual - Actual version (e.g., "8.11.3")
|
|
130
|
-
* @param {string} required - Required version range (e.g., "^8.0.0")
|
|
184
|
+
* @param {string} required - Required version range (e.g., "^8.0.0" or "^5.0.0 || ^6.0.0")
|
|
131
185
|
* @returns {boolean} True if version is compatible
|
|
132
186
|
*/
|
|
133
187
|
function isVersionCompatible(actual, required) {
|
|
134
188
|
if (!actual || !required) return false;
|
|
135
189
|
|
|
190
|
+
// Handle OR operators (||)
|
|
191
|
+
if (required.includes('||')) {
|
|
192
|
+
const ranges = required.split('||').map(r => r.trim());
|
|
193
|
+
return ranges.some(range => isVersionCompatible(actual, range));
|
|
194
|
+
}
|
|
195
|
+
|
|
136
196
|
// Remove ^ and ~ prefixes
|
|
137
197
|
const cleanRequired = required.replace(/^[\^~]/, '');
|
|
138
198
|
|
|
@@ -1196,6 +1196,7 @@ export async function getLastNMonths(resourceName, field, months = 12, options,
|
|
|
1196
1196
|
const dataMap = new Map(data.map(d => [d.cohort, d]));
|
|
1197
1197
|
|
|
1198
1198
|
const current = new Date(monthsAgo);
|
|
1199
|
+
current.setDate(1); // Set to 1st of month to avoid day overflow bugs
|
|
1199
1200
|
for (let i = 0; i < months; i++) {
|
|
1200
1201
|
const cohort = current.toISOString().substring(0, 7);
|
|
1201
1202
|
result.push(dataMap.get(cohort) || { cohort, ...emptyRecord });
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Identity Provider Plugin
|
|
2
|
+
|
|
3
|
+
Complete OAuth2/OpenID Connect Identity Provider for S3DB.
|
|
4
|
+
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
import { Database } from 's3db.js';
|
|
9
|
+
import { IdentityPlugin } from 's3db.js/plugins/identity';
|
|
10
|
+
|
|
11
|
+
const db = new Database({
|
|
12
|
+
connectionString: 'http://minioadmin:minioadmin@localhost:9000/myapp'
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
await db.initialize();
|
|
16
|
+
|
|
17
|
+
const identityPlugin = new IdentityPlugin({
|
|
18
|
+
issuer: 'http://localhost:4000',
|
|
19
|
+
database: db
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
await identityPlugin.initialize();
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Access at: http://localhost:4000/login
|
|
26
|
+
|
|
27
|
+
## Features
|
|
28
|
+
|
|
29
|
+
✅ **Complete Authentication System**
|
|
30
|
+
- Login/Logout with session management
|
|
31
|
+
- User registration with email verification
|
|
32
|
+
- Password reset flow
|
|
33
|
+
- Profile management
|
|
34
|
+
|
|
35
|
+
✅ **OAuth2/OpenID Connect Server**
|
|
36
|
+
- Authorization code flow with PKCE
|
|
37
|
+
- Client credentials flow
|
|
38
|
+
- Refresh token support
|
|
39
|
+
- Full OIDC compliance
|
|
40
|
+
|
|
41
|
+
✅ **Admin Panel**
|
|
42
|
+
- User management (CRUD, status, roles)
|
|
43
|
+
- OAuth2 client management
|
|
44
|
+
- Session monitoring
|
|
45
|
+
|
|
46
|
+
✅ **100% White-Label UI**
|
|
47
|
+
- 30+ theme customization options (colors, fonts, logos)
|
|
48
|
+
- **Custom CSS injection** - Poder total para customização
|
|
49
|
+
- Tailwind 4 CDN - Classes utilitárias prontas
|
|
50
|
+
- Custom page overrides
|
|
51
|
+
- Responsive design
|
|
52
|
+
- Social media integration
|
|
53
|
+
|
|
54
|
+
✅ **Registration Controls**
|
|
55
|
+
- Enable/disable public registration
|
|
56
|
+
- Email domain whitelist/blacklist
|
|
57
|
+
- Email verification requirement
|
|
58
|
+
|
|
59
|
+
✅ **Security**
|
|
60
|
+
- bcrypt password hashing
|
|
61
|
+
- Configurable password policy
|
|
62
|
+
- Session management with device tracking
|
|
63
|
+
- CSRF protection
|
|
64
|
+
|
|
65
|
+
## Documentation
|
|
66
|
+
|
|
67
|
+
- **[Complete Documentation](../../../docs/plugins/identity-plugin.md)** - Full guide with all features
|
|
68
|
+
- **[Configuration Reference](../../../docs/plugins/identity-config-reference.md)** - All configuration options
|
|
69
|
+
- **[White-Label Guide](../../../docs/plugins/identity/WHITELABEL.md)** - 🎨 Complete branding customization guide
|
|
70
|
+
- **[Examples Index](../../../docs/plugins/identity-examples.md)** - Example code and use cases
|
|
71
|
+
|
|
72
|
+
## Examples
|
|
73
|
+
|
|
74
|
+
| Example | Description |
|
|
75
|
+
|---------|-------------|
|
|
76
|
+
| `e85-identity-whitelabel.js` | S3dbCorp complete white-label branding |
|
|
77
|
+
| `e86-custom-login-page.js` | Custom login page with HTML override |
|
|
78
|
+
| `e87-identity-no-registration.js` | Disabled public registration |
|
|
79
|
+
|
|
80
|
+
Run examples:
|
|
81
|
+
```bash
|
|
82
|
+
node docs/examples/e85-identity-whitelabel.js
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Basic Configuration
|
|
86
|
+
|
|
87
|
+
### Minimal Setup
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
new IdentityPlugin({
|
|
91
|
+
issuer: 'http://localhost:4000',
|
|
92
|
+
database: db
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Production Setup
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
new IdentityPlugin({
|
|
100
|
+
issuer: 'https://auth.company.com',
|
|
101
|
+
database: db,
|
|
102
|
+
|
|
103
|
+
registration: {
|
|
104
|
+
enabled: true,
|
|
105
|
+
requireEmailVerification: true,
|
|
106
|
+
allowedDomains: ['company.com']
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
passwordPolicy: {
|
|
110
|
+
minLength: 12,
|
|
111
|
+
requireSymbols: true,
|
|
112
|
+
bcryptRounds: 12
|
|
113
|
+
},
|
|
114
|
+
|
|
115
|
+
session: {
|
|
116
|
+
sessionExpiry: '8h',
|
|
117
|
+
cookieSecure: true,
|
|
118
|
+
cookieSameSite: 'Strict'
|
|
119
|
+
},
|
|
120
|
+
|
|
121
|
+
email: {
|
|
122
|
+
enabled: true,
|
|
123
|
+
smtp: {
|
|
124
|
+
host: process.env.SMTP_HOST,
|
|
125
|
+
port: 587,
|
|
126
|
+
auth: {
|
|
127
|
+
user: process.env.SMTP_USER,
|
|
128
|
+
pass: process.env.SMTP_PASS
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
|
|
133
|
+
ui: {
|
|
134
|
+
companyName: 'My Company',
|
|
135
|
+
primaryColor: '#0066CC',
|
|
136
|
+
logoUrl: 'https://company.com/logo.svg'
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Access Points
|
|
142
|
+
|
|
143
|
+
After starting the server:
|
|
144
|
+
|
|
145
|
+
- **Login**: `/login`
|
|
146
|
+
- **Register**: `/register`
|
|
147
|
+
- **Profile**: `/profile` (requires login)
|
|
148
|
+
- **Admin**: `/admin` (requires admin role)
|
|
149
|
+
- **OIDC Discovery**: `/.well-known/openid-configuration`
|
|
150
|
+
- **JWKS**: `/.well-known/jwks.json`
|
|
151
|
+
|
|
152
|
+
## OAuth2 Endpoints
|
|
153
|
+
|
|
154
|
+
- `GET /oauth/authorize` - Authorization (consent screen)
|
|
155
|
+
- `POST /oauth/token` - Token exchange
|
|
156
|
+
- `GET /oauth/userinfo` - OIDC UserInfo
|
|
157
|
+
- `POST /oauth/introspect` - Token introspection
|
|
158
|
+
- `POST /oauth/revoke` - Token revocation
|
|
159
|
+
- `POST /oauth/register` - Client registration
|
|
160
|
+
|
|
161
|
+
## Admin Panel
|
|
162
|
+
|
|
163
|
+
Create admin user:
|
|
164
|
+
|
|
165
|
+
```javascript
|
|
166
|
+
const usersResource = db.resources.users;
|
|
167
|
+
|
|
168
|
+
await usersResource.insert({
|
|
169
|
+
email: 'admin@example.com',
|
|
170
|
+
name: 'Admin User',
|
|
171
|
+
passwordHash: await hashPassword('SecurePass123!'),
|
|
172
|
+
status: 'active',
|
|
173
|
+
emailVerified: true,
|
|
174
|
+
role: 'admin'
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Access admin panel at `/admin` after login.
|
|
179
|
+
|
|
180
|
+
## White-Label Customization
|
|
181
|
+
|
|
182
|
+
### Basic Branding
|
|
183
|
+
|
|
184
|
+
```javascript
|
|
185
|
+
ui: {
|
|
186
|
+
companyName: 'Acme Corp',
|
|
187
|
+
tagline: 'Secure Cloud Solutions',
|
|
188
|
+
logoUrl: 'https://acme.com/logo.svg',
|
|
189
|
+
primaryColor: '#ff6600',
|
|
190
|
+
supportEmail: 'support@acme.com'
|
|
191
|
+
}
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Custom Pages
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
import { html } from 'hono/html';
|
|
198
|
+
|
|
199
|
+
function MyCustomLoginPage(props) {
|
|
200
|
+
const { error, email, config } = props;
|
|
201
|
+
|
|
202
|
+
return html`<!DOCTYPE html>
|
|
203
|
+
<html>
|
|
204
|
+
<head><title>Login - ${config.companyName}</title></head>
|
|
205
|
+
<body>
|
|
206
|
+
<form method="POST" action="/login">
|
|
207
|
+
<input type="email" name="email" value="${email}" />
|
|
208
|
+
<input type="password" name="password" />
|
|
209
|
+
<button>Sign In</button>
|
|
210
|
+
</form>
|
|
211
|
+
</body>
|
|
212
|
+
</html>`;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
ui: {
|
|
216
|
+
customPages: {
|
|
217
|
+
login: MyCustomLoginPage
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Registration Controls
|
|
223
|
+
|
|
224
|
+
### Disable Public Registration
|
|
225
|
+
|
|
226
|
+
```javascript
|
|
227
|
+
registration: {
|
|
228
|
+
enabled: false,
|
|
229
|
+
customMessage: 'Contact admin for access'
|
|
230
|
+
}
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Corporate Emails Only
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
registration: {
|
|
237
|
+
enabled: true,
|
|
238
|
+
allowedDomains: ['company.com', 'partner.com']
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Block Temporary Emails
|
|
243
|
+
|
|
244
|
+
```javascript
|
|
245
|
+
registration: {
|
|
246
|
+
enabled: true,
|
|
247
|
+
blockedDomains: ['tempmail.com', 'guerrillamail.com']
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
## Architecture
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
Identity Plugin
|
|
255
|
+
├── OAuth2/OIDC Server (authorization, tokens, PKCE)
|
|
256
|
+
├── UI Pages (login, register, profile, admin)
|
|
257
|
+
├── Session Manager (cookie-based sessions)
|
|
258
|
+
├── Email Service (SMTP integration)
|
|
259
|
+
└── S3DB Resources
|
|
260
|
+
├── plg_oauth_keys (RSA keys)
|
|
261
|
+
├── plg_oauth_clients (registered clients)
|
|
262
|
+
├── plg_auth_codes (authorization codes)
|
|
263
|
+
├── plg_sessions (user sessions)
|
|
264
|
+
├── plg_password_reset_tokens (reset tokens)
|
|
265
|
+
└── users (user accounts)
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## Security Best Practices
|
|
269
|
+
|
|
270
|
+
1. **Use HTTPS in production**
|
|
271
|
+
```javascript
|
|
272
|
+
session: { cookieSecure: true },
|
|
273
|
+
server: { security: { hsts: true } }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
2. **Strong password policy**
|
|
277
|
+
```javascript
|
|
278
|
+
passwordPolicy: {
|
|
279
|
+
minLength: 12,
|
|
280
|
+
requireSymbols: true,
|
|
281
|
+
bcryptRounds: 12
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
3. **Email verification required**
|
|
286
|
+
```javascript
|
|
287
|
+
registration: { requireEmailVerification: true }
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
4. **Restrict origins**
|
|
291
|
+
```javascript
|
|
292
|
+
server: {
|
|
293
|
+
cors: { origin: ['https://app.company.com'] }
|
|
294
|
+
}
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Troubleshooting
|
|
298
|
+
|
|
299
|
+
### Session not persisting
|
|
300
|
+
|
|
301
|
+
Check cookie settings match deployment (HTTP vs HTTPS):
|
|
302
|
+
```javascript
|
|
303
|
+
session: {
|
|
304
|
+
cookieSecure: false, // Development (HTTP)
|
|
305
|
+
// cookieSecure: true, // Production (HTTPS)
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Email not sending
|
|
310
|
+
|
|
311
|
+
Test SMTP connection:
|
|
312
|
+
```bash
|
|
313
|
+
# Use MailHog for testing
|
|
314
|
+
docker run -d -p 1025:1025 -p 8025:8025 mailhog/mailhog
|
|
315
|
+
|
|
316
|
+
SMTP_HOST=localhost SMTP_PORT=1025 node your-app.js
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Admin panel not accessible
|
|
320
|
+
|
|
321
|
+
User needs admin role:
|
|
322
|
+
```javascript
|
|
323
|
+
await usersResource.update(userId, { role: 'admin' });
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## Resources
|
|
327
|
+
|
|
328
|
+
- [Full Documentation](../../../docs/plugins/identity-plugin.md)
|
|
329
|
+
- [Configuration Reference](../../../docs/plugins/identity-config-reference.md)
|
|
330
|
+
- [Examples](../../../docs/plugins/identity-examples.md)
|
|
331
|
+
- [S3DB Documentation](../../../README.md)
|
|
332
|
+
|
|
333
|
+
## License
|
|
334
|
+
|
|
335
|
+
MIT
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MFA Manager - Multi-Factor Authentication for Identity Plugin
|
|
3
|
+
*
|
|
4
|
+
* Handles TOTP (Time-based One-Time Password) generation, verification,
|
|
5
|
+
* and backup codes management.
|
|
6
|
+
*
|
|
7
|
+
* Compatible with: Google Authenticator, Authy, Microsoft Authenticator, 1Password
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* import { MFAManager } from './concerns/mfa-manager.js';
|
|
11
|
+
*
|
|
12
|
+
* const mfaManager = new MFAManager({
|
|
13
|
+
* issuer: 'MyApp',
|
|
14
|
+
* algorithm: 'SHA1',
|
|
15
|
+
* digits: 6,
|
|
16
|
+
* period: 30
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* // Enroll user
|
|
20
|
+
* const enrollment = await mfaManager.generateEnrollment('user@example.com');
|
|
21
|
+
* console.log(enrollment.qrCodeUrl); // Display QR code
|
|
22
|
+
* console.log(enrollment.secret); // Manual entry key
|
|
23
|
+
*
|
|
24
|
+
* // Verify TOTP token
|
|
25
|
+
* const isValid = mfaManager.verifyTOTP(enrollment.secret, '123456');
|
|
26
|
+
*
|
|
27
|
+
* // Generate backup codes
|
|
28
|
+
* const backupCodes = mfaManager.generateBackupCodes(10);
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import { requirePluginDependency } from '../../concerns/plugin-dependencies.js';
|
|
32
|
+
import { idGenerator } from '../../../concerns/id.js';
|
|
33
|
+
|
|
34
|
+
export class MFAManager {
|
|
35
|
+
constructor(options = {}) {
|
|
36
|
+
this.options = {
|
|
37
|
+
issuer: options.issuer || 'S3DB Identity',
|
|
38
|
+
algorithm: options.algorithm || 'SHA1', // SHA1, SHA256, SHA512
|
|
39
|
+
digits: options.digits || 6, // 6 or 8 digits
|
|
40
|
+
period: options.period || 30, // 30 seconds
|
|
41
|
+
window: options.window || 1, // Allow ±1 time step (90s total)
|
|
42
|
+
backupCodesCount: options.backupCodesCount || 10,
|
|
43
|
+
backupCodeLength: options.backupCodeLength || 8
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
this.OTPAuth = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Initialize MFA Manager (load otpauth library)
|
|
51
|
+
*/
|
|
52
|
+
async initialize() {
|
|
53
|
+
this.OTPAuth = await requirePluginDependency(
|
|
54
|
+
'otpauth',
|
|
55
|
+
'IdentityPlugin (MFA)',
|
|
56
|
+
'Multi-Factor Authentication'
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Generate MFA enrollment data for a user
|
|
62
|
+
* @param {string} accountName - User email or username
|
|
63
|
+
* @returns {Object} Enrollment data with secret, QR code URL, and backup codes
|
|
64
|
+
*/
|
|
65
|
+
generateEnrollment(accountName) {
|
|
66
|
+
if (!this.OTPAuth) {
|
|
67
|
+
throw new Error('[MFA] OTPAuth library not initialized');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Generate TOTP secret
|
|
71
|
+
const totp = new this.OTPAuth.TOTP({
|
|
72
|
+
issuer: this.options.issuer,
|
|
73
|
+
label: accountName,
|
|
74
|
+
algorithm: this.options.algorithm,
|
|
75
|
+
digits: this.options.digits,
|
|
76
|
+
period: this.options.period,
|
|
77
|
+
secret: this.OTPAuth.Secret.fromBase32(
|
|
78
|
+
this.OTPAuth.Secret.generate().base32
|
|
79
|
+
)
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Generate QR code URL
|
|
83
|
+
const qrCodeUrl = totp.toString();
|
|
84
|
+
|
|
85
|
+
// Generate backup codes
|
|
86
|
+
const backupCodes = this.generateBackupCodes(this.options.backupCodesCount);
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
secret: totp.secret.base32, // For manual entry
|
|
90
|
+
qrCodeUrl, // For QR code scanning
|
|
91
|
+
backupCodes, // Emergency access codes
|
|
92
|
+
algorithm: this.options.algorithm,
|
|
93
|
+
digits: this.options.digits,
|
|
94
|
+
period: this.options.period
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Verify a TOTP token
|
|
100
|
+
* @param {string} secret - Base32 encoded secret
|
|
101
|
+
* @param {string} token - 6-digit token from authenticator app
|
|
102
|
+
* @returns {boolean} True if valid
|
|
103
|
+
*/
|
|
104
|
+
verifyTOTP(secret, token) {
|
|
105
|
+
if (!this.OTPAuth) {
|
|
106
|
+
throw new Error('[MFA] OTPAuth library not initialized');
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const totp = new this.OTPAuth.TOTP({
|
|
111
|
+
issuer: this.options.issuer,
|
|
112
|
+
algorithm: this.options.algorithm,
|
|
113
|
+
digits: this.options.digits,
|
|
114
|
+
period: this.options.period,
|
|
115
|
+
secret: this.OTPAuth.Secret.fromBase32(secret)
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Validate token with time window
|
|
119
|
+
const delta = totp.validate({
|
|
120
|
+
token,
|
|
121
|
+
window: this.options.window
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// delta is null if invalid, or number if valid
|
|
125
|
+
return delta !== null;
|
|
126
|
+
} catch (error) {
|
|
127
|
+
console.error('[MFA] TOTP verification error:', error.message);
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Generate backup codes for emergency access
|
|
134
|
+
* @param {number} count - Number of codes to generate
|
|
135
|
+
* @returns {Array<string>} Array of backup codes
|
|
136
|
+
*/
|
|
137
|
+
generateBackupCodes(count = 10) {
|
|
138
|
+
const codes = [];
|
|
139
|
+
const length = this.options.backupCodeLength;
|
|
140
|
+
|
|
141
|
+
for (let i = 0; i < count; i++) {
|
|
142
|
+
// Generate random alphanumeric code
|
|
143
|
+
const code = idGenerator()
|
|
144
|
+
.replace(/[^a-zA-Z0-9]/g, '')
|
|
145
|
+
.substring(0, length)
|
|
146
|
+
.toUpperCase();
|
|
147
|
+
|
|
148
|
+
codes.push(code);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return codes;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Hash backup codes for storage
|
|
156
|
+
* @param {Array<string>} codes - Backup codes
|
|
157
|
+
* @returns {Array<string>} Hashed codes
|
|
158
|
+
*/
|
|
159
|
+
async hashBackupCodes(codes) {
|
|
160
|
+
const crypto = await import('crypto');
|
|
161
|
+
return codes.map(code => {
|
|
162
|
+
return crypto.createHash('sha256')
|
|
163
|
+
.update(code)
|
|
164
|
+
.digest('hex');
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Verify a backup code
|
|
170
|
+
* @param {string} code - Backup code to verify
|
|
171
|
+
* @param {Array<string>} hashedCodes - Array of hashed backup codes
|
|
172
|
+
* @returns {number|null} Index of matched code, or null if not found
|
|
173
|
+
*/
|
|
174
|
+
async verifyBackupCode(code, hashedCodes) {
|
|
175
|
+
const crypto = await import('crypto');
|
|
176
|
+
const hashedInput = crypto.createHash('sha256')
|
|
177
|
+
.update(code.toUpperCase())
|
|
178
|
+
.digest('hex');
|
|
179
|
+
|
|
180
|
+
return hashedCodes.findIndex(hash => hash === hashedInput);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Generate QR code data URL for display
|
|
185
|
+
* @param {string} qrCodeUrl - OTP auth URL
|
|
186
|
+
* @returns {Promise<string>} Data URL for QR code image
|
|
187
|
+
*/
|
|
188
|
+
async generateQRCodeDataURL(qrCodeUrl) {
|
|
189
|
+
try {
|
|
190
|
+
const QRCode = await requirePluginDependency(
|
|
191
|
+
'qrcode',
|
|
192
|
+
'IdentityPlugin (MFA)',
|
|
193
|
+
'QR code generation for MFA enrollment'
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
return await QRCode.toDataURL(qrCodeUrl);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error('[MFA] QR code generation error:', error.message);
|
|
199
|
+
return null;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default MFAManager;
|