Skip to content

Commit 23d457a

Browse files
committed
Merge pull request #46 from Bitstackers/shelf-http
Shelf HTTP for auth server
2 parents fb0fe6d + c475880 commit 23d457a

File tree

9 files changed

+214
-153
lines changed

9 files changed

+214
-153
lines changed

bin/authserver.dart

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import 'package:logging/logging.dart';
77
import '../lib/auth_server/configuration.dart' as auth;
88
import '../lib/configuration.dart';
99
import '../lib/auth_server/database.dart';
10-
import 'package:openreception_framework/httpserver.dart' as http;
1110
import '../lib/auth_server/router.dart' as router;
1211
import '../lib/auth_server/token_vault.dart';
1312
import '../lib/auth_server/token_watcher.dart' as watcher;
@@ -37,7 +36,7 @@ void main(List<String> args) {
3736
.then((_) => startDatabase())
3837
.then((_) => watcher.setup())
3938
.then((_) => vault.loadFromDirectory(auth.config.serverTokenDir))
40-
.then((_) => http.start(auth.config.httpport, router.setup))
39+
.then((_) => router.start())
4140
.catchError(log.shout);
4241
}
4342
} on ArgumentError catch(e) {

lib/auth_server/router.dart

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
library authenticationserver.router;
22

33
import 'dart:async';
4-
import 'dart:io';
54
import 'dart:convert';
5+
import 'dart:io' as IO;
66

7-
import 'package:http/http.dart' as http;
8-
import 'package:route/server.dart';
7+
import 'package:logging/logging.dart';
8+
9+
import 'package:openreception_framework/service-io.dart' as _transport;
10+
11+
import 'package:shelf/shelf.dart' as shelf;
12+
import 'package:shelf/shelf_io.dart' as shelf_io;
13+
import 'package:shelf_route/shelf_route.dart' as shelf_route;
14+
import 'package:shelf_cors/shelf_cors.dart' as shelf_cors;
915

10-
//import 'cache.dart' as cache;
1116
import 'configuration.dart';
1217
import 'database.dart' as db;
1318
import 'googleauth.dart';
1419
import 'token_watcher.dart' as watcher;
1520
import 'token_vault.dart';
16-
import 'package:openreception_framework/httpserver.dart';
17-
import 'package:openreception_framework/common.dart';
1821

1922
part 'router/invalidate.dart';
2023
part 'router/login.dart';
@@ -24,25 +27,42 @@ part 'router/validate.dart';
2427

2528
part 'router/refresher.dart';
2629

27-
final Pattern anything = new UrlPattern(r'/(.*)');
28-
final Pattern loginUrl = new UrlPattern('/token/create');
29-
final Pattern oauthReturnUrl = new UrlPattern('/token/oauth2callback');
30-
final Pattern userUrl = new UrlPattern('/token/([0-9a-zA-Z]+)');
31-
final Pattern validateUrl = new UrlPattern('/token/([0-9a-zA-Z]+)/validate');
32-
final Pattern invalidateUrl = new UrlPattern('/token/([0-9a-zA-Z]+)/invalidate');
33-
34-
final Pattern refreshUrl = new UrlPattern('/token/([0-9a-zA-Z]+)/refresh');
35-
36-
void setup(HttpServer server) {
37-
Router router = new Router(server);
38-
39-
router
40-
..serve(loginUrl, method: 'GET').listen(login)
41-
..serve(oauthReturnUrl, method: 'GET').listen(oauthCallback)
42-
..serve(userUrl, method: 'GET').listen(userinfo)
43-
..serve(validateUrl, method: 'GET').listen(validateToken)
44-
..serve(invalidateUrl, method: 'POST').listen(invalidateToken)
45-
..serve(anything, method: 'OPTIONS').listen(preFlight)
46-
..serve(refreshUrl, method: 'GET').listen(refresher)
47-
..defaultStream.listen(page404);
30+
const String libraryName = 'authserver.router';
31+
final Logger log = new Logger(libraryName);
32+
33+
const Map corsHeaders = const {
34+
'Access-Control-Allow-Origin': '*',
35+
'Access-Control-Allow-Methods': 'GET, PUT, POST, DELETE'
36+
};
37+
38+
_transport.Client httpClient = new _transport.Client();
39+
40+
/// Simple access logging.
41+
void _accessLogger(String msg, bool isError) {
42+
if (isError) {
43+
log.severe(msg);
44+
} else {
45+
log.finest(msg);
46+
}
47+
}
48+
49+
Future<IO.HttpServer> start({String hostname: '0.0.0.0', int port: 4050}) {
50+
var router = shelf_route.router()
51+
..get('/token/create', login)
52+
..get('/token/oauth2callback', oauthCallback)
53+
..get('/token/{token}', userinfo)
54+
..get('/token/{token}/validate', validateToken)
55+
..post('/token/{token}/invalidate', invalidateToken)
56+
..get('/token/{token}/refresh', login);
57+
58+
var handler = const shelf.Pipeline()
59+
.addMiddleware(
60+
shelf_cors.createCorsHeadersMiddleware(corsHeaders: corsHeaders))
61+
.addMiddleware(shelf.logRequests(logger: _accessLogger))
62+
.addHandler(router.handler);
63+
64+
log.fine('Serving interfaces:');
65+
shelf_route.printRoutes(router, printer: log.fine);
66+
67+
return shelf_io.serve(handler, hostname, port);
4868
}
Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,21 @@
11
part of authenticationserver.router;
22

3-
void invalidateToken(HttpRequest request) {
4-
String token = request.uri.pathSegments.elementAt(1);
3+
shelf.Response invalidateToken(shelf.Request request) {
4+
final String token = shelf_route.getPathParameter(request, 'token');
55

6-
if(token != null && token.isNotEmpty) {
6+
if (token != null && token.isNotEmpty) {
77
try {
88
vault.removeToken(token);
9-
writeAndClose(request, '{}');
10-
} catch(error) {
11-
serverError(request, 'authenticationserver.router.invalidateToken: Failed to remove token "$token" $error');
9+
return new shelf.Response.ok(JSON.encode(const {}));
10+
} catch (error, stacktrace) {
11+
log.severe(error, stacktrace);
12+
return new shelf.Response.internalServerError(
13+
body: 'authenticationserver.router.invalidateToken: '
14+
'Failed to remove token "$token" $error');
1215
}
1316
} else {
14-
serverError(request, 'authenticationserver.router.invalidateToken: No token parameter was specified');
17+
return new shelf.Response.internalServerError(
18+
body: 'authenticationserver.router.invalidateToken: '
19+
'No token parameter was specified');
1520
}
16-
17-
}
21+
}

lib/auth_server/router/login.dart

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,45 @@
11
part of authenticationserver.router;
22

3-
void login(HttpRequest request) {
3+
shelf.Response login(shelf.Request request) {
4+
final String returnUrlString =
5+
request.url.queryParameters
6+
.containsKey('returnurl')
7+
? request.url.queryParameters['returnurl']
8+
: '';
9+
10+
log.finest('returnUrlString:$returnUrlString');
11+
412
try {
513
//Because the library does not allow to set custom query parameters
614
Map googleParameters = {
715
'access_type': 'offline',
816
'state': config.clientURL
917
};
1018

11-
if(request.uri.queryParameters.containsKey('returnurl')) {
19+
if (returnUrlString.isNotEmpty) {
1220
//validating the url by parsing it.
13-
Uri returnUrl = Uri.parse(request.uri.queryParameters['returnurl']);
21+
Uri returnUrl = Uri.parse(returnUrlString);
1422
googleParameters['state'] = returnUrl.toString();
1523
}
1624

17-
Uri authUrl = googleAuthUrl(config.clientId, config.clientSecret, config.redirectUri);
25+
Uri authUrl =
26+
googleAuthUrl(config.clientId, config.clientSecret, config.redirectUri);
1827

1928
googleParameters.addAll(authUrl.queryParameters);
20-
Uri googleOauthRequestUrl = new Uri(scheme: authUrl.scheme, host: authUrl.host, port: authUrl.port, path: authUrl.path, queryParameters: googleParameters, fragment: authUrl.fragment);
21-
request.response.redirect(googleOauthRequestUrl);
29+
Uri googleOauthRequestUrl = new Uri(
30+
scheme: authUrl.scheme,
31+
host: authUrl.host,
32+
port: authUrl.port,
33+
path: authUrl.path,
34+
queryParameters: googleParameters,
35+
fragment: authUrl.fragment);
36+
37+
log.finest('Redirecting to $googleOauthRequestUrl');
2238

23-
} catch(error) {
24-
serverError(request, 'authenticationserver.router.login: $error');
39+
return new shelf.Response.found(googleOauthRequestUrl);
40+
} catch (error, stacktrace) {
41+
log.severe(error, stacktrace);
42+
return new shelf.Response.internalServerError(
43+
body: 'Failed log in error:$error');
2544
}
2645
}

lib/auth_server/router/oauthcallback.dart

Lines changed: 83 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,99 @@
11
part of authenticationserver.router;
22

3-
void oauthCallback(HttpRequest request) {
4-
try {
5-
//State is used as a return URL.
6-
String state = queryParameter(request.uri, 'state');
7-
if(state == null) {
8-
serverError(request, 'authenticationserver.router.oauthCallback() State parameter is missing "${request.uri}"');
9-
return;
10-
}
11-
Uri returnUrl = Uri.parse(state);
12-
Map postBody =
13-
{
14-
"grant_type": "authorization_code",
15-
"code": request.uri.queryParameters['code'],
16-
"redirect_uri": config.redirectUri.toString(),
17-
"client_id": config.clientId,
18-
"client_secret": config.clientSecret
19-
};
20-
String body = mapToUrlFormEncodedPostBody(postBody);
21-
logger.debug('authenticationserver.router.oauthCallback() Sending request to google. "${tokenEndpoint}" body "${body}"');
22-
23-
//Now we have the "code" which will be exchanged to a token.
24-
http.post(tokenEndpoint, headers: {'content-type':'application/x-www-form-urlencoded'}, body: postBody)
25-
.then((http.Response response) {
26-
Map json = JSON.decode(response.body);
27-
28-
if(json.containsKey('error')) {
29-
serverError(request, 'authenticationserver.router.oauthCallback() Authtication failed. "${json}"');
3+
Future<shelf.Response> oauthCallback(shelf.Request request) {
4+
final String stateString = request.url.queryParameters.containsKey('state')
5+
? request.url.queryParameters['state']
6+
: '';
307

31-
} else {
32-
json['expiresAt'] = dateTimeToJson(new DateTime.now().add(config.tokenexpiretime));
33-
return getUserInfo(json['access_token']).then((Map userData) {
34-
if(userData == null || userData.isEmpty) {
35-
logger.debug('authenticationserver.router.oauthCallback() token:"${json['access_token']}" userdata:"${userData}"');
36-
request.response.statusCode = 403;
37-
writeAndClose(request, JSON.encode({'status': 'Forbidden!'}));
38-
39-
} else {
40-
json['identity'] = userData;
41-
42-
String cacheObject = JSON.encode(json);
43-
String hash = Sha256Token(cacheObject);
44-
45-
try {
46-
vault.insertToken(hash, json);
47-
Map queryParameters = {'settoken' : hash};
48-
request.response.redirect(new Uri(
49-
scheme: returnUrl.scheme,
50-
userInfo: returnUrl.userInfo,
51-
host: returnUrl.host,
52-
port: returnUrl.port,
53-
path: returnUrl.path,
54-
queryParameters: queryParameters));
55-
56-
} catch(error) {
57-
serverError(request, 'authenticationserver.router.oauthCallback uri ${request.uri} error: "${error}" data: "$json"');
58-
}
8+
if (stateString.isEmpty) {
9+
return new Future.value(new shelf.Response.internalServerError(
10+
body: 'State parameter is missing "${request.url}"'));
11+
}
12+
13+
log.finest('stateString:$stateString');
14+
15+
16+
final Uri returnUrl = Uri.parse(stateString);
17+
final Map postBody = {
18+
"grant_type": "authorization_code",
19+
"code": request.url.queryParameters['code'],
20+
"redirect_uri": config.redirectUri.toString(),
21+
"client_id": config.clientId,
22+
"client_secret": config.clientSecret
23+
};
24+
25+
log.finest(
26+
'Sending request to google. "${tokenEndpoint}" body "${postBody}"');
27+
28+
//Now we have the "code" which will be exchanged to a token.
29+
return httpClient.postForm(tokenEndpoint, postBody).then((String response) {
30+
Map json = JSON.decode(response);
31+
32+
if (json.containsKey('error')) {
33+
return new shelf.Response.internalServerError(
34+
body: 'authenticationserver.router.oauthCallback() '
35+
'Authentication failed. "${json}"');
36+
} else {
37+
///FIXME: Change to use format from framework AND update the dummy tokens.
38+
json['expiresAt'] =
39+
new DateTime.now().add(config.tokenexpiretime).toString();
40+
return getUserInfo(json['access_token']).then((Map userData) {
41+
if (userData == null || userData.isEmpty) {
42+
log.finest('authenticationserver.router.oauthCallback() '
43+
'token:"${json['access_token']}" userdata:"${userData}"');
44+
45+
return new shelf.Response.forbidden(
46+
JSON.encode(const {'status': 'Forbidden'}));
47+
} else {
48+
json['identity'] = userData;
49+
50+
String cacheObject = JSON.encode(json);
51+
String hash = Sha256Token(cacheObject);
52+
53+
try {
54+
vault.insertToken(hash, json);
55+
Map queryParameters = {'settoken': hash};
56+
57+
return new shelf.Response.found(new Uri(
58+
scheme: returnUrl.scheme,
59+
userInfo: returnUrl.userInfo,
60+
host: returnUrl.host,
61+
port: returnUrl.port,
62+
path: returnUrl.path,
63+
queryParameters: queryParameters));
64+
} catch (error, stackTrace) {
65+
log.severe(error, stackTrace);
66+
67+
return new shelf.Response.internalServerError(
68+
body: 'authenticationserver.router.oauthCallback '
69+
'uri ${request.url} error: "${error}" data: "$json"');
5970
}
60-
}).catchError((error) {
61-
logger.error('authenticationserver.router.oauthCallback() requested userInfo error "${error}"');
62-
request.response.statusCode = 403;
63-
writeAndClose(request, JSON.encode({'status': 'Forbidden.'}));
64-
});
65-
}
71+
}
72+
}).catchError((error, stackTrace) {
73+
log.severe(error, stackTrace);
74+
return new shelf.Response.forbidden(
75+
JSON.encode(const {'status': 'Forbidden'}));
76+
});
77+
}
78+
}).catchError((error, stackTrace) {
79+
log.severe(error, stackTrace);
6680

67-
}).catchError((error) => serverError(request, 'authenticationserver.router.oauthCallback uri ${request.uri} error: "${error}"'));
68-
} catch(e) {
69-
serverError(request, 'authenticationserver.router.oauthCallback() error "${e}" Url "${request.uri}"');
70-
}
81+
return new shelf.Response.internalServerError(
82+
body: 'authenticationserver.router.oauthCallback uri ${request.url} error: "${error}"');
83+
});
7184
}
7285

7386
/**
7487
* Asks google for the user data, for the user bound to the [access_token].
7588
*/
7689
Future<Map> getUserInfo(String access_token) {
77-
String url = 'https://www.googleapis.com/oauth2/v1/userinfo?alt=json&access_token=${access_token}';
90+
Uri url = Uri.parse('https://www.googleapis.com/oauth2/'
91+
'v1/userinfo?alt=json&access_token=${access_token}');
7892

79-
return http.get(url).then((http.Response response) {
80-
Map googleProfile = JSON.decode(response.body);
93+
return httpClient.get(url).then((String response) {
94+
Map googleProfile = JSON.decode(response);
8195
return db.getUser(googleProfile['email']).then((Map agent) {
82-
if(agent.isNotEmpty) {
96+
if (agent.isNotEmpty) {
8397
agent['remote_attributes'] = googleProfile;
8498
return agent;
8599
} else {

0 commit comments

Comments
 (0)