diff --git a/public/css/credentialManager.css b/public/css/credentialManager.css index d4fd1a7..e893aa0 100644 --- a/public/css/credentialManager.css +++ b/public/css/credentialManager.css @@ -62,7 +62,7 @@ background-color: var(--accent-primary); } -/* Manual Input Group */ +/* Token Input Group */ .input-group { display: flex; gap: 0.5rem; @@ -154,3 +154,58 @@ .hidden { display: none !important; } + +.input-group-vertical { + display: flex; + flex-direction: column; + gap: 0.75rem; + margin-bottom: 1.5rem; +} + +.alt-actions { + display: flex; + flex-direction: column; + gap: 0.75rem; + padding-top: 1.5rem; + border-top: 1px solid var(--border-light); +} + +.btn-primary { + padding: 12px; + background-color: var(--action-primary); + color: var(--text-white); + border-radius: 4px; + font-weight: 700; +} + +.btn-outline { + padding: 10px; + background: transparent; + border: 1px solid var(--border-medium); + color: var(--text-muted-dark); + font-size: 0.85rem; + letter-spacing: 0.5px; +} + +.btn-outline:hover { + background-color: var(--bg-subtle); + border-color: var(--border-grey-dark); +} + +.back-link-btn { + background: none; + border: none; + color: var(--link-classic); + font-size: 0.9rem; + padding: 0; + text-decoration: underline; + margin-top: 1rem; +} + +.enforcement-zone { + margin-top: 1.5rem; + text-align: left; + background: var(--bg-subtle); + padding: 1.25rem; + border-radius: 4px; +} diff --git a/public/js/credentialManager.js b/public/js/credentialManager.js index 227ce8a..341a583 100644 --- a/public/js/credentialManager.js +++ b/public/js/credentialManager.js @@ -5,28 +5,80 @@ class CredentialManager { constructor() { this.container = document.getElementById("manager-container"); - this.token = this.container ? this.container.dataset.token : null; + this.token = this.container?.dataset.token || null; + this.sections = { + login: document.getElementById("login-section"), + manual: document.getElementById("manual-section"), + creds: document.getElementById("credential-section"), + request: document.getElementById("request-section"), + }; } /** * Initializes event listeners for the reveal action. */ init() { - const self = this; - const revealBtn = document.getElementById("reveal-btn"); + // UI Navigation Elements + const showTokenBtn = document.getElementById("show-token-entry-btn"); + const backBtns = document.querySelectorAll(".back-link-btn"); + const submitTokenBtn = document.getElementById("submit-token-btn"); + const saveCheck = document.getElementById("save-check"); + const returnLoginBtn = document.getElementById("return-to-login-btn"); - if (revealBtn) { - revealBtn.addEventListener("click", () => self.handleReveal()); + // Navigation Listeners + if (showTokenBtn) { + showTokenBtn.addEventListener("click", () => + this._switchState("login-section", "token-entry-section"), + ); } + + backBtns.forEach((btn) => { + btn.addEventListener("click", () => + this._switchState("token-entry-section", "login-section"), + ); + }); + + // Token Workflow + if (submitTokenBtn) { + submitTokenBtn.addEventListener("click", () => { + const input = document.getElementById("token-input-field"); + this.token = input.value.trim(); + if (this.token) this.handleReveal(); + }); + } + + // Auto-trigger if token is in URL + if (this.token && this.token !== "") { + this._switchState("login-section", "token-entry-section"); + this.handleReveal(); + } + + // Enforcement Workflow + if (saveCheck) { + saveCheck.addEventListener("change", (e) => { + returnLoginBtn.disabled = !e.target.checked; + returnLoginBtn.classList.toggle("btn-disabled", !e.target.checked); + }); + } + + if (returnLoginBtn) { + returnLoginBtn.addEventListener("click", () => { + this._switchState("credential-section", "login-section"); + }); + } + } + _switchState(fromId, toId) { + document.getElementById(fromId).classList.add("hidden"); + document.getElementById(toId).classList.remove("hidden"); } submitToken() { const submitBtn = document.getElementById("submit-token-btn"); - const manualInput = document.getElementById("token-input"); + const tokenInput = document.getElementById("token-input"); if (manualBtn) { manualBtn.addEventListener("click", () => { - const tokenValue = manualInput.value.trim(); + const tokenValue = tokenInput.value.trim(); if (tokenValue) { // Update container attribute for use by existing logic container.setAttribute("data-token", tokenValue); @@ -42,25 +94,19 @@ * Orchestrates the reveal process via POST request. */ async handleReveal() { - const self = this; - const btn = document.getElementById("reveal-btn"); - - self._toggleLoading(btn, true); + const btn = document.getElementById("submit-token-btn"); + this._toggleLoading(btn, true); try { const response = await fetch( - `https://access.jasonpoage.com/access/${self.token}`, - { - method: "GET", - headers: { "Content-Type": "application/json" }, - }, + `https://access.jasonpoage.com/access/${this.token}`, ); - const data = await self._processResponse(response); - self._displayCredentials(data); + const data = await this._processResponse(response); + this._displayCredentials(data); } catch (err) { - self._handleRevealError(err.message); + this._handleRevealError(err.message); } finally { - self._toggleLoading(btn, false); + this._toggleLoading(btn, false); } } @@ -80,18 +126,13 @@ * Updates DOM elements with credential data and transitions visibility. */ _displayCredentials(data) { - const self = this; - const revealSection = document.getElementById("reveal-section"); - const credSection = document.getElementById("credential-section"); + document.getElementById("token-entry-section").classList.add("hidden"); + document.getElementById("credential-section").classList.remove("hidden"); - document.getElementById("reveal-message").innerText = data.message; document.getElementById("username").innerText = data.username; document.getElementById("password").innerText = data.password; - revealSection.classList.add("hidden"); - credSection.classList.remove("hidden"); - - self._initCopyButtons(); + this._initCopyButtons(); } /** diff --git a/src/controllers/accessController.js b/src/controllers/accessController.js index 912e552..241e03a 100644 --- a/src/controllers/accessController.js +++ b/src/controllers/accessController.js @@ -7,7 +7,7 @@ try { res.renderWithBaseContext("pages/credentials.handlebars", { title: "Portfolio Access", - token, + token: token ?? "", }); } catch (err) { next(err); diff --git a/src/routes/guestAccessRouter.js b/src/routes/guestAccessRouter.js index 5b739fe..e189886 100644 --- a/src/routes/guestAccessRouter.js +++ b/src/routes/guestAccessRouter.js @@ -17,5 +17,6 @@ ); router.get("/guest-access/:token", logEvent("admin"), handleAccessConsumption); +router.get("/guest-access", logEvent("admin"), handleAccessConsumption); module.exports = router; diff --git a/src/views/pages/credentials.handlebars b/src/views/pages/credentials.handlebars index 6200448..ae23e8e 100644 --- a/src/views/pages/credentials.handlebars +++ b/src/views/pages/credentials.handlebars @@ -7,41 +7,51 @@ {{/section}}
- {{!-- Initial State: Reveal Button --}} - {{#if token}} - {{!-- State: Token Provided via URL --}} -
-

Portfolio Access

-

Click the button below to generate your one-time credentials.

- -
- {{else}} - {{!-- State: Manual Token Entry Required --}} -
-

Manual Access

-

No access token detected. Please enter your token manually to continue.

-
- - -
- -
- {{/if}} - {{!-- Result State: Credentials (Hidden Initially) --}} + {{!-- State 1: Standard Login (Default) --}} +
+

System Access

+

Authenticate to access protected services on jasonpoage.com.

+ +
+ +
+ + + +
+
+ +
+ + +
+
+ + {{!-- State 2: Token Entry --}} + + + {{!-- State 3: Credential Reveal & Enforcement --}}