duct-framework / duct

Server-side application framework for Clojure
MIT License
1.13k stars 51 forks source link

ring middleware cannot access components #48

Closed zerg000000 closed 7 years ago

zerg000000 commented 8 years ago

authentication/authorization are common use case of middleware, which always involve datastore (component). However, under current implementation, middleware must be a function called by system, and have no chance to declare the dependencies of components and inject the component required by the middleware.

dadair-ca commented 8 years ago

You can define and use middleware around your endpoint routes just as you normally would. Not everything has to go into the :middleware key of the system configuration map.

Here's an example:

(defn data-endpoint
  [config]
  (let [{:keys [db jwt]} config
        backend (jws-backend jwt)]
    (-> (routes
          (wrap-routes
            (GET "/data" {{start :start end :end} :params} (index db start end))
            restrict #{:user :modeller :admin}))
        (wrap-authorization backend)
        (wrap-authentication backend))))
zerg000000 commented 8 years ago

Actually, I am using similar method to get it to work, but the middleware configuration become everywhere. Take authentication as example, I will not use the auth middleware on only one endpoint. e.g. I need to have an oauth middleware to protect the whole api, so I need to wrap-oauth in every endpoint.

Another issue is the order of the middlewares. The config.edn defined middlewares will wrap outer. If I have a middleware which have dependencies on component and must be placed outer, I have to make all middlewares placed in endpoint. e.g. I have a cors middleware need to read database for accepted domain name pattern, it must be placed at the first middleware. Therefore, the not-found, ring-defaults all will need to defined in endpoint instead of inside config.edn.

weavejester commented 8 years ago

I don't currently have a good solution to this. You can put middleware in the endpoint of course. You could also create your own HandlerComponent, since there's nothing in Duct that says you have to use the components it supplies for you. Perhaps use the Duct HandlerComponent code as a basis, and give it the capability to pass in the containing component to the middleware functions.

I'll give this some thought, and try to get a solution in for the next version. I'm changing the configuration around anyway, so a better solution may present itself in the process.

dadair-ca commented 8 years ago

Couldn't you create different root endpoints, and pass endpoints to those rather than the handler component?

So for example, you'd have the :app handler component, and you could have both a :secure and :insecure endpoint. The app would depend on both (i.e. :app [:secure :insecure]), and you would have overarching middleware applied to :app. Then in the :secure endpoint you'd manually wrap everything in auth middleware, but you could pass in other endpoint routes?

So like :secure [:ep1 :ep2 :db] and you'd do something like:

(def secure-endpoint [config]
  (let [{:keys [ep1 ep2 db]} config]
    (wrap-authentication
      (routes
        (:build-routes ep1)
        (:build-routes ep2))
       ;; stuff with db for auth)))
zerg000000 commented 8 years ago

Thanks @adairdavid. I think this is a much better workaround that don't need to add middlewares everywhere.

weavejester commented 7 years ago

Fixed by 0.9.0.