fix cookie-auth example to use new turso libsql

This commit is contained in:
Jamie Barton 2024-10-19 19:25:40 +01:00
parent 15143487a5
commit 72b329da7c
5 changed files with 81 additions and 137 deletions

View File

@ -2,7 +2,7 @@
"require": { "require": {
"notrab/dumbo": "@dev", "notrab/dumbo": "@dev",
"latte/latte": "^3.0", "latte/latte": "^3.0",
"darkterminal/turso-client-http": "^2.9" "turso/libsql": "@dev"
}, },
"repositories": [ "repositories": [
{ {
@ -13,7 +13,7 @@
"scripts": { "scripts": {
"start": [ "start": [
"Composer\\Config::disableProcessTimeout", "Composer\\Config::disableProcessTimeout",
"php -S localhost:8000 -t ." "php -d \"ffi.enable=1\" -S localhost:8000 -t ."
] ]
}, },
"prefer-stable": false "prefer-stable": false

View File

@ -4,39 +4,35 @@ require __DIR__ . "/vendor/autoload.php";
use Dumbo\Dumbo; use Dumbo\Dumbo;
use Dumbo\Helpers\Cookie; use Dumbo\Helpers\Cookie;
use Dumbo\Middleware\CsrfMiddleware;
use Latte\Engine as LatteEngine; use Latte\Engine as LatteEngine;
use Darkterminal\TursoHttp\LibSQL; use Libsql\Database;
$app = new Dumbo(); $app = new Dumbo();
$latte = new LatteEngine(); $latte = new LatteEngine();
$dsn = "http://127.0.0.1:8001"; $db = new Database(path: "file.db");
$db = new LibSQL($dsn); $conn = $db->connect();
$latte->setAutoRefresh(true); $latte->setAutoRefresh(true);
$latte->setTempDirectory(null); $latte->setTempDirectory(null);
const COOKIE_SECRET = "somesecretkey"; const COOKIE_SECRET = "somesecretkey";
const SESSION_COOKIE_NAME = "dumbo_session_id"; const SESSION_COOKIE_NAME = "dumbo_session_id";
$db->execute(" $conn->executeBatch("
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE, username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL password TEXT NOT NULL
) );
");
$db->execute("
CREATE TABLE IF NOT EXISTS sessions ( CREATE TABLE IF NOT EXISTS sessions (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
user_id INTEGER NOT NULL, user_id INTEGER NOT NULL,
expires_at INTEGER NOT NULL, expires_at INTEGER NOT NULL,
user_agent TEXT, user_agent TEXT,
ip_address TEXT ip_address TEXT
) );
"); ");
function render($latte, $view, $params = []) function render($latte, $view, $params = [])
@ -44,9 +40,9 @@ function render($latte, $view, $params = [])
return $latte->renderToString(__DIR__ . "/views/$view.latte", $params); return $latte->renderToString(__DIR__ . "/views/$view.latte", $params);
} }
function invalidateAllUserSessions($userId, $db) function invalidateAllUserSessions($userId, $conn)
{ {
$db->prepare("DELETE FROM sessions WHERE user_id = ?")->execute([$userId]); $conn->execute("DELETE FROM sessions WHERE user_id = ?", [$userId]);
} }
$app->onError(function ($error, $c) { $app->onError(function ($error, $c) {
@ -62,65 +58,28 @@ $app->onError(function ($error, $c) {
); );
}); });
$app->use( $app->use(function ($c, $next) use ($conn) {
CsrfMiddleware::csrf([
"getToken" => function ($ctx) {
return Cookie::get($ctx, "csrf_token") ?? null;
},
"setToken" => function ($ctx, $token) {
Cookie::set($ctx, "csrf_token", $token, [
"httpOnly" => true,
"secure" => true,
"sameSite" => "Lax",
]);
},
])
);
$app->use(function ($c, $next) use ($db) {
$sessionId = Cookie::getSigned($c, COOKIE_SECRET, SESSION_COOKIE_NAME); $sessionId = Cookie::getSigned($c, COOKIE_SECRET, SESSION_COOKIE_NAME);
$debugSessionId = $_COOKIE["debug_session"] ?? "Not set";
error_log(
"Middleware: Session ID from cookie: " .
($sessionId ? $sessionId : "Not set")
);
error_log("Middleware: Debug Session ID: " . $debugSessionId);
if ($sessionId) { if ($sessionId) {
$result = $db $result = $conn
->query("SELECT * FROM sessions WHERE id = ? AND expires_at > ?", [ ->query("SELECT * FROM sessions WHERE id = ? AND expires_at > ?", [
$sessionId, $sessionId,
time(), time(),
]) ])
->fetchArray(LibSQL::LIBSQL_ASSOC); ->fetchArray();
if (!empty($result)) { if (!empty($result)) {
error_log("Middleware: Valid session found for ID: " . $sessionId); $user = $conn
$user = $db
->query("SELECT * FROM users WHERE id = ?", [ ->query("SELECT * FROM users WHERE id = ?", [
$result[0]["user_id"], $result[0]["user_id"],
]) ])
->fetchArray(LibSQL::LIBSQL_ASSOC); ->fetchArray();
if (!empty($user)) { if (!empty($user)) {
error_log(
"Middleware: User found for session: " .
$user[0]["username"]
);
$c->set("user", $user[0]); $c->set("user", $user[0]);
} else {
error_log(
"Middleware: No user found for session ID: " . $sessionId
);
} }
} else {
error_log(
"Middleware: No valid session found for ID: " . $sessionId
);
} }
} else {
error_log("Middleware: No session cookie found");
} }
return $next($c); return $next($c);
@ -149,14 +108,11 @@ $app->get("/", function ($c) use ($latte) {
}); });
$app->get("/register", function ($c) use ($latte) { $app->get("/register", function ($c) use ($latte) {
$csrfToken = $c->get("csrf_token"); $html = render($latte, "register");
$html = render($latte, "register", [
"csrf_token" => $csrfToken,
]);
return $c->html($html); return $c->html($html);
}); });
$app->post("/register", function ($c) use ($db, $latte) { $app->post("/register", function ($c) use ($conn, $latte) {
$body = $c->req->body(); $body = $c->req->body();
$username = $body["username"] ?? ""; $username = $body["username"] ?? "";
$password = $body["password"] ?? ""; $password = $body["password"] ?? "";
@ -170,15 +126,20 @@ $app->post("/register", function ($c) use ($db, $latte) {
try { try {
$hashedPassword = password_hash($password, PASSWORD_DEFAULT); $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
$db->prepare( $result = $conn
"INSERT INTO users (username, password) VALUES (?, ?)" ->query(
)->execute([$username, $hashedPassword]); "INSERT INTO users (username, password) VALUES (?, ?) RETURNING id",
[$username, $hashedPassword]
)
->fetchArray();
$c->set("flash_message", "Registration successful. Please log in."); $c->set("flash_message", "Registration successful. Please log in.");
return $c->redirect("/login"); return $c->redirect("/login");
} catch (Exception $e) { } catch (Exception $e) {
$errorMessage = "Registration failed: " . $e->getMessage();
error_log($errorMessage);
$html = render($latte, "register", [ $html = render($latte, "register", [
"error" => "Username already exists", "error" => $errorMessage,
]); ]);
return $c->html($html); return $c->html($html);
} }
@ -186,26 +147,25 @@ $app->post("/register", function ($c) use ($db, $latte) {
$app->get("/login", function ($c) use ($latte) { $app->get("/login", function ($c) use ($latte) {
$flashMessage = $c->get("flash_message"); $flashMessage = $c->get("flash_message");
$csrfToken = $c->get("csrf_token");
$html = render($latte, "login", [ $html = render($latte, "login", [
"flash_message" => $flashMessage, "flash_message" => $flashMessage,
"csrf_token" => $csrfToken,
]); ]);
$c->set("flash_message", null); // Clear the flash message after displaying $c->set("flash_message", null);
return $c->html($html); return $c->html($html);
}); });
$app->post("/login", function ($c) use ($db, $latte) { $app->post("/login", function ($c) use ($conn, $latte) {
$body = $c->req->body(); $body = $c->req->body();
$username = $body["username"] ?? ""; $username = $body["username"] ?? "";
$password = $body["password"] ?? ""; $password = $body["password"] ?? "";
error_log("Login attempt for username: " . $username); error_log("Login attempt for username: " . $username);
$result = $db $result = $conn
->query("SELECT * FROM users WHERE username = ?", [$username]) ->query("SELECT * FROM users WHERE username = ?", [$username])
->fetchArray(LibSQL::LIBSQL_ASSOC); ->fetchArray();
if (!empty($result) && password_verify($password, $result[0]["password"])) { if (!empty($result) && password_verify($password, $result[0]["password"])) {
$sessionId = bin2hex(random_bytes(16)); $sessionId = bin2hex(random_bytes(16));
@ -214,27 +174,15 @@ $app->post("/login", function ($c) use ($db, $latte) {
$ipAddress = $_SERVER["REMOTE_ADDR"]; $ipAddress = $_SERVER["REMOTE_ADDR"];
try { try {
$db->prepare( $conn->query(
"INSERT INTO sessions (id, user_id, expires_at, user_agent, ip_address) VALUES (?, ?, ?, ?, ?)" "INSERT INTO sessions (id, user_id, expires_at, user_agent, ip_address) VALUES (?, ?, ?, ?, ?)",
)->execute([ [
$sessionId, $sessionId,
$result[0]["id"], $result[0]["id"],
$expiresAt, $expiresAt,
$userAgent, $userAgent,
$ipAddress, $ipAddress,
]); ]
error_log("Session created: " . $sessionId);
// Set a debug cookie
setcookie(
"debug_session",
$sessionId,
time() + 3600,
"/",
"",
true,
false
); );
Cookie::setSigned( Cookie::setSigned(
@ -251,25 +199,16 @@ $app->post("/login", function ($c) use ($db, $latte) {
] ]
); );
error_log(
"Session cookie set: " .
SESSION_COOKIE_NAME .
" = " .
$sessionId
);
$c->set("flash_message", "Login successful."); $c->set("flash_message", "Login successful.");
error_log("Login successful for user: " . $username);
return $c->redirect("/"); return $c->redirect("/");
} catch (Exception $e) { } catch (Exception $e) {
error_log("Login error: " . $e->getMessage());
$html = render($latte, "login", [ $html = render($latte, "login", [
"error" => "An error occurred during login. Please try again.", "error" => "An error occurred during login. Please try again.",
]); ]);
return $c->html($html); return $c->html($html);
} }
} else { } else {
error_log("Login failed for username: " . $username);
$html = render($latte, "login", [ $html = render($latte, "login", [
"error" => "Invalid username or password", "error" => "Invalid username or password",
]); ]);
@ -277,13 +216,11 @@ $app->post("/login", function ($c) use ($db, $latte) {
} }
}); });
$app->get("/logout", function ($c) use ($db) { $app->get("/logout", function ($c) use ($conn) {
$sessionId = Cookie::getSigned($c, COOKIE_SECRET, SESSION_COOKIE_NAME); $sessionId = Cookie::getSigned($c, COOKIE_SECRET, SESSION_COOKIE_NAME);
if ($sessionId) { if ($sessionId) {
$db->prepare("DELETE FROM sessions WHERE id = ?")->execute([ $conn->execute("DELETE FROM sessions WHERE id = ?", [$sessionId]);
$sessionId,
]);
} }
Cookie::delete($c, SESSION_COOKIE_NAME, [ Cookie::delete($c, SESSION_COOKIE_NAME, [
@ -296,30 +233,28 @@ $app->get("/logout", function ($c) use ($db) {
return $c->redirect("/"); return $c->redirect("/");
}); });
$app->get("/settings", function ($c) use ($db, $latte) { $app->get("/settings", function ($c) use ($conn, $latte) {
$user = $c->get("user"); $user = $c->get("user");
if (!$user) { if (!$user) {
return $c->redirect("/login"); return $c->redirect("/login");
} }
$csrfToken = $c->get("csrf_token"); $sessions = $conn
$sessions = $db
->query( ->query(
"SELECT id, user_agent, ip_address, expires_at FROM sessions WHERE user_id = ? AND expires_at > ?", "SELECT id, user_agent, ip_address, expires_at FROM sessions WHERE user_id = ? AND expires_at > ?",
[$user["id"], time()] [$user["id"], time()]
) )
->fetchArray(LibSQL::LIBSQL_ASSOC); ->fetchArray();
$html = render($latte, "settings", [ $html = render($latte, "settings", [
"user" => $user, "user" => $user,
"sessions" => $sessions, "sessions" => $sessions,
"csrf_token" => $csrfToken,
]); ]);
return $c->html($html); return $c->html($html);
}); });
$app->post("/settings", function ($c) use ($db, $latte) { $app->post("/settings", function ($c) use ($conn, $latte) {
$user = $c->get("user"); $user = $c->get("user");
if (!$user) { if (!$user) {
return $c->redirect("/login"); return $c->redirect("/login");
@ -330,9 +265,9 @@ $app->post("/settings", function ($c) use ($db, $latte) {
$newPassword = $body["password"] ?? ""; $newPassword = $body["password"] ?? "";
$currentPassword = $body["current_password"] ?? ""; $currentPassword = $body["current_password"] ?? "";
$result = $db $result = $conn
->query("SELECT * FROM users WHERE id = ?", [$user["id"]]) ->query("SELECT * FROM users WHERE id = ?", [$user["id"]])
->fetchArray(LibSQL::LIBSQL_ASSOC); ->fetchArray();
if ( if (
empty($result) || empty($result) ||
!password_verify($currentPassword, $result[0]["password"]) !password_verify($currentPassword, $result[0]["password"])
@ -359,27 +294,31 @@ $app->post("/settings", function ($c) use ($db, $latte) {
if (!empty($updateFields)) { if (!empty($updateFields)) {
$updateParams[] = $user["id"]; $updateParams[] = $user["id"];
$db->prepare( $query =
"UPDATE users SET " . implode(", ", $updateFields) . " WHERE id = ?" "UPDATE users SET " .
)->execute($updateParams); implode(", ", $updateFields) .
" WHERE id = ? RETURNING *";
$updatedUser = $conn->query($query, $updateParams)->fetchArray();
if (!empty($newPassword)) { if (!empty($updatedUser)) {
invalidateAllUserSessions($user["id"], $db); if (!empty($newPassword)) {
$c->set("flash_message", "Password changed. Please log in again."); invalidateAllUserSessions($user["id"], $conn);
return $c->redirect("/login"); $c->set(
"flash_message",
"Password changed. Please log in again."
);
return $c->redirect("/login");
}
$c->set("user", $updatedUser[0]);
$c->set("flash_message", "Settings updated successfully.");
} }
$user = $db
->query("SELECT * FROM users WHERE id = ?", [$user["id"]])
->fetchArray(LibSQL::LIBSQL_ASSOC)[0];
$c->set("user", $user);
$c->set("flash_message", "Settings updated successfully.");
} }
return $c->redirect("/settings"); return $c->redirect("/settings");
}); });
$app->post("/invalidate-session", function ($c) use ($db) { $app->post("/invalidate-session", function ($c) use ($conn) {
$user = $c->get("user"); $user = $c->get("user");
if (!$user) { if (!$user) {
return $c->redirect("/login"); return $c->redirect("/login");
@ -391,17 +330,28 @@ $app->post("/invalidate-session", function ($c) use ($db) {
return $c->redirect("/settings"); return $c->redirect("/settings");
} }
$result = $db $result = $conn
->query("SELECT id FROM sessions WHERE id = ? AND user_id = ?", [ ->query("SELECT id FROM sessions WHERE id = ? AND user_id = ?", [
$sessionToInvalidate, $sessionToInvalidate,
$user["id"], $user["id"],
]) ])
->fetchArray(LibSQL::LIBSQL_ASSOC); ->fetchArray();
if (!empty($result)) { if (!empty($result)) {
$db->prepare("DELETE FROM sessions WHERE id = ?")->execute([ $conn->execute("DELETE FROM sessions WHERE id = ?", [
$sessionToInvalidate, $sessionToInvalidate,
]); ]);
$currentSessionId = Cookie::getSigned(
$c,
COOKIE_SECRET,
SESSION_COOKIE_NAME
);
if ($sessionToInvalidate === $currentSessionId) {
Cookie::delete($c, SESSION_COOKIE_NAME);
return $c->redirect("/login");
}
$c->set("flash_message", "Session invalidated successfully"); $c->set("flash_message", "Session invalidated successfully");
} else { } else {
$c->set("flash_message", "Invalid session"); $c->set("flash_message", "Invalid session");

View File

@ -6,7 +6,6 @@
<p style="color: red;">{$error}</p> <p style="color: red;">{$error}</p>
{/if} {/if}
<form action="/login" method="post"> <form action="/login" method="post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div> <div>
<label for="username">Username:</label> <label for="username">Username:</label>
<input type="text" id="username" name="username" required> <input type="text" id="username" name="username" required>
@ -16,7 +15,6 @@
<input type="password" id="password" name="password" required> <input type="password" id="password" name="password" required>
</div> </div>
<button type="submit">Login</button> <button type="submit">Login</button>
<input type="hidden" name="csrf_token" value="{$csrf_token}">
</form> </form>
<p>Don't have an account? <a href="/register">Register</a></p> <p>Don't have an account? <a href="/register">Register</a></p>
<p><a href="/">Back to Home</a></p> <p><a href="/">Back to Home</a></p>

View File

@ -6,7 +6,6 @@
<p style="color: red;">{$error}</p> <p style="color: red;">{$error}</p>
{/if} {/if}
<form action="/register" method="post"> <form action="/register" method="post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div> <div>
<label for="username">Username:</label> <label for="username">Username:</label>
<input type="text" id="username" name="username" required> <input type="text" id="username" name="username" required>
@ -16,7 +15,6 @@
<input type="password" id="password" name="password" required> <input type="password" id="password" name="password" required>
</div> </div>
<button type="submit">Register</button> <button type="submit">Register</button>
<input type="hidden" name="csrf_token" value="{$csrf_token}">
</form> </form>
<p>Already have an account? <a href="/login">Login</a></p> <p>Already have an account? <a href="/login">Login</a></p>
<p><a href="/">Back to Home</a></p> <p><a href="/">Back to Home</a></p>

View File

@ -9,7 +9,6 @@
<p style="color: green;">{$success}</p> <p style="color: green;">{$success}</p>
{/if} {/if}
<form action="/settings" method="post"> <form action="/settings" method="post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<div> <div>
<label for="username">New Username:</label> <label for="username">New Username:</label>
<input type="text" id="username" name="username" value="{$user['username']}"> <input type="text" id="username" name="username" value="{$user['username']}">
@ -41,7 +40,6 @@
<td>{date('Y-m-d H:i:s', $session['expires_at'])}</td> <td>{date('Y-m-d H:i:s', $session['expires_at'])}</td>
<td> <td>
<form action="/invalidate-session" method="post"> <form action="/invalidate-session" method="post">
<input type="hidden" name="csrf_token" value="{$csrf_token}">
<input type="hidden" name="session_id" value="{$session['id']}"> <input type="hidden" name="session_id" value="{$session['id']}">
<button type="submit">Invalidate</button> <button type="submit">Invalidate</button>
</form> </form>