initial commit

This commit is contained in:
Henry Hiles 2025-06-18 10:32:03 -04:00
commit 217621daac
No known key found for this signature in database
21 changed files with 1401 additions and 0 deletions

159
lib/helpers/api_helper.dart Normal file
View file

@ -0,0 +1,159 @@
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:shelf/shelf.dart";
import "package:http/http.dart" as http;
import "package:matrixgate/models/matrix_user.dart";
import "package:riverpod/riverpod.dart";
class ApiHelper {
final Ref ref;
ApiHelper(this.ref);
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 redirectUri = data["redirect_uri"];
final state = data["state"] ?? "";
final loginRes = await http.post(
Uri.https(settings.homeserver, "_matrix/client/v3/login"),
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.https(
settings.homeserver,
"_matrix/client/v3/user/$userId/openid/request",
),
headers: {"Authorization": "Bearer $accessToken"},
);
if (openidRes.statusCode != 200) {
return Response.forbidden(
"OpenID request failed, status code ${openidRes.statusCode}",
);
}
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));
return Response.found("$redirectUri?code=$code&state=$state");
}
Future<Response> tokenHandler(Request request) async {
final settings = ref.read(SettingsController.provider)!;
final body = Uri.splitQueryString(await request.readAsString());
final code = body["code"];
final codes = ref.read(AuthCodeController.provider);
if (code == null || !codes.containsKey(code)) {
return Response(400, body: "Invalid code");
}
final user = codes[code]!;
ref.read(AuthCodeController.provider.notifier).remove(code);
final jwt = JWT(
{
"exp":
DateTime.now().add(Duration(minutes: 10)).millisecondsSinceEpoch ~/
1000,
"iat": DateTime.now().millisecondsSinceEpoch ~/ 1000,
},
subject: user.userId,
issuer: ref.read(SettingsController.provider)!.issuer,
audience: Audience([body["client_id"]!]),
);
final token = jwt.sign(
SecretKey(
await File.fromUri(Uri.file(settings.jwtSecretFile)).readAsString(),
),
algorithm: JWTAlgorithm.HS256,
);
return Response.ok(
json.encode({
"id_token": token,
"token_type": "Bearer",
"expires_in": 600,
}),
headers: {"Content-Type": "application/json"},
);
}
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"},
);
}
final token = auth.substring(7);
final matrixResp = await http.get(
Uri.https(
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"},
);
}
return Response.ok(matrixResp.body);
}
Response jwks(_) => Response.ok(json.encode({"keys": []}));
Response openidConfiguration(_) {
final settings = ref.read(SettingsController.provider)!;
return Response.ok(
json.encode({
"issuer": settings.issuer,
"authorization_endpoint": settings.authorizeEndpoint,
"token_endpoint": "${settings.issuer}/token",
"userinfo_endpoint": "${settings.issuer}/userInfo",
"jwks_uri": "${settings.issuer}/jwks.json",
"response_types_supported": ["code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["HS256"],
}),
headers: {"Content-Type": "application/json"},
);
}
static final provider = Provider<ApiHelper>(ApiHelper.new);
}