Files
asset-management-system/app/Services/Security/TwoFactorService.php

104 lines
2.9 KiB
PHP

<?php
namespace App\Services\Security;
use App\Models\User;
use Illuminate\Support\Str;
class TwoFactorService
{
public function generateSecret(int $length = 20): string
{
return $this->base32Encode(random_bytes($length));
}
public function generateRecoveryCodes(int $count = 8): array
{
return collect(range(1, $count))
->map(fn () => Str::upper(Str::random(10)))
->toArray();
}
public function buildOtpAuthUrl(User $user, string $secret, string $issuer = 'AssetManagement'): string
{
$label = rawurlencode("{$issuer}:{$user->email}");
$issuerEnc = rawurlencode($issuer);
return "otpauth://totp/{$label}?secret={$secret}&issuer={$issuerEnc}";
}
public function verifyCode(string $secret, string $code, int $window = 1): bool
{
$code = trim($code);
if (!ctype_digit($code)) {
return false;
}
$timestamp = floor(time() / 30);
for ($i = -$window; $i <= $window; $i++) {
if ($this->totp($secret, $timestamp + $i) === $code) {
return true;
}
}
return false;
}
private function totp(string $secret, int $timestamp): string
{
$key = $this->base32Decode($secret);
$binaryTime = pack('N*', 0) . pack('N*', $timestamp);
$hash = hash_hmac('sha1', $binaryTime, $key, true);
$offset = ord(substr($hash, -1)) & 0x0F;
$truncatedHash = substr($hash, $offset, 4);
$value = unpack('N', $truncatedHash)[1] & 0x7FFFFFFF;
$modulo = 10 ** 6;
return str_pad((string) ($value % $modulo), 6, '0', STR_PAD_LEFT);
}
private function base32Encode(string $data): string
{
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$binary = '';
foreach (str_split($data) as $char) {
$binary .= str_pad(decbin(ord($char)), 8, '0', STR_PAD_LEFT);
}
$fiveBitChunks = str_split($binary, 5);
$encoded = '';
foreach ($fiveBitChunks as $chunk) {
if (strlen($chunk) < 5) {
$chunk = str_pad($chunk, 5, '0', STR_PAD_RIGHT);
}
$encoded .= $alphabet[bindec($chunk)];
}
return $encoded;
}
private function base32Decode(string $data): string
{
$alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
$data = strtoupper($data);
$binary = '';
foreach (str_split($data) as $char) {
$pos = strpos($alphabet, $char);
if ($pos === false) {
continue;
}
$binary .= str_pad(decbin($pos), 5, '0', STR_PAD_LEFT);
}
$eightBitChunks = str_split($binary, 8);
$decoded = '';
foreach ($eightBitChunks as $chunk) {
if (strlen($chunk) === 8) {
$decoded .= chr(bindec($chunk));
}
}
return $decoded;
}
}