Skip to content

Commit

Permalink
Create "remember me" functionality for basic auth, which sets an unex…
Browse files Browse the repository at this point in the history
…piring jwt and refreshes the cookie during requests (#1132)

* Create "remember me" functionality for basic auth, which sets an unexpiring jwt and refreshes the cookie during requests
  • Loading branch information
aekaisato authored Oct 28, 2024
1 parent 123309d commit 64bbe67
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 16 deletions.
11 changes: 6 additions & 5 deletions server/crypto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,12 +60,13 @@ export class JWTIssuer {

createJWT(
payload: Record<string, unknown>,
expirySeconds: number,
expirySeconds?: number,
): Promise<string> {
return create({ alg: "HS512", typ: "JWT" }, {
...payload,
exp: getNumericDate(expirySeconds),
}, this.key);
const jwtPayload = { ...payload };
if (expirySeconds) {
jwtPayload.exp = getNumericDate(expirySeconds);
}
return create({ alg: "HS512", typ: "JWT" }, jwtPayload, this.key);
}

verifyAndDecodeJWT(jwt: string): Promise<Record<string, unknown>> {
Expand Down
50 changes: 39 additions & 11 deletions server/http_server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,7 @@ export class HttpServer {
this.app.get("/.logout", (c) => {
const url = new URL(c.req.url);
deleteCookie(c, authCookieName(url.host));
deleteCookie(c, "refreshLogin");

return c.redirect("/.auth");
});
Expand All @@ -331,20 +332,22 @@ export class HttpServer {
validator("form", (value, c) => {
const username = value["username"];
const password = value["password"];
const rememberMe = value["rememberMe"];

if (
!username || typeof username !== "string" ||
!password || typeof password !== "string"
!password || typeof password !== "string" ||
(rememberMe && typeof rememberMe !== "string")
) {
return c.redirect("/.auth?error=0");
}

return { username, password };
return { username, password, rememberMe };
}),
async (c) => {
const req = c.req;
const url = new URL(c.req.url);
const { username, password } = req.valid("form");
const { username, password, rememberMe } = req.valid("form");

const {
user: expectedUser,
Expand All @@ -353,24 +356,30 @@ export class HttpServer {

if (username === expectedUser && password === expectedPassword) {
// Generate a JWT and set it as a cookie
const jwt = await this.spaceServer.jwtIssuer.createJWT(
{ username },
authenticationExpirySeconds,
);
const jwt = rememberMe
? await this.spaceServer.jwtIssuer.createJWT({ username })
: await this.spaceServer.jwtIssuer.createJWT(
{ username },
authenticationExpirySeconds,
);
console.log("Successful auth");
const inAWeek = new Date(
Date.now() + authenticationExpirySeconds * 1000,
);
setCookie(c, authCookieName(url.host), jwt, {
expires: new Date(
Date.now() + authenticationExpirySeconds * 1000,
), // in a week
expires: inAWeek,
// sameSite: "Strict",
// httpOnly: true,
});
if (rememberMe) {
setCookie(c, "refreshLogin", "true", { expires: inAWeek });
}
const values = await c.req.parseBody();
const from = values["from"];
return c.redirect(typeof from === "string" ? from : "/");
} else {
console.error("Authentication failed, redirecting to auth page.");
return c.redirect("/.auth?error=1", 401);
return c.redirect("/.auth?error=1");
}
},
).all((c) => {
Expand Down Expand Up @@ -404,6 +413,7 @@ export class HttpServer {
const authToken = authHeader.slice("Bearer ".length);
if (authToken === this.spaceServer.authToken) {
// All good, let's proceed
this.refreshLogin(c, host);
return next();
} else {
console.log(
Expand Down Expand Up @@ -435,10 +445,28 @@ export class HttpServer {
return redirectToAuth();
}
}
this.refreshLogin(c, host);
return next();
});
}

private refreshLogin(c: Context, host: string) {
if (getCookie(c, "refreshLogin")) {
const inAWeek = new Date(
Date.now() + authenticationExpirySeconds * 1000,
);
const jwt = getCookie(c, authCookieName(host));
if (jwt) {
setCookie(c, authCookieName(host), jwt, {
expires: inAWeek,
// sameSite: "Strict",
// httpOnly: true,
});
setCookie(c, "refreshLogin", "true", { expires: inAWeek });
}
}
}

private addFsRoutes() {
this.app.use(
"*",
Expand Down
4 changes: 4 additions & 0 deletions web/auth.html
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ <h1>
placeholder="Password"
/>
</div>
<div>
<input type="checkbox" name="rememberMe" id="rememberMe" />
<label>Remember me</label>
</div>
<div>
<input type="submit" value="Login" />
</div>
Expand Down

0 comments on commit 64bbe67

Please sign in to comment.