blob: 52817ac5034fc563f27331ffa8826c30ff434d47 [file] [log] [blame]
// Copyright (c) 2021, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:io';
import '../access_credentials.dart';
import '../exceptions.dart';
import '../typedefs.dart';
import 'auth_code.dart';
import 'authorization_code_grant_abstract_flow.dart';
/// Runs an oauth2 authorization code grant flow using an HTTP server.
///
/// This class is able to run an oauth2 authorization flow. It takes a user
/// supplied function which will be called with an URI. The user is expected
/// to navigate to that URI and to grant access to the client.
///
/// Once the user has granted access to the client, Google will redirect the
/// user agent to a URL pointing to a locally running HTTP server. Which in turn
/// will be able to extract the authorization code from the URL and use it to
/// obtain access credentials.
class AuthorizationCodeGrantServerFlow
extends AuthorizationCodeGrantAbstractFlow {
final PromptUserForConsent userPrompt;
final int listenPort;
AuthorizationCodeGrantServerFlow(
super.clientId,
super.scopes,
super.client,
this.userPrompt, {
super.hostedDomain,
this.listenPort = 0,
});
@override
Future<AccessCredentials> run() async {
final server = await HttpServer.bind('localhost', listenPort);
try {
final port = server.port;
final redirectionUri = 'http://localhost:$port';
final state = randomState();
final codeVerifier = createCodeVerifier();
// Prompt user and wait until they goes to URL and the google
// authorization server calls back to our locally running HTTP server.
userPrompt(
authenticationUri(
redirectionUri,
state: state,
codeVerifier: codeVerifier,
).toString(),
);
final request = await server.first;
final uri = request.uri;
try {
if (request.method != 'GET') {
throw Exception(
'Invalid response from server '
'(expected GET request callback, got: ${request.method}).',
);
}
final returnedState = uri.queryParameters['state'];
if (state != returnedState) {
throw Exception(
'Invalid response from server (state did not match).',
);
}
final error = uri.queryParameters['error'];
if (error != null) {
throw UserConsentException(
'Error occurred while obtaining access credentials: $error',
);
}
final code = uri.queryParameters['code'];
if (code == null || code.isEmpty) {
throw Exception(
'Invalid response from server (no auth code transmitted).',
);
}
final credentials = await obtainAccessCredentialsUsingCodeImpl(
code,
redirectionUri,
codeVerifier: codeVerifier,
);
// TODO: We could introduce a user-defined redirect page.
request.response
..statusCode = 200
..headers.set('content-type', 'text/html; charset=UTF-8')
..write(
'''
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Authorization successful.</title>
</head>
<body>
<h2 style="text-align: center">Application has successfully obtained access credentials</h2>
<p style="text-align: center">This window can be closed now.</p>
</body>
</html>''',
);
await request.response.close();
return credentials;
} catch (e) {
request.response.statusCode = 500;
await request.response.close().catchError((_) {});
rethrow;
}
} finally {
await server.close();
}
}
}