summary refs log tree commit diff
diff options
context:
space:
mode:
authorRory& <root@rory.gay>2025-12-16 06:58:45 +0100
committerRory& <root@rory.gay>2025-12-16 07:00:41 +0100
commite98da0ac65de1942a8fd6181e2a7309ec5324b2a (patch)
treed6a55f8b05de2181dcf7c46dd93e433c80431d46
parentMore maybe un-blowing-up (diff)
downloadserver-ts-e98da0ac65de1942a8fd6181e2a7309ec5324b2a.tar.xz
Upate session info
-rw-r--r--assets/openapi.json290
-rw-r--r--assets/schemas.json302
-rw-r--r--src/util/util/Token.ts36
3 files changed, 479 insertions, 149 deletions
diff --git a/assets/openapi.json b/assets/openapi.json

index 0ac9295d..6a5298b8 100644 --- a/assets/openapi.json +++ b/assets/openapi.json
@@ -171,6 +171,200 @@ "token_type" ] }, + "KeySchema": { + "type": "object", + "properties": { + "_type": { + "type": [ + "string", + "integer", + "object" + ] + }, + "_output": { + "type": [ + "string", + "integer", + "object" + ] + }, + "_input": {}, + "_def": {}, + "description": { + "type": "string" + }, + "~standard": { + "$ref": "#/components/schemas/StandardSchemaV1.Props<any,string|number|symbol>" + }, + "spa": { + "description": "Alias of safeParseAsync", + "type": "object", + "additionalProperties": false + } + }, + "required": [ + "_def", + "_input", + "_output", + "_type", + "spa", + "~standard" + ] + }, + "RouteResponse": { + "type": "object", + "properties": { + "status": { + "type": "integer" + }, + "body": { + "type": "string", + "pattern": "^.*Response$" + }, + "headers": { + "$ref": "#/components/schemas/Record<string,string>" + } + } + }, + "hcaptchaResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "challenge_ts": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "credit": { + "type": "boolean" + }, + "error-codes": { + "type": "array", + "items": { + "type": "string" + } + }, + "score": { + "type": "integer" + }, + "score_reason": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "challenge_ts", + "credit", + "error-codes", + "hostname", + "score", + "score_reason", + "success" + ] + }, + "recaptchaResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "score": { + "type": "integer" + }, + "action": { + "type": "string" + }, + "challenge_ts": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "error-codes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "required": [ + "action", + "challenge_ts", + "hostname", + "score", + "success" + ] + }, + "AbuseIpDbBlacklistResponse": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "generatedAt": { + "type": "string", + "default": "2020-09-24T19:54:11+00:00" + } + }, + "additionalProperties": false, + "required": [ + "generatedAt" + ] + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ipAddress": { + "type": "string", + "default": "5.188.10.179" + }, + "abuseConfidenceScore": { + "type": "integer", + "default": 100 + }, + "lastReportedAt": { + "type": "string", + "default": "2020-09-24T19:17:02+00:00" + } + }, + "additionalProperties": false, + "required": [ + "abuseConfidenceScore", + "ipAddress", + "lastReportedAt" + ] + }, + "default": [ + { + "ipAddress": "5.188.10.179", + "abuseConfidenceScore": 100, + "lastReportedAt": "2020-09-24T19:17:02+00:00" + }, + { + "ipAddress": "185.222.209.14", + "abuseConfidenceScore": 100, + "lastReportedAt": "2020-09-24T19:17:02+00:00" + }, + { + "ipAddress": "191.96.249.183", + "abuseConfidenceScore": 100, + "lastReportedAt": "2020-09-24T19:17:01+00:00" + } + ] + } + }, + "required": [ + "data", + "meta" + ] + }, "StoredEmailResponse": { "type": "object", "properties": { @@ -485,71 +679,6 @@ "message" ] }, - "AbuseIpDbBlacklistResponse": { - "type": "object", - "properties": { - "meta": { - "type": "object", - "properties": { - "generatedAt": { - "type": "string", - "default": "2020-09-24T19:54:11+00:00" - } - }, - "additionalProperties": false, - "required": [ - "generatedAt" - ] - }, - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "ipAddress": { - "type": "string", - "default": "5.188.10.179" - }, - "abuseConfidenceScore": { - "type": "integer", - "default": 100 - }, - "lastReportedAt": { - "type": "string", - "default": "2020-09-24T19:17:02+00:00" - } - }, - "additionalProperties": false, - "required": [ - "abuseConfidenceScore", - "ipAddress", - "lastReportedAt" - ] - }, - "default": [ - { - "ipAddress": "5.188.10.179", - "abuseConfidenceScore": 100, - "lastReportedAt": "2020-09-24T19:17:02+00:00" - }, - { - "ipAddress": "185.222.209.14", - "abuseConfidenceScore": 100, - "lastReportedAt": "2020-09-24T19:17:02+00:00" - }, - { - "ipAddress": "191.96.249.183", - "abuseConfidenceScore": 100, - "lastReportedAt": "2020-09-24T19:17:01+00:00" - } - ] - } - }, - "required": [ - "data", - "meta" - ] - }, "InteractionSchema": { "type": "object", "properties": { @@ -7805,15 +7934,15 @@ "Session": { "type": "object", "properties": { + "session_id": { + "type": "string" + }, "user_id": { "type": "string" }, "user": { "$ref": "#/components/schemas/User" }, - "session_id": { - "type": "string" - }, "activities": { "type": "array", "items": { @@ -7831,11 +7960,15 @@ }, "version": { "type": "integer" + }, + "location": { + "type": "string" } }, "additionalProperties": false, "required": [ "client", + "location", "os", "version" ] @@ -7846,7 +7979,21 @@ "status": { "$ref": "#/components/schemas/Status" }, - "id": { + "is_admin_session": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "last_seen": { + "type": "string", + "format": "date-time" + }, + "last_seen_ip": { + "type": "string" + }, + "last_seen_location": { "type": "string" } }, @@ -7854,7 +8001,10 @@ "activities", "client_info", "client_status", - "id", + "created_at", + "is_admin_session", + "last_seen", + "last_seen_ip", "session_id", "status", "user", diff --git a/assets/schemas.json b/assets/schemas.json
index 3c1ea8b8..eed813e0 100644 --- a/assets/schemas.json +++ b/assets/schemas.json
@@ -152,6 +152,210 @@ ], "$schema": "http://json-schema.org/draft-07/schema#" }, + "KeySchema": { + "type": "object", + "properties": { + "_type": { + "type": [ + "string", + "integer", + "object" + ] + }, + "_output": { + "type": [ + "string", + "integer", + "object" + ] + }, + "_input": {}, + "_def": {}, + "description": { + "type": "string" + }, + "~standard": { + "$ref": "#/definitions/StandardSchemaV1.Props<any,string|number|symbol>" + }, + "spa": { + "description": "Alias of safeParseAsync", + "type": "object", + "additionalProperties": false + } + }, + "additionalProperties": false, + "required": [ + "_def", + "_input", + "_output", + "_type", + "spa", + "~standard" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "RouteResponse": { + "type": "object", + "properties": { + "status": { + "type": "integer" + }, + "body": { + "type": "string", + "pattern": "^.*Response$" + }, + "headers": { + "$ref": "#/definitions/Record<string,string>" + } + }, + "additionalProperties": false, + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "hcaptchaResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "challenge_ts": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "credit": { + "type": "boolean" + }, + "error-codes": { + "type": "array", + "items": { + "type": "string" + } + }, + "score": { + "type": "integer" + }, + "score_reason": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "challenge_ts", + "credit", + "error-codes", + "hostname", + "score", + "score_reason", + "success" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "recaptchaResponse": { + "type": "object", + "properties": { + "success": { + "type": "boolean" + }, + "score": { + "type": "integer" + }, + "action": { + "type": "string" + }, + "challenge_ts": { + "type": "string" + }, + "hostname": { + "type": "string" + }, + "error-codes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "action", + "challenge_ts", + "hostname", + "score", + "success" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, + "AbuseIpDbBlacklistResponse": { + "type": "object", + "properties": { + "meta": { + "type": "object", + "properties": { + "generatedAt": { + "type": "string", + "default": "2020-09-24T19:54:11+00:00" + } + }, + "additionalProperties": false, + "required": [ + "generatedAt" + ] + }, + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "ipAddress": { + "type": "string", + "default": "5.188.10.179" + }, + "abuseConfidenceScore": { + "type": "integer", + "default": 100 + }, + "lastReportedAt": { + "type": "string", + "default": "2020-09-24T19:17:02+00:00" + } + }, + "additionalProperties": false, + "required": [ + "abuseConfidenceScore", + "ipAddress", + "lastReportedAt" + ] + }, + "default": [ + { + "ipAddress": "5.188.10.179", + "abuseConfidenceScore": 100, + "lastReportedAt": "2020-09-24T19:17:02+00:00" + }, + { + "ipAddress": "185.222.209.14", + "abuseConfidenceScore": 100, + "lastReportedAt": "2020-09-24T19:17:02+00:00" + }, + { + "ipAddress": "191.96.249.183", + "abuseConfidenceScore": 100, + "lastReportedAt": "2020-09-24T19:17:01+00:00" + } + ] + } + }, + "additionalProperties": false, + "required": [ + "data", + "meta" + ], + "$schema": "http://json-schema.org/draft-07/schema#" + }, "StoredEmailResponse": { "type": "object", "properties": { @@ -482,73 +686,6 @@ ], "$schema": "http://json-schema.org/draft-07/schema#" }, - "AbuseIpDbBlacklistResponse": { - "type": "object", - "properties": { - "meta": { - "type": "object", - "properties": { - "generatedAt": { - "type": "string", - "default": "2020-09-24T19:54:11+00:00" - } - }, - "additionalProperties": false, - "required": [ - "generatedAt" - ] - }, - "data": { - "type": "array", - "items": { - "type": "object", - "properties": { - "ipAddress": { - "type": "string", - "default": "5.188.10.179" - }, - "abuseConfidenceScore": { - "type": "integer", - "default": 100 - }, - "lastReportedAt": { - "type": "string", - "default": "2020-09-24T19:17:02+00:00" - } - }, - "additionalProperties": false, - "required": [ - "abuseConfidenceScore", - "ipAddress", - "lastReportedAt" - ] - }, - "default": [ - { - "ipAddress": "5.188.10.179", - "abuseConfidenceScore": 100, - "lastReportedAt": "2020-09-24T19:17:02+00:00" - }, - { - "ipAddress": "185.222.209.14", - "abuseConfidenceScore": 100, - "lastReportedAt": "2020-09-24T19:17:02+00:00" - }, - { - "ipAddress": "191.96.249.183", - "abuseConfidenceScore": 100, - "lastReportedAt": "2020-09-24T19:17:01+00:00" - } - ] - } - }, - "additionalProperties": false, - "required": [ - "data", - "meta" - ], - "$schema": "http://json-schema.org/draft-07/schema#" - }, "InteractionSchema": { "type": "object", "properties": { @@ -8281,15 +8418,15 @@ "Session": { "type": "object", "properties": { + "session_id": { + "type": "string" + }, "user_id": { "type": "string" }, "user": { "$ref": "#/definitions/User" }, - "session_id": { - "type": "string" - }, "activities": { "type": "array", "items": { @@ -8307,11 +8444,15 @@ }, "version": { "type": "integer" + }, + "location": { + "type": "string" } }, "additionalProperties": false, "required": [ "client", + "location", "os", "version" ] @@ -8322,7 +8463,21 @@ "status": { "$ref": "#/definitions/Status" }, - "id": { + "is_admin_session": { + "type": "boolean" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "last_seen": { + "type": "string", + "format": "date-time" + }, + "last_seen_ip": { + "type": "string" + }, + "last_seen_location": { "type": "string" } }, @@ -8331,7 +8486,10 @@ "activities", "client_info", "client_status", - "id", + "created_at", + "is_admin_session", + "last_seen", + "last_seen_ip", "session_id", "status", "user", diff --git a/src/util/util/Token.ts b/src/util/util/Token.ts
index 2e116503..a364fa24 100644 --- a/src/util/util/Token.ts +++ b/src/util/util/Token.ts
@@ -26,6 +26,8 @@ import { existsSync } from "fs"; import { FindManyOptions, FindOptions, FindOptionsRelationByString, FindOptionsSelect, FindOptionsSelectByString, FindOptionsWhere } from "typeorm"; import * as console from "node:console"; import { randomUpperString } from "@spacebar/api"; +import { IpDataClient } from "./networking"; +import { TimeSpan } from "./Timespan"; /// Change history: /// 1 - Initial version with HS256 @@ -63,8 +65,8 @@ export const checkToken = ( ipAddress?: string; fingerprint?: string; }, -): Promise<UserTokenData> => - new Promise((resolve, reject) => { +): Promise<UserTokenData> => { + return new Promise((resolve, reject) => { token = token.replace("Bot ", ""); // there is no bot distinction in sb token = token.replace("Bearer ", ""); // allow bearer tokens @@ -77,17 +79,25 @@ export const checkToken = ( return rejectAndLog(reject, "Invalid Token meow " + err); } - const user = await User.findOne({ - where: { id: decoded.id }, - select: [...(opts?.select || []), "id", "bot", "disabled", "deleted", "rights", "data"], - relations: opts?.relations, - }); + const [user, session] = await Promise.all([ + User.findOne({ + where: { id: decoded.id }, + select: [...(opts?.select || []), "id", "bot", "disabled", "deleted", "rights", "data"], + relations: opts?.relations, + }), + decoded.did ? Session.findOne({ where: { session_id: decoded.did, user_id: decoded.id } }) : undefined, + ]); if (!user) { logAuth("validateUser rejected: User not found"); return rejectAndLog(reject, "User not found"); } + if (decoded.did && !session) { + logAuth("validateUser rejected: Session not found"); + return rejectAndLog(reject, "Invalid Token"); + } + // we need to round it to seconds as it saved as seconds in jwt iat and valid_tokens_since is stored in milliseconds if (decoded.iat * 1000 < new Date(user.data.valid_tokens_since).setSeconds(0, 0)) { logAuth("validateUser rejected: Token not yet valid"); @@ -110,8 +120,19 @@ export const checkToken = ( return rejectAndLog(reject, "Invalid Token"); } + if (session && TimeSpan.fromDates(session.last_seen.getTime(), new Date().getTime()).totalSeconds >= 15) { + session.last_seen = new Date(); + if (opts?.ipAddress && opts?.ipAddress !== session.last_seen_ip) { + session.last_seen_ip = opts.ipAddress; + let ipInfo = await IpDataClient.getIpInfo(opts.ipAddress); + if (ipInfo?.ip) session.last_seen_location = `${ipInfo.emoji_flag} ${ipInfo.postal} ${ipInfo.city}, ${ipInfo.region}, ${ipInfo.country_name}`; + } + await session.save(); + } + const result: UserTokenData = { decoded, + session: session ?? undefined, user, // v1 can be told apart, v2 cant outside of missing device id and version tokenVersion: decoded.ver ?? legacyVersion ?? 2, @@ -136,6 +157,7 @@ export const checkToken = ( }); } else return reject("Invalid token algorithm"); }); +}; export async function generateToken(id: string, isAdminSession: boolean = false) { const iat = Math.floor(Date.now() / 1000);