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);
|