Skip to content

Architecture

Component overview

┌─────────────────────────────────────────────────────────┐
│  Browser                                                │
│  React SPA (Vite, Zustand, Tailwind)  :3000             │
└────────────────────┬────────────────────────────────────┘
                     │ HTTP + session cookie (same-origin)
┌────────────────────▼────────────────────────────────────┐
│  FastAPI backend                       :8000             │
│  ├── /auth/*   OAuth2 login/callback/me/logout          │
│  └── /deposit/* create / upload / status / publish      │
└────────────────────┬────────────────────────────────────┘
                     │ HTTPS (httpx async)
┌────────────────────▼────────────────────────────────────┐
│  Zenodo API                                             │
│  sandbox.zenodo.org/api  (or zenodo.org/api)            │
└─────────────────────────────────────────────────────────┘

OAuth 2.0 login flow

Browser          Backend           Zenodo
  │──── GET /auth/login ────►│
  │                          │── redirect ──────────────►│
  │◄────── 302 ──────────────│                           │
  │                          │                           │
  │  (user logs in at Zenodo and clicks Authorize)        │
  │                          │                           │
  │◄────────────────────── ?code=…&state=… ─────────────│
  │──── GET /auth/callback?code=…&state=… ──►│           │
  │                          │── POST /oauth/token ─────►│
  │                          │◄──── access_token ────────│
  │                          │  store token in session   │
  │◄──── 302 → /?auth=success│

The access token is stored only in the server-side session cookie (signed with SECRET_KEY). It is never sent to the browser or logged.


Deposit lifecycle

POST /deposit/create
  → Zenodo: POST /api/deposit/depositions
  ← returns { id, bucket_url, doi }

POST /deposit/{id}/upload   (one call per file)
  → reads file from multipart body into memory
  → Zenodo: PUT {bucket_url}/{filename}
  ← { filename, size, status }

GET /deposit/{id}/status
  → Zenodo: GET /api/deposit/depositions/{id}
  ← { state, files[], doi, … }

POST /deposit/{id}/publish
  → Zenodo: POST /api/deposit/depositions/{id}/actions/publish
  ← { doi, doi_url, html_url }

State management (frontend)

All wizard state lives in a single Zustand store (src/lib/store.js). There is no local-storage persistence — refreshing the page resets the wizard. This is intentional: it prevents stale deposit IDs from accumulating.


Security notes

Concern Mitigation
CSRF SameSite=lax session cookie; state param in OAuth
Token leakage Token stored server-side only in signed cookie
Data persistence No database; no file writes; /tmp only for OS temp
XSS React escapes all output; no dangerouslySetInnerHTML
CORS Only FRONTEND_URL is whitelisted