nostr verification working

This commit is contained in:
counterweight 2025-02-12 00:44:17 +01:00
parent f42ae5fc1d
commit 19667807bb
Signed by: counterweight
GPG key ID: 883EDBAA726BD96C
8 changed files with 258 additions and 7 deletions

View file

@ -1,5 +1,7 @@
const DEFAULT_SESSION_DURATION_SECONDS = 60 * 60 * 24 * 30;
const DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS = 60 * 60 * 24 * 30;
module.exports = {
DEFAULT_SESSION_DURATION_SECONDS
DEFAULT_SESSION_DURATION_SECONDS,
DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS
}

View file

@ -0,0 +1,27 @@
const { DataTypes } = require('sequelize');
const sequelize = require('../database');
const NostrChallengeCompleted = sequelize.define('NostrChallengeCompleted', {
uuid: {
type: DataTypes.UUID,
allowNull: false,
unique: true,
primaryKey: true
},
challenge: {
type: DataTypes.STRING,
allowNull: false
},
signed_event: {
type: DataTypes.JSONB,
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false
}
}, {
tableName: 'nostr_challenge_completed'
});
module.exports = NostrChallengeCompleted;

View file

@ -12,6 +12,10 @@ const NostrChallengeCreated = sequelize.define('NostrChallengeCreated', {
type: DataTypes.STRING,
allowNull: false
},
expires_at: {
type: DataTypes.DATE,
allowNull: false
},
created_at: {
type: DataTypes.DATE,
allowNull: false

View file

@ -9,9 +9,7 @@ async function login() {
if (!challengeResponse.ok) throw new Error("Failed to fetch challenge");
const { challenge } = await challengeResponse.json();
console.log(`Received challenge: ${challenge}`);
} catch (error) { }
/* const pubkey = await window.nostr.getPublicKey();
const pubkey = await window.nostr.getPublicKey();
const event = {
kind: 22242,
@ -29,6 +27,9 @@ async function login() {
body: JSON.stringify(signedEvent),
});
} catch (error) { }
/*
if (!verifyResponse.ok) throw new Error("Verification failed");
const verifyResult = await verifyResponse.json();

View file

@ -1,5 +1,5 @@
const express = require('express');
//const { generatePrivateKey, getPublicKey, verifySignature } = require("nostr-tools");
const { getPublicKey, verifyEvent } = require("nostr-tools");
const crypto = require("crypto");
const appInviteService = require('../services/appInviteService');
@ -64,4 +64,40 @@ router.get('/nostr-challenge', async (req, res) => {
res.json({ 'challenge': nostrChallenge.challenge });
});
router.post("/nostr-verify", async (req, res) => {
const signedEvent = req.body;
if (!signedEvent || !signedEvent.tags) {
return res.status(400).json({ success: false, error: "Invalid event format" });
}
const challengeTag = signedEvent.tags.find(tag => tag[0] === "challenge");
if (!challengeTag) {
return res.status(400).json({ success: false, error: "No challenge tag found" });
}
const challenge = challengeTag[1];
if (!(await nostrService.isNostrChallengeFresh(challenge))) {
return res.status(410).json({ success: false, error: "Challenge expired, request new one." })
}
if (await nostrService.hasNostrChallengeBeenCompleted(challenge)) {
return res.status(410).json({ success: false, error: "Challenge already used, request new one." })
}
const isSignatureValid = verifyEvent(signedEvent);
if (!isSignatureValid) {
return res.status(400).json({ success: false, error: "Invalid signature" });
}
await nostrService.completeNostrChallenge(
challenge,
signedEvent
)
return res.json({ success: true, signedEvent });
});
module.exports = router;

View file

@ -1,15 +1,76 @@
const uuid = require("uuid");
const crypto = require("crypto");
const { Op } = require('sequelize');
const NostrChallengeCreated = require('../models/NostrChallengeCreated');
const NostrChallengeCompleted = require("../models/NostrChallengeCompleted");
const constants = require('../constants');
async function createNostrChallenge() {
const currentTimestamp = new Date();
const expiryTimestamp = new Date(currentTimestamp.getTime());
expiryTimestamp.setSeconds(expiryTimestamp.getSeconds() + constants.DEFAULT_NOSTR_CHALLENGE_DURATION_SECONDS);
const nostrChallenge = await NostrChallengeCreated.create({
'uuid': uuid.v7(),
challenge: crypto.randomBytes(32).toString("hex"),
created_at: new Date().toISOString()
expires_at: expiryTimestamp.toISOString(),
created_at: currentTimestamp.toISOString()
});
return nostrChallenge;
}
exports.createNostrChallenge = createNostrChallenge;
async function isNostrChallengeFresh(challengeString) {
const nostrChallenge = await NostrChallengeCreated.findOne({
where: {
challenge: challengeString,
expires_at: {
[Op.gt]: new Date()
}
}
});
if (nostrChallenge) {
return true;
}
return false;
}
async function hasNostrChallengeBeenCompleted(challengeString) {
const completedNostrChallenge = await NostrChallengeCompleted.findOne(
{
where: {
challenge: challengeString
}
}
);
if (completedNostrChallenge) {
return true;
}
return false;
}
async function completeNostrChallenge(
challenge,
signedEvent
) {
await NostrChallengeCompleted.create({
'uuid': uuid.v7(),
challenge: challenge,
signed_event: signedEvent,
created_at: new Date().toISOString()
}
);
return;
}
exports.createNostrChallenge = createNostrChallenge;
exports.isNostrChallengeFresh = isNostrChallengeFresh;
exports.hasNostrChallengeBeenCompleted = hasNostrChallengeBeenCompleted;
exports.completeNostrChallenge = completeNostrChallenge;