Add index.js
This commit is contained in:
commit
e7cee49491
1 changed files with 191 additions and 0 deletions
191
index.js
Normal file
191
index.js
Normal file
|
@ -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}`);
|
||||||
|
});
|
Loading…
Reference in a new issue