Telefoon-app: koppelen en bevestigen
FAQ › Profielserver › Telefoon-app · English
Wat is het?
De EUZOS-app (Flutter, app/; Android en iOS) is de sleutel van de gebruiker. Hij meldt zich bij de profielserver aan met e-mailadres en wachtwoord, koppelt de telefoon door een P-256-sleutelpaar te maken in de beveiligde chip van de telefoon (Secure Enclave op iOS, Android Keystore/StrongBox op Android), en bevestigt daarna inlogpogingen met Face ID of een vingerafdruk: inloggen op een pc (QR-code op het inlogscherm) en inloggen op de beheerpagina. De privésleutel verlaat de chip nooit. De app wordt per omgeving gebouwd (flavor lokaal, sandbox, productie) en praat alleen met de vastgepinde server van zijn eigen omgeving.
Hoe doe ik…?
Hoe koppel ik mijn telefoon?
Meld je in de app aan met je e-mailadres en wachtwoord (alleen een actief account). De app maakt dan de chipsleutel, vraagt Face ID / vingerafdruk, ondertekent het koppelbericht en stuurt het met POST /api/app/v2/devices. De server antwoordt met een apparaat-id en een apparaattoken (ezd_…), dat de app bewaart in de Keychain / versleuteld met een sleutel uit de Android Keystore. Zonder (sterke) biometrie weigert de app te koppelen; de terugval is dan de webpagina.
Hoe log ik in op een pc met mijn telefoon?
Scan de QR-code op het inlogscherm van de pc met de scanner van de app. De app haalt het verzoek op (GET /api/app/v2/session/<S>) en toont het IP-adres van de pc, de koppelcode in grote cijfers ("Staat deze code op de pc voor je?"), een waarschuwing als de pc op een ander netwerk zit, en de keuze Werk of school / Privé. Tik op Inloggen op deze pc; de chip vraagt Face ID / vingerafdruk en ondertekent het inlogbericht (POST /api/app/v2/session/<S>/confirm).
En als ik het verzoek via een link open in plaats van via de scanner?
Dan toont de app de koppelcode niet: je moet de code typen die op de pc voor je staat, en alleen als die klopt kun je inloggen. "Heeft iemand je de link of de code gestuurd: weigeren."
Hoe weiger ik een inlogverzoek?
Tik op Weigeren, of ga terug zonder te kiezen: de app stuurt POST /api/app/v2/session/<S>/reject. De pc toont een nieuwe QR-code.
Hoe zie ik mijn pc's en meld ik ze af?
In de app toont de lijst met pc-sessies (GET /api/app/v2/pc-sessions) elke ingelogde pc met IP, profiel, apparaat, sinds/verloopt en de methode (telefoon, web, wachtwoord, …). Een pc die met een wachtwoord is ingelogd, krijgt een waarschuwing. Meld er een af met POST /api/app/v2/pc-sessions/<ref>/logout; de pc merkt het bij zijn volgende statusvraag.
Hoe verwijder ik een telefoon?
Trek hem in via de apparatenlijst van de app (GET /api/app/v2/devices, DELETE /api/app/v2/devices/<id>): met een aanmelding met wachtwoord elk eigen apparaat, met een apparaattoken alleen dat apparaat zelf. De beheerder gebruikt device-revoke <apparaat-id> of de pagina Telefoons. Intrekken meldt ook de pc's af die met dat apparaat zijn ingelogd.
Waarom vraagt de app me opnieuw te koppelen?
De chipsleutel wordt onbruikbaar als er een nieuwe vingerafdruk of een nieuw gezicht wordt toegevoegd (zo ontworpen: kSecAccessControlBiometryCurrentSet / setInvalidatedByBiometricEnrollment(true)). De brug meldt dan ongeldig; koppel opnieuw. De app wist zijn koppeling alleen na een 401 op het apparaattoken die een tweede keer bevestigd wordt, nooit bij een 5xx of een netwerkfout.
Hoe bevestig ik het inloggen op de beheerpagina?
Scan de QR-code op https://admin.euzos.eu/beheer (link /b/<id>). De app toont "Inloggen op beheer vanaf <IP>" met de koppelcode; Bevestigen ondertekent het bericht EUZOS-beheer-v1. Zie Beheerpagina.
Details
Koppelen (docs/PROFILE-PROTOCOL.md, "Pairing a phone"):
POST /api/app/v2/sign-in→ aanmeldtokeneza_…(15 min, alleen in het geheugen van de server) en een eenmalige koppeluitdaging (128 bits).- De app maakt de P-256-sleutel in de chip (geen terugval op de pincode van het toestel).
- Hij ondertekent het koppelbericht (hieronder) na biometrie.
POST /api/app/v2/devicesmet naam, platform (ios/android), publieke sleutel (base64, SEC1 of SPKI DER), handtekening en optioneel attestatie (opgeslagen, nog niet gecontroleerd).- Antwoord 201: apparaat-id (16 hex) en apparaattoken
ezd_…(256 bits).
Hoogstens 10 actieve telefoons per account.
Een pc-login bevestigen: de server bouwt het inlogbericht zelf opnieuw op uit zijn eigen gegevens en controleert: apparaat niet ingetrokken, sessie wacht nog, profiel bestaat, |tijd − nu| ≤ 120 s, handtekening klopt. De uitdaging hoort bij dit apparaat en wordt na elke poging vernieuwd (een handtekening is één keer bruikbaar). Zonder eerdere GET session/<S> geeft confirm 409.
Pinning: de app pint de SHA-256 van de SubjectPublicKeyInfo van de serversleutel (met een reservepin); Caddy hergebruikt zijn privésleutel bij het vernieuwen van het certificaat (app/lib/pinning.dart, docs/PROFILESERVER-SETUP.md "Pinning").
Zelfde netwerk: same_network is true/false als pc en telefoon hetzelfde soort adres hebben (IPv4 per adres, IPv6 per /64) en anders null; de app maakt eerst verbinding over IPv4, zodat de vergelijking meestal lukt.
Routes die de app gebruikt: de huidige app-code roept de oude routes /api/app/v1/… aan (app/lib/api.dart); de server biedt dezelfde handlers aan op /api/app/v2/… met Engelse JSON-veldnamen. Beide staan hieronder.
Naslag
Functies en modules
| Functie / module | Rol |
|---|---|
euzos_profileserver::app::sign_in, logout | aanmelden met wachtwoord in de app (eza_-token) |
euzos_profileserver::app::pair_challenge, pair, pair_message | koppelen en het koppelbericht |
euzos_profileserver::app::devices, revoke | telefoons tonen en intrekken |
euzos_profileserver::app::session, confirm, reject, login_message, same_network | een pc-login bevestigen |
euzos_profileserver::app::pc_sessions, pc_logout | pc's tonen en op afstand afmelden |
euzos_profileserver::app::server | omgeving, domein, publieke sleutel, schakelaars |
euzos_profileserver::admin_page::app::request, confirm, reject | inloggen op de beheerpagina bevestigen |
app/lib/chip.dart (ChannelChipKey) | de Dart-kant van de chipbrug |
app/lib/messages.dart | bouwt de ondertekende berichten byte voor byte |
app/lib/pinning.dart | pinnen van de publieke sleutel |
app/ios/Runner/ChipSleutel.swift, app/android/app/src/main/kotlin/eu/euzos/app/ChipSleutel.kt | de native chipsleutel |
API-eindpunten
Allemaal JSON, Authorization: Bearer <token>; fouten {"error": …, "code": …}.
| Methode | Pad | Oud Nederlands pad | Token | Doel |
|---|---|---|---|---|
| GET | /api/app/v2/server | /api/app/v1/server | — | omgeving, server, sleutel-id, publieke sleutel, web_fallback, registration_open |
| POST | /api/app/v2/sign-in | /api/app/v1/aanmelden | — | aanmelden → eza_-token + koppeluitdaging |
| POST | /api/app/v2/logout | /api/app/v1/afmelden | eza_ | de aanmelding beëindigen |
| POST | /api/app/v2/devices/challenge | /api/app/v1/apparaten/uitdaging | eza_ | nieuwe koppeluitdaging |
| POST | /api/app/v2/devices | /api/app/v1/apparaten | eza_ | koppelen → apparaat-id + ezd_-token |
| GET | /api/app/v2/devices | /api/app/v1/apparaten | eza_/ezd_ | telefoons tonen |
| DELETE | /api/app/v2/devices/<id> | /api/app/v1/apparaten/<id> | eza_ (elk eigen), ezd_ (zichzelf) | een telefoon intrekken |
| GET | /api/app/v2/session/<S> | /api/app/v1/sessie/<S> | ezd_ | een pc-inlogverzoek ophalen (markeert het als "gezien") |
| POST | /api/app/v2/session/<S>/confirm | /api/app/v1/sessie/<S>/bevestig | ezd_ | bevestigen met handtekening |
| POST | /api/app/v2/session/<S>/reject | /api/app/v1/sessie/<S>/weiger | ezd_ | weigeren |
| GET | /api/app/v2/pc-sessions | /api/app/v1/pcsessies | eza_/ezd_ | ingelogde pc's |
| POST | /api/app/v2/pc-sessions/<ref>/logout | /api/app/v1/pcsessies/<ref>/afmelden | eza_/ezd_ | een pc op afstand afmelden |
| GET | /api/app/v2/admin/<id> | /api/app/v1/beheer/<id> | ezd_ | een inlogverzoek van de beheerpagina ophalen |
| POST | /api/app/v2/admin/<id>/confirm | /api/app/v1/beheer/<id>/bevestig | ezd_ | het bevestigen |
| POST | /api/app/v2/admin/<id>/reject | /api/app/v1/beheer/<id>/weiger | ezd_ | het weigeren |
| GET | /.well-known/apple-app-site-association | — | — | universele links op iOS (/l/*, /v/*, /b/*) |
| GET | /.well-known/assetlinks.json | — | — | app-links op Android |
De oude routes vertalen de JSON-veldnamen (bijvoorbeeld device_token ↔ apparaat_token, challenge ↔ uitdaging, signature ↔ handtekening, pc_sessions ↔ pcsessies); de waarden zijn gelijk. Volledige lijst in API.
Configuratiebestanden
| Sleutel (serverconfiguratie) | Betekenis |
|---|---|
app_duration (oud app_duur) | geldigheid van een eza_-aanmelding, standaard 900 s |
confirm_duration (oud bevestig_duur) | hoe lang een opgehaalde sessie geldig blijft, standaard 120 s |
apple_app_id | TEAMID.eu.euzos.app voor universele links; zonder: 404 |
android_package (oud android_pakket), android_fingerprints (oud android_vingerafdrukken) | app-links op Android |
Protocolberichten en formaten
Elke regel eindigt op \n (ook de laatste), UTF-8, geen spaties rond =. De sleutels blijven Nederlands (onderdeel van het ondertekende formaat).
Koppelbericht (app::pair_message):
EUZOS-koppelen-v1
omgeving=<lokaal|sandbox|productie>
server=<domein>
gebruiker=<e-mailadres, genormaliseerd>
uitdaging=<koppeluitdaging, 32 hex>
sleutel=<SHA-256 van de publieke sleutel in SEC1-vorm, 64 hex>
Inlogbericht voor een pc (app::login_message), ECDSA P-256 / SHA-256, DER of 64 bytes r||s:
EUZOS-inloggen-v1
omgeving=<omgeving>
server=<domein>
sessie=<S>
pc=<SHA-256(pc-geheim) in hex, of ->
pcip=<IP van de pc>
profiel=<zakelijk|prive>
tijd=<Unix-tijd van de telefoon>
uitdaging=<uitdaging U in hex>
apparaat=<apparaat-id>
Inlogbericht voor de beheerpagina: EUZOS-beheer-v1 (zie Beheerpagina).
Koppelcode op pc en telefoon: (eerste 4 bytes van SHA-256("EUZOS-koppelcode-v1\0" || S || SHA-256(G)) als big-endian getal) mod 1 000 000, zes cijfers (crate::pairing_code).
Native kanaal eu.euzos.app/chip (MethodChannel; argument alias = de sleutelnaam per omgeving). Methode- en argumentnamen zijn Nederlandse waarden op de lijn:
| Methode | Argumenten | Geeft terug |
|---|---|---|
heeftSleutel | — | bool: is er een sleutel |
maakSleutel | — | publieke sleutel (SEC1 of SPKI DER) van een nieuw sleutelpaar |
publiekeSleutel | — | de publieke sleutel |
onderteken | bericht (bytes), titel, reden | DER-handtekening na biometrie |
verwijderSleutel | — | de sleutel verwijderen |
biometrie | — | ok, geen_biometrie of geen_chip |
toestelnaam | — | een toestelnaam voor de lijst |
Foutcodes van de brug: geannuleerd, ongeldig (sleutel onbruikbaar: opnieuw koppelen), geen_sleutel, geen_biometrie, geen_chip, mislukt.
Zie ook
- docs/PROFILE-PROTOCOL.md, "Pairing a phone", "Logging in on a PC", "The API for the app (JSON)"
- docs/PROFILESERVER-SETUP.md, "Pinning"
- docs/ADMIN-PAGE.md, "The app"
- Pc-login · Beheerpagina · API
EUZOS