fix: JWT not persisting after reboot
This commit is contained in:
@@ -1012,15 +1012,31 @@ router.post("/login", async (req, res) => {
|
|||||||
|
|
||||||
// Route: Logout user
|
// Route: Logout user
|
||||||
// POST /users/logout
|
// POST /users/logout
|
||||||
router.post("/logout", async (req, res) => {
|
router.post("/logout", authenticateJWT, async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const userId = (req as AuthenticatedRequest).userId;
|
const authReq = req as AuthenticatedRequest;
|
||||||
|
const userId = authReq.userId;
|
||||||
|
|
||||||
if (userId) {
|
if (userId) {
|
||||||
authManager.logoutUser(userId);
|
// Get sessionId from JWT if available
|
||||||
|
const token =
|
||||||
|
req.cookies?.jwt || req.headers["authorization"]?.split(" ")[1];
|
||||||
|
let sessionId: string | undefined;
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
try {
|
||||||
|
const payload = await authManager.verifyJWTToken(token);
|
||||||
|
sessionId = payload?.sessionId;
|
||||||
|
} catch (error) {
|
||||||
|
// Ignore token verification errors during logout
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await authManager.logoutUser(userId, sessionId);
|
||||||
authLogger.info("User logged out", {
|
authLogger.info("User logged out", {
|
||||||
operation: "user_logout",
|
operation: "user_logout",
|
||||||
userId,
|
userId,
|
||||||
|
sessionId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1036,29 +1052,35 @@ router.post("/logout", async (req, res) => {
|
|||||||
// Route: Get current user's info using JWT
|
// Route: Get current user's info using JWT
|
||||||
// GET /users/me
|
// GET /users/me
|
||||||
router.get("/me", authenticateJWT, async (req: Request, res: Response) => {
|
router.get("/me", authenticateJWT, async (req: Request, res: Response) => {
|
||||||
|
console.log("=== /users/me CALLED ===");
|
||||||
const userId = (req as AuthenticatedRequest).userId;
|
const userId = (req as AuthenticatedRequest).userId;
|
||||||
|
console.log("User ID from JWT:", userId);
|
||||||
|
|
||||||
if (!isNonEmptyString(userId)) {
|
if (!isNonEmptyString(userId)) {
|
||||||
|
console.log("ERROR: Invalid userId");
|
||||||
authLogger.warn("Invalid userId in JWT for /users/me");
|
authLogger.warn("Invalid userId in JWT for /users/me");
|
||||||
return res.status(401).json({ error: "Invalid userId" });
|
return res.status(401).json({ error: "Invalid userId" });
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const user = await db.select().from(users).where(eq(users.id, userId));
|
const user = await db.select().from(users).where(eq(users.id, userId));
|
||||||
|
console.log("User found:", user.length > 0 ? "YES" : "NO");
|
||||||
|
|
||||||
if (!user || user.length === 0) {
|
if (!user || user.length === 0) {
|
||||||
|
console.log("ERROR: User not found in database");
|
||||||
authLogger.warn(`User not found for /users/me: ${userId}`);
|
authLogger.warn(`User not found for /users/me: ${userId}`);
|
||||||
return res.status(401).json({ error: "User not found" });
|
return res.status(401).json({ error: "User not found" });
|
||||||
}
|
}
|
||||||
|
|
||||||
const isDataUnlocked = authManager.isUserUnlocked(userId);
|
console.log("SUCCESS: Returning user info");
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
userId: user[0].id,
|
userId: user[0].id,
|
||||||
username: user[0].username,
|
username: user[0].username,
|
||||||
is_admin: !!user[0].is_admin,
|
is_admin: !!user[0].is_admin,
|
||||||
is_oidc: !!user[0].is_oidc,
|
is_oidc: !!user[0].is_oidc,
|
||||||
totp_enabled: !!user[0].totp_enabled,
|
totp_enabled: !!user[0].totp_enabled,
|
||||||
data_unlocked: isDataUnlocked,
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
console.log("ERROR: Exception thrown:", err);
|
||||||
authLogger.error("Failed to get username", err);
|
authLogger.error("Failed to get username", err);
|
||||||
res.status(500).json({ error: "Failed to get username" });
|
res.status(500).json({ error: "Failed to get username" });
|
||||||
}
|
}
|
||||||
@@ -1429,7 +1451,7 @@ router.post("/complete-reset", async (req, res) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userIdFromJwt === userId && authManager.isUserUnlocked(userId)) {
|
if (userIdFromJwt === userId) {
|
||||||
// Logged-in user: preserve data
|
// Logged-in user: preserve data
|
||||||
try {
|
try {
|
||||||
const success = await authManager.resetUserPasswordWithPreservedDEK(
|
const success = await authManager.resetUserPasswordWithPreservedDEK(
|
||||||
@@ -1825,15 +1847,6 @@ router.post("/totp/verify-login", async (req, res) => {
|
|||||||
req.headers["x-electron-app"] === "true" ||
|
req.headers["x-electron-app"] === "true" ||
|
||||||
req.headers["X-Electron-App"] === "true";
|
req.headers["X-Electron-App"] === "true";
|
||||||
|
|
||||||
const isDataUnlocked = authManager.isUserUnlocked(userRecord.id);
|
|
||||||
|
|
||||||
if (!isDataUnlocked) {
|
|
||||||
return res.status(401).json({
|
|
||||||
error: "Session expired - please log in again",
|
|
||||||
code: "SESSION_EXPIRED",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
authLogger.success("TOTP verification successful", {
|
authLogger.success("TOTP verification successful", {
|
||||||
operation: "totp_verify_success",
|
operation: "totp_verify_success",
|
||||||
userId: userRecord.id,
|
userId: userRecord.id,
|
||||||
@@ -1848,7 +1861,6 @@ router.post("/totp/verify-login", async (req, res) => {
|
|||||||
userId: userRecord.id,
|
userId: userRecord.id,
|
||||||
is_oidc: !!userRecord.is_oidc,
|
is_oidc: !!userRecord.is_oidc,
|
||||||
totp_enabled: !!userRecord.totp_enabled,
|
totp_enabled: !!userRecord.totp_enabled,
|
||||||
data_unlocked: isDataUnlocked,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (isElectron) {
|
if (isElectron) {
|
||||||
@@ -2218,12 +2230,10 @@ router.get("/data-status", authenticateJWT, async (req, res) => {
|
|||||||
const userId = (req as AuthenticatedRequest).userId;
|
const userId = (req as AuthenticatedRequest).userId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const isUnlocked = authManager.isUserUnlocked(userId);
|
// Data lock functionality has been removed - always return unlocked for authenticated users
|
||||||
res.json({
|
res.json({
|
||||||
unlocked: isUnlocked,
|
unlocked: true,
|
||||||
message: isUnlocked
|
message: "Data is unlocked",
|
||||||
? "Data is unlocked"
|
|
||||||
: "Data is locked - re-authenticate with password",
|
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
authLogger.error("Failed to check data status", err, {
|
authLogger.error("Failed to check data status", err, {
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ class AuthManager {
|
|||||||
private static instance: AuthManager;
|
private static instance: AuthManager;
|
||||||
private systemCrypto: SystemCrypto;
|
private systemCrypto: SystemCrypto;
|
||||||
private userCrypto: UserCrypto;
|
private userCrypto: UserCrypto;
|
||||||
private invalidatedTokens: Set<string> = new Set();
|
|
||||||
|
|
||||||
private constructor() {
|
private constructor() {
|
||||||
this.systemCrypto = SystemCrypto.getInstance();
|
this.systemCrypto = SystemCrypto.getInstance();
|
||||||
@@ -54,6 +53,22 @@ class AuthManager {
|
|||||||
this.userCrypto.setSessionExpiredCallback((userId: string) => {
|
this.userCrypto.setSessionExpiredCallback((userId: string) => {
|
||||||
this.invalidateUserTokens(userId);
|
this.invalidateUserTokens(userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Run session cleanup every 5 minutes
|
||||||
|
setInterval(
|
||||||
|
() => {
|
||||||
|
this.cleanupExpiredSessions().catch((error) => {
|
||||||
|
databaseLogger.error(
|
||||||
|
"Failed to run periodic session cleanup",
|
||||||
|
error,
|
||||||
|
{
|
||||||
|
operation: "session_cleanup_periodic",
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
5 * 60 * 1000,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static getInstance(): AuthManager {
|
static getInstance(): AuthManager {
|
||||||
@@ -237,48 +252,81 @@ class AuthManager {
|
|||||||
|
|
||||||
async verifyJWTToken(token: string): Promise<JWTPayload | null> {
|
async verifyJWTToken(token: string): Promise<JWTPayload | null> {
|
||||||
try {
|
try {
|
||||||
if (this.invalidatedTokens.has(token)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const jwtSecret = await this.systemCrypto.getJWTSecret();
|
const jwtSecret = await this.systemCrypto.getJWTSecret();
|
||||||
const payload = jwt.verify(token, jwtSecret) as JWTPayload;
|
const payload = jwt.verify(token, jwtSecret) as JWTPayload;
|
||||||
|
|
||||||
|
// For tokens with sessionId, verify the session exists in database
|
||||||
|
// This ensures revoked sessions are rejected even after backend restart
|
||||||
|
if (payload.sessionId) {
|
||||||
|
try {
|
||||||
|
const sessionRecords = await db
|
||||||
|
.select()
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.id, payload.sessionId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (sessionRecords.length === 0) {
|
||||||
|
databaseLogger.warn(
|
||||||
|
"JWT token has no matching session in database",
|
||||||
|
{
|
||||||
|
operation: "jwt_verify_failed",
|
||||||
|
reason: "session_not_found",
|
||||||
|
sessionId: payload.sessionId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} catch (dbError) {
|
||||||
|
databaseLogger.error(
|
||||||
|
"Failed to check session in database during JWT verification",
|
||||||
|
dbError,
|
||||||
|
{
|
||||||
|
operation: "jwt_verify_session_check_failed",
|
||||||
|
sessionId: payload.sessionId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
// Continue anyway - database errors shouldn't block valid JWTs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
databaseLogger.info("JWT verification successful", {
|
||||||
|
operation: "jwt_verify_success",
|
||||||
|
userId: payload.userId,
|
||||||
|
sessionId: payload.sessionId,
|
||||||
|
});
|
||||||
return payload;
|
return payload;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
databaseLogger.warn("JWT verification failed", {
|
databaseLogger.warn("JWT verification failed", {
|
||||||
operation: "jwt_verify_failed",
|
operation: "jwt_verify_failed",
|
||||||
error: error instanceof Error ? error.message : "Unknown error",
|
error: error instanceof Error ? error.message : "Unknown error",
|
||||||
|
errorName: error instanceof Error ? error.name : "Unknown",
|
||||||
});
|
});
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateJWTToken(token: string): void {
|
invalidateJWTToken(token: string): void {
|
||||||
this.invalidatedTokens.add(token);
|
// No-op: Token invalidation is now handled through database session deletion
|
||||||
|
databaseLogger.info(
|
||||||
|
"Token invalidation requested (handled via session deletion)",
|
||||||
|
{
|
||||||
|
operation: "token_invalidate",
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidateUserTokens(userId: string): void {
|
invalidateUserTokens(userId: string): void {
|
||||||
databaseLogger.info("User tokens invalidated due to data lock", {
|
databaseLogger.info("User tokens invalidation requested due to data lock", {
|
||||||
operation: "user_tokens_invalidate",
|
operation: "user_tokens_invalidate",
|
||||||
userId,
|
userId,
|
||||||
});
|
});
|
||||||
|
// Session cleanup will happen through revokeAllUserSessions if needed
|
||||||
}
|
}
|
||||||
|
|
||||||
async revokeSession(sessionId: string): Promise<boolean> {
|
async revokeSession(sessionId: string): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
// Get the session to blacklist the token
|
// Delete the session from database
|
||||||
const sessionRecords = await db
|
// The JWT will be invalidated because verifyJWTToken checks for session existence
|
||||||
.select()
|
|
||||||
.from(sessions)
|
|
||||||
.where(eq(sessions.id, sessionId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (sessionRecords.length > 0) {
|
|
||||||
const session = sessionRecords[0];
|
|
||||||
this.invalidatedTokens.add(session.jwtToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete the session instead of marking as revoked
|
|
||||||
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
||||||
|
|
||||||
databaseLogger.info("Session deleted", {
|
databaseLogger.info("Session deleted", {
|
||||||
@@ -301,19 +349,18 @@ class AuthManager {
|
|||||||
exceptSessionId?: string,
|
exceptSessionId?: string,
|
||||||
): Promise<number> {
|
): Promise<number> {
|
||||||
try {
|
try {
|
||||||
// Get all user sessions to blacklist tokens
|
// Get session count before deletion
|
||||||
let query = db.select().from(sessions).where(eq(sessions.userId, userId));
|
const userSessions = await db
|
||||||
|
.select()
|
||||||
|
.from(sessions)
|
||||||
|
.where(eq(sessions.userId, userId));
|
||||||
|
|
||||||
const userSessions = await query;
|
const deletedCount = userSessions.filter(
|
||||||
|
(s) => !exceptSessionId || s.id !== exceptSessionId,
|
||||||
|
).length;
|
||||||
|
|
||||||
// Add all tokens to blacklist (except the excepted one)
|
// Delete sessions from database
|
||||||
for (const session of userSessions) {
|
// JWTs will be invalidated because verifyJWTToken checks for session existence
|
||||||
if (!exceptSessionId || session.id !== exceptSessionId) {
|
|
||||||
this.invalidatedTokens.add(session.jwtToken);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete sessions instead of marking as revoked
|
|
||||||
if (exceptSessionId) {
|
if (exceptSessionId) {
|
||||||
await db
|
await db
|
||||||
.delete(sessions)
|
.delete(sessions)
|
||||||
@@ -327,10 +374,6 @@ class AuthManager {
|
|||||||
await db.delete(sessions).where(eq(sessions.userId, userId));
|
await db.delete(sessions).where(eq(sessions.userId, userId));
|
||||||
}
|
}
|
||||||
|
|
||||||
const deletedCount = userSessions.filter(
|
|
||||||
(s) => !exceptSessionId || s.id !== exceptSessionId,
|
|
||||||
).length;
|
|
||||||
|
|
||||||
databaseLogger.info("User sessions deleted", {
|
databaseLogger.info("User sessions deleted", {
|
||||||
operation: "user_sessions_delete",
|
operation: "user_sessions_delete",
|
||||||
userId,
|
userId,
|
||||||
@@ -350,30 +393,28 @@ class AuthManager {
|
|||||||
|
|
||||||
async cleanupExpiredSessions(): Promise<number> {
|
async cleanupExpiredSessions(): Promise<number> {
|
||||||
try {
|
try {
|
||||||
// Get expired sessions to blacklist their tokens
|
// Get expired sessions count
|
||||||
const expiredSessions = await db
|
const expiredSessions = await db
|
||||||
.select()
|
.select()
|
||||||
.from(sessions)
|
.from(sessions)
|
||||||
.where(sql`${sessions.expiresAt} < datetime('now')`);
|
.where(sql`${sessions.expiresAt} < datetime('now')`);
|
||||||
|
|
||||||
// Add expired tokens to blacklist
|
const expiredCount = expiredSessions.length;
|
||||||
for (const session of expiredSessions) {
|
|
||||||
this.invalidatedTokens.add(session.jwtToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete expired sessions
|
// Delete expired sessions
|
||||||
|
// JWTs will be invalidated because verifyJWTToken checks for session existence
|
||||||
await db
|
await db
|
||||||
.delete(sessions)
|
.delete(sessions)
|
||||||
.where(sql`${sessions.expiresAt} < datetime('now')`);
|
.where(sql`${sessions.expiresAt} < datetime('now')`);
|
||||||
|
|
||||||
if (expiredSessions.length > 0) {
|
if (expiredCount > 0) {
|
||||||
databaseLogger.info("Expired sessions cleaned up", {
|
databaseLogger.info("Expired sessions cleaned up", {
|
||||||
operation: "sessions_cleanup",
|
operation: "sessions_cleanup",
|
||||||
count: expiredSessions.length,
|
count: expiredCount,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return expiredSessions.length;
|
return expiredCount;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
databaseLogger.error("Failed to cleanup expired sessions", error, {
|
databaseLogger.error("Failed to cleanup expired sessions", error, {
|
||||||
operation: "sessions_cleanup_failed",
|
operation: "sessions_cleanup_failed",
|
||||||
@@ -465,8 +506,20 @@ class AuthManager {
|
|||||||
|
|
||||||
// Session exists, no need to check isRevoked since we delete sessions instead
|
// Session exists, no need to check isRevoked since we delete sessions instead
|
||||||
|
|
||||||
// Check if session has expired
|
// Check if session has expired by comparing timestamps
|
||||||
if (new Date(session.expiresAt) < new Date()) {
|
const sessionExpiryTime = new Date(session.expiresAt).getTime();
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const isExpired = sessionExpiryTime < currentTime;
|
||||||
|
|
||||||
|
if (isExpired) {
|
||||||
|
databaseLogger.warn("Session has expired", {
|
||||||
|
operation: "session_expired",
|
||||||
|
sessionId: payload.sessionId,
|
||||||
|
expiresAt: session.expiresAt,
|
||||||
|
expiryTime: sessionExpiryTime,
|
||||||
|
currentTime: currentTime,
|
||||||
|
difference: currentTime - sessionExpiryTime,
|
||||||
|
});
|
||||||
return res.status(401).json({
|
return res.status(401).json({
|
||||||
error: "Session has expired",
|
error: "Session has expired",
|
||||||
code: "SESSION_EXPIRED",
|
code: "SESSION_EXPIRED",
|
||||||
@@ -508,15 +561,14 @@ class AuthManager {
|
|||||||
return res.status(401).json({ error: "Authentication required" });
|
return res.status(401).json({ error: "Authentication required" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Try to get data key if available (may be null after restart)
|
||||||
const dataKey = this.userCrypto.getUserDataKey(userId);
|
const dataKey = this.userCrypto.getUserDataKey(userId);
|
||||||
if (!dataKey) {
|
authReq.dataKey = dataKey || undefined;
|
||||||
return res.status(401).json({
|
|
||||||
error: "Session expired - please log in again",
|
// Note: Data key will be null after backend restart until user performs
|
||||||
code: "SESSION_EXPIRED",
|
// an operation that requires decryption. This is expected behavior.
|
||||||
});
|
// Individual routes that need encryption should check dataKey explicitly.
|
||||||
}
|
|
||||||
|
|
||||||
authReq.dataKey = dataKey;
|
|
||||||
next();
|
next();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -580,8 +632,44 @@ class AuthManager {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
logoutUser(userId: string): void {
|
async logoutUser(userId: string, sessionId?: string): Promise<void> {
|
||||||
this.userCrypto.logoutUser(userId);
|
this.userCrypto.logoutUser(userId);
|
||||||
|
|
||||||
|
// Delete the specific session from database if sessionId provided
|
||||||
|
if (sessionId) {
|
||||||
|
try {
|
||||||
|
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
||||||
|
databaseLogger.info("Session deleted on logout", {
|
||||||
|
operation: "session_delete_logout",
|
||||||
|
userId,
|
||||||
|
sessionId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
databaseLogger.error("Failed to delete session on logout", error, {
|
||||||
|
operation: "session_delete_logout_failed",
|
||||||
|
userId,
|
||||||
|
sessionId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no sessionId, delete all sessions for this user
|
||||||
|
try {
|
||||||
|
await db.delete(sessions).where(eq(sessions.userId, userId));
|
||||||
|
databaseLogger.info("All user sessions deleted on logout", {
|
||||||
|
operation: "sessions_delete_logout",
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
databaseLogger.error(
|
||||||
|
"Failed to delete user sessions on logout",
|
||||||
|
error,
|
||||||
|
{
|
||||||
|
operation: "sessions_delete_logout_failed",
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getUserDataKey(userId: string): Buffer | null {
|
getUserDataKey(userId: string): Buffer | null {
|
||||||
|
|||||||
@@ -91,18 +91,6 @@ export function Auth({
|
|||||||
setInternalLoggedIn(loggedIn);
|
setInternalLoggedIn(loggedIn);
|
||||||
}, [loggedIn]);
|
}, [loggedIn]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const clearJWTOnLoad = async () => {
|
|
||||||
try {
|
|
||||||
await logoutUser();
|
|
||||||
} catch {
|
|
||||||
// Ignore logout errors on initial load
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
clearJWTOnLoad();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getRegistrationAllowed().then((res) => {
|
getRegistrationAllowed().then((res) => {
|
||||||
setRegistrationAllowed(res.allowed);
|
setRegistrationAllowed(res.allowed);
|
||||||
|
|||||||
@@ -34,13 +34,6 @@ function AppContent() {
|
|||||||
setIsAuthenticated(true);
|
setIsAuthenticated(true);
|
||||||
setIsAdmin(!!meRes.is_admin);
|
setIsAdmin(!!meRes.is_admin);
|
||||||
setUsername(meRes.username || null);
|
setUsername(meRes.username || null);
|
||||||
|
|
||||||
if (!meRes.data_unlocked) {
|
|
||||||
console.warn("User data is locked - re-authentication required");
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
setIsAdmin(false);
|
|
||||||
setUsername(null);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
|
|||||||
@@ -133,18 +133,6 @@ export function Auth({
|
|||||||
setInternalLoggedIn(loggedIn);
|
setInternalLoggedIn(loggedIn);
|
||||||
}, [loggedIn]);
|
}, [loggedIn]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const clearJWTOnLoad = async () => {
|
|
||||||
try {
|
|
||||||
await logoutUser();
|
|
||||||
} catch (error) {
|
|
||||||
console.log("JWT cleanup on HomepageAuth load:", error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
clearJWTOnLoad();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getRegistrationAllowed().then((res) => {
|
getRegistrationAllowed().then((res) => {
|
||||||
setRegistrationAllowed(res.allowed);
|
setRegistrationAllowed(res.allowed);
|
||||||
|
|||||||
@@ -31,13 +31,6 @@ const AppContent: FC = () => {
|
|||||||
setIsAuthenticated(true);
|
setIsAuthenticated(true);
|
||||||
setIsAdmin(!!meRes.is_admin);
|
setIsAdmin(!!meRes.is_admin);
|
||||||
setUsername(meRes.username || null);
|
setUsername(meRes.username || null);
|
||||||
|
|
||||||
if (!meRes.data_unlocked) {
|
|
||||||
console.warn("User data is locked - re-authentication required");
|
|
||||||
setIsAuthenticated(false);
|
|
||||||
setIsAdmin(false);
|
|
||||||
setUsername(null);
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.catch((err) => {
|
||||||
setIsAuthenticated(false);
|
setIsAuthenticated(false);
|
||||||
|
|||||||
@@ -310,6 +310,10 @@ function createApiInstance(
|
|||||||
if (isSessionExpired && typeof window !== "undefined") {
|
if (isSessionExpired && typeof window !== "undefined") {
|
||||||
console.warn("Session expired - please log in again");
|
console.warn("Session expired - please log in again");
|
||||||
|
|
||||||
|
// Clear the JWT cookie to prevent reload loop
|
||||||
|
document.cookie =
|
||||||
|
"jwt=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
|
||||||
|
|
||||||
import("sonner").then(({ toast }) => {
|
import("sonner").then(({ toast }) => {
|
||||||
toast.warning("Session expired - please log in again");
|
toast.warning("Session expired - please log in again");
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user