mbkauthe 4.4.0 → 4.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,9 +4,11 @@
4
4
  [![License](https://img.shields.io/badge/License-GPL--2.0-blue.svg)](LICENSE)
5
5
  [![Node.js](https://img.shields.io/badge/node-%3E%3D14.0.0-brightgreen.svg)](https://nodejs.org/)
6
6
  [![Publish](https://github.com/MIbnEKhalid/mbkauthe/actions/workflows/publish.yml/badge.svg?branch=main)](https://github.com/MIbnEKhalid/mbkauthe/actions/workflows/publish.yml)
7
+ [![Downloads](https://img.shields.io/npm/dm/mbkauthe.svg)](https://www.npmjs.com/package/mbkauthe)
8
+
7
9
 
8
10
  <p align="center">
9
- <img height="64px" src="./public/icon.svg" alt="MBKAuthe" />
11
+ <img height="64px" src="./public/logo.png" alt="MBKAuthe" />
10
12
  </p>
11
13
 
12
14
  **MBKAuthe** is a production-ready authentication system for Node.js with Express and PostgreSQL. Features include secure login, 2FA, role-based access, OAuth (GitHub & Google), multi-session support, and multi-app user management.
package/docs/db.md CHANGED
@@ -193,8 +193,8 @@ The system handles various error cases:
193
193
  ## Login Page Updates
194
194
 
195
195
  The login page now includes:
196
- - A "Continue with GitHub" button
197
- - A "Continue with Google" button
196
+ - A "Login with GitHub" button
197
+ - A "Login with Google" button
198
198
  - A divider ("or") between regular and OAuth login
199
199
  - Proper styling that matches your existing design
200
200
 
package/index.js CHANGED
@@ -70,7 +70,7 @@ if (process.env.test === "dev") {
70
70
  const port = 5555;
71
71
  app.use(router);
72
72
  app.get(["/dashboard", "/home", "/"], (req, res) => {
73
- return res.redirect("/mbkauthe/login");
73
+ return res.redirect("/mbkauthe/");
74
74
  });
75
75
  app.get("/showmessage", (req, res) => {
76
76
  //uncomment line 26 on showmessage.handlebars for testing, after testing comment it back
@@ -150,65 +150,26 @@ router.get('/user/profilepic', async (req, res) => {
150
150
  });
151
151
 
152
152
  // Test route
153
- router.get('/test', validateSession, LoginLimit, async (req, res) => {
154
- if (req.session?.user) {
155
- return res.send(`
156
- <head>
157
- <meta name="viewport" content="width=device-width,initial-scale=1">
158
- <script src="/mbkauthe/main.js"></script>
159
- <style>
160
- :root{--bg:#f7fafc;--card:#ffffff;--muted:#6b7280;--accent:#2563eb;--success:#059669;--danger:#ef4444;--radius:12px}
161
- html,body{height:100%;margin:0;font-family:Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial}
162
- body{background:linear-gradient(180deg, #f0f4ff 0%, var(--bg) 100%);display:flex;align-items:center;justify-content:center;padding:32px}
163
- .container{width:100%;max-width:920px}
164
- .card{background:var(--card);border-radius:var(--radius);padding:22px;box-shadow:0 6px 24px rgba(16,24,40,0.06);display:grid;grid-template-columns:120px 1fr;gap:20px;align-items:start}
165
- .avatar{width:96px;height:96px;border-radius:50%;background:linear-gradient(135deg,#e6eefc,#fff);display:flex;align-items:center;justify-content:center;font-weight:700;color:var(--accent);font-size:28px;border:2px solid rgba(37,99,235,0.08);position:relative;overflow:hidden}
166
- .avatar img{width:100%;height:100%;object-fit:cover;display:block}
167
- .avatar .initials{position:absolute;inset:0;display:none;align-items:center;justify-content:center;font-weight:700;color:var(--accent);font-size:28px;background:linear-gradient(135deg,#eef2ff,#fff)}
168
- .meta{display:flex;flex-direction:column;gap:8px}
169
- .status{color:var(--success);font-weight:600;display:flex;align-items:center;gap:8px}
170
- .user-title{font-size:18px;font-weight:700;margin:0}
171
- .user-sub{color:var(--muted);margin:0;font-size:13px}
172
- .details{background:#fbfdff;border-radius:10px;padding:12px;font-family:monospace;font-size:13px;color:#111;display:flex;flex-direction:column;gap:6px}
173
- .actions{margin-top:14px;display:flex;flex-wrap:wrap;gap:8px}
174
- .btn{display:inline-flex;align-items:center;gap:8px;padding:9px 14px;border-radius:10px;border:1px solid transparent;cursor:pointer;text-decoration:none}
175
- .btn-primary{background:var(--accent);color:white}
176
- .btn-outline{background:transparent;border-color:rgba(37,99,235,0.12);color:var(--accent)}
177
- .btn-danger{background:var(--danger);color:#fff}
178
- a{color:inherit}
179
- @media (max-width:640px){.card{grid-template-columns:1fr;align-items:stretch}.avatar{width:80px;height:80px}}
180
- </style>
181
- </head>
182
- <div class="container">
183
- <div class="card" role="region" aria-label="User Session">
184
- <div class="avatar" aria-hidden="true">
185
- <img src="/mbkauthe/user/profilepic?u=${encodeURIComponent(req.session.user.username)}" alt="Avatar for ${req.session.user.username}" title="${req.session.user.fullname || req.session.user.username}" loading="lazy" decoding="async" width="96" height="96" onerror="this.style.display='none';var s=this.nextElementSibling; if(s) s.style.display='flex';" />
186
- <div class="initials" aria-hidden="true" style="display:none">${(req.session.user.fullname && req.session.user.fullname[0]) || req.session.user.username[0]}</div>
187
- </div>
188
- <div class="meta">
189
- <div>
190
- <div class="status">✅ Authentication successful</div>
191
- <h3 class="user-title">${req.session.user.username} <small style="color:var(--muted);font-weight:600">· ${req.session.user.role}</small></h3>
192
- <p class="user-sub">ID: ${req.session.user.id} · Session: ${req.session.user.sessionId.slice(0,8)}…</p>
193
- </div>
194
-
195
- <div class="details" aria-live="polite">
196
- <div>Full Name: ${req.session.user.fullname || 'N/A'}</div>
197
- <div>Allowed Apps: ${Array.isArray(req.session.user.allowedApps) ? req.session.user.allowedApps.join(', ') : 'N/A'}</div>
198
- </div>
199
-
200
- <div class="actions">
201
- <button class="btn btn-primary" onclick="logout()" aria-label="Log out">Logout</button>
202
- <a class="btn btn-outline" href="https://portal.mbktech.org/">Web Portal</a>
203
- <a class="btn btn-outline" href="https://portal.mbktech.org/user/settings">User Settings</a>
204
- <a class="btn btn-outline" href="/mbkauthe/info">Info Page</a>
205
- <a class="btn btn-outline" href="/mbkauthe/login">Login Page</a>
206
- </div>
207
- </div>
208
- </div>
209
- </div>
210
- `);
211
- }
153
+ router.get(['/test', '/'], validateSession, LoginLimit, async (req, res) => {
154
+ const { username, fullname, role, id, sessionId, allowedApps } = req.session.user;
155
+
156
+ const sessionExpiry = req.session.cookie?.expires
157
+ ? new Date(req.session.cookie.expires).toISOString()
158
+ : null;
159
+
160
+ return res.render('test.handlebars', {
161
+ layout: false,
162
+ username,
163
+ fullname: fullname || 'N/A',
164
+ role,
165
+ id,
166
+ sessionIdShort: sessionId.slice(0, 8),
167
+ profilePicUrl: encodeURIComponent(username),
168
+ displayName: fullname || username,
169
+ initial: (fullname && fullname[0]) || username[0],
170
+ allowedApps: Array.isArray(allowedApps) ? allowedApps.join(', ') : 'N/A',
171
+ sessionExpiry
172
+ });
212
173
  });
213
174
 
214
175
  router.post('/test', validateSession, LoginLimit, async (req, res) => {
@@ -476,15 +437,13 @@ export async function getLatestVersion() {
476
437
  }
477
438
  }
478
439
 
440
+
441
+ const { APP_NAME, DOMAIN, IS_DEPLOYED, loginRedirectURL } = mbkautheVar;
442
+ const safe_mbkautheVar = { APP_NAME, DOMAIN, IS_DEPLOYED, loginRedirectURL };
443
+
479
444
  // Info page
480
445
  router.get(["/info", "/i"], LoginLimit, async (req, res) => {
481
446
  let latestVersion;
482
- const parameters = req.query;
483
- let authorized = false;
484
-
485
- if (parameters.password && mbkautheVar.Main_SECRET_TOKEN) {
486
- authorized = String(parameters.password) === String(mbkautheVar.Main_SECRET_TOKEN);
487
- }
488
447
 
489
448
  try {
490
449
  latestVersion = await getLatestVersion();
@@ -493,13 +452,12 @@ router.get(["/info", "/i"], LoginLimit, async (req, res) => {
493
452
  }
494
453
 
495
454
  try {
496
- res.render("info.handlebars", {
455
+ res.render("info_mbkauthe.handlebars", {
497
456
  layout: false,
498
- mbkautheVar: mbkautheVar,
499
- version: packageJson.version,
457
+ mbkautheVar: safe_mbkautheVar,
458
+ CurrentVersion: packageJson.version,
500
459
  APP_VERSION: appVersion,
501
- latestVersion,
502
- authorized: authorized,
460
+ latestVersion
503
461
  });
504
462
  } catch (err) {
505
463
  console.error("[mbkauthe] Error fetching version information:", err);
@@ -517,6 +475,22 @@ router.get(["/info", "/i"], LoginLimit, async (req, res) => {
517
475
  }
518
476
  });
519
477
 
478
+ router.get(["/info.json", "/i.json"], LoginLimit, async (req, res) => {
479
+ let latestVersion;
480
+ try {
481
+ latestVersion = await getLatestVersion();
482
+ } catch (err) {
483
+ console.error("[mbkauthe] Error fetching package-lock.json:", err);
484
+ }
485
+
486
+ try {
487
+ res.json({ mbkautheVar: safe_mbkautheVar, CurrentVersion: packageJson.version, APP_VERSION: appVersion, latestVersion });
488
+ } catch (err) {
489
+ console.error("[mbkauthe] Error fetching version information:", err);
490
+ res.status(500).json({ success: false, message: "Failed to fetch version information" });
491
+ }
492
+ });
493
+
520
494
  // Terminate all sessions (admin endpoint)
521
495
  router.post("/api/terminateAllSessions", AdminOperationLimit, authenticate(mbkautheVar.Main_SECRET_TOKEN), async (req, res) => {
522
496
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mbkauthe",
3
- "version": "4.4.0",
3
+ "version": "4.6.0",
4
4
  "description": "MBKTech's reusable authentication system for Node.js applications.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -180,13 +180,13 @@
180
180
  <h2><i class="fas fa-code-branch"></i> Version Information</h2>
181
181
  <div class="info-row">
182
182
  <div class="info-label">Current Version:</div>
183
- <div class="info-value" id="CurrentVersion">{{version}}</div>
183
+ <div class="info-value" id="CurrentVersion">{{CurrentVersion}}</div>
184
184
  </div>
185
185
  <div class="info-row">
186
186
  <div class="info-label">Latest Version:</div>
187
187
  <div class="info-value">
188
188
  {{latestVersion}}
189
- {{#if (eq latestVersion version)}}
189
+ {{#if (eq latestVersion CurrentVersion)}}
190
190
  <span class="version-status version-up-to-date">
191
191
  <i class="fas fa-check-circle"></i> Up to date
192
192
  </span>
@@ -206,7 +206,7 @@
206
206
  <div class="info-value">{{APP_VERSION}}</div>
207
207
  </div>
208
208
  <div class="info-row">
209
- <div class="info-label">Domain:</div>
209
+ <div class="info-label">DOMAIN:</div>
210
210
  <div class="info-value">{{mbkautheVar.DOMAIN}}</div>
211
211
  </div>
212
212
  <div class="info-row">
@@ -93,7 +93,7 @@
93
93
  {{#if githubLoginEnabled }}
94
94
  <a type="button" id="githubLoginBtn" class="btn-social btn-switch-side last-used-parent">
95
95
  <i class="fab fa-github"></i>
96
- <span>Continue with GitHub</span>
96
+ <span>Login with GitHub</span>
97
97
  {{#if lastLoginGithub}}<span class="last-used-badge" aria-hidden="true"
98
98
  title="Last used">Last</span>{{/if}}
99
99
  </a>
@@ -101,7 +101,7 @@
101
101
  {{#if googleLoginEnabled }}
102
102
  <a type="button" id="googleLoginBtn" class="btn-social btn-google-side last-used-parent">
103
103
  <i class="fab fa-google"></i>
104
- <span>Continue with Google</span>
104
+ <span>Login with Google</span>
105
105
  {{#if lastLoginGoogle}}<span class="last-used-badge" aria-hidden="true"
106
106
  title="Last used">Last</span>{{/if}}
107
107
  </a>
@@ -182,13 +182,13 @@
182
182
  </div>
183
183
  <div class="social-icons-row">
184
184
  <a type="button" class="swi s mobile-github-btn last-used-parent"
185
- title="Continue with GitHub">
185
+ title="Login with GitHub">
186
186
  <i class="fab fa-github"></i>
187
187
  {{#if lastLoginGithub}}<span class="last-used-badge" aria-hidden="true"
188
188
  title="Last used">Last</span>{{/if}}
189
189
  </a>
190
190
  <a type="button" class="swi s mobile-google-btn last-used-parent"
191
- title="Continue with Google">
191
+ title="Login with Google">
192
192
  <i class="fab fa-google"></i>
193
193
  {{#if lastLoginGoogle}}<span class="last-used-badge" aria-hidden="true"
194
194
  title="Last used">Last</span>{{/if}}
@@ -48,7 +48,7 @@
48
48
  display: flex;
49
49
  justify-content: space-between;
50
50
  align-items: center;
51
- padding: 1rem 1.5rem;
51
+ padding: 10px 1.5rem;
52
52
  max-width: 1400px;
53
53
  margin: 0 auto;
54
54
  }
@@ -69,6 +69,7 @@
69
69
  .logo-text {
70
70
  font-size: 2rem;
71
71
  font-weight: 700;
72
+ padding-top: 10px;
72
73
  color: var(--light);
73
74
  }
74
75
 
@@ -78,7 +79,7 @@
78
79
 
79
80
  .logo-comp {
80
81
  margin-top: 20px;
81
- font-size: 1.2rem;
82
+ font-size: 1rem;
82
83
  font-weight: bold;
83
84
  color: var(--text-light);
84
85
  }
@@ -608,7 +609,7 @@
608
609
  flex-direction: column;
609
610
  width: 40%;
610
611
  background: linear-gradient(135deg, rgba(33, 150, 243, 0.08), rgba(0, 184, 148, 0.08));
611
- padding: 3rem 2rem;
612
+ padding: 2.5rem 2rem;
612
613
  border-right: 1px solid rgba(0, 184, 148, 0.3);
613
614
  position: relative;
614
615
  overflow: hidden;
@@ -803,6 +804,24 @@
803
804
  }
804
805
  }
805
806
 
807
+ @media (max-width: 400px) {
808
+
809
+ .logo svg,
810
+ .logo-image {
811
+ height: 30px;
812
+ }
813
+
814
+ .logo-text {
815
+ font-size: 1.6rem;
816
+ }
817
+
818
+ .mbkauthe-btn-login {
819
+ font-size: 1rem !important;
820
+ padding: 6px !important;
821
+ }
822
+
823
+ }
824
+
806
825
  @media (max-width: 768px) {
807
826
  .login-box {
808
827
  padding: 2rem;
@@ -0,0 +1,260 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ {{> head pageTitle="Session Test" ogUrl="/mbkauthe/test"}}
5
+
6
+ <body>
7
+ {{> header}}
8
+
9
+ <style>
10
+ .session-card {
11
+ background: rgba(10, 20, 20, 0.95);
12
+ backdrop-filter: blur(10px);
13
+ border-radius: var(--border-radius);
14
+ padding: 2.5rem;
15
+ width: 100%;
16
+ max-width: 760px;
17
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
18
+ border: 1px solid rgba(0, 184, 148, 0.2);
19
+ position: relative;
20
+ z-index: 2;
21
+ transition: var(--transition);
22
+ display: grid;
23
+ grid-template-columns: 110px 1fr;
24
+ gap: 2rem;
25
+ align-items: start;
26
+ }
27
+
28
+ .session-card:hover {
29
+ box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
30
+ border-color: rgba(0, 184, 148, 0.3);
31
+ }
32
+
33
+ .avatar {
34
+ width: 96px;
35
+ height: 96px;
36
+ border-radius: 50%;
37
+ background: linear-gradient(135deg, rgba(0, 184, 148, 0.2), rgba(33, 150, 243, 0.15));
38
+ border: 2px solid rgba(0, 184, 148, 0.3);
39
+ position: relative;
40
+ overflow: hidden;
41
+ display: flex;
42
+ align-items: center;
43
+ justify-content: center;
44
+ }
45
+
46
+ .avatar img {
47
+ width: 100%;
48
+ height: 100%;
49
+ object-fit: cover;
50
+ display: block;
51
+ }
52
+
53
+ .avatar .initials {
54
+ position: absolute;
55
+ inset: 0;
56
+ display: none;
57
+ align-items: center;
58
+ justify-content: center;
59
+ font-weight: 700;
60
+ color: var(--accent);
61
+ font-size: 2rem;
62
+ }
63
+
64
+ .session-meta {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: 1rem;
68
+ }
69
+
70
+ .session-status {
71
+ color: var(--success);
72
+ font-weight: 600;
73
+ display: flex;
74
+ align-items: center;
75
+ gap: 8px;
76
+ font-size: 0.95rem;
77
+ }
78
+
79
+ .session-title {
80
+ font-size: 1.3rem;
81
+ font-weight: 700;
82
+ color: var(--light);
83
+ margin: 0;
84
+ }
85
+
86
+ .session-sub {
87
+ color: var(--text-light);
88
+ font-size: 0.85rem;
89
+ margin: 0;
90
+ }
91
+
92
+ .session-details {
93
+ background: rgba(0, 0, 0, 0.3);
94
+ border: 1px solid rgba(0, 184, 148, 0.15);
95
+ border-radius: var(--border-radius);
96
+ padding: 1rem 1.2rem;
97
+ font-family: 'Courier New', monospace;
98
+ font-size: 0.85rem;
99
+ color: var(--text-light);
100
+ display: flex;
101
+ flex-direction: column;
102
+ gap: 6px;
103
+ }
104
+
105
+ .session-details div span {
106
+ color: var(--accent);
107
+ font-weight: 600;
108
+ }
109
+
110
+ .session-actions {
111
+ display: flex;
112
+ flex-wrap: wrap;
113
+ gap: 10px;
114
+ }
115
+
116
+ .btn {
117
+ display: inline-flex;
118
+ align-items: center;
119
+ gap: 8px;
120
+ padding: 10px 18px;
121
+ border-radius: var(--border-radius);
122
+ border: none;
123
+ cursor: pointer;
124
+ font-size: 0.9rem;
125
+ font-weight: 600;
126
+ text-decoration: none;
127
+ transition: var(--transition);
128
+ }
129
+
130
+ .btn-primary {
131
+ background: var(--accent);
132
+ color: var(--dark);
133
+ }
134
+
135
+ .btn-primary:hover {
136
+ background: #00a07f;
137
+ box-shadow: 0 4px 12px rgba(0, 184, 148, 0.4);
138
+ }
139
+
140
+ .btn-outline {
141
+ background: transparent;
142
+ border: 1px solid rgba(0, 184, 148, 0.35);
143
+ color: var(--accent);
144
+ }
145
+
146
+ .btn-outline:hover {
147
+ background: rgba(0, 184, 148, 0.1);
148
+ border-color: var(--accent);
149
+ }
150
+
151
+ .btn-danger {
152
+ background: var(--danger);
153
+ color: #fff;
154
+ }
155
+
156
+ .btn-danger:hover {
157
+ background: #e55f5f;
158
+ box-shadow: 0 4px 12px rgba(255, 118, 117, 0.4);
159
+ }
160
+
161
+ @media (max-width: 600px) {
162
+ .session-card {
163
+ grid-template-columns: 1fr;
164
+ }
165
+
166
+ .avatar {
167
+ width: 80px;
168
+ height: 80px;
169
+ }
170
+ }
171
+ </style>
172
+
173
+ <section class="login-container">
174
+ {{> backgroundElements}}
175
+
176
+ <div class="session-card" role="region" aria-label="User Session">
177
+ <div class="avatar" aria-hidden="true">
178
+ <img src="/mbkauthe/user/profilepic?u={{profilePicUrl}}"
179
+ alt="Avatar for {{username}}"
180
+ title="{{displayName}}"
181
+ loading="lazy" decoding="async" width="96" height="96"
182
+ onerror="this.style.display='none';var s=this.nextElementSibling; if(s) s.style.display='flex';" />
183
+ <div class="initials" aria-hidden="true" style="display:none">{{initial}}</div>
184
+ </div>
185
+
186
+ <div class="session-meta">
187
+ <div>
188
+ <div class="session-status">✅ Authentication successful</div>
189
+ <h3 class="session-title">{{username}} <small style="color:var(--text-light);font-weight:500">· {{role}}</small></h3>
190
+ <p class="session-sub">ID: {{id}} · Session: {{sessionIdShort}}…</p>
191
+ </div>
192
+
193
+ <div class="session-details" aria-live="polite">
194
+ <div><span>Full Name:</span> {{fullname}}</div>
195
+ <div><span>Allowed Apps:</span> {{allowedApps}}</div>
196
+ <div><span>Session Expires:</span> <span id="session-expiry-display">{{#if sessionExpiry}}{{sessionExpiry}}{{else}}N/A{{/if}}</span></div>
197
+ {{#if sessionExpiry}}<div id="session-countdown"><span>Time Remaining:</span> <span id="countdown-value">calculating…</span></div>{{/if}}
198
+ </div>
199
+
200
+ <div class="session-actions">
201
+ <button class="btn btn-danger" onclick="logout()" aria-label="Log out">
202
+ <i class="fas fa-sign-out-alt"></i> Logout
203
+ </button>
204
+ <a class="btn btn-outline" href="https://portal.mbktech.org/">
205
+ <i class="fas fa-globe"></i> Web Portal
206
+ </a>
207
+ <a class="btn btn-outline" href="https://portal.mbktech.org/user/settings">
208
+ <i class="fas fa-user-cog"></i> User Settings
209
+ </a>
210
+ <a class="btn btn-outline" href="/mbkauthe/info">
211
+ <i class="fas fa-info-circle"></i> Info Page
212
+ </a>
213
+ <a class="btn btn-outline" href="/mbkauthe/login">
214
+ <i class="fas fa-sign-in-alt"></i> Login Page
215
+ </a>
216
+ </div>
217
+ </div>
218
+ </div>
219
+ </section>
220
+
221
+ <script src="/mbkauthe/main.js"></script>
222
+ {{#if sessionExpiry}}
223
+ <script>
224
+ (function () {
225
+ var expiry = new Date('{{sessionExpiry}}');
226
+ var display = document.getElementById('session-expiry-display');
227
+ var countdown = document.getElementById('countdown-value');
228
+
229
+ // Format the expiry date in local time
230
+ if (display) {
231
+ display.textContent = expiry.toLocaleString();
232
+ }
233
+
234
+ function updateCountdown() {
235
+ var now = new Date();
236
+ var diff = expiry - now;
237
+ if (diff <= 0) {
238
+ countdown.textContent = 'Expired';
239
+ countdown.style.color = 'var(--danger)';
240
+ return;
241
+ }
242
+ var h = Math.floor(diff / 3600000);
243
+ var m = Math.floor((diff % 3600000) / 60000);
244
+ var s = Math.floor((diff % 60000) / 1000);
245
+ countdown.textContent =
246
+ (h ? h + 'h ' : '') +
247
+ (h || m ? m + 'm ' : '') +
248
+ s + 's';
249
+
250
+ // warn when under 5 minutes
251
+ countdown.style.color = diff < 300000 ? 'var(--warning)' : 'var(--success)';
252
+ }
253
+
254
+ updateCountdown();
255
+ setInterval(updateCountdown, 1000);
256
+ })();
257
+ </script>
258
+ {{/if}}
259
+ </body>
260
+ </html>
@@ -1,6 +1,6 @@
1
- {{#if version}}
1
+ {{#if CurrentVersion}}
2
2
  <!-- Version Info -->
3
3
  <div class="version-info">
4
- v{{version}}
4
+ v{{CurrentVersion}}
5
5
  </div>
6
6
  {{/if}}