Working with client side auth

This commit is contained in:
Henry Hiles 2025-06-19 16:46:03 -04:00
commit 61ded00870
No known key found for this signature in database
11 changed files with 72 additions and 125 deletions

View file

@ -1,11 +1,11 @@
import "dart:io";
import "dart:convert";
import "package:dart_jsonwebtoken/dart_jsonwebtoken.dart";
import "package:matrixgate/controllers/auth_code_controller.dart";
import "package:matrixgate/controllers/settings_controller.dart";
import "package:matrixoidc/controllers/auth_code_controller.dart";
import "package:matrixoidc/controllers/settings_controller.dart";
import "package:shelf/shelf.dart";
import "package:http/http.dart" as http;
import "package:matrixgate/models/matrix_user.dart";
import "package:matrixoidc/models/matrix_user.dart";
import "package:riverpod/riverpod.dart";
class ApiHelper {
@ -15,74 +15,33 @@ class ApiHelper {
Future<Response> handleLogin(Request request) async {
final body = await request.readAsString();
final data = Uri.splitQueryString(body);
final settings = ref.read(SettingsController.provider)!;
final username = data["username"];
final password = data["password"];
final userId = data["user_id"];
final accessToken = data["access_token"];
final redirectUri = data["redirect_uri"];
final state = data["state"] ?? "";
final clientId = data["client_id"];
final scope = data["scope"];
final nonce = data["nonce"];
if ([
username,
password,
redirectUri,
clientId,
nonce,
scope,
].any((f) => f == null)) {
return Response(400, body: "Missing required field(s)");
if (userId == null || accessToken == null || redirectUri == null) {
return Response(400, body: "Missing parameters");
}
if (!Uri.parse(redirectUri!).host.endsWith(settings.serviceDomain)) {
return Response(403, body: "Redirect URI not allowed");
}
final loginRes = await http.post(
Uri.parse("${settings.homeserver}/_matrix/client/v3/login"),
headers: {"Content-Type": "application/json"},
body: json.encode({
"type": "m.login.password",
"identifier": {"type": "m.id.user", "user": username},
"password": password,
}),
);
if (loginRes.statusCode != 200) {
return Response.forbidden("Login failed");
}
final loginData = json.decode(loginRes.body);
final userId = loginData["user_id"];
final accessToken = loginData["access_token"];
final openidRes = await http.post(
Uri.parse(
"${settings.homeserver}/_matrix/client/v3/user/${Uri.encodeComponent(userId)}/openid/request_token",
),
final settings = ref.read(SettingsController.provider)!;
final whoamiRes = await http.get(
Uri.parse("${settings.homeserver}/_matrix/client/v3/account/whoami"),
headers: {"Authorization": "Bearer $accessToken"},
);
if (openidRes.statusCode != 200) {
return Response.forbidden(
"OpenID request failed, status code ${openidRes.statusCode}",
);
if (whoamiRes.statusCode != 200) {
return Response.forbidden("Access token validation failed");
}
final openidToken = json.decode(openidRes.body)["access_token"];
final code = base64Url.encode(
List<int>.generate(16, (_) => DateTime.now().millisecond % 256),
);
ref
.read(AuthCodeController.provider.notifier)
.set(
code,
MatrixUser(userId: userId, matrixToken: openidToken, nonce: nonce!),
);
.set(code, MatrixUser(userId: userId, matrixToken: accessToken));
return Response.found("$redirectUri?code=$code&state=$state");
}
@ -111,7 +70,6 @@ class ApiHelper {
final jwt = JWT(
{
"nonce": user.nonce,
"exp":
DateTime.now().add(Duration(days: 7)).millisecondsSinceEpoch ~/
1000,
@ -137,31 +95,26 @@ class ApiHelper {
Future<Response> userinfoHandler(Request request) async {
final auth = request.headers["authorization"];
if (auth == null || !auth.startsWith("Bearer ")) {
return Response.forbidden(
json.encode({"error": "missing_token"}),
headers: {"content-type": "application/json"},
);
return Response.forbidden("No token");
}
final token = auth.substring(7);
final matrixResp = await http.get(
Uri.parse(
"${ref.read(SettingsController.provider)!.homeserver}/_matrix/federation/v1/openid/userinfo",
),
headers: {"Authorization": "Bearer $token"},
);
if (matrixResp.statusCode != 200) {
return Response.forbidden(
json.encode({"error": "invalid_token"}),
headers: {"content-type": "application/json"},
try {
final token = auth.substring(7);
final jwt = JWT.verify(
token,
SecretKey(
await File.fromUri(
Uri.file(ref.read(SettingsController.provider)!.jwtSecretFile),
).readAsString(),
),
);
return Response.ok(
jsonEncode({"sub": jwt.subject}),
headers: {"Content-Type": "application/json"},
);
} catch (e) {
return Response.forbidden("Invalid token");
}
return Response.ok(
matrixResp.body,
headers: {"content-type": "application/json"},
);
}
Response jwks(_) => Response.ok(