This commit is contained in:
doomtube 2025-08-13 00:10:25 -04:00
parent e8864cc853
commit 1d42a9a623
9 changed files with 2122 additions and 542 deletions

View file

@ -1,6 +1,7 @@
#include "UserController.h"
#include "../services/DatabaseService.h"
#include <drogon/MultiPart.h>
#include <drogon/Cookie.h>
#include <fstream>
#include <random>
#include <sstream>
@ -51,18 +52,46 @@ namespace {
return false;
}
}
// Helper to set httpOnly auth cookie
void setAuthCookie(const HttpResponsePtr& resp, const std::string& token) {
Cookie authCookie("auth_token", token);
authCookie.setPath("/");
authCookie.setHttpOnly(true);
authCookie.setSecure(false); // Set to true in production with HTTPS
authCookie.setMaxAge(86400); // 24 hours
authCookie.setSameSite(Cookie::SameSite::kLax);
resp->addCookie(authCookie);
}
// Helper to clear auth cookie
void clearAuthCookie(const HttpResponsePtr& resp) {
Cookie authCookie("auth_token", "");
authCookie.setPath("/");
authCookie.setHttpOnly(true);
authCookie.setMaxAge(0); // Expire immediately
resp->addCookie(authCookie);
}
}
UserInfo UserController::getUserFromRequest(const HttpRequestPtr &req) {
UserInfo user;
std::string auth = req->getHeader("Authorization");
if (auth.empty() || auth.substr(0, 7) != "Bearer ") {
return user;
// First try to get from cookie
std::string token = req->getCookie("auth_token");
// Fallback to Authorization header for API clients
if (token.empty()) {
std::string auth = req->getHeader("Authorization");
if (!auth.empty() && auth.substr(0, 7) == "Bearer ") {
token = auth.substr(7);
}
}
if (!token.empty()) {
AuthService::getInstance().validateToken(token, user);
}
std::string token = auth.substr(7);
AuthService::getInstance().validateToken(token, user);
return user;
}
@ -168,9 +197,10 @@ void UserController::login(const HttpRequestPtr &req,
[callback, username](bool success, const std::string& token, const UserInfo& user) {
if (success) {
LOG_INFO << "Login successful for user: " << username;
Json::Value resp;
resp["success"] = true;
resp["token"] = token;
// Don't send token in body for cookie-based auth
resp["user"]["id"] = static_cast<Json::Int64>(user.id);
resp["user"]["username"] = user.username;
resp["user"]["isAdmin"] = user.isAdmin;
@ -179,8 +209,11 @@ void UserController::login(const HttpRequestPtr &req,
resp["user"]["bio"] = user.bio;
resp["user"]["avatarUrl"] = user.avatarUrl;
resp["user"]["pgpOnlyEnabledAt"] = user.pgpOnlyEnabledAt;
resp["user"]["colorCode"] = user.colorCode; // Use colorCode for consistency with database field name
callback(jsonResp(resp));
resp["user"]["colorCode"] = user.colorCode;
auto response = jsonResp(resp);
setAuthCookie(response, token);
callback(response);
} else {
LOG_WARN << "Login failed for user: " << username;
callback(jsonError(token.empty() ? "Invalid credentials" : token, k401Unauthorized));
@ -195,6 +228,22 @@ void UserController::login(const HttpRequestPtr &req,
}
}
void UserController::logout(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
try {
Json::Value resp;
resp["success"] = true;
resp["message"] = "Logged out successfully";
auto response = jsonResp(resp);
clearAuthCookie(response);
callback(response);
} catch (const std::exception& e) {
LOG_ERROR << "Exception in logout: " << e.what();
callback(jsonError("Internal server error"));
}
}
void UserController::pgpChallenge(const HttpRequestPtr &req,
std::function<void(const HttpResponsePtr &)> &&callback) {
try {
@ -264,7 +313,7 @@ void UserController::pgpVerify(const HttpRequestPtr &req,
if (success) {
Json::Value resp;
resp["success"] = true;
resp["token"] = token;
// Don't send token in body for cookie-based auth
resp["user"]["id"] = static_cast<Json::Int64>(user.id);
resp["user"]["username"] = user.username;
resp["user"]["isAdmin"] = user.isAdmin;
@ -273,8 +322,11 @@ void UserController::pgpVerify(const HttpRequestPtr &req,
resp["user"]["bio"] = user.bio;
resp["user"]["avatarUrl"] = user.avatarUrl;
resp["user"]["pgpOnlyEnabledAt"] = user.pgpOnlyEnabledAt;
resp["user"]["colorCode"] = user.colorCode; // Add colorCode to PGP login response
callback(jsonResp(resp));
resp["user"]["colorCode"] = user.colorCode;
auto response = jsonResp(resp);
setAuthCookie(response, token);
callback(response);
} else {
callback(jsonError("Invalid signature", k401Unauthorized));
}
@ -782,7 +834,7 @@ void UserController::updateColor(const HttpRequestPtr &req,
AuthService::getInstance().updateUserColor(user.id, newColor,
[callback, user](bool success, const std::string& error, const std::string& finalColor) {
if (success) {
// Fetch updated user info and generate new token with updated color
// Fetch updated user info
AuthService::getInstance().fetchUserInfo(user.id,
[callback, finalColor](bool fetchSuccess, const UserInfo& updatedUser) {
if (fetchSuccess) {
@ -792,13 +844,16 @@ void UserController::updateColor(const HttpRequestPtr &req,
Json::Value resp;
resp["success"] = true;
resp["color"] = finalColor;
resp["token"] = newToken; // Return new token with updated color
resp["user"]["id"] = static_cast<Json::Int64>(updatedUser.id);
resp["user"]["username"] = updatedUser.username;
resp["user"]["isAdmin"] = updatedUser.isAdmin;
resp["user"]["isStreamer"] = updatedUser.isStreamer;
resp["user"]["colorCode"] = updatedUser.colorCode;
callback(jsonResp(resp));
auto response = jsonResp(resp);
// Update auth cookie with new token
setAuthCookie(response, newToken);
callback(response);
} else {
// Color was updated but couldn't fetch full user info
Json::Value resp;