PageSourceSearch

https://nismc.com/wp-content/plugins/org-2fa-vault/assets/portal.js?ver=1.0.0

js nismc.com collected 2026-10-07 03:10:14 UTC 5,674 bytes, 155 lines download raw bytes

1(function () {
2  const root = document.getElementById('o2v-portal');
3  if (!root) return;
4
5  const loginBox = root.querySelector('.o2v-login-box');
6  const codesBox = root.querySelector('.o2v-codes-box');
7  const userEl = document.getElementById('o2v-username');
8  const passEl = document.getElementById('o2v-password');
9  const loginBtn = document.getElementById('o2v-login-btn');
10  const logoutBtn = document.getElementById('o2v-logout-btn');
11  const errEl = document.getElementById('o2v-login-err');
12  const welcomeEl = document.getElementById('o2v-welcome');
13  const listEl = document.getElementById('o2v-codes-list');
14  const timerFill = document.getElementById('o2v-timer-fill');
15  const timerSecs = document.getElementById('o2v-timer-secs');
16
17  const PERIOD = 30;
18  const SESSION_LIMIT_MS = 60 * 1000; // hard logout after 1 minute, no matter what
19  let token = sessionStorage.getItem('o2v_token') || null;
20  let countdownTimer = null;   // ticks every second, visual only
21  let sessionTimer = null;     // fires exactly once, 60s after login
22  let remaining = PERIOD;      // seconds left in the current TOTP window
23
24  function showCodes(name) {
25    loginBox.style.display = 'none';
26    codesBox.style.display = 'block';
27    welcomeEl.textContent = 'Signed in as ' + name;
28  }
29
30  function showLogin() {
31    codesBox.style.display = 'none';
32    loginBox.style.display = 'block';
33    if (countdownTimer) clearInterval(countdownTimer);
34    if (sessionTimer) clearTimeout(sessionTimer);
35  }
36
37  function escapeHtml(s) {
38    return String(s).replace(/[&<>"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;'}[c]));
39  }
40
41  function paintTimer() {
42    timerSecs.textContent = remaining + 's';
43    const pct = (remaining / PERIOD) * 100;
44    timerFill.style.width = pct + '%';
45    timerFill.classList.toggle('o2v-timer-warn', remaining <= 5);
46  }
47
48  function startCountdown() {
49    if (countdownTimer) clearInterval(countdownTimer);
50    countdownTimer = setInterval(() => {
51      remaining -= 1;
52      if (remaining <= 0) {
53        fetchCodes(); // window rolled over server-side too — get the new codes now
54        return;
55      }
56      paintTimer();
57    }, 1000);
58  }
59
60  async function doHardLogout(reason) {
61    if (!token) return;
62    const body = new URLSearchParams({ action: 'o2v_logout', nonce: O2V.nonce, token });
63    await fetch(O2V.ajaxUrl, { method: 'POST', body });
64    sessionStorage.removeItem('o2v_token');
65    token = null;
66    showLogin();
67    if (reason) errEl.textContent = reason;
68  }
69
70  // Fixed 60-second session: starts the moment login succeeds and fires
71  // regardless of mouse movement, clicks, or any other activity.
72  function startSessionTimer() {
73    if (sessionTimer) clearTimeout(sessionTimer);
74    sessionTimer = setTimeout(() => {
75      doHardLogout('Session ended after 1 minute — please log in again.');
76    }, SESSION_LIMIT_MS);
77  }
78
79  async function fetchCodes() {
80    const body = new URLSearchParams({ action: 'o2v_get_codes', nonce: O2V.nonce, token });
81    const res = await fetch(O2V.ajaxUrl, { method: 'POST', body });
82    const data = await res.json();
83
84    if (!data.success) {
85      sessionStorage.removeItem('o2v_token');
86      token = null;
87      showLogin();
88      errEl.textContent = data.data.message || 'Session expired.';
89      return;
90    }
91
92    showCodes(data.data.name);
93
94    listEl.innerHTML = '';
95    data.data.codes.forEach(acc => {
96      const card = document.createElement('div');
97      card.className = 'o2v-account-card';
98      card.innerHTML =
99        '<div><div class="o2v-account-label">' + escapeHtml(acc.label) + '</div>' +
100        '<div class="o2v-timer">tap to copy · refreshes automatically</div></div>' +
101        '<div class="o2v-account-code">' + escapeHtml(acc.code) + '</div>';
102      card.addEventListener('click', () => navigator.clipboard.writeText(acc.code));
103      listEl.appendChild(card);
104    });
105    if (!data.data.codes.length) {
106      listEl.innerHTML = '<p style="color:#8890b5;">No accounts have been assigned to you yet.</p>';
107    }
108
109    // Sync the visual countdown to the server's real TOTP window.
110    remaining = data.data.seconds || PERIOD;
111    paintTimer();
112    startCountdown();
113  }
114
115  loginBtn.addEventListener('click', async () => {
116    errEl.textContent = '';
117    const body = new URLSearchParams({
118      action: 'o2v_login', nonce: O2V.nonce,
119      username: userEl.value.trim(), password: passEl.value
120    });
121    const res = await fetch(O2V.ajaxUrl, { method: 'POST', body });
122    const data = await res.json();
123    if (!data.success) {
124      errEl.textContent = data.data.message || 'Login failed.';
125      return;
126    }
127    token = data.data.token;
128    sessionStorage.setItem('o2v_token', token);
129    passEl.value = '';
130    await fetchCodes();
131    startSessionTimer();
132  });
133
134  logoutBtn.addEventListener('click', () => doHardLogout());
135
136  passEl.addEventListener('keydown', e => { if (e.key === 'Enter') loginBtn.click(); });
137
138  // If the phone/tab was backgrounded, browsers throttle timers —
139  // resync codes when the tab becomes active again. The 60s session
140  // timer itself is untouched by activity, so it still fires on schedule.
141  document.addEventListener('visibilitychange', () => {
142    if (document.visibilityState === 'visible' && token) fetchCodes();
143  });
144  window.addEventListener('focus', () => {
145    if (token) fetchCodes();
146  });
147
148  // Note: an existing sessionStorage token from a previous page load has no
149  // way of knowing how much of its 60s already elapsed, so a fresh page
150  // load with a saved token gets a full new 60s window rather than none.
151  if (token) {
152    fetchCodes();
153    startSessionTimer();
154  }
155})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.