somratpro Claude Sonnet 4.6 commited on
Commit
715f4a5
·
1 Parent(s): b25ba68

fix: auth on env-builder, single login for terminal, new login UI

Browse files

- Protect /env-builder and /env-builder.js with requireAuth (same as /app)
- Inject Authorization: token <JUPYTER_TOKEN> when proxying to JupyterLab
so JupyterLab skips its own login screen — one password instead of two
- New login page: HuggingClaw-style dark card (matches project aesthetic)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. health-server.js +33 -33
health-server.js CHANGED
@@ -116,42 +116,36 @@ function loginUrl(nextPath) {
116
 
117
  function renderLoginPage(nextPath, errorMessage = "") {
118
  const safeNext = sanitizeNext(nextPath);
119
- const errorHtml = errorMessage
120
- ? `<div class="error">${escapeHtml(errorMessage)}</div>`
121
- : "";
122
- return `<!doctype html>
123
- <html lang="en">
124
- <head>
125
- <meta charset="utf-8" />
126
- <meta name="viewport" content="width=device-width, initial-scale=1" />
127
- <title>HuggingMes Login</title>
128
  <style>
129
- :root { color-scheme: dark; --bg:#10141f; --panel:#171d2b; --line:#293246; --text:#f4f7fb; --muted:#9aa7bd; --bad:#ef4444; --accent:#38bdf8; }
130
- * { box-sizing:border-box; }
131
- body { margin:0; min-height:100vh; display:grid; place-items:center; font-family:Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background:var(--bg); color:var(--text); padding:20px; }
132
- main { width:min(440px, 100%); border:1px solid var(--line); background:var(--panel); border-radius:8px; padding:28px; }
133
- h1 { margin:0 0 8px; font-size:1.55rem; letter-spacing:0; }
134
- p { margin:0 0 22px; color:var(--muted); line-height:1.5; }
135
- label { display:block; color:var(--muted); font-size:.82rem; margin-bottom:8px; }
136
- input { width:100%; min-height:46px; border:1px solid var(--line); border-radius:7px; background:#0b0f18; color:var(--text); padding:0 12px; font:inherit; }
137
- button { width:100%; min-height:44px; margin-top:16px; border:0; border-radius:7px; color:#07111f; background:var(--accent); font:inherit; font-weight:750; cursor:pointer; }
138
- .error { border:1px solid rgba(239,68,68,.4); background:rgba(239,68,68,.1); color:#fecaca; border-radius:7px; padding:10px 12px; margin-bottom:16px; }
139
- </style>
140
- </head>
141
- <body>
142
- <main>
143
- <h1>Open HuggingMes</h1>
144
- <p>Enter the <code>GATEWAY_TOKEN</code> from your Space secrets.</p>
145
- ${errorHtml}
146
  <form method="post" action="${LOGIN_PATH}">
147
  <input type="hidden" name="next" value="${escapeHtml(safeNext)}" />
148
- <label for="token">GATEWAY_TOKEN</label>
149
- <input id="token" name="token" type="password" autocomplete="current-password" autofocus required />
150
- <button type="submit">Continue</button>
 
 
151
  </form>
152
- </main>
153
- </body>
154
- </html>`;
155
  }
156
 
157
  function escapeHtml(value) {
@@ -590,6 +584,7 @@ const server = http.createServer(async (req, res) => {
590
  }
591
 
592
  if (path === "/env-builder" || path === "/env-builder/") {
 
593
  try {
594
  const html = fs.readFileSync(require("path").join(__dirname, "env-builder.html"), "utf8");
595
  res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
@@ -602,6 +597,7 @@ const server = http.createServer(async (req, res) => {
602
  }
603
 
604
  if (path === "/env-builder.js") {
 
605
  try {
606
  const js = fs.readFileSync(require("path").join(__dirname, "env-builder.js"), "utf8");
607
  res.writeHead(200, { "content-type": "application/javascript; charset=utf-8" });
@@ -707,7 +703,11 @@ const server = http.createServer(async (req, res) => {
707
  res.end("JupyterLab is not running. Set DEV_MODE=true and JUPYTER_TOKEN in Space secrets to enable /terminal/.");
708
  return;
709
  }
710
- proxyRequest(req, res, JUPYTER_PORT);
 
 
 
 
711
  });
712
  return;
713
  }
 
116
 
117
  function renderLoginPage(nextPath, errorMessage = "") {
118
  const safeNext = sanitizeNext(nextPath);
119
+ return `<!doctype html><html lang="en"><head>
120
+ <meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/>
121
+ <title>HuggingMes</title>
 
 
 
 
 
 
122
  <style>
123
+ :root{color-scheme:dark;--bg:#08080f;--panel:#12111b;--line:#26243a;--text:#f6f4ff;--muted:#7f7a9e;--bad:#fb7185}
124
+ *{box-sizing:border-box}body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;font-family:Inter,ui-sans-serif,system-ui,-apple-system,sans-serif;background:var(--bg);color:var(--text);padding:24px}
125
+ .card{border:1px solid var(--line);background:var(--panel);border-radius:14px;padding:36px 32px;max-width:400px;width:100%;text-align:center}
126
+ h1{margin:0 0 8px;font-size:1.4rem}
127
+ .sub{color:var(--muted);font-size:.82rem;margin:0 0 24px}
128
+ .row{display:flex;gap:8px;margin-top:16px}
129
+ input{flex:1;background:#0d0c18;border:1px solid var(--line);border-radius:7px;padding:10px 12px;color:var(--text);font-size:.95rem;outline:none;transition:border-color .15s}
130
+ input:focus{border-color:#6366f1}
131
+ button{background:#fff;color:#000;border:none;border-radius:7px;padding:10px 20px;font-weight:700;font-size:.95rem;cursor:pointer;transition:opacity .15s;white-space:nowrap}
132
+ button:hover{opacity:.85}
133
+ .err{color:var(--bad);font-size:.82rem;margin-top:10px}
134
+ code{background:#232234;border:1px solid #34324c;border-radius:5px;padding:2px 6px;font-size:.88em}
135
+ </style></head><body>
136
+ <div class="card">
137
+ <h1>🪽 HuggingMes</h1>
138
+ <p class="sub">Enter your <code>GATEWAY_TOKEN</code> to continue</p>
 
139
  <form method="post" action="${LOGIN_PATH}">
140
  <input type="hidden" name="next" value="${escapeHtml(safeNext)}" />
141
+ <div class="row">
142
+ <input type="password" name="token" placeholder="GATEWAY_TOKEN" autofocus autocomplete="current-password" required>
143
+ <button type="submit">Unlock</button>
144
+ </div>
145
+ ${errorMessage ? `<p class="err">Invalid token — try again</p>` : ""}
146
  </form>
147
+ </div>
148
+ </body></html>`;
 
149
  }
150
 
151
  function escapeHtml(value) {
 
584
  }
585
 
586
  if (path === "/env-builder" || path === "/env-builder/") {
587
+ if (!requireAuth(req, res)) return;
588
  try {
589
  const html = fs.readFileSync(require("path").join(__dirname, "env-builder.html"), "utf8");
590
  res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
 
597
  }
598
 
599
  if (path === "/env-builder.js") {
600
+ if (!requireAuth(req, res)) return;
601
  try {
602
  const js = fs.readFileSync(require("path").join(__dirname, "env-builder.js"), "utf8");
603
  res.writeHead(200, { "content-type": "application/javascript; charset=utf-8" });
 
703
  res.end("JupyterLab is not running. Set DEV_MODE=true and JUPYTER_TOKEN in Space secrets to enable /terminal/.");
704
  return;
705
  }
706
+ // Inject the Jupyter token so JupyterLab skips its own login screen.
707
+ // User already authenticated via GATEWAY_TOKEN — no second prompt needed.
708
+ const jToken = process.env.JUPYTER_TOKEN || "";
709
+ const overrides = jToken ? { authorization: `token ${jToken}` } : {};
710
+ proxyRequest(req, res, JUPYTER_PORT, (p) => p, overrides);
711
  });
712
  return;
713
  }