diff --git a/.gitignore b/.gitignore index 31265ac..6932fd8 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .env.* !.env.example + # --- PYTHON BACKEND --- __pycache__/ *.pyc diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e6e5da --- /dev/null +++ b/README.md @@ -0,0 +1,334 @@ +# ORCID SWORD System + +
+ +![FastAPI](https://img.shields.io/badge/FastAPI-109989?style=for-the-badge&logo=fastapi&logoColor=white) +![Python](https://img.shields.io/badge/Python-3776AB?style=for-the-badge&logo=python&logoColor=white) +![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB) +![Vite](https://img.shields.io/badge/Vite-646CFF?style=for-the-badge&logo=vite&logoColor=white) +![PostgreSQL](https://img.shields.io/badge/PostgreSQL-316192?style=for-the-badge&logo=postgresql&logoColor=white) +![Redis](https://img.shields.io/badge/Redis-D82C20?style=for-the-badge&logo=redis&logoColor=white) +![Docker](https://img.shields.io/badge/Docker-0db7ed?style=for-the-badge&logo=docker&logoColor=white) +![ORCID OAuth](https://img.shields.io/badge/OAuth2-ORCID-A6CE39?style=for-the-badge) + +
+ +
+ Full-stack platform for ORCID authentication, researcher synchronization, and publication export in SWORD XML / ZIP formats. +
+ +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Overview: What is this project meant + +`orcid-system` is designed for research workflows where ORCID data must be ingested, normalized, and exported. + +Core capabilities: + +- ORCID OAuth 3-legged login +- researcher search and synchronization against ORCID +- publication export by selection or by researcher +- export formats: **SWORD XML** and **ZIP** +- dual access model: user JWT or service API key (for export) + +> [!NOTE] +> The stack is local-first with Docker, but includes production-oriented hardening (CORS policy, trusted hosts, security headers, rate limiting, etc.). + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Tech Stack + +### Backend +- FastAPI +- SQLAlchemy +- PostgreSQL +- Redis +- python-jose (JWT) +- slowapi (rate limit) +- APScheduler +- httpx + +### Frontend +- React 19 +- Vite 8 +- React Router +- TailwindCSS 4 +- Sonner (notifications) + +### Infrastructure +- Docker / Docker Compose + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Quick Start + +From the project root: + +```bash +docker compose down +docker compose up --build +``` + +Default local URLs: + +- Frontend: `http://localhost:5173` +- Backend: `http://localhost:8000` + +> [!IMPORTANT] +> Current compose mapping uses loopback binding: +> - `127.0.0.1:5173:5173` +> - `127.0.0.1:8000:8000` +> This means services are reachable from the host machine, not exposed publicly by default. + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Environment Configuration + +Backend: +- Main file: `backend/.env` +- Reference: `backend/.env.example` + +Frontend: +- Compose/dev file: `frontend/.env` +- Optional local override for host dev: `frontend/.env.local` + +Important backend variables: + +- `ORCID_CLIENT_ID` +- `ORCID_CLIENT_SECRET` +- `ORCID_REDIRECT_URI` +- `JWT_SECRET` +- `API_KEY_NAME` +- `API_KEY_VALUE` +- `CORS_ALLOWED_ORIGINS` +- `TRUSTED_HOSTS` +- `DATABASE_URL` +- `REDIS_URL` + +Important frontend variables: + +- `VITE_API_URL` (empty in Docker setup, so Vite proxy handles `/api`) +- `VITE_API_PROXY_TARGET` (defaults to `http://backend:8000` in compose network) +- `VITE_API_KEY` (must match backend `API_KEY_VALUE` when using API key mode) +- `VITE_USE_MOCKS` +- `VITE_ORCID_PUBLIC_API_BASE` (optional override) + +> [!WARNING] +> Never commit real production secrets. Rotate `JWT_SECRET`, `API_KEY_VALUE`, and `ORCID_CLIENT_SECRET` before deployment. + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). ngrok Bridge for Local OAuth Callback + +To test OAuth callback from ORCID in local environments, compose can inject a public callback URL: + +```yaml +environment: + ORCID_REDIRECT_URI: https://jargon-supreme-palpable.ngrok-free.dev/callback +``` + +> [!NOTE] +> Values under `docker-compose.yml -> services.backend.environment` override `backend/.env` inside the container. + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). API Endpoints + +Base backend URL: `http://localhost:8000` + +| Module | Method | Endpoint | Auth | Parameters | Body | Notes | +| :--- | :---: | :--- | :--- | :--- | :--- | ---: | +| Health | `GET` | `/health` | None | None | None | Liveness check | +| ORCID Auth | `GET` | `/api/auth/orcid/authorize` | None | None | None | Redirects to ORCID | +| ORCID Auth | `GET` | `/api/auth/orcid/callback` | None | `code`, `state` | None | Exchanges OAuth code for backend JWT | +| ORCID Auth | `GET` | `/callback` | None | `code`, `state` | None | Alias for callback flow | +| Researchers | `POST` | `/api/researchers/search` | Optional Bearer | None | `{"orcid_ids":[...]}` | Batch search/sync | +| Researchers | `POST` | `/api/researchers/{orcid_id}/sync` | Optional Bearer | `orcid_id` | None | Full sync of one researcher | +| Export SWORD | `POST` | `/api/export/sword/publications` | Bearer or API key | None | `["publication_uuid", ...]` | Export selected publications | +| Export SWORD | `GET` | `/api/export/sword/researcher/{orcid_id}` | Bearer or API key | `orcid_id` | None | Export all by researcher | +| Export ZIP | `POST` | `/api/export/zip/publications` | Bearer or API key | None | `["publication_uuid", ...]` | Export selected publications | +| Export ZIP | `GET` | `/api/export/zip/researcher/{orcid_id}` | Bearer or API key | `orcid_id` | None | Export all by researcher | + +> [!IMPORTANT] +> `.../publications` endpoints require **publication IDs**, not `researcher.id`. + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Request Examples + +### Health +```bash +curl http://localhost:8000/health +``` + +### Search one or more researchers +```bash +curl -X POST "http://localhost:8000/api/researchers/search" \ + -H "Content-Type: application/json" \ + -d "{\"orcid_ids\":[\"0009-0000-0793-5376\"]}" +``` + +### Sync one researcher +```bash +curl -X POST "http://localhost:8000/api/researchers/0009-0000-0793-5376/sync" +``` + +### Export SWORD by researcher (API key mode) +```bash +curl "http://localhost:8000/api/export/sword/researcher/0009-0000-0793-5376" \ + -H "X-API-Key: YOUR_API_KEY" \ + -o sword.xml +``` + +### Export ZIP by publication IDs (Bearer mode) +```bash +curl -X POST "http://localhost:8000/api/export/zip/publications" \ + -H "Authorization: Bearer YOUR_JWT" \ + -H "Content-Type: application/json" \ + -d "[\"04f6a2a6-b753-4432-982b-b88160f627fe\"]" \ + -o export.zip +``` + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Security Controls + +Implemented controls in backend: + +- strict CORS allowlist +- trusted host filtering +- request body size limit +- OAuth `state` validation +- JWT validation with issuer/audience claims +- API key validation via constant-time comparison +- rate limiting +- security headers middleware +- non-root container and reduced privileges + +> [!WARNING] +> For production, enforce HTTPS behind a reverse proxy and set concrete values for `CORS_ALLOWED_ORIGINS` and `TRUSTED_HOSTS`. + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Frontend Details + +The frontend is a React SPA using route-based navigation and a centralized API client. + +### Frontend routing + +- `/` → landing page +- `/dashboard/:orcid` → researcher dashboard +- `/group` → multi-researcher results +- `/callback` → OAuth callback handler + +### Frontend API behavior (`frontend/src/services/api.js`) + +- central HTTP wrapper with `ApiError` +- includes `X-API-Key` on requests when configured +- includes `Authorization: Bearer ` when token exists in `localStorage` +- supports mock mode through `VITE_USE_MOCKS` +- supports Vite proxy mode when `VITE_API_URL` is empty + +### Vite proxy (`frontend/vite.config.js`) + +- `/api` proxied to `VITE_API_PROXY_TARGET` (`http://backend:8000` in compose) +- `/health` proxied to same target +- dev host settings allow tunnel scenarios (ngrok callback testing) + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Project Structure + +```text +orcid-system/ +├── backend/ +│ ├── app/ +│ │ ├── api/ +│ │ │ ├── auth.py +│ │ │ ├── researchers.py +│ │ │ └── export.py +│ │ ├── core/ +│ │ │ ├── config.py +│ │ │ ├── rate_limit.py +│ │ │ ├── security_headers.py +│ │ │ ├── error_handlers.py +│ │ │ └── body_size.py +│ │ ├── db/ +│ │ │ ├── models.py +│ │ │ ├── session.py +│ │ │ └── repositories/ +│ │ ├── security/ +│ │ │ ├── jwt.py +│ │ │ ├── api_key.py +│ │ │ └── oauth_state.py +│ │ ├── services/ +│ │ │ ├── orcid_client.py +│ │ │ ├── sword_generator.py +│ │ │ ├── zip_generator.py +│ │ │ └── sync_service.py +│ │ ├── utils/ +│ │ └── main.py +│ ├── .env +│ ├── .env.example +│ ├── .env.production +│ ├── Dockerfile +│ └── requirements.txt +├── frontend/ +│ ├── src/ +│ │ ├── components/ +│ │ │ ├── dashboard/ +│ │ │ ├── layout/ +│ │ │ └── ui/ +│ │ ├── contexts/ +│ │ │ └── AuthContext.jsx +│ │ ├── pages/ +│ │ │ ├── LandingPage.jsx +│ │ │ ├── DashboardPage.jsx +│ │ │ ├── GroupResultsPage.jsx +│ │ │ └── AuthCallbackPage.jsx +│ │ ├── services/ +│ │ │ ├── api.js +│ │ │ └── mocks.js +│ │ ├── utils/ +│ │ ├── App.jsx +│ │ └── main.jsx +│ ├── .env +│ ├── package.json +│ ├── vite.config.js +│ └── eslint.config.js +├── docker-compose.yml +└── README.md +``` + +--- + +## ![certificate](https://www.readmecodegen.com/api/social-icon?name=certificate&size=20). Production Checklist + +- [ ] `ENVIRONMENT=production` +- [ ] `DEBUG=false` +- [ ] rotate all secrets +- [ ] define strict `CORS_ALLOWED_ORIGINS` +- [ ] define strict `TRUSTED_HOSTS` +- [ ] enforce HTTPS via reverse proxy +- [ ] keep DB/Redis private +- [ ] configure monitoring and backups + +--- + +## ![github](https://www.readmecodegen.com/api/social-icon?name=github&size=20&color=%238b5cf6). Authors and Team + +This project is the result of the collaboration with the **University of Jaén**. + +| Role | Developer | GitHub | +| :--- | :--- | :--- | +| **Frontend** | Alexis López Moral | [@AlexisLopez-Dev](https://github.com/AlexisLopez-Dev) | +| **Backend** | Mireya Cueto Garrido | [@MireyaCueto](https://github.com/MireyaCueto) | + +### Direction +* **Proyect Supervisor:** Luis Martínez López + +--- + +

+ Built with professional care and ❤️ for secure research data workflows at the University of Jaén. +

diff --git a/docker-compose.yml b/docker-compose.yml index a0ea598..96075b0 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,7 +5,7 @@ services: container_name: orcid-backend restart: unless-stopped ports: - - "127.0.0.1:8000:8000" + - "127.0.0.1:8072:8000" env_file: - ./backend/.env environment: @@ -36,7 +36,7 @@ services: container_name: orcid-frontend restart: unless-stopped ports: - - "127.0.0.1:5173:5173" + - "127.0.0.1:8073:5173" depends_on: - backend env_file: