Skip to content

[Security] Sharing links lack expiration — permanently valid shared notes can't be revoked #271

@anshul23102

Description

@anshul23102

Executive Summary

When users share notes via link, those links never expire. Even if the user regrets sharing or deletes the note, the link remains valid and accessible forever.

Proposed Solution

const ShareLinkSchema = new Schema({
  noteId: String,
  userId: String,  // note owner
  token: String,   // unique share token
  createdAt: Date,
  expiresAt: Date,
  accessCount: Number,
  maxAccess: Number,  // null = unlimited
  isRevoked: Boolean,
});

// Create share link
app.post('/api/notes/:id/share', authenticateUser, async (req, res) => {
  const note = await Note.findOne({ _id: req.params.id, userId: req.user.id });
  if (!note) return res.status(404).json({ error: 'Note not found' });
  
  const token = crypto.randomBytes(32).toString('hex');
  const expiresAt = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);  // 7 days default
  
  const shareLink = await ShareLink.create({
    noteId: note._id,
    userId: req.user.id,
    token,
    createdAt: new Date(),
    expiresAt,
    accessCount: 0,
    maxAccess: req.body.maxAccess || null,
    isRevoked: false,
  });
  
  res.json({ 
    shareUrl: `${process.env.APP_URL}/share/${token}`,
    expiresAt,
    maxAccess: shareLink.maxAccess,
  });
});

// Access shared note
app.get('/api/share/:token', async (req, res) => {
  const shareLink = await ShareLink.findOne({ token: req.params.token });
  
  if (!shareLink) return res.status(404).json({ error: 'Link not found' });
  if (shareLink.isRevoked) return res.status(403).json({ error: 'Link revoked' });
  if (new Date() > shareLink.expiresAt) return res.status(403).json({ error: 'Link expired' });
  if (shareLink.maxAccess && shareLink.accessCount >= shareLink.maxAccess) {
    return res.status(403).json({ error: 'Max access limit reached' });
  }
  
  const note = await Note.findById(shareLink.noteId);
  shareLink.accessCount += 1;
  await shareLink.save();
  
  res.json({ note });
});

// Revoke share link
app.delete('/api/share/:token', authenticateUser, async (req, res) => {
  const shareLink = await ShareLink.findOne({ token: req.params.token });
  
  if (!shareLink || shareLink.userId !== req.user.id) {
    return res.status(403).json({ error: 'Unauthorized' });
  }
  
  shareLink.isRevoked = true;
  await shareLink.save();
  
  res.json({ message: 'Share link revoked' });
});

Checklist

  • I have searched existing issues and confirmed this is not a duplicate
  • I have read the CONTRIBUTING.md guidelines
  • I have provided clear steps to reproduce the issue
  • I have described expected vs. actual behavior clearly
  • This issue title is clear and specific
  • This repository has been verified as NSOC on https://www.nsoc.in/projects

@HarshYadav152 Could you please /assign this issue to me? I would like to implement expiring and revocable share links under NSOC '26.

/assign

Metadata

Metadata

Assignees

No one assigned

    Labels

    nsoc26Tells us this project is associated with Nexus Spring of Code

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions