Skip to content

Commit

Permalink
Hacking improvements (#14)
Browse files Browse the repository at this point in the history
* Add more "hacking detected" messages. One will be picked by random
instead of always using the same one.

* Instead of posting the "hacking detected" message immediately after
login, post it after a set period of time based on the hacker's skill level.

* On hacker login API call return the seconds until detection, so that a
progress bar can be shown in the frontend.
  • Loading branch information
nicou authored Dec 17, 2023
1 parent c2a88b9 commit 376b7e4
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 31 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ module.exports = {
'object-shorthand': 1,
'prefer-arrow-callback': 1,
'spaced-comment': 1,
'no-redeclare': 2,
'no-redeclare': [2, { ignoreDeclarationMerge: true }],
'no-throw-literal': 2,
'no-useless-concat': 2,
'no-void': 2,
Expand Down Expand Up @@ -100,5 +100,9 @@ module.exports = {
],
'dot-location': [2, 'property'],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-unused-vars': [2, {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
}],
},
};
15 changes: 9 additions & 6 deletions db/redux/box/index.js → db/redux/box/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ import driftingValueBlobs from './driftingValue';
import buttonboard from './buttonboard';
import reactorWiring from './reactorWiring';

airlock.forEach(saveBlob);
fuseboxes.forEach(saveBlob);
jumpdriveBlobs.forEach(saveBlob);
driftingValueBlobs.forEach(saveBlob);
buttonboard.forEach(saveBlob);
reactorWiring.forEach(saveBlob);
const blobs = [
...airlock,
...fuseboxes,
...jumpdriveBlobs,
...driftingValueBlobs,
...buttonboard,
...reactorWiring,
];
blobs.forEach(saveBlob);
14 changes: 13 additions & 1 deletion db/redux/misc/index.js → db/redux/misc/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { saveBlob } from '../helpers';
import { Stores } from '../../../src/store/types';
import { SkillLevels } from '../../../src/utils/groups';
import { Duration } from '../../../src/utils/time';

const blobs = [
const blobs: unknown[] = [
// Used for the once-per-game Velian minigame thingy
{
type: 'misc',
Expand Down Expand Up @@ -55,6 +58,15 @@ sunt eaque dolor id nisi magni.`
show_20110_tumor: true,
show_20070_alien: false,
},
{
type: 'misc',
id: Stores.HackerDetectionTimes,
detection_times: {
[SkillLevels.Novice]: Duration.minutes(1),
[SkillLevels.Master]: Duration.minutes(2),
[SkillLevels.Expert]: Duration.minutes(5),
}
},
];

blobs.forEach(saveBlob);
16 changes: 15 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
"shelljs": "^0.8.3",
"signale": "^1.4.0",
"socket.io": "^2.1.1",
"socket.io-prometheus": "^0.2.1"
"socket.io-prometheus": "^0.2.1",
"zod": "^3.22.4"
},
"devDependencies": {
"@types/body-parser": "^1.19.2",
Expand Down
13 changes: 9 additions & 4 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dotenv/config';
import { Server } from 'http';
import express from 'express';
import { HttpError } from 'http-errors';
import express, { NextFunction, Request, Response } from 'express';
import bodyParser from 'body-parser';
import socketIo from 'socket.io';
import { logger, loggerMiddleware } from './logger';
Expand Down Expand Up @@ -67,7 +68,7 @@ app.use((req, res, next) => {
});

// Setup routes
app.get('/', (req, res) => res.redirect('/api-docs'));
app.get('/', (req: Request, res: Response) => res.redirect('/api-docs'));
app.use('/fleet', fleet);
app.use('/starmap', starmap);
app.use('/person', person);
Expand Down Expand Up @@ -116,9 +117,13 @@ app.post('/emit/:eventName', (req, res) => {
});

// Error handling middleware
app.use(async (err, req, res, next) => {
app.use(async (err: HttpError, req: Request, res: Response, _next: NextFunction) => {
let status = 500;
if ('statusCode' in err) {
status = err.statusCode;
}
logger.error(err.message);
return res.status(err.status || 500).json({ error: err.message });
return res.status(status).json({ error: err.message });
});

// Setup Socket.IO
Expand Down
48 changes: 32 additions & 16 deletions src/routes/person.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { Router } from 'express';
import { Person, Entry, Group, getFilterableValues, setPersonsVisible } from '../models/person';
import { addShipLogEntry, AuditLogEntry } from '../models/log';
import { Person, Entry, Group, getFilterableValues, setPersonsVisible } from '@/models/person';
import { addShipLogEntry, AuditLogEntry } from '@/models/log';
import { handleAsyncErrors } from './helpers';
import { get, pick, mapKeys, snakeCase } from 'lodash';
import { NotFound, BadRequest } from 'http-errors';
import { logger } from '../logger';
import { logger } from '@/logger';
import { getHackingDetectionTime, getRandomHackingIntrustionDetectionMessage } from '@/utils/hacking';
import { Duration, getRandomNumberBetween } from '@/utils/time';
const router = new Router();

const DEFAULT_PERSON_PAGE = 1;
Expand Down Expand Up @@ -81,20 +83,34 @@ router.get('/groups', handleAsyncErrors(async (req, res) => {
*/
router.get('/:id', handleAsyncErrors(async (req, res) => {
const isLogin = req.query.login === 'true';
const person_id = req.params.id;
const person = await Person.forge({ id: person_id }).fetchWithRelated();
if (isLogin && person) {
const hacker_id = get(req.query, 'hacker_id', null);
if (!hacker_id) {
logger.warn(`Social Hub login to /person/${person_id} with no hacker_id in request`);
}
AuditLogEntry.forge().save({
person_id,
hacker_id,
type: 'HACKER_LOGIN'
});
addShipLogEntry('WARNING', 'Intrusion detection system has detected malicious activity');
const hackerId = req.query.hacker_id;
const personId = req.params.id;
const person = await Person.forge({ id: personId }).fetchWithRelated();

const isHackerLogin = isLogin && person && hackerId;
if (isHackerLogin) {
const hacker = await Person.forge({ id: hackerId }).fetchWithRelated();
// Get the hacking detection time based on the hacker's skill level
const [detectionTimeMs] = await Promise.all([
await getHackingDetectionTime(hacker),
AuditLogEntry.forge().save({
person_id: personId,
hacker_id: hackerId,
type: 'HACKER_LOGIN'
}),
]);

const intrusionDetectedMessage = getRandomHackingIntrustionDetectionMessage();

// TODO: If the hacker logs out, we should be able to match to this timeout, cancel it,
// and run the function immediately
setTimeout(() => {
addShipLogEntry('WARNING', intrusionDetectedMessage);
}, detectionTimeMs);

return res.json({ ...person, hacker: { detectionTimeMs, intrusionDetectedMessage } });
}

res.json(person);
}));

Expand Down
1 change: 0 additions & 1 deletion src/store/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,3 @@ export function initState(state: Record<string, unknown>) {
}

export default store;

17 changes: 17 additions & 0 deletions src/store/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { SkillLevels } from '@/utils/groups';
import { z } from 'zod';

export const Stores = {
HackerDetectionTimes: 'hacker_detection_times',
} as const;

export const HackerDetectionTimes = z.object({
type: z.literal('misc'),
id: z.literal(Stores.HackerDetectionTimes),
detection_times: z.object({
[SkillLevels.Novice]: z.number().min(0).int(),
[SkillLevels.Master]: z.number().min(0).int(),
[SkillLevels.Expert]: z.number().min(0).int(),
}),
});
export type HackerDetectionTimes = z.infer<typeof HackerDetectionTimes>;
27 changes: 27 additions & 0 deletions src/utils/groups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const SkillLevels = {
Novice: 'skill:novice',
Master: 'skill:master',
Expert: 'skill:expert',
} as const;
export type SkillLevel = typeof SkillLevels[keyof typeof SkillLevels];

export function getHighestSkillLevel(person: unknown): SkillLevel {
if (typeof person !== 'object' || person === null || !('groups' in person)) {
return SkillLevels.Novice;
}

// Dumb hack to work around Bookshelf models returning relations in a dumb way
const { groups }: { groups: string[] } = JSON.parse(JSON.stringify(person));

if (!Array.isArray(groups)) {
return SkillLevels.Novice;
}
const skillLevels = groups.filter((group: string) => typeof group === 'string' && group.startsWith('skill:'));
if (skillLevels.length === 0) {
return SkillLevels.Novice;
}

if (skillLevels.includes(SkillLevels.Expert)) return SkillLevels.Expert;
if (skillLevels.includes(SkillLevels.Master)) return SkillLevels.Master;
return SkillLevels.Novice;
}
50 changes: 50 additions & 0 deletions src/utils/hacking.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { getPath } from '@/store/store';
import { SkillLevels, getHighestSkillLevel } from './groups';
import { Duration } from './time';
import { HackerDetectionTimes, Stores } from '@/store/types';
import { logger } from '@/logger';

const hackingIntrustionDetectionMessages = [
'Intrusion detection system has detected malicious activity',
'System has detected unusual network patterns',
'Unusual activity recognized within the system',
'System analysis reveals potential illicit entry',
'Inconsistent data patterns suggest possible intrusion',
'System surveillance notes suspicious activity',
'Potential intrusion detected by the system\'s defense mechanism',
'Cyber defense system has detected irregular activity',
'Unusual system access patterns detected',
'Abnormal network behavior suggests possible intrusion',
'Inconsistent network patterns hinting at potential breach',
'System reports unusual activity, possible intrusion',
'System has detected a potential security breach',
'System\'s defense mechanism notes potential intrusion',
'System analysis records irregular access patterns',
'Network surveillance system detects potential malicious activity',
];

export const getRandomHackingIntrustionDetectionMessage = () => {
const randomIndex = Math.floor(Math.random() * hackingIntrustionDetectionMessages.length);
return hackingIntrustionDetectionMessages[randomIndex];
};

export const getHackingDetectionTime = async (hackerPerson: unknown): Promise<number> => {
const skillLevel = getHighestSkillLevel(hackerPerson);
const detectionTimesBlob = await getPath(['data', 'misc', Stores.HackerDetectionTimes]);
const detectionTimes = HackerDetectionTimes.safeParse(detectionTimesBlob);
if (!detectionTimes.success) {
logger.error('Failed to parse detection times blob, returning 1min', detectionTimesBlob);
return Duration.minutes(1);
}

switch (skillLevel) {
case SkillLevels.Expert:
return detectionTimes.data.detection_times[SkillLevels.Expert];
case SkillLevels.Master:
return detectionTimes.data.detection_times[SkillLevels.Master];
case SkillLevels.Novice:
return detectionTimes.data.detection_times[SkillLevels.Novice];
default:
return Duration.minutes(1);
}
};

0 comments on commit 376b7e4

Please sign in to comment.