🐶 Account JavaScript API backed by PouchDB
Switch branches/tags
26 camp-60 code-coverage first-timers-only-update-license first-timers-update-license-year greenkeeper-pouchdb-adapter-memory-6.0.7 greenkeeper-pouchdb-core-6.0.7 greenkeeper-pouchdb-errors-6.0.7 greenkeeper/base64url-3.0.0 greenkeeper/couchdb-calculate-session-id-1.1.3 greenkeeper/couchdb-calculate-session-id-pin-1.1.2 greenkeeper/coveralls-3.0.1 greenkeeper/lodash-4.17.1 greenkeeper/lodash-4.17.9 greenkeeper/nyc-11.8.0 greenkeeper/nyc-pin-11.7.3 greenkeeper/pouchdb-adapter-memory-6.3.0 greenkeeper/pouchdb-adapter-memory-6.3.1 greenkeeper/pouchdb-adapter-memory-6.3.2 greenkeeper/pouchdb-adapter-memory-6.3.3 greenkeeper/pouchdb-adapter-memory-pin-6.2.0 greenkeeper/pouchdb-core-6.1.0 greenkeeper/pouchdb-core-6.1.1 greenkeeper/pouchdb-core-6.1.2 greenkeeper/pouchdb-core-6.2.0 greenkeeper/pouchdb-core-6.3.0 greenkeeper/pouchdb-core-6.3.1 greenkeeper/pouchdb-core-6.3.2 greenkeeper/pouchdb-core-6.3.3 greenkeeper/pouchdb-core-pin-6.1.1 greenkeeper/pouchdb-core-pin-6.2.0 greenkeeper/pouchdb-errors-6.4.1 greenkeeper/pouchdb-errors-6.4.2 greenkeeper/pouchdb-errors-6.4.3 greenkeeper/pouchdb-mapreduce-6.3.0 greenkeeper/pouchdb-mapreduce-6.3.1 greenkeeper/pouchdb-mapreduce-6.3.2 greenkeeper/pouchdb-mapreduce-6.3.3 greenkeeper/pouchdb-mapreduce-6.4.0 greenkeeper/pouchdb-mapreduce-6.4.1 greenkeeper/pouchdb-mapreduce-6.4.2 greenkeeper/pouchdb-mapreduce-6.4.3 greenkeeper/pouchdb-mapreduce-pin-6.2.0 greenkeeper/pouchdb-mapreduce-pin-6.3.4 greenkeeper/semantic-release-15.0.0 greenkeeper/semantic-release-15.0.1 greenkeeper/semantic-release-15.0.2 greenkeeper/semantic-release-15.0.3 greenkeeper/semantic-release-15.1.2 greenkeeper/semantic-release-15.1.3 greenkeeper/semantic-release-15.1.4 greenkeeper/semantic-release-15.1.5 greenkeeper/semantic-release-15.1.6 greenkeeper/semantic-release-15.1.7 greenkeeper/semantic-release-15.1.8 greenkeeper/semantic-release-15.1.11 greenkeeper/semantic-release-15.3.0 greenkeeper/semantic-release-15.4.1 greenkeeper/semantic-release-15.4.2 greenkeeper/semantic-release-15.4.3 greenkeeper/semantic-release-15.4.4 greenkeeper/semantic-release-15.5.0 greenkeeper/tap-11.1.3 greenkeeper/tap-11.1.4 greenkeeper/tap-11.1.5 greenkeeper/tap-12.0.0 greenkeeper/tap-12.0.1 greenkeeper/tap-pin-10.2.1 greenkeeper/tap-pin-11.1.2 greenkeeper/uuid-3.2.1 initial-version master prepare-tests-for-camp-58 readme-mention-pouchdb
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.github
lib
test
.gitignore
.travis.yml
LICENSE
README.md
index.js
package.json

README.md

hoodie-account-server-api

Account JavaScript API backed by PouchDB

Build Status Coverage Status Dependency Status devDependency Status

@hoodie/account-server-api is a JavaScript API to manage user accounts and authentication backed by PouchDB. Features include account profiles and tokens.

Example

var AccountApi = require('@hoodie/account-server-api')
var PouchDB = require('pouchdb')

var api = new AccountApi({
  PouchDB: PouchDB,
  usersDb: 'my-users-db',
  secret: 'secret123'
})

API

@hoodie/account-server-api is a subset of hoodie-account-client/admin. If you see any inconsistencies, please create an issue

Constructor

new AccountApi(options)
Argument Type Description Required
options.PouchDB Object PouchDB constructor Yes
options.secret String Server secret, like CouchDB’s couch_httpd_auth.secret Yes
options.usersDb String Defaults to \_users No

Returns an api instance.

Examples

var PouchDB = require('pouchdb')
var api = new AccountApi({
  PouchDB: PouchDB,
  secret: 'secret123',
  usersDb: 'my-users-db'
})

api.sessions.add()

Admins can create a session for any user.

admin.sessions.add(options)
Argument Type Description Required
options.account.username String Token gets invalidated after first usage Yes (unless options.account.token set)
options.account.token String - Yes (unless options.account.username set)
options.account.password String Only applicable if options.account.username is set. If only username is passed then it’s assumed that an admin wants to create a session without any validation of user credentials. No
options.timeout Number Time from now until expiration of session in seconds. Defaults to no timeout. No

Resolves with sessionProperties

{
  id: 'session123',
  // account is always included
  account: {
    id: 'account456',
    username: 'pat@example.com'
  }
}

Rejects with:

UnauthenticatedError Session is invalid
UnconfirmedError Account has not been confirmed yet
NotFoundError Account could not be found
Error A custom error set on the account object, e.g. the account could be blocked due to missing payments
ConnectionError Could not connect to server

Examples

// create session if pat’s password is "secret"
admin.sessions.add({
  account: {
    username: 'pat',
    password: 'secret'
  }
}).then(function (sessionProperties) {
  var sessionId = sessionProperties.id
  var username = sessionProperties.account.username
}).catch(function (error) {
  console.error(error)
})
// create session for pat
admin.sessions.add({
  account: {
    username: 'pat'
  }
}).then(function (sessionProperties) {
  var sessionId = sessionProperties.id
  var username = sessionProperties.account.username
}).catch(function (error) {
  console.error(error)
})
// create session using a one-time auth token
admin.sessions.add({
  account: {
    token: 'secrettoken123'
  }
}).then(function (sessionProperties) {
  var sessionId = sessionProperties.id
  var username = sessionProperties.account.username
}).catch(function (error) {
  console.error(error)
})

api.sessions.find()

admin.sessions.find(sessionId)
Argument Type Description Required
sessionId String - Yes

Resolves with sessionProperties

{
  id: 'session123',
  // account is always included
  account: {
    id: 'account456',
    username: 'pat@example.com'
    // admin accounts have no profile
  }
}

Rejects with:

UnauthenticatedError Session is invalid
NotFoundError Session could not be found
ConnectionError Could not connect to server

Example

admin.sessions.find('abc4567').then(function (sessionProperties) {
  console.log('Session is valid.')
}).catch(function (error) {
  if (error.name === 'NotFoundError') {
    console.log('Session is invalid')
    return
  }

  console.error(error)
})

api.sessions.findAll()


🐕 TO BE DONE: #27


admin.sessions.findAll(options)
Argument Type Description Required
options.include String If set to "account.profile", the profile: {...} property will be added to the response. No
options.sort String or String[] string of comma-separated list of attributes to sort by, or array of strings, see JSON API: Sorting No
options.fields Object Map of fields to include in response by type, see JSON API: Sparse Fieldset No
options.page.offset Number see JSON API: Pagination No
options.page.limit Number see JSON API: Pagination No

Resolves with Array of sessionProperties

[{
  id: 'session123',
  account: {
    id: 'account456',
    username: 'pat@example.com'
  }
}, {
  id: 'session456',
  account: {
    id: 'account789',
    username: 'sam@example.com'
  }
}]

Rejects with:

UnauthenticatedError Session is invalid
ConnectionError Could not connect to server

Example

admin.sessions.findAll()
  .then(function (sessions) {})
  .catch(function (error) {
    console.error(error)
  })

api.sessions.remove()

admin.sessions.remove(sessionId)
Argument Type Description Required
sessionId String - Yes

Resolves with sessionProperties

{
  id: 'session123',
  account: {
    id: 'account456',
    username: 'pat@example.com'
  }
}

Rejects with:

UnauthenticatedError Session is invalid
NotFoundError Session could not be found
ConnectionError Could not connect to server

Example

admin.sessions.remove('abc4567')
  .then(function (sessionProperties) {})
  .catch(function (error) {
    console.error(error)
  })

NOTE: #27 Deleting a Session does not really have an effect today, as no session state is kept, and sessions are hash based


api.sessions.removeAll()


🐕 TO BE DONE: #27


admin.sessions.removeAll(options)
Argument Type Description Required
options.sort String or String[] string of comma-separated list of attributes to sort by, or array of strings, see JSON API: Sorting No
options.fields Object Map of fields to include in response by type, see JSON API: Sparse Fieldset No
options.page.offset Number see JSON API: Pagination No
options.page.limit Number see JSON API: Pagination No

Resolves with Array of sessionProperties

[{
  id: 'session123',
  account: {
    id: 'account456',
    username: 'pat@example.com'
  }
}, {
  id: 'session456',
  account: {
    id: 'account789',
    username: 'sam@example.com'
  }
}]

Rejects with:

UnauthenticatedError Session is invalid
ConnectionError Could not connect to server

Example

admin.sessions.removeAll()
  .then(function (sessions) {})
  .catch(function (error) {
    if (error.name === 'NotFoundError') {
      console.log('Session is invalid')
      return
    }

    console.error(error)
  })

api.accounts.add()

admin.accounts.add(object)
Argument Type Required
accountProperties.username String Yes
accountProperties.password String Yes

Resolves with accountProperties:

{
  "id": "account123",
  "username": "pat",
  "createdAt": "2016-01-01T00:00:00.000Z",
  "updatedAt": "2016-01-01T00:00:00.000Z",
  "profile": {
    "fullname": "Dr. Pat Hook"
  }
}

Rejects with:

UnauthenticatedError Session is invalid
InvalidError Username must be set
ConflictError Username <username> already exists
ConnectionError Could not connect to server

Example

admin.accounts.add({
  username: 'pat',
  password: 'secret',
  profile: {
    fullname: 'Dr Pat Hook'
  }
})
  .then(function (accountProperties) {})
  .catch(function (error) {
    console.error(error)
  })

api.accounts.find()

An account can be looked up by account.id, username or token.

  • If a username property is present, it will be looked up by username
  • If an id property is present, it will be looked up by accountId
  • If an token property is present, it will be looked up by token
admin.accounts.find(idOrObject, options)
Argument Type Description Required
idOrObject String account ID. Same as {id: accountId} No
idOrObject.id String account ID. Same as passing accountId as string No
idOrObject.username String Lookup account by username No
idOrObject.token String Lookup account by one-time token No
options.include String If set to "profile", the profile: {...} property will be added to the response No

Resolves with accountProperties:

{
  "id": "account123",
  "username": "pat",
  "createdAt": "2016-01-01T00:00:00.000Z",
  "updatedAt": "2016-01-01T00:00:00.000Z",
  // if options.include === 'profile'
  "profile": {
    "fullname": "Dr. Pat Hook"
  }
}

Rejects with:

UnauthenticatedError Session is invalid
NotFoundError Account not found
ConnectionError Could not connect to server

Example

admin.accounts.find({ username: 'pat' })
  .then(function (accountProperties) {})
  .catch(function (error) {
    console.error(error)
  })

api.accounts.findAll()

admin.accounts.findAll(options)
Argument Type Description Required
options.include String If set to "profile", the profile: {...} property will be added to the response. No
options.sort String or String[] string of comma-separated list of attributes to sort by, or array of strings, see JSON API: Sorting No
options.fields Object Map of fields to include in response by type, see JSON API: Sparse Fieldset No
options.page.offset Number see JSON API: Pagination No
options.page.limit Number see JSON API: Pagination No

Resolves with Array of accountProperties

[{
  "id": "account123",
  "username": "pat",
  "createdAt": "2016-01-01T00:00:00.000Z",
  "updatedAt": "2016-01-01T00:00:00.000Z",
  // if options.include === 'profile'
  "profile": {
    "fullname": "Dr. Pat Hook"
  }
}, {
  "id": "account456",
  "username": "sam",
  "createdAt": "2016-01-01T00:00:00.000Z",
  "updatedAt": "2016-01-01T00:00:00.000Z",
  // if options.include === 'profile'
  "profile": {
    "fullname": "Lady Samident"
  }
}]

Rejects with:

UnauthenticatedError Session is invalid
ConnectionError Could not connect to server

Example

admin.accounts.findAll()
  .then(function (accounts) {})
  .catch(function (error) {
    console.error(error)
  })

api.accounts.update()

An account can be looked up by account.id, username or token.

  • If a username property is present, it will be looked up by username
  • If an id property is present, it will be looked up by accountId
  • If an token property is present, it will be looked up by token
admin.accounts.update(idOrObject, changedProperties, options)
// or
admin.accounts.update(accountProperties, options)
Argument Type Description Required
idOrObject String account ID. Same as {id: accountId} No
idOrObject.id String account ID. Same as passing accountId as string No
idOrObject.username String Lookup account by username No
idOrObject.token String Lookup account by one-time token. Token gets invalidated after first usage No
changedProperties Object Object of properties & values that changed. Other properties remain unchanged. Yes
accountProperties Object Must have an id or a username property. The user’s account will be updated with the passed properties. Existing properties not passed remain unchanged. Yes
options.include String If set to "profile", the profile: {...} property will be added to the response. Defaults to "profile" if accountProperties.profile or changedProperties.profile is set. No

Resolves with accountProperties:

{
  "id": "account123",
  "username": "pat",
  "createdAt": "2016-01-01T00:00:00.000Z",
  "updatedAt": "2016-01-01T00:00:00.000Z",
  // if options.include === 'profile'
  "profile": {
    "fullname": "Dr. Pat Hook"
  }
}

Rejects with:

UnauthenticatedError Session is invalid
NotFoundError Account not found
ConnectionError Could not connect to server

Examples

admin.accounts.update({ username: 'pat' }, { foo: 'bar' })
  .then(function (accountProperties) {})
  .catch(function (error) {
    console.error(error)
  })
// same as
admin.accounts.update({ username: 'pat', foo: 'bar' })
  .then(function (accountProperties) {})
  .catch(function (error) {
    console.error(error)
  })

api.accounts.updateAll()


🐕 TO BE DONE: create issue and link it here


api.accounts.remove()

An account can be looked up by account.id, username or token.

  • If a username property is present, it will be looked up by username
  • If an id property is present, it will be looked up by accountId
  • If an token property is present, it will be looked up by token
admin.accounts.remove(idOrObject, changedProperties, options)
// or
admin.accounts.remove(accountProperties, options)
Argument Type Description Required
idOrObject String account ID. Same as {id: accountId} No
idOrObject.id String account ID. Same as passing accountId as string No
idOrObject.username String Lookup account by username No
idOrObject.token String Lookup account by one-time token No
changedProperties Object Object of properties & values that changed. Other properties remain unchanged. Yes
accountProperties Object Must have an id or a username property. The user’s account will be updated with the passed properties. Existing properties not passed remain unchanged. Note that accountProperties.token is not allowed, as it’s not a valid account property, but an option to look up an account. An account can have multiple tokens at once. Yes
options.include String If set to "profile", the profile: {...} property will be added to the response. Defaults to "profile" if accountProperties.profile or changedProperties.profile is set. No

Resolves with accountProperties:

{
  "id": "account123",
  "username": "pat",
  "createdAt": "2016-01-01T00:00:00.000Z",
  "updatedAt": "2016-02-01T00:00:00.000Z",
  "deletedAt": "2016-03-01T00:00:00.000Z",
  // if options.include === 'profile'
  "profile": {
    "fullname": "Dr. Pat Hook"
  }
}

Rejects with:

UnauthenticatedError Session is invalid
NotFoundError Account not found
ConnectionError Could not connect to server

Examples

admin.accounts.remove({ username: 'pat' }, { reason: 'foo bar' })
  .then(function (accountProperties) {})
  .catch(function (error) {
    console.error(error)
  })
// same as
admin.accounts.remove({ username: 'pat', reason: 'foo bar' })
  .then(function (accountProperties) {})
  .catch(function (error) {
    console.error(error)
  })

api.accounts.removeAll()


🐕 TO BE DONE: create issue and link it here


api.requests.add()


🐕 TO BE DONE: create issue and link it here


admin.requests.add({
  type: 'passwordreset',
  email: 'pat@example.com'
})

Resolves with

{
  id: 'request123',
  type: 'passwordreset',
  email: 'pat@example.com'
}

api.requests.find()


🐕 TO BE DONE: create issue and link it here


admin.requests.find('token123')
admin.requests.find({id: 'token123'})

api.requests.findAll()


🐕 TO BE DONE: create issue and link it here


admin.requests.findAll()

api.requests.remove()


🐕 TO BE DONE: create issue and link it here


admin.requests.remove('token123')
admin.requests.find({id: 'token123'})

api.requests.removeAll()


🐕 TO BE DONE: create issue and link it here


api.account()

The admin.account method returns a scoped API for one account, see below

var account = admin.account(idOrObject)

Examples

admin.account('account123')
admin.account({id: 'account123'})
admin.account({username: 'pat@example.com'})
admin.account({token: 'token456'})

api.account().profile.find()


🐕 TO BE DONE: create issue and link it here


admin.account(idOrObject).profile.find()

resolves with profileProperties

{
  "id": "account123-profile",
  "fullname": "Dr Pat Hook",
  "address": {
    "city": "Berlin",
    "street": "Adalberststraße 4a"
  }
}

api.account().profile.update()


🐕 TO BE DONE: create issue and link it here


admin.account(idOrObject).profile.update(changedProperties)

resolves with profileProperties

{
  "id": "account123-profile",
  "fullname": "Dr Pat Hook",
  "address": {
    "city": "Berlin",
    "street": "Adalberststraße 4a"
  }
}

api.account().tokens.add()

admin.account('account123').tokens.add(properties)
Argument Type Description Required
properties.type String Every token needs a type, for example "passwordreset" Yes
properties.id String Optional token id. If none is passed, a UUID will be generated No
properties.timeout Number Time from now until expiration of token in seconds. Defaults to 7200 (2 hours) No

resolves with tokenProperties

{
  "id": "token123",
  "type": "passwordreset",
  "accountId": "account123",
  "contact": "pat@example.com",
  "createdAt": "2016-01-01T00:00:00.000Z"
}

Rejects with:

NotFoundError Account not found
ConnectionError Could not connect to server

Example

admin.account({username: 'pat@example.com'}).account.tokens.add({
  type: 'passwordreset',
  email: 'pat@example.com'
})

api.account().tokens.find()

admin.account(idOrObject).tokens.find(id)
Argument Type Description Required
id String token id Yes

resolves with tokenProperties

{
  "id": "token123",
  "type": "passwordreset",
  "accountId": "account123",
  "contact": "pat@example.com",
  "createdAt": "2016-01-01T00:00:00.000Z"
}

Rejects with:

NotFoundError Account not found
ConnectionError Could not connect to server

Example

admin.account({username: 'pat'}).tokens.find('token123')

api.account().tokens.findAll()


🐕 TO BE DONE: create issue and link it here


admin.account(idOrObject).tokens.findAll(options)

resolves with array of tokenProperties

[{
  "id": "token123",
  "type": "passwordreset",
  "accountId": "account123",
  "contact": "pat@example.com",
  "createdAt": "2016-01-01T00:00:00.000Z"
}, {
  "id": "token456",
  "type": "session",
  "accountId": "account123",
  "createdAt": "2016-01-02T00:00:00.000Z"
}]

Example

admin.account({username: 'pat'}).tokens.findAll()
  .then(function (tokens) {})
  .catch(function (error) {
    console.error(error)
  })

api.account().tokens.remove()


🐕 TO BE DONE: create issue and link it here


admin.account(idOrObject).tokens.remove(idOrObject)

resolves with tokenProperties

{
  "id": "token123",
  "type": "passwordreset",
  "accountId": "account123",
  "contact": "pat@example.com",
  "createdAt": "2016-01-01T00:00:00.000Z"
}

Example

admin.account({username: 'pat'}).tokens.removes('token123')

api.account().roles.add()


🐕 TO BE DONE: create issue and link it here


admin.account(idOrObject).roles.add(name)

resolves with roleName

"mycustomrole"

Example

admin.account({username: 'pat'}).roles.add('mycustomrole')

api.account().roles.findAll()


🐕 TO BE DONE: create issue and link it here


admin.account(idOrObject).roles.add(name)

resolves with array of roleNames

["mycustomrole", "myothercustomrole"]

Example

admin.account({username: 'pat'}).roles.findAll()
  .then(function (roles) {})
  .catch(function (error) {
    console.error(error)
  })

api.account().roles.remove()


🐕 TO BE DONE: create issue and link it here


admin.account(idOrObject).roles.remove(name)

resolves with roleName

"mycustomrole"

Example

admin.account({username: 'pat'}).roles.remove('mycustomrole')

Events


🐕 TO BE DONE: #35


Events emitted on

  • admin.sessions
  • admin.accounts
  • admin.requests
change triggered for any add, update and remove event
add
update
remove
admin.sessions.on('change', function (eventName, session) {})
admin.accounts.on('update', function (account) {})
admin.requests.on('remove', handler)

Contributing

Have a look at the Hoodie project's contribution guidelines. If you want to hang out you can join our Hoodie Community Chat.

Testing

Local setup

git clone https://github.com/hoodiehq/hoodie-account-server-api.git
cd hoodie-account-server-api
npm install

Run all tests and code style checks

npm test

If you want to run a single test you can do it with

./node_modules/.bin/tap test/unit/sessions/remove-test.js

License

Apache 2.0