Skip to content

Commit

Permalink
slight UI update for task response streaming and time syncing
Browse files Browse the repository at this point in the history
  • Loading branch information
its-a-feature committed Dec 17, 2024
1 parent eddbd55 commit 173cd29
Show file tree
Hide file tree
Showing 16 changed files with 59 additions and 41 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.3.1-rc33] - 2024-12-17

### Changed

- Updated the login / refresh process to return the current UTC timestamp
- this is used in the UI to try to prevent clock skew on callback times

## [3.3.1-rc32] - 2024-12-14

### Changed
Expand Down
6 changes: 6 additions & 0 deletions MythicReactUI/CHANGELOG.MD
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.66] - 2024-12-17

### Changed

- Fixing slight issues with pagination for task responses

## [0.2.65] - 2024-12-12

### Changed
Expand Down
19 changes: 16 additions & 3 deletions MythicReactUI/src/cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,33 @@ export const mePreferences = makeVar(operatorSettingDefaults);
export const successfulLogin = (data) => {
localStorage.setItem("access_token", data.access_token);
localStorage.setItem("refresh_token", data.refresh_token);
localStorage.setItem("user", JSON.stringify(data.user));


let now = new Date();
let serverNow = new Date(data.user.current_utc_time);
const difference = (serverNow - now) / 1000;
let me = {...data.user};
me.server_skew = difference;
meState({
loggedIn: true,
...data
...me,
server_skew: difference
});
localStorage.setItem("user", JSON.stringify(me));
restartWebsockets();
}
export const successfulRefresh = (data) => {
localStorage.setItem("access_token", data.access_token);
localStorage.setItem("refresh_token", data.refresh_token);
let now = new Date();
let serverNow = new Date(data.user.current_utc_time);
const difference = (serverNow - now) / 1000;
let me = {...meState()};
me.server_skew = difference;
meState({
loggedIn: true,
access_token: localStorage.getItem("access_token"),
...meState()
...me
});
}
export const FailedRefresh = () =>{
Expand Down
2 changes: 1 addition & 1 deletion MythicReactUI/src/components/EventFeedNotifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export function EventFeedNotifications(props) {
console.error(error);
snackActions.error("Mythic encountered an error getting operational event stream", {autoHideDuration: 2000});
}
}, [loading, data, error, me.user]);
}, [loading, data, error, me.user?.id]);
return null;
}

Original file line number Diff line number Diff line change
Expand Up @@ -706,7 +706,7 @@ function CallbacksTablePreMemo(props){
return <CallbacksTableStringCell key={`callback${row.id}_${c.name}`} cellData={row.pid}
rowData={{...row, selected: row.id === clickedCallbackID}} />;
case "Last Checkin":
return <CallbacksTableLastCheckinCell key={`callback${row.id}_${c.name}`}
return <CallbacksTableLastCheckinCell key={`callback${row.id}_${c.name}`} me={props.me}
rowData={{...row, selected: row.id === clickedCallbackID}}
cellData={row.last_checkin} />;
case "Description":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,15 @@ export const CallbacksTableStringCell = React.memo(({rowData, cellData}) => {
<div>{cellData}</div>
)
}, areEqual)
export const CallbacksTableLastCheckinCell = React.memo( ({rowData, cellData}) => {
export const CallbacksTableLastCheckinCell = React.memo( ({rowData, cellData, me}) => {
const adjustOutput = (newTime) => {
if(newTime === "a few seconds"){
moment.relativeTimeThreshold('s', 60);
moment.relativeTimeThreshold('ss', 0);
return moment(rowData.last_checkin + "Z", "YYYY-MM-DDTHH:mm:ss.SSSSSSZ").fromNow(true)
return moment(rowData.last_checkin + "Z", "YYYY-MM-DDTHH:mm:ss.SSSSSSZ").subtract(me?.user?.server_skew || 0, 'second').fromNow(true)
}
return newTime;
return moment(rowData.last_checkin + "Z", "YYYY-MM-DDTHH:mm:ss.SSSSSSZ").subtract(me?.user?.server_skew || 0, 'second').fromNow(true);
//return newTime;
}
const theme = useTheme();
if(rowData?.payload?.payloadtype?.agent_type !== "agent"){
Expand Down
30 changes: 8 additions & 22 deletions MythicReactUI/src/components/pages/Callbacks/ResponseDisplay.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,15 +105,14 @@ export const ResponseDisplay = (props) =>{
const NonInteractiveResponseDisplay = (props) => {
const [output, setOutput] = React.useState("");
const [rawResponses, setRawResponses] = React.useState([]);
const taskID = React.useRef(props.task.id);
const [taskID, setTaskID] = React.useState(props.task.id);
const search = React.useRef("");
const [totalCount, setTotalCount] = React.useState(0);
const [openBackdrop, setOpenBackdrop] = React.useState(true);
const initialResponseStreamLimit = GetMythicSetting({setting_name: "experiment-responseStreamLimit", default_value: 50});
const [fetchMoreResponses] = useLazyQuery(getResponsesLazyQuery, {
fetchPolicy: "network-only",
onCompleted: (data) => {
//console.log("fetchMoreResponses called", data)
// set raw responses to be what we just manually fetched
const responseArray = data.response.map( r =>{ return {...r, response: b64DecodeUnicode(r.response)}});
setRawResponses(responseArray);
Expand All @@ -123,7 +122,6 @@ const NonInteractiveResponseDisplay = (props) => {
}, b64DecodeUnicode(""));
setOutput(responses);
// update maxID

if(!props.selectAllOutput){
setTotalCount(data.response_aggregate.aggregate.count);
}
Expand Down Expand Up @@ -170,27 +168,13 @@ const NonInteractiveResponseDisplay = (props) => {
}, [props.selectAllOutput]);
React.useEffect( () => {
setOpenBackdrop(true);
setOutput("");
setRawResponses([]);
setTotalCount(0);
onSubmitPageChange(1);
}, [props.task.id]);
const subscriptionDataCallback = ({data}) => {
//console.log("fetchLimit", fetchLimit, "totalCount", totalCount);
if(props.task.id !== taskID.current){
console.log("props.task.id !== taskID.current", props.task.id, taskID.current)
taskID.current = props.task.id;
// this is the latest batch of responses

// base64 decode all of the response data
const responseArray = data.data.response_stream.map( r =>{ return {...r, response: b64DecodeUnicode(r.response)}});
// set the aggregated output
const responses = responseArray.reduce( (prev, cur) => {
return prev + cur.response;
}, b64DecodeUnicode(""));
setOutput(responses);

setRawResponses(responseArray);
setTotalCount(responseArray.length);
setOpenBackdrop(false);
} else {

if(rawResponses.length >= initialResponseStreamLimit && initialResponseStreamLimit > 0 && !props.selectAllOutput){
// we won't display it
Expand All @@ -200,17 +184,19 @@ const NonInteractiveResponseDisplay = (props) => {
return;
}
// we still have some room to view more, but only room for initialResponseStreamLimit - totalFetched.current
let newTotal = totalCount;
const newerResponses = data.data.response_stream.reduce( (prev, cur) => {
let prevIndex = prev.findIndex( (v,i,a) => v.id === cur.id);
if(prevIndex >= 0){
prev[prevIndex] = {...cur, response: b64DecodeUnicode(cur.response)};
return prev;
}
newTotal += 1;
return [...prev, {...cur, response: b64DecodeUnicode(cur.response)}]
}, rawResponses);
// sort them to make sure we're still in order
newerResponses.sort( (a,b) => a.id > b.id ? 1 : -1);
setTotalCount(totalCount + data.data.response_stream.length);
setTotalCount(newTotal);
// newerResponses is everything we've seen plus everything new
if(initialResponseStreamLimit > 0 && !props.selectAllOutput){
// take just the responses that make up our stream limit
Expand All @@ -230,7 +216,7 @@ const NonInteractiveResponseDisplay = (props) => {
setOutput(outputResponses);
}
setOpenBackdrop(false);
}

};
useSubscription(subResponsesStream, {
variables: {task_id: props.task.id},
Expand Down
2 changes: 1 addition & 1 deletion MythicReactUI/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {snackActions} from './components/utilities/Snackbar';
import jwt_decode from 'jwt-decode';
import {meState} from './cache';

export const mythicUIVersion = "0.2.65";
export const mythicUIVersion = "0.2.66";

let fetchingNewToken = false;

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.1-rc32
3.3.1-rc33
2 changes: 1 addition & 1 deletion mythic-docker/src/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.3.1-rc32
3.3.1-rc33
5 changes: 5 additions & 0 deletions mythic-docker/src/webserver/controllers/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"github.com/its-a-feature/Mythic/authentication/mythicjwt"
"net/http"
"strings"
"time"

"github.com/gin-gonic/gin"
"github.com/its-a-feature/Mythic/authentication"
Expand Down Expand Up @@ -46,6 +47,7 @@ func Login(c *gin.Context) {
"id": currentOperation.CurrentOperator.ID,
"user_id": currentOperation.CurrentOperator.ID,
"view_utc_time": currentOperation.CurrentOperator.ViewUtcTime,
"current_utc_time": time.Now().UTC(),
}
// setting cookie max age to 2 days
c.SetCookie("mythic", accessToken, 60*60*24*2, "/", strings.Split(c.Request.Host, ":")[0], true, true)
Expand Down Expand Up @@ -84,6 +86,7 @@ func GetMeWebhook(c *gin.Context) {
"current_operation_id": currentOperation.CurrentOperation.ID,
"user_id": currentOperation.CurrentOperator.ID,
"id": currentOperation.CurrentOperator.ID,
"current_utc_time": time.Now().UTC(),
})
return
}
Expand Down Expand Up @@ -123,6 +126,8 @@ func RefreshJWT(c *gin.Context) {
"username": currentOperation.CurrentOperator.Username,
"id": currentOperation.CurrentOperator.ID,
"user_id": currentOperation.CurrentOperator.ID,
"view_utc_time": currentOperation.CurrentOperator.ViewUtcTime,
"current_utc_time": time.Now().UTC(),
}
// setting cookie max age to 2 days
c.Set("user_id", currentOperation.CurrentOperator.ID)
Expand Down
6 changes: 3 additions & 3 deletions mythic-react-docker/mythic/public/asset-manifest.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"files": {
"main.css": "/new/static/css/main.602591e6.css",
"main.js": "/new/static/js/main.153cfd6c.js",
"main.js": "/new/static/js/main.ec3c5064.js",
"static/media/mythic-red.png": "/new/static/media/mythic-red.203468a4e5240d239aa0.png",
"static/media/mythic_red_small.svg": "/new/static/media/mythic_red_small.793b41cc7135cdede246661ec232976b.svg",
"index.html": "/new/index.html",
"main.602591e6.css.map": "/new/static/css/main.602591e6.css.map",
"main.153cfd6c.js.map": "/new/static/js/main.153cfd6c.js.map"
"main.ec3c5064.js.map": "/new/static/js/main.ec3c5064.js.map"
},
"entrypoints": [
"static/css/main.602591e6.css",
"static/js/main.153cfd6c.js"
"static/js/main.ec3c5064.js"
]
}
2 changes: 1 addition & 1 deletion mythic-react-docker/mythic/public/index.html
Original file line number Diff line number Diff line change
@@ -1 +1 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.153cfd6c.js"></script><link href="/new/static/css/main.602591e6.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/new/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><link rel="apple-touch-icon" href="/new/logo192.png"/><link rel="manifest" href="/new/manifest.json"/><title>Mythic</title><script defer="defer" src="/new/static/js/main.ec3c5064.js"></script><link href="/new/static/css/main.602591e6.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>

Large diffs are not rendered by default.

Large diffs are not rendered by default.

0 comments on commit 173cd29

Please sign in to comment.