diff options
| author | Collin Williams <96917990+bluedragon1221@users.noreply.github.com> | 2026-04-09 13:14:01 -0500 |
|---|---|---|
| committer | Collin Williams <96917990+bluedragon1221@users.noreply.github.com> | 2026-04-09 13:14:01 -0500 |
| commit | 21de12cd6e63adf463674942f1a8f9b658f47945 (patch) | |
| tree | 5229a5300c1e69d637a1e03539dc19de42244440 /modules/services/nixos/jta | |
| parent | 2147a65b5ff7cef352d95dfa72c705145771f3a3 (diff) | |
jta changes
Diffstat (limited to 'modules/services/nixos/jta')
| -rw-r--r-- | modules/services/nixos/jta/assignments.html | 148 | ||||
| -rw-r--r-- | modules/services/nixos/jta/default.nix | 1 | ||||
| -rw-r--r-- | modules/services/nixos/jta/index.html | 199 | ||||
| -rw-r--r-- | modules/services/nixos/jta/main.go | 31 | ||||
| -rw-r--r-- | modules/services/nixos/jta/style.css | 29 |
5 files changed, 311 insertions, 97 deletions
diff --git a/modules/services/nixos/jta/assignments.html b/modules/services/nixos/jta/assignments.html new file mode 100644 index 0000000..3031a29 --- /dev/null +++ b/modules/services/nixos/jta/assignments.html @@ -0,0 +1,148 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>jta@ganymede</title> + <link rel="stylesheet" href="style.css"> + <style> + body { + margin: 0; + max-width: none; + padding: 0; + background: var(--bg); + } + + header { + padding: 1.25rem 1.5rem; + text-align: center; + font-family: var(--font-heading); + font-size: var(--step-2); + line-height: 1.2; + letter-spacing: -0.01em; + font-weight: 700; + background: #020617; + border-bottom: 1px solid var(--border); + } + + .section { + padding: 1rem; + max-width: 1100px; + margin: 0 auto; + } + + .section-title { + font-size: var(--step-1); + line-height: 1.25; + letter-spacing: -0.01em; + margin: 1rem 0 0.5rem 0.5rem; + color: var(--text-soft); + } + + .container { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); + gap: 1rem; + padding: 0 1rem 1rem 1rem; + } + + .card { + background: var(--card); + border: 1px solid var(--border); + border-radius: 16px; + padding: 1rem; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4); + transition: transform 0.15s ease, box-shadow 0.15s ease; + cursor: pointer; + text-decoration: none; + color: var(--text); + display: block; + } + + .card:hover { + transform: translateY(-4px); + box-shadow: 0 10px 20px rgba(0, 0, 0, 0.6); + } + + .title { + font-size: var(--step-1); + line-height: 1.3; + letter-spacing: -0.01em; + font-weight: 600; + margin-bottom: 0.5rem; + } + + .due { + font-size: var(--step--1); + color: var(--muted); + margin-bottom: 0.5rem; + } + + .desc { + font-size: var(--step--1); + color: var(--text-soft); + line-height: 1.55; + } + </style> + </head> + <body> + <header>Juksere Trives Aldri</header> + + <div id="app"></div> + + <script> + const app = document.getElementById("app"); + + function renderHome(data) { + const groups = {}; + data.forEach((item) => { + const cls = item.class || "Other"; + if (!groups[cls]) groups[cls] = []; + groups[cls].push(item); + }); + + Object.entries(groups).forEach(([cls, items]) => { + const section = document.createElement("div"); + section.className = "section"; + + const title = document.createElement("div"); + title.className = "section-title"; + title.textContent = cls; + section.appendChild(title); + + const container = document.createElement("div"); + container.className = "container"; + + items.forEach((item) => { + const card = document.createElement("a"); + card.className = "card"; + card.href = item.link || "#"; + + card.innerHTML = ` + <div class="title">${item.title}</div> + <div class="due">Due: ${item.due}</div> + ${item.description ? `<div class="desc">${item.description}</div>` : ""} + `; + + container.appendChild(card); + }); + + section.appendChild(container); + app.appendChild(section); + }); + } + + fetch("assignments.json") + .then((res) => { + if (!res.ok) { + throw new Error("Failed to load data"); + } + return res.json(); + }) + .then((data) => renderHome(data)) + .catch(() => { + app.innerHTML = "<p style='padding:1rem;'>Failed to load data.</p>"; + }); + </script> + </body> +</html> diff --git a/modules/services/nixos/jta/default.nix b/modules/services/nixos/jta/default.nix index c1ea012..d2908bc 100644 --- a/modules/services/nixos/jta/default.nix +++ b/modules/services/nixos/jta/default.nix @@ -33,6 +33,7 @@ in { environment = { PORT = toString cfg.port; ROOT_DIR = "/media/jta"; + AUTH_PASSWORD_HASH = "550b6785c4bce2ab41a5e2eaa1eb43172d5abbd58237f1c78cd32cb190edc2c5"; # alfreddabuttlerwithtwots }; serviceConfig = { diff --git a/modules/services/nixos/jta/index.html b/modules/services/nixos/jta/index.html index fd93de6..96b6f5f 100644 --- a/modules/services/nixos/jta/index.html +++ b/modules/services/nixos/jta/index.html @@ -3,127 +3,132 @@ <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> - <title>jta@ganymede</title> + <title>JTA Login</title> <link rel="stylesheet" href="style.css"> <style> body { margin: 0; - max-width: none; - padding: 0; + min-height: 100vh; + display: grid; + place-items: center; + padding: 1rem; background: var(--bg); } - - header { - padding: 1.25rem 1.5rem; + + .card { + width: min(520px, 100%); + background: var(--card); + border: 1px solid var(--border); + border-radius: 16px; + padding: 1.5rem; + box-shadow: 0 10px 28px rgba(0, 0, 0, 0.38); + } + + h1 { + margin: 0 0 0.25rem 0; text-align: center; font-family: var(--font-heading); font-size: var(--step-2); - line-height: 1.2; - letter-spacing: -0.01em; + line-height: 1.15; font-weight: 700; - background: #020617; - border-bottom: 1px solid var(--border); - } - - .section { - padding: 1rem; - max-width: 1100px; - margin: 0 auto; } - - .section-title { - font-size: var(--step-1); - line-height: 1.25; - letter-spacing: -0.01em; - margin: 1rem 0 0.5rem 0.5rem; + + p { + margin: 0.5rem 0 1.2rem 0; + text-align: center; color: var(--text-soft); + line-height: 1.5; } - - .container { - display: grid; - grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - gap: 1rem; - padding: 0 1rem 1rem 1rem; + + .actions { + display: flex; + justify-content: center; + gap: 0.75rem; } - - .card { - background: var(--card); + + button { + appearance: none; border: 1px solid var(--border); - border-radius: 16px; - padding: 1rem; - box-shadow: 0 4px 12px rgba(0,0,0,0.4); - transition: transform 0.15s ease, box-shadow 0.15s ease; - cursor: pointer; - text-decoration: none; + border-radius: 12px; + background: #0f172a; color: var(--text); - display: block; + padding: 0.65rem 1rem; + font-size: var(--step-0); + font-weight: 600; + cursor: pointer; + transition: transform 0.15s ease, box-shadow 0.15s ease; } - - .card:hover { - transform: translateY(-4px); - box-shadow: 0 10px 20px rgba(0,0,0,0.6); + + button:hover { + transform: translateY(-1px); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.35); + } + + #status { + margin-top: 1rem; + text-align: center; + min-height: 1.25rem; + font-size: var(--step--1); + color: var(--text-soft); } - - .title { font-size: var(--step-1); line-height: 1.3; letter-spacing: -0.01em; font-weight: 600; margin-bottom: 0.5rem; } - .due { font-size: var(--step--1); color: var(--muted); margin-bottom: 0.5rem; } - .desc { font-size: var(--step--1); color: var(--text-soft); line-height: 1.55; } </style> </head> <body> - <header>Juksere Trives Aldri</header> + <div class="card"> + <h1>JTA Portal</h1> + <p>Public landing page. Login is required to view assignments and all other pages.</p> + + <div class="actions"> + <button id="login-btn" type="button">Login</button> + </div> - <div id="app"></div> + <div id="status" aria-live="polite"></div> + </div> <script> - const app = document.getElementById("app"); - - function renderHome(data) { - // Group by class - const groups = {}; - data.forEach(item => { - const cls = item.class || "Other"; - if (!groups[cls]) groups[cls] = []; - groups[cls].push(item); - }); - - // Render sections - Object.entries(groups).forEach(([cls, items]) => { - const section = document.createElement("div"); - section.className = "section"; - - const title = document.createElement("div"); - title.className = "section-title"; - title.textContent = cls; - section.appendChild(title); - - const container = document.createElement("div"); - container.className = "container"; - - items.forEach(item => { - const card = document.createElement("a"); - card.className = "card"; - card.href = item.link || "#"; - - card.innerHTML = ` - <div class="title">${item.title}</div> - <div class="due">Due: ${item.due}</div> - ${item.description ? `<div class="desc">${item.description}</div>` : ""} - `; - - container.appendChild(card); - }); - - section.appendChild(container); - app.appendChild(section); - }); - } - - fetch("assignments.json") - .then(res => res.json()) - .then(data => renderHome(data)) - .catch(() => { - app.innerHTML = "<p style='padding:1rem;'>Failed to load data.</p>"; - }); + const authCookieName = "jta_auth"; + const status = document.getElementById("status"); + const loginButton = document.getElementById("login-btn"); + + function toHex(buffer) { + const bytes = new Uint8Array(buffer); + return Array.from(bytes) + .map((b) => b.toString(16).padStart(2, "0")) + .join(""); + } + + async function sha256Hex(input) { + const data = new TextEncoder().encode(input); + const digest = await crypto.subtle.digest("SHA-256", data); + return toHex(digest); + } + + function setAuthCookie(hashValue) { + const maxAge = 10 * 365 * 24 * 60 * 60; + document.cookie = `${authCookieName}=${hashValue}; Path=/; Max-Age=${maxAge}; SameSite=Lax`; + } + + async function loginWithPrompt() { + const password = prompt("Enter password"); + if (password === null) { + status.textContent = "Login cancelled."; + return; + } + + status.textContent = "Logging in..."; + + const hashValue = await sha256Hex(password); + setAuthCookie(hashValue); + + status.textContent = "Success. Redirecting..."; + window.location.href = "/assignments"; + } + + loginButton.addEventListener("click", () => { + loginWithPrompt().catch(() => { + status.textContent = "Network error while logging in."; + }); + }); </script> </body> </html> diff --git a/modules/services/nixos/jta/main.go b/modules/services/nixos/jta/main.go index 3e5fc1f..1390d7f 100644 --- a/modules/services/nixos/jta/main.go +++ b/modules/services/nixos/jta/main.go @@ -29,8 +29,28 @@ var styleCSS string //go:embed index.html var indexHTML string +//go:embed assignments.html +var assignmentsHTML string + var pageTmpl = template.Must(template.New("md_template.html").Parse(mdTemplate)) +const authCookieName = "jta_auth" + +func isAuthenticated(r *http.Request) bool { + expectedHash := strings.TrimSpace(os.Getenv("AUTH_PASSWORD_HASH")) + if expectedHash == "" { + return false + } + + cookie, err := r.Cookie(authCookieName) + if err != nil { + return false + } + + providedHash := strings.TrimSpace(cookie.Value) + return strings.EqualFold(providedHash, expectedHash) +} + func serveMarkdown(w http.ResponseWriter, r *http.Request) { // Prevent path traversal clean := filepath.Clean(r.URL.Path) @@ -81,6 +101,17 @@ func main() { return } + if !isAuthenticated(r) { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + + if r.URL.Path == "/assignments" || r.URL.Path == "/assignments.html" { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + w.Write([]byte(assignmentsHTML)) + return + } + // Intercept markdown files if strings.HasSuffix(r.URL.Path, ".md") { serveMarkdown(w, r) diff --git a/modules/services/nixos/jta/style.css b/modules/services/nixos/jta/style.css index 1d52d19..ec09ca9 100644 --- a/modules/services/nixos/jta/style.css +++ b/modules/services/nixos/jta/style.css @@ -124,6 +124,35 @@ code { border-radius: 6px; } +img { + display: block; + max-width: 100%; + height: auto; + margin: 1rem 0; + border-radius: 10px; +} + +p > img, +li > img { + margin: 0.75rem 0; +} + +a > img { + transition: transform 0.15s ease, box-shadow 0.15s ease; +} + +a:hover > img { + transform: translateY(-1px); + box-shadow: 0 8px 20px rgba(2, 6, 23, 0.45); +} + +img[width], +img[height] { + width: auto; + height: auto; + max-width: 100%; +} + /* Links */ a { color: #93c5fd; |
