Skip to content

Commit 2a52c78

Browse files
committed
add security to websockets
1 parent 96d95b9 commit 2a52c78

File tree

7 files changed

+68
-7
lines changed

7 files changed

+68
-7
lines changed

interface/src/authentication/Authentication.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,3 +61,13 @@ export function redirectingAuthorizedFetch(url: RequestInfo, params?: RequestIni
6161
});
6262
});
6363
}
64+
65+
export function addAccessTokenParameter(url: string) {
66+
const accessToken = localStorage.getItem(ACCESS_TOKEN);
67+
if (!accessToken) {
68+
return url;
69+
}
70+
const parsedUrl = new URL(url);
71+
parsedUrl.searchParams.set(ACCESS_TOKEN, accessToken);
72+
return parsedUrl.toString();
73+
}

interface/src/components/SocketController.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Sockette from 'sockette';
33
import throttle from 'lodash/throttle';
44
import { withSnackbar, WithSnackbarProps } from 'notistack';
55

6+
import { addAccessTokenParameter } from '../authentication';
67
import { extractEventValue } from '.';
78

89
export interface SocketControllerProps<D> extends WithSnackbarProps {
@@ -47,7 +48,7 @@ export function socketController<D, P extends SocketControllerProps<D>>(wsUrl: s
4748
constructor(props: Omit<P, keyof SocketControllerProps<D>> & WithSnackbarProps) {
4849
super(props);
4950
this.state = {
50-
ws: new Sockette(wsUrl, {
51+
ws: new Sockette(addAccessTokenParameter(wsUrl), {
5152
onmessage: this.onMessage,
5253
onopen: this.onOpen,
5354
onclose: this.onClose,

lib/framework/SecurityManager.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
#define DEFAULT_JWT_SECRET "esp8266-react"
1010

11+
#define ACCESS_TOKEN_PARAMATER "access_token"
12+
1113
#define AUTHORIZATION_HEADER "Authorization"
1214
#define AUTHORIZATION_HEADER_PREFIX "Bearer "
1315
#define AUTHORIZATION_HEADER_PREFIX_LEN 7
@@ -72,6 +74,11 @@ class SecurityManager {
7274
*/
7375
virtual String generateJWT(User* user) = 0;
7476

77+
/**
78+
* Filter a request with the provided predicate, only returning true if the predicate matches.
79+
*/
80+
virtual ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate) = 0;
81+
7582
/**
7683
* Wrap the provided request to provide validation against an AuthenticationPredicate.
7784
*/

lib/framework/SecuritySettingsService.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ Authentication SecuritySettingsService::authenticateRequest(AsyncWebServerReques
2222
value = value.substring(AUTHORIZATION_HEADER_PREFIX_LEN);
2323
return authenticateJWT(value);
2424
}
25+
} else if (request->hasParam(ACCESS_TOKEN_PARAMATER)) {
26+
AsyncWebParameter* tokenParamater = request->getParam(ACCESS_TOKEN_PARAMATER);
27+
String value = tokenParamater->value();
28+
return authenticateJWT(value);
2529
}
2630
return Authentication();
2731
}
@@ -73,6 +77,13 @@ String SecuritySettingsService::generateJWT(User* user) {
7377
return _jwtHandler.buildJWT(payload);
7478
}
7579

80+
ArRequestFilterFunction SecuritySettingsService::filterRequest(AuthenticationPredicate predicate) {
81+
return [this, predicate](AsyncWebServerRequest* request) {
82+
Authentication authentication = authenticateRequest(request);
83+
return predicate(authentication);
84+
};
85+
}
86+
7687
ArRequestHandlerFunction SecuritySettingsService::wrapRequest(ArRequestHandlerFunction onRequest,
7788
AuthenticationPredicate predicate) {
7889
return [this, onRequest, predicate](AsyncWebServerRequest* request) {

lib/framework/SecuritySettingsService.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ class SecuritySettingsService : public SettingsService<SecuritySettings>, public
6363
Authentication authenticate(String& username, String& password);
6464
Authentication authenticateRequest(AsyncWebServerRequest* request);
6565
String generateJWT(User* user);
66+
ArRequestFilterFunction filterRequest(AuthenticationPredicate predicate);
6667
ArRequestHandlerFunction wrapRequest(ArRequestHandlerFunction onRequest, AuthenticationPredicate predicate);
6768
ArJsonRequestHandlerFunction wrapCallback(ArJsonRequestHandlerFunction callback, AuthenticationPredicate predicate);
6869

lib/framework/SettingsSocket.h

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414

1515
/**
1616
* SettingsSocket is designed to provide WebSocket based communication for making and observing updates to settings.
17-
*
18-
* TODO - Security via a parameter, optional on construction to start!
1917
*/
2018
template <class T>
2119
class SettingsSocket {
@@ -24,12 +22,16 @@ class SettingsSocket {
2422
SettingsDeserializer<T>* settingsDeserializer,
2523
SettingsService<T>* settingsService,
2624
AsyncWebServer* server,
27-
char const* socketPath) :
25+
char const* socketPath,
26+
SecurityManager* securityManager,
27+
AuthenticationPredicate authenticationPredicate = AuthenticationPredicates::IS_ADMIN) :
2828
_settingsSerializer(settingsSerializer),
2929
_settingsDeserializer(settingsDeserializer),
3030
_settingsService(settingsService),
3131
_server(server),
3232
_webSocket(socketPath) {
33+
_settingsService->addUpdateHandler([&](String originId) { transmitData(nullptr, originId); }, false);
34+
_webSocket.setFilter(securityManager->filterRequest(authenticationPredicate));
3335
_webSocket.onEvent(std::bind(&SettingsSocket::onWSEvent,
3436
this,
3537
std::placeholders::_1,
@@ -38,7 +40,29 @@ class SettingsSocket {
3840
std::placeholders::_4,
3941
std::placeholders::_5,
4042
std::placeholders::_6));
43+
_server->addHandler(&_webSocket);
44+
_server->on(socketPath, HTTP_GET, std::bind(&SettingsSocket::forbidden, this, std::placeholders::_1));
45+
}
46+
47+
SettingsSocket(SettingsSerializer<T>* settingsSerializer,
48+
SettingsDeserializer<T>* settingsDeserializer,
49+
SettingsService<T>* settingsService,
50+
AsyncWebServer* server,
51+
char const* socketPath) :
52+
_settingsSerializer(settingsSerializer),
53+
_settingsDeserializer(settingsDeserializer),
54+
_settingsService(settingsService),
55+
_server(server),
56+
_webSocket(socketPath) {
4157
_settingsService->addUpdateHandler([&](String originId) { transmitData(nullptr, originId); }, false);
58+
_webSocket.onEvent(std::bind(&SettingsSocket::onWSEvent,
59+
this,
60+
std::placeholders::_1,
61+
std::placeholders::_2,
62+
std::placeholders::_3,
63+
std::placeholders::_4,
64+
std::placeholders::_5,
65+
std::placeholders::_6));
4266
_server->addHandler(&_webSocket);
4367
}
4468

@@ -50,8 +74,15 @@ class SettingsSocket {
5074
AsyncWebSocket _webSocket;
5175

5276
/**
53-
* Responds to the WSEvent by sending the current settings to the clients when they connect and by applying the changes
54-
* sent to the socket directly to the settings service.
77+
* Renders a forbidden respnose to the client if they fail to connect.
78+
*/
79+
void forbidden(AsyncWebServerRequest* request) {
80+
request->send(403);
81+
}
82+
83+
/**
84+
* Responds to the WSEvent by sending the current settings to the clients when they connect and by applying the
85+
* changes sent to the socket directly to the settings service.
5586
*/
5687
void onWSEvent(AsyncWebSocket* server,
5788
AsyncWebSocketClient* client,

src/LightSettingsService.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ LightSettingsService::LightSettingsService(AsyncWebServer* server,
1212
LightBrokerSettingsService* lightBrokerSettingsService) :
1313
_settingsEndpoint(&SERIALIZER, &DESERIALIZER, this, server, LIGHT_SETTINGS_ENDPOINT_PATH, securityManager),
1414
_settingsBroker(&HA_SERIALIZER, &HA_DESERIALIZER, this, mqttClient),
15-
_settingsSocket(&SERIALIZER, &DESERIALIZER, this, server, LIGHT_SETTINGS_SOCKET_PATH),
15+
_settingsSocket(&SERIALIZER, &DESERIALIZER, this, server, LIGHT_SETTINGS_SOCKET_PATH, securityManager),
1616
_mqttClient(mqttClient),
1717
_lightBrokerSettingsService(lightBrokerSettingsService) {
1818
// configure blink led to be output

0 commit comments

Comments
 (0)