Back to Wekan

Per-User Data Audit - Current Status Summary

docs/Security/PerUserDataAudit2025-12-23/CURRENT_STATUS.md

9.089.1 KB
Original Source

Per-User Data Audit - Current Status Summary

Last Updated: 2025-12-23
Status: ✅ Architecture Finalized
Scope: All data persistence related to swimlanes, lists, cards, checklists, checklistItems


Key Decision: Data Classification

The system now enforces clear separation:

✅ Per-Board Data (MongoDB Documents)

Stored in swimlane/list/card/checklist/checklistItem documents. All users see the same value.

EntityPropertiesWhere Stored
Swimlanetitle, color, height, sort, archivedswimlanes.js document
Listtitle, color, width, sort, archived, wipLimit, starredlists.js document
Cardtitle, color, description, swimlaneId, listId, sort, archivedcards.js document
Checklisttitle, sort, hideCheckedItems, hideAllItemschecklists.js document
ChecklistItemtitle, sort, isFinishedchecklistItems.js document

🔒 Per-User Data (User Profile + Cookies)

Stored in user.profile or cookies. Each user has their own value, not visible to others.

EntityPropertiesWhere Stored
UsercollapsedSwimlanesuser.profile.collapsedSwimlanes[boardId][swimlaneId]
UsercollapsedListsuser.profile.collapsedLists[boardId][listId]
UserhideMiniCardLabelTextuser.profile.hideMiniCardLabelText[boardId]
Public UsercollapsedSwimlanesCookie: wekan-collapsed-swimlanes
Public UsercollapsedListsCookie: wekan-collapsed-lists

Changes Implemented ✅

1. Schema Changes (swimlanes.js, lists.js) ✅ DONE

Swimlanes: Added height field

javascript
height: {
  type: Number,
  optional: true,
  defaultValue: -1,  // -1 = auto-height, 50-2000 = fixed
  custom() {
    const h = this.value;
    if (h !== -1 && (h < 50 || h > 2000)) {
      return 'heightOutOfRange';
    }
  },
}

Lists: Added width field

javascript
width: {
  type: Number,
  optional: true,
  defaultValue: 272,  // 100-1000 pixels
  custom() {
    const w = this.value;
    if (w < 100 || w > 1000) {
      return 'widthOutOfRange';
    }
  },
}

Status: ✅ Implemented in swimlanes.js and lists.js

2. Card Position Storage (cards.js) ✅ ALREADY CORRECT

Cards already store position per-board:

  • sort field: decimal number determining order (shared)
  • swimlaneId: which swimlane (shared)
  • listId: which list (shared)

Status: ✅ No changes needed

3. Checklist Position Storage (checklists.js) ✅ ALREADY CORRECT

Checklists already store position per-board:

  • sort field: decimal number determining order (shared)
  • hideCheckedChecklistItems: per-board setting
  • hideAllChecklistItems: per-board setting

Status: ✅ No changes needed

4. ChecklistItem Position Storage (checklistItems.js) ✅ ALREADY CORRECT

ChecklistItems already store position per-board:

  • sort field: decimal number determining order (shared)

Status: ✅ No changes needed


Changes Not Yet Implemented

1. User Model Refactoring (users.js) ⏳ TODO

Current State: Users.js still has per-user width/height methods that read from user.profile:

  • getListWidth(boardId, listId) - reads user.profile.listWidths
  • getSwimlaneHeight(boardId, swimlaneId) - reads user.profile.swimlaneHeights
  • setListWidth(boardId, listId, width) - writes to user.profile.listWidths
  • setSwimlaneHeight(boardId, swimlaneId, height) - writes to user.profile.swimlaneHeights

Required Change:

  • Remove per-user width/height storage from user.profile
  • Refactor methods to read from list/swimlane documents instead
  • Remove from user schema definition

Status: ⏳ Pending - See IMPLEMENTATION_GUIDE.md for details

2. Migration Script ⏳ TODO

Current State: No migration exists to move existing per-user data to per-board

Required:

  • Create server/migrations/migrateToPerBoardStorage.js
  • Migrate user.profile.swimlaneHeights → swimlane.height
  • Migrate user.profile.listWidths → list.width
  • Remove old fields from user profiles
  • Track migration status

Status: ⏳ Pending - Template available in IMPLEMENTATION_GUIDE.md


Data Examples

Before (Mixed Per-User/Per-Board - WRONG)

javascript
// Swimlane document (per-board)
{
  _id: 'swim123',
  title: 'Development',
  boardId: 'board123',
  // height stored in user profile (per-user) - WRONG!
}

// User A profile (per-user)
{
  _id: 'userA',
  profile: {
    swimlaneHeights: {
      'board123': {
        'swim123': 300  // Only User A sees 300px height
      }
    }
  }
}

// User B profile (per-user)
{
  _id: 'userB',
  profile: {
    swimlaneHeights: {
      'board123': {
        'swim123': 400  // Only User B sees 400px height
      }
    }
  }
}

After (Correct Per-Board/Per-User Separation)

javascript
// Swimlane document (per-board - ALL USERS SEE THIS)
{
  _id: 'swim123',
  title: 'Development',
  boardId: 'board123',
  height: 300  // All users see 300px height
}

// User A profile (per-user - only User A's preferences)
{
  _id: 'userA',
  profile: {
    collapsedSwimlanes: {
      'board123': {
        'swim123': false  // User A: swimlane not collapsed
      }
    },
    collapsedLists: { ... },
    hideMiniCardLabelText: { ... }
    // height and width REMOVED - now in documents
  }
}

// User B profile (per-user - only User B's preferences)
{
  _id: 'userB',
  profile: {
    collapsedSwimlanes: {
      'board123': {
        'swim123': true  // User B: swimlane is collapsed
      }
    },
    collapsedLists: { ... },
    hideMiniCardLabelText: { ... }
    // height and width REMOVED - now in documents
  }
}

Testing Evidence Required

Before Starting UI Integration

  1. Schema Validation

    • Swimlane with height = -1 → accepts
    • Swimlane with height = 100 → accepts
    • Swimlane with height = 25 → rejects (< 50)
    • Swimlane with height = 3000 → rejects (> 2000)
  2. Data Retrieval

    • Swimlanes.findOne('swim123').height returns correct value
    • Lists.findOne('list456').width returns correct value
    • Default values used when not set
  3. Data Updates

    • Swimlanes.update('swim123', { $set: { height: 500 } }) succeeds
    • Lists.update('list456', { $set: { width: 400 } }) succeeds
  4. Per-User Isolation

    • User A collapses swimlane → User B's collapse status unchanged
    • User A hides labels → User B's visibility unchanged

Integration Path

Phase 1: ✅ Schema Definition (DONE)

  • Added height to Swimlanes
  • Added width to Lists
  • Both with validation (custom functions)

Phase 2: ⏳ User Model Refactoring (NEXT)

  • Update user methods to read from documents
  • Remove per-user storage from user.profile
  • Create migration script

Phase 3: ⏳ UI Integration (AFTER Phase 2)

  • Update client code to use new storage locations
  • Update Meteor methods to update documents
  • Update subscriptions if needed

Phase 4: ⏳ Testing & Deployment (FINAL)

  • Run automated tests
  • Manual testing with multiple users
  • Deploy with data migration

Backward Compatibility

For Existing Installations

  • Old user.profile.swimlaneHeights data will be preserved until migration
  • Old user.profile.listWidths data will be preserved until migration
  • New code can read from either location during transition
  • Migration script handles moving data safely

For New Installations

  • Only per-board storage will be used
  • User.profile will only contain per-user settings
  • No legacy data to migrate

File Reference

DocumentPurpose
DATA_PERSISTENCE_ARCHITECTURE.mdComplete architecture specification
IMPLEMENTATION_GUIDE.mdStep-by-step implementation instructions
models/swimlanes.jsSwimlane model with new height field
models/lists.jsList model with new width field

Quick Reference: What Changed?

New Behavior

  • Swimlane Height: Now stored in swimlane document (per-board)
  • List Width: Now stored in list document (per-board)
  • Card Positions: Always been in card document (per-board) ✅
  • Collapse States: Remain in user.profile (per-user) ✅
  • Label Visibility: Remains in user.profile (per-user) ✅

Old Behavior (Being Removed)

  • ❌ Swimlane Height: Was in user.profile (per-user)
  • ❌ List Width: Was in user.profile (per-user)

No Change (Already Correct)

  • ✅ Card Positions: In card document (per-board)
  • ✅ Checklist Positions: In checklist document (per-board)
  • ✅ Collapse States: In user.profile (per-user)

Success Criteria

After all phases complete:

  1. ✅ All swimlane heights stored in swimlane documents
  2. ✅ All list widths stored in list documents
  3. ✅ All positions stored in swimlane/list/card/checklist/checklistItem documents
  4. ✅ Only collapse states and label visibility in user profiles
  5. ✅ No duplicate storage of widths/heights
  6. ✅ All users see same dimensions for swimlanes/lists
  7. ✅ Each user has independent collapse preferences
  8. ✅ Data validates against range constraints

Status: ✅ Phase 1 Complete, Awaiting Phase 2