diff --git a/.gitignore b/.gitignore
index 5c61289..5c835c7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,8 @@ config.json
public/b
public/ca
public/t
+public/hall_cache
+public/hall_custom
deleted/b
deleted/ca
deleted/t
diff --git a/config_example.json b/config_example.json
index 0b3aced..e2f3a13 100644
--- a/config_example.json
+++ b/config_example.json
@@ -38,6 +38,8 @@
"cache": false,
"eps": 155,
"background": true,
+ "log_user_ips": false,
+ "hash_user_ips": true,
"description": "Example Description",
"themes": [ "amoled" ],
diff --git a/migrations/f0ckm_schema.sql b/migrations/f0ckm_schema.sql
index 225bf52..24b38cb 100644
--- a/migrations/f0ckm_schema.sql
+++ b/migrations/f0ckm_schema.sql
@@ -2699,5 +2699,19 @@ GRANT ALL ON SCHEMA public TO PUBLIC;
-- PostgreSQL database dump complete
--
-\unrestrict RMNKNzVQLV2ZcwmM3bmhglTot5nRoju9FmRyi3eUMfNy6iJUBfHRIgXnbrpJikG
+-- Migration to add user_ips table for historical IP logging
+CREATE TABLE IF NOT EXISTS user_ips (
+ id SERIAL PRIMARY KEY,
+ user_id INTEGER NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
+ ip TEXT NOT NULL,
+ first_seen TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ last_seen TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
+ UNIQUE(user_id, ip)
+);
+CREATE INDEX IF NOT EXISTS idx_user_ips_user_id ON user_ips(user_id);
+
+-- Add IP tracking to user_sessions for "current" IP view
+ALTER TABLE user_sessions ADD COLUMN IF NOT EXISTS ip TEXT;
+
+\unrestrict RMNKNzVQLV2ZcwmM3bmhglTot5nRoju9FmRyi3eUMfNy6iJUBfHRIgXnbrpJikG
diff --git a/public/s/css/f0ckm.css b/public/s/css/f0ckm.css
index ebb9b4a..fcdffa2 100644
--- a/public/s/css/f0ckm.css
+++ b/public/s/css/f0ckm.css
@@ -11642,15 +11642,13 @@ i.iconset#a_oc {
.responsive-table td {
border: none;
position: relative;
- padding: 12px 15px 12px 42% !important;
- /* Slightly reduced labels width for more content space */
+ padding: 12px 15px !important;
min-height: 48px;
display: flex;
- align-items: center;
- justify-content: flex-start;
- flex-wrap: nowrap;
- /* Prevent wrapping inside standard cells */
- gap: 12px;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+ gap: 6px;
text-align: left !important;
border-bottom: 1px solid rgba(255, 255, 255, 0.03);
box-sizing: border-box;
@@ -11682,9 +11680,8 @@ i.iconset#a_oc {
.responsive-table td::before {
content: attr(data-label);
- position: absolute;
- left: 15px;
- width: 35%;
+ position: static;
+ width: auto;
white-space: nowrap;
font-weight: 800;
color: var(--accent);
@@ -11692,6 +11689,7 @@ i.iconset#a_oc {
text-transform: uppercase;
letter-spacing: 1px;
opacity: 0.8;
+ margin-bottom: 2px;
}
/* Special handling for the Actions cell */
@@ -13508,4 +13506,56 @@ body.scroller-active #gchat-reopen-bubble {
#nav-meme-link, #nav-upload-link {
white-space: nowrap;
-}
\ No newline at end of file
+}
+/* --- Admin IP History Styles --- */
+.admin-ips-table {
+ width: 100%;
+ border-collapse: separate;
+ border-spacing: 0 8px;
+ color: var(--white);
+}
+.admin-ips-table th {
+ padding: 15px;
+ text-align: left;
+ text-transform: uppercase;
+ font-size: 0.75rem;
+ letter-spacing: 1px;
+ color: #888;
+ border-bottom: 1px solid rgba(255,255,255,0.05);
+}
+.admin-ips-table tr {
+ transition: all 0.2s ease;
+}
+.admin-ips-table tbody tr {
+ background: rgba(255, 255, 255, 0.02);
+}
+.admin-ips-table tbody tr:hover {
+ background: rgba(255, 255, 255, 0.05);
+}
+.admin-ips-table td {
+ padding: 15px;
+ vertical-align: middle;
+}
+.ip-badge {
+ font-family: monospace;
+ padding: 4px 10px;
+ background: rgba(var(--accent-rgb, 153, 255, 0), 0.1);
+ border: 1px solid rgba(var(--accent-rgb, 153, 255, 0), 0.2);
+ color: var(--accent);
+ border-radius: 4px;
+ font-size: 0.9rem;
+ word-break: break-all;
+ display: inline-block;
+ max-width: 100%;
+}
+.date-cell {
+ font-size: 0.85rem;
+ color: #aaa;
+}
+.date-label {
+ display: block;
+ font-size: 0.7rem;
+ color: #666;
+ text-transform: uppercase;
+ margin-bottom: 2px;
+}
diff --git a/public/s/js/f0ckm.js b/public/s/js/f0ckm.js
index 2bb5ab1..9379e3d 100644
--- a/public/s/js/f0ckm.js
+++ b/public/s/js/f0ckm.js
@@ -23,6 +23,14 @@ window.cancelAnimFrame = (function () {
return div.innerHTML;
};
+ window.getCurrentItemId = () => {
+ const path = window.location.pathname;
+ // Explicitly ignore admin/mod/settings paths to avoid false positives from user IDs, etc.
+ if (path.includes('/admin/') || path.includes('/mod/') || path.includes('/settings') || path.includes('/user/')) return null;
+ const match = path.match(/\/(\d+)\/?$/);
+ return match ? match[1] : null;
+ };
+
// OS and Browser detection for CSS targeting
const ua = navigator.userAgent;
const htmlEl = document.documentElement;
@@ -120,9 +128,7 @@ window.cancelAnimFrame = (function () {
});
// Refresh background canvas if it matches the current item
- const pathParts = window.location.pathname.split('/');
- const numParts = pathParts.filter(s => /^\d+$/.test(s));
- const currentId = numParts.length > 0 ? numParts[numParts.length - 1] : null;
+ const currentId = window.getCurrentItemId();
if (currentId === idStr && window.initBackground) {
window.initBackground();
}
@@ -943,9 +949,7 @@ window.cancelAnimFrame = (function () {
// Always use the thumbnail first for instant backdrop — thumbnails are tiny,
// often browser-cached from grid view, and give us a frame-0 equivalent for GIFs too.
// Extract item ID from URL for thumbnail path.
- const pathParts = window.location.pathname.split('/');
- const numParts = pathParts.filter(s => /^\d+$/.test(s));
- const itemId = numParts.length > 0 ? numParts[numParts.length - 1] : null;
+ const itemId = window.getCurrentItemId();
const showCanvas = () => {
canvas.classList.remove('fader-out', 'fast-fade');
@@ -1055,9 +1059,7 @@ window.cancelAnimFrame = (function () {
if (background) {
canvas._bgFadingOut = false;
// Draw the item thumbnail if we have an item ID in the URL
- const pathParts = window.location.pathname.split('/');
- const numParts = pathParts.filter(s => /^\d+$/.test(s));
- const itemId = numParts.length > 0 ? numParts[numParts.length - 1] : null;
+ const itemId = window.getCurrentItemId();
if (itemId) {
const context = canvas.getContext('2d');
const cw = canvas.width = canvas.clientWidth | 0;
diff --git a/public/s/js/upload.js b/public/s/js/upload.js
index bb26c1f..278e183 100644
--- a/public/s/js/upload.js
+++ b/public/s/js/upload.js
@@ -1,11 +1,11 @@
-const escapeHtmlUpload = (unsafe) => {
+window.escapeHtmlUpload = window.escapeHtmlUpload || ((unsafe) => {
return (unsafe || '').toString()
.replace(/&/g, "&")
.replace(//g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
-};
+});
window.initUploadForm = (selector) => {
const form = (typeof selector === 'string') ? document.querySelector(selector) : selector;
@@ -749,7 +749,7 @@ window.initUploadForm = (selector) => {
chip.className = 'tag-chip';
chip.style.cursor = 'pointer';
chip.title = 'Click to edit prefix or tag';
- chip.innerHTML = `${escapeHtmlUpload(tagName)}`;
+ chip.innerHTML = `${window.escapeHtmlUpload(tagName)}`;
// Remove button logic
chip.querySelector('button').addEventListener('click', (e) => {
@@ -867,7 +867,7 @@ window.initUploadForm = (selector) => {
const sug = document.createElement('div');
sug.className = 'meta-suggestion';
sug.setAttribute('data-text', text);
- sug.innerHTML = ` ${escapeHtmlUpload(text)}`;
+ sug.innerHTML = ` ${window.escapeHtmlUpload(text)}`;
sug.addEventListener('mouseup', (ev) => {
const sel = window.getSelection?.()?.toString().trim();
@@ -976,7 +976,7 @@ window.initUploadForm = (selector) => {
const scoreStr = typeof s.score === 'number' ? s.score.toFixed(2) : '0.00';
html += `
- ${escapeHtmlUpload(s.tag)}
+ ${window.escapeHtmlUpload(s.tag)}
${s.tagged || 0}× · ${scoreStr}
`;
diff --git a/src/inc/routes/admin.mjs b/src/inc/routes/admin.mjs
index c9bb83a..4238b31 100644
--- a/src/inc/routes/admin.mjs
+++ b/src/inc/routes/admin.mjs
@@ -11,7 +11,7 @@ import cfg from "../config.mjs";
import security from "../security.mjs";
import crypto from "crypto";
import path from "path";
-import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getEnablePdf, setEnablePdf } from "../settings.mjs";
+import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getEnablePdf, setEnablePdf, getLogUserIps, setLogUserIps, getHashUserIps, setHashUserIps } from "../settings.mjs";
export default (router, tpl) => {
router.get(/^\/login(\/)?$/, async (req, res) => {
@@ -102,13 +102,17 @@ export default (router, tpl) => {
created_at: stamp,
last_used: stamp,
last_action: "/login",
- kmsi: typeof req.post.kmsi !== 'undefined' ? 1 : 0
+ kmsi: typeof req.post.kmsi !== 'undefined' ? 1 : 0,
+ ip: ip
};
await db`
- insert into "user_sessions" ${db(blah, 'user_id', 'session', 'csrf_token', 'browser', 'created_at', 'last_used', 'last_action', 'kmsi')
+ insert into "user_sessions" ${db(blah, 'user_id', 'session', 'csrf_token', 'browser', 'created_at', 'last_used', 'last_action', 'kmsi', 'ip')
}
`;
+
+ // Log IP for historical data
+ await security.logUserIP(user[0].id, ip);
return res.writeHead(301, {
"Cache-Control": "no-cache, public",
@@ -277,6 +281,8 @@ export default (router, tpl) => {
totals: await lib.countf0cks(),
session: req.session,
manual_approval: getManualApproval(),
+ log_user_ips: getLogUserIps(),
+ hash_user_ips: getHashUserIps(),
tmp: null
}, req)
});
@@ -303,6 +309,32 @@ export default (router, tpl) => {
activeUsers: activeUsernames.length,
activeUserList: activeUsernames,
totals: await lib.countf0cks(),
+ log_user_ips: getLogUserIps(),
+ tmp: null
+ }, req)
+ });
+ });
+
+ router.get(/\/admin\/user\/(?\d+)\/ips(\/)?$/, lib.auth, async (req, res) => {
+ const userId = +req.params.userId;
+ const user = await db`select "user", login from "user" where id = ${userId} limit 1`;
+ if (user.length === 0) return res.reply({ code: 404, body: 'User not found' });
+
+ const rows = await db`
+ select * from user_ips
+ where user_id = ${userId}
+ order by last_seen desc
+ `;
+
+ res.reply({
+ body: tpl.render("admin/user_ips", {
+ session: req.session,
+ targetUser: user[0],
+ ips: rows,
+ page_meta: {
+ title: `IP History - ${user[0].user}`
+ },
+ totals: await lib.countf0cks(),
tmp: null
}, req)
});
@@ -580,10 +612,14 @@ export default (router, tpl) => {
router.post(/^\/admin\/settings\/?$/, lib.auth, async (req, res) => {
const manual_approval = req.post.manual_approval === 'on' ? 'true' : 'false';
const registration_open = req.post.registration_open === 'on' ? 'true' : 'false';
+ const log_user_ips = req.post.log_user_ips === 'on' ? 'true' : 'false';
+ const hash_user_ips = req.post.hash_user_ips === 'on' ? 'true' : 'false';
const min_tags = isNaN(parseInt(req.post.min_tags)) ? 3 : Math.max(0, parseInt(req.post.min_tags));
const trusted_uploads = Math.max(0, parseInt(req.post.trusted_uploads) ?? 3);
await db`INSERT INTO site_settings (key, value) VALUES ('manual_approval', ${manual_approval}) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`;
+ await db`INSERT INTO site_settings (key, value) VALUES ('log_user_ips', ${log_user_ips}) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`;
+ await db`INSERT INTO site_settings (key, value) VALUES ('hash_user_ips', ${hash_user_ips}) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`;
if (cfg.websrv.open_registration_web_toggle !== false) {
await db`INSERT INTO site_settings (key, value) VALUES ('registration_open', ${registration_open}) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`;
@@ -594,6 +630,8 @@ export default (router, tpl) => {
await db`INSERT INTO site_settings (key, value) VALUES ('trusted_uploads', ${trusted_uploads.toString()}) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value`;
setManualApproval(manual_approval === 'true');
+ setLogUserIps(log_user_ips === 'true');
+ setHashUserIps(hash_user_ips === 'true');
setMinTags(min_tags);
setTrustedUploads(trusted_uploads);
@@ -697,6 +735,7 @@ const page = Math.max(1, parseInt(req.url.qs?.page) || 1);
total,
hasMore: users.length === limit,
totals: await lib.countf0cks(),
+ log_user_ips: getLogUserIps(),
tmp: null
};
diff --git a/src/inc/security.mjs b/src/inc/security.mjs
index ab23980..3268ad7 100644
--- a/src/inc/security.mjs
+++ b/src/inc/security.mjs
@@ -26,7 +26,10 @@ export default new class {
* @returns {string}
*/
getRealIP(req) {
- let ip = req.headers['x-real-ip'] ||
+ let ip = req.headers['cf-connecting-ip'] ||
+ req.headers['true-client-ip'] ||
+ req.headers['x-client-ip'] ||
+ req.headers['x-real-ip'] ||
(req.headers['x-forwarded-for'] ? req.headers['x-forwarded-for'].split(',')[0].trim() : null) ||
req.socket.remoteAddress;
@@ -34,11 +37,14 @@ export default new class {
// Handle IPv6 loopback and mapped IPv4
if (ip === "::1") ip = "127.0.0.1";
- if (ip.startsWith("::ffff:")) ip = ip.substring(7);
+ if (ip && ip.startsWith("::ffff:")) ip = ip.substring(7);
// Basic IPv6 normalization (ensure consistent case and representation if possible)
- // Note: Simple hex strings for IP are fine for hashing as long as Nginx is consistent.
- if (ip.includes(":")) ip = ip.toLowerCase();
+ if (ip && ip.includes(":")) ip = ip.toLowerCase();
+
+ if (cfg.main.development && ip === "127.0.0.1" && req.headers) {
+ console.debug('[SECURITY] Local IP detected. Headers:', req.headers);
+ }
return ip;
}
@@ -163,4 +169,22 @@ export default new class {
return false;
}
+
+ /**
+ * Log user IP for historical tracking if enabled.
+ * @param {number} userId
+ * @param {string} ip
+ */
+ async logUserIP(userId, ip) {
+ if (!cfg.websrv.log_user_ips || !userId || !ip) return;
+
+ const { getHashUserIps } = await import("./settings.mjs");
+ const finalIp = getHashUserIps() ? this.hashIP(ip) : ip;
+
+ await db`
+ insert into user_ips (user_id, ip)
+ values (${userId}, ${finalIp})
+ on conflict (user_id, ip) do update set last_seen = now()
+ `.catch(err => console.error(`[SECURITY] Failed to log user IP:`, err));
+ }
};
diff --git a/src/inc/settings.mjs b/src/inc/settings.mjs
index 3f10a09..3437139 100644
--- a/src/inc/settings.mjs
+++ b/src/inc/settings.mjs
@@ -44,3 +44,18 @@ export const setPrivateMessages = (val) => private_messages = !!val;
export const getDefaultLayout = () => default_layout;
export const setDefaultLayout = (val) => default_layout = (val === 'legacy' ? 'legacy' : 'modern');
+
+let log_user_ips = false;
+export const getLogUserIps = () => log_user_ips;
+export const setLogUserIps = (val) => {
+ log_user_ips = !!val;
+ // Also update the config object for components that read from it directly
+ cfg.websrv.log_user_ips = log_user_ips;
+};
+
+let hash_user_ips = false;
+export const getHashUserIps = () => hash_user_ips;
+export const setHashUserIps = (val) => {
+ hash_user_ips = !!val;
+ cfg.websrv.hash_user_ips = hash_user_ips;
+};
diff --git a/src/index.mjs b/src/index.mjs
index 0136667..607de2b 100644
--- a/src/index.mjs
+++ b/src/index.mjs
@@ -19,6 +19,7 @@ import { handleMetaStrip } from "./meta_strip_handler.mjs";
import { getManualApproval, setManualApproval, getMinTags, setMinTags, getRegistrationOpen, setRegistrationOpen, getTrustedUploads, setTrustedUploads, getBypassDuplicateCheck, setBypassDuplicateCheck, getProtectFiles, setProtectFiles, getPrivateMessages, setPrivateMessages, getDefaultLayout, setDefaultLayout, getEnablePdf, setEnablePdf } from "./inc/settings.mjs";
import { updateHallsCache, getHalls } from "./inc/halls_cache.mjs";
import { createI18n } from "./inc/i18n.mjs";
+import security from "./inc/security.mjs";
const nginx502 = `
502 Bad Gateway
@@ -297,12 +298,17 @@ process.on('uncaughtException', err => {
// log last action (Fire-and-Forget)
if (!req.url.pathname.startsWith('/api/notifications')) {
+ const { getLogUserIps, getHashUserIps } = await import("./inc/settings.mjs");
+ const currentIp = security.getRealIP(req);
+ const finalIp = getHashUserIps() ? security.hashIP(currentIp) : currentIp;
+
db`
update "user_sessions" set ${db({
last_used: ~~(Date.now() / 1e3),
last_action: req.url.pathname,
- browser: req.headers['user-agent']
- }, 'last_used', 'last_action', 'browser')
+ browser: req.headers['user-agent'],
+ ...(getLogUserIps() ? { ip: finalIp } : {})
+ }, 'last_used', 'last_action', 'browser', ...(getLogUserIps() ? ['ip'] : []))
}
where id = ${+user[0].sess_id}
`.catch(e => console.error('[MIDDLEWARE] Session update failed:', e));
@@ -310,6 +316,9 @@ process.on('uncaughtException', err => {
// Update last_seen on user table (Fire-and-Forget) — feeds the 30-day orakel pool
db`update "user" set last_seen = ${~~(Date.now() / 1e3)} where id = ${+user[0].id}`
.catch(e => console.error('[MIDDLEWARE] last_seen update failed:', e));
+
+ // Log IP for historical data
+ security.logUserIP(user[0].id, currentIp);
}
if (req.session.admin) {
@@ -655,6 +664,30 @@ process.on('uncaughtException', err => {
setEnablePdf(!!cfg.enable_pdf);
console.log(`[BOOT] Enable PDF setting: ${getEnablePdf()}`);
+ // Fetch log_user_ips and hash_user_ips setting
+ const { getLogUserIps, setLogUserIps, getHashUserIps, setHashUserIps } = await import("./inc/settings.mjs");
+ try {
+ const lipSetting = await db`SELECT value FROM site_settings WHERE key = 'log_user_ips' LIMIT 1`;
+ if (lipSetting.length > 0) {
+ setLogUserIps(lipSetting[0].value === 'true');
+ } else {
+ setLogUserIps(!!cfg.websrv.log_user_ips);
+ }
+ console.log(`[BOOT] Log User IPs: ${getLogUserIps()}`);
+
+ const hipSetting = await db`SELECT value FROM site_settings WHERE key = 'hash_user_ips' LIMIT 1`;
+ if (hipSetting.length > 0) {
+ setHashUserIps(hipSetting[0].value === 'true');
+ } else {
+ setHashUserIps(!!cfg.websrv.hash_user_ips);
+ }
+ console.log(`[BOOT] Hash User IPs: ${getHashUserIps()}`);
+ } catch (e) {
+ console.warn(`[BOOT] IP logging settings fetch failed:`, e.message);
+ setLogUserIps(!!cfg.websrv.log_user_ips);
+ setHashUserIps(!!cfg.websrv.hash_user_ips);
+ }
+
// Load bypass_duplicate_check from config.json (static — not a DB setting)
if (cfg.websrv.bypass_duplicate_check === true) {
setBypassDuplicateCheck(true);
diff --git a/views/admin.html b/views/admin.html
index b24fe5d..86ba84a 100644
--- a/views/admin.html
+++ b/views/admin.html
@@ -40,6 +40,26 @@
+
+
+
+
Log all historical IPs for user accounts.
+
+
+
+
+
+
+
Anonymize IPs by hashing them (same as failed logins).
+
+
+
@if(registration_web_toggle_enabled)
@@ -99,6 +119,8 @@
const status = document.getElementById('settings-status');
const approvalToggle = document.getElementById('manual_approval_toggle');
const registrationToggle = document.getElementById('registration_open_toggle');
+ const logIpsToggle = document.getElementById('log_user_ips_toggle');
+ const hashIpsToggle = document.getElementById('hash_user_ips_toggle');
const minTagsInput = document.getElementById('min_tags_input');
const trustedUploadsInput = document.getElementById('trusted_uploads_input');
@@ -114,6 +136,8 @@
},
body: new URLSearchParams({
manual_approval: approvalToggle.checked ? 'on' : 'off',
+ log_user_ips: logIpsToggle.checked ? 'on' : 'off',
+ hash_user_ips: hashIpsToggle.checked ? 'on' : 'off',
...(registrationToggle ? { registration_open: registrationToggle.checked ? 'on' : 'off' } : {}),
min_tags: minTagsInput.value,
trusted_uploads: trustedUploadsInput.value,
diff --git a/views/admin/sessions.html b/views/admin/sessions.html
index ad4643c..22ec814 100644
--- a/views/admin/sessions.html
+++ b/views/admin/sessions.html
@@ -30,6 +30,12 @@
+ @if(log_user_ips)
+
+ IP:
+ {{ s.ip || 'unknown' }}
+
+ @endif
Browser:
{{ s.browser }}
diff --git a/views/admin/user_ips.html b/views/admin/user_ips.html
new file mode 100644
index 0000000..e988544
--- /dev/null
+++ b/views/admin/user_ips.html
@@ -0,0 +1,56 @@
+@include(snippets/header)
+
+
+
+
+
+
+ Back to User Manager
+
+
IP History: {!! targetUser.user !!}
+
Historical IP addresses associated with this account.
+
+
+
+
+
+
+
+@include(snippets/footer)
diff --git a/views/admin/users_list.html b/views/admin/users_list.html
index 5b2faae..3f05c60 100644
--- a/views/admin/users_list.html
+++ b/views/admin/users_list.html
@@ -68,6 +68,9 @@
@endif
@if(u.id)
+ @if(log_user_ips)
+
IP Hist
+ @endif
diff --git a/views/snippets/footer.html b/views/snippets/footer.html
index 4cd7376..ee75fb1 100644
--- a/views/snippets/footer.html
+++ b/views/snippets/footer.html
@@ -537,6 +537,7 @@
@if(session)
+
diff --git a/views/snippets/navbar.html b/views/snippets/navbar.html
index 83ba063..3bb6d2b 100644
--- a/views/snippets/navbar.html
+++ b/views/snippets/navbar.html
@@ -375,7 +375,4 @@
-
-
-
@endif