mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-10-28 06:05:24 +00:00
fix: reject expired sessions on sockets, api key desktop links and 192.0.0.0/24
This commit is contained in:
1 parent
45908b5df3
commit
af09aa1028
5 files changed
+25
-2
No files matched your search
@@ -87,12 +87,17 @@ router.get("/v2/info", async (_req: Request, res: Response) => {
|
|||||||
* description: The desktop session token and the account it belongs to.
|
* description: The desktop session token and the account it belongs to.
|
||||||
* 401:
|
* 401:
|
||||||
* description: Not signed in.
|
* description: Not signed in.
|
||||||
|
* 403:
|
||||||
|
* description: API keys cannot link a desktop.
|
||||||
*/
|
*/
|
||||||
router.post(
|
router.post(
|
||||||
"/v2/link",
|
"/v2/link",
|
||||||
authenticateJWT,
|
authenticateJWT,
|
||||||
async (req: Request, res: Response) => {
|
async (req: Request, res: Response) => {
|
||||||
const userId = userOf(req);
|
const userId = userOf(req);
|
||||||
|
if ((req as AuthenticatedRequest).apiKeyId) {
|
||||||
|
return res.status(403).json({ error: "API keys cannot link a desktop" });
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const user = await createCurrentUserRepository().findById(userId);
|
const user = await createCurrentUserRepository().findById(userId);
|
||||||
if (!user) return res.status(401).json({ error: "User not found" });
|
if (!user) return res.status(401).json({ error: "User not found" });
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ vi.mock("../../database/db/index.js", () => ({
|
|||||||
vi.mock("../../database/repositories/factory.js", () => ({
|
vi.mock("../../database/repositories/factory.js", () => ({
|
||||||
createCurrentSettingsRepository: () => ({ get: async () => null }),
|
createCurrentSettingsRepository: () => ({ get: async () => null }),
|
||||||
createCurrentSessionRepository: () => ({
|
createCurrentSessionRepository: () => ({
|
||||||
findById: async (id: string) => ({ id }),
|
findById: async (id: string) =>
|
||||||
|
id === "expired"
|
||||||
|
? { id, expiresAt: new Date(Date.now() - 1000).toISOString() }
|
||||||
|
: { id },
|
||||||
}),
|
}),
|
||||||
createCurrentUserRepository: () => ({}),
|
createCurrentUserRepository: () => ({}),
|
||||||
createCurrentApiKeyRepository: () => ({}),
|
createCurrentApiKeyRepository: () => ({}),
|
||||||
@@ -151,6 +154,15 @@ describe("AuthManager token handling", () => {
|
|||||||
expect(await authManager.verifyJWTToken(token)).toBeNull();
|
expect(await authManager.verifyJWTToken(token)).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("rejects a token whose session has expired", async () => {
|
||||||
|
const token = jwt.sign(
|
||||||
|
{ userId: "user-1", sessionId: "expired" },
|
||||||
|
jwtSecret,
|
||||||
|
{ expiresIn: "1h" },
|
||||||
|
);
|
||||||
|
expect(await authManager.verifyJWTToken(token)).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
it("still accepts the sessionless pending second-factor token", async () => {
|
it("still accepts the sessionless pending second-factor token", async () => {
|
||||||
const token = jwt.sign({ userId: "user-1", pendingTOTP: true }, jwtSecret, {
|
const token = jwt.sign({ userId: "user-1", pendingTOTP: true }, jwtSecret, {
|
||||||
expiresIn: "10m",
|
expiresIn: "10m",
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ describe("isBlockedAddress", () => {
|
|||||||
expect(isBlockedAddress("192.168.1.1")).toBe(true);
|
expect(isBlockedAddress("192.168.1.1")).toBe(true);
|
||||||
expect(isBlockedAddress("127.0.0.1")).toBe(true);
|
expect(isBlockedAddress("127.0.0.1")).toBe(true);
|
||||||
expect(isBlockedAddress("169.254.1.1")).toBe(true);
|
expect(isBlockedAddress("169.254.1.1")).toBe(true);
|
||||||
|
expect(isBlockedAddress("192.0.0.170")).toBe(true);
|
||||||
expect(isBlockedAddress("100.64.0.1")).toBe(true);
|
expect(isBlockedAddress("100.64.0.1")).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -414,6 +414,10 @@ class AuthManager {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (new Date(sessionRecord.expiresAt).getTime() < Date.now()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
await this.migrateDataKeyFromPayload(payload);
|
await this.migrateDataKeyFromPayload(payload);
|
||||||
} catch (dbError) {
|
} catch (dbError) {
|
||||||
databaseLogger.error(
|
databaseLogger.error(
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ const blockedIpv4Ranges = [
|
|||||||
["127.0.0.0", 8],
|
["127.0.0.0", 8],
|
||||||
["169.254.0.0", 16], // link-local
|
["169.254.0.0", 16], // link-local
|
||||||
["172.16.0.0", 12],
|
["172.16.0.0", 12],
|
||||||
|
["192.0.0.0", 24], // IETF protocol assignments
|
||||||
["192.168.0.0", 16],
|
["192.168.0.0", 16],
|
||||||
["198.18.0.0", 15], // benchmarking
|
["198.18.0.0", 15], // benchmarking
|
||||||
["224.0.0.0", 4], // multicast
|
["224.0.0.0", 4], // multicast
|
||||||
@@ -70,7 +71,7 @@ export function isBlockedAddress(address: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extracted so the blocklist decision can be tested directly against a
|
// Extracted so the blocklist decision can be tested directly against a
|
||||||
// fake DNS resolver, instead of only through a real fetch()/Agent call —
|
// fake DNS resolver, instead of only through a real fetch()/Agent call,
|
||||||
// the actual bug here lived entirely in this callback, several layers
|
// the actual bug here lived entirely in this callback, several layers
|
||||||
// below where undici's own "fetch failed" wrapping would otherwise hide it.
|
// below where undici's own "fetch failed" wrapping would otherwise hide it.
|
||||||
export function createDnsLookupHook(
|
export function createDnsLookupHook(
|
||||||
|
|||||||
Reference in new issue
Block a user