Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
const express = require('express');
const mongoose = require('mongoose');
const shareRoutes = require('./shareRoutes');

const app = express();

app.use(express.json());
app.use('/', shareRoutes);

mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/shares');

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
10 changes: 10 additions & 0 deletions backend/models/AccessLog.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const mongoose = require('mongoose');

const accessLogSchema = new mongoose.Schema({
tokenHash: { type: String, required: true, index: true },
ip: { type: String, required: true },
timestamp: { type: Date, default: Date.now },
success: { type: Boolean, required: true }
});

module.exports = mongoose.model('AccessLog', accessLogSchema);
12 changes: 12 additions & 0 deletions backend/models/Share.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const mongoose = require('mongoose');

const shareSchema = new mongoose.Schema({
tokenHash: { type: String, required: true, unique: true, index: true },
resourceId: { type: String, required: true },
ownerId: { type: String, required: true },
expiresAt: { type: Date, required: true },
revoked: { type: Boolean, default: false },
createdAt: { type: Date, default: Date.now }
});

module.exports = mongoose.model('Share', shareSchema);
12 changes: 12 additions & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"name": "secure-sharing-backend",
"version": "1.0.0",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"dependencies": {
"express": "^4.18.2",
"mongoose": "^7.5.0"
}
}
53 changes: 53 additions & 0 deletions backend/shareRoutes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const express = require('express');
const router = express.Router();
const shareService = require('./shareService');

// Placeholder auth middleware - replace with actual auth
const auth = (req, res, next) => {
req.user = { id: 'testOwner' }; // Replace with actual user from auth
next();
};

router.post('/share', auth, async (req, res) => {
try {
const { resourceId, expiresAt } = req.body;
const ownerId = req.user.id;
const result = await shareService.createShareLink({ resourceId, ownerId, expiresAt });
res.json(result);
} catch (error) {
res.status(400).json({ error: error.message });
}
});

router.get('/share/:token', async (req, res) => {
try {
const { token } = req.params;
const ip = req.ip || req.connection.remoteAddress;
const resource = await shareService.getSharedResource(token, ip);
res.json(resource);
} catch (error) {
res.status(403).json({ error: error.message });
}
});

router.delete('/share/:token', auth, async (req, res) => {
try {
const { token } = req.params;
await shareService.revokeShare(token);
res.json({ message: 'Revoked' });
} catch (error) {
res.status(400).json({ error: error.message });
}
});

router.get('/share/:token/logs', auth, async (req, res) => {
try {
const { token } = req.params;
const logs = await shareService.getAccessLogs(token);
res.json(logs);
} catch (error) {
res.status(400).json({ error: error.message });
}
});

module.exports = router;
62 changes: 62 additions & 0 deletions backend/shareService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const crypto = require('crypto');
const Share = require('./models/Share');
const AccessLog = require('./models/AccessLog');

async function createShareLink({ resourceId, ownerId, expiresAt }) {
const token = crypto.randomBytes(32).toString('hex');
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
const share = new Share({
tokenHash,
resourceId,
ownerId,
expiresAt: new Date(expiresAt),
revoked: false
});
await share.save();
return { token, url: `/share/${token}` };
}

async function getSharedResource(token, ip) {
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
const share = await Share.findOne({ tokenHash });
if (!share) {
await logAccess(tokenHash, ip, false);
throw new Error('Invalid token');
}
if (share.revoked) {
await logAccess(tokenHash, ip, false);
throw new Error('Link revoked');
}
if (new Date() > share.expiresAt) {
await logAccess(tokenHash, ip, false);
throw new Error('Link expired');
}
await logAccess(tokenHash, ip, true);
return { resourceId: share.resourceId, ownerId: share.ownerId };
}

async function revokeShare(token) {
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
await Share.updateOne({ tokenHash }, { revoked: true });
}

async function getAccessLogs(token) {
const tokenHash = crypto.createHash('sha256').update(token).digest('hex');
return await AccessLog.find({ tokenHash }).sort({ timestamp: -1 });
}

async function logAccess(tokenHash, ip, success) {
const log = new AccessLog({
tokenHash,
ip,
success
});
await log.save();
}

module.exports = {
createShareLink,
getSharedResource,
revokeShare,
getAccessLogs
};
Loading