+38
scripts/gen-invite.js
+38
scripts/gen-invite.js
···
1
+
import { Database } from "bun:sqlite";
2
+
3
+
const db = new Database("readit.db", {
4
+
strict: true,
5
+
});
6
+
7
+
// Create the invites table if it doesn't exist
8
+
db.run(`
9
+
CREATE TABLE IF NOT EXISTS invites (
10
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
11
+
token TEXT NOT NULL,
12
+
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
13
+
usedAt TIMESTAMP
14
+
)
15
+
`);
16
+
17
+
// Generate a new invite token
18
+
function generateInviteToken() {
19
+
const hasher = new Bun.CryptoHasher("sha256", "super-secret-invite-key");
20
+
return hasher.update(Math.random().toString()).digest("hex");
21
+
}
22
+
23
+
// Store the token in the database
24
+
function createInvite() {
25
+
const token = generateInviteToken();
26
+
db.run("INSERT INTO invites (token) VALUES ($token)", { token });
27
+
console.log(`Invite token created: ${token}`);
28
+
}
29
+
30
+
// CLI usage
31
+
const command = process.argv[2];
32
+
const arg = process.argv[3];
33
+
34
+
if (command === "create") {
35
+
createInvite();
36
+
} else {
37
+
console.log("requires an arg");
38
+
}
+37
src/invite.js
+37
src/invite.js
···
1
+
const { db } = require("./db");
2
+
3
+
const validateInviteToken = async (req, res, next) => {
4
+
const token = req.query.token;
5
+
6
+
if (!token) {
7
+
return res.render("register", {
8
+
message: "this instance requires an invite",
9
+
isDisabled: true,
10
+
});
11
+
}
12
+
13
+
const invite = db
14
+
.query("SELECT * FROM invites WHERE token = $token AND usedAt IS null")
15
+
.get({ token });
16
+
17
+
if (!invite) {
18
+
return res.render("register", {
19
+
message: "this invite token is invalid",
20
+
isDisabled: true,
21
+
});
22
+
}
23
+
24
+
if (invite.usedAt) {
25
+
return res.render("register", {
26
+
message: "this invite has been claimed",
27
+
isDisabled: true,
28
+
});
29
+
}
30
+
31
+
req.invite = invite;
32
+
next();
33
+
};
34
+
35
+
module.exports = {
36
+
validateInviteToken,
37
+
};
+7
src/public/styles.css
+7
src/public/styles.css
···
491
491
color: var(--bg-color);
492
492
}
493
493
494
+
.submit-button button:disabled {
495
+
width: 100%;
496
+
padding: 12px;
497
+
background-color: var(--bg-color-muted);
498
+
color: var(--text-color-muted);
499
+
}
500
+
494
501
.register-error-message {
495
502
flex-flow: row wrap;
496
503
color: var(--error-text-color);
+10
-3
src/routes/index.js
+10
-3
src/routes/index.js
···
6
6
const { JWT_KEY } = require("../");
7
7
const { db } = require("../db");
8
8
const { authenticateToken } = require("../auth");
9
+
const { validateInviteToken } = require("../invite");
9
10
10
11
const router = express.Router();
11
12
const G = new geddit.Geddit();
···
113
114
res.render("media", { kind, url });
114
115
});
115
116
116
-
router.get("/register", async (req, res) => {
117
-
res.render("register");
117
+
router.get("/register", validateInviteToken, async (req, res) => {
118
+
res.render("register", { isDisabled: false, token: req.query.token });
118
119
});
119
120
120
-
router.post("/register", async (req, res) => {
121
+
router.post("/register", validateInviteToken, async (req, res) => {
121
122
const { username, password, confirm_password } = req.body;
122
123
123
124
if (!username || !password || !confirm_password) {
···
141
142
142
143
try {
143
144
const hashedPassword = await Bun.password.hash(password);
145
+
146
+
db.query("UPDATE invites SET usedAt = CURRENT_TIMESTAMP WHERE id = $id", {
147
+
id: req.invite.id,
148
+
});
149
+
144
150
const insertedRecord = db
145
151
.query(
146
152
"INSERT INTO users (username, password_hash) VALUES ($username, $hashedPassword)",
···
159
165
})
160
166
.redirect("/");
161
167
} catch (err) {
168
+
console.log(err);
162
169
return res.render("register", {
163
170
message: "error registering user, try again later",
164
171
});
+6
-2
src/views/register.pug
+6
-2
src/views/register.pug
···
1
1
include ../mixins/head
2
2
3
+
- var action = "/register" + (token?`?token=${token}`:'')
3
4
doctype html
4
5
html
5
6
+head("register")
···
9
10
if message
10
11
div.register-error-message
11
12
| #{message}
12
-
form(action="/register" method="post")
13
+
form(action=`${action}` method="post")
13
14
div.input-text
14
15
label(for="username") username
15
16
input(type="text" name="username" required)
···
20
21
label(for="confirm_password") confirm password
21
22
input(type="password" name="confirm_password" required)
22
23
div.submit-button
23
-
button(type="submit") register
24
+
if isDisabled
25
+
button(type="submit" disabled) register'nt :(
26
+
else
27
+
button(type="submit") register
24
28
div
25
29
p
26
30
| already have an account?