Bisher legte die Teamleitung jedem Fahrer von Hand ein Konto an und gab das Passwort weiter. Bei zwanzig Leuten in einer WhatsApp-Gruppe ist ein Link die kuerzere Strecke. Die oeffentliche Seite der Einladung laeuft ueber eigene Routen in pb_hooks, nicht ueber die Collection: Ohne Login ist dort nichts sichtbar, auch nicht mit Token. Der Token kommt vom Server, ein mitgeschickter wird abgewiesen. Dazu der Login-Vorspann vor jeder Regel, die @request.auth.id erwaehnt. Das ist ein Loch und keine Kosmetik: Eine leere Relation ist in PocketBase gleich dem leeren @request.auth.id einer anonymen Anfrage — ein Team ohne Admins stuende sonst offen im Netz. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01P32KoesVtABd6xWsqMKzhr
96 lines
3.6 KiB
JavaScript
96 lines
3.6 KiB
JavaScript
/// <reference path="../pb_data/types.d.ts" />
|
|
|
|
// Jede Regel, die auf `@request.auth.id` zeigt, bekommt einen Login-Vorspann.
|
|
//
|
|
// ACHTUNG, das ist ein Loch und keine Kosmetik: Eine *leere* Relation ist in
|
|
// PocketBase gleich dem *leeren* `@request.auth.id` einer anonymen Anfrage.
|
|
// `team.admins.id ?= @request.auth.id` ist damit für jedes Team ohne Admins
|
|
// wahr — auch ohne jeden Login. Dasselbe gilt für jede optionale Relation,
|
|
// die leer sein kann: `created_by`, `stewards`, `rider.user`.
|
|
//
|
|
// Nachgewiesen an einer frischen Instanz mit einem Team ohne Admins:
|
|
// Ein POST auf /api/collections/riders/records **ganz ohne Token** legte den
|
|
// Fahrer an. Betroffen sind nach demselben Muster
|
|
//
|
|
// teams update (admins leer)
|
|
// riders create/update/delete
|
|
// trails update/delete, trail_versions create/delete
|
|
// trail_flags create/update/delete
|
|
// trail_markers update/delete (created_by, stewards)
|
|
// trail_comments update/delete (created_by, stewards)
|
|
// event_series update/delete (created_by)
|
|
// event_participants create/update/delete (event.created_by)
|
|
//
|
|
// Der Vorspann `@request.auth.id != "" && (…)` kann nichts erlauben, was
|
|
// vorher verboten war — er nimmt nur die anonyme Anfrage heraus, die keine
|
|
// dieser Regeln je erfüllen sollte. Deshalb wird er stumpf vor jede Regel
|
|
// gesetzt, die `@request.auth.id` erwähnt, statt die betroffenen Stellen
|
|
// einzeln zu suchen: Was hier nicht auffällt, fällt sonst gar nicht auf.
|
|
//
|
|
// Gelesen werden die Regeln aus der Datenbank, nicht aus dieser Datei. Eine
|
|
// im Admin-UI von Hand geänderte Regel wird also mit ihrem eigenen Wortlaut
|
|
// abgesichert und nicht überschrieben.
|
|
|
|
const GUARD = '@request.auth.id != ""'
|
|
|
|
const COLLECTIONS = [
|
|
'users', 'teams', 'events', 'runs', 'riders', 'times',
|
|
'trails', 'trail_versions', 'trail_flags', 'trail_markers', 'trail_comments',
|
|
'event_participants', 'event_series', 'team_invites',
|
|
]
|
|
|
|
const RULES = ['listRule', 'viewRule', 'createRule', 'updateRule', 'deleteRule']
|
|
|
|
migrate((app) => {
|
|
for (const name of COLLECTIONS) {
|
|
let collection
|
|
try {
|
|
collection = app.findCollectionByNameOrId(name)
|
|
} catch (err) {
|
|
continue // Collection gibt es auf dieser Instanz (noch) nicht.
|
|
}
|
|
|
|
let changed = false
|
|
|
|
for (const key of RULES) {
|
|
const rule = collection[key]
|
|
|
|
// Leere Regel heißt „nur Superuser" und braucht keinen Schutz;
|
|
// eine Regel ohne @request.auth.id ist nicht gemeint.
|
|
if (!rule || rule.indexOf('@request.auth.id') === -1) continue
|
|
if (rule.indexOf(GUARD) === 0) continue
|
|
|
|
collection[key] = GUARD + ' && (' + rule + ')'
|
|
changed = true
|
|
}
|
|
|
|
if (changed) app.save(collection)
|
|
}
|
|
}, (app) => {
|
|
const prefix = GUARD + ' && ('
|
|
|
|
for (const name of COLLECTIONS) {
|
|
// team_invites bringt den Vorspann aus 1754501600 mit; ihn hier
|
|
// abzuräumen hieße, ein Loch zu graben, das es vorher nicht gab.
|
|
if (name === 'team_invites') continue
|
|
|
|
let collection
|
|
try {
|
|
collection = app.findCollectionByNameOrId(name)
|
|
} catch (err) {
|
|
continue
|
|
}
|
|
|
|
let changed = false
|
|
|
|
for (const key of RULES) {
|
|
const rule = collection[key]
|
|
if (!rule || rule.indexOf(prefix) !== 0 || rule.charAt(rule.length - 1) !== ')') continue
|
|
|
|
collection[key] = rule.slice(prefix.length, -1)
|
|
changed = true
|
|
}
|
|
|
|
if (changed) app.save(collection)
|
|
}
|
|
})
|