+ create user with public/private key

+ sign and verify votes and prevent unverified updates
This commit is contained in:
2026-04-04 22:36:17 +02:00
parent b5cb0e83e3
commit bc5e2eead8
15 changed files with 10672 additions and 52 deletions

View File

@@ -2,7 +2,7 @@
import { ref, watch, onUnmounted } from 'vue';
import * as Y from 'yjs';
export const usePoll = (pollId: Ref<string | null>) => {
export const usePoll = (pollId: Ref<string | null>, user: Ref<UserData | null>) => {
const pollData = ref<PollData>({});
const isConnected = ref(false);
const connectionAttempFailed = ref(false);
@@ -26,7 +26,10 @@ export const usePoll = (pollId: Ref<string | null>) => {
// 1. Fetch Snapshot from Nuxt API
try {
const response = await $fetch<{ update: number[] | null }>(`/api/polls/${id}`);
const response = await $fetch<{ update: number[] | null }>(`/api/polls/${id}`).catch((e) => {
console.error("Failed to get poll: " + id,e)
});
//trust the server without verification.
if (response?.update) {
Y.applyUpdate(ydoc, new Uint8Array(response.update));
}
@@ -37,20 +40,28 @@ export const usePoll = (pollId: Ref<string | null>) => {
yMap = ydoc.getMap<SignedData<VoteData>[]>('shared-poll');
// 2. Local State Sync
yMap.observe(() => {
pollData.value = yMap!.toJSON();
yMap.observe(async () => {
await performUpdateAndVerify();
saveStateToServer(id);
});
pollData.value = yMap.toJSON();
await performUpdateAndVerify();
// 3. P2P Connection
const { WebrtcProvider } = await import('y-webrtc');
provider = new WebrtcProvider(`nuxt-p2p-${id}`, ydoc, {
signaling: ["ws:localhost:4444", "ws:lynxpi.ddns.net:4444"]
signaling: ["ws://localhost:4444", "ws://lynxpi.ddns.net:4444"]
});
provider.on('synced', (arg: {synced: boolean}) => isConnected.value = arg.synced);
provider.on('peers', (data: any) => connectedPeers.value = data.webrtcPeers.length + 1);
provider.on('synced', (arg: {synced: boolean}) => {
isConnected.value = arg.synced;
console.log('Connection synced:', arg.synced) // "connected" or "disconnected"
});
provider.on('status', (event: { connected: boolean }) => {
console.log('Connection status:', event.connected) // "connected" or "disconnected"
})
provider.on('peers', (data: any) => {
connectedPeers.value = data.webrtcPeers.length + 1
});
};
const saveStateToServer = async (id: string) => {
@@ -59,7 +70,9 @@ export const usePoll = (pollId: Ref<string | null>) => {
await $fetch(`/api/polls/${id}`, {
method: 'POST',
body: { update: Array.from(stateUpdate) }
}).catch(() => {});
}).catch((e) => {
console.error("Failed to update poll",e)
});
};
// Watch for ID changes (e.g., user clicks a link or goes back)
@@ -77,12 +90,29 @@ export const usePoll = (pollId: Ref<string | null>) => {
if (yMap && !yMap.has(optionName)) yMap.set(optionName, []);
};
const vote = (optionName: string, uuid: string) => {
if (yMap?.has(optionName)) {
var voteData : SignedData<VoteData>[] | undefined = yMap.get(optionName)
if(voteData != undefined){
const performUpdateAndVerify = async () => {
const pollDataUpdate = yMap!.toJSON();
console.log("Poll Data Update: ", pollDataUpdate)
for(var option in pollDataUpdate){
console.log("verifying votes for option: " + option);
const votes = pollDataUpdate[option] || [];
const verified = await verifyAllVotesForOption(votes);
if(!verified){
console.error("Failed to verify option: "+option)
return;
}
}
console.log("All options verified! :)")
pollData.value = pollDataUpdate
}
const vote = async (optionName: string) => {
const currentUser = user.value;
if (currentUser != undefined && yMap?.has(optionName)) {
const voteData = [...(yMap.get(optionName) || [])];
if(voteData != undefined && currentUser.private_key){
var unsignedVoteData : VoteData = {
userid: uuid,
userid: currentUser.userid,
timestamp: new Date().toISOString()
}
var newVote : SignedData<VoteData> = {
@@ -90,7 +120,9 @@ export const usePoll = (pollId: Ref<string | null>) => {
signature: "",
}
voteData?.push(newVote)
yMap.set(optionName, voteData);
const signature = await signVote(voteData,currentUser.private_key);
newVote.signature=signature
yMap?.set(optionName, voteData);
}
}
};