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.
Files changed (110) hide show
  1. package/README.md +25 -10
  2. package/dist/{s3db.cjs.js → s3db.cjs} +38801 -32446
  3. package/dist/s3db.cjs.map +1 -0
  4. package/dist/s3db.es.js +38653 -32291
  5. package/dist/s3db.es.js.map +1 -1
  6. package/package.json +218 -22
  7. package/src/concerns/id.js +90 -6
  8. package/src/concerns/index.js +2 -1
  9. package/src/concerns/password-hashing.js +150 -0
  10. package/src/database.class.js +6 -2
  11. package/src/plugins/api/auth/basic-auth.js +40 -10
  12. package/src/plugins/api/auth/index.js +49 -3
  13. package/src/plugins/api/auth/oauth2-auth.js +171 -0
  14. package/src/plugins/api/auth/oidc-auth.js +789 -0
  15. package/src/plugins/api/auth/oidc-client.js +462 -0
  16. package/src/plugins/api/auth/path-auth-matcher.js +284 -0
  17. package/src/plugins/api/concerns/event-emitter.js +134 -0
  18. package/src/plugins/api/concerns/failban-manager.js +651 -0
  19. package/src/plugins/api/concerns/guards-helpers.js +402 -0
  20. package/src/plugins/api/concerns/metrics-collector.js +346 -0
  21. package/src/plugins/api/index.js +510 -57
  22. package/src/plugins/api/middlewares/failban.js +305 -0
  23. package/src/plugins/api/middlewares/rate-limit.js +301 -0
  24. package/src/plugins/api/middlewares/request-id.js +74 -0
  25. package/src/plugins/api/middlewares/security-headers.js +120 -0
  26. package/src/plugins/api/middlewares/session-tracking.js +194 -0
  27. package/src/plugins/api/routes/auth-routes.js +119 -78
  28. package/src/plugins/api/routes/resource-routes.js +73 -30
  29. package/src/plugins/api/server.js +1139 -45
  30. package/src/plugins/api/utils/custom-routes.js +102 -0
  31. package/src/plugins/api/utils/guards.js +213 -0
  32. package/src/plugins/api/utils/mime-types.js +154 -0
  33. package/src/plugins/api/utils/openapi-generator.js +91 -12
  34. package/src/plugins/api/utils/path-matcher.js +173 -0
  35. package/src/plugins/api/utils/static-filesystem.js +262 -0
  36. package/src/plugins/api/utils/static-s3.js +231 -0
  37. package/src/plugins/api/utils/template-engine.js +188 -0
  38. package/src/plugins/cloud-inventory/drivers/alibaba-driver.js +853 -0
  39. package/src/plugins/cloud-inventory/drivers/aws-driver.js +2554 -0
  40. package/src/plugins/cloud-inventory/drivers/azure-driver.js +637 -0
  41. package/src/plugins/cloud-inventory/drivers/base-driver.js +99 -0
  42. package/src/plugins/cloud-inventory/drivers/cloudflare-driver.js +620 -0
  43. package/src/plugins/cloud-inventory/drivers/digitalocean-driver.js +698 -0
  44. package/src/plugins/cloud-inventory/drivers/gcp-driver.js +645 -0
  45. package/src/plugins/cloud-inventory/drivers/hetzner-driver.js +559 -0
  46. package/src/plugins/cloud-inventory/drivers/linode-driver.js +614 -0
  47. package/src/plugins/cloud-inventory/drivers/mock-drivers.js +449 -0
  48. package/src/plugins/cloud-inventory/drivers/mongodb-atlas-driver.js +771 -0
  49. package/src/plugins/cloud-inventory/drivers/oracle-driver.js +768 -0
  50. package/src/plugins/cloud-inventory/drivers/vultr-driver.js +636 -0
  51. package/src/plugins/cloud-inventory/index.js +20 -0
  52. package/src/plugins/cloud-inventory/registry.js +146 -0
  53. package/src/plugins/cloud-inventory/terraform-exporter.js +362 -0
  54. package/src/plugins/cloud-inventory.plugin.js +1333 -0
  55. package/src/plugins/concerns/plugin-dependencies.js +62 -2
  56. package/src/plugins/eventual-consistency/analytics.js +1 -0
  57. package/src/plugins/eventual-consistency/consolidation.js +2 -2
  58. package/src/plugins/eventual-consistency/garbage-collection.js +2 -2
  59. package/src/plugins/eventual-consistency/install.js +2 -2
  60. package/src/plugins/identity/README.md +335 -0
  61. package/src/plugins/identity/concerns/mfa-manager.js +204 -0
  62. package/src/plugins/identity/concerns/password.js +138 -0
  63. package/src/plugins/identity/concerns/resource-schemas.js +273 -0
  64. package/src/plugins/identity/concerns/token-generator.js +172 -0
  65. package/src/plugins/identity/email-service.js +422 -0
  66. package/src/plugins/identity/index.js +1052 -0
  67. package/src/plugins/identity/oauth2-server.js +1033 -0
  68. package/src/plugins/identity/oidc-discovery.js +285 -0
  69. package/src/plugins/identity/rsa-keys.js +323 -0
  70. package/src/plugins/identity/server.js +500 -0
  71. package/src/plugins/identity/session-manager.js +453 -0
  72. package/src/plugins/identity/ui/layouts/base.js +251 -0
  73. package/src/plugins/identity/ui/middleware.js +135 -0
  74. package/src/plugins/identity/ui/pages/admin/client-form.js +247 -0
  75. package/src/plugins/identity/ui/pages/admin/clients.js +179 -0
  76. package/src/plugins/identity/ui/pages/admin/dashboard.js +181 -0
  77. package/src/plugins/identity/ui/pages/admin/user-form.js +283 -0
  78. package/src/plugins/identity/ui/pages/admin/users.js +263 -0
  79. package/src/plugins/identity/ui/pages/consent.js +262 -0
  80. package/src/plugins/identity/ui/pages/forgot-password.js +104 -0
  81. package/src/plugins/identity/ui/pages/login.js +144 -0
  82. package/src/plugins/identity/ui/pages/mfa-backup-codes.js +180 -0
  83. package/src/plugins/identity/ui/pages/mfa-enrollment.js +187 -0
  84. package/src/plugins/identity/ui/pages/mfa-verification.js +178 -0
  85. package/src/plugins/identity/ui/pages/oauth-error.js +225 -0
  86. package/src/plugins/identity/ui/pages/profile.js +361 -0
  87. package/src/plugins/identity/ui/pages/register.js +226 -0
  88. package/src/plugins/identity/ui/pages/reset-password.js +128 -0
  89. package/src/plugins/identity/ui/pages/verify-email.js +172 -0
  90. package/src/plugins/identity/ui/routes.js +2541 -0
  91. package/src/plugins/identity/ui/styles/main.css +465 -0
  92. package/src/plugins/index.js +4 -1
  93. package/src/plugins/ml/base-model.class.js +65 -16
  94. package/src/plugins/ml/classification-model.class.js +1 -1
  95. package/src/plugins/ml/timeseries-model.class.js +3 -1
  96. package/src/plugins/ml.plugin.js +584 -31
  97. package/src/plugins/shared/error-handler.js +147 -0
  98. package/src/plugins/shared/index.js +9 -0
  99. package/src/plugins/shared/middlewares/compression.js +117 -0
  100. package/src/plugins/shared/middlewares/cors.js +49 -0
  101. package/src/plugins/shared/middlewares/index.js +11 -0
  102. package/src/plugins/shared/middlewares/logging.js +54 -0
  103. package/src/plugins/shared/middlewares/rate-limit.js +73 -0
  104. package/src/plugins/shared/middlewares/security.js +158 -0
  105. package/src/plugins/shared/response-formatter.js +264 -0
  106. package/src/plugins/state-machine.plugin.js +57 -2
  107. package/src/resource.class.js +140 -12
  108. package/src/schema.class.js +30 -1
  109. package/src/validator.class.js +57 -6
  110. package/dist/s3db.cjs.js.map +0 -1
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Registration Page
3
+ */
4
+
5
+ import { html } from 'hono/html';
6
+ import { BaseLayout } from '../layouts/base.js';
7
+
8
+ /**
9
+ * Render registration page
10
+ * @param {Object} props - Page properties
11
+ * @param {string} [props.error] - Error message
12
+ * @param {string} [props.email] - Pre-filled email
13
+ * @param {string} [props.name] - Pre-filled name
14
+ * @param {Object} [props.passwordPolicy] - Password policy configuration
15
+ * @param {Object} [props.config] - UI configuration
16
+ * @returns {string} HTML string
17
+ */
18
+ export function RegisterPage(props = {}) {
19
+ const { error = null, email = '', name = '', passwordPolicy = {}, config = {} } = props;
20
+
21
+ const companyName = config.companyName || 'S3DB';
22
+
23
+ // Extract password policy
24
+ const minLength = passwordPolicy.minLength || 8;
25
+ const maxLength = passwordPolicy.maxLength || 128;
26
+ const requireUppercase = passwordPolicy.requireUppercase !== false;
27
+ const requireLowercase = passwordPolicy.requireLowercase !== false;
28
+ const requireNumbers = passwordPolicy.requireNumbers !== false;
29
+ const requireSymbols = passwordPolicy.requireSymbols || false;
30
+
31
+ // Build password requirements text
32
+ const requirements = [];
33
+ requirements.push(`${minLength}-${maxLength} characters`);
34
+ if (requireUppercase) requirements.push('uppercase letter');
35
+ if (requireLowercase) requirements.push('lowercase letter');
36
+ if (requireNumbers) requirements.push('number');
37
+ if (requireSymbols) requirements.push('symbol');
38
+
39
+ const inputClasses = [
40
+ 'block w-full rounded-2xl border bg-white/[0.08] px-4 py-3 text-base text-white',
41
+ 'shadow-[0_1px_0_rgba(255,255,255,0.06)] transition placeholder:text-slate-300/70 focus:outline-none focus:ring-2',
42
+ error ? 'border-red-400/70 focus:border-red-400 focus:ring-red-400/40' : 'border-white/10 focus:border-white/40 focus:ring-white/30'
43
+ ].join(' ');
44
+
45
+ const checkboxClasses = [
46
+ 'h-5 w-5 rounded border-white/30 bg-slate-900/70 text-primary',
47
+ 'focus:ring-2 focus:ring-primary/40 focus:ring-offset-0 focus:outline-none'
48
+ ].join(' ');
49
+
50
+ const content = html`
51
+ <section class="mx-auto flex w-full max-w-5xl flex-col items-center gap-12 text-slate-100 md:flex-row md:items-start md:justify-between">
52
+ <div class="order-2 w-full max-w-xl md:order-1">
53
+ <div class="relative isolate overflow-hidden rounded-3xl border border-white/10 bg-slate-900/60 p-10 shadow-2xl shadow-slate-900/60 backdrop-blur">
54
+ <div class="pointer-events-none absolute -right-24 -top-28 h-64 w-64 rounded-full bg-primary/25 blur-3xl"></div>
55
+ <div class="pointer-events-none absolute -bottom-28 -left-24 h-56 w-56 rounded-full bg-secondary/20 blur-[120px]"></div>
56
+
57
+ <div class="relative z-10 space-y-8">
58
+ <header class="space-y-2 text-center">
59
+ <h2 class="text-2xl font-semibold tracking-tight text-white">
60
+ Create Account
61
+ </h2>
62
+ <p class="text-sm text-slate-300">
63
+ Join ${companyName} Identity to access all secure services.
64
+ </p>
65
+ </header>
66
+
67
+ ${error ? html`
68
+ <div class="rounded-xl border border-red-500/40 bg-red-500/10 px-4 py-3 text-sm leading-6 text-red-100 shadow-md shadow-red-900/30">
69
+ ${error}
70
+ </div>
71
+ ` : ''}
72
+
73
+ <form method="POST" action="/register" class="space-y-6">
74
+ <div class="space-y-2">
75
+ <label for="name" class="text-sm font-semibold text-slate-200">
76
+ Full Name
77
+ </label>
78
+ <input
79
+ type="text"
80
+ class="${inputClasses}"
81
+ id="name"
82
+ name="name"
83
+ value="${name}"
84
+ required
85
+ autofocus
86
+ autocomplete="name"
87
+ minlength="2"
88
+ maxlength="100"
89
+ />
90
+ </div>
91
+
92
+ <div class="space-y-2">
93
+ <label for="email" class="text-sm font-semibold text-slate-200">
94
+ Email address
95
+ </label>
96
+ <input
97
+ type="email"
98
+ class="${inputClasses}"
99
+ id="email"
100
+ name="email"
101
+ value="${email}"
102
+ required
103
+ autocomplete="email"
104
+ />
105
+ <p class="text-xs text-slate-400">
106
+ We'll send you a verification email to confirm your account.
107
+ </p>
108
+ </div>
109
+
110
+ <div class="space-y-2">
111
+ <label for="password" class="text-sm font-semibold text-slate-200">
112
+ Password
113
+ </label>
114
+ <input
115
+ type="password"
116
+ class="${inputClasses}"
117
+ id="password"
118
+ name="password"
119
+ required
120
+ autocomplete="new-password"
121
+ minlength="${minLength}"
122
+ maxlength="${maxLength}"
123
+ />
124
+ <div class="rounded-2xl border border-white/5 bg-white/[0.06] px-4 py-3 text-xs leading-5 text-slate-200">
125
+ <span class="font-semibold text-white/80">Must include:</span>
126
+ <ul class="mt-2 list-disc space-y-1 pl-5 text-slate-300">
127
+ ${requirements.map(req => html`<li>${req}</li>`)}
128
+ </ul>
129
+ </div>
130
+ </div>
131
+
132
+ <div class="space-y-2">
133
+ <label for="confirm_password" class="text-sm font-semibold text-slate-200">
134
+ Confirm Password
135
+ </label>
136
+ <input
137
+ type="password"
138
+ class="${inputClasses}"
139
+ id="confirm_password"
140
+ name="confirm_password"
141
+ required
142
+ autocomplete="new-password"
143
+ />
144
+ </div>
145
+
146
+ <label class="flex items-start gap-3 text-sm text-slate-300">
147
+ <input
148
+ type="checkbox"
149
+ class="${checkboxClasses} mt-0.5"
150
+ id="agree_terms"
151
+ name="agree_terms"
152
+ value="1"
153
+ required
154
+ />
155
+ <span>
156
+ I agree to the
157
+ <a href="/terms" class="font-semibold text-primary transition hover:text-white" target="_blank">Terms of Service</a>
158
+ and
159
+ <a href="/privacy" class="font-semibold text-primary transition hover:text-white" target="_blank">Privacy Policy</a>.
160
+ </span>
161
+ </label>
162
+
163
+ <button
164
+ type="submit"
165
+ class="inline-flex w-full items-center justify-center rounded-2xl bg-gradient-to-r from-primary via-primary to-secondary px-4 py-3 text-base font-semibold text-white transition duration-200 hover:-translate-y-0.5 focus:outline-none focus:ring-2 focus:ring-white/30"
166
+ style="box-shadow: 0 18px 45px var(--color-primary-glow);"
167
+ >
168
+ Create Account
169
+ </button>
170
+ </form>
171
+
172
+ <p class="text-center text-sm text-slate-300">
173
+ Already have an account?
174
+ <a href="/login" class="font-semibold text-primary transition hover:text-white">
175
+ Sign in
176
+ </a>
177
+ </p>
178
+ </div>
179
+ </div>
180
+ </div>
181
+
182
+ <div class="order-1 max-w-xl text-center md:order-2 md:text-left">
183
+ ${config.logoUrl ? html`
184
+ <div class="mb-6 flex justify-center md:justify-start">
185
+ <img src="${config.logoUrl}" alt="${config.title || 'Identity Logo'}" class="h-12 w-auto" />
186
+ </div>
187
+ ` : ''}
188
+ <h1 class="text-3xl font-semibold tracking-tight text-white md:text-4xl">
189
+ Welcome to ${config.title || 'S3DB Identity'}
190
+ </h1>
191
+ <p class="mt-4 text-base text-slate-300 md:text-lg">
192
+ ${config.tagline || 'Create a secure identity to access your workspace and applications.'}
193
+ </p>
194
+ <div class="mt-8 grid gap-4 text-left text-sm text-slate-300">
195
+ <div class="rounded-2xl border border-white/5 bg-white/[0.04] px-4 py-3">
196
+ <span class="font-semibold text-white">✅ Fast onboarding</span>
197
+ <p class="mt-1 text-slate-300">
198
+ Start collaborating in minutes with instant verification.
199
+ </p>
200
+ </div>
201
+ <div class="rounded-2xl border border-white/5 bg-white/[0.04] px-4 py-3">
202
+ <span class="font-semibold text-white">🔐 Enterprise-grade security</span>
203
+ <p class="mt-1 text-slate-300">
204
+ Backed by multi-layer encryption and continuous monitoring.
205
+ </p>
206
+ </div>
207
+ <div class="rounded-2xl border border-white/5 bg-white/[0.04] px-4 py-3">
208
+ <span class="font-semibold text-white">💬 Live support</span>
209
+ <p class="mt-1 text-slate-300">
210
+ Our team is ready to help you with any registration issues.
211
+ </p>
212
+ </div>
213
+ </div>
214
+ </div>
215
+ </section>
216
+ `;
217
+
218
+ return BaseLayout({
219
+ title: 'Create Account',
220
+ content,
221
+ config,
222
+ error: null // Error shown in form
223
+ });
224
+ }
225
+
226
+ export default RegisterPage;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Reset Password Page
3
+ */
4
+
5
+ import { html } from 'hono/html';
6
+ import { BaseLayout } from '../layouts/base.js';
7
+
8
+ /**
9
+ * Render reset password page
10
+ * @param {Object} props - Page properties
11
+ * @param {string} [props.error] - Error message
12
+ * @param {string} [props.token] - Reset token from URL
13
+ * @param {Object} [props.passwordPolicy] - Password policy configuration
14
+ * @param {Object} [props.config] - UI configuration
15
+ * @returns {string} HTML string
16
+ */
17
+ export function ResetPasswordPage(props = {}) {
18
+ const { error = null, token = '', passwordPolicy = {}, config = {} } = props;
19
+
20
+ // Extract password policy
21
+ const minLength = passwordPolicy.minLength || 8;
22
+ const maxLength = passwordPolicy.maxLength || 128;
23
+ const requireUppercase = passwordPolicy.requireUppercase !== false;
24
+ const requireLowercase = passwordPolicy.requireLowercase !== false;
25
+ const requireNumbers = passwordPolicy.requireNumbers !== false;
26
+ const requireSymbols = passwordPolicy.requireSymbols || false;
27
+
28
+ // Build password requirements text
29
+ const requirements = [];
30
+ requirements.push(`${minLength}-${maxLength} characters`);
31
+ if (requireUppercase) requirements.push('uppercase letter');
32
+ if (requireLowercase) requirements.push('lowercase letter');
33
+ if (requireNumbers) requirements.push('number');
34
+ if (requireSymbols) requirements.push('symbol');
35
+
36
+ const inputClasses = [
37
+ 'block w-full rounded-2xl border bg-white/[0.08] px-4 py-3 text-base text-white',
38
+ 'shadow-[0_1px_0_rgba(255,255,255,0.06)] transition placeholder:text-slate-300/70 focus:outline-none focus:ring-2',
39
+ error ? 'border-red-400/70 focus:border-red-400 focus:ring-red-400/40' : 'border-white/10 focus:border-white/40 focus:ring-white/30'
40
+ ].join(' ');
41
+
42
+ const content = html`
43
+ <section class="mx-auto w-full max-w-lg text-slate-100">
44
+ <div class="relative isolate overflow-hidden rounded-3xl border border-white/10 bg-slate-900/60 p-10 shadow-2xl shadow-slate-900/60 backdrop-blur">
45
+ <div class="pointer-events-none absolute -right-20 -top-28 h-60 w-60 rounded-full bg-primary/25 blur-3xl"></div>
46
+ <div class="pointer-events-none absolute -bottom-24 -left-28 h-56 w-56 rounded-full bg-secondary/20 blur-[120px]"></div>
47
+
48
+ <div class="relative z-10 space-y-6">
49
+ <header class="space-y-2 text-center">
50
+ <h2 class="text-2xl font-semibold tracking-tight text-white">
51
+ Set New Password
52
+ </h2>
53
+ <p class="text-sm text-slate-300">
54
+ Choose a strong password to secure your account.
55
+ </p>
56
+ </header>
57
+
58
+ <form method="POST" action="/reset-password" class="space-y-6">
59
+ <input type="hidden" name="token" value="${token}" />
60
+
61
+ <div class="space-y-2">
62
+ <label for="password" class="text-sm font-semibold text-slate-200">
63
+ New Password
64
+ </label>
65
+ <input
66
+ type="password"
67
+ class="${inputClasses}"
68
+ id="password"
69
+ name="password"
70
+ required
71
+ autofocus
72
+ autocomplete="new-password"
73
+ minlength="${minLength}"
74
+ maxlength="${maxLength}"
75
+ />
76
+ <div class="rounded-2xl border border-white/5 bg-white/[0.06] px-4 py-3 text-xs leading-5 text-slate-200">
77
+ <span class="font-semibold text-white/80">Must include:</span>
78
+ <ul class="mt-2 list-disc space-y-1 pl-5 text-slate-300">
79
+ ${requirements.map(req => html`<li>${req}</li>`)}
80
+ </ul>
81
+ </div>
82
+ </div>
83
+
84
+ <div class="space-y-2">
85
+ <label for="confirm_password" class="text-sm font-semibold text-slate-200">
86
+ Confirm New Password
87
+ </label>
88
+ <input
89
+ type="password"
90
+ class="${inputClasses}"
91
+ id="confirm_password"
92
+ name="confirm_password"
93
+ required
94
+ autocomplete="new-password"
95
+ />
96
+ ${error ? html`<p class="text-xs text-red-200">${error}</p>` : ''}
97
+ </div>
98
+
99
+ <button
100
+ type="submit"
101
+ class="inline-flex w-full items-center justify-center rounded-2xl bg-gradient-to-r from-primary via-primary to-secondary px-4 py-3 text-base font-semibold text-white transition duration-200 hover:-translate-y-0.5 focus:outline-none focus:ring-2 focus:ring-white/30"
102
+ style="box-shadow: 0 18px 45px var(--color-primary-glow);"
103
+ >
104
+ Reset Password
105
+ </button>
106
+ </form>
107
+
108
+ <p class="text-center text-sm text-slate-300">
109
+ Remember your password?
110
+ <a href="/login" class="font-semibold text-primary transition hover:text-white">
111
+ Sign in
112
+ </a>
113
+ </p>
114
+ </div>
115
+ </div>
116
+ </section>
117
+ `;
118
+
119
+ return BaseLayout({
120
+ title: 'Set New Password',
121
+ content,
122
+ config,
123
+ error: null, // Error shown in form
124
+ success: null
125
+ });
126
+ }
127
+
128
+ export default ResetPasswordPage;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Email Verification Page
3
+ */
4
+
5
+ import { html } from 'hono/html';
6
+ import { BaseLayout } from '../layouts/base.js';
7
+
8
+ /**
9
+ * Render email verification page
10
+ * @param {Object} props - Page properties
11
+ * @param {string} [props.status] - Verification status (success, error, expired, pending)
12
+ * @param {string} [props.email] - User email (for resend)
13
+ * @param {string} [props.message] - Status message
14
+ * @param {Object} [props.config] - UI configuration
15
+ * @returns {string} HTML string
16
+ */
17
+ export function VerifyEmailPage(props = {}) {
18
+ const { status = 'pending', email = '', message = '', config = {} } = props;
19
+
20
+ const statusConfig = {
21
+ success: {
22
+ icon: '✅',
23
+ title: 'Email Verified!',
24
+ color: 'var(--color-success)',
25
+ defaultMessage: 'Your email address has been successfully verified.'
26
+ },
27
+ error: {
28
+ icon: '❌',
29
+ title: 'Verification Failed',
30
+ color: 'var(--color-danger)',
31
+ defaultMessage: 'The verification link is invalid or has already been used.'
32
+ },
33
+ expired: {
34
+ icon: '⏰',
35
+ title: 'Link Expired',
36
+ color: 'var(--color-warning)',
37
+ defaultMessage: 'This verification link has expired. Please request a new one.'
38
+ },
39
+ pending: {
40
+ icon: '📧',
41
+ title: 'Verify Your Email',
42
+ color: 'var(--color-primary)',
43
+ defaultMessage: 'Please check your email for a verification link.'
44
+ }
45
+ };
46
+
47
+ const currentStatus = statusConfig[status] || statusConfig.pending;
48
+ const displayMessage = message || currentStatus.defaultMessage;
49
+
50
+ const primaryButtonClasses = [
51
+ 'inline-flex items-center justify-center rounded-2xl bg-gradient-to-r',
52
+ 'from-primary via-primary to-secondary px-6 py-3 text-sm font-semibold text-white',
53
+ 'transition duration-200 hover:-translate-y-0.5 focus:outline-none focus:ring-2 focus:ring-white/30'
54
+ ].join(' ');
55
+
56
+ const secondaryButtonClasses = [
57
+ 'inline-flex items-center justify-center rounded-2xl border border-white/15 bg-white/[0.06]',
58
+ 'px-6 py-3 text-sm font-semibold text-white transition duration-200 hover:bg-white/[0.12]',
59
+ 'focus:outline-none focus:ring-2 focus:ring-white/20'
60
+ ].join(' ');
61
+
62
+ const content = html`
63
+ <section class="mx-auto w-full max-w-3xl text-slate-100">
64
+ <div class="flex flex-col items-center gap-4 pb-8 text-center">
65
+ ${config.logoUrl ? html`
66
+ <img src="${config.logoUrl}" alt="${config.title || 'Identity Logo'}" class="h-14 w-auto" />
67
+ ` : ''}
68
+ <h1 class="text-3xl font-semibold tracking-tight text-white md:text-4xl">
69
+ ${currentStatus.title}
70
+ </h1>
71
+ </div>
72
+
73
+ <div class="relative isolate overflow-hidden rounded-3xl border border-white/10 bg-slate-900/60 px-10 py-12 text-center shadow-2xl shadow-slate-900/60 backdrop-blur">
74
+ <div class="pointer-events-none absolute -left-24 top-10 h-64 w-64 rounded-full bg-primary/25 blur-3xl"></div>
75
+ <div class="pointer-events-none absolute -right-16 -top-20 h-56 w-56 rounded-full bg-secondary/25 blur-[120px]"></div>
76
+
77
+ <div class="relative z-10 space-y-8">
78
+ <div class="mx-auto flex h-20 w-20 items-center justify-center rounded-full border border-white/10 bg-white/[0.08] text-4xl">
79
+ ${currentStatus.icon}
80
+ </div>
81
+
82
+ <p class="text-base text-slate-200 md:text-lg" style="color: ${currentStatus.color};">
83
+ ${displayMessage}
84
+ </p>
85
+
86
+ ${status === 'success' ? html`
87
+ <div class="space-y-4">
88
+ <a href="/login" class="${primaryButtonClasses}" style="box-shadow: 0 18px 45px var(--color-primary-glow);">
89
+ Sign In
90
+ </a>
91
+ <div class="text-sm text-slate-300">
92
+ <a href="/profile" class="font-semibold text-primary transition hover:text-white">
93
+ Go to your profile
94
+ </a>
95
+ </div>
96
+ </div>
97
+ ` : status === 'error' || status === 'expired' ? html`
98
+ <div class="space-y-6">
99
+ ${email ? html`
100
+ <form method="POST" action="/verify-email/resend">
101
+ <input type="hidden" name="email" value="${email}" />
102
+ <button type="submit" class="${primaryButtonClasses}" style="box-shadow: 0 18px 45px var(--color-primary-glow);">
103
+ Send New Verification Email
104
+ </button>
105
+ </form>
106
+ ` : html`
107
+ <a href="/login" class="${primaryButtonClasses}" style="box-shadow: 0 18px 45px var(--color-primary-glow);">
108
+ Sign In to Resend
109
+ </a>
110
+ `}
111
+ <div class="text-sm text-slate-300">
112
+ <a href="/login" class="font-semibold text-primary transition hover:text-white">
113
+ Sign in
114
+ </a>
115
+ </div>
116
+ </div>
117
+ ` : html`
118
+ <div class="space-y-6">
119
+ <div class="rounded-2xl border border-white/10 bg-white/[0.06] px-6 py-5 text-left text-sm text-slate-200">
120
+ <p class="mb-2 font-semibold text-white/90">
121
+ Didn't receive the email?
122
+ </p>
123
+ <ul class="list-disc space-y-1 pl-5 text-slate-300">
124
+ <li>Check your spam or junk folder</li>
125
+ <li>Make sure the email address is correct</li>
126
+ <li>Wait a few minutes and check again</li>
127
+ </ul>
128
+ </div>
129
+
130
+ ${email ? html`
131
+ <form method="POST" action="/verify-email/resend">
132
+ <input type="hidden" name="email" value="${email}" />
133
+ <button type="submit" class="${secondaryButtonClasses}">
134
+ Resend Verification Email
135
+ </button>
136
+ </form>
137
+ ` : html`
138
+ <a href="/login" class="${secondaryButtonClasses}">
139
+ Sign In to Resend
140
+ </a>
141
+ `}
142
+
143
+ <div class="text-sm text-slate-300">
144
+ <a href="/login" class="font-semibold text-primary transition hover:text-white">
145
+ Back to sign in
146
+ </a>
147
+ </div>
148
+ </div>
149
+ `}
150
+ </div>
151
+ </div>
152
+
153
+ ${status === 'success' ? html`
154
+ <div class="mt-6 rounded-2xl border border-emerald-400/40 bg-emerald-500/10 px-6 py-5 text-center text-sm leading-6 text-emerald-100 shadow-lg shadow-emerald-900/30">
155
+ <strong>Your account is now fully activated!</strong><br>
156
+ You can now access all features and services.
157
+ </div>
158
+ ` : ''}
159
+ </section>
160
+ `;
161
+
162
+ return BaseLayout({
163
+ title: currentStatus.title,
164
+ content,
165
+ config,
166
+ user: null,
167
+ error: null,
168
+ success: null
169
+ });
170
+ }
171
+
172
+ export default VerifyEmailPage;