180 lines
6.5 KiB
PHP
Executable File
180 lines
6.5 KiB
PHP
Executable File
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use App\Models\Webauth;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
|
|
class FingerprintController extends Controller
|
|
{
|
|
protected string $rpName = 'a.montask.ir';
|
|
protected string $rpId = 'a.montask.ir';
|
|
|
|
/** برای ثبت اثر انگشت */
|
|
public function generateRegisterOptions(Request $request)
|
|
{
|
|
$user_identity = $_COOKIE['user_identity'] ?? null;
|
|
if (!$user_identity) {
|
|
return response()->json(['error' => 'کاربر لاگین نیست'], 403);
|
|
}
|
|
|
|
$customer = User::where('user_name', $user_identity)->first();
|
|
if (!$customer) {
|
|
return response()->json(['error' => 'کاربر یافت نشد'], 404);
|
|
}
|
|
|
|
$challenge = random_bytes(32);
|
|
$challengeBase64 = $this->b64e($challenge);
|
|
|
|
Cache::put("register_challenge_{$customer->id}", $challengeBase64, 120);
|
|
|
|
return response()->json([
|
|
"publicKey" => [
|
|
"rp" => [
|
|
"name" => $this->rpName,
|
|
"id" => $this->rpId,
|
|
],
|
|
"user" => [
|
|
"id" => $this->b64e($customer->id),
|
|
"name" => $customer->user_name,
|
|
"displayName" => $customer->name,
|
|
"number" => $customer->number,
|
|
],
|
|
"challenge" => $challengeBase64,
|
|
"pubKeyCredParams" => [
|
|
["type" => "public-key", "alg" => -7]
|
|
],
|
|
"authenticatorSelection" => [
|
|
"userVerification" => "required"
|
|
]
|
|
]
|
|
]);
|
|
}
|
|
|
|
/** تایید ثبت اثر انگشت */
|
|
public function verifyRegister(Request $request)
|
|
{
|
|
$user_identity = $_COOKIE['user_identity'] ?? null;
|
|
if (!$user_identity) {
|
|
return response()->json(['error' => 'کاربر لاگین نیست'], 403);
|
|
}
|
|
|
|
$customer = User::where('user_name', $user_identity)->first();
|
|
if (!$customer) {
|
|
return response()->json(['error' => 'کاربر یافت نشد'], 404);
|
|
}
|
|
|
|
$data = $request->all();
|
|
$storedChallenge = Cache::pull("register_challenge_{$customer->id}");
|
|
|
|
if (!$storedChallenge) return response()->json(["error" => "challenge not found"], 400);
|
|
if (!isset($data['response']['clientDataJSON'])) return response()->json(["error" => "clientDataJSON missing"], 400);
|
|
|
|
$clientDataJSON = base64_decode(strtr($data['response']['clientDataJSON'], '-_', '+/'));
|
|
$clientData = json_decode($clientDataJSON, true);
|
|
if (!$clientData) return response()->json(["error" => "Invalid clientData"], 400);
|
|
if (!hash_equals($clientData['challenge'], $storedChallenge)) return response()->json(["error" => "Invalid challenge"], 403);
|
|
if ($clientData['origin'] !== 'https://' . $this->rpId) return response()->json(["error" => "Invalid origin"], 403);
|
|
if ($clientData['type'] !== 'webauthn.create') return response()->json(["error" => "Invalid type"], 403);
|
|
|
|
Webauth::create([
|
|
"name" => $request->input("name", "دستگاه " . $customer->name),
|
|
"customer_id" => $customer->id,
|
|
"credential_id" => $data["id"],
|
|
"public_key" => $data["response"]["publicKey"],
|
|
"attestation" => json_encode($data["response"]["attestationObject"]),
|
|
"transports" => null,
|
|
"sign_count" => 0,
|
|
"last_used_at" => now(),
|
|
]);
|
|
|
|
return response()->json(["message" => "اثر انگشت با موفقیت ثبت شد"]);
|
|
}
|
|
|
|
/** تولید challenge برای ورود */
|
|
public function generateLoginOptions()
|
|
{
|
|
$challenge = $this->b64e(random_bytes(32));
|
|
Cache::put("login_challenge_global", $challenge, 120);
|
|
|
|
return response()->json([
|
|
"publicKey" => [
|
|
"challenge" => $challenge,
|
|
"rpId" => $this->rpId,
|
|
"userVerification" => "required",
|
|
"timeout" => 60000,
|
|
]
|
|
]);
|
|
}
|
|
|
|
|
|
/** تایید ورود */
|
|
public function verifyLogin(Request $request)
|
|
{
|
|
$data = $request->all();
|
|
|
|
$credentialId = $data['id'] ?? null;
|
|
if (!$credentialId) {
|
|
return response()->json(["message" => "credential_id missing"], 400);
|
|
}
|
|
|
|
$credential = Webauth::where("credential_id", $credentialId)->first();
|
|
if (!$credential) {
|
|
return response()->json(["message" => "اثر انگشت شناخته نشد"], 404);
|
|
}
|
|
|
|
$customer = User::find($credential->customer_id);
|
|
if (!$customer) {
|
|
return response()->json(["message" => "کاربر حذف شده"], 404);
|
|
}
|
|
|
|
$storedChallenge = Cache::pull("login_challenge_global");
|
|
if (!$storedChallenge) {
|
|
return response()->json(["message" => "Challenge منقضی شد"], 403);
|
|
}
|
|
|
|
// clientData
|
|
$clientDataJSON = base64_decode(strtr($data['response']['clientDataJSON'], '-_', '+/'));
|
|
$clientData = json_decode($clientDataJSON, true);
|
|
|
|
if ($clientData['challenge'] !== $storedChallenge) {
|
|
return response()->json(["message" => "Invalid challenge"], 403);
|
|
}
|
|
|
|
if ($clientData['origin'] !== 'https://' . $this->rpId) {
|
|
return response()->json(["message" => "Invalid origin"], 403);
|
|
}
|
|
|
|
// authenticatorData
|
|
$authData = base64_decode(strtr($data['response']['authenticatorData'], '-_', '+/'));
|
|
|
|
$signature = base64_decode(strtr($data['response']['signature'], '-_', '+/'));
|
|
$clientDataHash = hash('sha256', $clientDataJSON, true);
|
|
$signedData = $authData . $clientDataHash;
|
|
|
|
$ok = openssl_verify($signedData, $signature, $credential->public_key, OPENSSL_ALGO_SHA256);
|
|
if ($ok !== 1) {
|
|
return response()->json(["message" => "Invalid signature"], 403);
|
|
}
|
|
|
|
// لاگین
|
|
$token = auth('customer')->login($customer);
|
|
auth('customer')->setUser($customer);
|
|
|
|
return response()->json([
|
|
"message" => "ورود موفق",
|
|
"token" => $token,
|
|
"user_id" => $customer->id
|
|
]);
|
|
}
|
|
|
|
private function b64e($v)
|
|
{
|
|
return rtrim(strtr(base64_encode($v),'+/','-_'),'=');
|
|
}
|
|
}
|