Skip to content

Commit 31d9be9

Browse files
committed
Authenticate requests between components
Use a randomly generated token to authenticate requests to the server for all endpoints, including WebSockets. The token is passed to the node server via STDIN, which should sufficiently prevent eavesdropping.
1 parent a10db15 commit 31d9be9

17 files changed

+178
-27
lines changed

Uebersicht/UBAppDelegate.m

Lines changed: 82 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,13 @@ @implementation UBAppDelegate {
3232
UBWidgetsStore* widgetsStore;
3333
UBWidgetsController* widgetsController;
3434
BOOL needsRefresh;
35+
NSString *token;
3536
}
3637

3738
@synthesize statusBarMenu;
3839

40+
static const uint TOKEN_LENGTH_256_BITS = 32;
41+
3942
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
4043
{
4144
needsRefresh = YES;
@@ -104,24 +107,49 @@ - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
104107

105108
// start server and load webview
106109
portOffset = 0;
110+
token = generateToken(TOKEN_LENGTH_256_BITS);
107111
[self startUp];
108112

109113
[self listenToWallpaperChanges];
110114
}
111115

112-
- (NSDictionary*)fetchState
116+
- (void)fetchState:(void (^)(NSDictionary*))callback
113117
{
114-
[[UBWebSocket sharedSocket] open:[self serverUrl:@"ws"]];
118+
[[UBWebSocket sharedSocket] open:[self serverUrl:@"ws"]
119+
token:token];
120+
115121
NSURL *urlPath = [[self serverUrl:@"http"] URLByAppendingPathComponent: @"state/"];
116-
NSData *jsonData = [NSData dataWithContentsOfURL:urlPath];
117-
NSError *error = nil;
118-
NSDictionary *dataDictionary = [NSJSONSerialization
119-
JSONObjectWithData: jsonData
120-
options: NSJSONReadingMutableContainers
121-
error: &error
122-
];
123-
if (error) NSLog(@"%@", error);
124-
return dataDictionary;
122+
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:urlPath];
123+
[request setValue:@"Übersicht" forHTTPHeaderField:@"Origin"];
124+
[request setValue:[NSString stringWithFormat:@"token=%@", token] forHTTPHeaderField:@"Cookie"];
125+
NSURLSessionDataTask *t = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
126+
if (error) {
127+
NSLog(@"error loading state: %@", error);
128+
dispatch_async(dispatch_get_main_queue(), ^{
129+
callback(@{});
130+
});
131+
return;
132+
}
133+
134+
NSError *jsonError = nil;
135+
NSDictionary *dataDictionary = [NSJSONSerialization
136+
JSONObjectWithData: data
137+
options: NSJSONReadingMutableContainers
138+
error: &jsonError
139+
];
140+
if (jsonError) {
141+
NSLog(@"error parsing state: %@", error);
142+
dispatch_async(dispatch_get_main_queue(), ^{
143+
callback(@{});
144+
});
145+
return;
146+
}
147+
148+
dispatch_async(dispatch_get_main_queue(), ^{
149+
callback(dataDictionary);
150+
});
151+
}];
152+
[t resume];
125153
}
126154

127155
- (void)startUp
@@ -132,10 +160,11 @@ - (void)startUp
132160
void (^handleData)(NSString*) = ^(NSString* output) {
133161
// note that these might be called several times
134162
if ([output rangeOfString:@"server started"].location != NSNotFound) {
135-
[self->widgetsStore reset: [self fetchState]];
136-
// this will trigger a render
137-
[self->screensController syncScreens:self];
138-
163+
[self fetchState:^(NSDictionary* state) {
164+
[self->widgetsStore reset: state];
165+
// this will trigger a render
166+
[self->screensController syncScreens:self];
167+
}];
139168
} else if ([output rangeOfString:@"EADDRINUSE"].location != NSNotFound) {
140169
self->portOffset++;
141170
}
@@ -216,6 +245,20 @@ - (NSTask*)launchWidgetServer:(NSString*)widgetPath
216245

217246
NSTask *task = [[NSTask alloc] init];
218247

248+
NSError *secretsErr = NULL;
249+
NSData *secrets = [NSJSONSerialization dataWithJSONObject:@{
250+
@"token": token,
251+
} options:0 error:&secretsErr];
252+
if (!secrets) {
253+
NSLog(@"ERROR: %@", secretsErr);
254+
return NULL;
255+
}
256+
NSPipe *inPipe = [NSPipe pipe];
257+
NSFileHandle *fh = [inPipe fileHandleForWriting];
258+
[fh writeData:secrets];
259+
[fh closeFile];
260+
[task setStandardInput:inPipe];
261+
219262
[task setStandardOutput:[NSPipe pipe]];
220263
[task.standardOutput fileHandleForReading].readabilityHandler = ^(NSFileHandle *handle) {
221264
NSData *output = [handle availableData];
@@ -285,6 +328,7 @@ - (void)screensChanged:(NSDictionary*)screens
285328
[windowsController
286329
updateWindows:screens
287330
baseUrl: [self serverUrl: @"http"]
331+
token:token
288332
interactionEnabled: preferences.enableInteraction
289333
forceRefresh: needsRefresh
290334
];
@@ -444,6 +488,29 @@ void wallpaperSettingsChanged(
444488
}
445489
}
446490

491+
/*!
492+
@function generateToken
493+
494+
@abstract
495+
Returns a base64-encoded @p NSString* of specified number of random bytes.
496+
497+
@param length
498+
A reasonably large, non-zero number representing the length in bytes.
499+
For example, a value of 32 would generate a 256-bit token.
500+
501+
@result Returns @p NSString* on success; panics on failure.
502+
*/
503+
NSString* generateToken(uint length) {
504+
UInt8 buf[length];
505+
506+
int error = SecRandomCopyBytes(kSecRandomDefault, length, &buf);
507+
if (error != errSecSuccess) {
508+
panic("failed to generate token");
509+
}
510+
511+
return [[NSData dataWithBytes:buf length:length] base64EncodedStringWithOptions:0];
512+
}
513+
447514
#
448515
# pragma mark script support
449516
#

Uebersicht/UBWebSocket.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
@interface UBWebSocket : NSObject <SRWebSocketDelegate>
1313

1414
+ (id)sharedSocket;
15-
- (void)open:(NSURL*)aUrl;
15+
- (void)open:(NSURL*)aUrl
16+
token:(NSString*)aToken;
1617
- (void)close;
1718
- (void)send:(id)message;
1819
- (void)listen:(void (^)(id))listener;

Uebersicht/UBWebSocket.m

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ @implementation UBWebSocket {
1313
NSMutableArray* queuedMessages;
1414
SRWebSocket* ws;
1515
NSURL* url;
16+
NSString *token;
1617
}
1718

1819

@@ -49,16 +50,26 @@ - (void)listen:(void (^)(id))listener
4950
}
5051

5152
- (void)open:(NSURL*)aUrl
53+
token:(NSString*)aToken
5254
{
5355
if (ws) {
5456
return;
5557
}
5658

59+
token = aToken;
5760
url = aUrl;
5861
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
5962
[request setValue:@"Übersicht" forHTTPHeaderField:@"Origin"];
6063
ws = [[SRWebSocket alloc] initWithURLRequest: request];
6164
ws.delegate = self;
65+
ws.requestCookies = @[
66+
[NSHTTPCookie cookieWithProperties:@{
67+
NSHTTPCookieDomain: url.host,
68+
NSHTTPCookiePath: @"/",
69+
NSHTTPCookieName: @"token",
70+
NSHTTPCookieValue: token,
71+
}],
72+
];
6273
[ws open];
6374
}
6475

@@ -76,7 +87,8 @@ - (void)reopen
7687
{
7788
[self close];
7889
if (url) {
79-
[self open:url];
90+
[self open:url
91+
token:token];
8092
}
8193
}
8294

Uebersicht/UBWebViewController.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
- (id)initWithFrame:(NSRect)frame;
1717
- (void)load:(NSURL*)url;
18+
- (void)setToken:(NSString*)token;
1819
- (void)reload;
1920
- (void)redraw;
2021
- (void)destroy;

Uebersicht/UBWebViewController.m

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
@implementation UBWebViewController {
1616
NSURL* url;
17+
NSString *token;
1718
}
1819

1920
@synthesize view;
@@ -44,7 +45,14 @@ - (void)load:(NSURL*)newUrl
4445
default:
4546
break;
4647
}
47-
[(WKWebView*)view loadRequest:[NSURLRequest requestWithURL: url]];
48+
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url];
49+
[request setValue:@"Übersicht" forHTTPHeaderField:@"Origin"];
50+
[(WKWebView*)view loadRequest:request];
51+
}
52+
53+
- (void)setToken:(NSString*)newToken
54+
{
55+
token = newToken;
4856
}
4957

5058
- (void)reload
@@ -173,15 +181,23 @@ - (void)webView:(WKWebView *)sender
173181
[self handleWebviewLoadError:error];
174182
}
175183

176-
177184
- (void)webView: (WKWebView *)theWebView
178185
decidePolicyForNavigationAction: (WKNavigationAction*)action
179186
decisionHandler: (void (^)(WKNavigationActionPolicy))handler
180187
{
181188
if (!action.targetFrame.mainFrame) {
182189
handler(WKNavigationActionPolicyAllow);
183190
} else if ([action.request.URL isEqual: url]) {
184-
handler(WKNavigationActionPolicyAllow);
191+
NSHTTPCookie *c = [NSHTTPCookie cookieWithProperties:@{
192+
NSHTTPCookieDomain: url.host,
193+
NSHTTPCookiePath: @"/",
194+
NSHTTPCookieName: @"token",
195+
NSHTTPCookieValue: token,
196+
@"HttpOnly": @"TRUE",
197+
}];
198+
[theWebView.configuration.websiteDataStore.httpCookieStore setCookie:c completionHandler:^{
199+
handler(WKNavigationActionPolicyAllow);
200+
}];
185201
} else if (action.navigationType == WKNavigationTypeLinkActivated) {
186202
[[NSWorkspace sharedWorkspace] openURL:action.request.URL];
187203
handler(WKNavigationActionPolicyCancel);

Uebersicht/UBWindow.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ typedef NS_ENUM(NSInteger, UBWindowType) {
2626

2727
- (id)initWithWindowType:(UBWindowType)type;
2828
- (void)loadUrl:(NSURL*)url;
29+
- (void)setToken:(NSString*)token;
2930
- (void)reload;
3031
- (void)workspaceChanged;
3132
- (void)wallpaperChanged;

Uebersicht/UBWindow.m

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ - (void)loadUrl:(NSURL*)url
6262
[webViewController load:url];
6363
}
6464

65+
- (void)setToken:(NSString*)token
66+
{
67+
[webViewController setToken:token];
68+
}
69+
6570
- (void)reload
6671
{
6772
[webViewController reload];

Uebersicht/UBWindowGroup.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
1818

1919
- (id)initWithInteractionEnabled:(BOOL)interactionEnabled;
2020
- (void)loadUrl:(NSURL*)Url;
21+
- (void)setToken:(NSString*)token;
2122
- (void)reload;
2223
- (void)close;
2324
- (void)setFrame:(NSRect)frame display:(BOOL)flag;

Uebersicht/UBWindowGroup.m

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,12 @@ - (void)loadUrl:(NSURL*)url
5454
[background loadUrl: url];
5555
}
5656

57+
- (void)setToken:(NSString*)token
58+
{
59+
[foreground setToken:token];
60+
[background setToken:token];
61+
}
62+
5763
- (void)setFrame:(NSRect)frame display:(BOOL)flag
5864
{
5965
[foreground setFrame:frame display:flag];

Uebersicht/UBWindowsController.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_BEGIN
1515

1616
- (void)updateWindows:(NSDictionary*)screens
1717
baseUrl:(NSURL*)baseUrl
18+
token:(NSString*)token
1819
interactionEnabled:(Boolean)interactionEnabled
1920
forceRefresh:(Boolean)forceRefresh;
2021

Uebersicht/UBWindowsController.m

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ - (id)init
3131

3232
- (void)updateWindows:(NSDictionary*)screens
3333
baseUrl:(NSURL*)baseUrl
34+
token:(NSString*)token
3435
interactionEnabled:(Boolean)interactionEnabled
3536
forceRefresh:(Boolean)forceRefresh
3637
{
@@ -43,6 +44,7 @@ - (void)updateWindows:(NSDictionary*)screens
4344
initWithInteractionEnabled: interactionEnabled
4445
];
4546
[windows setObject:windowGroup forKey:screenId];
47+
[windowGroup setToken:token];
4648
[windowGroup loadUrl: [self screenUrl:screenId baseUrl:baseUrl]];
4749
} else {
4850
windowGroup = windows[screenId];

server/server.coffee

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ parseArgs = require 'minimist'
22
UebersichtServer = require './src/app.coffee'
33
cors_proxy = require 'cors-anywhere'
44
path = require 'path'
5+
fs = require 'fs'
56

67
handleError = (err) ->
78
console.log(err.message || err)
89
throw err
910

1011
try
12+
secrets = JSON.parse fs.readFileSync(process.stdin.fd, 'utf-8')
1113
args = parseArgs process.argv.slice(2)
1214
widgetPath = path.resolve(__dirname, args.d ? args.dir ? './widgets')
1315
port = args.p ? args.port ? 41416
16+
token = secrets.token ? "0987654321" # TODO check not empty
1417
settingsPath = path.resolve(__dirname, args.s ? args.settings ? './settings')
1518
publicPath = path.resolve(__dirname, './public')
1619
options =
@@ -21,6 +24,7 @@ try
2124
widgetPath,
2225
settingsPath,
2326
publicPath,
27+
token,
2428
options,
2529
-> console.log 'server started on port', port
2630
)

server/src/SharedSocket.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ function handleError(err) {
2727
console.error(err);
2828
}
2929

30-
exports.open = function open(url) {
31-
ws = new WebSocket(url, ['ws'], {origin: 'Übersicht'});
30+
exports.open = function open(url, token) {
31+
ws = new WebSocket(url, ['ws'], {origin: 'Übersicht', headers:{cookie:`token=${token}`}});
3232

3333
if (ws.on) {
3434
ws.on('open', handleWSOpen);
@@ -63,4 +63,3 @@ exports.onOpen = function onOpen(listener) {
6363
exports.send = function send(data) {
6464
ws.send(data);
6565
};
66-

0 commit comments

Comments
 (0)