44 lines
3.0 KiB
PHP
44 lines
3.0 KiB
PHP
<?php
|
|
/**
|
|
* Minimaler Ersatz für Googles Token- und FCM-Schnittstelle (Router für "php -S"), nur für Tests.
|
|
* POST /token -> prüft das JWT (RS256, öffentlicher Schlüssel aus FCM_PUBKEY_FILE) und liefert ein Zugriffstoken
|
|
* POST /v1/projects/<projekt>/messages:send -> prüft das Zugriffstoken und protokolliert die Nachricht in FCM_LOG (eine JSON-Zeile je Aufruf)
|
|
* Enthält ein Geräte-Token das Wort "gone", antwortet der Server wie Google bei einem abgemeldeten Gerät (404 NOT_FOUND).
|
|
*/
|
|
$log = (string) getenv('FCM_LOG');
|
|
$path = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
|
|
$raw = (string) file_get_contents('php://input');
|
|
$write = function (array $row) use ($log) { file_put_contents($log, json_encode($row, JSON_UNESCAPED_UNICODE) . "\n", FILE_APPEND | LOCK_EX); };
|
|
$b64d = fn(string $s) => base64_decode(strtr($s, '-_', '+/'));
|
|
|
|
if ($path === '/token' && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
parse_str($raw, $form);
|
|
$parts = explode('.', (string) ($form['assertion'] ?? ''));
|
|
$ok = false;
|
|
$claims = [];
|
|
if (count($parts) === 3 && ($form['grant_type'] ?? '') === 'urn:ietf:params:oauth:grant-type:jwt-bearer') {
|
|
$pub = openssl_pkey_get_public((string) file_get_contents((string) getenv('FCM_PUBKEY_FILE')));
|
|
$ok = $pub && openssl_verify($parts[0] . '.' . $parts[1], $b64d($parts[2]), $pub, OPENSSL_ALGO_SHA256) === 1;
|
|
$claims = json_decode($b64d($parts[1]), true) ?: [];
|
|
$ok = $ok && ($claims['scope'] ?? '') === 'https://www.googleapis.com/auth/firebase.messaging' && ($claims['exp'] ?? 0) > time();
|
|
}
|
|
$write(['kind' => 'token', 'ok' => $ok, 'iss' => $claims['iss'] ?? null]);
|
|
header('Content-Type: application/json');
|
|
if (!$ok) { http_response_code(400); echo json_encode(['error' => 'invalid_grant']); exit; }
|
|
echo json_encode(['access_token' => 'tok-' . bin2hex(random_bytes(6)), 'expires_in' => 3600, 'token_type' => 'Bearer']);
|
|
exit;
|
|
}
|
|
|
|
if (preg_match('#^/v1/projects/([^/]+)/messages:send$#', (string) $path, $m) && $_SERVER['REQUEST_METHOD'] === 'POST') {
|
|
$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
|
|
$msg = json_decode($raw, true)['message'] ?? [];
|
|
header('Content-Type: application/json');
|
|
if (!str_starts_with($auth, 'Bearer tok-')) { $write(['kind' => 'send', 'ok' => false, 'reason' => 'auth']); http_response_code(401); echo json_encode(['error' => ['status' => 'UNAUTHENTICATED']]); exit; }
|
|
$token = (string) ($msg['token'] ?? '');
|
|
if (str_contains($token, 'gone')) { $write(['kind' => 'send', 'ok' => false, 'token' => $token, 'reason' => 'gone']); http_response_code(404); echo json_encode(['error' => ['status' => 'NOT_FOUND', 'message' => 'Requested entity was not found.']]); exit; }
|
|
$write(['kind' => 'send', 'ok' => true, 'project' => $m[1], 'token' => $token, 'data' => $msg['data'] ?? [], 'priority' => $msg['android']['priority'] ?? '', 'hasNotificationField' => isset($msg['notification'])]);
|
|
echo json_encode(['name' => 'projects/' . $m[1] . '/messages/' . bin2hex(random_bytes(4))]);
|
|
exit;
|
|
}
|
|
http_response_code(404);
|