Fixes #206
parent
dcdcb7c102
commit
089bc02ddd
3
build.ts
3
build.ts
|
@ -22,6 +22,9 @@ async function prepareAssets(dist: string) {
|
|||
await copy("web/index.html", `${dist}/web/index.html`, {
|
||||
overwrite: true,
|
||||
});
|
||||
await copy("web/auth.html", `${dist}/web/auth.html`, {
|
||||
overwrite: true,
|
||||
});
|
||||
await copy("web/images/favicon.png", `${dist}/web/favicon.png`, {
|
||||
overwrite: true,
|
||||
});
|
||||
|
|
|
@ -22,7 +22,7 @@ export class HttpSpacePrimitives implements SpacePrimitives {
|
|||
): Promise<Response> {
|
||||
const result = await fetch(url, options);
|
||||
if (result.status === 401) {
|
||||
// Invalid credentials, reloading the browser should trigger a BasicAuth dialog
|
||||
// Invalid credentials, reloading the browser should trigger authentication
|
||||
location.reload();
|
||||
throw Error("Unauthorized");
|
||||
}
|
||||
|
|
|
@ -178,22 +178,49 @@ export class HttpServer {
|
|||
}
|
||||
|
||||
private addPasswordAuth(app: Application) {
|
||||
const excludedPaths = ["/manifest.json", "/favicon.png"];
|
||||
const excludedPaths = [
|
||||
"/manifest.json",
|
||||
"/favicon.png",
|
||||
"/logo.png",
|
||||
"/.auth",
|
||||
];
|
||||
if (this.user) {
|
||||
app.use(async ({ request, response }, next) => {
|
||||
const b64User = btoa(this.user);
|
||||
app.use(async ({ request, response, cookies }, next) => {
|
||||
if (!excludedPaths.includes(request.url.pathname)) {
|
||||
if (
|
||||
request.headers.get("Authorization") ===
|
||||
`Basic ${btoa(this.user!)}`
|
||||
) {
|
||||
await next();
|
||||
const authCookie = await cookies.get("auth");
|
||||
if (!authCookie || authCookie !== b64User) {
|
||||
response.redirect(`/.auth?refer=${request.url.pathname}`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (request.url.pathname === "/.auth") {
|
||||
if (request.method === "GET") {
|
||||
response.headers.set("Content-type", "text/html");
|
||||
response.body = this.systemBoot.assetBundle.readTextFileSync(
|
||||
"web/auth.html",
|
||||
);
|
||||
return;
|
||||
} else if (request.method === "POST") {
|
||||
const values = await request.body({ type: "form" }).value;
|
||||
const username = values.get("username"),
|
||||
password = values.get("password"),
|
||||
refer = values.get("refer");
|
||||
if (this.user === `${username}:${password}`) {
|
||||
await cookies.set("auth", b64User, {
|
||||
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7), // in a week
|
||||
sameSite: "strict",
|
||||
});
|
||||
response.redirect(refer || "/");
|
||||
// console.log("All headers", request.headers);
|
||||
} else {
|
||||
response.redirect("/.auth?error=1");
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
response.status = 401;
|
||||
response.headers.set(
|
||||
"WWW-Authenticate",
|
||||
`Basic realm="Please enter your username and password"`,
|
||||
);
|
||||
response.body = "Unauthorized";
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// Unauthenticated access to excluded paths
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="/favicon.png" />
|
||||
<title>Login to Silver Bullet</title>
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
footer {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: #e1e1e1;
|
||||
border-bottom: #cacaca 1px solid;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
padding: 8px;
|
||||
font-size: 28px;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
form {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
form>div {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: red;
|
||||
}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<h1>Login to <img src="/logo.png" style="height: 1ch;" /> Silver Bullet</h1>
|
||||
</header>
|
||||
<form action="/.auth" method="POST">
|
||||
<input type="hidden" name="refer" value="" />
|
||||
<div class="error-message"></div>
|
||||
<div>
|
||||
<input type="text" name="username" id="username" autocomplete="off" autocorrect="off" autocapitalize="off"
|
||||
autofocus placeholder="Username" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="password" name="password" id="password" placeholder="Password" />
|
||||
</div>
|
||||
<div>
|
||||
<input type="submit" value="Login" />
|
||||
</div>
|
||||
<footer>
|
||||
<a href="https://silverbullet.md">What is Silver Bullet?</a>
|
||||
</footer>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
|
||||
const refer = params.get('refer');
|
||||
if (refer) {
|
||||
document.querySelector('input[name="refer"]').value = refer;
|
||||
}
|
||||
|
||||
if (params.get('error')) {
|
||||
document.querySelector('.error-message').innerText = "Invalid username or password";
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -32,9 +32,6 @@
|
|||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<script>
|
||||
document.documentElement.dataset.theme = localStorage.theme ?? "light";
|
||||
</script>
|
||||
<link rel="stylesheet" href="/main.css" />
|
||||
<script type="module" src="/client.js"></script>
|
||||
<link rel="manifest" href="/manifest.json" -->
|
||||
|
|
Loading…
Reference in New Issue