$request->header('X-Gitea-Event'), 'github_event' => $request->header('X-GitHub-Event'), 'delivery' => $request->header('X-Gitea-Delivery'), 'authorization_present' => $request->headers->has('Authorization'), 'signature_present' => $request->headers->has('X-Gitea-Signature'), ]); $authResponse = $this->validateAuthorization($request); if ($authResponse instanceof JsonResponse) { return $authResponse; } $signatureResponse = $this->validateSignature($request); if ($signatureResponse instanceof JsonResponse) { return $signatureResponse; } $event = (string) ($request->header('X-Gitea-Event') ?: $request->header('X-GitHub-Event')); $payload = $request->json()->all(); if ($event !== 'push') { Log::info('Gitea webhook ignored.', [ 'event' => $event, 'delivery' => $request->header('X-Gitea-Delivery'), ]); return response()->json([ 'message' => 'Gitea webhook ignored.', 'event' => $event, ]); } $commits = data_get($payload, 'commits', []); Log::info('Gitea push webhook received.', [ 'repository' => data_get($payload, 'repository.full_name'), 'ref' => data_get($payload, 'ref'), 'branch' => str_replace('refs/heads/', '', (string) data_get($payload, 'ref')), 'before' => data_get($payload, 'before'), 'after' => data_get($payload, 'after'), 'commit_count' => is_countable($commits) ? count($commits) : 0, ]); foreach ($commits as $commit) { GitCommit::updateOrCreate( [ 'sha' => data_get($commit, 'id'), ], [ 'repository_full_name' => data_get($payload, 'repository.full_name'), 'repository_name' => data_get($payload, 'repository.name'), 'branch' => str_replace( 'refs/heads/', '', (string) data_get($payload, 'ref'), ), 'author_name' => data_get($commit, 'author.name'), 'author_email' => data_get($commit, 'author.email'), 'message' => data_get($commit, 'message'), 'commit_url' => data_get($commit, 'url'), 'committed_at' => data_get($commit, 'timestamp') ? Carbon::parse(data_get($commit, 'timestamp')) : now(), 'payload' => $commit, ], ); Log::info('Git commit stored.', [ 'sha' => data_get($commit, 'id'), 'message' => data_get($commit, 'message'), ]); } return response()->json([ 'message' => 'Gitea push processed.', 'repository' => data_get($payload, 'repository.full_name'), 'branch' => str_replace('refs/heads/', '', (string) data_get($payload, 'ref')), 'commit_count' => is_countable($commits) ? count($commits) : 0, ]); } /** * Validate the optional Gitea Authorization header. */ private function validateAuthorization(Request $request): ?JsonResponse { $expected = (string) config('services.gitea.webhook_authorization'); if ($expected === '') { return null; } $actual = (string) $request->header('Authorization'); if (hash_equals($expected, $actual)) { return null; } Log::warning('Rejected Gitea webhook because Authorization header was invalid.', [ 'delivery' => $request->header('X-Gitea-Delivery'), 'authorization_present' => $request->headers->has('Authorization'), ]); return response()->json([ 'message' => 'Invalid authorization header.', ], 403); } /** * Validate the optional Gitea HMAC SHA-256 signature. */ private function validateSignature(Request $request): ?JsonResponse { $secret = (string) config('services.gitea.webhook_secret'); if ($secret === '') { return null; } $payload = $request->getContent(); $signature = (string) $request->header('X-Gitea-Signature'); $expected = hash_hmac('sha256', $payload, $secret); if ($signature !== '' && hash_equals($expected, $signature)) { return null; } Log::warning('Rejected Gitea webhook because signature was invalid.', [ 'delivery' => $request->header('X-Gitea-Delivery'), 'signature_present' => $request->headers->has('X-Gitea-Signature'), 'expected' => $expected, 'actual' => $signature, ]); return response()->json([ 'message' => 'Invalid Gitea signature.', ], 403); } }