Search code examples
dartaqueduct

How can I pass data from channel.dart to a middleware handle method?


I have created a middleware controller, that is responsible for authentication. The controller will be linked in front of the resource controllers. I assume this is the right way to do it?

In the handle method, I'm executing a database query, which requires the connection information. However when I try to pass the data through the constructor, it tells me that setters are not allowed.

What would be the best way to pass a Map with connection information from the channel.dart into a Middleware controller (see dbConnectionDetails)?

channel.dart

@override
  Future prepare() async {
    logger.onRecord.listen((rec) => print("$rec ${rec.error ?? ""} ${rec.stackTrace ?? ""}"));

    dbConnectionDetails = ConnectionSettings (
      user: "mydbuser",
      password: "mydbpass",
      host: "localhost",
      port: 3306
    );
}

@override
  Controller get entryPoint {
    final router = Router();

    router
      .route("/api/user/query/complex")
      .link(() => AuthMiddleware())
      .link(() => UserComplexQueryController(dbConnectionDetails));
}

In the example above, I can pass the dbConnectionDetails into the constructor of the UserComplexQueryController, so that I can use it for the database connection.

However if I pass the dbConnectionDetails into the constructor of AuthMiddleWare, then I get following error:

ArgumentError (Invalid argument(s): Invalid controller 'AuthMiddleware'. Controllers must not have setters and all fields must be marked as final, or it must implement 'Recyclable'.)

The AuthMiddleWare in my case is getting a token parsed in the URL and will check if the token is valid in the database. As you can see, I found no other way than to add the dbConnectionDetails directly within the AuthMiddleware isValid function. But I would prefer, if I could pass it down from channel.dart.

class AuthMiddleware extends Controller {
 // This does not work, as setters not allowed
 //AuthMiddleware(this.dbConnectionDetails);
 //
 //ConnectionSettings dbConnectionDetails

  @override
  Future<RequestOrResponse> handle(Request request) async {
    if (await isValid(request)) {
      return request;
    }
    return Response.unauthorized();
  }

  Future<bool> isValid(Request request) async {
    final String token = request.raw.requestedUri.queryParameters["token"];
    final int timestamp = (DateTime.now().toUtc().millisecondsSinceEpoch / 1000).round();

    ConnectionSettings dbConnectionDetails = ConnectionSettings (
      user: "myuser",
      password: "mypass",
      host: "localhost",
      port: 3306
    );

    AuthTokenDao authTokenDao = new AuthTokenDao(dbConnectionDetails);

    if(await authTokenDao.validateToken(token, timestamp)) {
      return true;
    }

    return false;
  }
}

And yes, you have seen right. I'm connecting to a MySQL database :)


Solution

  • Edit by CA: The solution (provided by Joe Conway):

    class AuthMiddleware extends Controller {
       // Works by adding "final"
       final ConnectionSettings dbConnectionDetails;
    
       // Now dbConnectionDetails can be passed within the constructor.
       AuthMiddleware(this.dbConnectionDetails);
    
      @override
      Future<RequestOrResponse> handle(Request request) async {
        if (await isValid(request)) {
          return request;
        }
        return Response.unauthorized();
      }
    
      Future<bool> isValid(Request request) async {
        final String token = request.raw.requestedUri.queryParameters["token"];
        final int timestamp = (DateTime.now().toUtc().millisecondsSinceEpoch / 1000).round();
    
        /* Removed this    
        ConnectionSettings dbConnectionDetails = ConnectionSettings (
          user: "myuser",
          password: "mypass",
          host: "localhost",
          port: 3306
        );
        */    
        AuthTokenDao authTokenDao = new AuthTokenDao(dbConnectionDetails);
    
        if(await authTokenDao.validateToken(token, timestamp)) {
          return true;
        }
    
        return false;
      }
    }