Luminaria / frontend /lib /app-auth.ts
senlinyy's picture
feat: add login
6230227
Raw
History Blame Contribute Delete
1.94 kB
export const APP_COOKIE = "app_session";
export const APP_SESSION_MAX_AGE = 60 * 60 * 24 * 7;
export const DEFAULT_APP_USERNAME = "student";
export const DEFAULT_APP_PASSWORD = "password";
const encoder = new TextEncoder();
export function appUsername() {
return process.env.APP_USERNAME || DEFAULT_APP_USERNAME;
}
export function appPassword() {
return process.env.APP_PASSWORD || DEFAULT_APP_PASSWORD;
}
function safeEqual(a: string, b: string) {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i += 1) {
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return diff === 0;
}
async function sha256Hex(value: string) {
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(value));
return [...new Uint8Array(digest)]
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
}
export async function appSessionToken() {
return sha256Hex(`${appUsername()}:${appPassword()}`);
}
export async function verifyAppCredentials(username: string, password: string) {
return safeEqual(username, appUsername()) && safeEqual(password, appPassword());
}
export async function isValidAppSession(value: string | undefined) {
if (!value) return false;
return safeEqual(value, await appSessionToken());
}
function attributes(isHttps: boolean, maxAge: number) {
const parts = [`Path=/`, `Max-Age=${maxAge}`, "HttpOnly"];
if (isHttps) {
parts.push("Secure", "SameSite=None", "Partitioned");
} else {
parts.push("SameSite=Lax");
}
return parts.join("; ");
}
export function setAppSessionCookie(headers: Headers, value: string, isHttps: boolean) {
headers.append(
"set-cookie",
`${APP_COOKIE}=${encodeURIComponent(value)}; ${attributes(isHttps, APP_SESSION_MAX_AGE)}`
);
}
export function clearAppSessionCookie(headers: Headers, isHttps: boolean) {
headers.append("set-cookie", `${APP_COOKIE}=; ${attributes(isHttps, 0)}`);
}