Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Readme

Val Town Token Session Auth

Protect your vals behind your Val Town API Token. Use session cookies to persist authentication.

Forked from @pomdtr/password_auth.

Usage

import { vtTokenSessionAuth } from "https://esm.town/v/stevekrouse/vtTokenSessionAuthSafe"; export default vtTokenSessionAuth(() => Response.json("Authenticated!"))

To sign out, navigate to /signout.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import { deleteCookie, getCookies, setCookie } from "https://deno.land/std/http/cookie.ts";
import { inferRequestVal } from "https://esm.town/v/andreterron/inferRequestVal?v=2";
import { sqlite } from "https://esm.town/v/std/sqlite?v=4";
import { html } from "https://esm.town/v/stevekrouse/html?v=5";
import { zip } from "npm:lodash-es";
import { nanoid } from "npm:nanoid";
type Session = {
id: string;
expiresAt: number;
};
async function createSessionTable(tableName: string) {
await sqlite.execute(`CREATE TABLE ${tableName} (
id TEXT NOT NULL PRIMARY KEY,
expires_at INTEGER NOT NULL,
val_slug STRING NOT NULL
);`);
}
async function createSession(tableName: string, valSlug: string): Promise<Session> {
try {
const expires_at = new Date();
expires_at.setDate(expires_at.getDate() + 7);
const session: Session = { id: nanoid(), expiresAt: expires_at.getTime() };
await sqlite.execute({
sql: `INSERT INTO ${tableName} (id, val_slug, expires_at) VALUES (?, ?, ?)`,
args: [session.id, valSlug, session.expiresAt],
});
return session;
} catch (e) {
if (e.message.includes("no such table")) {
await createSessionTable(tableName);
return createSession(tableName, valSlug);
}
throw e;
}
}
async function getSession(tableName: string, sessionID: string, valSlug: string): Promise<Session> {
try {
const { rows, columns } = await sqlite.execute({
sql: `SELECT * FROM ${tableName} WHERE id = ? AND val_slug = ?`,
args: [sessionID, valSlug],
});
if (rows.length == 0) {
return null;
}
return Object.fromEntries(zip(columns, rows.at(0))) as Session;
} catch (e) {
if (e.message.includes("no such table")) {
return null;
}
throw e;
}
}
const loginPage = (val) => `<html>
<head>
<link rel="icon" href="https://fav.farm/🔒" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
</head>
<body style="display: flex; justify-content: center; align-items: center;">
<article>
<p>This website is protected behind ${val.handle}'s Val Town API token.</p>
<p>If you are ${val.handle}, get your <a href="https://www.val.town/settings/api" target="_blank">API token here</a>.</p>
<p>If you are not ${val.handle}, you can fork <a href="https://val.town/v/${val.handle}/${val.name}">this val</a> to your account, and then login with your API token.
<footer>
<form method="POST" style="margin-block-end: 0em;">
<fieldset role="group" style="margin-bottom: 0em;">
<input id="password" placeholder="Password" name="password" type="password" />
<input type="submit" value="Sign In"/>
</fieldset>
</form>
</footer>
</article>
</body>
</html>`;
export function redirect(location: string): Response {
return new Response(null, {
headers: {
location,
},
status: 302,
});
}
type Handler = (req?: Request) => Response | Promise<Response>;
type PasswordAuthOptions = {
verifyPassword: (password: string) => boolean | Promise<boolean>;
sessionTable?: string;
};
const cookieName = "auth_session";
September 11, 2024