feat(api): complete phase 9 mobile contracts

This commit is contained in:
poyrazavsever
2026-07-17 09:38:22 +03:00
parent 46a746d2d4
commit b04031a3e3
25 changed files with 4896 additions and 19 deletions
@@ -0,0 +1,163 @@
---
title: ADR-0018 — Mobil Device Pairing ve Token Lifecycle
status: accepted-design-not-implemented
date: 2026-07-17
---
# ADR-0018 — Mobil device pairing ve token lifecycle
## Bağlam
React Native istemcisi kullanıcı tarafından girilen self-hosted Neta URL'sine bağlanacak. Web session cookie'sini kopyalamak, uzun ömürlü API key vermek veya owner şifresini cihazda sürekli saklamak güvenli bir pairing modeli değildir.
Faz 9 yalnızca discovery ve API v1 temelini yayınlar. Pairing/token endpoint'leri bu ADR uygulanmadan açılmaz.
## Karar
İlk device pairing sürümü owner cihazları için tek kullanımlık, kısa ömürlü bir pairing challenge ve DB-backed opaque token modeli kullanacaktır.
Planlanan endpoint'ler:
```text
POST /api/v1/pairing-codes
POST /api/v1/device-sessions/exchange
POST /api/v1/device-sessions/refresh
GET /api/v1/device-sessions
DELETE /api/v1/device-sessions/:id
```
### Pairing oluşturma
- Yalnızca aktif freelancer session'ı pairing oluşturabilir.
- Browser owner'dan güncel şifre veya eşdeğer step-up doğrulaması istenir.
- QR modu 256-bit rastgele secret taşır.
- Manuel giriş modu 10 karakter Crockford Base32 kod kullanır; benzer karakterler kullanılmaz.
- DB'de yalnızca HMAC/SHA-256 digest saklanır; raw secret yalnızca bir kez gösterilir.
- Challenge en fazla 5 dakika geçerlidir ve tek kullanımlıdır.
- Challenge; creator, expiry, attempt count, requested scopes ve durum içerir.
- Aynı owner için en fazla üç aktif challenge bulunabilir.
- Beş başarısız deneme challenge'ı kilitler.
- Oluşturma ve exchange IP/instance seviyesinde rate limit ve auth audit event üretir.
### Exchange
Mobil istemci şu bilgileri gönderir:
- Pairing secret/code
- Cihazda üretilmiş opaque install ID
- Kullanıcının verdiği device name
- Platform (`ios`/`android`)
- App version
- OS version'ın hassas olmayan major bilgisi
Server challenge'ı `BEGIN IMMEDIATE` transaction içinde doğrular ve tüketir. Aynı transaction device session/token family kaydını oluşturur. Başarısız exchange challenge'ı tüketmez; attempt sayısını artırır.
Owner role/scopeları server tarafından atanır. İstemci owner/user ID veya scope seçemez.
### Token modeli
- Tokenlar JWT değil, 256-bit opaque random bearer değerleridir.
- DB'de yalnızca keyed digest saklanır.
- Access token varsayılan 15 dakika geçerlidir.
- Refresh token varsayılan 30 gün geçerlidir.
- Her refresh işleminde access ve refresh token birlikte rotate edilir.
- Eski refresh token yeniden kullanılırsa token family `compromised` olur ve family içindeki tüm tokenlar atomik revoke edilir.
- Aynı cihaz için eşzamanlı refresh yarışı kısa grace/replay kaydıyla açıkça yönetilir; iki aktif refresh token bırakılmaz.
- Bearer token yalnızca `Authorization: Bearer` header'ında kabul edilir; query, URL veya log'a yazılmaz.
- Raw tokenlar API response dışında hiçbir log/audit kaydına girmez.
- React Native tokenları iOS Keychain/Android Keystore destekli secure storage'da tutar.
- İlk sürüm bearer modelidir. Device-bound public key/DPoP ayrı ADR olmadan eklenmez.
### Scope
İlk pairing yalnızca freelancer cihazı içindir. Token scope'ları explicit allowlist'tir:
```text
profile:read
clients:read
projects:read
tasks:read
calendar:read
finance:read
journal:read
```
Mutation scope'ları ilgili `/api/v1` resource endpoint'leri ve authorization testleri yayınlandıkça ayrı ayrı eklenir. `*` veya implicit admin scope kullanılmaz.
Client portal pairing'i owner pairing'inden ayrı ürün/güvenlik kararıdır; ilk implementasyona dahil değildir.
## Device token lifecycle
Durumlar:
```text
pending_pairing -> active -> expired
-> revoked
-> compromised
```
- `pending_pairing`: Challenge üretildi, token yok.
- `active`: Exchange tamamlandı ve token family kullanılabilir.
- `expired`: Refresh lifetime sona erdi.
- `revoked`: Owner, kullanıcı disable, şifre güvenlik olayı veya “tüm cihazlardan çıkış” nedeniyle kapatıldı.
- `compromised`: Refresh reuse veya güvenlik sinyali tespit edildi.
Kurallar:
- Owner dashboard'u cihaz adı, platform, oluşturulma, son kullanım ve yaklaşık IP bilgisini görür.
- Owner tek cihazı veya tüm cihazları revoke edebilir.
- Client/freelancer hesabı disable edildiğinde tüm device session'lar transaction içinde revoke edilir.
- Owner şifresi değiştiğinde varsayılan politika tüm device session'ları revoke etmektir.
- 30 gün kullanılmayan device session expire edilir.
- Son kullanım zamanı en fazla beş dakikada bir coalesce edilerek yazılır.
- Token cleanup job'u uygulama başlangıcında ve kontrollü periyotta expired kayıtları temizler; aktif request path'i toplu cleanup yapmaz.
## Backup ve restore güvenliği
Eski DB backup'ı revoke edilmiş token kayıtlarını yeniden aktif hale getirebilir. Pairing implementasyonunun release blocker'ı:
1. `instance_settings` içinde bir device token epoch tutulur.
2. Bütün token digest doğrulamaları bu epoch'a bağlanır.
3. `db:restore` başarılı atomik swap sonrasında epoch'u yeni random değerle rotate eder.
4. Böylece restore tüm eski device tokenları otomatik geçersiz kılar.
5. Owner restore sonrasında cihazları yeniden pair eder.
Bu mekanizma uygulanmadan device token endpoint'leri yayınlanamaz.
## HTTPS ve transport
- Remote pairing, exchange, refresh ve authenticated API için HTTPS zorunludur.
- Reverse proxy `X-Forwarded-Proto`/canonical origin'i doğru iletmelidir.
- HTTP yalnızca loopback/emulator geliştirme ortamında kabul edilir ve production token üretmez.
- TLS sertifika hatası kullanıcı tarafından sessizce bypass edilemez.
- Discovery API origin değiştirirse mobil istemci kullanıcı onayı olmadan credential göndermez.
## Audit ve privacy
Audit event'leri:
- `pairing_created`
- `pairing_failed`
- `pairing_consumed`
- `device_session_refreshed`
- `device_session_revoked`
- `device_token_reuse_detected`
Audit metadata raw token, pairing secret, tam IP geçmişi veya gereksiz device fingerprint içermez. Device name kullanıcı tarafından değiştirilebilir ve output-encode edilir.
## Uygulama öncesi zorunlu testler
- Pairing raw secret'ın DB/log'da bulunmaması
- Expired, consumed, locked ve brute-force challenge negatifleri
- Concurrent double exchange'de yalnızca bir başarı
- Owner/client role ve cross-owner negatifleri
- Access expiry ve refresh rotation
- Refresh reuse ile family revoke
- Disabled user ve password change revoke
- Restore sonrası token epoch invalidation
- HTTPS/loopback policy
- Tokenların URL, error ve audit output'una sızmaması
## Sonuç
Faz 9'da capability `auth.device-pairing` değeri `planned` kalır. Bu ADR'ın schema, service, rate-limit, restore epoch ve negatif test maddeleri tamamlanmadan `pairing-codes` veya `device-sessions` route'u oluşturulmaz.
@@ -2,7 +2,7 @@
title: Neta Self-Hosted v3 Ana Dönüşüm Planı
description: Supabase çıkışı, SQLite tabanlı backend, Better Auth, Poyraz UI v3 ve instance özelleştirmesi için ana yol haritası.
status: active
current_phase: "9Mobil API hazırlığı"
current_phase: "10Kullanıcı yönlendirmeli sayfa tasarımları"
last_updated: 2026-07-17
---
@@ -527,7 +527,7 @@ Faz 5'in başlangıç noktası freelancer runtime'ındaki Supabase erişimleridi
- [x] Portal backend'i Faz 6 sözleşmesine göre tamamlandı.
- [x] AI/chat ve business backend'i Faz 7 sözleşmesine göre tamamlandı.
- [x] Import ve runtime Supabase temizliği Faz 8'de tamamlandı.
- [ ] Mobil API sınırı Faz 9'da tamamlandı.
- [x] Mobil API sınırı Faz 9'da tamamlandı.
## 15. Revizyon güvenliği ve kota işlemi
@@ -580,13 +580,13 @@ Mobil hazırlık checklist'i:
- [x] API response envelope standardı tanımlandı.
- [x] API hata kodları tanımlandı.
- [ ] API sürümleme stratejisi tanımlandı.
- [ ] Instance metadata sözleşmesi tanımlandı.
- [ ] Minimum desteklenen client sürümü alanı düşünüldü.
- [ ] Capability listesi sözleşmesi düşünüldü.
- [x] API sürümleme stratejisi tanımlandı.
- [x] Instance metadata sözleşmesi tanımlandı.
- [x] Minimum desteklenen client sürümü alanı düşünüldü.
- [x] Capability listesi sözleşmesi düşünüldü.
- [x] Service katmanı cookie/Next.js objelerine bağımlı değil.
- [ ] Mobil pairing ilk release kapsamı dışında tutuldu.
- [ ] Gelecekte HTTPS zorunluluğu belgelendi.
- [x] Mobil pairing ilk release kapsamı dışında tutuldu.
- [x] Gelecekte HTTPS zorunluluğu belgelendi.
## 17. Supabase veri import ve cutover planı
@@ -744,7 +744,7 @@ Mümkün olduğunda küçük ve doğrudan test araçları tercih edilir; test al
- [x] Branding ayarları belgelendi.
- [x] Backup/restore belgelendi.
- [x] Upgrade/migration akışı belgelendi.
- [ ] Mobil API sınırı belgelendi.
- [x] Mobil API sınırı belgelendi.
- [x] Eski ve çelişkili Supabase belgeleri archive veya kaldırıldı.
- [x] ADR-0006 Poyraz UI v3 kararıyla güncellendi.
- [x] ADR-0007 ile PWA runtime durumu uyumlu hale getirildi.
@@ -984,11 +984,24 @@ Faz 8 tamamlanma notu (2026-07-17):
Amaç: React Native geliştirmesine başlamadan önce instance keşif ve stabil API sınırını tamamlamak.
- [ ] `/api/v1` sözleşmesi yayınlandı.
- [ ] `/.well-known/neta` sözleşmesi yayınlandı.
- [ ] Instance metadata ve capability modeli tamamlandı.
- [ ] Pairing güvenlik tasarımı ayrı ADR olarak yazıldı.
- [ ] Device token lifecycle tasarlandı.
- [x] `/api/v1` sözleşmesi yayınlandı.
- [x] `/.well-known/neta` sözleşmesi yayınlandı.
- [x] Instance metadata ve capability modeli tamamlandı.
- [x] Pairing güvenlik tasarımı ayrı ADR olarak yazıldı.
- [x] Device token lifecycle tasarlandı.
Faz 9 tamamlanma notu (2026-07-17):
- `/.well-known/neta`; protocol/discovery sürümü, kalıcı instance kimliği, application adı, mutlak API/meta/health URL'leri ve HTTPS politikasını public discovery belgesi olarak yayınlar.
- `/api/v1/meta`; server/API sürümü, instance/organization kimliği, absolute branding asset URL'leri, SemVer minimum mobil sürümü, platformlar, auth yöntemi, capability durumları ve navigasyon linklerini standart success envelope içinde döndürür.
- `/api/v1/health` SQLite/migration readiness'i v1 envelope ve `SERVICE_UNAVAILABLE` hata koduyla sunar. `/api/v1/me` yalnızca geçerli Better Auth session'ıyla güvenli user/role/client bağı ve session expiry döndürür; token/password/secret içermez.
- `instance_settings` ve migration `0007`; ilk discovery isteğinde concurrency-safe oluşturulan UUID'yi backup/restore ile korunacak kalıcı instance kimliği yapar. Domain veya marka adı kimlik olarak kullanılmaz.
- API major sürümü URL'de `/api/v1` olarak kilitlendi. Additive alan/capability değişiklikleri v1 içinde; alan silme, tip/anlam/auth kırılması yeni major içinde yapılacaktır. Tüm v1 yanıtları `X-Neta-API-Version: 1` taşır.
- `NETA_MINIMUM_MOBILE_VERSION` opsiyonel SemVer alt sınırı olarak eklendi. Bilinmeyen capability/alanları yok sayma ve `planned` capability'yi kullanmama kuralı belgelendi.
- Pairing runtime'a sahte/eksik endpoint olarak eklenmedi; `auth.device-pairing` capability'si `planned` durumundadır. Ayrı ADR-0018; tek kullanımlık challenge, rate limit, hash-only secret, opaque access/refresh token rotation, reuse detection, scope, revoke/expire/compromised lifecycle, secure storage ve restore sonrası token epoch invalidation gereksinimlerini kilitler.
- Mobil URL bağlantı algoritması; origin normalizasyonu, HTTPS, redirect/downgrade koruması, discovery/meta instance ID eşlemesi ve kimlik değişiminde credential'ı sessizce kullanmama kurallarıyla `phase-9-mobile-api.md` belgesinde yayınlandı.
- `phase9:api-boundary` pairing route'larının tasarım uygulanmadan açılmadığını ve API/service sınırını doğrular. `phase9:smoke`; eşzamanlı discovery, metadata, minimum sürüm, capabilities, health, anonymous/owner/client `/me`, disabled client reddi ve absolute branding URL akışlarını gerçek Next.js + Better Auth üzerinde doğrular.
- Typecheck, hedefli ESLint, migration drift kontrolü, production build, Faz 8 source/build artifact sınırı ve `git diff --check` başarılıdır.
Çıkış kriteri: Mobil istemci backend'in iç uygulama detaylarına bağımlı olmadan entegrasyona başlayabilir.
@@ -1002,8 +1015,8 @@ Başlangıç koşulları:
- [x] Faz 6 portal backend geçişi tamamlandı.
- [x] Faz 7 kapsamındaki runtime modülleri tamamlandı veya açıkça ertelendi.
- [x] Faz 8 Supabase runtime temizliği ve release hardening tamamlandı.
- [ ] Faz 9 mobil API hazırlığı tamamlandı.
- [ ] Tasarım sırasında kullanılacak backend veri sözleşmeleri stabil.
- [x] Faz 9 mobil API hazırlığı tamamlandı.
- [x] Tasarım sırasında kullanılacak backend veri sözleşmeleri stabil.
Sayfa grupları:
@@ -1051,7 +1064,7 @@ Her sayfa için tasarım kabul checklist'i:
- [x] Hedef schema tamamlandı.
- [x] Service/repository sınırı tamamlandı.
- [x] Server-side authorization tamamlandı.
- [ ] API v1 sınırı hazırlandı.
- [x] API v1 sınırı hazırlandı.
### Supabase çıkışı
@@ -0,0 +1,196 @@
---
title: Faz 9 — Mobil API ve Instance Discovery Sözleşmesi
description: React Native istemcileri için discovery, API v1, metadata, capability, sürümleme ve güvenlik sınırı.
status: completed
last_updated: 2026-07-17
---
# Faz 9 — Mobil API ve instance discovery
Bu faz mobil uygulamanın Neta backend iç yapısını bilmeden bir self-hosted instance'ı tanımasını sağlar. Mobil uygulamanın kendisi ve device pairing implementasyonu kapsam dışıdır.
## 1. Bağlantı akışı
Kullanıcı mobil uygulamaya instance URL'sini girer:
```text
https://neta.example.com
```
İstemci:
1. URL'yi yalnızca origin olacak şekilde normalize eder; credential, query ve fragment kabul etmez.
2. Production'da HTTPS olmayan remote origin'i reddeder. HTTP yalnızca `localhost`, `127.0.0.1` ve emulator geliştirme adresleri için açık kullanıcı onayıyla kullanılabilir.
3. `GET /.well-known/neta` çağrısını yapar.
4. `protocol=neta` ve desteklenen `discoveryVersion` değerini doğrular.
5. Discovery belgesindeki API URL'lerinin beklenen origin'den çıkmadığını doğrular.
6. `GET /api/v1/meta` çağrısını yapar ve `instance.id` değerini discovery `instanceId` değeriyle karşılaştırır.
7. Daha önce kaydedilmiş aynı origin farklı bir `instanceId` döndürürse bunu yeni/restore edilmiş instance olarak kullanıcıya açıkça gösterir; mevcut credential'ı sessizce yeniden kullanmaz.
8. Minimum client sürümünü ve capability listesini değerlendirir.
9. Instance kaydını `origin + instanceId + applicationName + iconUrl` ile yerel güvenli metadata alanına kaydeder.
Redirect takibi en fazla üç hop olmalı ve HTTPS'ten HTTP'ye downgrade edilmemelidir. Discovery veya meta içindeki URL kullanıcı girdisinden bağımsız güven kaynağı sayılmaz.
## 2. Endpoint özeti
| Endpoint | Auth | Cache | Amaç |
| --- | --- | --- | --- |
| `GET /.well-known/neta` | Public | 60 saniye public | Instance ve API keşfi |
| `GET /api/v1/meta` | Public | 60 saniye public | Marka, sürüm ve capability bilgisi |
| `GET /api/v1/health` | Public | No-store | DB/migration readiness |
| `GET /api/v1/me` | Better Auth session | Private/no-store | Güvenli kullanıcı/session özeti |
Public endpoint'ler session oluşturmaz ve secret dönmez. `/me` geçersiz, süresi dolmuş veya disabled hesaba ait session için `401 UNAUTHENTICATED` döndürür.
## 3. Discovery v1
Örnek:
```json
{
"protocol": "neta",
"discoveryVersion": 1,
"instanceId": "6d26e558-9c93-4cd8-93a1-5c8d5166045a",
"applicationName": "Poyraz Studio",
"api": {
"version": "1",
"baseUrl": "https://neta.example.com/api/v1",
"metaUrl": "https://neta.example.com/api/v1/meta",
"healthUrl": "https://neta.example.com/api/v1/health"
},
"security": {
"httpsRequired": true,
"insecureLoopbackAllowed": true
}
}
```
`instanceId` ilk discovery isteğinde kriptografik UUID olarak oluşturulur, `instance_settings` tablosunda saklanır ve backup/restore ile korunur. Domain veya marka adı instance kimliği değildir.
## 4. API v1 envelope
Başarılı yanıt:
```json
{
"ok": true,
"data": {}
}
```
Hata:
```json
{
"ok": false,
"error": {
"code": "UNAUTHENTICATED",
"message": "Geçerli bir oturum gerekli.",
"details": {}
}
}
```
`details` opsiyoneldir. `/api/v1` yanıtları `X-Neta-API-Version: 1` header'ı taşır. Mobil istemci kullanıcıya göstereceği metni `message` alanından alabilir; program akışını yalnızca stabil `code` üzerinden kurmalıdır.
Mevcut hata kodları:
- `VALIDATION_ERROR`
- `UNAUTHENTICATED`
- `FORBIDDEN`
- `NOT_FOUND`
- `CONFLICT`
- `INVARIANT_VIOLATION`
- `UPSTREAM_ERROR`
- `UPSTREAM_TIMEOUT`
- `SERVICE_UNAVAILABLE`
- `INTERNAL_ERROR`
## 5. Metadata ve capability
`/api/v1/meta` şu grupları döndürür:
- Protokol/discovery/API major sürümü
- Neta server package sürümü
- Kalıcı instance kimliği ve oluşturulma zamanı
- Application/organization adı
- Mobil için absolute logo/icon URL'leri ve semantic marka bilgisi
- Minimum desteklenen mobil client sürümü
- `ios` ve `android` platform listesi
- Authentication durumu
- Capability listesi
- Discovery, API, health ve me linkleri
Capability kaydı:
```json
{
"id": "portal.client",
"version": 1,
"status": "available",
"access": "client"
}
```
İstemci bilinmeyen capability ID ve alanlarını yok saymalıdır. `status=planned`, endpoint'in kullanılabilir olduğu anlamına gelmez.
İlk capability seti:
- `instance.discovery`
- `instance.branding`
- `auth.better-auth-cookie`
- `files.local`
- `freelancer.core`
- `portal.client`
- `ai.assistant`
- `auth.device-pairing``planned`
## 6. Minimum mobil sürüm
Instance sahibi opsiyonel SemVer tabanını environment ile ilan edebilir:
```env
NETA_MINIMUM_MOBILE_VERSION=1.2.0
```
Boşsa metadata `minimumSupportedVersion: null` döndürür ve server sürüm zorlaması yapmaz. İlk mobil release yayınlandığında istemci kendi sürümünü SemVer olarak karşılaştırır:
- Client sürümü minimumdan düşükse authenticated mutation başlatmaz.
- Discovery/meta/health erişimini korur.
- Kullanıcıya upgrade gereksinimini gösterir.
- Pre-release SemVer yalnızca test kanallarında kullanılmalıdır.
## 7. API sürümleme politikası
- Major sürüm URL'dedir: `/api/v1`.
- Yeni opsiyonel alan, yeni endpoint ve yeni capability additive değişikliktir; v1 içinde yapılabilir.
- Alan silme, alan tipini değiştirme, mevcut enum anlamını değiştirme veya auth modelini kırma yeni major `/api/v2` gerektirir.
- ID'ler opaque string kabul edilir; UUID formatına client iş mantığı bağlanmaz.
- Timestamp'ler UTC ISO-8601 string'dir.
- Para değerleri ilgili resource API'leri yayınlandığında integer minor unit olacaktır.
- Liste endpoint'leri yayınlandığında cursor pagination kullanacaktır; offset sözleşmesi varsayılmaz.
- İstemci bilinmeyen JSON alanlarını ve enum/capability değerlerini forward-compatible biçimde yok sayar.
- Eski major kaldırılmadan önce metadata capability ve release notlarıyla deprecation duyurulur.
## 8. Authentication sınırı
Bugünkü `/me`, Better Auth cookie session'ını doğrular; web ve entegrasyon smoke testleri aynı güvenli session adapter'ını kullanır. Session token, password hash veya AI secret yanıt içine girmez.
React Native için kalıcı bearer/device session üretimi bu fazda uygulanmadı. Mobil istemci `auth.device-pairing.status=planned` gördüğünde pairing UI'ını etkinleştirmemelidir. Gelecek güvenlik ve lifecycle kararı [ADR-0018](adr-0018-device-pairing.md) belgesindedir.
## 9. Test ve kalite kapısı
`pnpm phase9:smoke` şunları gerçek Next.js ve Better Auth akışında doğrular:
- Eşzamanlı discovery isteklerinde tek ve kalıcı instance ID
- Discovery/meta ID ve absolute URL tutarlılığı
- Public cache ve session oluşturmama davranışı
- API version header ve envelope
- Readiness health
- Minimum client sürümü ve capability modeli
- Anonymous `/me` negatif testi
- Freelancer `/me`
- Client `/me` ve client kimlik bağı
- Disabled client session reddi
- Branding değişikliğinin absolute mobile metadata'ya yansıması
- Pairing route'larının implementasyon tamamlanmadan yayınlanmaması