nexus/integration_test/helpers/oauth.dart
2026-11-10 18:29:02 -05:00

68 lines
2.2 KiB
Dart

import "package:http/http.dart" as http;
import "fakes.dart";
/// Fake platforms that do the browser's part of OAuth on Continuwuity, logging
/// in as [username], then hand the redirect back to Nexus.
class OAuthFakes(final String username, final String password) {
final links = FakeAppLinks();
late final launcher = FakeUrlLauncher(onLaunch);
Future<void> onLaunch(Uri url) async {
if (!url.queryParameters.containsKey("code_challenge")) return;
final redirect = await authorize(url);
// Nexus listens for the redirect after the launch returns
await Future.delayed(const .new(milliseconds: 500));
links.open(redirect);
}
/// Logs in on [authorizationUrl], then confirms the grant, returning the URI
/// Continuwuity redirects back to the app with.
Future<Uri> authorize(Uri authorizationUrl) async {
final client = http.Client();
String? cookie;
Future<Uri> followRedirect(
String method,
Uri url, [
Map<String, String>? form,
]) async {
final request = http.Request(method, url)
..followRedirects = false
..headers.addAll({
// Continuwuity only accepts its forms from top-level navigations
"Sec-Fetch-Site": "same-origin",
"Sec-Fetch-Mode": "navigate",
"Sec-Fetch-Dest": "document",
"Cookie": ?cookie,
});
if (form != null) request.bodyFields = form;
final response = await http.Response.fromStream(
await client.send(request),
);
if (response.headers["set-cookie"] case final setCookie?) {
cookie = setCookie.split(";").first;
}
return switch ((response.statusCode, response.headers["location"])) {
(302 || 303 || 307, final location?) => url.resolve(location),
(final status, _) => throw StateError(
"Expected $method $url to redirect, got $status: ${response.body}",
),
};
}
try {
final loginUrl = await followRedirect("GET", authorizationUrl);
final grantUrl = await followRedirect("POST", loginUrl, {
"identifier": username,
"password": password,
});
return await followRedirect("POST", grantUrl, {"stage": "confirm"});
} finally {
client.close();
}
}
}