refactor: Functionize identity provider. (#74)

* refactor: Functionize identity provider.

* docs: Building a custom identity provider.

* refactor: Functionize clock.
This commit is contained in:
Hayden Young 2023-05-25 00:48:01 +08:00 committed by GitHub
parent 60fbe47ee3
commit 794136c762
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 331 additions and 309 deletions

View File

@ -47,7 +47,7 @@ const type = 'ipfs'
* @param {Array} [params.write] An array of identity ids who can write to the * @param {Array} [params.write] An array of identity ids who can write to the
* database. * database.
* @param {module:Storage} [params.storage] An instance of a compatible storage. * @param {module:Storage} [params.storage] An instance of a compatible storage.
* @returns {module:AccessControllers.AccessControllers-IPFS} An * @return {module:AccessControllers.AccessControllers-IPFS} An
* IPFSAccessController function. * IPFSAccessController function.
* @memberof module:AccessControllers * @memberof module:AccessControllers
*/ */
@ -70,7 +70,7 @@ const IPFSAccessController = ({ write, storage } = {}) => async ({ orbitdb, iden
/** /**
* Verifies the write permission of an entry. * Verifies the write permission of an entry.
* @param {module:Log~Entry} entry An entry to verify. * @param {module:Log~Entry} entry An entry to verify.
* @returns {boolean} True if the entry's identity has write permission, * @return {boolean} True if the entry's identity has write permission,
* false otherwise. * false otherwise.
* @memberof module:AccessControllers.AccessControllers-IPFS * @memberof module:AccessControllers.AccessControllers-IPFS
*/ */

View File

@ -27,7 +27,7 @@ const type = 'orbitdb'
* IPFSAccessController. * IPFSAccessController.
* @param {Array} [params.write] An array of identity ids who can write to the * @param {Array} [params.write] An array of identity ids who can write to the
* database. * database.
* @returns {module:AccessControllers.AccessControllers-OrbitDB} An * @return {module:AccessControllers.AccessControllers-OrbitDB} An
* IPFSAccessController function. * IPFSAccessController function.
* @memberof module:AccessControllers * @memberof module:AccessControllers
*/ */
@ -42,7 +42,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities
/** /**
* Verifies the write permission of an entry. * Verifies the write permission of an entry.
* @param {module:Log~Entry} entry An entry to verify. * @param {module:Log~Entry} entry An entry to verify.
* @returns {boolean} True if the entry's identity has write permission, * @return {boolean} True if the entry's identity has write permission,
* false otherwise. * false otherwise.
* @memberof module:AccessControllers.AccessControllers-OrbitDB * @memberof module:AccessControllers.AccessControllers-OrbitDB
* @instance * @instance
@ -68,7 +68,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities
* *
* The returned capabilities will be a mixture of admin and write access * The returned capabilities will be a mixture of admin and write access
* addresses. * addresses.
* @returns {Array} A list of addresses with admin and write access. * @return {Array} A list of addresses with admin and write access.
* @memberof module:AccessControllers.AccessControllers-OrbitDB * @memberof module:AccessControllers.AccessControllers-OrbitDB
* @instance * @instance
*/ */
@ -98,7 +98,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities
/** /**
* Gets a list of addresses with the specified capability. * Gets a list of addresses with the specified capability.
* @param {string} capability A capability (e.g. write). * @param {string} capability A capability (e.g. write).
* @returns {Array} One or more addresses with the spcified capability. * @return {Array} One or more addresses with the spcified capability.
* @memberof module:AccessControllers.AccessControllers-OrbitDB * @memberof module:AccessControllers.AccessControllers-OrbitDB
* @instance * @instance
*/ */
@ -120,7 +120,7 @@ const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities
* Checks whether an address has a capability. * Checks whether an address has a capability.
* @param {string} capability A capability (e.g. write). * @param {string} capability A capability (e.g. write).
* @param {string} key An address. * @param {string} key An address.
* @returns {boolean} True if the address has the capability, false * @return {boolean} True if the address has the capability, false
* otherwise. * otherwise.
* @memberof module:AccessControllers.AccessControllers-OrbitDB * @memberof module:AccessControllers.AccessControllers-OrbitDB
* @instance * @instance

View File

@ -61,7 +61,7 @@ const parseAddress = (address) => {
* Creates an instance of OrbitDBAddress. * Creates an instance of OrbitDBAddress.
* @function * @function
* @param {OrbitDBAddress|string} address A valid OrbitDB database address. * @param {OrbitDBAddress|string} address A valid OrbitDB database address.
* @returns {OrbitDBAddress} An instance of OrbitDBAddress. * @return {OrbitDBAddress} An instance of OrbitDBAddress.
* @instance * @instance
*/ */
const OrbitDBAddress = (address) => { const OrbitDBAddress = (address) => {
@ -89,7 +89,7 @@ const OrbitDBAddress = (address) => {
/** /**
* Returns OrbitDBAddress as a string. * Returns OrbitDBAddress as a string.
* @function * @function
* @returns {string} The string form of OrbitDBAddress. * @return {string} The string form of OrbitDBAddress.
* @memberof module:Address~OrbitDBAddress * @memberof module:Address~OrbitDBAddress
*/ */
const toString = () => { const toString = () => {

View File

@ -54,7 +54,7 @@ const DefaultOptions = { indexBy: '_id' }
* Defines a Documents database. * Defines a Documents database.
* @param {Object} options Various options for configuring the Document store. * @param {Object} options Various options for configuring the Document store.
* @param {string} [params.indexBy=_id] An index. * @param {string} [params.indexBy=_id] An index.
* @returns {module:Database.Database-Documents} A Documents function. * @return {module:Database.Database-Documents} A Documents function.
* @memberof module:Database * @memberof module:Database
*/ */
const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => {
@ -66,7 +66,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
* Stores a document to the store. * Stores a document to the store.
* @function * @function
* @param {Object} doc An object representing a key/value list of fields. * @param {Object} doc An object representing a key/value list of fields.
* @returns {string} The hash of the new oplog entry. * @return {string} The hash of the new oplog entry.
* @memberof module:Database.Database-Documents * @memberof module:Database.Database-Documents
* @instance * @instance
*/ */
@ -82,7 +82,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
* Deletes a document from the store. * Deletes a document from the store.
* @function * @function
* @param {string} key The key of the doc to delete. * @param {string} key The key of the doc to delete.
* @returns {string} The hash of the new oplog entry. * @return {string} The hash of the new oplog entry.
* @memberof module:Database.Database-Documents * @memberof module:Database.Database-Documents
* @instance * @instance
*/ */
@ -96,7 +96,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
* Gets a document from the store by key. * Gets a document from the store by key.
* @function * @function
* @param {string} key The key of the doc to get. * @param {string} key The key of the doc to get.
* @returns {Object} The doc corresponding to key or null. * @return {Object} The doc corresponding to key or null.
* @memberof module:Database.Database-Documents * @memberof module:Database.Database-Documents
* @instance * @instance
*/ */
@ -113,7 +113,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
* @function * @function
* @param {function(Object)} findFn A function for querying for specific * @param {function(Object)} findFn A function for querying for specific
* results. * results.
* @returns {Array} Found documents. * @return {Array} Found documents.
* @memberof module:Database.Database-Documents * @memberof module:Database.Database-Documents
* @instance * @instance
*/ */
@ -132,8 +132,8 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
/** /**
* Iterates over documents. * Iterates over documents.
* @function * @function
* @params {Object} [filters={}] Various filters to apply to the iterator. * @param {Object} [filters={}] Various filters to apply to the iterator.
* @params {string} [filters.amount=-1] The number of results to fetch. * @param {string} [filters.amount=-1] The number of results to fetch.
* @yields [string, string, string] The next document as hash/key/value. * @yields [string, string, string] The next document as hash/key/value.
* @memberof module:Database.Database-Documents * @memberof module:Database.Database-Documents
* @instance * @instance
@ -160,7 +160,7 @@ const Documents = ({ indexBy } = DefaultOptions) => async ({ ipfs, identity, add
/** /**
* Returns all documents. * Returns all documents.
* @function * @function
* @returns [][string, string, string] An array of documents as hash/key * @return [][string, string, string] An array of documents as hash/key
* value entries. * value entries.
* @memberof module:Database.Database-Documents * @memberof module:Database.Database-Documents
* @instance * @instance

View File

@ -37,7 +37,7 @@ import Database from '../database.js'
/** /**
* Defines an Events database. * Defines an Events database.
* @returns {module:Database.Database-Events} A Events function. * @return {module:Database.Database-Events} A Events function.
* @memberof module:Database * @memberof module:Database
*/ */
const Events = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { const Events = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => {
@ -49,7 +49,7 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory,
* Adds an event to the store. * Adds an event to the store.
* @function * @function
* @param {*} value The event to be added. * @param {*} value The event to be added.
* @returns {string} The hash of the new oplog entry. * @return {string} The hash of the new oplog entry.
* @memberof module:Database.Database-Events * @memberof module:Database.Database-Events
* @instance * @instance
*/ */
@ -61,7 +61,7 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory,
* Gets an event from the store by hash. * Gets an event from the store by hash.
* @function * @function
* @param {string} hash The hash of the event to get. * @param {string} hash The hash of the event to get.
* @returns {*} The value corresponding to hash or null. * @return {*} The value corresponding to hash or null.
* @memberof module:Database.Database-Events * @memberof module:Database.Database-Events
* @instance * @instance
*/ */
@ -73,16 +73,16 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory,
/** /**
* Iterates over events. * Iterates over events.
* @function * @function
* @params {Object} [filters={}] Various filters to apply to the iterator. * @param {Object} [filters={}] Various filters to apply to the iterator.
* @params {string} [filters.gt] All events which are greater than the * @param {string} [filters.gt] All events which are greater than the
* given hash. * given hash.
* @params {string} [filters.gte] All events which are greater than or equal * @param {string} [filters.gte] All events which are greater than or equal
* to the given hash. * to the given hash.
* @params {string} [filters.lt] All events which are less than the given * @param {string} [filters.lt] All events which are less than the given
* hash. * hash.
* @params {string} [filters.lte] All events which are less than or equal to * @param {string} [filters.lte] All events which are less than or equal to
* the given hash. * the given hash.
* @params {string} [filters.amount=-1] The number of results to fetch. * @param {string} [filters.amount=-1] The number of results to fetch.
* @yields [string, string] The next event as hash/value. * @yields [string, string] The next event as hash/value.
* @memberof module:Database.Database-Events * @memberof module:Database.Database-Events
* @instance * @instance
@ -99,7 +99,7 @@ const Events = () => async ({ ipfs, identity, address, name, access, directory,
/** /**
* Returns all events. * Returns all events.
* @function * @function
* @returns [][string, string] An array of events as hash/value entries. * @return [][string, string] An array of events as hash/value entries.
* @memberof module:Database.Database-Events * @memberof module:Database.Database-Events
* @instance * @instance
*/ */

View File

@ -54,7 +54,7 @@ const valueEncoding = 'json'
* @param {Object} options Various options for configuring the KeyValueIndexed * @param {Object} options Various options for configuring the KeyValueIndexed
* store. * store.
* @param {module:Storage} [storage=LevelStorage] A compatible storage. * @param {module:Storage} [storage=LevelStorage] A compatible storage.
* @returns {module:Database.Database-KeyValueIndexed} A KeyValueIndexed * @return {module:Database.Database-KeyValueIndexed} A KeyValueIndexed
* function. * function.
* @memberof module:Database * @memberof module:Database
*/ */
@ -93,7 +93,7 @@ const KeyValueIndexed = ({ storage } = {}) => async ({ ipfs, identity, address,
* Gets a value from the store by key. * Gets a value from the store by key.
* @function * @function
* @param {string} key The key of the value to get. * @param {string} key The key of the value to get.
* @returns {*} The value corresponding to key or null. * @return {*} The value corresponding to key or null.
* @memberof module:Database.Database-KeyValueIndexed * @memberof module:Database.Database-KeyValueIndexed
* @instance * @instance
*/ */
@ -108,8 +108,8 @@ const KeyValueIndexed = ({ storage } = {}) => async ({ ipfs, identity, address,
/** /**
* Iterates over keyvalue pairs. * Iterates over keyvalue pairs.
* @function * @function
* @params {Object} [filters={}] Various filters to apply to the iterator. * @param {Object} [filters={}] Various filters to apply to the iterator.
* @params {string} [filters.amount=-1] The number of results to fetch. * @param {string} [filters.amount=-1] The number of results to fetch.
* @yields [string, string, string] The next key/value as key/value/hash. * @yields [string, string, string] The next key/value as key/value/hash.
* @memberof module:Database.Database-KeyValueIndexed * @memberof module:Database.Database-KeyValueIndexed
* @instance * @instance

View File

@ -37,7 +37,7 @@ import Database from '../database.js'
/** /**
* Defines an KeyValue database. * Defines an KeyValue database.
* @returns {module:Database.Database-KeyValue} A KeyValue function. * @return {module:Database.Database-KeyValue} A KeyValue function.
* @memberof module:Database * @memberof module:Database
*/ */
const KeyValue = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => { const KeyValue = () => async ({ ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically, onUpdate }) => {
@ -50,7 +50,7 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory
* @function * @function
* @param {string} key The key to store. * @param {string} key The key to store.
* @param {*} value The value to store. * @param {*} value The value to store.
* @returns {string} The hash of the new oplog entry. * @return {string} The hash of the new oplog entry.
* @memberof module:Database.Database-KeyValue * @memberof module:Database.Database-KeyValue
* @instance * @instance
*/ */
@ -73,7 +73,7 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory
* Gets a value from the store by key. * Gets a value from the store by key.
* @function * @function
* @param {string} key The key of the value to get. * @param {string} key The key of the value to get.
* @returns {*} The value corresponding to key or null. * @return {*} The value corresponding to key or null.
* @memberof module:Database.Database-KeyValue * @memberof module:Database.Database-KeyValue
* @instance * @instance
*/ */
@ -91,8 +91,8 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory
/** /**
* Iterates over keyvalue pairs. * Iterates over keyvalue pairs.
* @function * @function
* @params {Object} [filters={}] Various filters to apply to the iterator. * @param {Object} [filters={}] Various filters to apply to the iterator.
* @params {string} [filters.amount=-1] The number of results to fetch. * @param {string} [filters.amount=-1] The number of results to fetch.
* @yields [string, string, string] The next key/value as key/value/hash. * @yields [string, string, string] The next key/value as key/value/hash.
* @memberof module:Database.Database-KeyValue * @memberof module:Database.Database-KeyValue
* @instance * @instance
@ -119,7 +119,7 @@ const KeyValue = () => async ({ ipfs, identity, address, name, access, directory
/** /**
* Returns all key/value pairs. * Returns all key/value pairs.
* @function * @function
* @returns [][string, string, string] An array of key/value pairs as * @return [][string, string, string] An array of key/value pairs as
* key/value/hash entries. * key/value/hash entries.
* @memberof module:Database.Database-KeyValue * @memberof module:Database.Database-KeyValue
* @instance * @instance

View File

@ -13,7 +13,7 @@ import KeyStore, { signMessage, verifyMessage } from '../key-store.js'
import { LRUStorage, IPFSBlockStorage, MemoryStorage, ComposedStorage } from '../storage/index.js' import { LRUStorage, IPFSBlockStorage, MemoryStorage, ComposedStorage } from '../storage/index.js'
import pathJoin from '../utils/path-join.js' import pathJoin from '../utils/path-join.js'
const DefaultProviderType = PublicKeyIdentityProvider.type const DefaultProviderType = 'publickey'
const DefaultIdentityKeysPath = pathJoin('./orbitdb', 'identities') const DefaultIdentityKeysPath = pathJoin('./orbitdb', 'identities')
const supportedTypes = { const supportedTypes = {
@ -36,7 +36,7 @@ const supportedTypes = {
* module. * module.
* @param {IPFS} [params.ipfs] An instance of IPFS. This param is not required * @param {IPFS} [params.ipfs] An instance of IPFS. This param is not required
* if storage is provided. * if storage is provided.
* @returns {module:Identities~Identities} An instance of Identities. * @return {module:Identities~Identities} An instance of Identities.
* @instance * @instance
*/ */
const Identities = async ({ keystore, path, storage, ipfs } = {}) => { const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
@ -58,7 +58,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
/** /**
* Gets an identity by hash. * Gets an identity by hash.
* @param {string} hash An identity hash. * @param {string} hash An identity hash.
* @returns {Identity} An instance of identity. * @return {Identity} An instance of identity.
* @memberof module:Identities~Identities * @memberof module:Identities~Identities
* @instance * @instance
*/ */
@ -72,8 +72,8 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
/** /**
* Creates an identity, adding it to storage. * Creates an identity, adding it to storage.
* @param {Object} options Various options for configuring a new identity. * @param {Object} options Various options for configuring a new identity.
* @param {string} [options.type=PublicKeyIdentityProvider.type] The type of provider to use for generating an identity. * @param {string} [options.type=publickey] The type of provider to use for generating an identity.
* @returns {Identity} An instance of identity. * @return {Identity} An instance of identity.
* @memberof module:Identities~Identities * @memberof module:Identities~Identities
* @instance * @instance
*/ */
@ -81,8 +81,8 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
options.keystore = keystore options.keystore = keystore
const type = options.type || DefaultProviderType const type = options.type || DefaultProviderType
const Provider = getProviderFor(type) const Provider = getProviderFor(type).default
const identityProvider = new Provider(options) const identityProvider = Provider(options)
const id = await identityProvider.getId(options) const id = await identityProvider.getId(options)
const privateKey = await keystore.getKey(id) || await keystore.createKey(id) const privateKey = await keystore.getKey(id) || await keystore.createKey(id)
const publicKey = keystore.getPublic(privateKey) const publicKey = keystore.getPublic(privateKey)
@ -103,7 +103,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
/** /**
* Verifies an identity using the identity's provider. * Verifies an identity using the identity's provider.
* @param {Identity} identity The identity to verify. * @param {Identity} identity The identity to verify.
* @returns {boolean} True the identity is valid, false otherwise. * @return {boolean} True the identity is valid, false otherwise.
* @memberof module:Identities~Identities * @memberof module:Identities~Identities
*/ */
const verifyIdentity = async (identity) => { const verifyIdentity = async (identity) => {
@ -137,7 +137,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
* Signs data using an identity. * Signs data using an identity.
* @param {Identity} identity The identity to use for signing. * @param {Identity} identity The identity to use for signing.
* @param {string} data The data to sign. * @param {string} data The data to sign.
* @returns {string} The signed data. * @return {string} The signed data.
* @throws Private signing key not fund from KeyStore when no signing key can * @throws Private signing key not fund from KeyStore when no signing key can
* be retrieved. * be retrieved.
* @memberof module:Identities~Identities * @memberof module:Identities~Identities
@ -170,7 +170,7 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
/** /**
* Checks whether an identity provider is supported. * Checks whether an identity provider is supported.
* @param {string} type The identity provider type. * @param {string} type The identity provider type.
* @returns {boolean} True if the identity provider is supported, false * @return {boolean} True if the identity provider is supported, false
* otherwise. * otherwise.
* @static * @static
*/ */
@ -181,7 +181,7 @@ const isProviderSupported = (type) => {
/** /**
* Gets an identity provider. * Gets an identity provider.
* @param {string} type The identity provider type. * @param {string} type The identity provider type.
* @returns {IdentityProvider} The IdentityProvider module corresponding to * @return {IdentityProvider} The IdentityProvider module corresponding to
* type. * type.
* @throws IdentityProvider type is not supported if the identity provider is * @throws IdentityProvider type is not supported if the identity provider is
* not supported. * not supported.

View File

@ -23,7 +23,7 @@ const hashStringEncoding = base58btc
* @param {string} params.type The type of identity provider. * @param {string} params.type The type of identity provider.
* @param {function} params.sign A sign function. * @param {function} params.sign A sign function.
* @param {function} params.verify A verify function. * @param {function} params.verify A verify function.
* @returns {module:Identity~Identity} An instance of Identity. * @return {module:Identity~Identity} An instance of Identity.
* @throws Identity id is required if id is not provided. * @throws Identity id is required if id is not provided.
* @throws Invalid public key if publicKey is not provided. * @throws Invalid public key if publicKey is not provided.
* @throws Signatures object is required if signature is not provided. * @throws Signatures object is required if signature is not provided.
@ -62,7 +62,7 @@ const Identity = async ({ id, publicKey, signatures, type, sign, verify } = {})
/** /**
* Encode an Identity to a serializable form. * Encode an Identity to a serializable form.
* @param {Identity} identity Identity to encode, * @param {Identity} identity Identity to encode,
* @returns {Object} Object with fields hash and bytes. * @return {Object} Object with fields hash and bytes.
* @static * @static
*/ */
const _encodeIdentity = async (identity) => { const _encodeIdentity = async (identity) => {
@ -76,7 +76,7 @@ const _encodeIdentity = async (identity) => {
/** /**
* Decode an Identity from bytes * Decode an Identity from bytes
* @param {Uint8Array} bytes Bytes from which to decode an Identity from * @param {Uint8Array} bytes Bytes from which to decode an Identity from
* @returns {Identity} * @return {Identity}
* @static * @static
*/ */
const decodeIdentity = async (bytes) => { const decodeIdentity = async (bytes) => {

View File

@ -1,4 +1,3 @@
/** @module IdentityProviders */
export { export {
default as Identities, default as Identities,
addIdentityProvider, addIdentityProvider,
@ -12,4 +11,4 @@ export {
isEqual isEqual
} from './identity.js' } from './identity.js'
export { default as PublicKeyIdentityProvider } from './providers/publickey.js' export { PublicKeyIdentityProvider } from './providers/index.js'

View File

@ -1,4 +1,52 @@
/**
* @module IdentityProviders
* @description
* Identity providers.
*
* ## Custom Providers
*
* An identity provider provides a method for signing and verifying an
* identity using a particular cryptographic mechanism.
*
* A custom identity provider can be used provided the module takes the
* following form:
* ```javascript
* // A unique name for the identity provider
* const type = 'custom'
*
* // check whether the identity was signed by the identity's id.
* const verifyIdentity = identity => {
*
* }
*
* // The identity provider.
* const MyCustomIdentityProvider = ({ keystore }) => {
* const getId = async ({ id } = {}) => {
*
* }
*
* const signIdentity = async (data, { id } = {}) => {
*
* }
*
* return {
* getId,
* signIdentity
* }
* }
*
* export { MyCustomIdentityProvider as default, verifyIdentity, type }
* ```
*
* To use it, add it to the list of known identity providers:
* ```javascript
* import * as MyCustomIdentityProvider from 'my-custom-identity-provider'
* addIdentityProvider(MyCustomIdentityProvider)
* ```
*
* where my-custom-identity-provider is the custom module.
*/
// export { default as DIDIdentityProvider } from './did.js' // export { default as DIDIdentityProvider } from './did.js'
// export { default as EthIdentityProvider } from './ethereum.js' // export { default as EthIdentityProvider } from './ethereum.js'
export { default as IdentityProvider } from './interface.js' export * as PublicKeyIdentityProvider from './publickey.js'
export { default as PublicKeyIdentityProvider } from './publickey.js'

View File

@ -1,26 +0,0 @@
class IdentityProvider {
/* Return id of identity (to be signed by orbit-db public key) */
async getId (options) {}
/* Return signature of OrbitDB public key signature */
async signIdentity (data, options) {}
/* Verify a signature of OrbitDB public key signature */
static async verifyIdentity (identity) {}
/* Return the type for this identity provider */
static get type () {
throw new Error('\'static get type ()\' needs to be defined in the inheriting class')
}
/*
Return the type for this identity-proider
NOTE! This is the only property of the interface that
shouldn't be overridden in the inherited IdentityProvider
*/
get type () {
return this.constructor.type
}
}
export default IdentityProvider

View File

@ -1,41 +1,77 @@
/** /**
* @namespace module:IdentityProviders.IdentityProviders-PublicKey * @module PublicKeyIdentityProvider
* @description PublicKey Identity Provider * @memberof module:IdentityProviders
* @description
* The PublicKey Identity Provider signs and verifies an identity using the
* public key of a private/public key pair.
*/ */
import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import IdentityProvider from './interface.js'
import { signMessage, verifyMessage } from '../../key-store.js' import { signMessage, verifyMessage } from '../../key-store.js'
/**
* The type of identity provider.
* @return string
* @const
*/
const type = 'publickey' const type = 'publickey'
class PublicKeyIdentityProvider extends IdentityProvider { /**
constructor ({ keystore }) { * Verifies an identity using the identity's id.
super() * @param {module:Identity} identity
* @return {boolean} True if the identity is valid, false otherwise.
* @static
*/
const verifyIdentity = identity => {
const { id, publicKey, signatures } = identity
return verifyMessage(signatures.publicKey, id, publicKey + signatures.id)
}
if (!keystore) { /**
throw new Error('PublicKeyIdentityProvider requires a keystore parameter') * Instantiates the publickey identity provider.
} * @return {module:IdentityProviders.IdentityProvider-PublicKey} A public key identity provider function.
*/
const PublicKeyIdentityProvider = ({ keystore }) => {
/**
* @namespace module:IdentityProviders.IdentityProvider-PublicKey
* @memberof module:IdentityProviders
* @description The instance returned by {@link module:IdentityProviders.IdentityProvider-PublicKey}.
*/
this._keystore = keystore if (!keystore) {
throw new Error('PublicKeyIdentityProvider requires a keystore parameter')
} }
// Returns the type of the identity provider /**
static get type () { return type } * Gets the id.
* @memberof module:IdentityProviders.IdentityProvider-PublicKey
async getId ({ id } = {}) { * @param {String} id The id to retrieve.
* @return {String} The identity's id.
* @instance
*/
const getId = async ({ id } = {}) => {
if (!id) { if (!id) {
throw new Error('id is required') throw new Error('id is required')
} }
const key = await this._keystore.getKey(id) || await this._keystore.createKey(id)
const key = await keystore.getKey(id) || await keystore.createKey(id)
return uint8ArrayToString(key.public.marshal(), 'base16') return uint8ArrayToString(key.public.marshal(), 'base16')
} }
async signIdentity (data, { id } = {}) { /**
* Signs an identity using the identity's id.
* @memberof module:IdentityProviders.IdentityProvider-PublicKey
* @param {*} data The identity data to sign.
* @param {Object} params One or more parameters for configuring Database.
* @param {string} [params.id] The identity's id.
* @return {string} A signature.
* @instance
*/
const signIdentity = async (data, { id } = {}) => {
if (!id) { if (!id) {
throw new Error('id is required') throw new Error('id is required')
} }
const key = await this._keystore.getKey(id) const key = await keystore.getKey(id)
if (!key) { if (!key) {
throw new Error(`Signing key for '${id}' not found`) throw new Error(`Signing key for '${id}' not found`)
} }
@ -43,11 +79,10 @@ class PublicKeyIdentityProvider extends IdentityProvider {
return signMessage(key, data) return signMessage(key, data)
} }
static async verifyIdentity (identity) { return {
const { id, publicKey, signatures } = identity getId,
// Verify that identity was signed by the ID signIdentity
return verifyMessage(signatures.publicKey, id, publicKey + signatures.id)
} }
} }
export default PublicKeyIdentityProvider export { PublicKeyIdentityProvider as default, verifyIdentity, type }

View File

@ -21,9 +21,9 @@ const unmarshalPubKey = crypto.keys.supportedKeys.secp256k1.unmarshalSecp256k1Pu
/** /**
* Verifies a signature used for signing data. * Verifies a signature used for signing data.
* @params {string} signature The generated signature. * @param {string} signature The generated signature.
* @params {string} publicKey The derived public key of the key pair. * @param {string} publicKey The derived public key of the key pair.
* @params {string} data The data to be verified. * @param {string} data The data to be verified.
* @return {boolean} True if the signature is valid, false otherwise. * @return {boolean} True if the signature is valid, false otherwise.
* @throws No signature given if no signature is provided. * @throws No signature given if no signature is provided.
* @throws Given publicKey was undefined if no publicKey is provided. * @throws Given publicKey was undefined if no publicKey is provided.
@ -60,8 +60,8 @@ const verifySignature = async (signature, publicKey, data) => {
/** /**
* Signs data using a key pair. * Signs data using a key pair.
* @params {string} key The key to use for signing data. * @param {string} key The key to use for signing data.
* @params {string} data The data to sign. * @param {string} data The data to sign.
* @return {string} A signature. * @return {string} A signature.
* @throws No signing key given if no key is provided. * @throws No signing key given if no key is provided.
* @throws Given input data was undefined if no data is provided. * @throws Given input data was undefined if no data is provided.
@ -87,9 +87,9 @@ const verifiedCachePromise = LRUStorage({ size: 1000 })
/** /**
* Verifies input data against a cached version of the signed message. * Verifies input data against a cached version of the signed message.
* @params {string} signature The generated signature. * @param {string} signature The generated signature.
* @params {string} publicKey The derived public key of the key pair. * @param {string} publicKey The derived public key of the key pair.
* @params {string} data The data to be verified. * @param {string} data The data to be verified.
* @return {boolean} True if the the data and cache match, false otherwise. * @return {boolean} True if the the data and cache match, false otherwise.
* @static * @static
*/ */

View File

@ -20,7 +20,7 @@ const hashStringEncoding = base58btc
* @param {Object} params One or more parameters for configuring Manifest. * @param {Object} params One or more parameters for configuring Manifest.
* @param {IPFS} params.ipfs An instance of IPFS. * @param {IPFS} params.ipfs An instance of IPFS.
* @param {module:Storage} [param.storage=module:Storage.Storage-ComposedStorage] An instance of Storage. * @param {module:Storage} [param.storage=module:Storage.Storage-ComposedStorage] An instance of Storage.
* @returns {module:Manifest} An instance of Manifest. * @return {module:Manifest} An instance of Manifest.
* @instance * @instance
*/ */
const Manifest = async ({ ipfs, storage } = {}) => { const Manifest = async ({ ipfs, storage } = {}) => {
@ -37,7 +37,7 @@ const Manifest = async ({ ipfs, storage } = {}) => {
/** /**
* Gets the manifest data from the underlying storage. * Gets the manifest data from the underlying storage.
* @param {string} address The address of the manifest. * @param {string} address The address of the manifest.
* @returns {*} The manifest data. * @return {*} The manifest data.
* @memberof module:Manifest~Manifest * @memberof module:Manifest~Manifest
*/ */
const get = async (address) => { const get = async (address) => {
@ -53,7 +53,7 @@ const Manifest = async ({ ipfs, storage } = {}) => {
* @param {string} type The type of the database. * @param {string} type The type of the database.
* @param {string} accessController The type of access controller. * @param {string} accessController The type of access controller.
* @param {Object} meta Metadata. * @param {Object} meta Metadata.
* @returns {Object} A hash and manifest. * @return {Object} A hash and manifest.
* @throws name is required if no name is provided. * @throws name is required if no name is provided.
* @throws type is required if no type is provided. * @throws type is required if no type is provided.
* @throws accessController is required if no access controller is provided. * @throws accessController is required if no access controller is provided.

View File

@ -1,33 +1,26 @@
/* Lamport Clock */ /* Lamport Clock */
class Clock { const compareClocks = (a, b) => {
constructor (id, time) { // Calculate the "distance" based on the clock, ie. lower or greater
this.id = id const dist = a.time - b.time
this.time = time || 0
}
tick () { // If the sequence number is the same (concurrent events),
return new Clock(this.id, ++this.time) // and the IDs are different, take the one with a "lower" id
} if (dist === 0 && a.id !== b.id) return a.id < b.id ? -1 : 1
merge (clock) { return dist
this.time = Math.max(this.time, clock.time) }
return new Clock(this.id, this.time)
}
clone () { const tickClock = (clock) => {
return new Clock(this.id, this.time) return Clock(clock.id, ++clock.time)
} }
static compare (a, b) { const Clock = (id, time) => {
// Calculate the "distance" based on the clock, ie. lower or greater time = time || 0
const dist = a.time - b.time
// If the sequence number is the same (concurrent events), return {
// and the IDs are different, take the one with a "lower" id id,
if (dist === 0 && a.id !== b.id) return a.id < b.id ? -1 : 1 time
return dist
} }
} }
export default Clock export { Clock as default, compareClocks, tickClock }

View File

@ -1,4 +1,4 @@
import Clock from './clock.js' import { compareClocks } from './clock.js'
/** /**
* Sort two entries as Last-Write-Wins (LWW). * Sort two entries as Last-Write-Wins (LWW).
@ -8,7 +8,7 @@ import Clock from './clock.js'
* *
* @param {Entry} a First entry * @param {Entry} a First entry
* @param {Entry} b Second entry * @param {Entry} b Second entry
* @returns {number} 1 if a is latest, -1 if b is latest * @return {number} 1 if a is latest, -1 if b is latest
*/ */
function LastWriteWins (a, b) { function LastWriteWins (a, b) {
// Ultimate conflict resolution (take the first/left arg) // Ultimate conflict resolution (take the first/left arg)
@ -27,11 +27,11 @@ function LastWriteWins (a, b) {
* @param {Entry} a First entry to compare * @param {Entry} a First entry to compare
* @param {Entry} b Second entry to compare * @param {Entry} b Second entry to compare
* @param {function(a, b)} resolveConflict A function to call if entries are concurrent (happened at the same time). The function should take in two entries and return 1 if the first entry should be chosen and -1 if the second entry should be chosen. * @param {function(a, b)} resolveConflict A function to call if entries are concurrent (happened at the same time). The function should take in two entries and return 1 if the first entry should be chosen and -1 if the second entry should be chosen.
* @returns {number} 1 if a is greater, -1 if b is greater * @return {number} 1 if a is greater, -1 if b is greater
*/ */
function SortByClocks (a, b, resolveConflict) { function SortByClocks (a, b, resolveConflict) {
// Compare the clocks // Compare the clocks
const diff = Clock.compare(a.clock, b.clock) const diff = compareClocks(a.clock, b.clock)
// If the clocks are concurrent, use the provided // If the clocks are concurrent, use the provided
// conflict resolution function to determine which comes first // conflict resolution function to determine which comes first
return diff === 0 ? resolveConflict(a, b) : diff return diff === 0 ? resolveConflict(a, b) : diff
@ -42,7 +42,7 @@ function SortByClocks (a, b, resolveConflict) {
* @param {Entry} a First entry to compare * @param {Entry} a First entry to compare
* @param {Entry} b Second entry to compare * @param {Entry} b Second entry to compare
* @param {function(a, b)} resolveConflict A function to call if the clocks ids are the same. The function should take in two entries and return 1 if the first entry should be chosen and -1 if the second entry should be chosen. * @param {function(a, b)} resolveConflict A function to call if the clocks ids are the same. The function should take in two entries and return 1 if the first entry should be chosen and -1 if the second entry should be chosen.
* @returns {number} 1 if a is greater, -1 if b is greater * @return {number} 1 if a is greater, -1 if b is greater
*/ */
function SortByClockId (a, b, resolveConflict) { function SortByClockId (a, b, resolveConflict) {
// Sort by ID if clocks are concurrent, // Sort by ID if clocks are concurrent,
@ -55,7 +55,7 @@ function SortByClockId (a, b, resolveConflict) {
/** /**
* A wrapper function to throw an error if the results of a passed function return zero * A wrapper function to throw an error if the results of a passed function return zero
* @param {function(a, b)} [tiebreaker] The tiebreaker function to validate. * @param {function(a, b)} [tiebreaker] The tiebreaker function to validate.
* @returns {function(a, b)} 1 if a is greater, -1 if b is greater * @return {function(a, b)} 1 if a is greater, -1 if b is greater
* @throws {Error} if func ever returns 0 * @throws {Error} if func ever returns 0
*/ */
function NoZeroes (func) { function NoZeroes (func) {

View File

@ -21,7 +21,7 @@ const hashStringEncoding = base58btc
* @param {Clock} [clock] The clock * @param {Clock} [clock] The clock
* @param {Array<string|Entry>} [next=[]] An array of CIDs as base58btc encoded strings * @param {Array<string|Entry>} [next=[]] An array of CIDs as base58btc encoded strings
* @param {Array<string|Entry>} [refs=[]] An array of CIDs as base58btc encoded strings * @param {Array<string|Entry>} [refs=[]] An array of CIDs as base58btc encoded strings
* @returns {Promise<Entry>} * @return {Promise<Entry>}
* @example * @example
* const entry = await Entry.create(identity, 'log1', 'hello') * const entry = await Entry.create(identity, 'log1', 'hello')
* console.log(entry) * console.log(entry)
@ -33,7 +33,7 @@ const create = async (identity, id, payload, clock = null, next = [], refs = [])
if (payload == null) throw new Error('Entry requires a payload') if (payload == null) throw new Error('Entry requires a payload')
if (next == null || !Array.isArray(next)) throw new Error("'next' argument is not an array") if (next == null || !Array.isArray(next)) throw new Error("'next' argument is not an array")
clock = clock || new Clock(identity.publicKey) clock = clock || Clock(identity.publicKey)
const entry = { const entry = {
id, // For determining a unique chain id, // For determining a unique chain
@ -84,7 +84,7 @@ const verify = async (identities, entry) => {
/** /**
* Check if an object is an Entry. * Check if an object is an Entry.
* @param {Entry} obj * @param {Entry} obj
* @returns {boolean} * @return {boolean}
*/ */
const isEntry = (obj) => { const isEntry = (obj) => {
return obj && obj.id !== undefined && return obj && obj.id !== undefined &&
@ -102,7 +102,7 @@ const isEqual = (a, b) => {
/** /**
* Decode a serialized Entry from bytes * Decode a serialized Entry from bytes
* @param {Uint8Array} bytes * @param {Uint8Array} bytes
* @returns {Entry} * @return {Entry}
*/ */
const decode = async (bytes) => { const decode = async (bytes) => {
const { value } = await Block.decode({ bytes, codec, hasher }) const { value } = await Block.decode({ bytes, codec, hasher })
@ -112,12 +112,12 @@ const decode = async (bytes) => {
/** /**
* Encode an Entry to a serializable form * Encode an Entry to a serializable form
* @param {Entry} entry * @param {Entry} entry
* @returns {TODO} * @return {TODO}
*/ */
const _encodeEntry = async (entry) => { const _encodeEntry = async (entry) => {
const { cid, bytes } = await Block.encode({ value: entry, codec, hasher }) const { cid, bytes } = await Block.encode({ value: entry, codec, hasher })
const hash = cid.toString(hashStringEncoding) const hash = cid.toString(hashStringEncoding)
const clock = new Clock(entry.clock.id, entry.clock.time) const clock = Clock(entry.clock.id, entry.clock.time)
return { return {
...entry, ...entry,
clock, clock,

View File

@ -76,7 +76,7 @@ const Heads = async ({ storage, heads }) => {
* This function is private and not exposed in the Log API * This function is private and not exposed in the Log API
* *
* @param {Array<Entry>} entries Entries to search heads from * @param {Array<Entry>} entries Entries to search heads from
* @returns {Array<Entry>} * @return {Array<Entry>}
*/ */
const findHeads = (entries) => { const findHeads = (entries) => {
entries = new Set(entries) entries = new Set(entries)

View File

@ -9,7 +9,7 @@
*/ */
import LRU from 'lru' import LRU from 'lru'
import Entry from './entry.js' import Entry from './entry.js'
import Clock from './clock.js' import Clock, { tickClock } from './clock.js'
import Heads from './heads.js' import Heads from './heads.js'
import ConflictResolution from './conflict-resolution.js' import ConflictResolution from './conflict-resolution.js'
import MemoryStorage from '../storage/memory.js' import MemoryStorage from '../storage/memory.js'
@ -80,20 +80,20 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
/** /**
* Returns the clock of the log. * Returns the clock of the log.
* @returns {Clock} * @return {Clock}
* @memberof module:Log~Log * @memberof module:Log~Log
* @instance * @instance
*/ */
const clock = async () => { const clock = async () => {
// Find the latest clock from the heads // Find the latest clock from the heads
const maxTime = Math.max(0, (await heads()).reduce(maxClockTimeReducer, 0)) const maxTime = Math.max(0, (await heads()).reduce(maxClockTimeReducer, 0))
return new Clock(identity.publicKey, maxTime) return Clock(identity.publicKey, maxTime)
} }
/** /**
* Returns the current heads of the log * Returns the current heads of the log
* *
* @returns {Array<module:Log~Entry>} * @return {Array<module:Log~Entry>}
* @memberof module:Log~Log * @memberof module:Log~Log
* @instance * @instance
*/ */
@ -105,7 +105,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
/** /**
* Returns all entries in the log * Returns all entries in the log
* *
* @returns {Array<module:Log~Entry>} * @return {Array<module:Log~Entry>}
* @memberof module:Log~Log * @memberof module:Log~Log
* @instance * @instance
*/ */
@ -121,7 +121,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
* Retrieve an entry * Retrieve an entry
* *
* @param {string} hash The hash of the entry to retrieve * @param {string} hash The hash of the entry to retrieve
* @returns {module:Log~Entry} * @return {module:Log~Entry}
* @memberof module:Log~Log * @memberof module:Log~Log
* @instance * @instance
*/ */
@ -169,7 +169,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
identity, identity,
id, id,
data, data,
(await clock()).tick(), tickClock(await clock()),
nexts, nexts,
refs refs
) )
@ -353,7 +353,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
* @param {string} options.gte Beginning hash of the iterator, inclusive * @param {string} options.gte Beginning hash of the iterator, inclusive
* @param {string} options.lt Ending hash of the iterator, non-inclusive * @param {string} options.lt Ending hash of the iterator, non-inclusive
* @param {string} options.lte Ending hash of the iterator, inclusive * @param {string} options.lte Ending hash of the iterator, inclusive
* @returns {Symbol.asyncIterator} Iterator object of log entries * @return {Symbol.asyncIterator} Iterator object of log entries
* *
* @examples * @examples
* *
@ -473,7 +473,7 @@ const Log = async (identity, { logId, logHeads, access, entryStorage, headsStora
/** /**
* Check if an object is a Log. * Check if an object is a Log.
* @param {Log} obj * @param {Log} obj
* @returns {boolean} * @return {boolean}
* @memberof module:Log~Log * @memberof module:Log~Log
* @instance * @instance
*/ */

View File

@ -17,7 +17,7 @@
* @function * @function
* @param {module:Storage} storage1 A storage instance. * @param {module:Storage} storage1 A storage instance.
* @param {module:Storage} storage2 A storage instance. * @param {module:Storage} storage2 A storage instance.
* @returns {module:Storage.Storage-Composed} An instance of ComposedStorage. * @return {module:Storage.Storage-Composed} An instance of ComposedStorage.
* @memberof module:Storage * @memberof module:Storage
* @instance * @instance
*/ */

View File

@ -18,7 +18,7 @@ const defaultTimeout = 30000
* @param {number} [params.timeout=defaultTimeout] A timeout in ms. * @param {number} [params.timeout=defaultTimeout] A timeout in ms.
* @param {boolean} [params.pin=false] True, if the block should be pinned, * @param {boolean} [params.pin=false] True, if the block should be pinned,
* false otherwise. * false otherwise.
* @returns {module:Storage.Storage-IPFS} An instance of IPFSBlockStorage. * @return {module:Storage.Storage-IPFS} An instance of IPFSBlockStorage.
* @memberof module:Storage * @memberof module:Storage
* @throw An instance of ipfs is required if params.ipfs is not specified. * @throw An instance of ipfs is required if params.ipfs is not specified.
* @instance * @instance

View File

@ -18,7 +18,7 @@ const defaultValueEncoding = 'view'
* LevelStorage. * LevelStorage.
* @param {string} [params.path=defaultPath] The Level path. * @param {string} [params.path=defaultPath] The Level path.
* @param {string} [params.valueEncoding=defaultValueEncoding] Value encoding. * @param {string} [params.valueEncoding=defaultValueEncoding] Value encoding.
* @returns {module:Storage.Storage-Level} An instance of LevelStorage. * @return {module:Storage.Storage-Level} An instance of LevelStorage.
* @memberof module:Storage * @memberof module:Storage
* @instance * @instance
*/ */

View File

@ -14,7 +14,7 @@ const defaultSize = 1000000
* @param {Object} [params={}] One or more parameters for configuring * @param {Object} [params={}] One or more parameters for configuring
* IPFSBlockStorage. * IPFSBlockStorage.
* @param {string} [params.size=defaultSize] The number of elements to store. * @param {string} [params.size=defaultSize] The number of elements to store.
* @returns {module:Storage.Storage-LRU} An instance of LRUStorage. * @return {module:Storage.Storage-LRU} An instance of LRUStorage.
* @memberof module:Storage * @memberof module:Storage
* @instance * @instance
*/ */

View File

@ -8,7 +8,7 @@
/** /**
* Creates an instance of MemoryStorage. * Creates an instance of MemoryStorage.
* @function * @function
* @returns {module:Storage.Storage-Memory} An instance of LRUStorage. * @return {module:Storage.Storage-Memory} An instance of LRUStorage.
* @memberof module:Storage * @memberof module:Storage
* @instance * @instance
*/ */

View File

@ -1,7 +1,7 @@
/** /**
* Creates an id from an alphanumeric character list. * Creates an id from an alphanumeric character list.
* @param {number} [length=32] The length of the id. * @param {number} [length=32] The length of the id.
* @returns {string} An id. * @return {string} An id.
* @see {@link https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript} * @see {@link https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript}
* @memberof module:Utils * @memberof module:Utils
*/ */

View File

@ -4,7 +4,7 @@ import pathJoin from './path-join.js'
* Checks that the given address has '/_access' as the last part. * Checks that the given address has '/_access' as the last part.
* @function * @function
* @param {string} address The address to check. * @param {string} address The address to check.
* @returns {string} The address appended with /_access. * @return {string} The address appended with /_access.
* @memberof module:Utils * @memberof module:Utils
*/ */
export default address => { export default address => {

View File

@ -213,18 +213,30 @@ describe('Identities', function () {
}) })
it('false signature doesn\'t verify', async () => { it('false signature doesn\'t verify', async () => {
class IP { const IP = () => {
async getId () { return 'pubKey' } const verifyIdentity = async (data) => { return false }
async signIdentity (data) { return `false signature '${data}'` } const FakeIdentityProvider = () => {
const getId = () => { return 'pubKey' }
static async verifyIdentity (data) { return false } const signIdentity = (data) => { return `false signature '${data}'` }
static get type () { return 'fake' } return {
getId,
signIdentity,
type: 'fake'
}
}
return {
default: FakeIdentityProvider,
verifyIdentity,
type: 'fake'
}
} }
addIdentityProvider(IP) addIdentityProvider(IP())
identity = await identities.createIdentity({ type: IP.type }) identity = await identities.createIdentity({ type: IP().type })
const verified = await identities.verifyIdentity(identity) const verified = await identities.verifyIdentity(identity)
assert.strictEqual(verified, false) assert.strictEqual(verified, false)
}) })

View File

@ -1,11 +1,11 @@
import Clock from '../../src/oplog/clock.js' import Clock, { tickClock, compareClocks } from '../../src/oplog/clock.js'
import { strictEqual } from 'assert' import { strictEqual } from 'assert'
describe('Clock', () => { describe('Clock', () => {
it('creates a new clock', () => { it('creates a new clock', () => {
const id = 'A' const id = 'A'
const time = 0 const time = 0
const clock = new Clock(id, time) const clock = Clock(id, time)
strictEqual(clock.id, id) strictEqual(clock.id, id)
strictEqual(clock.time, time) strictEqual(clock.time, time)
}) })
@ -13,7 +13,7 @@ describe('Clock', () => {
it('creates a new clock with default time', () => { it('creates a new clock with default time', () => {
const id = 'A' const id = 'A'
const time = 0 const time = 0
const clock = new Clock(id) const clock = Clock(id)
strictEqual(clock.id, id) strictEqual(clock.id, id)
strictEqual(clock.time, time) strictEqual(clock.time, time)
}) })
@ -21,7 +21,7 @@ describe('Clock', () => {
it('creates a new clock with time starting at 1', () => { it('creates a new clock with time starting at 1', () => {
const id = 'A' const id = 'A'
const time = 1 const time = 1
const clock = new Clock(id, time) const clock = Clock(id, time)
strictEqual(clock.id, id) strictEqual(clock.id, id)
strictEqual(clock.time, time) strictEqual(clock.time, time)
}) })
@ -29,144 +29,104 @@ describe('Clock', () => {
it('advances clock forward 1 tick', () => { it('advances clock forward 1 tick', () => {
const id = 'A' const id = 'A'
const time = 1 const time = 1
const clock = new Clock(id) const clock = tickClock(Clock(id))
clock.tick()
strictEqual(clock.time, time) strictEqual(clock.time, time)
}) })
it('advances clock forward 2 ticks', () => { it('advances clock forward 2 ticks', () => {
const id = 'A' const id = 'A'
const time = 2 const time = 2
const clock = new Clock(id) const clock = tickClock(tickClock(Clock(id)))
clock.tick()
clock.tick()
strictEqual(clock.time, time) strictEqual(clock.time, time)
}) })
it('merges clock2 into clock1', () => {
const id1 = 'A'
const clock1 = new Clock(id1)
const id2 = 'B'
const time2 = 2
const clock2 = new Clock(id2, time2)
clock1.merge(clock2)
strictEqual(clock1.id, id1)
strictEqual(clock1.time, time2)
})
it('merges two clocks with the same time', () => {
const id1 = 'A'
const time1 = 1
const clock1 = new Clock(id1, time1)
const id2 = 'B'
const time2 = 1
const clock2 = new Clock(id2, time2)
clock1.merge(clock2)
strictEqual(clock1.id, id1)
strictEqual(clock1.time, time1)
})
it('clones a clock', () => {
const id = 'A'
const time = 1
const clock = new Clock(id, time)
const clonedClock = clock.clone()
strictEqual(clonedClock.id, id)
strictEqual(clonedClock.time, time)
})
describe('Compare clocks', () => { describe('Compare clocks', () => {
it('compares clocks when clock1\'s time is 1 less than clock2\'s', () => { it('compares clocks when clock1\'s time is 1 less than clock2\'s', () => {
const id1 = 'A' const id1 = 'A'
const time1 = 1 const time1 = 1
const clock1 = new Clock(id1, time1) const clock1 = Clock(id1, time1)
const id2 = 'B' const id2 = 'B'
const time2 = 2 const time2 = 2
const clock2 = new Clock(id2, time2) const clock2 = Clock(id2, time2)
const expected = -1 const expected = -1
strictEqual(Clock.compare(clock1, clock2), expected) strictEqual(compareClocks(clock1, clock2), expected)
}) })
it('compares clocks when clock1\'s time is 3 less than clock2\'s', () => { it('compares clocks when clock1\'s time is 3 less than clock2\'s', () => {
const id1 = 'A' const id1 = 'A'
const time1 = 1 const time1 = 1
const clock1 = new Clock(id1, time1) const clock1 = Clock(id1, time1)
const id2 = 'B' const id2 = 'B'
const time2 = 4 const time2 = 4
const clock2 = new Clock(id2, time2) const clock2 = Clock(id2, time2)
const expected = -3 const expected = -3
strictEqual(Clock.compare(clock1, clock2), expected) strictEqual(compareClocks(clock1, clock2), expected)
}) })
it('compares clocks when clock1\'s time is 1 more than clock2\'s', () => { it('compares clocks when clock1\'s time is 1 more than clock2\'s', () => {
const id1 = 'A' const id1 = 'A'
const time1 = 2 const time1 = 2
const clock1 = new Clock(id1, time1) const clock1 = Clock(id1, time1)
const id2 = 'B' const id2 = 'B'
const time2 = 1 const time2 = 1
const clock2 = new Clock(id2, time2) const clock2 = Clock(id2, time2)
const expected = 1 const expected = 1
strictEqual(Clock.compare(clock1, clock2), expected) strictEqual(compareClocks(clock1, clock2), expected)
}) })
it('compares clocks when clock1\'s time is 3 more than clock2\'s', () => { it('compares clocks when clock1\'s time is 3 more than clock2\'s', () => {
const id1 = 'A' const id1 = 'A'
const time1 = 4 const time1 = 4
const clock1 = new Clock(id1, time1) const clock1 = Clock(id1, time1)
const id2 = 'B' const id2 = 'B'
const time2 = 1 const time2 = 1
const clock2 = new Clock(id2, time2) const clock2 = Clock(id2, time2)
const expected = 3 const expected = 3
strictEqual(Clock.compare(clock1, clock2), expected) strictEqual(compareClocks(clock1, clock2), expected)
}) })
it('compares clocks when clock1\'s id is less than clock2\'s', () => { it('compares clocks when clock1\'s id is less than clock2\'s', () => {
const id1 = 'A' const id1 = 'A'
const time1 = 1 const time1 = 1
const clock1 = new Clock(id1, time1) const clock1 = Clock(id1, time1)
const id2 = 'B' const id2 = 'B'
const time2 = 1 const time2 = 1
const clock2 = new Clock(id2, time2) const clock2 = Clock(id2, time2)
const expected = -1 const expected = -1
strictEqual(Clock.compare(clock1, clock2), expected) strictEqual(compareClocks(clock1, clock2), expected)
}) })
it('compares clocks when clock1\'s id is more than clock2\'s', () => { it('compares clocks when clock1\'s id is more than clock2\'s', () => {
const id1 = 'B' const id1 = 'B'
const time1 = 1 const time1 = 1
const clock1 = new Clock(id1, time1) const clock1 = Clock(id1, time1)
const id2 = 'A' const id2 = 'A'
const time2 = 1 const time2 = 1
const clock2 = new Clock(id2, time2) const clock2 = Clock(id2, time2)
const expected = 1 const expected = 1
strictEqual(Clock.compare(clock1, clock2), expected) strictEqual(compareClocks(clock1, clock2), expected)
}) })
it('compares clocks when clock1 is the same as clock2', () => { it('compares clocks when clock1 is the same as clock2', () => {
const id = 'A' const id = 'A'
const time = 1 const time = 1
const clock1 = new Clock(id, time) const clock1 = Clock(id, time)
const clock2 = new Clock(id, time) const clock2 = Clock(id, time)
const expected = 0 const expected = 0
strictEqual(Clock.compare(clock1, clock2), expected) strictEqual(compareClocks(clock1, clock2), expected)
}) })
}) })
}) })

View File

@ -31,22 +31,22 @@ describe('ConflictResolution', () => {
it('returns -1 when first clock\'s id is less than second clock\'s', () => { it('returns -1 when first clock\'s id is less than second clock\'s', () => {
const expected = -1 const expected = -1
const record1 = { clock: new Clock('A') } const record1 = { clock: Clock('A') }
const record2 = { clock: new Clock('B') } const record2 = { clock: Clock('B') }
strictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected) strictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected)
}) })
it('returns 1 when first clock\'s id is greater than second clock\'s', () => { it('returns 1 when first clock\'s id is greater than second clock\'s', () => {
const expected = 1 const expected = 1
const record1 = { clock: new Clock('B') } const record1 = { clock: Clock('B') }
const record2 = { clock: new Clock('A') } const record2 = { clock: Clock('A') }
strictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected) strictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected)
}) })
it('returns the clock when clocks have the same id', () => { it('returns the clock when clocks have the same id', () => {
const expected = { clock: new Clock('A') } const expected = { clock: Clock('A') }
const record1 = { clock: new Clock('A') } const record1 = { clock: Clock('A') }
const record2 = { clock: new Clock('A') } const record2 = { clock: Clock('A') }
deepStrictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected) deepStrictEqual(ConflictResolution.SortByClockId(record1, record2, fallbackFn), expected)
}) })
}) })
@ -59,22 +59,22 @@ describe('ConflictResolution', () => {
it('returns -1 when a\'s time is less than b\'s', () => { it('returns -1 when a\'s time is less than b\'s', () => {
const expected = -1 const expected = -1
const record1 = { clock: new Clock('A', 1) } const record1 = { clock: Clock('A', 1) }
const record2 = { clock: new Clock('B', 2) } const record2 = { clock: Clock('B', 2) }
strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected) strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected)
}) })
it('returns 1 when a\'s time is greater than b\'s', () => { it('returns 1 when a\'s time is greater than b\'s', () => {
const expected = 1 const expected = 1
const record1 = { clock: new Clock('A', 2) } const record1 = { clock: Clock('A', 2) }
const record2 = { clock: new Clock('B', 1) } const record2 = { clock: Clock('B', 1) }
strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected) strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected)
}) })
it('returns -1 when a\'s time is equal to b\'s', () => { it('returns -1 when a\'s time is equal to b\'s', () => {
const expected = -1 const expected = -1
const record1 = { clock: new Clock('A', 1) } const record1 = { clock: Clock('A', 1) }
const record2 = { clock: new Clock('B', 1) } const record2 = { clock: Clock('B', 1) }
strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected) strictEqual(ConflictResolution.SortByClocks(record1, record2, fallbackFn), expected)
}) })
}) })
@ -82,29 +82,29 @@ describe('ConflictResolution', () => {
describe('Last write wins', () => { describe('Last write wins', () => {
it('returns -1 when a\'s time is less than b\'s', () => { it('returns -1 when a\'s time is less than b\'s', () => {
const expected = -1 const expected = -1
const record1 = { clock: new Clock('A', 1) } const record1 = { clock: Clock('A', 1) }
const record2 = { clock: new Clock('B', 2) } const record2 = { clock: Clock('B', 2) }
strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected) strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected)
}) })
it('returns 1 when a\'s time is greater than b\'s', () => { it('returns 1 when a\'s time is greater than b\'s', () => {
const expected = 1 const expected = 1
const record1 = { clock: new Clock('A', 2) } const record1 = { clock: Clock('A', 2) }
const record2 = { clock: new Clock('B', 1) } const record2 = { clock: Clock('B', 1) }
strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected) strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected)
}) })
it('returns -1 when a\'s time is equal to b\'s', () => { it('returns -1 when a\'s time is equal to b\'s', () => {
const expected = -1 const expected = -1
const record1 = { clock: new Clock('A', 1) } const record1 = { clock: Clock('A', 1) }
const record2 = { clock: new Clock('B', 1) } const record2 = { clock: Clock('B', 1) }
strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected) strictEqual(ConflictResolution.LastWriteWins(record1, record2), expected)
}) })
it('returns the clock when a and b are the same', () => { it('returns the clock when a and b are the same', () => {
const expected = { clock: new Clock('A') } const expected = { clock: Clock('A') }
const record1 = { clock: new Clock('A') } const record1 = { clock: Clock('A') }
const record2 = { clock: new Clock('A') } const record2 = { clock: Clock('A') }
deepStrictEqual(ConflictResolution.LastWriteWins(record1, record2), expected) deepStrictEqual(ConflictResolution.LastWriteWins(record1, record2), expected)
}) })
}) })
@ -112,17 +112,17 @@ describe('ConflictResolution', () => {
describe('ConflictResolution records', () => { describe('ConflictResolution records', () => {
it('sorts by clock time', () => { it('sorts by clock time', () => {
const expected = [ const expected = [
{ clock: new Clock('A', 1) }, { clock: Clock('A', 1) },
{ clock: new Clock('B', 2) }, { clock: Clock('B', 2) },
{ clock: new Clock('C', 3) }, { clock: Clock('C', 3) },
{ clock: new Clock('D', 4) } { clock: Clock('D', 4) }
] ]
const records = [ const records = [
{ clock: new Clock('C', 3) }, { clock: Clock('C', 3) },
{ clock: new Clock('A', 1) }, { clock: Clock('A', 1) },
{ clock: new Clock('D', 4) }, { clock: Clock('D', 4) },
{ clock: new Clock('B', 2) } { clock: Clock('B', 2) }
] ]
deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected) deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected)
@ -130,17 +130,17 @@ describe('ConflictResolution', () => {
it('sorts by clock time when id is the same', () => { it('sorts by clock time when id is the same', () => {
const expected = [ const expected = [
{ clock: new Clock('A', 1) }, { clock: Clock('A', 1) },
{ clock: new Clock('A', 2) }, { clock: Clock('A', 2) },
{ clock: new Clock('A', 3) }, { clock: Clock('A', 3) },
{ clock: new Clock('A', 4) } { clock: Clock('A', 4) }
] ]
const records = [ const records = [
{ clock: new Clock('A', 3) }, { clock: Clock('A', 3) },
{ clock: new Clock('A', 1) }, { clock: Clock('A', 1) },
{ clock: new Clock('A', 4) }, { clock: Clock('A', 4) },
{ clock: new Clock('A', 2) } { clock: Clock('A', 2) }
] ]
deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected) deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected)
@ -148,17 +148,17 @@ describe('ConflictResolution', () => {
it('sorts by clock id', () => { it('sorts by clock id', () => {
const expected = [ const expected = [
{ clock: new Clock('A') }, { clock: Clock('A') },
{ clock: new Clock('B') }, { clock: Clock('B') },
{ clock: new Clock('C') }, { clock: Clock('C') },
{ clock: new Clock('D') } { clock: Clock('D') }
] ]
const records = [ const records = [
{ clock: new Clock('C') }, { clock: Clock('C') },
{ clock: new Clock('A') }, { clock: Clock('A') },
{ clock: new Clock('D') }, { clock: Clock('D') },
{ clock: new Clock('B') } { clock: Clock('B') }
] ]
deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected) deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected)
@ -166,17 +166,17 @@ describe('ConflictResolution', () => {
it('sorts the same clock', () => { it('sorts the same clock', () => {
const expected = [ const expected = [
{ clock: new Clock('A') }, { clock: Clock('A') },
{ clock: new Clock('A') }, { clock: Clock('A') },
{ clock: new Clock('B') }, { clock: Clock('B') },
{ clock: new Clock('B') } { clock: Clock('B') }
] ]
const records = [ const records = [
{ clock: new Clock('B') }, { clock: Clock('B') },
{ clock: new Clock('A') }, { clock: Clock('A') },
{ clock: new Clock('B') }, { clock: Clock('B') },
{ clock: new Clock('A') } { clock: Clock('A') }
] ]
deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected) deepStrictEqual(records.sort(ConflictResolution.LastWriteWins), expected)

View File

@ -3,6 +3,7 @@ import rmrf from 'rimraf'
import { copy } from 'fs-extra' import { copy } from 'fs-extra'
import { Entry, Identities, KeyStore } from '../../src/index.js' import { Entry, Identities, KeyStore } from '../../src/index.js'
import testKeysPath from '../fixtures/test-keys-path.js' import testKeysPath from '../fixtures/test-keys-path.js'
import { tickClock } from '../../src/oplog/clock.js'
const { create, isEntry } = Entry const { create, isEntry } = Entry
const keysPath = './testkeys' const keysPath = './testkeys'
@ -79,7 +80,7 @@ describe('Entry', function () {
const payload1 = 'hello world' const payload1 = 'hello world'
const payload2 = 'hello again' const payload2 = 'hello again'
const entry1 = await create(testIdentity, 'A', payload1) const entry1 = await create(testIdentity, 'A', payload1)
entry1.clock.tick() entry1.clock = tickClock(entry1.clock)
const entry2 = await create(testIdentity, 'A', payload2, entry1.clock, [entry1]) const entry2 = await create(testIdentity, 'A', payload2, entry1.clock, [entry1])
strictEqual(entry2.payload, payload2) strictEqual(entry2.payload, payload2)
strictEqual(entry2.next.length, 1) strictEqual(entry2.next.length, 1)

View File

@ -350,19 +350,19 @@ describe('Log - Join', async function () {
strictEqual((await log4.clock()).time, 8) strictEqual((await log4.clock()).time, 8)
const expectedData = [ const expectedData = [
{ payload: 'helloA1', id: 'X', clock: new Clock(testIdentity.publicKey, 1) }, { payload: 'helloA1', id: 'X', clock: Clock(testIdentity.publicKey, 1) },
{ payload: 'helloB1', id: 'X', clock: new Clock(testIdentity2.publicKey, 1) }, { payload: 'helloB1', id: 'X', clock: Clock(testIdentity2.publicKey, 1) },
{ payload: 'helloD1', id: 'X', clock: new Clock(testIdentity4.publicKey, 1) }, { payload: 'helloD1', id: 'X', clock: Clock(testIdentity4.publicKey, 1) },
{ payload: 'helloA2', id: 'X', clock: new Clock(testIdentity.publicKey, 2) }, { payload: 'helloA2', id: 'X', clock: Clock(testIdentity.publicKey, 2) },
{ payload: 'helloB2', id: 'X', clock: new Clock(testIdentity2.publicKey, 2) }, { payload: 'helloB2', id: 'X', clock: Clock(testIdentity2.publicKey, 2) },
{ payload: 'helloD2', id: 'X', clock: new Clock(testIdentity4.publicKey, 2) }, { payload: 'helloD2', id: 'X', clock: Clock(testIdentity4.publicKey, 2) },
{ payload: 'helloC1', id: 'X', clock: new Clock(testIdentity3.publicKey, 3) }, { payload: 'helloC1', id: 'X', clock: Clock(testIdentity3.publicKey, 3) },
{ payload: 'helloC2', id: 'X', clock: new Clock(testIdentity3.publicKey, 4) }, { payload: 'helloC2', id: 'X', clock: Clock(testIdentity3.publicKey, 4) },
{ payload: 'helloD3', id: 'X', clock: new Clock(testIdentity4.publicKey, 5) }, { payload: 'helloD3', id: 'X', clock: Clock(testIdentity4.publicKey, 5) },
{ payload: 'helloD4', id: 'X', clock: new Clock(testIdentity4.publicKey, 6) }, { payload: 'helloD4', id: 'X', clock: Clock(testIdentity4.publicKey, 6) },
{ payload: 'helloA5', id: 'X', clock: new Clock(testIdentity.publicKey, 7) }, { payload: 'helloA5', id: 'X', clock: Clock(testIdentity.publicKey, 7) },
{ payload: 'helloD5', id: 'X', clock: new Clock(testIdentity4.publicKey, 7) }, { payload: 'helloD5', id: 'X', clock: Clock(testIdentity4.publicKey, 7) },
{ payload: 'helloD6', id: 'X', clock: new Clock(testIdentity4.publicKey, 8) } { payload: 'helloD6', id: 'X', clock: Clock(testIdentity4.publicKey, 8) }
] ]
const values = await log4.values() const values = await log4.values()