Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3137 UI refresh token logic #3188

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 80 additions & 20 deletions monkey/monkey_island/cc/ui/src/services/AuthService.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import _ from 'lodash';
import React from 'react';

export function getErrors(errors) {
Expand All @@ -17,8 +16,13 @@ export default class AuthService {
REGISTRATION_API_ENDPOINT = '/api/register';
REGISTRATION_STATUS_API_ENDPOINT = '/api/registration-status';

TOKEN_NAME_IN_LOCALSTORAGE = 'authentication_token';
TOKEN_NAME_IN_RESPONSE = 'authentication_token';
REFRESH_AUTH_TOKEN_ENDPOINT = '/api/refresh-authentication-token'

AUTH_TOKEN_NAME_IN_LOCALSTORAGE = 'authentication_token';
AUTH_TOKEN_NAME_IN_RESPONSE = 'authentication_token';

REFRESH_TOKEN_NAME_IN_LOCALSTORAGE = 'refresh_token';
REFRESH_TOKEN_NAME_IN_RESPONSE = 'refresh_token';

login = (username, password) => {
return this._login(username, password);
Expand All @@ -29,16 +33,65 @@ export default class AuthService {
.then(response => response.json())
.then(response => {
if(response.meta.code === 200){
this._removeToken();
this._removeTokens(
[this.AUTH_TOKEN_NAME_IN_LOCALSTORAGE,
this.REFRESH_TOKEN_NAME_IN_LOCALSTORAGE]
);
}
return response;
});
}

authFetch = (url, options) => {
return this._authFetch(url, options);
return this._authFetch(url, options).then(res => {
if(res.status === 401){
this._refreshAuthToken();
return this._authFetch(url, options);
}
return res;
});
};

_refreshAuthToken = () => {
let refreshToken = this._getToken(this.REFRESH_TOKEN_NAME_IN_LOCALSTORAGE);
if (refreshToken) {
this._fetchNewTokenPair()
.then(response => response.json().then(data => ({status: response.status, body: data})))
.then(object => {
if(object.status === 200) {
this._setTokenPairFromResponseObject(object);
} else if (object.status === 401) {
this._removeTokens(
[this.AUTH_TOKEN_NAME_IN_LOCALSTORAGE,
this.REFRESH_TOKEN_NAME_IN_LOCALSTORAGE]
);
}
})
}
}

_fetchNewTokenPair = () => {
const options = {
method: 'POST',
// https://stackoverflow.com/questions/11508463/javascript-set-object-key-by-variable
body: JSON.stringify({
[this.REFRESH_TOKEN_NAME_IN_RESPONSE]: this._getToken(this.REFRESH_TOKEN_NAME_IN_LOCALSTORAGE)
}),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
}
}
return fetch(this.REFRESH_AUTH_TOKEN_ENDPOINT, options);
}

_setTokenPairFromResponseObject = (object) => {
let authToken = this._getTokenFromResponse(this.AUTH_TOKEN_NAME_IN_RESPONSE, object.body);
let refreshToken = this._getTokenFromResponse(this.REFRESH_TOKEN_NAME_IN_RESPONSE, object.body);

this._setTokenPair(authToken, refreshToken);
}

_login = (username, password) => {
return this._authFetch(this.LOGIN_ENDPOINT, {
method: 'POST',
Expand All @@ -48,12 +101,16 @@ export default class AuthService {
})
}).then(response => response.json())
.then(res => {
let token = this._getTokenFromResponse(res);
if (token !== undefined) {
this._setToken(token);
let authToken = this._getTokenFromResponse(this.AUTH_TOKEN_NAME_IN_RESPONSE, res);
let refreshToken = this._getTokenFromResponse(this.REFRESH_TOKEN_NAME_IN_RESPONSE, res);
if (authToken !== undefined && refreshToken !== undefined) {
this._setTokenPair(authToken, refreshToken);
return {result: true};
} else {
this._removeToken();
this._removeTokens(
[this.AUTH_TOKEN_NAME_IN_LOCALSTORAGE,
this.REFRESH_TOKEN_NAME_IN_LOCALSTORAGE]
);
return {result: false, errors: res['response']['errors']};
}
})
Expand Down Expand Up @@ -81,8 +138,8 @@ export default class AuthService {
})
};

_getTokenFromResponse= (response) => {
return _.get(response, 'response.user.'+this.TOKEN_NAME_IN_RESPONSE, undefined);
_getTokenFromResponse = (tokenName, responseObject) => {
return responseObject?.response?.user?.[tokenName];
}

_authFetch = (url, options = {}) => {
Expand All @@ -92,7 +149,7 @@ export default class AuthService {
};

if (this.loggedIn()) {
headers['Authentication-Token'] = this._getToken();
headers['Authentication-Token'] = this._getToken(this.AUTH_TOKEN_NAME_IN_LOCALSTORAGE);
}

if (Object.prototype.hasOwnProperty.call(options, 'headers')) {
Expand All @@ -109,7 +166,7 @@ export default class AuthService {
res.clone().json().then(res_json => {
console.log('Got 401 from server while trying to authFetch: ' + JSON.stringify(res_json));
});
this._removeToken();
this._removeTokens([this.AUTH_TOKEN_NAME_IN_LOCALSTORAGE]);
}
return res;
})
Expand All @@ -125,20 +182,23 @@ export default class AuthService {
};

loggedIn() {
const token = this._getToken();
const token = this._getToken(this.AUTH_TOKEN_NAME_IN_LOCALSTORAGE);
return (token !== null);
}

_setToken(idToken) {
localStorage.setItem(this.TOKEN_NAME_IN_LOCALSTORAGE, idToken);
_setTokenPair(authToken, refreshToken) {
localStorage.setItem(this.AUTH_TOKEN_NAME_IN_LOCALSTORAGE, authToken);
localStorage.setItem(this.REFRESH_TOKEN_NAME_IN_LOCALSTORAGE, refreshToken);
}

_removeToken() {
localStorage.removeItem(this.TOKEN_NAME_IN_LOCALSTORAGE);
_removeTokens(tokenNameArray) {
tokenNameArray?.forEach(tokenName => {
localStorage.removeItem(tokenName);
Comment on lines +194 to +196
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason we don't want to always remove the auth and refresh tokens together?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, there is. When we do a request and it is 401, we can immediately remove the authentication token but not the refresh token as we need it so we can generate a new token pair.

})
}

_getToken() {
return localStorage.getItem(this.TOKEN_NAME_IN_LOCALSTORAGE)
_getToken(tokenName) {
return localStorage.getItem(tokenName)
}

}
12 changes: 0 additions & 12 deletions vulture_allowlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,10 @@
from common.common_consts import AGENT_OTP_ENVIRONMENT_VARIABLE
from common.credentials import LMHash, NTHash, SecretEncodingConfig
from common.types import Lock, NetworkPort, PluginName
from infection_monkey.exploit import IslandAPIAgentOTPProvider
from infection_monkey.exploit.log4shell_utils.ldap_server import LDAPServerFactory
from infection_monkey.exploit.tools import secret_type_filter
from infection_monkey.exploit.zerologon import NetrServerPasswordSet, NetrServerPasswordSetResponse
from infection_monkey.exploit.zerologon_utils.remote_shell import RemoteShell
from infection_monkey.island_api_client import http_island_api_client
from infection_monkey.transport.http import FileServHTTPRequestHandler
from monkey_island.cc.deployment import Deployment
from monkey_island.cc.models import IslandMode, Machine
Expand All @@ -24,7 +22,6 @@
MongoAgentEventRepository,
MongoOTPRepository,
)
from monkey_island.cc.services.authentication_service.token import TokenValidator
from monkey_island.cc.services.authentication_service.user import User
from monkey_island.cc.services.reporting.exploitations.monkey_exploitation import MonkeyExploitation

Expand Down Expand Up @@ -146,15 +143,6 @@

secret_type_filter

# Remove after #3077
http_island_api_client.get_otp
IslandAPIAgentOTPProvider
AGENT_OTP_ENVIRONMENT_VARIABLE

# Remove after #3137
TokenValidator.validate_token
_refresh_token_validator

# Remove after #3078
IOTPRepository.insert_otp
IOTPRepository.get_expiration
Expand Down