v1.8.0 #429

Merged
LukeGus merged 198 commits from dev-1.8.0 into main 2025-11-05 16:36:16 +00:00
4 changed files with 80 additions and 22 deletions
Showing only changes of commit 4b078821c3 - Show all commits

View File

@@ -2358,8 +2358,9 @@ router.delete("/sessions/:sessionId", authenticateJWT, async (req, res) => {
operation: "session_revoke", operation: "session_revoke",
sessionId, sessionId,
revokedBy: userId, revokedBy: userId,
sessionUserId: session.userId,
}); });
res.json({ message: "Session revoked successfully" }); res.json({ success: true, message: "Session revoked successfully" });
} else { } else {
res.status(500).json({ error: "Failed to revoke session" }); res.status(500).json({ error: "Failed to revoke session" });
} }

View File

@@ -267,6 +267,11 @@ class AuthManager {
.limit(1); .limit(1);
if (sessionRecords.length === 0) { if (sessionRecords.length === 0) {
databaseLogger.warn("Session not found during JWT verification", {
operation: "jwt_verify_session_not_found",
sessionId: payload.sessionId,
userId: payload.userId,
});
return null; return null;
} }
} catch (dbError) { } catch (dbError) {
@@ -278,6 +283,7 @@ class AuthManager {
sessionId: payload.sessionId, sessionId: payload.sessionId,
}, },
); );
return null;
} }
} }
return payload; return payload;
@@ -299,6 +305,22 @@ class AuthManager {
try { try {
await db.delete(sessions).where(eq(sessions.id, sessionId)); await db.delete(sessions).where(eq(sessions.id, sessionId));
try {
const { saveMemoryDatabaseToFile } = await import(
"../database/db/index.js"
);
await saveMemoryDatabaseToFile();
} catch (saveError) {
databaseLogger.error(
"Failed to save database after session revocation",
saveError,
{
operation: "session_revoke_db_save_failed",
sessionId,
},
);
}
return true; return true;
} catch (error) { } catch (error) {
databaseLogger.error("Failed to delete session", error, { databaseLogger.error("Failed to delete session", error, {
@@ -336,6 +358,22 @@ class AuthManager {
await db.delete(sessions).where(eq(sessions.userId, userId)); await db.delete(sessions).where(eq(sessions.userId, userId));
} }
try {
const { saveMemoryDatabaseToFile } = await import(
"../database/db/index.js"
);
await saveMemoryDatabaseToFile();
} catch (saveError) {
databaseLogger.error(
"Failed to save database after revoking all user sessions",
saveError,
{
operation: "user_sessions_revoke_db_save_failed",
userId,
},
);
}
return deletedCount; return deletedCount;
} catch (error) { } catch (error) {
databaseLogger.error("Failed to delete user sessions", error, { databaseLogger.error("Failed to delete user sessions", error, {
@@ -440,6 +478,11 @@ class AuthManager {
.limit(1); .limit(1);
if (sessionRecords.length === 0) { if (sessionRecords.length === 0) {
databaseLogger.warn("Session not found in middleware", {
operation: "middleware_session_not_found",
sessionId: payload.sessionId,
userId: payload.userId,
});
return res.status(401).json({ return res.status(401).json({
error: "Session not found", error: "Session not found",
code: "SESSION_NOT_FOUND", code: "SESSION_NOT_FOUND",
@@ -479,10 +522,11 @@ class AuthManager {
}); });
}); });
} catch (error) { } catch (error) {
databaseLogger.error("Session check failed", error, { databaseLogger.error("Session check failed in middleware", error, {
operation: "session_check_failed", operation: "middleware_session_check_failed",
sessionId: payload.sessionId, sessionId: payload.sessionId,
}); });
return res.status(500).json({ error: "Session check failed" });
} }
} }

View File

@@ -12,6 +12,10 @@ import { Auth } from "@/ui/mobile/authentication/Auth.tsx";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Toaster } from "@/components/ui/sonner.tsx"; import { Toaster } from "@/components/ui/sonner.tsx";
function isReactNativeWebView(): boolean {
return typeof window !== "undefined" && !!(window as any).ReactNativeWebView;
}
const AppContent: FC = () => { const AppContent: FC = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const { tabs, currentTab, getTab } = useTabs(); const { tabs, currentTab, getTab } = useTabs();
@@ -114,7 +118,7 @@ const AppContent: FC = () => {
); );
} }
if (!isAuthenticated) { if (!isAuthenticated || isReactNativeWebView()) {
return ( return (
<div className="h-screen w-screen flex items-center justify-center bg-dark-bg p-4"> <div className="h-screen w-screen flex items-center justify-center bg-dark-bg p-4">
<Auth <Auth

View File

@@ -239,7 +239,6 @@ export function Auth({
const [meRes] = await Promise.all([getUserInfo()]); const [meRes] = await Promise.all([getUserInfo()]);
setLoggedIn(true);
setIsAdmin(!!meRes.is_admin); setIsAdmin(!!meRes.is_admin);
setUsername(meRes.username || null); setUsername(meRes.username || null);
setUserId(meRes.userId || null); setUserId(meRes.userId || null);
@@ -252,6 +251,7 @@ export function Auth({
return; return;
} }
setLoggedIn(true);
onAuthSuccess({ onAuthSuccess({
isAdmin: !!meRes.is_admin, isAdmin: !!meRes.is_admin,
username: meRes.username || null, username: meRes.username || null,
@@ -411,7 +411,6 @@ export function Auth({
localStorage.setItem("jwt", res.token); localStorage.setItem("jwt", res.token);
} }
setLoggedIn(true);
setIsAdmin(!!res.is_admin); setIsAdmin(!!res.is_admin);
setUsername(res.username || null); setUsername(res.username || null);
setUserId(res.userId || null); setUserId(res.userId || null);
@@ -425,6 +424,7 @@ export function Auth({
return; return;
} }
setLoggedIn(true);
onAuthSuccess({ onAuthSuccess({
isAdmin: !!res.is_admin, isAdmin: !!res.is_admin,
username: res.username || null, username: res.username || null,
@@ -506,7 +506,6 @@ export function Auth({
getUserInfo() getUserInfo()
.then((meRes) => { .then((meRes) => {
setLoggedIn(true);
setIsAdmin(!!meRes.is_admin); setIsAdmin(!!meRes.is_admin);
setUsername(meRes.username || null); setUsername(meRes.username || null);
setUserId(meRes.userId || null); setUserId(meRes.userId || null);
@@ -524,6 +523,7 @@ export function Auth({
return; return;
} }
setLoggedIn(true);
onAuthSuccess({ onAuthSuccess({
isAdmin: !!meRes.is_admin, isAdmin: !!meRes.is_admin,
username: meRes.username || null, username: meRes.username || null,
@@ -578,21 +578,14 @@ export function Auth({
</svg> </svg>
); );
return ( if (isReactNativeWebView() && mobileAuthSuccess) {
<div return (
className={`w-full max-w-md flex flex-col bg-dark-bg overflow-y-auto my-2 ${className || ""}`} <div
style={{ maxHeight: "calc(100vh - 1rem)" }} className={`w-full max-w-md flex flex-col bg-dark-bg overflow-y-auto my-2 ${className || ""}`}
{...props} style={{ maxHeight: "calc(100vh - 1rem)" }}
> {...props}
{isReactNativeWebView() && !mobileAuthSuccess && ( >
<Alert className="mb-4 border-blue-500 bg-blue-500/10"> <div className="flex flex-col items-center justify-center min-h-[400px] gap-4 px-4">
<Smartphone className="h-4 w-4" />
<AlertTitle>{t("auth.mobileApp")}</AlertTitle>
<AlertDescription>{t("auth.loggingInToMobileApp")}</AlertDescription>
</Alert>
)}
{isReactNativeWebView() && mobileAuthSuccess && (
<div className="flex flex-col items-center justify-center h-64 gap-4">
<div className="w-16 h-16 rounded-full bg-green-500/20 flex items-center justify-center"> <div className="w-16 h-16 rounded-full bg-green-500/20 flex items-center justify-center">
<svg <svg
className="w-10 h-10 text-green-500" className="w-10 h-10 text-green-500"
@@ -617,6 +610,22 @@ export function Auth({
</p> </p>
</div> </div>
</div> </div>
</div>
);
}
return (
<div
className={`w-full max-w-md flex flex-col bg-dark-bg overflow-y-auto my-2 ${className || ""}`}
style={{ maxHeight: "calc(100vh - 1rem)" }}
{...props}
>
{isReactNativeWebView() && !mobileAuthSuccess && (
<Alert className="mb-4 border-blue-500 bg-blue-500/10">
<Smartphone className="h-4 w-4" />
<AlertTitle>{t("auth.mobileApp")}</AlertTitle>
<AlertDescription>{t("auth.loggingInToMobileApp")}</AlertDescription>
</Alert>
)} )}
{!mobileAuthSuccess && error && ( {!mobileAuthSuccess && error && (
<Alert variant="destructive" className="mb-4"> <Alert variant="destructive" className="mb-4">