Skip to content

feat(mega): session-token auth, 2FA, worker hardening + thumbnails - #239

Merged
Gnathonic merged 21 commits into
developfrom
feat/mega-session-auth
Jun 30, 2026
Merged

feat(mega): session-token auth, 2FA, worker hardening + thumbnails#239
Gnathonic merged 21 commits into
developfrom
feat/mega-session-auth

Conversation

@Gnathonic

Copy link
Copy Markdown
Owner

Summary

Security rework of the MEGA cloud-sync provider, plus thumbnail support and several bugs found during live verification.

Auth (the security fix):

  • Stop storing the plaintext password. Persist a reused, revocable session token instead (megajs toJSON()/fromJSON()), restored on load without a re-login.
  • Add two-step 2FA login (secondFactorCodeEMFAREQUIRED → reveal a code field).
  • One-time migration for existing users: silently log in with their stored email/password once, capture the session, and delete the password.
  • Needs-attention reconnect on ESID (expired/revoked session) — no password is ever stored to fall back on.

Worker hardening:

  • Upload worker authenticates with the session blob, not the password.
  • Download workers download owned nodes directly via { sid, nodeId, fileKey } — the share-link hack is removed, and the download worker never receives the account master key.

Thumbnails: uploaded image files now get a MEGA thumbnail attribute (uploadAttribute) so they preview in MEGA's browser — a contain-fit 120×120 JPEG rendered in the worker, dark letterbox, best-effort.

Bugs found in live verification:

  • megajs storage.close() issues {a:'sml'} which terminates the shared session sid → ESID(-15) on every reused session. Never close() a reused session; reinitialize() reloads in place; keepalive:false disables the buggy server-change poll that also crashed on deletes.
  • Deleting a backed-up volume now removes its sidecars (.mokuro, thumbnail .webp), not just the archive.
  • Fixed a reactivity-race "Delete failed (reading 'provider')" toast in VolumeItem.

Security notes

No plaintext MEGA password is persisted in localStorage or sent to any worker. The persisted token is a sanitized toJSON() blob (sid + master key, no password) — strictly less dangerous than the password (revocable; can't change account credentials or disable 2FA). The download worker never sees the master key.

Testing

749 unit tests passing; npm run check 0/0; eslint 0 errors; prettier clean. Live-verified against a real MEGA account: non-2FA login + reload reuses the session (no us request, no stored password), worker downloads issue no link/unshare, image uploads preview in MEGA, volume delete removes sidecars with no sc crash. Built subagent-driven with per-task review; design + plan under docs/superpowers/.

Notes

  • A couple of fixes (sidecar delete, delete toast) are general cloud-delete bugs, not MEGA-specific.
  • Branch was cut from an earlier develop; rebase/merge is clean (no overlapping files with develop's recent commits).

🤖 Generated with Claude Code

Gnathonic and others added 21 commits June 29, 2026 17:54
Replace plaintext password storage with a reused, revocable session
token (megajs toJSON/fromJSON); add two-step 2FA login; replace the
share-link download hack and upload-worker password with lightweight
session-based file access. Phased: auth+upload worker, then download
workers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ardening

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…anitizer, key encoder)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…kens

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…rsistence

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… meaningful

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…t re-login

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…epalive leak

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…estore

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…assword

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…share links

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…o share links)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…set test chunks

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… upload-cred shape

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…place

ROOT CAUSE of live ESID(-15) on reused sessions: megajs storage.close()
issues {a:'sml'}, terminating the session sid server-side. The branch reuses
ONE persisted sid across all storages (login, restore, reinitialize, upload
worker), so closing any of them invalidated the persisted token — the next
reload's fromJSON+reload and all downloads then failed with ESID(-15). The
pre-branch code minted a fresh sid per login, so close() was harmless.

- reinitialize(): refresh the file tree in place via reload(true) on the
  existing session; remove the fromJSON-rebuild + oldStorage.close().
- resetUploadStorage(): use api.close() (aborts the keepalive poll only)
  instead of storage.close() (which kills the session).

Regression tests assert reinitialize calls reload(true) and never close().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MEGA's browser only previews a node when it has an encrypted thumbnail
file-attribute (fa); a raw upload has none, so uploaded thumbnail images
showed no preview. In the upload worker, when the uploaded file is an image
(by mimeType/extension), render a 120x120 JPEG from the blob via
OffscreenCanvas and attach it with node.uploadAttribute('thumbnail', ...).
Best-effort and fully self-contained in the provider: failures (incl. no
OffscreenCanvas) never fail the upload; non-image uploads (cbz/json) skip it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
MEGA type-0 thumbnails must be square 120x120, but cover-cropping chopped
portrait manga covers. Switch to contain-fit (preserve aspect, center,
white letterbox) so the full cover shows. Quality 0.7->0.8.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…elete toast

Three issues found deleting a MEGA-backed volume:
- Sidecars (.mokuro, thumbnail .webp) were orphaned: deleteFile removes only
  one node. Add unifiedCloudManager.deleteManagedVolume() which deletes the
  archive + all managed sidecars (sidecars first, .cbz last so a sidecar
  failure leaves the volume retryable). VolumeItem's two delete paths use it.
- Toast 'Cannot read properties of undefined (reading provider)': onBackupClicked
  read the reactive cloudFile after the await re-derived it to undefined.
  Capture the provider before the await.
- megajs 'Cannot read properties of undefined (reading parent)' crash: the
  keepalive server-change (sc) poll has a buggy delete handler. We never use
  push updates (we reload explicitly), so create sessions with keepalive:false
  (login + both fromJSON restore sites).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 30, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
mokuro-reader Building Building Preview, Comment Jun 30, 2026 7:13am

Request Review

@Gnathonic
Gnathonic merged commit 6178f32 into develop Jun 30, 2026
7 of 9 checks passed
@Gnathonic
Gnathonic deleted the feat/mega-session-auth branch June 30, 2026 07:26
adrian-tompkins pushed a commit to adrian-tompkins/mokuro-reader that referenced this pull request Aug 2, 2026
feat: Add service worker update notification banner
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant