wekan/docs/Security/PerUserDataAudit2025-12-23/CURRENT_STATUS.md
Lauri Ojansivu a039bb1066
Some checks are pending
Docker / build (push) Waiting to run
Docker Image CI / build (push) Waiting to run
Release Charts / release (push) Waiting to run
Test suite / Meteor tests (push) Waiting to run
Test suite / Coverage report (push) Blocked by required conditions
Per-User and Board-level data save fixes. Part 3.
Thanks to xet7 !
2025-12-23 09:03:41 +02:00

9.1 KiB

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.

Entity Properties Where Stored
Swimlane title, color, height, sort, archived swimlanes.js document
List title, color, width, sort, archived, wipLimit, starred lists.js document
Card title, color, description, swimlaneId, listId, sort, archived cards.js document
Checklist title, sort, hideCheckedItems, hideAllItems checklists.js document
ChecklistItem title, sort, isFinished checklistItems.js document

🔒 Per-User Data (User Profile + Cookies)

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

Entity Properties Where Stored
User collapsedSwimlanes user.profile.collapsedSwimlanes[boardId][swimlaneId]
User collapsedLists user.profile.collapsedLists[boardId][listId]
User hideMiniCardLabelText user.profile.hideMiniCardLabelText[boardId]
Public User collapsedSwimlanes Cookie: wekan-collapsed-swimlanes
Public User collapsedLists Cookie: wekan-collapsed-lists

Changes Implemented

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

Swimlanes: Added height field

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

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)

// 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)

// 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

Document Purpose
DATA_PERSISTENCE_ARCHITECTURE.md Complete architecture specification
IMPLEMENTATION_GUIDE.md Step-by-step implementation instructions
models/swimlanes.js Swimlane model with new height field
models/lists.js List 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