@toznysecure/account-sdk

Javascript SDK for performing Tozny platform account level operations

Usage no npm install needed!

<script type="module">
  import toznysecureAccountSdk from 'https://cdn.skypack.dev/@toznysecure/account-sdk';
</script>

README

Tozny JS Account SDK

Build Status

Javascript SDK for performing Tozny platform account level operations

Approach

The Tozny platform client SDKs abstract over working with the Tozny platform as a client. In addition to client level operations, Tozny offers endpoints which perform various account level operations—they control how the overall account operates. This functionality is only useful to a very small subset of Tozny clients. For this reason it does not make sense to ship the full set of account operation in the client SDK. It also does not make sense to rewrite all of the request handling and cryptographic operations in another SDK for handling account operations. Especially as different cryptographic methods and target are defined. To reconcile this, the account SDK in this repository consumes a valid instance of a Tozny client implementation and layers functionality on top of it enabling account level operations. This allows the crypto to remain context specific while the account operations are defined in a single package.

Getting Started

To use the SDK, first require it as well as the Tozny client SDK package.

npm install @toznysecure/sdk
npm install @toznysecure/account-sdk --production

The --production flag, though not necessary, installs the library without the any build & dev dependencies.

Create a new Account connection object -- this defines the API and specific client SDK in use.

const { Account } = require('@toznysecure/account-sdk')
const Tozny = require('@toznysecure/sdk/node')

const account = new Account(Tozny, 'http://platform.local.tozny.com:8000') // where the second parameter is the API url for the Tozny platform instance

The Account instance provides methods for creating a account client in various ways. The client object returned from each is identical in use and operation, but the method matches a context specific means of creating the client. See the Account class definition for the full list of available methods and return values.

Example

With the account object created above, you can use the login method as the standard means of getting a client instance for a pre-existing client. This is an asynchronous operation -- it returns a promise. This can be used in a promise chain, or in an async/await style function.

let accountClient
async getClient() {
  if(!accountClient) {
    const email = process.env.ACCOUNT_EMAIL
    const password = process.env.ACCOUNT_PASSWORD
    accountClient = await account.login(email, password)
  }
  return accountClient
}

async getRealms() {
  const accountClient = await getClient()
   return accountClient.listRealms()
}

getRealms().then(console.log)

This caches the client connection in a scope variable so it doesn't need to log in each time. Application methods can then use the client whenever they need to run account-level transactions. This would typically be done as a state value, or stored in an object as a larger part of an application.

Account Clients

Account clients provide managed access to account level operations. The actual HTTP request sequence, authentication method, etc. is hidden behind the higher level methods. This allows us to maintain a consistent API for use in account applications and change implementation details as needed (e.g. a new endpoint, a different auth method, etc.). These higher level methods should correspond with specific account actions (e.g. createWebHook, etc). The parameters will provide insight into what is required to perform those operations.

Usage Examples

This is a limited set of operations that may be useful. Additional methods are available, review the account object and client object source to see the full list of methods available.

Register a new account

const name = 'Jon Snow'
const email = 'jsnow@example.com'
const password = 'keep this secret'
const accountClient = await account.register(name, email, password)

Log in to an existing account

const email = 'jsnow@example.com'
const password = 'keep this secret'
const accountClient = await account.login(email, password)

Serialize account for string-based storage and recreate it from the serialization

const serializedString = JSON.stringify(accountClient.serialize())
accountClient = account.fromObject(JSON.parse(serializedString))
await accountClient.refreshProfile()

Change the password for an account

const password = 'keep this secret'
const newPassword = 'hide this safely'
await accountClient.changePassword({ password, newPassword })

Gather account billing information

const billingInfo = await accountClient.billingStatus()
console.log(billingInfo.accountActive)
console.log(billingInfo.isGoodStanding)

List Registration tokens in the account

const tokens = await accountClient.registrationTokens()
for (let token of tokens) {
  console.log(token.name)
  console.log(token.token)
}

Create a registration token

const name = 'example token'
const permissions = {
  enabled: true,
  allowed_types: ['general', 'identity'],
}
const token = await accountClient.newRegistrationToken(name, permissions)

Delete a registration token

const tokenString = fetchedToken.token
await accountClient.deleteRegistrationToken(tokenString)

List TozStore clients in the account

let clientList = await accountClient.listClientInfo()
for (let clientInfo of clientList.clients) {
  console.log(clientInfo.clientId)
}
// next page
clientList = await accountClient.listClientInfo(clientList.nextToken)
for (let clientInfo of clientList.clients) {
  console.log(clientInfo.clientId)
}

Fetch an individual TozStore client's info

const clientId = '000000000000-0000-0000-0000-00000000'
const clientInfo = accountClient.getClientInfo(clientId)
console.log(clientInfo.publicKey)
console.log(clientInfo.enabled)
console.log(clientInfo.hasBackup)

Disable/Enable a TozStore client

const clientId = '000000000000-0000-0000-0000-00000000'
// disable
accountClient.setClientEnabled(clientId, false)
// enable
accountClient.setClientEnabled(clientId, true)

List Identity Realms

const realmList = await accountClient.listRealms()
for (let realm of realmList.realms) {
  console.log(realm.name)
}

Create and Identity Realm and Realm Broker

// create a realm
const registrationToken = tokenInfo.token // Must have permissions to register 'broker' clients
const realmName = 'westeros'
const sovereignName = 'cersei'
const createdRealm = await accountClient.createRealm(realmName, sovereignName)
const realmBrokerIdentity = await accountClient.registerRealmBrokerIdentity(
  createdRealm.name,
  registrationToken
)

Delete a realm

const realmName = 'westeros'
await accountClient.deleteRealm(realmName)

Create a group

const realmName = 'westeros'
const myNewGroup = {
  name: 'WhiteWalkers',
}
const group = await accountClient.createRealmGroup(realmName, myNewGroup)

Fetch an existing realm group

const realmName = 'westeros'
const groupId = '000000000000-0000-0000-0000-00000000'
const group = await accountClient.describeRealmGroup(realmName, groupId)

Update a realm group

const realmName = 'westeros'
const groupId = '000000000000-0000-0000-0000-00000000'
const group = await accountClient.describeRealmGroup(realmName, groupId)

group.name = 'WhiteWalkers'
group.attributes.status = 'undead'
group.attributes.temperature = 'cold'
await accountClient.updateRealmGroup(realmName, group)

List groups in a realm

const realmName = 'westeros'
const groups = await accountClient.listRealmGroups(realmName)

Delete a realm group

const realmName = 'westeros'
const groupId = '000000000000-0000-0000-0000-00000000'
await accountClient.deleteRealmGroup(realmName, groupId)

Create a realm role

const realmName = 'westeros'
const myNewRole = {
  name: 'NightsWatch',
  description: 'Offer protection from northern baddies.',
}
const role = await accountClient.createRealmRole(realmName, myNewRole)

Fetch an existing realm role

const realmName = 'westeros'
const roleId = '000000000000-0000-0000-0000-00000000'
const role = await accountClient.describeRealmRole(realmName, roleId)

Update a realm role

const realmName = 'westeros'
const roleId = '000000000000-0000-0000-0000-00000000'
const role = await accountClient.describeRealmRole(realmName, roleId)

role.name = 'WhiteWalkers'
role.description = 'Updated'
await accountClient.updateRealmRole(realmName, role)

List roles in a realm

const realmName = 'westeros'
const roles = await accountClient.listRealmRoles(realmName)

Delete a realm role

const realmName = 'westeros'
const roleId = '000000000000-0000-0000-0000-00000000'
await accountClient.deleteRealmRole(realmName, roleId)

Create a new realm application role

const realmName = 'westeros'
const applicationId = '000000000000-0000-0000-0000-00000000'
const myNewRole = {
  name: 'NightsWatch',
  description: 'Offer protection from northern baddies.',
}
const role = await accountClient.createRealmApplicationRole(realmName, applicationId, myNewRole)

Fetch an existing realm application role

const realmName = 'westeros'
const applicationId = '000000000000-0000-0000-0000-00000000'
const roleName = 'NightsWatch'
const role = await accountClient.describeRealmApplicationRole(realmName, applicationId, roleName)

Update a realm application role

const realmName = 'westeros'
const applicationId = '000000000000-0000-0000-0000-00000000'
const roleName = 'NightsWatch'
const role = await accountClient.describeRealmApplicationRole(realmName, applicationId, roleName)

const originalRoleName = roleName
role.name = 'WhiteWalkers'
role.description = 'Updated'
await accountClient.updateRealmApplicationRole(realmName, applicationId, originalRoleName, role)

List application roles in a realm

const realmName = 'westeros'
const applicationId = '000000000000-0000-0000-0000-00000000'
const roles = await accountClient.listRealmApplicationRoles(realmName, applicationId)

Delete a realm application role

const realmName = 'westeros'
const applicationId = '000000000000-0000-0000-0000-00000000'
const roleName = 'NightsWatch'
await accountClient.deleteRealmApplicationRole(realmName, applicationId, roleName)

List identities in a realm

const realmName = 'westeros'
// list identities in westeros 10 at a time
const max = 10
const idList = accountClient.listIdentities(realmName, max)
while (!idList.done) {
  const identities = await idList.next()
  for (let identity of identities) {
    console.log(identity.username)
  }
}
// Note: If the value of max is higher than the maximum allowed by the server, idList.next() will only return up to the number of identities allowed by the server

Register an Identity in a Realm

// Note: Identities should be registered using Tozny's JS SDK (@toznysecure/sdk/node) instead of the account client. The account client's registerIdentity() is intended for internal use.

// Create a token
const token = await accountClient.newRegistrationToken(tokenName, permissions)

// Create a Realm object
const realmName = 'realmName'
const appName = 'account'
const brokerTargetUrl = 'https://id.tozny.com/example/recover'
const apiUrl = 'https://api.e3db.com'
const realm = new Tozny.identity.Realm(
  realmName,
  appName,
  brokerTargetUrl,
  apiUrl
)

// Register Identity
const username = 'user'
identity = await realm.register(
  username,
  'secure-password',
  token.token,
  'email@example.com'
)

Delete Identity from Realm

// Get the identityId you wish to delete
const identity = accountClient.identityDetails(realmName, username)

// Delete identity
await accountClient.deleteIdentity(realmName, identity.toznyId)

Get details about an identity in a realm

const realmName = 'westeros'
const username = 'jaime'
// get details about jaime, including roles and groups
const details = accountClient.identityDetails(realmName, username)
for (let group in details.groups) {
  console.log(group.name)
}
for (let realmRole in details.roles.realm) {
  console.log(realmRole.name)
}
// where 'kingGuard' is the name of a client application in the realm
for (let clientRole in details.roles.client.kingGuard) {
  console.log(clientRole.name)
}

Get Identity Group membership

const identity = await accountClient.identityDetails(realmName, username)
const groupList = await client.groupMembership(realmName, identity.toznyId)

Update Identity Group membership

const toznyEngineersGroup = await client.createRealmGroup(realmName, {
  name: 'ToznyEngineers',
})
await client.updateGroupMembership(realmName, identityId, {
  groups: [toznyEngineersGroup.id],
})

Join Realm Groups

const toznyEngineersGroup = await client.createRealmGroup(realmName, {
  name: 'ToznyEngineers',
})
await client.joinGroups(realmName, identityId, {
  groups: [toznyEngineersGroup.id],
})

Leave Realm Groups

const toznyEngineersGroup = await client.createRealmGroup(realmName, {
  name: 'ToznyEngineers',
})
await client.joinGroups(realmName, identityId, {
  groups: [toznyEngineersGroup.id],
})
await client.leaveGroups(realmName, identityId, {
  groups: [toznyEngineersGroup.id],
})

Default Realm Groups When a new user is created in a realm they are added to each defaultRealmGroup note: when default realm groups changed existing users groups are not updated

List Default Realm Groups

const groupList = await client.listDefaultRealmGroups(realmName)

Replace Default Realm Groups

const toznyEngineersGroup = await client.createRealmGroup(realmName, {
  name: 'ToznyEngineers',
})
await client.replaceDefaultRealmGroups(realmName, {
  groups: [toznyEngineersGroup.id],
})

Add Default Realm Groups

const toznyEngineersGroup = await client.createRealmGroup(realmName, {
  name: 'ToznyEngineers',
})
await client.addDefaultRealmGroups(realmName, {
  groups: [toznyEngineersGroup.id],
})

Remove Default Realm Groups

const toznyEngineersGroup = await client.createRealmGroup(realmName, {
  name: 'ToznyEngineers',
})
await client.addDefaultRealmGroups(realmName, {
  groups: [toznyEngineersGroup.id],
})
await client.removeDefaultRealmGroups(realmName, {
  groups: [toznyEngineersGroup.id],
})

Terms of Service

Your use of the Tozny JavaScript SDK must abide by our Terms of Service, as detailed in the linked document.