hoodiehq / discussion

General discussions and questions about Hoodie
7 stars 1 forks source link

Moving Auth / Session logic from CouchDB to Node #86

Closed gr2m closed 8 years ago

gr2m commented 8 years ago

Goal

Remove dependency on CouchDB’s built-in authorization system (/_session requests, _users database, db/_security settings).

Motivation

  1. Much more efficient session handling. We currently send extra requests between CouchDB and hoodie-server to determine if a user session is valid
  2. More control of user session data, so we can more easily pass in user context to plugins: https://github.com/hoodiehq/hoodie-server-account/issues/14
  3. OAuth becomes simpler to add
  4. Compatibility with Cloudant

    How to

We think that we can move the auth logic to Node without loosing compatibility with CouchDB.

For example, user accounts could be stored in any database and we would calculate salt & password hashes just as CouchDB does. If the database is _users though, then we would store the user accounts just as CouchDB expects them, so a user account created with Hoodie can be used to sign in to the CouchDB directly, if needed.

We would manage read/write access to databases by adding according roles to the accounts, e.g. "db:namehere:read".

And for CouchDB and others that support it, we set the _security on created databases to something like

{
    "admins": {
        "names": [],
        "roles": []
    },
    "members": {
        "names": [],
        "roles": [
            "db:namehere:read",
            "db:namehere:write"
        ]
    }
}

and add the design doc similar to what we do right now:

{
    "_id": "_design/permissions",
    "validate_doc_update": "function (newDoc, oldDoc, userCtx) {\nfor (var i = 0; i < userCtx.roles.length; i++) {\nvar r = userCtx.roles[i];\nif (r === \"db:namehere:write\" || r === \"_admin\") {\nreturn;\n}\n}\nthrow {unauthorized: \"You are not authorized to write to this database\"};\n}"
}

By following the roles conventions, we only need to know the account roles to determine if a user is allowed to read / write to a certain database, which will reduce the interaction with CouchDB to a minimium.

Next steps

  1. @boennemann & @christophwitzko will create a new branch on hoodie-account-server and implement the new auth system as a proof of concept.
  2. Once implemented, we will use it as a base of discussion if we want to go the path for the new Hoodie or not
janl commented 8 years ago

Sounds good to me. How do we start?

gr2m commented 8 years ago

I talked to @jan because it’s not yet clear on how we plan to build this without compromising security. The major concern is that if we decide to move the authorisation layer to api, and we make a slight mistake in our implementation, the door is open for data leaks. And we must eliminate that possibility.

I think we can account for that by adding another requirement: we don’t replace the CouchDB auth / security features, we add an additional layer to Node. So even if we do something wrong in our Node layer, CouchDB will error based on user & database _security settings, as it is today.

That will of course only work if CouchDB is used as Hoodie’s back-end, it will not help us with Cloudant. So I don’t mind leaving the Cloudant compatibility out of scope for this discussion. The benefits still outweigh the costs

Example

As a user of a Todo app

  1. I sign up as “pat” for an account in Firefox
  2. create one todo
  3. sign in as “pat” to the same account in Chrome
  4. I see my todo

Below I show the requests the differences in the requests that Hoodie sends to CouchDB behind the curtain of Hoodie’s REST API

1. I sign up for an account in Firefox

Before

PUT /_users/org.couchdb.users:pat
POST /_session

After

PUT /_users/org.couchdb.users:pat

Difference

With the new setup, we still persist user accounts in /_users, so the part is the same. Hoodie will pick up the new account, confirm it by adding roles [“id:abc4567”, “db:abc4567:read”, “db:abc4567:write”], just like Hoodie is working now. And it creates the “user-abc4567” database with the /_security and the /_design/permissions just like before.

There is no POST /_session because we calculate the session ID exactly the same way that CouchDB would do it, only in the node layer, based on CouchDB secret, session timeout, password hash & salt.

So now we can respond to a sign in request, without talking to CouchDB, and we can tell if a session is valid. But just in case something goes wrong in the node layer, we will still always send the AuthSession Cookie towards CouchDB as it is today, and CouchDB remains as our security layer for all requests against databases.

2. create one todo

Todo gets created locally, and then pushed to Hoodie using the sync APIs.

Before

POST /user-abc4567/_bulk_docs
Cookie: AuthSession=….

After

POST /user-abc4567/_bulk_docs
Cookie: AuthSession=….

Difference

There is no difference if the user has a valid session. We can proxy trough all requests if we want.

We could fail early because api knows if the user has a valid session and if the user is authorised, but we can leave that out of scope for now, to keep things simpler.

3. sign in as “pat” to the same account in Chrome

Before

POST /_session

After

no request to CouchDB

Difference

Just as in step 1, we calculate the session id in node the same way it would be calculated in CouchDB.

4. I see my todo

After sign in, the user pulls changes from the database belonging to the user

Before

POST /user-abc4567/_bulk_docs
Cookie: AuthSession=….

After

POST /user-abc4567/_bulk_docs
Cookie: AuthSession=….

Difference

There is no difference if the user has a valid session. We can proxy trough all requests if we want.

We could fail early because api knows if the user has a valid session and if the user is authorised, but we can leave that out of scope for now, to keep things simpler.

NickColley commented 8 years ago

:+1: for the thoughtful consideration on security

gr2m commented 8 years ago

This might be relevant: https://github.com/pouchdb/express-pouchdb/issues/271 @boennemann @christophwitzko.

gr2m commented 8 years ago

this is done in new hoodie