Readme

Email Auth for Val Town

⚠️ Require a pro account (needed to send email to users)

Usage

Create an http server, and wrap it in the emailAuth middleware.

import { emailAuth } from "https://esm.town/v/pomdtr/email_auth" export default emailAuth((req) => { return new Response(`your mail is ${req.headers.get("X-User-Email")}`); }, { verifyEmail: (email) => { return email == "steve@val.town" } });

💡 If you do not want to put your email in clear text, just move it to an env var (ex: Deno.env.get("email"))

If you want to allow anyone to access your val, just use:

import { emailAuth } from "https://esm.town/v/pomdtr/email_auth" export default emailAuth((req) => { return new Response(`your mail is ${req.headers.get("X-User-Email")}`); }, { verifyEmail: (_email) => true });

Each time someone tries to access your val but is not allowed, you will get an email with:

  • the email of the user trying to log in
  • the name of the val the he want to access

You can then just add the user to your whitelist to allow him in (and the user will not need to confirm his email again) !

TODO

  • Add expiration for verification codes and session tokens
  • use links instead of code for verification
  • improve errors pages
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 { email as sendEmail } from "https://esm.town/v/std/email?v=11";
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";
import { createDate, TimeSpan } from "npm:oslo";
import { alphabet, generateRandomString } from "npm:oslo/crypto";
type Session = {
id: string;
email: string;
expiresAt: number;
};
async function createSessionTable(sessionTableName: string) {
await sqlite.execute(`CREATE TABLE ${sessionTableName} (
id TEXT NOT NULL PRIMARY KEY,
expires_at INTEGER NOT NULL,
email STRING NOT NULL,
val_slug STRING NOT NULL
);`);
}
async function createCodeTable(tableName: string) {
await sqlite.execute(`CREATE TABLE ${tableName} (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
code STRING NOT NULL,
email STRING NOT NULL,
expires_at INTEGER NOT NULL
)`);
}
async function generateEmailVerificationCode(tableName, email: string): Promise<string> {
try {
await sqlite.execute({ sql: `DELETE FROM ${tableName} WHERE email = ?`, args: [email] });
const code = generateRandomString(8, alphabet("0-9"));
const expires_at = createDate(new TimeSpan(5, "m")); // 5 minutes
await sqlite.execute({
sql: `INSERT INTO ${tableName} (email, code, expires_at) VALUES (?, ?, ?)`,
args: [email, code, expires_at.getTime() / 1000],
});
return code;
} catch (e) {
if (e.message.includes("no such table")) {
await createCodeTable(tableName);
return generateEmailVerificationCode(tableName, email);
}
throw e;
}
}
async function createSession(tableName: string, valSlug: string, email: string): Promise<Session> {
try {
const expiresAt = new Date();
expiresAt.setDate(expiresAt.getDate() + 7);
const sessionID = nanoid();
await sqlite.execute({
sql: `INSERT INTO ${tableName} (id, val_slug, expires_at, email) VALUES (?, ?, ?, ?)`,
args: [sessionID, valSlug, expiresAt.getTime() / 1000, email],
});
return {
id: sessionID,
email,
expiresAt: expiresAt.getTime(),
};
} catch (e) {
if (e.message.includes("no such table")) {
await createSessionTable(tableName);
return createSession(tableName, valSlug, email);
}
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;
}
}
Val Town is a social website to write and deploy JavaScript.
Build APIs and schedule functions from your browser.
Comments
3
vawogbemi avatar

Is there a way to modify this to allow any email that is of a certain domain?

pomdtr avatar

Yeah definitely. Maybe the middleware should add a verifyEmail function, that return true if the email is allowed.

pomdtr avatar

Just added support for it!

v46
June 19, 2024