validate([ 'licenseKey' => ['required', 'string', 'max:128'], 'installationID' => ['required', 'uuid'], 'appVersion' => ['required', 'string', 'max:32'], 'platform' => ['required', 'string', 'max:32'], ]); return DB::transaction(function () use ($data, $signer) { $license = License::where('license_key_hash', LicenseKey::hash($data['licenseKey'])) ->lockForUpdate() ->first(); if (! $license) { throw ValidationException::withMessages(['licenseKey' => 'The licence key is not valid.']); } if (! $license->is_usable) { return response()->json(['message' => 'This licence is expired or has been revoked.'], 403); } $installationID = strtolower($data['installationID']); $activation = LicenseActivation::where('license_id', $license->id) ->where('installation_id', $installationID) ->first(); if ($activation && $activation->deactivated_at === null) { $activation->update([ 'last_seen_at' => now(), 'platform' => $data['platform'], 'app_version' => $data['appVersion'], ]); } else { $activeCount = LicenseActivation::where('license_id', $license->id) ->whereNull('deactivated_at') ->count(); if ($activeCount >= $license->activation_limit) { return response()->json(['message' => 'This licence has reached its activation limit. Deactivate another Mac first.'], 409); } if ($activation) { $activation->update([ 'deactivated_at' => null, 'activated_at' => now(), 'last_seen_at' => now(), 'platform' => $data['platform'], 'app_version' => $data['appVersion'], ]); } else { LicenseActivation::create([ 'license_id' => $license->id, 'installation_id' => $installationID, 'platform' => $data['platform'], 'app_version' => $data['appVersion'], 'activated_at' => now(), 'last_seen_at' => now(), ]); } } return response()->json([ 'licenseToken' => $signer->sign($license, $data['installationID']), ]); }); } public function validateInstallation( Request $request, LicenseTokenVerifier $verifier, LicenseSigner $signer, ): JsonResponse { $data = $request->validate([ 'licenseToken' => ['required', 'string', 'max:8192'], 'installationID' => ['required', 'uuid'], 'appVersion' => ['required', 'string', 'max:32'], 'platform' => ['required', 'string', 'max:32'], ]); try { $payload = $verifier->verify($data['licenseToken']); } catch (\Throwable) { return response()->json(['message' => 'The licence token is invalid.'], 400); } if (strcasecmp((string) ($payload['installationID'] ?? ''), $data['installationID']) !== 0) { return response()->json(['message' => 'The installation does not match this licence token.'], 400); } return DB::transaction(function () use ($data, $payload, $signer) { $license = License::whereKey((string) ($payload['licenseID'] ?? '')) ->lockForUpdate() ->first(); if (! $license || ! $license->is_usable) { return response()->json(['message' => 'This licence is expired or has been revoked.'], 403); } $activation = LicenseActivation::where('license_id', $license->id) ->where('installation_id', strtolower($data['installationID'])) ->first(); if (! $activation || $activation->deactivated_at !== null) { return response()->json(['message' => 'This Mac has been deactivated from the licence portal.'], 403); } $activation->update([ 'last_seen_at' => now(), 'platform' => $data['platform'], 'app_version' => $data['appVersion'], ]); return response()->json([ 'active' => true, // Refreshing the signed token also applies subscription edition/expiry changes // without requiring the user to reactivate manually. 'licenseToken' => $signer->sign($license, $data['installationID']), ]); }); } public function deactivate(Request $request, LicenseTokenVerifier $verifier): JsonResponse { $data = $request->validate([ 'licenseToken' => ['required', 'string', 'max:8192'], 'installationID' => ['required', 'uuid'], ]); try { $payload = $verifier->verify($data['licenseToken']); } catch (\Throwable) { return response()->json(['message' => 'The licence token is invalid.'], 400); } if (strcasecmp((string) ($payload['installationID'] ?? ''), $data['installationID']) !== 0) { return response()->json(['message' => 'The installation does not match this licence token.'], 400); } LicenseActivation::where('license_id', (string) ($payload['licenseID'] ?? '')) ->where('installation_id', strtolower($data['installationID'])) ->whereNull('deactivated_at') ->update(['deactivated_at' => now(), 'last_seen_at' => now()]); return response()->json((object) []); } }