From e7cee49491d240790f1d7432d77880369067b06c Mon Sep 17 00:00:00 2001 From: sneedgroup-holder Date: Wed, 6 Nov 2024 19:18:37 +0000 Subject: [PATCH] Add index.js --- index.js | 191 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 index.js diff --git a/index.js b/index.js new file mode 100644 index 0000000..7039eb5 --- /dev/null +++ b/index.js @@ -0,0 +1,191 @@ +const express = require('express'); +const bodyParser = require('body-parser'); +const bcrypt = require('bcryptjs'); +const jwt = require('jsonwebtoken'); +const { ActivityPubRouter, createActor } = require('activitypub-express'); +const uuid = require('uuid'); +const fetch = require('node-fetch'); + +const app = express(); +const port = 3000; + +const SECRET_KEY = 'your_secret_key'; // Replace with a secure key in a real app + +// In-memory "database" for users and posts +const users = {}; +const posts = {}; +const followers = {}; + +// Middleware to parse JSON +app.use(bodyParser.json()); + +// Helper function to create an actor URL +const actorURL = (username) => `http://localhost:${port}/users/${username}`; + +// Helper function to generate JWT token +const generateToken = (user) => { + return jwt.sign({ id: user.username }, SECRET_KEY, { expiresIn: '1h' }); +}; + +// Middleware to protect routes +const authenticate = (req, res, next) => { + const token = req.headers['authorization']?.split(' ')[1]; + if (!token) { + return res.status(401).json({ error: 'Unauthorized' }); + } + + jwt.verify(token, SECRET_KEY, (err, decoded) => { + if (err) { + return res.status(403).json({ error: 'Invalid or expired token' }); + } + req.user = decoded; + next(); + }); +}; + +// Route to create a new user (actor with password) +app.post('/users', async (req, res) => { + const { username, displayName, password } = req.body; + + if (users[username]) { + return res.status(400).json({ error: 'User already exists' }); + } + + // Hash the password before saving + const hashedPassword = await bcrypt.hash(password, 10); + + const actor = createActor({ + id: actorURL(username), + username, + displayName, + inbox: `http://localhost:${port}/users/${username}/inbox`, + outbox: `http://localhost:${port}/users/${username}/outbox`, + followers: `http://localhost:${port}/users/${username}/followers`, + following: `http://localhost:${port}/users/${username}/following`, + }); + + users[username] = { ...actor, password: hashedPassword }; + res.status(201).json(actor); +}); + +// Route to log in a user and get a token +app.post('/login', async (req, res) => { + const { username, password } = req.body; + + const user = users[username]; + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + // Compare hashed password + const isPasswordValid = await bcrypt.compare(password, user.password); + if (!isPasswordValid) { + return res.status(401).json({ error: 'Invalid password' }); + } + + // Generate and return JWT token + const token = generateToken(user); + res.json({ token }); +}); + +// Route to create a post (status update) with authentication +app.post('/users/:username/posts', authenticate, (req, res) => { + const { username } = req.params; + const { content } = req.body; + + if (req.user.id !== username) { + return res.status(403).json({ error: 'You can only post as yourself' }); + } + + if (!users[username]) { + return res.status(404).json({ error: 'User not found' }); + } + + const post = { + id: `http://localhost:${port}/posts/${uuid.v4()}`, + type: 'Note', + content, + author: users[username], + published: new Date().toISOString(), + }; + + posts[post.id] = post; + res.status(201).json(post); +}); + +// Route to get a user’s posts +app.get('/users/:username/posts', authenticate, (req, res) => { + const { username } = req.params; + + if (req.user.id !== username) { + return res.status(403).json({ error: 'You can only view your own posts' }); + } + + if (!users[username]) { + return res.status(404).json({ error: 'User not found' }); + } + + const userPosts = Object.values(posts).filter(post => post.author.username === username); + res.json(userPosts); +}); + +// ActivityPub setup +const activityPubRouter = new ActivityPubRouter(); + +// Handle incoming Follow activity +activityPubRouter.post('/users/:username/inbox', authenticate, async (req, res) => { + const { username } = req.params; + const activity = req.body; + + if (!users[username]) { + return res.status(404).json({ error: 'User not found' }); + } + + if (activity.type === 'Follow') { + const actor = activity.actor; + + // Add follower to the user’s list of followers + if (!followers[username]) { + followers[username] = []; + } + followers[username].push(actor.id); + + res.status(200).json({ status: 'Followed successfully' }); + + // Optionally, notify the actor that the follow was accepted + await sendFollowResponse(actor, username); + } else { + res.status(400).json({ error: 'Unsupported activity type' }); + } +}); + +// Federated actor info endpoint +activityPubRouter.get('/users/:username', (req, res) => { + const { username } = req.params; + + if (!users[username]) { + return res.status(404).json({ error: 'User not found' }); + } + + res.json(users[username]); +}); + +// Outbox endpoint to send posts +activityPubRouter.get('/users/:username/outbox', (req, res) => { + const { username } = req.params; + + if (!users[username]) { + return res.status(404).json({ error: 'User not found' }); + } + + // Fetch posts from this user’s outbox + const userPosts = Object.values(posts).filter(post => post.author.username === username); + res.json(userPosts); +}); + +app.use(activityPubRouter.router); + +// Start the server +app.listen(port, () => { + console.log(`Federated social network with authentication running at http://localhost:${port}`); +});