@awesome-lang-auth/node 1.10.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/LICENSE +21 -0
- package/README.detailed.md +4059 -0
- package/README.md +248 -0
- package/dist/abstract/base-auth-strategy.abstract.d.ts +7 -0
- package/dist/abstract/base-auth-strategy.abstract.d.ts.map +1 -0
- package/dist/abstract/base-auth-strategy.abstract.js +7 -0
- package/dist/abstract/base-auth-strategy.abstract.js.map +1 -0
- package/dist/abstract/base-oauth-strategy.abstract.d.ts +27 -0
- package/dist/abstract/base-oauth-strategy.abstract.d.ts.map +1 -0
- package/dist/abstract/base-oauth-strategy.abstract.js +11 -0
- package/dist/abstract/base-oauth-strategy.abstract.js.map +1 -0
- package/dist/adapters/express.d.ts +45 -0
- package/dist/adapters/express.d.ts.map +1 -0
- package/dist/adapters/express.js +49 -0
- package/dist/adapters/express.js.map +1 -0
- package/dist/adapters/fastify.d.ts +72 -0
- package/dist/adapters/fastify.d.ts.map +1 -0
- package/dist/adapters/fastify.js +63 -0
- package/dist/adapters/fastify.js.map +1 -0
- package/dist/auth-configurator.d.ts +65 -0
- package/dist/auth-configurator.d.ts.map +1 -0
- package/dist/auth-configurator.js +127 -0
- package/dist/auth-configurator.js.map +1 -0
- package/dist/events/auth-event-bus.d.ts +68 -0
- package/dist/events/auth-event-bus.d.ts.map +1 -0
- package/dist/events/auth-event-bus.js +60 -0
- package/dist/events/auth-event-bus.js.map +1 -0
- package/dist/events/auth-event-names.d.ts +34 -0
- package/dist/events/auth-event-names.d.ts.map +1 -0
- package/dist/events/auth-event-names.js +41 -0
- package/dist/events/auth-event-names.js.map +1 -0
- package/dist/http-types.d.ts +135 -0
- package/dist/http-types.d.ts.map +1 -0
- package/dist/http-types.js +18 -0
- package/dist/http-types.js.map +1 -0
- package/dist/index.d.ts +80 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/interfaces/api-key-store.interface.d.ts +94 -0
- package/dist/interfaces/api-key-store.interface.d.ts.map +1 -0
- package/dist/interfaces/api-key-store.interface.js +3 -0
- package/dist/interfaces/api-key-store.interface.js.map +1 -0
- package/dist/interfaces/auth-strategy.interface.d.ts +6 -0
- package/dist/interfaces/auth-strategy.interface.d.ts.map +1 -0
- package/dist/interfaces/auth-strategy.interface.js +3 -0
- package/dist/interfaces/auth-strategy.interface.js.map +1 -0
- package/dist/interfaces/linked-accounts-store.interface.d.ts +74 -0
- package/dist/interfaces/linked-accounts-store.interface.d.ts.map +1 -0
- package/dist/interfaces/linked-accounts-store.interface.js +3 -0
- package/dist/interfaces/linked-accounts-store.interface.js.map +1 -0
- package/dist/interfaces/pending-link-store.interface.d.ts +77 -0
- package/dist/interfaces/pending-link-store.interface.d.ts.map +1 -0
- package/dist/interfaces/pending-link-store.interface.js +3 -0
- package/dist/interfaces/pending-link-store.interface.js.map +1 -0
- package/dist/interfaces/roles-permissions-store.interface.d.ts +129 -0
- package/dist/interfaces/roles-permissions-store.interface.d.ts.map +1 -0
- package/dist/interfaces/roles-permissions-store.interface.js +3 -0
- package/dist/interfaces/roles-permissions-store.interface.js.map +1 -0
- package/dist/interfaces/session-store.interface.d.ts +92 -0
- package/dist/interfaces/session-store.interface.d.ts.map +1 -0
- package/dist/interfaces/session-store.interface.js +3 -0
- package/dist/interfaces/session-store.interface.js.map +1 -0
- package/dist/interfaces/settings-store.interface.d.ts +91 -0
- package/dist/interfaces/settings-store.interface.d.ts.map +1 -0
- package/dist/interfaces/settings-store.interface.js +3 -0
- package/dist/interfaces/settings-store.interface.js.map +1 -0
- package/dist/interfaces/sse-distributor.interface.d.ts +25 -0
- package/dist/interfaces/sse-distributor.interface.d.ts.map +1 -0
- package/dist/interfaces/sse-distributor.interface.js +3 -0
- package/dist/interfaces/sse-distributor.interface.js.map +1 -0
- package/dist/interfaces/telemetry-store.interface.d.ts +72 -0
- package/dist/interfaces/telemetry-store.interface.d.ts.map +1 -0
- package/dist/interfaces/telemetry-store.interface.js +3 -0
- package/dist/interfaces/telemetry-store.interface.js.map +1 -0
- package/dist/interfaces/template-store.interface.d.ts +37 -0
- package/dist/interfaces/template-store.interface.d.ts.map +1 -0
- package/dist/interfaces/template-store.interface.js +3 -0
- package/dist/interfaces/template-store.interface.js.map +1 -0
- package/dist/interfaces/tenant-store.interface.d.ts +97 -0
- package/dist/interfaces/tenant-store.interface.d.ts.map +1 -0
- package/dist/interfaces/tenant-store.interface.js +3 -0
- package/dist/interfaces/tenant-store.interface.js.map +1 -0
- package/dist/interfaces/token-store.interface.d.ts +10 -0
- package/dist/interfaces/token-store.interface.d.ts.map +1 -0
- package/dist/interfaces/token-store.interface.js +3 -0
- package/dist/interfaces/token-store.interface.js.map +1 -0
- package/dist/interfaces/user-metadata-store.interface.d.ts +50 -0
- package/dist/interfaces/user-metadata-store.interface.d.ts.map +1 -0
- package/dist/interfaces/user-metadata-store.interface.js +3 -0
- package/dist/interfaces/user-metadata-store.interface.js.map +1 -0
- package/dist/interfaces/user-store.interface.d.ts +113 -0
- package/dist/interfaces/user-store.interface.d.ts.map +1 -0
- package/dist/interfaces/user-store.interface.js +3 -0
- package/dist/interfaces/user-store.interface.js.map +1 -0
- package/dist/interfaces/webhook-store.interface.d.ts +139 -0
- package/dist/interfaces/webhook-store.interface.d.ts.map +1 -0
- package/dist/interfaces/webhook-store.interface.js +3 -0
- package/dist/interfaces/webhook-store.interface.js.map +1 -0
- package/dist/middleware/api-key.middleware.d.ts +35 -0
- package/dist/middleware/api-key.middleware.d.ts.map +1 -0
- package/dist/middleware/api-key.middleware.js +45 -0
- package/dist/middleware/api-key.middleware.js.map +1 -0
- package/dist/middleware/auth.middleware.d.ts +13 -0
- package/dist/middleware/auth.middleware.d.ts.map +1 -0
- package/dist/middleware/auth.middleware.js +55 -0
- package/dist/middleware/auth.middleware.js.map +1 -0
- package/dist/middleware/jwks-auth.middleware.d.ts +18 -0
- package/dist/middleware/jwks-auth.middleware.d.ts.map +1 -0
- package/dist/middleware/jwks-auth.middleware.js +77 -0
- package/dist/middleware/jwks-auth.middleware.js.map +1 -0
- package/dist/models/api-key.model.d.ts +66 -0
- package/dist/models/api-key.model.d.ts.map +1 -0
- package/dist/models/api-key.model.js +3 -0
- package/dist/models/api-key.model.js.map +1 -0
- package/dist/models/auth-config.model.d.ts +387 -0
- package/dist/models/auth-config.model.d.ts.map +1 -0
- package/dist/models/auth-config.model.js +3 -0
- package/dist/models/auth-config.model.js.map +1 -0
- package/dist/models/errors.d.ts +7 -0
- package/dist/models/errors.d.ts.map +1 -0
- package/dist/models/errors.js +14 -0
- package/dist/models/errors.js.map +1 -0
- package/dist/models/session.model.d.ts +28 -0
- package/dist/models/session.model.d.ts.map +1 -0
- package/dist/models/session.model.js +3 -0
- package/dist/models/session.model.js.map +1 -0
- package/dist/models/tenant.model.d.ts +20 -0
- package/dist/models/tenant.model.d.ts.map +1 -0
- package/dist/models/tenant.model.js +3 -0
- package/dist/models/tenant.model.js.map +1 -0
- package/dist/models/token.model.d.ts +21 -0
- package/dist/models/token.model.d.ts.map +1 -0
- package/dist/models/token.model.js +3 -0
- package/dist/models/token.model.js.map +1 -0
- package/dist/models/user.model.d.ts +92 -0
- package/dist/models/user.model.d.ts.map +1 -0
- package/dist/models/user.model.js +3 -0
- package/dist/models/user.model.js.map +1 -0
- package/dist/router/admin.router.d.ts +188 -0
- package/dist/router/admin.router.d.ts.map +1 -0
- package/dist/router/admin.router.js +1507 -0
- package/dist/router/admin.router.js.map +1 -0
- package/dist/router/auth.router.d.ts +199 -0
- package/dist/router/auth.router.d.ts.map +1 -0
- package/dist/router/auth.router.js +1636 -0
- package/dist/router/auth.router.js.map +1 -0
- package/dist/router/openapi.d.ts +98 -0
- package/dist/router/openapi.d.ts.map +1 -0
- package/dist/router/openapi.js +1518 -0
- package/dist/router/openapi.js.map +1 -0
- package/dist/router/router-events.d.ts +38 -0
- package/dist/router/router-events.d.ts.map +1 -0
- package/dist/router/router-events.js +74 -0
- package/dist/router/router-events.js.map +1 -0
- package/dist/router/tools.router.d.ts +104 -0
- package/dist/router/tools.router.d.ts.map +1 -0
- package/dist/router/tools.router.js +227 -0
- package/dist/router/tools.router.js.map +1 -0
- package/dist/router/ui.router.d.ts +49 -0
- package/dist/router/ui.router.d.ts.map +1 -0
- package/dist/router/ui.router.js +272 -0
- package/dist/router/ui.router.js.map +1 -0
- package/dist/services/api-key.service.d.ts +53 -0
- package/dist/services/api-key.service.d.ts.map +1 -0
- package/dist/services/api-key.service.js +66 -0
- package/dist/services/api-key.service.js.map +1 -0
- package/dist/services/jwks.service.d.ts +73 -0
- package/dist/services/jwks.service.d.ts.map +1 -0
- package/dist/services/jwks.service.js +174 -0
- package/dist/services/jwks.service.js.map +1 -0
- package/dist/services/mailer.service.d.ts +30 -0
- package/dist/services/mailer.service.d.ts.map +1 -0
- package/dist/services/mailer.service.js +248 -0
- package/dist/services/mailer.service.js.map +1 -0
- package/dist/services/notification.service.d.ts +110 -0
- package/dist/services/notification.service.d.ts.map +1 -0
- package/dist/services/notification.service.js +79 -0
- package/dist/services/notification.service.js.map +1 -0
- package/dist/services/password.service.d.ts +5 -0
- package/dist/services/password.service.d.ts.map +1 -0
- package/dist/services/password.service.js +17 -0
- package/dist/services/password.service.js.map +1 -0
- package/dist/services/sms.service.d.ts +14 -0
- package/dist/services/sms.service.d.ts.map +1 -0
- package/dist/services/sms.service.js +51 -0
- package/dist/services/sms.service.js.map +1 -0
- package/dist/services/token.service.d.ts +62 -0
- package/dist/services/token.service.d.ts.map +1 -0
- package/dist/services/token.service.js +354 -0
- package/dist/services/token.service.js.map +1 -0
- package/dist/stores/memory-template.store.d.ts +12 -0
- package/dist/stores/memory-template.store.d.ts.map +1 -0
- package/dist/stores/memory-template.store.js +35 -0
- package/dist/stores/memory-template.store.js.map +1 -0
- package/dist/strategies/api-key/api-key.strategy.d.ts +72 -0
- package/dist/strategies/api-key/api-key.strategy.d.ts.map +1 -0
- package/dist/strategies/api-key/api-key.strategy.js +182 -0
- package/dist/strategies/api-key/api-key.strategy.js.map +1 -0
- package/dist/strategies/local/local.strategy.d.ts +19 -0
- package/dist/strategies/local/local.strategy.d.ts.map +1 -0
- package/dist/strategies/local/local.strategy.js +45 -0
- package/dist/strategies/local/local.strategy.js.map +1 -0
- package/dist/strategies/magic-link/magic-link.strategy.d.ts +8 -0
- package/dist/strategies/magic-link/magic-link.strategy.d.ts.map +1 -0
- package/dist/strategies/magic-link/magic-link.strategy.js +53 -0
- package/dist/strategies/magic-link/magic-link.strategy.js.map +1 -0
- package/dist/strategies/oauth/generic-oauth.strategy.d.ts +120 -0
- package/dist/strategies/oauth/generic-oauth.strategy.d.ts.map +1 -0
- package/dist/strategies/oauth/generic-oauth.strategy.js +88 -0
- package/dist/strategies/oauth/generic-oauth.strategy.js.map +1 -0
- package/dist/strategies/oauth/github.strategy.d.ts +31 -0
- package/dist/strategies/oauth/github.strategy.d.ts.map +1 -0
- package/dist/strategies/oauth/github.strategy.js +72 -0
- package/dist/strategies/oauth/github.strategy.js.map +1 -0
- package/dist/strategies/oauth/google.strategy.d.ts +31 -0
- package/dist/strategies/oauth/google.strategy.d.ts.map +1 -0
- package/dist/strategies/oauth/google.strategy.js +61 -0
- package/dist/strategies/oauth/google.strategy.js.map +1 -0
- package/dist/strategies/sms/sms.strategy.d.ts +7 -0
- package/dist/strategies/sms/sms.strategy.d.ts.map +1 -0
- package/dist/strategies/sms/sms.strategy.js +39 -0
- package/dist/strategies/sms/sms.strategy.js.map +1 -0
- package/dist/strategies/two-factor/totp.strategy.d.ts +12 -0
- package/dist/strategies/two-factor/totp.strategy.d.ts.map +1 -0
- package/dist/strategies/two-factor/totp.strategy.js +32 -0
- package/dist/strategies/two-factor/totp.strategy.js.map +1 -0
- package/dist/tools/auth-tools.d.ts +200 -0
- package/dist/tools/auth-tools.d.ts.map +1 -0
- package/dist/tools/auth-tools.js +232 -0
- package/dist/tools/auth-tools.js.map +1 -0
- package/dist/tools/sse-manager.d.ts +118 -0
- package/dist/tools/sse-manager.d.ts.map +1 -0
- package/dist/tools/sse-manager.js +163 -0
- package/dist/tools/sse-manager.js.map +1 -0
- package/dist/tools/sse-notify.decorator.d.ts +74 -0
- package/dist/tools/sse-notify.decorator.d.ts.map +1 -0
- package/dist/tools/sse-notify.decorator.js +78 -0
- package/dist/tools/sse-notify.decorator.js.map +1 -0
- package/dist/tools/webhook-action.d.ts +130 -0
- package/dist/tools/webhook-action.d.ts.map +1 -0
- package/dist/tools/webhook-action.js +144 -0
- package/dist/tools/webhook-action.js.map +1 -0
- package/dist/tools/webhook-sender.d.ts +31 -0
- package/dist/tools/webhook-sender.d.ts.map +1 -0
- package/dist/tools/webhook-sender.js +80 -0
- package/dist/tools/webhook-sender.js.map +1 -0
- package/dist/ui-assets/2fa.html +115 -0
- package/dist/ui-assets/account-conflict.html +108 -0
- package/dist/ui-assets/admin.css +263 -0
- package/dist/ui-assets/admin.js +1927 -0
- package/dist/ui-assets/auth.js +725 -0
- package/dist/ui-assets/base.css +219 -0
- package/dist/ui-assets/forgot-password.html +93 -0
- package/dist/ui-assets/link-verify.html +74 -0
- package/dist/ui-assets/login.html +135 -0
- package/dist/ui-assets/magic-link.html +112 -0
- package/dist/ui-assets/register.html +122 -0
- package/dist/ui-assets/reset-password.html +103 -0
- package/dist/ui-assets/ui-i18n-keys.json +78 -0
- package/dist/ui-assets/verify-email.html +66 -0
- package/package.json +89 -0
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap');
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
--primary-color: #4a90d9;
|
|
5
|
+
--primary-color-hover: #357abd;
|
|
6
|
+
--secondary-color: #6c757d;
|
|
7
|
+
--secondary-color-hover: #5a6268;
|
|
8
|
+
--bg-color: #f8fafc;
|
|
9
|
+
--bg-image: none;
|
|
10
|
+
--card-bg: #ffffff;
|
|
11
|
+
--text-color: #1e293b;
|
|
12
|
+
--text-muted: #64748b;
|
|
13
|
+
--error-color: #ef4444;
|
|
14
|
+
--success-color: #22c55e;
|
|
15
|
+
--border-color: #e2e8f0;
|
|
16
|
+
--input-focus: #4a90d9;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
* {
|
|
20
|
+
box-sizing: border-box;
|
|
21
|
+
margin: 0;
|
|
22
|
+
padding: 0;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
body {
|
|
26
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
27
|
+
background-color: var(--bg-color);
|
|
28
|
+
background-image: var(--bg-image);
|
|
29
|
+
background-size: cover;
|
|
30
|
+
background-position: center;
|
|
31
|
+
background-attachment: fixed;
|
|
32
|
+
color: var(--text-color);
|
|
33
|
+
display: flex;
|
|
34
|
+
justify-content: center;
|
|
35
|
+
align-items: center;
|
|
36
|
+
min-height: 100vh;
|
|
37
|
+
padding: 20px;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
.auth-container {
|
|
41
|
+
width: 100%;
|
|
42
|
+
max-width: 400px;
|
|
43
|
+
background: var(--card-bg);
|
|
44
|
+
border: 1px solid var(--border-color);
|
|
45
|
+
border-radius: 12px;
|
|
46
|
+
padding: 32px;
|
|
47
|
+
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.logo {
|
|
51
|
+
display: block;
|
|
52
|
+
height: 48px;
|
|
53
|
+
width: auto;
|
|
54
|
+
margin: 0 auto 16px;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
h1 {
|
|
58
|
+
font-size: 24px;
|
|
59
|
+
font-weight: 700;
|
|
60
|
+
text-align: center;
|
|
61
|
+
color: var(--primary-color);
|
|
62
|
+
margin-bottom: 4px;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
h2 {
|
|
66
|
+
font-size: 18px;
|
|
67
|
+
font-weight: 400;
|
|
68
|
+
text-align: center;
|
|
69
|
+
color: var(--text-muted);
|
|
70
|
+
margin-bottom: 24px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.alert {
|
|
74
|
+
padding: 12px;
|
|
75
|
+
border-radius: 6px;
|
|
76
|
+
font-size: 14px;
|
|
77
|
+
margin-bottom: 16px;
|
|
78
|
+
border: 1px solid transparent;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.alert-error {
|
|
82
|
+
background-color: #fef2f2;
|
|
83
|
+
color: var(--error-color);
|
|
84
|
+
border-color: #fee2e2;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.alert-success {
|
|
88
|
+
background-color: #f0fdf4;
|
|
89
|
+
color: var(--success-color);
|
|
90
|
+
border-color: #dcfce7;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
form {
|
|
94
|
+
display: flex;
|
|
95
|
+
flex-direction: column;
|
|
96
|
+
gap: 16px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.form-group {
|
|
100
|
+
display: flex;
|
|
101
|
+
flex-direction: column;
|
|
102
|
+
gap: 4px;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
label {
|
|
106
|
+
font-size: 14px;
|
|
107
|
+
font-weight: 600;
|
|
108
|
+
color: #374151;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
input {
|
|
112
|
+
width: 100%;
|
|
113
|
+
padding: 8px 12px;
|
|
114
|
+
border: 1px solid #d1d5db;
|
|
115
|
+
border-radius: 6px;
|
|
116
|
+
font-size: 14px;
|
|
117
|
+
transition: border-color 0.2s;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
input:focus {
|
|
121
|
+
outline: none;
|
|
122
|
+
border-color: var(--input-focus);
|
|
123
|
+
box-shadow: 0 0 0 3px rgba(74, 144, 217, 0.1);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
button {
|
|
127
|
+
width: 100%;
|
|
128
|
+
padding: 12px;
|
|
129
|
+
border: none;
|
|
130
|
+
border-radius: 6px;
|
|
131
|
+
font-size: 14px;
|
|
132
|
+
font-weight: 600;
|
|
133
|
+
cursor: pointer;
|
|
134
|
+
transition: background-color 0.2s, opacity 0.2s;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
button[type="submit"] {
|
|
138
|
+
background-color: var(--primary-color);
|
|
139
|
+
color: white;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
button[type="submit"]:hover {
|
|
143
|
+
background-color: var(--primary-color-hover);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
button:disabled {
|
|
147
|
+
opacity: 0.6;
|
|
148
|
+
cursor: not-allowed;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.divider {
|
|
152
|
+
position: relative;
|
|
153
|
+
margin: 24px 0;
|
|
154
|
+
border-top: 1px solid var(--border-color);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.divider span {
|
|
158
|
+
position: absolute;
|
|
159
|
+
top: 50%;
|
|
160
|
+
left: 50%;
|
|
161
|
+
transform: translate(-50%, -50%);
|
|
162
|
+
background: var(--card-bg);
|
|
163
|
+
padding: 0 8px;
|
|
164
|
+
font-size: 10px;
|
|
165
|
+
color: #9ca3af;
|
|
166
|
+
font-weight: 600;
|
|
167
|
+
text-transform: uppercase;
|
|
168
|
+
letter-spacing: 0.05em;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
.social-buttons {
|
|
172
|
+
display: flex;
|
|
173
|
+
flex-direction: column;
|
|
174
|
+
gap: 12px;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
.btn-social {
|
|
178
|
+
display: flex;
|
|
179
|
+
align-items: center;
|
|
180
|
+
justify-content: center;
|
|
181
|
+
gap: 8px;
|
|
182
|
+
background-color: white;
|
|
183
|
+
border: 1px solid var(--secondary-color);
|
|
184
|
+
color: var(--secondary-color);
|
|
185
|
+
text-decoration: none;
|
|
186
|
+
padding: 10px;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.btn-social:hover {
|
|
190
|
+
background-color: #f9fafb;
|
|
191
|
+
border-color: var(--secondary-color-hover);
|
|
192
|
+
color: var(--secondary-color-hover);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.icon {
|
|
196
|
+
width: 16px;
|
|
197
|
+
height: 16px;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.footer-links {
|
|
201
|
+
margin-top: 24px;
|
|
202
|
+
text-align: center;
|
|
203
|
+
font-size: 14px;
|
|
204
|
+
color: var(--text-muted);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.footer-links a {
|
|
208
|
+
color: var(--primary-color);
|
|
209
|
+
text-decoration: none;
|
|
210
|
+
font-weight: 600;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.footer-links a:hover {
|
|
214
|
+
text-decoration: underline;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.hidden {
|
|
218
|
+
display: none !important;
|
|
219
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Forgot Password - Awesome Node Auth</title>
|
|
8
|
+
<link rel="stylesheet" href="base.css">
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<div class="auth-container">
|
|
13
|
+
<img src="" alt="Logo" class="logo hidden">
|
|
14
|
+
<h1 class="site-name" data-i18n="site_name">Awesome Node Auth</h1>
|
|
15
|
+
<h2 data-i18n="forgot_password_title">Forgot Password</h2>
|
|
16
|
+
|
|
17
|
+
<div id="alert" class="alert hidden"></div>
|
|
18
|
+
|
|
19
|
+
<div id="request-form-container">
|
|
20
|
+
<p class="footer-links" style="margin-bottom: 20px; text-align: left;" data-i18n="forgot_password_instruction">
|
|
21
|
+
Enter your email address and we'll send you a link to reset your password.
|
|
22
|
+
</p>
|
|
23
|
+
<form id="forgot-form">
|
|
24
|
+
<div class="form-group">
|
|
25
|
+
<label for="email" data-i18n="email_label">Email</label>
|
|
26
|
+
<input type="email" id="email" name="email" required data-i18n="email_placeholder">
|
|
27
|
+
</div>
|
|
28
|
+
<button type="submit" id="submit-btn" data-i18n="send_reset_link_button">Send Reset Link</button>
|
|
29
|
+
</form>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<div id="success-message" class="hidden">
|
|
33
|
+
<div class="alert alert-success" data-i18n="forgot_password_success">
|
|
34
|
+
If an account exists for <strong id="sent-email"></strong>, you will receive a password reset link
|
|
35
|
+
shortly.
|
|
36
|
+
</div>
|
|
37
|
+
<p class="footer-links">
|
|
38
|
+
<a href="login" data-i18n="return_to_login_link">Return to login</a>
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class="footer-links" id="login-link">
|
|
43
|
+
<p><span data-i18n="remember_password_text">Remember your password?</span> <a href="login" data-i18n="login_link">Login</a></p>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
|
|
47
|
+
<script src="auth.js?v=2"></script>
|
|
48
|
+
<script>
|
|
49
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
50
|
+
await AuthService.init();
|
|
51
|
+
|
|
52
|
+
const form = document.getElementById('forgot-form');
|
|
53
|
+
const alert = document.getElementById('alert');
|
|
54
|
+
const submitBtn = document.getElementById('submit-btn');
|
|
55
|
+
const requestContainer = document.getElementById('request-form-container');
|
|
56
|
+
const successContainer = document.getElementById('success-message');
|
|
57
|
+
const loginLink = document.getElementById('login-link');
|
|
58
|
+
|
|
59
|
+
form.addEventListener('submit', async (e) => {
|
|
60
|
+
e.preventDefault();
|
|
61
|
+
alert.className = 'alert hidden';
|
|
62
|
+
submitBtn.disabled = true;
|
|
63
|
+
submitBtn.textContent = 'Sending...';
|
|
64
|
+
|
|
65
|
+
const email = form.email.value;
|
|
66
|
+
|
|
67
|
+
try {
|
|
68
|
+
const result = await AuthService.apiCall('/forgot-password', 'POST', { email });
|
|
69
|
+
|
|
70
|
+
if (result.success) {
|
|
71
|
+
document.getElementById('sent-email').textContent = email;
|
|
72
|
+
requestContainer.classList.add('hidden');
|
|
73
|
+
loginLink.classList.add('hidden');
|
|
74
|
+
successContainer.classList.remove('hidden');
|
|
75
|
+
} else {
|
|
76
|
+
alert.textContent = result.error || 'Request failed';
|
|
77
|
+
alert.classList.add('alert-error');
|
|
78
|
+
alert.classList.remove('hidden');
|
|
79
|
+
}
|
|
80
|
+
} catch (err) {
|
|
81
|
+
alert.textContent = 'An unexpected error occurred';
|
|
82
|
+
alert.classList.add('alert-error');
|
|
83
|
+
alert.classList.remove('hidden');
|
|
84
|
+
} finally {
|
|
85
|
+
submitBtn.disabled = false;
|
|
86
|
+
submitBtn.textContent = 'Send Reset Link';
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
</script>
|
|
91
|
+
</body>
|
|
92
|
+
|
|
93
|
+
</html>
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Linking Account - Awesome Node Auth</title>
|
|
8
|
+
<link rel="stylesheet" href="base.css">
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<div class="auth-container">
|
|
13
|
+
<img src="" alt="Logo" class="logo hidden">
|
|
14
|
+
<h1 class="site-name" data-i18n="site_name">Awesome Node Auth</h1>
|
|
15
|
+
<h2 data-i18n="account_linking_title">Account Linking</h2>
|
|
16
|
+
|
|
17
|
+
<div id="alert" class="alert hidden"></div>
|
|
18
|
+
|
|
19
|
+
<div id="loading" class="footer-links" data-i18n="account_linking_loading">
|
|
20
|
+
Completing account linking, please wait…
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div id="footer" class="footer-links hidden">
|
|
24
|
+
<a href="login" data-i18n="go_to_login_link">Go to login</a>
|
|
25
|
+
</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
<script src="auth.js?v=2"></script>
|
|
29
|
+
<script>
|
|
30
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
31
|
+
await AuthService.init();
|
|
32
|
+
|
|
33
|
+
const params = new URLSearchParams(window.location.search);
|
|
34
|
+
const token = params.get('token');
|
|
35
|
+
const alertEl = document.getElementById('alert');
|
|
36
|
+
const loading = document.getElementById('loading');
|
|
37
|
+
const footer = document.getElementById('footer');
|
|
38
|
+
|
|
39
|
+
function showResult(msg, isError) {
|
|
40
|
+
loading.classList.add('hidden');
|
|
41
|
+
footer.classList.remove('hidden');
|
|
42
|
+
alertEl.textContent = msg;
|
|
43
|
+
alertEl.className = 'alert ' + (isError ? 'alert-error' : 'alert-success');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!token) {
|
|
47
|
+
showResult('Invalid or missing link token.', true);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// POST /link-verify — completes the account link and optionally logs in
|
|
53
|
+
const result = await AuthService.apiCall('/link-verify', 'POST', {
|
|
54
|
+
token,
|
|
55
|
+
loginAfterLinking: true,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
if (result.success) {
|
|
59
|
+
showResult('Your account has been linked successfully! Redirecting…', false);
|
|
60
|
+
setTimeout(() => {
|
|
61
|
+
// If tokens were returned (loginAfterLinking), redirect to home
|
|
62
|
+
window.location.href = '../..';
|
|
63
|
+
}, 1500);
|
|
64
|
+
} else {
|
|
65
|
+
showResult(result.error || 'Account linking failed.', true);
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
showResult(err.message || 'An unexpected error occurred.', true);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
</script>
|
|
72
|
+
</body>
|
|
73
|
+
|
|
74
|
+
</html>
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Login - Awesome Node Auth</title>
|
|
8
|
+
<link rel="stylesheet" href="base.css">
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<div class="auth-container">
|
|
13
|
+
<img src="" alt="Logo" class="logo hidden">
|
|
14
|
+
<h1 class="site-name" data-i18n="site_name">Awesome Node Auth</h1>
|
|
15
|
+
<h2 data-i18n="login_title">Login</h2>
|
|
16
|
+
|
|
17
|
+
<div id="alert" class="alert alert-error hidden"></div>
|
|
18
|
+
|
|
19
|
+
<form id="login-form">
|
|
20
|
+
<div class="form-group">
|
|
21
|
+
<label for="email" data-i18n="email_label">Email</label>
|
|
22
|
+
<input type="email" id="email" name="email" required data-i18n="email_placeholder">
|
|
23
|
+
</div>
|
|
24
|
+
<div class="form-group">
|
|
25
|
+
<label for="password" data-i18n="password_label">Password</label>
|
|
26
|
+
<input type="password" id="password" name="password" required data-i18n="password_placeholder">
|
|
27
|
+
</div>
|
|
28
|
+
<button type="submit" id="submit-btn" data-i18n="login_button">Login</button>
|
|
29
|
+
</form>
|
|
30
|
+
|
|
31
|
+
<div id="social-section" class="hidden">
|
|
32
|
+
<div class="divider">
|
|
33
|
+
<span data-i18n="social_divider">Or continue with</span>
|
|
34
|
+
</div>
|
|
35
|
+
<div class="social-buttons">
|
|
36
|
+
<a href="../oauth/google" class="btn-social hidden" data-feature="google">
|
|
37
|
+
<svg class="icon" viewBox="0 0 24 24">
|
|
38
|
+
<path
|
|
39
|
+
d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"
|
|
40
|
+
fill="#4285F4" />
|
|
41
|
+
<path
|
|
42
|
+
d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
|
|
43
|
+
fill="#34A853" />
|
|
44
|
+
<path
|
|
45
|
+
d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
|
|
46
|
+
fill="#FBBC05" />
|
|
47
|
+
<path
|
|
48
|
+
d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
|
|
49
|
+
fill="#EA4335" />
|
|
50
|
+
</svg>
|
|
51
|
+
Google
|
|
52
|
+
</a>
|
|
53
|
+
<a href="../oauth/github" class="btn-social hidden" data-feature="github">
|
|
54
|
+
<svg class="icon" fill="currentColor" viewBox="0 0 24 24">
|
|
55
|
+
<path fill-rule="evenodd"
|
|
56
|
+
d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z"
|
|
57
|
+
clip-rule="evenodd"></path>
|
|
58
|
+
</svg>
|
|
59
|
+
GitHub
|
|
60
|
+
</a>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
</div>
|
|
64
|
+
|
|
65
|
+
<div class="footer-links">
|
|
66
|
+
<p data-feature="forgot-password" class="hidden"><a href="forgot-password" data-i18n="forgot_password_link">Forgot password?</a></p>
|
|
67
|
+
<p data-feature="register" class="hidden"><span data-i18n="no_account_text">Don't have an account?</span> <a href="register" data-i18n="signup_link">Sign up</a></p>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
<script src="auth.js?v=2"></script>
|
|
71
|
+
<script>
|
|
72
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
73
|
+
await AuthService.init();
|
|
74
|
+
|
|
75
|
+
// Redirect if already authenticated
|
|
76
|
+
if (AuthService.user) {
|
|
77
|
+
window.location.href = '/';
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Show social section if at least one provider is enabled
|
|
82
|
+
if (AuthService.config.features.google || AuthService.config.features.github) {
|
|
83
|
+
document.getElementById('social-section').classList.remove('hidden');
|
|
84
|
+
if (AuthService.config.features.google) document.querySelector('[data-feature="google"]').classList.remove('hidden');
|
|
85
|
+
if (AuthService.config.features.github) document.querySelector('[data-feature="github"]').classList.remove('hidden');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (AuthService.config.features.register) {
|
|
89
|
+
document.querySelector('[data-feature="register"]')?.classList.remove('hidden');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (AuthService.config.features.forgotPassword) {
|
|
93
|
+
document.querySelector('[data-feature="forgot-password"]')?.classList.remove('hidden');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const form = document.getElementById('login-form');
|
|
97
|
+
const alert = document.getElementById('alert');
|
|
98
|
+
const submitBtn = document.getElementById('submit-btn');
|
|
99
|
+
|
|
100
|
+
form.addEventListener('submit', async (e) => {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
alert.classList.add('hidden');
|
|
103
|
+
submitBtn.disabled = true;
|
|
104
|
+
submitBtn.textContent = 'Logging in...';
|
|
105
|
+
|
|
106
|
+
const email = form.email.value;
|
|
107
|
+
const password = form.password.value;
|
|
108
|
+
|
|
109
|
+
try {
|
|
110
|
+
const result = await AuthService.apiCall('/login', 'POST', { email, password });
|
|
111
|
+
|
|
112
|
+
if (result.success || result.requiresTwoFactor) {
|
|
113
|
+
if (result.requiresTwoFactor) {
|
|
114
|
+
// Redirect to 2FA page or show setup
|
|
115
|
+
window.location.href = `2fa?token=${result.tempToken}`;
|
|
116
|
+
} else {
|
|
117
|
+
window.location.href = '/';
|
|
118
|
+
}
|
|
119
|
+
} else {
|
|
120
|
+
alert.textContent = result.error || 'Login failed';
|
|
121
|
+
alert.classList.remove('hidden');
|
|
122
|
+
}
|
|
123
|
+
} catch (err) {
|
|
124
|
+
alert.textContent = 'An unexpected error occurred';
|
|
125
|
+
alert.classList.remove('hidden');
|
|
126
|
+
} finally {
|
|
127
|
+
submitBtn.disabled = false;
|
|
128
|
+
submitBtn.textContent = 'Login';
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
</script>
|
|
133
|
+
</body>
|
|
134
|
+
|
|
135
|
+
</html>
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Magic Link - Awesome Node Auth</title>
|
|
8
|
+
<link rel="stylesheet" href="base.css">
|
|
9
|
+
</head>
|
|
10
|
+
|
|
11
|
+
<body>
|
|
12
|
+
<div class="auth-container">
|
|
13
|
+
<img src="" alt="Logo" class="logo hidden">
|
|
14
|
+
<h1 class="site-name" data-i18n="site_name">Awesome Node Auth</h1>
|
|
15
|
+
<h2 data-i18n="magic_link_title">Magic Link</h2>
|
|
16
|
+
|
|
17
|
+
<div id="alert" class="alert hidden"></div>
|
|
18
|
+
|
|
19
|
+
<form id="magic-link-form">
|
|
20
|
+
<p class="footer-links" style="margin-bottom: 20px; text-align: left;" data-i18n="magic_link_instruction">
|
|
21
|
+
Enter your email and we'll send you a magic link to sign in instantly.
|
|
22
|
+
</p>
|
|
23
|
+
<div class="form-group">
|
|
24
|
+
<label for="email" data-i18n="email_label">Email</label>
|
|
25
|
+
<input type="email" id="email" name="email" required data-i18n="email_placeholder">
|
|
26
|
+
</div>
|
|
27
|
+
<button type="submit" id="submit-btn" data-feature="magicLink" data-i18n="send_magic_link_button">Send Magic Link</button>
|
|
28
|
+
</form>
|
|
29
|
+
|
|
30
|
+
<div id="success-message" class="hidden">
|
|
31
|
+
<div class="alert alert-success">
|
|
32
|
+
A magic link has been sent to your email. Check your inbox and click the link to log in.
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
35
|
+
|
|
36
|
+
<div class="footer-links">
|
|
37
|
+
<a href="login">Back to login</a>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
<script src="auth.js?v=2"></script>
|
|
42
|
+
<script>
|
|
43
|
+
document.addEventListener('DOMContentLoaded', async () => {
|
|
44
|
+
await AuthService.init();
|
|
45
|
+
|
|
46
|
+
const urlParams = new URLSearchParams(window.location.search);
|
|
47
|
+
const token = urlParams.get('token');
|
|
48
|
+
const alert = document.getElementById('alert');
|
|
49
|
+
const form = document.getElementById('magic-link-form');
|
|
50
|
+
const successMsg = document.getElementById('success-message');
|
|
51
|
+
|
|
52
|
+
// If token in URL, verify it automatically
|
|
53
|
+
if (token) {
|
|
54
|
+
form.classList.add('hidden');
|
|
55
|
+
document.querySelector('h2').textContent = 'Verifying Magic Link';
|
|
56
|
+
alert.textContent = 'Verifying your link, please wait...';
|
|
57
|
+
alert.className = 'alert';
|
|
58
|
+
alert.classList.remove('hidden');
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const result = await AuthService.apiCall('/magic-link/verify', 'POST', { token });
|
|
62
|
+
if (result.success) {
|
|
63
|
+
alert.textContent = 'Successfully logged in! Redirecting...';
|
|
64
|
+
alert.className = 'alert alert-success';
|
|
65
|
+
setTimeout(() => window.location.href = '/', 1500);
|
|
66
|
+
} else {
|
|
67
|
+
alert.textContent = result.error || 'Invalid or expired magic link';
|
|
68
|
+
alert.className = 'alert alert-error';
|
|
69
|
+
}
|
|
70
|
+
} catch (err) {
|
|
71
|
+
alert.textContent = 'An unexpected error occurred';
|
|
72
|
+
alert.className = 'alert alert-error';
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Otherwise show request form
|
|
78
|
+
form.addEventListener('submit', async (e) => {
|
|
79
|
+
e.preventDefault();
|
|
80
|
+
alert.className = 'alert hidden';
|
|
81
|
+
const submitBtn = document.getElementById('submit-btn');
|
|
82
|
+
submitBtn.disabled = true;
|
|
83
|
+
submitBtn.textContent = 'Sending...';
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
const result = await AuthService.apiCall('/magic-link/send', 'POST', {
|
|
87
|
+
email: form.email.value,
|
|
88
|
+
mode: 'login'
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (result.success) {
|
|
92
|
+
form.classList.add('hidden');
|
|
93
|
+
successMsg.classList.remove('hidden');
|
|
94
|
+
} else {
|
|
95
|
+
alert.textContent = result.error || 'Failed to send magic link';
|
|
96
|
+
alert.className = 'alert alert-error';
|
|
97
|
+
alert.classList.remove('hidden');
|
|
98
|
+
}
|
|
99
|
+
} catch (err) {
|
|
100
|
+
alert.textContent = 'An unexpected error occurred';
|
|
101
|
+
alert.className = 'alert alert-error';
|
|
102
|
+
alert.classList.remove('hidden');
|
|
103
|
+
} finally {
|
|
104
|
+
submitBtn.disabled = false;
|
|
105
|
+
submitBtn.textContent = 'Send Magic Link';
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
</script>
|
|
110
|
+
</body>
|
|
111
|
+
|
|
112
|
+
</html>
|