feat(push): native device-token registration + FCM/APNs senders
- /api/v1/devices (register) + /api/v1/devices/remove — auth-required, validates platform (ios|android), upserts by token; e2e covers register/validation/auth/remove. - db device_tokens table + deviceTokens repo. - push.js: FCM HTTP v1 (Android) and APNs token-based over HTTP/2 (iOS) folded into the single push.sendToUser path alongside Web Push; each transport independently config-gated and a silent no-op without creds. Dead tokens pruned on 404/410. - docs: CLIENTS.md Phase B updated; DEPLOY.md env table adds FCM/APNs vars. e2e 117/117. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cette révision appartient à :
@@ -95,6 +95,16 @@ function nextMsg(ws, type, timeout = 3000) {
|
||||
const reuse = await call('/api/v1/auth/refresh', { refreshToken: login.data.refreshToken });
|
||||
check('rotated (old) refresh token is rejected', reuse.status === 401);
|
||||
|
||||
// 3b'. Native device push tokens (FCM/APNs registration; delivery no-ops until creds set)
|
||||
const devReg = await call('/api/v1/devices', { platform: 'android', token: 'fcm-tok-123' }, cookie);
|
||||
check('device token registered', devReg.status === 200 && devReg.data.ok === true);
|
||||
const devBad = await call('/api/v1/devices', { platform: 'windows', token: 'x' }, cookie);
|
||||
check('device register rejects invalid platform', devBad.status === 400);
|
||||
const devNoAuth = await call('/api/v1/devices', { platform: 'ios', token: 'y' });
|
||||
check('device register requires auth', devNoAuth.status === 401);
|
||||
const devRm = await call('/api/v1/devices/remove', { token: 'fcm-tok-123' }, cookie);
|
||||
check('device token removed', devRm.status === 200);
|
||||
|
||||
// 3c. API keys (machine-to-machine integration), scoped + revocable
|
||||
const mkKey = await call('/api/v1/keys', { name: 'ci', scopes: ['report:read'] }, cookie);
|
||||
check('admin creates API key (bzc_ prefix)', mkKey.status === 200 && /^bzc_/.test(mkKey.data.key || ''));
|
||||
|
||||
Référencer dans un nouveau ticket
Bloquer un utilisateur