Spaces:
Sleeping
Sleeping
Commit ·
3ad0250
1
Parent(s): a314cc1
Critical Fix: Resolve 401 'Subject not found' loop, stabilize HF Space persistence, and improve session recovery
Browse files- backend/controllers/users.js +5 -0
- backend/middleware/auth.js +3 -1
- backend/models/Announcement.js +16 -8
- backend/models/Message.js +22 -16
- backend/models/User.js +18 -26
- backend/public/chat.html +7 -0
- backend/services/persistenceService.js +7 -1
backend/controllers/users.js
CHANGED
|
@@ -51,6 +51,11 @@ exports.updatePreferences = asyncHandler(async (req, res, next) => {
|
|
| 51 |
exports.getProfile = asyncHandler(async (req, res, next) => {
|
| 52 |
const user = await User.findById(req.user.id);
|
| 53 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
const profile = {
|
| 55 |
name: user.name,
|
| 56 |
role: user.role,
|
|
|
|
| 51 |
exports.getProfile = asyncHandler(async (req, res, next) => {
|
| 52 |
const user = await User.findById(req.user.id);
|
| 53 |
|
| 54 |
+
if (!user) {
|
| 55 |
+
res.clearCookie('token');
|
| 56 |
+
return next(new ErrorResponse('Not authorized', 401));
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
const profile = {
|
| 60 |
name: user.name,
|
| 61 |
role: user.role,
|
backend/middleware/auth.js
CHANGED
|
@@ -25,11 +25,13 @@ exports.protect = asyncHandler(async (req, res, next) => {
|
|
| 25 |
req.user = await User.findById(decoded.id);
|
| 26 |
|
| 27 |
if (!req.user) {
|
| 28 |
-
|
|
|
|
| 29 |
}
|
| 30 |
next();
|
| 31 |
} catch (err) {
|
| 32 |
console.log(`AUTH_FAILURE: Token verification failed (${err.message})`);
|
|
|
|
| 33 |
return next(new ErrorResponse('Not authorized: Link signature invalid', 401));
|
| 34 |
}
|
| 35 |
});
|
|
|
|
| 25 |
req.user = await User.findById(decoded.id);
|
| 26 |
|
| 27 |
if (!req.user) {
|
| 28 |
+
res.clearCookie('token');
|
| 29 |
+
return next(new ErrorResponse('Not authorized: Subject not found in archive. Please log in again.', 401));
|
| 30 |
}
|
| 31 |
next();
|
| 32 |
} catch (err) {
|
| 33 |
console.log(`AUTH_FAILURE: Token verification failed (${err.message})`);
|
| 34 |
+
res.clearCookie('token');
|
| 35 |
return next(new ErrorResponse('Not authorized: Link signature invalid', 401));
|
| 36 |
}
|
| 37 |
});
|
backend/models/Announcement.js
CHANGED
|
@@ -1,14 +1,22 @@
|
|
| 1 |
const { db } = require('../db');
|
| 2 |
|
| 3 |
class Announcement {
|
| 4 |
-
static
|
| 5 |
return {
|
| 6 |
-
sort: (sortQuery)
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 12 |
};
|
| 13 |
}
|
| 14 |
|
|
@@ -23,4 +31,4 @@ class Announcement {
|
|
| 23 |
}
|
| 24 |
}
|
| 25 |
|
| 26 |
-
module.exports = Announcement;
|
|
|
|
| 1 |
const { db } = require('../db');
|
| 2 |
|
| 3 |
class Announcement {
|
| 4 |
+
static findOne(query) {
|
| 5 |
return {
|
| 6 |
+
sort: function(sortQuery) {
|
| 7 |
+
this._sort = sortQuery;
|
| 8 |
+
return this;
|
| 9 |
+
},
|
| 10 |
+
then: function(resolve, reject) {
|
| 11 |
+
return new Promise((res, rej) => {
|
| 12 |
+
let q = db.announcements.find(query);
|
| 13 |
+
if (this._sort) q = q.sort(this._sort);
|
| 14 |
+
q.limit(1).exec((err, docs) => {
|
| 15 |
+
if (err) rej(err);
|
| 16 |
+
else res(docs[0] || null);
|
| 17 |
+
});
|
| 18 |
+
}).then(resolve, reject);
|
| 19 |
+
}
|
| 20 |
};
|
| 21 |
}
|
| 22 |
|
|
|
|
| 31 |
}
|
| 32 |
}
|
| 33 |
|
| 34 |
+
module.exports = Announcement;
|
backend/models/Message.js
CHANGED
|
@@ -1,23 +1,29 @@
|
|
| 1 |
const { db } = require('../db');
|
| 2 |
|
| 3 |
class Message {
|
| 4 |
-
static
|
| 5 |
-
|
| 6 |
-
sort: (sortQuery)
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
});
|
| 12 |
-
}),
|
| 13 |
-
|
| 14 |
-
db.messages.find(query).sort(sortQuery).exec((err, docs) => {
|
| 15 |
-
if (err) reject(err);
|
| 16 |
-
else resolve(docs);
|
| 17 |
-
});
|
| 18 |
-
})
|
| 19 |
-
})
|
| 20 |
};
|
|
|
|
| 21 |
}
|
| 22 |
|
| 23 |
static async create(msgData) {
|
|
@@ -40,4 +46,4 @@ class Message {
|
|
| 40 |
}
|
| 41 |
}
|
| 42 |
|
| 43 |
-
module.exports = Message;
|
|
|
|
| 1 |
const { db } = require('../db');
|
| 2 |
|
| 3 |
class Message {
|
| 4 |
+
static find(query) {
|
| 5 |
+
const chain = {
|
| 6 |
+
sort: function(sortQuery) {
|
| 7 |
+
this._sort = sortQuery;
|
| 8 |
+
return this;
|
| 9 |
+
},
|
| 10 |
+
limit: function(limitNum) {
|
| 11 |
+
this._limit = limitNum;
|
| 12 |
+
return this;
|
| 13 |
+
},
|
| 14 |
+
then: function(resolve, reject) {
|
| 15 |
+
return new Promise((res, rej) => {
|
| 16 |
+
let q = db.messages.find(query);
|
| 17 |
+
if (this._sort) q = q.sort(this._sort);
|
| 18 |
+
if (this._limit) q = q.limit(this._limit);
|
| 19 |
+
q.exec((err, docs) => {
|
| 20 |
+
if (err) rej(err);
|
| 21 |
+
else res(docs);
|
| 22 |
});
|
| 23 |
+
}).then(resolve, reject);
|
| 24 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
};
|
| 26 |
+
return chain;
|
| 27 |
}
|
| 28 |
|
| 29 |
static async create(msgData) {
|
|
|
|
| 46 |
}
|
| 47 |
}
|
| 48 |
|
| 49 |
+
module.exports = Message;
|
backend/models/User.js
CHANGED
|
@@ -8,13 +8,24 @@ class User {
|
|
| 8 |
Object.assign(this, userData);
|
| 9 |
}
|
| 10 |
|
| 11 |
-
static
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
}
|
| 19 |
|
| 20 |
static async findById(id) {
|
|
@@ -35,7 +46,6 @@ class User {
|
|
| 35 |
userData.createdAt = userData.createdAt || new Date();
|
| 36 |
userData.role = userData.role || 'user';
|
| 37 |
|
| 38 |
-
// Ownership check
|
| 39 |
const owners = ['johanvoncd7@gmail.com', 'codexai@mightysmp.online'];
|
| 40 |
if (owners.includes(userData.email)) {
|
| 41 |
userData.role = 'owner';
|
|
@@ -86,24 +96,6 @@ class User {
|
|
| 86 |
});
|
| 87 |
});
|
| 88 |
}
|
| 89 |
-
|
| 90 |
-
// Chainable .select implementation for compatibility
|
| 91 |
-
static findOne(query) {
|
| 92 |
-
return {
|
| 93 |
-
select: (fields) => new Promise((resolve, reject) => {
|
| 94 |
-
db.users.findOne(query, (err, doc) => {
|
| 95 |
-
if (err) reject(err);
|
| 96 |
-
else resolve(doc ? new User(doc) : null);
|
| 97 |
-
});
|
| 98 |
-
}),
|
| 99 |
-
then: (resolve, reject) => {
|
| 100 |
-
db.users.findOne(query, (err, doc) => {
|
| 101 |
-
if (err) { if (reject) reject(err); }
|
| 102 |
-
else resolve(doc ? new User(doc) : null);
|
| 103 |
-
});
|
| 104 |
-
}
|
| 105 |
-
};
|
| 106 |
-
}
|
| 107 |
}
|
| 108 |
|
| 109 |
module.exports = User;
|
|
|
|
| 8 |
Object.assign(this, userData);
|
| 9 |
}
|
| 10 |
|
| 11 |
+
static findOne(query) {
|
| 12 |
+
const chain = {
|
| 13 |
+
select: function(fields) {
|
| 14 |
+
return this;
|
| 15 |
+
},
|
| 16 |
+
then: function(resolve, reject) {
|
| 17 |
+
return new Promise((res, rej) => {
|
| 18 |
+
db.users.findOne(query, (err, doc) => {
|
| 19 |
+
if (err) rej(err);
|
| 20 |
+
else res(doc ? new User(doc) : null);
|
| 21 |
+
});
|
| 22 |
+
}).then(resolve, reject);
|
| 23 |
+
},
|
| 24 |
+
catch: function(reject) {
|
| 25 |
+
return this.then(null, reject);
|
| 26 |
+
}
|
| 27 |
+
};
|
| 28 |
+
return chain;
|
| 29 |
}
|
| 30 |
|
| 31 |
static async findById(id) {
|
|
|
|
| 46 |
userData.createdAt = userData.createdAt || new Date();
|
| 47 |
userData.role = userData.role || 'user';
|
| 48 |
|
|
|
|
| 49 |
const owners = ['johanvoncd7@gmail.com', 'codexai@mightysmp.online'];
|
| 50 |
if (owners.includes(userData.email)) {
|
| 51 |
userData.role = 'owner';
|
|
|
|
| 96 |
});
|
| 97 |
});
|
| 98 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
}
|
| 100 |
|
| 101 |
module.exports = User;
|
backend/public/chat.html
CHANGED
|
@@ -307,6 +307,13 @@
|
|
| 307 |
input.value = ''; input.style.height = 'auto';
|
| 308 |
try {
|
| 309 |
const res = await fetch(`${API_BASE}/api/ai/chat`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: fd });
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 310 |
const reader = res.body.getReader(); const decoder = new TextDecoder();
|
| 311 |
let aiNode = appendMessage('ai', '', activeModel); let fullText = "";
|
| 312 |
while (true) {
|
|
|
|
| 307 |
input.value = ''; input.style.height = 'auto';
|
| 308 |
try {
|
| 309 |
const res = await fetch(`${API_BASE}/api/ai/chat`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: fd });
|
| 310 |
+
|
| 311 |
+
if (res.status === 401) {
|
| 312 |
+
localStorage.removeItem('token');
|
| 313 |
+
window.location.href = '/auth?error=SESSION_EXPIRED';
|
| 314 |
+
return;
|
| 315 |
+
}
|
| 316 |
+
|
| 317 |
const reader = res.body.getReader(); const decoder = new TextDecoder();
|
| 318 |
let aiNode = appendMessage('ai', '', activeModel); let fullText = "";
|
| 319 |
while (true) {
|
backend/services/persistenceService.js
CHANGED
|
@@ -26,7 +26,13 @@ exports.syncToCloud = async () => {
|
|
| 26 |
try {
|
| 27 |
execSync(`git add backend/data/*.db`, { cwd: rootDir });
|
| 28 |
execSync(`git ${gitIdent} commit -m "Neural Archive Sync: [$(date)]"`, { cwd: rootDir });
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
console.log('[PERSISTENCE] Cloud Sync: SUCCESS ✅');
|
| 31 |
} catch (gitErr) {
|
| 32 |
if (gitErr.message.includes('nothing to commit')) {
|
|
|
|
| 26 |
try {
|
| 27 |
execSync(`git add backend/data/*.db`, { cwd: rootDir });
|
| 28 |
execSync(`git ${gitIdent} commit -m "Neural Archive Sync: [$(date)]"`, { cwd: rootDir });
|
| 29 |
+
|
| 30 |
+
if (process.env.SPACE_ID || process.env.HF_TOKEN) {
|
| 31 |
+
console.log('[PERSISTENCE] HF_SPACE detected. Skipping remote push to prevent restart loop.');
|
| 32 |
+
} else {
|
| 33 |
+
execSync(`git push "${remoteUrl}" main`, { cwd: rootDir });
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
console.log('[PERSISTENCE] Cloud Sync: SUCCESS ✅');
|
| 37 |
} catch (gitErr) {
|
| 38 |
if (gitErr.message.includes('nothing to commit')) {
|