@javagt/express-easy-auth 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/demo/server.js ADDED
@@ -0,0 +1,195 @@
1
+ import 'dotenv/config';
2
+ import express from 'express';
3
+ import session from 'express-session';
4
+ import cookieParser from 'cookie-parser';
5
+ import helmet from 'helmet';
6
+ import cors from 'cors';
7
+ import path from 'path';
8
+ import { fileURLToPath } from 'url';
9
+ import { dirname } from 'path';
10
+
11
+ // 1. Import the library's main integration tools
12
+ import {
13
+ setupAuth,
14
+ authRouter,
15
+ SQLiteSessionStore,
16
+ authErrorLogger,
17
+ requireApiKey
18
+ } from '../src/index.js';
19
+ import { DatabaseSync } from 'node:sqlite';
20
+ import profileRouter from './profileRouter.js';
21
+
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = dirname(__filename);
24
+
25
+ // ─── CONFIG ──────────────────────────────────────────────────────────────────
26
+ const DOMAIN = process.env.DOMAIN || 'localhost';
27
+ const PORT = parseInt(process.env.PORT || '3000', 10);
28
+ const SESSION_SECRET = process.env.SESSION_SECRET || 'change-this-in-production-please';
29
+ const IS_PROD = process.env.NODE_ENV === 'production';
30
+
31
+ // Robust hostname extraction
32
+ let HOSTNAME = DOMAIN.replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
33
+ const IS_LOCALHOST = HOSTNAME.includes('localhost') || HOSTNAME.includes('127.0.0.1');
34
+ const PROTOCOL = (IS_PROD || !IS_LOCALHOST) ? 'https' : 'http';
35
+ const ORIGIN = `${PROTOCOL}://${HOSTNAME}${(!IS_PROD && IS_LOCALHOST && PORT !== 80 && PORT !== 443) ? `:${PORT}` : ''}`;
36
+
37
+ const config = {
38
+ domain: HOSTNAME,
39
+ port: PORT,
40
+ protocol: PROTOCOL,
41
+ origin: ORIGIN,
42
+ rpName: 'Auth Server Demo',
43
+ rpID: HOSTNAME,
44
+ };
45
+
46
+ const app = express();
47
+
48
+ // 2. Initialize Authentication Services
49
+ const dataDir = path.join(__dirname, '../data');
50
+ setupAuth(app, {
51
+ dataDir,
52
+ exposeErrors: !IS_PROD,
53
+ config
54
+ });
55
+
56
+ // 2b. Initialize Application Database (External to Auth Server)
57
+ const appDataDb = new DatabaseSync(path.join(dataDir, 'app_data.db'));
58
+ appDataDb.exec(`
59
+ CREATE TABLE IF NOT EXISTS profiles (
60
+ user_id TEXT PRIMARY KEY,
61
+ display_name TEXT,
62
+ bio TEXT,
63
+ avatar_url TEXT,
64
+ location TEXT,
65
+ website TEXT,
66
+ preferences TEXT, -- JSON string for app settings
67
+ created_at INTEGER NOT NULL,
68
+ updated_at INTEGER NOT NULL
69
+ );
70
+ `);
71
+
72
+ // Attach appDataDb to app for use in routers
73
+ app.set('appDataDb', appDataDb);
74
+
75
+
76
+ // ─── MIDDLEWARE ──────────────────────────────────────────────────────────────
77
+
78
+ app.set('trust proxy', 1);
79
+
80
+ app.use(helmet({
81
+ hsts: IS_PROD,
82
+ contentSecurityPolicy: {
83
+ directives: {
84
+ defaultSrc: ["'self'"],
85
+ scriptSrc: ["'self'"],
86
+ styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
87
+ fontSrc: ["'self'", 'https://fonts.gstatic.com'],
88
+ imgSrc: ["'self'", 'data:'],
89
+ connectSrc: ["'self'"],
90
+ },
91
+ },
92
+ }));
93
+
94
+ app.use(cors({
95
+ origin: ORIGIN,
96
+ credentials: true,
97
+ }));
98
+
99
+ app.use(express.json());
100
+ app.use(express.urlencoded({ extended: true }));
101
+ app.use(cookieParser(SESSION_SECRET));
102
+
103
+ // ─── SESSION ─────────────────────────────────────────────────────────────────
104
+
105
+ const sessionStore = new SQLiteSessionStore();
106
+
107
+ app.use(session({
108
+ secret: SESSION_SECRET,
109
+ store: sessionStore,
110
+ resave: false,
111
+ saveUninitialized: false,
112
+ name: 'auth.sid',
113
+ cookie: {
114
+ secure: PROTOCOL === 'https',
115
+ httpOnly: true,
116
+ sameSite: 'lax',
117
+ maxAge: 7 * 24 * 60 * 60 * 1000 // 1 week
118
+ },
119
+ }));
120
+
121
+ // ─── ROUTES ──────────────────────────────────────────────────────────────────
122
+
123
+ // 3. Mount Library & Application Routes
124
+ app.use('/api/v1/auth', authRouter);
125
+ app.use('/api/v1/profile', profileRouter);
126
+
127
+ // Sample Public API protected by API Keys
128
+ app.get('/api/public/data', requireApiKey, (req, res) => {
129
+ if (!req.permissions.includes('action:read')) {
130
+ return res.status(403).json({ error: 'Missing permission: action:read' });
131
+ }
132
+ res.json({
133
+ message: 'Success! You accessed this data with an API key.',
134
+ user: req.userId,
135
+ permissions: req.permissions,
136
+ timestamp: new Date().toISOString()
137
+ });
138
+ });
139
+
140
+ app.post('/api/public/data', requireApiKey, (req, res) => {
141
+ if (!req.permissions.includes('action:write')) {
142
+ return res.status(403).json({ error: 'Missing permission: action:write' });
143
+ }
144
+ res.json({
145
+ message: 'Success! You published data with an API key.',
146
+ publishedAt: new Date().toISOString()
147
+ });
148
+ });
149
+
150
+ // Health check
151
+ app.get('/api/health', (req, res) => {
152
+ res.json({ status: 'ok', domain: DOMAIN, timestamp: new Date().toISOString() });
153
+ });
154
+
155
+ // ─── DEMO MAILBOX (TEST ENDPOINTS) ──────────────────────────────────────────
156
+ const mailboxMessages = [];
157
+
158
+ app.get('/api/v1/test/mailbox', (req, res) => {
159
+ res.json({ messages: mailboxMessages });
160
+ });
161
+
162
+ app.post('/api/v1/test/mailbox', (req, res) => {
163
+ const { type, subject, body } = req.body;
164
+ const msg = {
165
+ id: Math.random().toString(36).substring(2, 9),
166
+ type: type || 'System',
167
+ subject: subject || 'No Subject',
168
+ body: body || '',
169
+ timestamp: Date.now()
170
+ };
171
+ mailboxMessages.unshift(msg);
172
+ if (mailboxMessages.length > 50) mailboxMessages.pop();
173
+ res.status(201).json(msg);
174
+ });
175
+
176
+ app.delete('/api/v1/test/mailbox', (req, res) => {
177
+ mailboxMessages.length = 0;
178
+ res.status(204).send();
179
+ });
180
+
181
+ // Serve frontend from demo directory
182
+ app.use(express.static(path.join(__dirname, './public')));
183
+
184
+ // ─── ERROR HANDLER ───────────────────────────────────────────────────────────
185
+
186
+ app.use(authErrorLogger);
187
+
188
+ // ─── START ───────────────────────────────────────────────────────────────────
189
+
190
+ app.listen(PORT, () => {
191
+ console.log(`\n🔐 Auth Demo Server running`);
192
+ console.log(` URL: ${ORIGIN}`);
193
+ console.log(` RPID: ${config.rpID}`);
194
+ console.log(` Env: ${IS_PROD ? 'production' : 'development'}\n`);
195
+ });
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Example 01: Basic setup
3
+ *
4
+ * Demonstrates how to initialize the auth library and protect a basic route.
5
+ */
6
+
7
+ import express from 'express';
8
+ import session from 'express-session';
9
+ // In your app, use: import { setupAuth, authRouter, SQLiteSessionStore, requireAuth } from 'auth-server';
10
+ import {
11
+ setupAuth,
12
+ authRouter,
13
+ SQLiteSessionStore,
14
+ requireAuth,
15
+ AuthClient
16
+ } from '../src/index.js';
17
+
18
+ const app = express();
19
+
20
+ // 1. JSON Middleware is required for API routes
21
+ app.use(express.json());
22
+
23
+ // 2. Initialize the Library
24
+ setupAuth(app, {
25
+ dataDir: './data-example', // Directory for SQLite DBs
26
+ exposeErrors: process.env.NODE_ENV !== 'production', // Explicit debug setting
27
+ config: {
28
+ domain: 'localhost',
29
+ rpName: 'Basic Example App',
30
+ rpID: 'localhost',
31
+ origin: 'http://localhost:3000'
32
+ }
33
+ });
34
+
35
+ // 3. Configure Sessions
36
+ // The library provides SQLiteSessionStore which is optimized for auth-server
37
+ app.use(session({
38
+ secret: 'keyboard-cat-secret',
39
+ store: new SQLiteSessionStore(),
40
+ resave: false,
41
+ saveUninitialized: false,
42
+ cookie: { secure: false } // 'true' in production with HTTPS
43
+ }));
44
+
45
+ // 1. Mount Auth (Unified Router)
46
+ app.use('/api/v1/auth', authRouter);
47
+
48
+ // 5. Protect your routes
49
+ app.get('/dashboard', requireAuth, (req, res) => {
50
+ res.json({
51
+ message: 'Access granted!',
52
+ userId: req.userId,
53
+ sessionID: req.sessionID
54
+ });
55
+ });
56
+
57
+ // 6. Client-side SDK Demo
58
+ app.get('/', (req, res) => {
59
+ res.send(`
60
+ <!DOCTYPE html>
61
+ <html>
62
+ <head><title>Auth SDK Demo - Basic</title></head>
63
+ <body style="font-family: sans-serif; padding: 2rem; background: #f4f4f9;">
64
+ <h1>Auth SDK Demo: Basic Setup</h1>
65
+ <p>This page uses the <b>AuthClient</b> SDK to interact with the backend.</p>
66
+ <div id="status">Checking status...</div>
67
+ <hr>
68
+ <button id="regBtn">Register Demo User</button>
69
+ <button id="loginBtn">Login Demo User</button>
70
+ <button id="logoutBtn">Logout</button>
71
+
72
+ <script type="module">
73
+ import { AuthClient } from '/auth-sdk.js';
74
+ const auth = new AuthClient();
75
+
76
+ async function updateStatus() {
77
+ const status = await auth.getStatus();
78
+ document.getElementById('status').innerText = 'Authenticated: ' + status.authenticated + (status.username ? ' (' + status.username + ')' : '');
79
+ console.log('Current Status:', status);
80
+ }
81
+
82
+ document.getElementById('regBtn').onclick = async () => {
83
+ const username = 'user_' + Math.floor(Math.random()*1000);
84
+ try {
85
+ const res = await auth.register(username, username + '@example.com', 'password123');
86
+ alert('Registered: ' + username);
87
+ await updateStatus();
88
+ } catch (e) { alert('Error: ' + e.message); }
89
+ };
90
+
91
+ document.getElementById('loginBtn').onclick = async () => {
92
+ const username = prompt('Username?');
93
+ try {
94
+ await auth.login(username, 'password123');
95
+ alert('Logged in!');
96
+ await updateStatus();
97
+ } catch (e) { alert('Error: ' + e.message); }
98
+ };
99
+
100
+ document.getElementById('logoutBtn').onclick = async () => {
101
+ await auth.logout();
102
+ alert('Logged out');
103
+ await updateStatus();
104
+ };
105
+
106
+ updateStatus();
107
+ </script>
108
+ </body>
109
+ </html>
110
+ `);
111
+ });
112
+
113
+ const PORT = 3000;
114
+ app.listen(PORT, () => {
115
+ console.log(`Example 01 running at http://localhost:${PORT}`);
116
+ console.log(`- Try: GET http://localhost:${PORT}/api/v1/auth/status`);
117
+ console.log(`- Try: GET http://localhost:${PORT}/dashboard (will return 401 until login)`);
118
+ });
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Example 02: Passkeys (WebAuthn)
3
+ *
4
+ * Demonstrates how to enable and manage biometric passkeys.
5
+ */
6
+
7
+ import express from 'express';
8
+ import session from 'express-session';
9
+ import {
10
+ setupAuth,
11
+ authRouter,
12
+ SQLiteSessionStore,
13
+ requireAuth
14
+ } from '../src/index.js';
15
+
16
+ const app = express();
17
+ app.use(express.json());
18
+
19
+ setupAuth(app, {
20
+ dataDir: './data-example',
21
+ config: {
22
+ domain: 'localhost',
23
+ rpName: 'Passkey Example',
24
+ rpID: 'localhost',
25
+ origin: 'http://localhost:3000'
26
+ }
27
+ });
28
+
29
+ app.use(session({
30
+ secret: 'passkey-secret',
31
+ store: new SQLiteSessionStore(),
32
+ resave: false,
33
+ saveUninitialized: false,
34
+ cookie: { secure: false }
35
+ }));
36
+
37
+ // 1. Mount Auth (Unified Router)
38
+ app.use('/api/v1/auth', authRouter);
39
+
40
+ // 2. The SDK will now automatically use /api/v1/auth/...
41
+
42
+ app.get('/', (req, res) => {
43
+ res.send(`
44
+ <!DOCTYPE html>
45
+ <html>
46
+ <head><title>Auth SDK Demo - Passkeys</title></head>
47
+ <body style="font-family: sans-serif; padding: 2rem; background: #fdf6e3;">
48
+ <h1>Auth SDK Demo: Passkeys</h1>
49
+ <p>This page demonstrates <b>WebAuthn</b> registration and login via the SDK.</p>
50
+ <div id="status">Checking status...</div>
51
+ <hr>
52
+ <div id="controls">
53
+ <button id="regBtn">1. Register Password Account</button>
54
+ <button id="pkRegBtn">2. Register Passkey</button>
55
+ <button id="pkLoginBtn">3. Login with Passkey</button>
56
+ </div>
57
+
58
+ <script type="module">
59
+ import { AuthClient } from '/auth-sdk.js';
60
+ const auth = new AuthClient();
61
+
62
+ async function updateStatus() {
63
+ const status = await auth.getStatus();
64
+ document.getElementById('status').innerHTML = \`
65
+ <strong>Authenticated:</strong> \${status.authenticated}<br>
66
+ <strong>User ID:</strong> \${status.userId || 'None'}<br>
67
+ <strong>Passkeys:</strong> \${status.passkeyCount || 0}
68
+ \`;
69
+ }
70
+
71
+ document.getElementById('regBtn').onclick = async () => {
72
+ const u = 'pk_user_' + Math.floor(Math.random()*1000);
73
+ try {
74
+ await auth.register(u, u + '@example.com', 'password123');
75
+ alert('Created password account: ' + u + '. Now register a passkey!');
76
+ await updateStatus();
77
+ } catch (e) { alert(e.message); }
78
+ };
79
+
80
+ document.getElementById('pkRegBtn').onclick = async () => {
81
+ try {
82
+ await auth.registerPasskey('My Laptop');
83
+ alert('Passkey Registered!');
84
+ await updateStatus();
85
+ } catch (e) { alert(e.message); }
86
+ };
87
+
88
+ document.getElementById('pkLoginBtn').onclick = async () => {
89
+ try {
90
+ const res = await auth.loginWithPasskey();
91
+ alert('Logged in with Passkey!');
92
+ await updateStatus();
93
+ } catch (e) { alert(e.message); }
94
+ };
95
+
96
+ updateStatus();
97
+ </script>
98
+ </body>
99
+ </html>
100
+ `);
101
+ });
102
+
103
+ const PORT = 3000;
104
+ app.listen(PORT, () => {
105
+ console.log(`Example 02 running at http://localhost:${PORT}`);
106
+ });
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Example 03: API Keys
3
+ *
4
+ * Demonstrates service-to-service authentication using API keys.
5
+ */
6
+
7
+ import express from 'express';
8
+ import { setupAuth, authRouter, requireApiKey } from '../src/index.js';
9
+
10
+ const app = express();
11
+ app.use(express.json());
12
+
13
+ setupAuth(app, {
14
+ dataDir: './data-example',
15
+ config: { domain: 'localhost' }
16
+ });
17
+
18
+ // 1. Mount Auth router for identity management
19
+ app.use('/api/v1/auth', authRouter);
20
+
21
+ // 2. Protect routes with the requireApiKey middleware
22
+ // It looks for 'X-API-Key' or 'Authorization: Bearer <key>'
23
+ app.get('/api/v1/protected-data', requireApiKey, (req, res) => {
24
+ // Authentication verified
25
+ // req.userId is set to the owner of the key
26
+ // req.permissions contains the key's allowed scopes
27
+
28
+ res.json({
29
+ success: true,
30
+ message: 'Accessed via API key',
31
+ owner: req.userId,
32
+ permissions: req.permissions
33
+ });
34
+ });
35
+
36
+ // 3. Granular permission check
37
+ app.post('/api/v1/write-data', requireApiKey, (req, res) => {
38
+ if (!req.permissions.includes('action:write')) {
39
+ return res.status(403).json({ error: 'Key lacks action:write permission' });
40
+ }
41
+
42
+ res.json({ success: true, message: 'Write operation authorized' });
43
+ });
44
+
45
+ app.get('/', (req, res) => {
46
+ res.send(`
47
+ <!DOCTYPE html>
48
+ <html>
49
+ <head><title>Auth SDK Demo - API Keys</title></head>
50
+ <body style="font-family: sans-serif; padding: 2rem; background: #eef2ff;">
51
+ <h1>Auth SDK Demo: API Keys</h1>
52
+ <p>This page demonstrates API key lifecycle management via the SDK.</p>
53
+
54
+ <div id="controls">
55
+ <button id="loginBtn">1. Login as Admin</button>
56
+ <button id="createKeyBtn">2. Create API Key</button>
57
+ <button id="testKeyBtn">3. Test Created Key</button>
58
+ </div>
59
+ <hr>
60
+ <div id="output" style="background: #333; color: #fff; padding: 1rem; border-radius: 4px; font-family: monospace; min-height: 100px; white-space: pre-wrap;">Console Output...</div>
61
+
62
+ <script type="module">
63
+ import { AuthClient } from '/auth-sdk.js';
64
+ const auth = new AuthClient();
65
+ let lastKey = null;
66
+
67
+ const log = (msg) => {
68
+ document.getElementById('output').innerText += '\\n> ' + msg;
69
+ };
70
+
71
+ document.getElementById('loginBtn').onclick = async () => {
72
+ try {
73
+ // Ensure a user exists first
74
+ await auth.register('api_admin', 'api@example.com', 'admin123').catch(() => {});
75
+ await auth.login('api_admin', 'admin123');
76
+ log('Logged in as api_admin');
77
+ } catch (e) { log('Login Error: ' + e.message); }
78
+ };
79
+
80
+ document.getElementById('createKeyBtn').onclick = async () => {
81
+ try {
82
+ const res = await auth.createApiKey('Demo Key', ['action:read']);
83
+ lastKey = res.key;
84
+ log('Key Created: ' + lastKey);
85
+ } catch (e) { log('Error: ' + e.message); }
86
+ };
87
+
88
+ document.getElementById('testKeyBtn').onclick = async () => {
89
+ if (!lastKey) return alert('Create a key first!');
90
+ try {
91
+ const res = await fetch('/api/v1/protected-data', {
92
+ headers: { 'X-API-Key': lastKey }
93
+ });
94
+ const data = await res.json();
95
+ log('Test Result: ' + JSON.stringify(data));
96
+ } catch (e) { log('Test Error: ' + e.message); }
97
+ };
98
+ </script>
99
+ </body>
100
+ </html>
101
+ `);
102
+ });
103
+
104
+ const PORT = 3000;
105
+ app.listen(PORT, () => {
106
+ console.log(`Example 03 running at http://localhost:${PORT}`);
107
+ console.log(`- Request with: curl -H "X-API-Key: YOUR_KEY" http://localhost:${PORT}/api/v1/protected-data`);
108
+ });
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Example 04: TOTP 2FA
3
+ *
4
+ * Demonstrates the full lifecycle of setting up and using TOTP 2FA.
5
+ */
6
+
7
+ import express from 'express';
8
+ import session from 'express-session';
9
+ import {
10
+ setupAuth,
11
+ authRouter,
12
+ SQLiteSessionStore,
13
+ requireAuth,
14
+ requireFreshAuth
15
+ } from '../src/index.js';
16
+
17
+ const app = express();
18
+ app.use(express.json());
19
+
20
+ setupAuth(app, {
21
+ dataDir: './data-example',
22
+ config: { domain: 'localhost' }
23
+ });
24
+
25
+ app.use(session({
26
+ secret: 'totp-secret',
27
+ store: new SQLiteSessionStore(),
28
+ resave: false,
29
+ saveUninitialized: false,
30
+ cookie: { secure: false }
31
+ }));
32
+
33
+ // 1. Mount Auth (Unified Router)
34
+ app.use('/api/v1/auth', authRouter);
35
+
36
+ // 1. Initial login (Password only)
37
+ // POST /api/auth/login
38
+
39
+ // 2. Setup 2FA (Requires valid session)
40
+ // POST /api/auth/2fa/setup -> Returns { secret, qrCode }
41
+
42
+ // 3. Verify Setup (Enables 2FA for the account)
43
+ // POST /api/auth/2fa/verify-setup { token: "123456" }
44
+
45
+ // 4. Test Fresh Auth (Protecting sensitive actions)
46
+ // requireFreshAuth ensures the user has authed within the last few minutes
47
+ app.post('/api/user/delete-account', requireAuth, requireFreshAuth, (req, res) => {
48
+ res.json({ success: true, message: 'Sensitive action authorized' });
49
+ });
50
+
51
+ app.get('/', (req, res) => {
52
+ res.send(`
53
+ <!DOCTYPE html>
54
+ <html>
55
+ <head><title>Auth SDK Demo - TOTP 2FA</title></head>
56
+ <body style="font-family: sans-serif; padding: 2rem; background: #fff5f5;">
57
+ <h1>Auth SDK Demo: TOTP 2FA</h1>
58
+ <p>This page demonstrates <b>TOTP</b> (Authenticator App) setup via the SDK.</p>
59
+ <div id="status">Checking status...</div>
60
+ <hr>
61
+ <div id="controls">
62
+ <button id="loginBtn">1. Login</button>
63
+ <button id="setupBtn">2. Setup 2FA</button>
64
+ </div>
65
+ <div id="qrArea" style="margin: 1rem 0; display: none;">
66
+ <p>Scan this QR code in your app (Google Authenticator, etc):</p>
67
+ <img id="qrImg" src="" style="border: 1px solid #ccc; padding: 10px;">
68
+ <p>Then enter the token below:</p>
69
+ <input type="text" id="tokenIn" placeholder="123456">
70
+ <button id="verifyBtn">Verify & Enable</button>
71
+ </div>
72
+
73
+ <script type="module">
74
+ import { AuthClient } from '/auth-sdk.js';
75
+ const auth = new AuthClient();
76
+
77
+ async function updateStatus() {
78
+ const status = await auth.getStatus();
79
+ document.getElementById('status').innerHTML = \`
80
+ <strong>Authenticated:</strong> \${status.authenticated}<br>
81
+ <strong>2FA Enabled:</strong> \${status.totpEnabled}<br>
82
+ <strong>Current User:</strong> \${status.username || 'None'}
83
+ \`;
84
+ }
85
+
86
+ document.getElementById('loginBtn').onclick = async () => {
87
+ const u = 'totp_user_' + Math.floor(Math.random()*1000);
88
+ try {
89
+ await auth.register(u, u + '@example.com', 'password123');
90
+ await auth.login(u, 'password123');
91
+ alert('Logged in! Now setup 2FA.');
92
+ await updateStatus();
93
+ } catch (e) { alert(e.message); }
94
+ };
95
+
96
+ document.getElementById('setupBtn').onclick = async () => {
97
+ try {
98
+ const res = await auth.setup2FA();
99
+ document.getElementById('qrImg').src = res.qrCode;
100
+ document.getElementById('qrArea').style.display = 'block';
101
+ alert('Scan the QR code!');
102
+ } catch (e) { alert(e.message); }
103
+ };
104
+
105
+ document.getElementById('verifyBtn').onclick = async () => {
106
+ const token = document.getElementById('tokenIn').value;
107
+ try {
108
+ await auth.verify2FASetup(token);
109
+ alert('2FA Enabled Successfully!');
110
+ document.getElementById('qrArea').style.display = 'none';
111
+ await updateStatus();
112
+ } catch (e) { alert(e.message); }
113
+ };
114
+
115
+ updateStatus();
116
+ </script>
117
+ </body>
118
+ </html>
119
+ `);
120
+ });
121
+
122
+ const PORT = 3000;
123
+ app.listen(PORT, () => {
124
+ console.log(`Example 04 running at http://localhost:${PORT}`);
125
+ });