Skip to content

Commit

Permalink
Merge pull request #95 from HiveTalk/room_owners
Browse files Browse the repository at this point in the history
Check Room owners and initial NIP98 [Work in Progress]
  • Loading branch information
bitkarrot authored Jan 20, 2025
2 parents ef6829d + d12d6f9 commit 13eae63
Show file tree
Hide file tree
Showing 7 changed files with 600 additions and 24 deletions.
3 changes: 3 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
HIVETALK_URL=<https://hivetalk.org>
SUPABASE_URL=<supabase_url>
SUPABASE_ANON_KEY=<supabase_anon_key>
165 changes: 165 additions & 0 deletions app/api/nip98/nip98_client.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// THIS IS SAMPLE CODE ONLY, DOES NOT WORK IN PRODUCTION

// <script src="https://unpkg.com/nostr-tools/lib/nostr.bundle.js"></script>

// const { signEvent } = NostrTools;

// var loggedIn = false;
// let pubkey = "";
// let username = "";
// let avatarURL = "https://cdn-icons-png.flaticon.com/512/149/149071.png";

// function loadUser() {
// if (window.nostr) {
// window.nostr.getPublicKey().then(function (pubkey) {
// if (pubkey) {
// loggedIn = true
// console.log("fetched pubkey", pubkey)
// }
// }).catch((err) => {
// console.log("LoadUser Err", err);
// console.log("logoff section")
// loggedIn = false
// });
// }
// }


// class NostrAuthClient {
// /**
// * Construct a new NostrAuthClient instance.
// * @param {string} pubkey - Nostr public key of the user.
// */
// constructor(pubkey) {
// this.publicKey = pubkey;
// }

// // Generate a Nostr event for HTTP authentication
// async createAuthEvent(url, method, payload = null) {
// const tags = [
// ['u', url],
// ['method', method.toUpperCase()]
// ];

// // If payload exists, add its SHA256 hash
// if (payload) {
// const payloadHash = await this.sha256(payload);
// tags.push(['payload', payloadHash]);
// }

// const event = {
// kind: 27235,
// created_at: Math.floor(Date.now() / 1000),
// tags: tags,
// content: '',
// pubkey: this.publicKey
// };
// console.log('event: ', event)

// // Calculate event ID
// event.id = await this.calculateId(event);

// // Sign the event
// event.sig = await window.nostr.signEvent(event);
// return event;
// }

// // Utility functions for cryptographic operations
// async sha256(message) {
// const msgBuffer = new TextEncoder().encode(message);
// const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
// const hashArray = Array.from(new Uint8Array(hashBuffer));
// return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
// }

// async calculateId(event) {
// const eventData = JSON.stringify([
// 0,
// event.pubkey,
// event.created_at,
// event.kind,
// event.tags,
// event.content
// ]);
// return await this.sha256(eventData);
// }
// }

// // Make an authenticated request
// async function fetchWithNostrAuth(url, options = {}) {
// const method = options.method || 'GET';
// const payload = options.body || null;

// const client = new NostrAuthClient(pubkey);
// const authEvent = await client.createAuthEvent(url, method, payload);

// // Convert event to base64
// const authHeader = 'Nostr ' + btoa(JSON.stringify(authEvent));

// // Add auth header to request
// const headers = new Headers(options.headers || {});
// headers.set('Authorization', authHeader);

// // Make the request
// return fetch(url, {
// ...options,
// headers
// });
// }

// // Helper function to get base domain/host with port if needed
// function getBaseUrl() {
// // Get the full host (includes port if it exists)
// const host = window.location.host;
// // Get the protocol (http: or https:)
// const protocol = window.location.protocol;
// // Combine them
// return `${protocol}//${host}`;
// }

// async function authNIP98() {

// const roomName = "TestRoom";
// const preferredRelays = ['wss://hivetalk.nostr1.com']
// const isModerator = true;

// try {
// const baseUrl = getBaseUrl();
// fetchWithNostrAuth(`${baseUrl}/api/auth`, {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json',
// },
// body: JSON.stringify({
// room: roomName,
// username: username,
// avatarURL: avatarURL,
// relays: preferredRelays,
// isPresenter: isModerator,
// }),
// }).then(response => {
// console.log('response', response.status)
// if (response.status === 302) {
// console.log("response status is 302") // Get the redirect URL from the response
// const data = response.json();
// window.location.href = data.redirectUrl;
// } else if (response.ok) {
// console.log("response.ok", response.ok)
// return response.json();
// } else {
// console.error('Login failed');
// }
// }).then(data => {
// console.log('auth success: ', data);
// document.getElementById('protected').innerHTML = data['message'];
// })

// } catch (error) {
// console.error('Error:', error);
// document.getElementById('protected').innerHTML = error;
// }
// }


// loadUser();
// authNIP98();
181 changes: 181 additions & 0 deletions app/api/nip98/nip98_server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// THIS IS SAMPLE CODE ONLY, DOES NOT WORK IN PRODUCTION


// const express = require('express')
// const cors = require('cors')
// const { verifyEvent } = require('nostr-tools');

// class NostrAuthMiddleware {
// constructor(options = {}) {
// this.timeWindow = options.timeWindow || 60; // seconds
// }

// // Middleware function for Express
// middleware() {
// return async (req, res, next) => {
// try {
// const isValid = await this.validateRequest(req);
// console.log("isValid : ", isValid)
// if (!isValid) {
// return res.status(401).json({
// error: 'Invalid Nostr authentication'
// });
// }
// next();
// } catch (error) {
// console.error('Nostr auth error:', error);
// res.status(401).json({
// error: 'Authentication failed'
// });
// }
// };
// }

// async validateRequest(req) {
// // Extract the Nostr event from Authorization header
// const authHeader = req.headers.authorization;
// console.log("validate request auth header: ", authHeader)

// if (!authHeader?.startsWith('Nostr ')) {
// return false;
// }

// try {
// // Decode the base64 event
// const eventStr = Buffer.from(authHeader.slice(6), 'base64').toString();
// const event = JSON.parse(eventStr);

// console.log("eventStr: ", eventStr)
// console.log('event decoded: ', event)

// // Validate the event
// return await this.validateEvent(event.sig, req);
// } catch (error) {
// console.error('Error parsing auth event:', error);
// return false;
// }
// }

// async validateEvent(event, req) {
// // 1. Check kind
// if (event.kind !== 27235) {
// return false;
// }
// console.log("check kind")

// // 2. Check timestamp
// const now = Math.floor(Date.now() / 1000);
// if (Math.abs(now - event.created_at) > this.timeWindow) {
// return false;
// }
// console.log("check timestamp")

// // 3. Check URL
// const urlTag = event.tags.find(tag => tag[0] === 'u');

// console.log('urltag: ', urlTag[1])
// console.log('full url: ', this.getFullUrl(req))

// if (!urlTag || urlTag[1] !== this.getFullUrl(req)) {
// return false;
// }
// console.log("check URL")

// // 4. Check method
// const methodTag = event.tags.find(tag => tag[0] === 'method');
// if (!methodTag || methodTag[1] !== req.method) {
// return false;
// }
// console.log("check method")

// // 5. Check payload hash if present
// if (req.body && Object.keys(req.body).length > 0) {
// const payloadTag = event.tags.find(tag => tag[0] === 'payload');
// if (payloadTag) {
// const bodyHash = await this.sha256(JSON.stringify(req.body));
// if (bodyHash !== payloadTag[1]) {
// return false;
// }
// }
// }
// console.log("check payload hash if present")

// // 6. Verify event signature
// return await this.verifySignature(event);
// }

// // Utility functions
// getFullUrl(req) {
// // return `${req.protocol}://${req.get('host')}${req.originalUrl}`;
// return `https://${req.get('host')}${req.originalUrl}`;
// }

// async sha256(message) {
// return crypto
// .createHash('sha256')
// .update(message)
// .digest('hex');
// }

// async calculateEventId(event) {
// // Serialize the event data according to NIP-01
// const serialized = JSON.stringify([
// 0, // Reserved for future use
// event.pubkey,
// event.created_at,
// event.kind,
// event.tags,
// event.content
// ]);

// // Calculate SHA256 hash
// return await this.sha256(serialized);
// }

// async verifySignature(event) {
// let isGood = verifyEvent(event)
// console.log("Verify Event", isGood)
// return isGood
// }

// }

// const app = express();
// const nostrAuth = new NostrAuthMiddleware();
// app.use(express.json({ limit: '50mb' })); // Increase the limit if necessary

// app.use(
// cors({
// origin: '*',
// })
// )

// app.get('/api', (req, res) => {
// res.setHeader('Content-Type', 'text/html')
// res.setHeader('Cache-Control', 's-max-age=1, stale-while-revalidate')
// res.send('hello world')
// })

// app.post('/api/auth',
// nostrAuth.middleware(),
// (req, res) => {
// try {
// // Accessing the JSON body sent by the client
// const { room, username, avatarURL, relays, isPresenter } = req.body;
// console.log('Room:', room);
// console.log('Username:', username);
// console.log('Avatar URL:', avatarURL);
// console.log('Relays:', relays);
// console.log('isPresenter', isPresenter);

// // TODO: Redirect to hivetalk room give above info, correctly
// res.status(200).json({ message: 'Authentication successful'});

// } catch (error) {
// console.log("authentication failed")
// res.status(401).json({ error: 'Authentication failed' });
// }
// }
// );

// module.exports = app;
Loading

0 comments on commit 13eae63

Please sign in to comment.