API: Zugriff strikt auf den Besitzer des API-Keys, Key generieren/anzeigen
Der X-API-Key identifiziert den Benutzer; alle Endpunkte waren bereits auf dessen user_id gescoped (27 Operationen geprueft). Zwei Luecken blieben: - Die Aufloesung Token -> Benutzer nahm bei mehrdeutigem Token per LIMIT 1 einfach den ersten Treffer. Haetten zwei Benutzer denselben Token, saehe der eine die Daten des anderen. Jetzt: fail closed (401 + Logeintrag), und /einstellungen weist einen bereits vergebenen Token mit 409 ab. - Ein leerer Token galt als Wert: wer seine Einstellungen einmal gespeichert hatte, besass eine API_TOKEN-Zeile mit ''. Leere Werte matchen jetzt nie. Neu in den Einstellungen (Abschnitt REST-API): - "Neu generieren" erzeugt einen zufaelligen Token (32 Byte, crypto.get- RandomValues); er wird nur ins Feld gefuellt und erst beim Speichern aktiv, ein Fehlklick laesst sich also verwerfen. - "Kopieren" legt den Token in die Zwischenablage; das Auge blendet ihn ein (bestand bereits fuer Secret-Felder). API aktualisiert: - PUT /settings kannte nur name/adresse/kundennummer, GET lieferte aber alle acht Felder. Jetzt schreibt PUT alle (email, telefon, ort, webseite, geburtsdatum) und aendert nur die im Body uebergebenen Felder; die Antwort enthaelt den neuen Stand. Swagger: - Beschreibung sagte "konfiguriert via Umgebungsvariable API_TOKEN" - das gilt seit der Multi-User-Umstellung nicht mehr. Jetzt dokumentiert: Token pro Benutzer aus den Einstellungen, Zugriff nur auf eigene Daten, fremde id -> 404, unbekannter/leerer/mehrdeutiger Token -> 401. - Settings-Schema um die fehlenden fuenf Felder ergaenzt. Verifiziert mit zwei Benutzern und je eigenem Key: Lesen, Aendern, Loeschen, Timeline, Anhaenge, E-Mails, Generierung, Jobangebote und Blacklist des jeweils anderen liefern durchgaengig 404; Listen, Export und Statistik zeigen nur eigene Daten; kollidierender Token -> 409; mehrdeutiger Token in der DB -> 401 fuer beide. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+61
-11
@@ -43,8 +43,19 @@ function buildOpenApiSpec(baseUrl = '') {
|
||||
'Bewerbungen, Statusverlauf, generierten Unterlagen (PDFs), ' +
|
||||
'E-Mail-Korrespondenz, Einstellungen und Statistiken.\n\n' +
|
||||
'Alle Endpunkte sind unter `/api/v1` gemountet und erfordern ' +
|
||||
'Authentifizierung über einen API-Key im Header `X-API-Key` ' +
|
||||
'(konfiguriert via Umgebungsvariable `API_TOKEN`).',
|
||||
'Authentifizierung über einen API-Key im Header `X-API-Key`.\n\n' +
|
||||
'### Der API-Key gehört immer genau einem Benutzer\n\n' +
|
||||
'Der Tracker ist eine Multi-User-Plattform. Jeder Benutzer legt seinen ' +
|
||||
'eigenen Token unter **Einstellungen → REST-API** an („Neu generieren“) — ' +
|
||||
'es gibt keinen globalen oder serverweiten Key.\n\n' +
|
||||
'Der übergebene `X-API-Key` bestimmt, **wessen** Daten die Anfrage sieht: ' +
|
||||
'jeder Endpunkt liest und schreibt ausschließlich die Daten des Benutzers, ' +
|
||||
'dem der Token gehört. Fremde Bewerbungen, E-Mails, Anhänge, Jobangebote ' +
|
||||
'oder Einstellungen sind über die API nicht erreichbar — auch nicht durch ' +
|
||||
'Angabe einer fremden `id`. In dem Fall antwortet die API mit `404`, als ' +
|
||||
'gäbe es den Datensatz nicht. Ein unbekannter, leerer oder mehrdeutiger ' +
|
||||
'Token führt zu `401`.\n\n' +
|
||||
'Ein neu generierter Token macht den vorherigen sofort ungültig.',
|
||||
},
|
||||
servers: baseUrl ? [{ url: `${baseUrl}/api/v1` }] : [{ url: '/api/v1' }],
|
||||
tags: [
|
||||
@@ -409,22 +420,48 @@ function buildOpenApiSpec(baseUrl = '') {
|
||||
'/settings': {
|
||||
get: {
|
||||
tags: ['Settings'],
|
||||
summary: 'Einstellungen abrufen',
|
||||
summary: 'Persönliche Angaben abrufen',
|
||||
description:
|
||||
'Liefert die persönlichen Angaben **des Benutzers, dem der API-Key gehört**. ' +
|
||||
'Hat er noch nichts gespeichert, ist die Antwort ein leeres Objekt `{}`.',
|
||||
responses: {
|
||||
'200': { description: 'Einstellungen', content: { 'application/json': { schema: { $ref: '#/components/schemas/Settings' } } } },
|
||||
'200': { description: 'Persönliche Angaben (ggf. `{}`)', content: { 'application/json': { schema: { $ref: '#/components/schemas/Settings' } } } },
|
||||
'401': errorResponse,
|
||||
'500': errorResponse,
|
||||
},
|
||||
},
|
||||
put: {
|
||||
tags: ['Settings'],
|
||||
summary: 'Einstellungen speichern',
|
||||
summary: 'Persönliche Angaben speichern',
|
||||
description:
|
||||
'Speichert die persönlichen Angaben des eigenen Benutzers. Es werden nur die ' +
|
||||
'Felder geändert, die im Body vorkommen — weggelassene Felder behalten ihren ' +
|
||||
'Wert. Existiert noch keine Zeile, wird sie angelegt.',
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: { 'application/json': { schema: { $ref: '#/components/schemas/Settings' } } },
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: { $ref: '#/components/schemas/Settings' },
|
||||
example: { name: 'Max Mustermann', ort: 'Gladbeck' },
|
||||
},
|
||||
},
|
||||
},
|
||||
responses: {
|
||||
'200': { description: 'Gespeichert', content: { 'application/json': { schema: { $ref: '#/components/schemas/Ok' } } } },
|
||||
'200': {
|
||||
description: 'Gespeichert; gibt den neuen Stand zurück',
|
||||
content: {
|
||||
'application/json': {
|
||||
schema: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
success: { type: 'boolean', example: true },
|
||||
settings: { $ref: '#/components/schemas/Settings' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
'400': errorResponse,
|
||||
'401': errorResponse,
|
||||
'500': errorResponse,
|
||||
},
|
||||
@@ -654,7 +691,11 @@ function buildOpenApiSpec(baseUrl = '') {
|
||||
type: 'apiKey',
|
||||
in: 'header',
|
||||
name: 'X-API-Key',
|
||||
description: 'API-Token aus der Umgebungsvariable API_TOKEN.',
|
||||
description:
|
||||
'Persönlicher API-Token des Benutzers, zu finden unter Einstellungen → ' +
|
||||
'REST-API (dort per „Neu generieren“ erzeugen und einblenden). Der Token ' +
|
||||
'identifiziert den Benutzer — die Anfrage sieht und ändert nur dessen ' +
|
||||
'eigene Daten.',
|
||||
},
|
||||
},
|
||||
schemas: {
|
||||
@@ -839,10 +880,19 @@ function buildOpenApiSpec(baseUrl = '') {
|
||||
},
|
||||
Settings: {
|
||||
type: 'object',
|
||||
description:
|
||||
'Persönliche Angaben des Benutzers, dem der API-Key gehört (fließen in ' +
|
||||
'die generierten Unterlagen ein). Hat der Benutzer noch nichts gespeichert, ' +
|
||||
'liefert GET ein leeres Objekt `{}` — das ist ein gültiger Zustand.',
|
||||
properties: {
|
||||
name: { type: 'string' },
|
||||
adresse: { type: 'string' },
|
||||
kundennummer: { type: 'string' },
|
||||
name: { type: 'string', example: 'Max Mustermann' },
|
||||
adresse: { type: 'string', example: 'Musterstraße 1, 12345 Musterstadt' },
|
||||
kundennummer: { type: 'string', description: 'Kundennummer beim Jobcenter' },
|
||||
email: { type: 'string', example: 'max@example.com' },
|
||||
telefon: { type: 'string', example: '02043 123456' },
|
||||
ort: { type: 'string', example: 'Gladbeck' },
|
||||
webseite: { type: 'string', example: 'example.com' },
|
||||
geburtsdatum: { type: 'string', example: '01.01.1990' },
|
||||
},
|
||||
},
|
||||
Statistics: {
|
||||
|
||||
Reference in New Issue
Block a user