aboutsummaryrefslogtreecommitdiffstats
path: root/routes
diff options
context:
space:
mode:
Diffstat (limited to 'routes')
-rw-r--r--routes/users.ts17
1 files changed, 14 insertions, 3 deletions
diff --git a/routes/users.ts b/routes/users.ts
index ab899978..711ad07f 100644
--- a/routes/users.ts
+++ b/routes/users.ts
@@ -101,6 +101,10 @@ export default {
const { user } = req;
try {
user.balance = await g(`balance:${user.id}`);
+ // The password-encrypted nostr key is only handed out by /login, after
+ // the password (and 2FA) check. A bearer token alone mustn't be enough
+ // to fetch it and brute-force or decrypt it offline.
+ user.hasnsec = !!user.nsec;
user.prompt = !!user.prompt;
if (user.pubkey) user.npub = nip19.npubEncode(user.pubkey);
@@ -197,7 +201,7 @@ export default {
l("registered new user", user.username);
- res.send({ ...pick(user, whitelist), sk: user.sk, token });
+ res.send({ ...pick(user, whitelist), nsec: user.nsec, sk: user.sk, token });
} catch (e) {
err("problem registering", e.message);
res.code(500).send(e.message);
@@ -214,7 +218,10 @@ export default {
return res.code(401).send("2fa required");
}
+ // Fresh seed on disable so a seed that leaked while 2FA was on is dead
+ // when it's re-enabled.
user.twofa = false;
+ user.otpsecret = authenticator.generateSecret();
await s(`user:${id}`, user);
emit(username, "user", pick(user, whitelist));
emit(username, "otpsecret", user.otpsecret);
@@ -484,7 +491,7 @@ export default {
secure: true,
sameSite: "lax",
});
- user = pick(user, whitelist);
+ user = { ...pick(user, whitelist), hasnsec: !!user.nsec, nsec: user.nsec };
res.send({ user, token });
} catch (e) {
err("login error", e.message, req.socket.remoteAddress);
@@ -552,7 +559,7 @@ export default {
secure: true,
sameSite: "lax",
});
- user = pick(user, whitelist);
+ user = { ...pick(user, whitelist), hasnsec: !!user.nsec };
res.send({ user, token });
} catch (e) {
err("nostr login error", e.message, req.socket.remoteAddress);
@@ -618,6 +625,10 @@ export default {
async otpsecret(req, res) {
try {
await requirePin(req);
+ // Only needed to enrol. Once 2FA is on, the seed never leaves the
+ // server again — otherwise a stolen token on a PIN-less account is
+ // enough to mint valid codes.
+ if (req.user.twofa) fail("2FA is already enabled");
const { otpsecret, username } = req.user;
res.send({ secret: otpsecret, username });
} catch (e) {