Add acid auth backend #12

Open
dmjio opened this Issue Oct 4, 2013 · 10 comments

Comments

Projects
None yet
2 participants
@dmjio

dmjio commented Oct 4, 2013

https://github.com/zopa/snaplet-auth-acid

Adding an auth backend would be very cool. Looks like someone has already tried. Curious to know your thoughts.

@mightybyte

This comment has been minimized.

Show comment Hide comment
@mightybyte

mightybyte Oct 4, 2013

Owner

I'm definitely willing to include an auth backend in this snaplet. That code looks like a good start. If someone can confirm (or better yet, demonstrate) for me that it works, I'd be more likely to merge it.

Owner

mightybyte commented Oct 4, 2013

I'm definitely willing to include an auth backend in this snaplet. That code looks like a good start. If someone can confirm (or better yet, demonstrate) for me that it works, I'd be more likely to merge it.

@dmjio

This comment has been minimized.

Show comment Hide comment
@dmjio

dmjio Oct 8, 2013

Got them working and existing in two different states (state/PersistentState/... and snaplets/AcidStateAuthManager/...). What do you think the best idea would be for merging them together? Or is it cool if we leave them separate.

app :: SnapletInit App App
app = makeSnaplet "app" "An snaplet example application." Nothing $ do
    h <- nestSnaplet "" heist $ heistInit "templates"
    s <- nestSnaplet "sess" sess $ initCookieSessionManager "site_key.txt" "sess" (Just 3600)
    a <- nestSnaplet "auth" auth $ initAcidAuthManager defAuthSettings sess
    db <- nestSnaplet "acid" acid $ acidInit (PersistentState 0)
    addRoutes routes
    addAuthSplices h auth
    return $ App h s a db

dmjio commented Oct 8, 2013

Got them working and existing in two different states (state/PersistentState/... and snaplets/AcidStateAuthManager/...). What do you think the best idea would be for merging them together? Or is it cool if we leave them separate.

app :: SnapletInit App App
app = makeSnaplet "app" "An snaplet example application." Nothing $ do
    h <- nestSnaplet "" heist $ heistInit "templates"
    s <- nestSnaplet "sess" sess $ initCookieSessionManager "site_key.txt" "sess" (Just 3600)
    a <- nestSnaplet "auth" auth $ initAcidAuthManager defAuthSettings sess
    db <- nestSnaplet "acid" acid $ acidInit (PersistentState 0)
    addRoutes routes
    addAuthSplices h auth
    return $ App h s a db
@mightybyte

This comment has been minimized.

Show comment Hide comment
@mightybyte

mightybyte Oct 9, 2013

Owner

That's great, thanks. We should definitely keep them separate. I can easily imagine situations where you would want to use one but not the other.

Owner

mightybyte commented Oct 9, 2013

That's great, thanks. We should definitely keep them separate. I can easily imagine situations where you would want to use one but not the other.

@dmjio

This comment has been minimized.

Show comment Hide comment
@dmjio

dmjio Oct 23, 2013

I'm going to release the snaplet-auth-acid package on hackage soon. But, if I could ask you a question somewhat related to this issue.

What would you say is the best way to go about adding fields to AuthUser? If using postgres it would be simple to do so through sql, but in acid-state, w/o modifying the actual record type in the snaplet-auth package it seems infeasible. Unless you could somehow extend the record while maintaining its type (Which I don't think is possible).

dmjio commented Oct 23, 2013

I'm going to release the snaplet-auth-acid package on hackage soon. But, if I could ask you a question somewhat related to this issue.

What would you say is the best way to go about adding fields to AuthUser? If using postgres it would be simple to do so through sql, but in acid-state, w/o modifying the actual record type in the snaplet-auth package it seems infeasible. Unless you could somehow extend the record while maintaining its type (Which I don't think is possible).

@mightybyte

This comment has been minimized.

Show comment Hide comment
@mightybyte

mightybyte Oct 23, 2013

Owner

Hmmm, I think I misunderstood your question about merging the two. I think it is very reasonable to merge the two projects. I'm totally willing to do that if you want to just add your code to snaplet-acid-state. But that's up to you.

As far as adding fields goes, I think the best way to do that is roughly equivalent to adding a separate table in a relational database. We can't modify the record type, but the end user can add something like Map UserId ExtraUserData to their application state.

Owner

mightybyte commented Oct 23, 2013

Hmmm, I think I misunderstood your question about merging the two. I think it is very reasonable to merge the two projects. I'm totally willing to do that if you want to just add your code to snaplet-acid-state. But that's up to you.

As far as adding fields goes, I think the best way to do that is roughly equivalent to adding a separate table in a relational database. We can't modify the record type, but the end user can add something like Map UserId ExtraUserData to their application state.

@dmjio

This comment has been minimized.

Show comment Hide comment
@dmjio

dmjio Oct 23, 2013

Oh, I assumed by separate you meant separate packages, my mistake. I guess I just don't like the fact that there are two db's. One for users, and another for the rest of all the extensions to make on users. Map UserId ExtraUserData, is exactly what I'm doing right now. I guess it really isn't a big deal. The extra data doesn't really need to be backed up per se, but for cases where it does, would be nice to have one db so I don't have to synchronize checkpoints across different states. The end goal is to use the Data.Remote package to perform checkpoints and backups remotely. It has an acidServer that can serve a state. Would be nice if this served state was from one db.

So in main we would haskell forkIO $ acidServer -- etc.. and then quickServeHttp

I can remove the package I just put on hackage and submit a pull for it here.

dmjio commented Oct 23, 2013

Oh, I assumed by separate you meant separate packages, my mistake. I guess I just don't like the fact that there are two db's. One for users, and another for the rest of all the extensions to make on users. Map UserId ExtraUserData, is exactly what I'm doing right now. I guess it really isn't a big deal. The extra data doesn't really need to be backed up per se, but for cases where it does, would be nice to have one db so I don't have to synchronize checkpoints across different states. The end goal is to use the Data.Remote package to perform checkpoints and backups remotely. It has an acidServer that can serve a state. Would be nice if this served state was from one db.

So in main we would haskell forkIO $ acidServer -- etc.. and then quickServeHttp

I can remove the package I just put on hackage and submit a pull for it here.

@mightybyte

This comment has been minimized.

Show comment Hide comment
@mightybyte

mightybyte Oct 23, 2013

Owner

I saw that you uploaded the package already. If you just want to stick with that, no worries. If you want to merge them, that's fine too. I'll leave it up to you.

I think it should be possible for the auth data to be in the same db as the rest of the application. And you might even be able to avoid a second map. You could make your auth backend add a wrapper around AuthUser. This would enable a single Map UserId (AmplifiedAuthUser s) where the user could specify any data type they want for s.

Owner

mightybyte commented Oct 23, 2013

I saw that you uploaded the package already. If you just want to stick with that, no worries. If you want to merge them, that's fine too. I'll leave it up to you.

I think it should be possible for the auth data to be in the same db as the rest of the application. And you might even be able to avoid a second map. You could make your auth backend add a wrapper around AuthUser. This would enable a single Map UserId (AmplifiedAuthUser s) where the user could specify any data type they want for s.

@dmjio

This comment has been minimized.

Show comment Hide comment
@dmjio

dmjio Oct 24, 2013

To fix the two state issue I've ended up using acidServerTLS to serve up a state for consumption by both snaplets (and an admin app as well). I have cabal generating 3 binaries (web, db, admin), which are self-explanatory.

Server

main :: IO ()
main = initAcidRemoteSSL "snaplets/state/"

initAcidRemoteSSL :: FilePath -> IO ()
initAcidRemoteSSL d = do let secrets = singleton $ B8.pack "12345"
                         bracket (openLocalStateFrom d emptyUS)
                           closeAcidState $ acidServerTLS "ssl/cert.pem" "ssl/key.pem" (sharedSecretCheck secrets) (PortNumber 8080)

Added to snaplet-acid-state, depends on acid-state-tls which @stepcut has on darcs

acidInitRemoteTLS :: IsAcidic st => ByteString -> HostName -> SnapletInit b (Acid st)
acidInitRemoteTLS secret hostname = makeSnaplet "acid-state" description Nothing $
    initWorker (openRemoteStateTLS (sharedSecretPerform  secret) hostname (PortNumber 8080))

Added to snaplet-auth-acid

initAcidAuthManager :: AuthSettings
                    -> SnapletLens b SessionManager
                    -> SnapletInit b (AuthManager b)
initAcidAuthManager s lns =
    makeSnaplet
      "state"
      "A snaplet providing user authentication using an Acid State backend"
      Nothing $ do
          removeResourceLockOnUnload
          rng  <- liftIO mkRNG
          key  <- liftIO $ getKey (asSiteKey s)
          acid <- liftIO openRemote
          return AuthManager
                   { backend               = acid
                   , session               = lns
                   , activeUser            = Nothing
                   , minPasswdLen          = asMinPasswdLen s
                   , rememberCookieName    = asRememberCookieName s
                   , rememberPeriod        = asRememberPeriod s
                   , siteKey               = key
                   , lockout               = asLockout s
                   , randomNumberGenerator = rng
                   }

openRemote :: IO (AcidState UserStore)
openRemote = openRemoteStateTLS (sharedSecretPerform $ B8.pack "12345") "localhost" (PortNumber 8080)

I like my solution, since it solves the double state issue, and would be more than happy to commit the acidInitRemoteTLS code once acid-state-tls hits hackage.

dmjio commented Oct 24, 2013

To fix the two state issue I've ended up using acidServerTLS to serve up a state for consumption by both snaplets (and an admin app as well). I have cabal generating 3 binaries (web, db, admin), which are self-explanatory.

Server

main :: IO ()
main = initAcidRemoteSSL "snaplets/state/"

initAcidRemoteSSL :: FilePath -> IO ()
initAcidRemoteSSL d = do let secrets = singleton $ B8.pack "12345"
                         bracket (openLocalStateFrom d emptyUS)
                           closeAcidState $ acidServerTLS "ssl/cert.pem" "ssl/key.pem" (sharedSecretCheck secrets) (PortNumber 8080)

Added to snaplet-acid-state, depends on acid-state-tls which @stepcut has on darcs

acidInitRemoteTLS :: IsAcidic st => ByteString -> HostName -> SnapletInit b (Acid st)
acidInitRemoteTLS secret hostname = makeSnaplet "acid-state" description Nothing $
    initWorker (openRemoteStateTLS (sharedSecretPerform  secret) hostname (PortNumber 8080))

Added to snaplet-auth-acid

initAcidAuthManager :: AuthSettings
                    -> SnapletLens b SessionManager
                    -> SnapletInit b (AuthManager b)
initAcidAuthManager s lns =
    makeSnaplet
      "state"
      "A snaplet providing user authentication using an Acid State backend"
      Nothing $ do
          removeResourceLockOnUnload
          rng  <- liftIO mkRNG
          key  <- liftIO $ getKey (asSiteKey s)
          acid <- liftIO openRemote
          return AuthManager
                   { backend               = acid
                   , session               = lns
                   , activeUser            = Nothing
                   , minPasswdLen          = asMinPasswdLen s
                   , rememberCookieName    = asRememberCookieName s
                   , rememberPeriod        = asRememberPeriod s
                   , siteKey               = key
                   , lockout               = asLockout s
                   , randomNumberGenerator = rng
                   }

openRemote :: IO (AcidState UserStore)
openRemote = openRemoteStateTLS (sharedSecretPerform $ B8.pack "12345") "localhost" (PortNumber 8080)

I like my solution, since it solves the double state issue, and would be more than happy to commit the acidInitRemoteTLS code once acid-state-tls hits hackage.

@dmjio dmjio closed this Oct 24, 2013

@dmjio dmjio reopened this Oct 24, 2013

@dmjio

This comment has been minimized.

Show comment Hide comment
@dmjio

dmjio Oct 24, 2013

Oops didn't mean to close it

dmjio commented Oct 24, 2013

Oops didn't mean to close it

@dmjio

This comment has been minimized.

Show comment Hide comment
@dmjio

dmjio Apr 2, 2014

@mightybyte Not to raise this from the dead, but I'd like to revisit this at NYC Hack if you're going to be there. The biggest problem I see w/ merging the snaplet-acid-state and snaplet-auth-acid is the template-haskell function makeAcidic from acid-state. Since it requires a single data constructor. You mentioned augmenting the AuthUser type which I'd like to discuss further if you'll be free sometime then.

dmjio commented Apr 2, 2014

@mightybyte Not to raise this from the dead, but I'd like to revisit this at NYC Hack if you're going to be there. The biggest problem I see w/ merging the snaplet-acid-state and snaplet-auth-acid is the template-haskell function makeAcidic from acid-state. Since it requires a single data constructor. You mentioned augmenting the AuthUser type which I'd like to discuss further if you'll be free sometime then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment