Account JavaScript API backed by PouchDB
@hoodie/account-server-api
is a JavaScript API to manage user accounts and
authentication backed by PouchDB. Features include account profiles and tokens.
var AccountApi = require('@hoodie/account-server-api')
var PouchDB = require('pouchdb')
var api = new AccountApi({
PouchDB: PouchDB,
usersDb: 'my-users-db',
secret: 'secret123'
})
@hoodie/account-server-api
is a subset of hoodie-account-client/admin.
If you see any inconsistencies, please create an issue
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'
})
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)
})
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)
})
🐕 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)
})
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
🐕 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)
})
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)
})
An account can be looked up by account.id, username or token.
username
property is present, it will be looked up by usernameid
property is present, it will be looked up by accountIdtoken
property is present, it will be looked up by tokenadmin.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)
})
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)
})
An account can be looked up by account.id, username or token.
username
property is present, it will be looked up by usernameid
property is present, it will be looked up by accountIdtoken
property is present, it will be looked up by tokenadmin.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)
})
🐕 TO BE DONE: create issue and link it here
An account can be looked up by account.id, username or token.
username
property is present, it will be looked up by usernameid
property is present, it will be looked up by accountIdtoken
property is present, it will be looked up by tokenadmin.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)
})
🐕 TO BE DONE: create issue and link it here
🐕 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'
}
🐕 TO BE DONE: create issue and link it here
admin.requests.find('token123')
admin.requests.find({id: 'token123'})
🐕 TO BE DONE: create issue and link it here
admin.requests.findAll()
🐕 TO BE DONE: create issue and link it here
admin.requests.remove('token123')
admin.requests.find({id: 'token123'})
🐕 TO BE DONE: create issue and link it here
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'})
🐕 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"
}
}
🐕 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"
}
}
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'
})
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')
🐕 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)
})
🐕 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')
🐕 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')
🐕 TO BE DONE: create issue and link it here
admin.account(idOrObject).roles.add(name)
resolves with array of roleName
s
["mycustomrole", "myothercustomrole"]
Example
admin.account({username: 'pat'}).roles.findAll()
.then(function (roles) {})
.catch(function (error) {
console.error(error)
})
🐕 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')
🐕 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)
Have a look at the Hoodie project's contribution guidelines. If you want to hang out you can join our Hoodie Community Chat.
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