165 lines
6.4 KiB
PHP
165 lines
6.4 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\License;
|
|
use App\Models\LicenseActivation;
|
|
use App\Services\Licensing\LicenseKey;
|
|
use App\Services\Licensing\LicenseSigner;
|
|
use App\Services\Licensing\LicenseTokenVerifier;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Illuminate\Validation\ValidationException;
|
|
|
|
final class LicenseActivationController extends Controller
|
|
{
|
|
public function activate(Request $request, LicenseSigner $signer): JsonResponse
|
|
{
|
|
$data = $request->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) []);
|
|
}
|
|
}
|