prepare("SELECT id FROM users WHERE email = ? OR username = ? LIMIT 1"); $check->execute([$email, $username]); if ($check->fetch()) { Security::abort(409, 'Email atau username sudah terdaftar.'); } // Generate token verifikasi $verifyToken = bin2hex(random_bytes(32)); $verifyExpiry = date('Y-m-d H:i:s', time() + 86400); // 24 jam $db->prepare(" INSERT INTO users (username, email, password, full_name, verify_token, verify_expires_at) VALUES (?, ?, ?, ?, ?, ?) ")->execute([ $username, $email, password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]), $full_name, $verifyToken, $verifyExpiry, ]); // Di production: kirim email verifikasi // self::sendVerificationEmail($email, $verifyToken); Security::respond([ 'username' => $username, 'email' => $email, 'verify_token' => $verifyToken, // HAPUS INI di production, kirim via email! 'verify_expires_at' => $verifyExpiry, 'note' => 'Cek email Anda untuk verifikasi akun.', ], 201, 'Registrasi berhasil. Silakan verifikasi email Anda.'); } // ── GET /auth/verify?token=xxx ──────────────────────────── public static function verifyEmail(): void { $token = Security::sanitize($_GET['token'] ?? ''); if (!$token) Security::abort(422, 'Token verifikasi wajib disertakan.'); $db = Database::getConnection(); $stmt = $db->prepare(" SELECT id FROM users WHERE verify_token = ? AND is_verified = 0 AND verify_expires_at > NOW() LIMIT 1 "); $stmt->execute([$token]); $user = $stmt->fetch(); if (!$user) { Security::abort(400, 'Token tidak valid atau sudah expired.'); } $db->prepare(" UPDATE users SET is_verified = 1, verify_token = NULL, verify_expires_at = NULL WHERE id = ? ")->execute([$user['id']]); Security::respond(['verified' => true], 200, 'Email berhasil diverifikasi! Sekarang Anda bisa membuat API key.'); } // ── POST /auth/login ────────────────────────────────────── public static function login(): void { $body = self::getBody(); $email = filter_var($body['email'] ?? '', FILTER_VALIDATE_EMAIL); $pass = $body['password'] ?? ''; if (!$email || !$pass) Security::abort(422, 'Email dan password wajib diisi.'); $db = Database::getConnection(); $stmt = $db->prepare("SELECT * FROM users WHERE email = ? LIMIT 1"); $stmt->execute([$email]); $user = $stmt->fetch(); if (!$user || !password_verify($pass, $user['password'])) { Security::abort(401, 'Email atau password salah.'); } if (!$user['is_verified']) { Security::abort(403, 'Akun belum diverifikasi. Cek email Anda.'); } if ($user['status'] !== 'active') { Security::abort(403, 'Akun Anda tidak aktif.'); } $jwt = Security::generateJWT([ 'user_id' => $user['id'], 'username' => $user['username'], 'email' => $user['email'], ]); Security::respond([ 'token' => $jwt, 'token_type' => 'Bearer', 'expires_in' => JWT_EXPIRY, 'user' => [ 'username' => $user['username'], 'email' => $user['email'], 'full_name' => $user['full_name'], ], ], 200, 'Login berhasil.'); } // ── POST /auth/api-keys (butuh JWT) ────────────────────── public static function createApiKey(): void { $user = self::requireJWT(); $body = self::getBody(); $keyName = Security::sanitize($body['key_name'] ?? 'Default'); $scope = $body['scope'] ?? 'all'; $expires = $body['expires_at'] ?? null; if (!in_array($scope, ['storage', 'chat', 'all'])) { Security::abort(422, "Scope tidak valid. Pilih: storage, chat, atau all."); } $db = Database::getConnection(); // Maksimal 10 key per user $count = $db->prepare("SELECT COUNT(*) FROM api_keys WHERE user_id = ?"); $count->execute([$user['user_id']]); if ($count->fetchColumn() >= 10) { Security::abort(429, 'Maksimal 10 API key per akun.'); } $keys = Security::generateApiKey(); $db->prepare(" INSERT INTO api_keys (user_id, api_key, api_secret, key_name, scope, expires_at) VALUES (?, ?, ?, ?, ?, ?) ")->execute([ $user['user_id'], $keys['hash_key'], $keys['hash_secret'], $keyName, $scope, $expires, ]); Security::respond([ 'api_key' => $keys['raw_key'], 'api_secret' => $keys['raw_secret'], 'key_name' => $keyName, 'scope' => $scope, 'expires_at' => $expires, 'warning' => 'Simpan API key dan secret ini! Secret tidak bisa dilihat lagi.', ], 201, 'API key berhasil dibuat.'); } // ── GET /auth/api-keys (butuh JWT) ─────────────────────── public static function listApiKeys(): void { $user = self::requireJWT(); $db = Database::getConnection(); $stmt = $db->prepare(" SELECT id, key_name, scope, rate_limit, is_active, last_used_at, expires_at, created_at FROM api_keys WHERE user_id = ? ORDER BY created_at DESC "); $stmt->execute([$user['user_id']]); Security::respond($stmt->fetchAll(), 200, 'Daftar API key.'); } // ── DELETE /auth/api-keys/{id} (butuh JWT) ─────────────── public static function revokeApiKey(int $keyId): void { $user = self::requireJWT(); $db = Database::getConnection(); $stmt = $db->prepare("DELETE FROM api_keys WHERE id = ? AND user_id = ?"); $stmt->execute([$keyId, $user['user_id']]); if ($stmt->rowCount() === 0) { Security::abort(404, 'API key tidak ditemukan.'); } Security::respond(['revoked' => true], 200, 'API key berhasil dihapus.'); } // ─── HELPERS ───────────────────────────────────────────── private static function requireJWT(): array { $auth = $_SERVER['HTTP_AUTHORIZATION'] ?? ''; if (!preg_match('/^Bearer\s+(.+)$/i', $auth, $m)) { Security::abort(401, 'Authorization header (Bearer token) wajib disertakan.'); } return Security::verifyJWT($m[1]); } private static function getBody(): array { $raw = file_get_contents('php://input'); $body = json_decode($raw, true) ?? []; return $body; } }