tell something smart in here.
cache for CouchDB _users doc
map of requestPromises. We maintain this list to avoid sending the same requests several times.
init account
we've put this into its own method so it's easier to inherit from Hoodie.Account and add custom logic
handle session
he ownerHash gets stored in every object created by the user. Make sure we have one.
authenticate on next tick
is there a pending password reset?
Use this method to assure that the user is authenticated:
hoodie.account.authenticate().done( doSomething ).fail( handleError )
if there is a pending signOut request, return its promise, but pipe it so that it always ends up rejected
if there is apending signIn request, return its promise
if username is not set, make sure to end the session
send request to check for session status. If there is a pending request already, return its promise.
uses standard CouchDB API to create a new document in _users db. The backend will automatically create a userDB based on the username address and approve the account by adding a "confirmed" role to the user doc. The account confirmation might take a while, so we keep trying to sign in with a 300ms timeout.
downcase username
If the user did not sign up himself yet, but data needs to be transfered to the couch, e.g. to send an email or to share data, the anonymousSignUp method can be used. It generates a random password and stores it locally in the browser.
If the user signes up for real later, we "upgrade" his account, meaning we change his username and password internally instead of creating another user.
uses standard CouchDB API to create a new user session (POST /_session). Besides the standard sign in we also check if the account has been confirmed (roles include "confirmed" role).
NOTE: When signing in, all local data gets cleared beforehand (with a signOut). Otherwise data that has been created beforehand (authenticated with another user account or anonymously) would be merged into the user account that signs in. That applies only if username isn't the same as current username.
downcase
uses standard CouchDB API to invalidate a user session (DELETE /_session)
shortcut for hoodie.on
shortcut for hoodie.trigger
shortcut for hoodie.request
return name of db
fetches _users doc from CouchDB and caches it in _doc
Note: the hoodie API requires the currentPassword for security reasons, but couchDb doesn't require it for a password change, so it's ignored in this implementation of the hoodie API.
This is kind of a hack. We need to create an object anonymously that is not exposed to others. The only CouchDB API othering such functionality is the _users database.
So we actualy sign up a new couchDB user with some special attributes. It will be picked up by the password reset worker and removeed once the password was resetted.
Note: the hoodie API requires the current password for security reasons, but technically we cannot (yet) prevent the user to change the username without knowing the current password, so it's not impulemented in the current implementation of the hoodie API.
But the current password is needed to login with the new username.
destroys a user's account
default couchDB user doc prefix
setters
ownerHash is stored with every new object in the createdBy
attribute. It does not get changed once it's set. That's why
we have to force it to be change for the $config/hoodie object.
handle a successful authentication request.
As long as there is no server error or internet connection issue,
the authenticate request (GET /_session) does always return
a 200 status. To differentiate whether the user is signed in or
not, we check userCtx.name in the response. If the user is not
signed in, it's null, otherwise the name the user signed in with
If the user is not signed in, we difeerentiate between users that signed in with a username / password or anonymously. For anonymous users, the password is stored in local store, so we don't need to trigger an 'unauthenticated' error, but instead try to sign in.
standard error handling for AJAX requests
in some case we get the object error directly, in others we get an xhr or even just a string back when the couch died entirely. Whe have to handle each case
handle response of a successful signUp request. Response looks like:
{
"ok": true,
"id": "org.couchdb.user:joe",
"rev": "1-e8747d9ae9776706da92810b1baa4248"
}
a delayed sign in is used after sign up and after a username change.
_delayedSignIn might call itself, when the user account is pending. In this case it passes the original defer, to keep a reference and finally resolve / reject it at some point
It might take a bit until the account has been confirmed
parse a successful sign in response from couchDB. Response looks like:
{
"ok": true,
"name": "test1",
"roles": [
"mvu85hy",
"confirmed"
]
}
we want to turn it into "test1", "mvu85hy" or reject the promise in case an error occured ("roles" array contains "error")
if an error occured, the userDB worker stores it to the $error attribute and adds the "error" role to the users doc object. If the user has the "error" role, we need to fetch his _users doc to find out what the error is, before we can reject the promise.
When the userDB worker created the database for the user and everthing worked out, it adds the role "confirmed" to the user. If the role is not present yet, it might be that the worker didn't pick up the the user doc yet, or there was an error. In this cases, we reject the promise with an "uncofirmed error"
options.verbose is true, when a user manually signed via hoodie.account.signIn(). We need to differentiate to other signIn requests, for example right after the signup or after a session timed out.
user reauthenticated, meaning
check for the status of a password reset. It might take a while until the password reset worker picks up the job and updates it
If a password reset request was successful, the $passwordRequest doc gets removed from _users by the worker, therefore a 401 is what we are waiting for.
Once called, it continues to request the status update with a one second timeout.
reject if there is no pending password reset request
send request to check status of password reset
If the request was successful there might have occured an error, which the worker stored in the special $error attribute. If that happens, we return a rejected promise with the $error, error. Otherwise reject the promise with a 'pending' error, as we are not waiting for a success full response, but a 401 error, indicating that our password was changed and our current session has been invalidated
If the error is a 401, it's exactly what we've been waiting for. In this case we resolve the promise.
change username and password in 3 steps
turn an anonymous account into a real account
we now can be sure that we fetched the latest _users doc, so we can update it without a potential conflict error.
dependend on what kind of error we get, we want to ignore it or not. When we get a "not_found" it means that the _users doc habe been removed already, so we don't need to do it anymore, but still want to finish the destroy locally, so we return a resolved promise
remove everything form the current account, so a new account can be initiated.
depending on wether the user signedUp manually or has been signed up anonymously
the prefix in the CouchDB _users doc differentiates.
An anonymous user is characterized by its username, that equals
its ownerHash (see anonymousSignUp)
We differentiate with hasAnonymousAccount(), because _userKey
is used within signUp method, so we need to be able to differentiate
between anonyomus and normal users before an account has been created.
turn a username into a valid users doc.id
get URL of my _users doc
update my _users doc.
If a new username has been passed, we set the special attribut $newUsername. This will let the username change worker create create a new _users doc for the new username and remove the current one
If a new password has been passed, salt and passwordsha get removed from _users doc and add the password in clear text. CouchDB will replace it with according passwordsha and a new salt server side
prepare updated _users doc
trigger password update when newPassword set
depending on whether a newUsername has been passed, we can sign in right away or have to use the delayed sign in to give the username change worker some time
make sure that the same request doesn't get sent twice by cancelling the previous one.
if there is a pending request, return its promise instead of sending another request
the sign in request that starts a CouchDB session if it succeeds. We separated the actual sign in request from the signIn method, as the latter also runs signOut intenrtally to clean up local data before starting a new session. But as other methods like signUp or changePassword do also need to sign in the user (again), these need to send the sign in request but without a signOut beforehand, as the user remains the same.
Hoodie.Account