148 lines
6.8 KiB
PHP
148 lines
6.8 KiB
PHP
<?php
|
|
include 'koneksi.php';
|
|
$token = $_GET['token'] ?? '';
|
|
$valid = false;
|
|
$account = null;
|
|
|
|
if (!empty($token)) {
|
|
$stmt = $pdo->prepare("SELECT * FROM account WHERE code_register = ?");
|
|
$stmt->execute([$token]);
|
|
$account = $stmt->fetch(PDO::FETCH_ASSOC);
|
|
if ($account) {
|
|
$valid = true;
|
|
}
|
|
}
|
|
?>
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
<title>Registrasi Sidik Jari</title>
|
|
<link rel="stylesheet" href="vendors/feather/feather.css">
|
|
<link rel="stylesheet" href="vendors/ti-icons/css/themify-icons.css">
|
|
<link rel="stylesheet" href="vendors/css/vendor.bundle.base.css">
|
|
<link rel="stylesheet" href="vendors/mdi/css/materialdesignicons.min.css">
|
|
<link rel="stylesheet" href="css/vertical-layout-light/style.css">
|
|
<link rel="shortcut icon" href="images/myn.png" />
|
|
<style>
|
|
body { background-color: #f8f9fa; display: flex; align-items: center; justify-content: center; min-height: 100vh; margin: 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif; }
|
|
.scan-card { background: #fff; padding: 25px 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.02); text-align: center; max-width: 320px; width: 100%; border: 1px solid #e2e8f0; }
|
|
.finger-icon { font-size: 50px; color: #4b49ac; margin-bottom: 15px; display: inline-block; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="scan-card">
|
|
<?php if ($valid && empty($account['finger_id'])): ?>
|
|
<i class="mdi mdi-fingerprint finger-icon"></i>
|
|
<h4 class="font-weight-bold mb-1" style="font-size: 18px;">Scan Fingerprint</h4>
|
|
<p class="text-muted mb-4" style="font-size:12px;">Akun: <strong><?= htmlspecialchars($account['name']); ?></strong><br>@<?= htmlspecialchars($account['username']); ?></p>
|
|
|
|
<button id="btnScan" class="btn btn-primary btn-block d-flex align-items-center justify-content-center" style="border-radius:8px; font-weight:600; padding:12px; font-size:16px;" onclick="startWebAuthnRegistration()">
|
|
<i class="mdi mdi-fingerprint mr-2" style="font-size: 20px;"></i> Scan
|
|
</button>
|
|
|
|
<div id="webAuthnStatus" class="mt-4 font-weight-bold" style="display:none; font-size:14px;"></div>
|
|
|
|
<!-- hidden fields for JS -->
|
|
<input type="hidden" id="fingerprintAccountId" value="<?= $account['id_account']; ?>">
|
|
|
|
<?php elseif ($valid && !empty($account['finger_id'])): ?>
|
|
<i class="mdi mdi-check-circle finger-icon" style="color:#28a745; text-shadow: 0 4px 10px rgba(40,167,69,0.2);"></i>
|
|
<h4 class="font-weight-bold mb-2">Sudah Terdaftar</h4>
|
|
<p class="text-muted mb-0" style="font-size:14px;">Akun: <strong><?= htmlspecialchars($account['name']); ?></strong><br>Sidik jari untuk akun ini sudah terdaftar. Maksimal 1 sidik jari per akun.</p>
|
|
<?php else: ?>
|
|
<script>window.location.href = '404.php';</script>
|
|
<?php exit; ?>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<script src="vendors/js/vendor.bundle.base.js"></script>
|
|
<script>
|
|
async function startWebAuthnRegistration() {
|
|
var btn = document.getElementById("btnScan");
|
|
var statusEl = document.getElementById("webAuthnStatus");
|
|
var accountId = document.getElementById("fingerprintAccountId").value;
|
|
|
|
btn.disabled = true;
|
|
statusEl.innerHTML = '<i class="mdi mdi-loading mdi-spin mr-1"></i>Menunggu Sensor...';
|
|
statusEl.className = "mt-4 text-info font-weight-bold";
|
|
statusEl.style.display = "block";
|
|
|
|
if (!window.PublicKeyCredential) {
|
|
statusEl.innerHTML = '<i class="mdi mdi-alert mr-1"></i>WebAuthn tidak didukung di browser ini.';
|
|
statusEl.className = "mt-4 text-danger font-weight-bold";
|
|
btn.disabled = false;
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const challenge = new Uint8Array(32); crypto.getRandomValues(challenge);
|
|
const userId = new Uint8Array(16); crypto.getRandomValues(userId);
|
|
|
|
<?php
|
|
$stmtEx = $pdo->query("SELECT finger_id FROM account WHERE finger_id IS NOT NULL AND finger_id != ''");
|
|
$allCredsArray = $stmtEx->fetchAll(PDO::FETCH_COLUMN);
|
|
?>
|
|
const allExistingCreds = <?= json_encode($allCredsArray) ?>;
|
|
const excludeList = allExistingCreds.map(idBase64 => {
|
|
return {
|
|
type: "public-key",
|
|
id: Uint8Array.from(atob(idBase64), c => c.charCodeAt(0))
|
|
};
|
|
});
|
|
|
|
const publicKey = {
|
|
challenge: challenge,
|
|
excludeCredentials: excludeList,
|
|
rp: { name: "Identia App", id: window.location.hostname },
|
|
user: { id: userId, name: "Identia_user", displayName: "Identia_user" },
|
|
pubKeyCredParams: [{ type: "public-key", alg: -7 }, { type: "public-key", alg: -257 }],
|
|
authenticatorSelection: {
|
|
authenticatorAttachment: "platform",
|
|
userVerification: "required",
|
|
residentKey: "required",
|
|
requireResidentKey: true
|
|
},
|
|
timeout: 60000,
|
|
attestation: "none"
|
|
};
|
|
|
|
const credential = await navigator.credentials.create({ publicKey });
|
|
const rawIdBase64 = btoa(String.fromCharCode.apply(null, new Uint8Array(credential.rawId)));
|
|
|
|
statusEl.innerHTML = '<i class="mdi mdi-loading mdi-spin mr-1"></i>Menyimpan Sidik Jari...';
|
|
|
|
let fd = new FormData();
|
|
fd.append('id_account', accountId);
|
|
fd.append('credential_id', rawIdBase64);
|
|
|
|
fetch("save_fingerprint_account.php", { method: "POST", body: fd })
|
|
.then(r => r.json())
|
|
.then(d => {
|
|
if(d.status === 'success') {
|
|
statusEl.innerHTML = '<i class="mdi mdi-check-circle mr-1 text-success" style="font-size:30px; display:block; margin-bottom:10px;"></i>Sidik Jari Berhasil Disimpan!<br><small class="text-muted mt-2 d-block">Anda dapat menutup halaman ini.</small>';
|
|
statusEl.className = "mt-4 text-success font-weight-bold";
|
|
btn.style.display = 'none';
|
|
} else {
|
|
statusEl.innerHTML = '<i class="mdi mdi-alert mr-1"></i>' + (d.message || 'Gagal menyimpan ke server');
|
|
statusEl.className = "mt-4 text-danger font-weight-bold";
|
|
btn.disabled = false;
|
|
}
|
|
}).catch(e => {
|
|
statusEl.innerHTML = '<i class="mdi mdi-alert mr-1"></i>Terjadi Kesalahan Jaringan.';
|
|
statusEl.className = "mt-4 text-danger font-weight-bold";
|
|
btn.disabled = false;
|
|
});
|
|
|
|
} catch (err) {
|
|
statusEl.innerHTML = '<i class="mdi mdi-alert mr-1"></i>Pendaftaran Dibatalkan / Gagal (' + err.message + ')';
|
|
statusEl.className = "mt-4 text-danger font-weight-bold";
|
|
btn.disabled = false;
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|