root-me: add logins
This commit is contained in:
parent
7686779fbb
commit
59ae200324
|
|
@ -0,0 +1,113 @@
|
|||
# Cyber Dashboard
|
||||
|
||||
Real-time cyber monitoring dashboard: ANSSI/CERT-FR feeds, geopolitical news, Kaspersky live map, and Root-me ranking.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Node.js ≥ 18
|
||||
- npm
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
| Environment variable | Description | Required |
|
||||
|---|---|---|
|
||||
| `ROOTME_API_KEY` | Root-me API key (Profile → Preferences) | Yes (Root-me widget) |
|
||||
| `DASHBOARD_PORT` | Listening port (default: `3000`) | No |
|
||||
|
||||
## Config files
|
||||
|
||||
**`logins.txt`** — one numeric Root-me user ID per line:
|
||||
```
|
||||
546528
|
||||
123456
|
||||
```
|
||||
|
||||
The ID can be found in the Root-me profile URL: `root-me.org/Username?inc=score&id_auteur=XXXXXX`
|
||||
|
||||
## Start
|
||||
|
||||
```bash
|
||||
ROOTME_API_KEY=<key> node server.js
|
||||
```
|
||||
|
||||
Then open `http://localhost:3000`.
|
||||
|
||||
## Manual alerts
|
||||
|
||||
```bash
|
||||
# Trigger an alert
|
||||
./alert.sh "INTRUSION DETECTED"
|
||||
|
||||
# With custom HTML
|
||||
./alert.sh "TITLE" path/to/content.html
|
||||
|
||||
# Dismiss the alert
|
||||
./dismiss.sh
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
```
|
||||
dashboard/
|
||||
├── server.js # Express + WebSocket server
|
||||
├── public/
|
||||
│ ├── index.html # 2×2 grid with carousel
|
||||
│ ├── style.css # Dark cyber theme
|
||||
│ └── app.js # WS client + feed polling
|
||||
├── logins.txt # Root-me user IDs to track
|
||||
├── alert.sh # Triggers an alert via POST
|
||||
└── dismiss.sh # Dismisses the alert via DELETE
|
||||
```
|
||||
|
||||
### Endpoints
|
||||
|
||||
| Method | Route | Description |
|
||||
|---|---|---|
|
||||
| `GET` | `/api/feeds/anssi` | CERT-FR bulletins (RSS) |
|
||||
| `GET` | `/api/feeds/geo` | Geopolitical news (Google News RSS) |
|
||||
| `GET` | `/api/rootme` | Root-me ranking (server cache) |
|
||||
| `GET` | `/api/alert` | Current alert state |
|
||||
| `POST` | `/api/alert` | Trigger an alert `{ message, html, image }` |
|
||||
| `DELETE` | `/api/alert` | Dismiss the alert |
|
||||
| `GET` | `/proxy?url=` | HTTP proxy (strips X-Frame-Options/CSP) |
|
||||
|
||||
### WebSocket events
|
||||
|
||||
The server pushes the following events to all connected clients:
|
||||
|
||||
| `type` | Payload | Description |
|
||||
|---|---|---|
|
||||
| `alert` | `{ message, html, image }` | Alert triggered |
|
||||
| `dismiss` | — | Alert dismissed |
|
||||
| `rootme_update` | `{ ranking[] }` | Updated Root-me ranking |
|
||||
| `rootme_flag` | `{ login, gained, newScore }` | A player just flagged a challenge |
|
||||
|
||||
## Contributing
|
||||
|
||||
### Commit conventions
|
||||
|
||||
```
|
||||
<scope>: <short description>
|
||||
```
|
||||
|
||||
Examples:
|
||||
```
|
||||
root-me: basic ranking
|
||||
alerts: new alerts
|
||||
all: init
|
||||
```
|
||||
|
||||
The scope reflects the component changed (`root-me`, `alerts`, `feeds`, `ui`, `server`, `all` for cross-cutting changes).
|
||||
|
||||
### Adding a widget
|
||||
|
||||
1. Add a `<section class="widget" id="widget-xxx">` in `index.html`
|
||||
2. Assign its grid cell in `style.css` (`grid-column` / `grid-row`)
|
||||
3. Add it to `CAROUSEL_PAGES` in `app.js`
|
||||
4. Add the corresponding endpoint in `server.js` if needed
|
||||
|
|
@ -1,3 +1,10 @@
|
|||
724433
|
||||
546528
|
||||
|
||||
709915
|
||||
1078733
|
||||
1060488
|
||||
967844
|
||||
716070
|
||||
1071634
|
||||
785590
|
||||
963602
|
||||
|
|
|
|||
35
server.js
35
server.js
|
|
@ -146,31 +146,44 @@ const ROOTME_POLL_MS = 10 * 60 * 1000;
|
|||
let rootmeCache = null;
|
||||
let rootmePrevScores = {}; // login → last known score
|
||||
|
||||
const ROOTME_REQUEST_DELAY_MS = 500;
|
||||
const rootmePlayerCache = {}; // id → { login, score, rank }
|
||||
|
||||
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
|
||||
|
||||
async function fetchRootmeRanking(apiKey) {
|
||||
const raw = fs.readFileSync(path.resolve('logins.txt'), 'utf8');
|
||||
const ids = raw.split('\n').map(l => l.trim()).filter(Boolean);
|
||||
const headers = { 'Cookie': `api_key=${apiKey}`, 'User-Agent': 'CyberDashboard/1.0' };
|
||||
|
||||
const results = await Promise.all(ids.map(async id => {
|
||||
const results = [];
|
||||
for (const id of ids) {
|
||||
try {
|
||||
const resp = await fetch(
|
||||
`https://api.www.root-me.org/auteurs/${id}`,
|
||||
{ headers, timeout: 10000 }
|
||||
);
|
||||
if (resp.status === 429) throw new Error('rate-limited');
|
||||
const profile = await resp.json();
|
||||
const profileRaw = Array.isArray(profile) ? profile[0] : profile;
|
||||
const user = profileRaw?.['0'] ?? profileRaw;
|
||||
if (!user || user.error) return null;
|
||||
|
||||
return { login: user.nom || id, score: Number(user.score) || 0, rank: user.position || null };
|
||||
if (resp.status === 429) {
|
||||
console.warn(`[rootme] rate-limited on id "${id}", using cached value`);
|
||||
if (rootmePlayerCache[id]) results.push(rootmePlayerCache[id]);
|
||||
} else {
|
||||
const profile = await resp.json();
|
||||
const profileRaw = Array.isArray(profile) ? profile[0] : profile;
|
||||
const user = profileRaw?.['0'] ?? profileRaw;
|
||||
if (user && !user.error) {
|
||||
const entry = { login: user.nom || id, score: Number(user.score) || 0, rank: user.position || null };
|
||||
rootmePlayerCache[id] = entry;
|
||||
results.push(entry);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[rootme] fetch error for id "${id}":`, err.message);
|
||||
return null;
|
||||
if (rootmePlayerCache[id]) results.push(rootmePlayerCache[id]);
|
||||
}
|
||||
}));
|
||||
await sleep(ROOTME_REQUEST_DELAY_MS);
|
||||
}
|
||||
|
||||
return results.filter(Boolean).sort((a, b) => b.score - a.score);
|
||||
return results.sort((a, b) => b.score - a.score);
|
||||
}
|
||||
|
||||
async function pollRootme() {
|
||||
|
|
|
|||
Loading…
Reference in New Issue