diff --git a/.gitignore b/.gitignore index aa6922d..3bc06ec 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .dart_tool/ -.direnv \ No newline at end of file +.direnv +secret \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 6d7a358..9d2c1f2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,13 +9,14 @@ "program": "bin/matrixgate.dart", "args": [ "--homeserver", - "foo", + "https://matrix.federated.nexus", "--jwtSecretFile", - "foo", + "secret", "--issuer", - "http://localhost:8080", + "http://localhost:8080/", "--authorizeEndpoint", - "https://federated.nexus/login" + "http://localhost:4321/login" + // "https://federated.nexus/login" ], "request": "launch", "type": "dart" diff --git a/bin/matrixgate.dart b/bin/matrixgate.dart index 469967d..de501ee 100644 --- a/bin/matrixgate.dart +++ b/bin/matrixgate.dart @@ -33,7 +33,7 @@ void main(List argsRaw) async { "/.well-known/openid-configuration", apiHelper.openidConfiguration, ) - ..get("/userInfo", apiHelper.userinfoHandler) + ..get("/userinfo", apiHelper.userinfoHandler) ..get("/jwks.json", apiHelper.jwks) ..post("/login", apiHelper.handleLogin) ..post("/token", apiHelper.tokenHandler)) diff --git a/lib/helpers/api_helper.dart b/lib/helpers/api_helper.dart index 591259d..6493dae 100644 --- a/lib/helpers/api_helper.dart +++ b/lib/helpers/api_helper.dart @@ -21,9 +21,26 @@ class ApiHelper { final password = data["password"]; final redirectUri = data["redirect_uri"]; final state = data["state"] ?? ""; + final clientId = data["client_id"]; + final scope = data["scope"]; + final nonce = data["nonce"]; + // Basic validation + if ([ + username, + password, + redirectUri, + clientId, + nonce, + scope, + ].any((v) => v == null)) { + return Response(400, body: "Missing required field(s)"); + } + + // Matrix login final loginRes = await http.post( Uri.https(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}, @@ -39,10 +56,11 @@ class ApiHelper { final userId = loginData["user_id"]; final accessToken = loginData["access_token"]; + // Request OpenID token from Matrix final openidRes = await http.post( Uri.https( settings.homeserver, - "_matrix/client/v3/user/$userId/openid/request", + "_matrix/client/v3/user/${Uri.encodeComponent(userId)}/openid/request", ), headers: {"Authorization": "Bearer $accessToken"}, ); @@ -55,13 +73,19 @@ class ApiHelper { final openidToken = json.decode(openidRes.body)["access_token"]; + // Generate and store authorization code final code = base64Url.encode( List.generate(16, (_) => DateTime.now().millisecond % 256), ); + ref .read(AuthCodeController.provider.notifier) - .set(code, MatrixUser(userId: userId, matrixToken: openidToken)); + .set( + code, + MatrixUser(userId: userId, matrixToken: openidToken, nonce: nonce!), + ); + // Redirect back to client return Response.found("$redirectUri?code=$code&state=$state"); } @@ -133,10 +157,16 @@ class ApiHelper { ); } - return Response.ok(matrixResp.body); + return Response.ok( + matrixResp.body, + headers: {"content-type": "application/json"}, + ); } - Response jwks(_) => Response.ok(json.encode({"keys": []})); + Response jwks(_) => Response.ok( + json.encode({"keys": []}), + headers: {"content-type": "application/json"}, + ); Response openidConfiguration(_) { final settings = ref.read(SettingsController.provider)!; @@ -145,7 +175,7 @@ class ApiHelper { "issuer": settings.issuer, "authorization_endpoint": settings.authorizeEndpoint, "token_endpoint": "${settings.issuer}/token", - "userinfo_endpoint": "${settings.issuer}/userInfo", + "userinfo_endpoint": "${settings.issuer}/userinfo", "jwks_uri": "${settings.issuer}/jwks.json", "response_types_supported": ["code"], "subject_types_supported": ["public"], diff --git a/lib/models/matrix_user.dart b/lib/models/matrix_user.dart index 805fb3f..e2d88df 100644 --- a/lib/models/matrix_user.dart +++ b/lib/models/matrix_user.dart @@ -8,6 +8,7 @@ abstract class MatrixUser with _$MatrixUser { const factory MatrixUser({ required String userId, required String matrixToken, + required String nonce, }) = _MatrixUser; factory MatrixUser.fromJson(Map json) => diff --git a/lib/models/matrix_user.freezed.dart b/lib/models/matrix_user.freezed.dart index 0dd35c7..5b73067 100644 --- a/lib/models/matrix_user.freezed.dart +++ b/lib/models/matrix_user.freezed.dart @@ -16,7 +16,7 @@ T _$identity(T value) => value; /// @nodoc mixin _$MatrixUser { - String get userId; String get matrixToken; + String get userId; String get matrixToken; String get nonce; /// Create a copy of MatrixUser /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @@ -29,16 +29,16 @@ $MatrixUserCopyWith get copyWith => _$MatrixUserCopyWithImpl Object.hash(runtimeType,userId,matrixToken); +int get hashCode => Object.hash(runtimeType,userId,matrixToken,nonce); @override String toString() { - return 'MatrixUser(userId: $userId, matrixToken: $matrixToken)'; + return 'MatrixUser(userId: $userId, matrixToken: $matrixToken, nonce: $nonce)'; } @@ -49,7 +49,7 @@ abstract mixin class $MatrixUserCopyWith<$Res> { factory $MatrixUserCopyWith(MatrixUser value, $Res Function(MatrixUser) _then) = _$MatrixUserCopyWithImpl; @useResult $Res call({ - String userId, String matrixToken + String userId, String matrixToken, String nonce }); @@ -66,10 +66,11 @@ class _$MatrixUserCopyWithImpl<$Res> /// Create a copy of MatrixUser /// with the given fields replaced by the non-null parameter values. -@pragma('vm:prefer-inline') @override $Res call({Object? userId = null,Object? matrixToken = null,}) { +@pragma('vm:prefer-inline') @override $Res call({Object? userId = null,Object? matrixToken = null,Object? nonce = null,}) { return _then(_self.copyWith( userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable as String,matrixToken: null == matrixToken ? _self.matrixToken : matrixToken // ignore: cast_nullable_to_non_nullable +as String,nonce: null == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable as String, )); } @@ -81,11 +82,12 @@ as String, @JsonSerializable() class _MatrixUser implements MatrixUser { - const _MatrixUser({required this.userId, required this.matrixToken}); + const _MatrixUser({required this.userId, required this.matrixToken, required this.nonce}); factory _MatrixUser.fromJson(Map json) => _$MatrixUserFromJson(json); @override final String userId; @override final String matrixToken; +@override final String nonce; /// Create a copy of MatrixUser /// with the given fields replaced by the non-null parameter values. @@ -100,16 +102,16 @@ Map toJson() { @override bool operator ==(Object other) { - return identical(this, other) || (other.runtimeType == runtimeType&&other is _MatrixUser&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.matrixToken, matrixToken) || other.matrixToken == matrixToken)); + return identical(this, other) || (other.runtimeType == runtimeType&&other is _MatrixUser&&(identical(other.userId, userId) || other.userId == userId)&&(identical(other.matrixToken, matrixToken) || other.matrixToken == matrixToken)&&(identical(other.nonce, nonce) || other.nonce == nonce)); } @JsonKey(includeFromJson: false, includeToJson: false) @override -int get hashCode => Object.hash(runtimeType,userId,matrixToken); +int get hashCode => Object.hash(runtimeType,userId,matrixToken,nonce); @override String toString() { - return 'MatrixUser(userId: $userId, matrixToken: $matrixToken)'; + return 'MatrixUser(userId: $userId, matrixToken: $matrixToken, nonce: $nonce)'; } @@ -120,7 +122,7 @@ abstract mixin class _$MatrixUserCopyWith<$Res> implements $MatrixUserCopyWith<$ factory _$MatrixUserCopyWith(_MatrixUser value, $Res Function(_MatrixUser) _then) = __$MatrixUserCopyWithImpl; @override @useResult $Res call({ - String userId, String matrixToken + String userId, String matrixToken, String nonce }); @@ -137,10 +139,11 @@ class __$MatrixUserCopyWithImpl<$Res> /// Create a copy of MatrixUser /// with the given fields replaced by the non-null parameter values. -@override @pragma('vm:prefer-inline') $Res call({Object? userId = null,Object? matrixToken = null,}) { +@override @pragma('vm:prefer-inline') $Res call({Object? userId = null,Object? matrixToken = null,Object? nonce = null,}) { return _then(_MatrixUser( userId: null == userId ? _self.userId : userId // ignore: cast_nullable_to_non_nullable as String,matrixToken: null == matrixToken ? _self.matrixToken : matrixToken // ignore: cast_nullable_to_non_nullable +as String,nonce: null == nonce ? _self.nonce : nonce // ignore: cast_nullable_to_non_nullable as String, )); } diff --git a/lib/models/matrix_user.g.dart b/lib/models/matrix_user.g.dart index e3aa600..76d8459 100644 --- a/lib/models/matrix_user.g.dart +++ b/lib/models/matrix_user.g.dart @@ -9,10 +9,12 @@ part of 'matrix_user.dart'; _MatrixUser _$MatrixUserFromJson(Map json) => _MatrixUser( userId: json['userId'] as String, matrixToken: json['matrixToken'] as String, + nonce: json['nonce'] as String, ); Map _$MatrixUserToJson(_MatrixUser instance) => { 'userId': instance.userId, 'matrixToken': instance.matrixToken, + 'nonce': instance.nonce, };