feat: Expose identity provider managers. (#75)

* feat: Expose identity provider managers.

* refactor: Remove unnecessary provider tools.

* refactor: Match AC management to other management mechanisms (e.g. Identity Providers).
This commit is contained in:
Hayden Young 2023-06-02 02:53:40 +08:00 committed by GitHub
parent 794136c762
commit b9e573dc6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 210 additions and 150 deletions

View File

@ -21,12 +21,12 @@ import IPFSAccessController from './ipfs.js'
import OrbitDBAccessController from './orbitdb.js' import OrbitDBAccessController from './orbitdb.js'
/** /**
* An array of available access controller types. * An array of available access controllers.
* @name types * @name accessControllers
* @ype [] * @ype []
* @return [] An array of access controller types. * @return [] An array of access controllers.
*/ */
const types = { const accessControllers = {
ipfs: IPFSAccessController, ipfs: IPFSAccessController,
orbitdb: OrbitDBAccessController orbitdb: OrbitDBAccessController
} }
@ -36,25 +36,15 @@ const types = {
* @param {string} type A valid access controller type. * @param {string} type A valid access controller type.
* @return {AccessController} The access controller module. * @return {AccessController} The access controller module.
*/ */
const get = (type) => { const getAccessController = (type) => {
if (!isSupported(type)) { if (!Object.keys(accessControllers).includes(type)) {
throw new Error(`AccessController type '${type}' is not supported`) throw new Error(`AccessController type '${type}' is not supported`)
} }
return types[type] return accessControllers[type]
} }
/** /**
* Checks whether the access controller exists. * Adds an access controller module to the list of supported access controller.
* @param {string} type A valid access controller type.
* @return {boolean} True if the access controller exists, false otherwise.
*/
const isSupported = type => {
return Object.keys(types).includes(type)
}
/**
* Adds an access controller module to the list of supported access controller
* types.
* @param {AccessController} accessController A compatible access controller * @param {AccessController} accessController A compatible access controller
* module. * module.
* @throws Access controller `type` already added if the access controller is * @throws Access controller `type` already added if the access controller is
@ -62,8 +52,8 @@ const isSupported = type => {
* @throws Given AccessController class needs to implement: type if the access * @throws Given AccessController class needs to implement: type if the access
* controller module does not implement a type property. * controller module does not implement a type property.
*/ */
const add = (accessController) => { const addAccessController = (accessController) => {
if (types[accessController.type]) { if (accessControllers[accessController.type]) {
throw new Error(`Access controller '${accessController.type}' already added.`) throw new Error(`Access controller '${accessController.type}' already added.`)
} }
@ -71,21 +61,20 @@ const add = (accessController) => {
throw new Error('Given AccessController class needs to implement: type.') throw new Error('Given AccessController class needs to implement: type.')
} }
types[accessController.type] = accessController accessControllers[accessController.type] = accessController
} }
/** /**
* Removes an access controller from the types list. * Removes an access controller from the list.
* @param {string} type A valid access controller type. * @param {string} type A valid access controller type.
*/ */
const remove = type => { const removeAccessController = type => {
delete types[type] delete accessControllers[type]
} }
export { export {
types, accessControllers,
get, getAccessController,
isSupported, addAccessController,
add, removeAccessController
remove
} }

View File

@ -4,9 +4,8 @@
* Identities provides a framework for generating and managing identity * Identities provides a framework for generating and managing identity
* details and providers. * details and providers.
*/ */
import Identity, { isIdentity, isEqual, decodeIdentity } from './identity.js' import Identity, { isIdentity, isEqual, decodeIdentity } from './identity.js'
import { PublicKeyIdentityProvider } from './providers/index.js' import { getProviderFor } from './providers/index.js'
// import DIDIdentityProvider from './identity-providers/did.js' // import DIDIdentityProvider from './identity-providers/did.js'
// import EthIdentityProvider from './identity-providers/ethereum.js' // import EthIdentityProvider from './identity-providers/ethereum.js'
import KeyStore, { signMessage, verifyMessage } from '../key-store.js' import KeyStore, { signMessage, verifyMessage } from '../key-store.js'
@ -14,13 +13,8 @@ import { LRUStorage, IPFSBlockStorage, MemoryStorage, ComposedStorage } from '..
import pathJoin from '../utils/path-join.js' import pathJoin from '../utils/path-join.js'
const DefaultProviderType = 'publickey' const DefaultProviderType = 'publickey'
const DefaultIdentityKeysPath = pathJoin('./orbitdb', 'identities')
const supportedTypes = { const DefaultIdentityKeysPath = pathJoin('./orbitdb', 'identities')
publickey: PublicKeyIdentityProvider
// [DIDIdentityProvider.type]: DIDIdentityProvider,
// [EthIdentityProvider.type]: EthIdentityProvider
}
/** /**
* Creates an instance of Identities. * Creates an instance of Identities.
@ -167,68 +161,6 @@ const Identities = async ({ keystore, path, storage, ipfs } = {}) => {
} }
} }
/**
* Checks whether an identity provider is supported.
* @param {string} type The identity provider type.
* @return {boolean} True if the identity provider is supported, false
* otherwise.
* @static
*/
const isProviderSupported = (type) => {
return Object.keys(supportedTypes).includes(type)
}
/**
* Gets an identity provider.
* @param {string} type The identity provider type.
* @return {IdentityProvider} The IdentityProvider module corresponding to
* type.
* @throws IdentityProvider type is not supported if the identity provider is
* not supported.
* @static
*/
const getProviderFor = (type) => {
if (!isProviderSupported(type)) {
throw new Error(`IdentityProvider type '${type}' is not supported`)
}
return supportedTypes[type]
}
/**
* Adds an identity provider.
* @param {IdentityProvider} IdentityProvider The identity provider to add.
* @throws IdentityProvider must be given as an argument if no module is
* provided.
* @throws 'Given IdentityProvider doesn't have a field 'type' if the
* IdentityProvider does not include a type property.
* @static
*/
const addIdentityProvider = (IdentityProvider) => {
if (!IdentityProvider) {
throw new Error('IdentityProvider must be given as an argument')
}
if (!IdentityProvider.type ||
typeof IdentityProvider.type !== 'string') {
throw new Error('Given IdentityProvider doesn\'t have a field \'type\'')
}
supportedTypes[IdentityProvider.type] = IdentityProvider
}
/**
* Removes an identity provider.
* @param {string} type The identity provider type.
* @static
*/
const removeIdentityProvider = (type) => {
delete supportedTypes[type]
}
export { export {
Identities as default, Identities as default
isProviderSupported,
addIdentityProvider,
removeIdentityProvider
} }

View File

@ -3,7 +3,6 @@
* @description * @description
* An identity. * An identity.
*/ */
import * as Block from 'multiformats/block' import * as Block from 'multiformats/block'
import * as dagCbor from '@ipld/dag-cbor' import * as dagCbor from '@ipld/dag-cbor'
import { sha256 } from 'multiformats/hashes/sha2' import { sha256 } from 'multiformats/hashes/sha2'

View File

@ -1,9 +1,4 @@
export { export { default as Identities } from './identities.js'
default as Identities,
addIdentityProvider,
removeIdentityProvider,
isProviderSupported
} from './identities.js'
export { export {
default as Identity, default as Identity,
@ -11,4 +6,8 @@ export {
isEqual isEqual
} from './identity.js' } from './identity.js'
export { PublicKeyIdentityProvider } from './providers/index.js' export {
PublicKeyIdentityProvider,
addIdentityProvider,
identityProviders
} from './providers/index.js'

View File

@ -46,7 +46,68 @@
* *
* where my-custom-identity-provider is the custom module. * where my-custom-identity-provider is the custom module.
*/ */
import * as PublicKeyIdentityProvider from './publickey.js'
const identityProviders = {
publickey: PublicKeyIdentityProvider
// [DIDIdentityProvider.type]: DIDIdentityProvider,
// [EthIdentityProvider.type]: EthIdentityProvider
}
/**
* Checks whether an identity provider is supported.
* @param {string} type The identity provider type.
* @return {boolean} True if the identity provider is supported, false
* otherwise.
* @static
*/
const isProviderSupported = (type) => {
return Object.keys(identityProviders).includes(type)
}
/**
* Gets an identity provider.
* @param {string} type The identity provider type.
* @return {IdentityProvider} The IdentityProvider module corresponding to
* type.
* @throws IdentityProvider type is not supported if the identity provider is
* not supported.
* @static
*/
const getProviderFor = (type) => {
if (!isProviderSupported(type)) {
throw new Error(`IdentityProvider type '${type}' is not supported`)
}
return identityProviders[type]
}
/**
* Adds an identity provider.
* @param {IdentityProvider} IdentityProvider The identity provider to add.
* @throws IdentityProvider must be given as an argument if no module is
* provided.
* @throws 'Given IdentityProvider doesn't have a field 'type' if the
* IdentityProvider does not include a type property.
* @static
*/
const addIdentityProvider = (IdentityProvider) => {
if (!IdentityProvider) {
throw new Error('IdentityProvider must be given as an argument')
}
if (!IdentityProvider.type ||
typeof IdentityProvider.type !== 'string') {
throw new Error('Given IdentityProvider doesn\'t have a field \'type\'')
}
if (identityProviders[IdentityProvider.type]) {
throw new Error(`Type already added: ${IdentityProvider.type}`)
}
identityProviders[IdentityProvider.type] = IdentityProvider
}
// 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 * as PublicKeyIdentityProvider from './publickey.js' export { identityProviders, addIdentityProvider, getProviderFor, PublicKeyIdentityProvider }

View File

@ -1,8 +1,39 @@
export { default as OrbitDB } from './orbitdb.js' export {
export { databaseTypes, addDatabaseType } from './orbitdb.js' default as OrbitDB,
export { default as OrbitDBAddress, isValidAddress, parseAddress } from './address.js' databaseTypes,
addDatabaseType
} from './orbitdb.js'
export {
default as OrbitDBAddress,
isValidAddress,
parseAddress
} from './address.js'
export { Log, Entry, DefaultAccessController } from './oplog/index.js' export { Log, Entry, DefaultAccessController } from './oplog/index.js'
export { default as Database } from './database.js' export { default as Database } from './database.js'
export { default as KeyStore } from './key-store.js' export { default as KeyStore } from './key-store.js'
export { Identities, isIdentity } from './identities/index.js'
export { IPFSBlockStorage, LevelStorage, LRUStorage, MemoryStorage, ComposedStorage } from './storage/index.js' export {
addAccessController,
removeAccessController,
getAccessController,
accessControllers
} from './access-controllers/index.js'
export {
Identities,
isIdentity,
identityProviders,
addIdentityProvider
} from './identities/index.js'
export {
IPFSBlockStorage,
LevelStorage,
LRUStorage,
MemoryStorage,
ComposedStorage
} from './storage/index.js'

View File

@ -35,7 +35,7 @@ import OrbitDBAddress, { isValidAddress } from './address.js'
import Manifests from './manifest.js' import Manifests from './manifest.js'
import { createId } from './utils/index.js' import { createId } from './utils/index.js'
import pathJoin from './utils/path-join.js' import pathJoin from './utils/path-join.js'
import * as AccessControllers from './access-controllers/index.js' import { getAccessController } from './access-controllers/index.js'
import IPFSAccessController from './access-controllers/ipfs.js' import IPFSAccessController from './access-controllers/ipfs.js'
/** /**
@ -168,7 +168,7 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => {
manifest = await manifests.get(addr.path) manifest = await manifests.get(addr.path)
const acType = manifest.accessController.split('/', 2).pop() const acType = manifest.accessController.split('/', 2).pop()
const acAddress = manifest.accessController.replaceAll(`/${acType}/`, '') const acAddress = manifest.accessController.replaceAll(`/${acType}/`, '')
AccessController = AccessControllers.get(acType)() AccessController = getAccessController(acType)()
accessController = await AccessController({ orbitdb: { open, identity, ipfs }, identities, address: acAddress }) accessController = await AccessController({ orbitdb: { open, identity, ipfs }, identities, address: acAddress })
name = manifest.name name = manifest.name
type = type || manifest.type type = type || manifest.type
@ -234,4 +234,4 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => {
} }
} }
export { OrbitDB as default, OrbitDBAddress, addDatabaseType, databaseTypes, AccessControllers } export { OrbitDB as default, OrbitDBAddress, addDatabaseType, databaseTypes }

48
test/fixtures/providers.js vendored Normal file
View File

@ -0,0 +1,48 @@
const customIdentityProvider = () => {
const verifyIdentity = async (data) => { return true }
const CustomIdentityProvider = () => {
const getId = () => { return 'custom' }
const signIdentity = (data) => { return `signature '${data}'` }
return {
getId,
signIdentity,
type: 'custom'
}
}
return {
default: CustomIdentityProvider,
type: 'custom',
verifyIdentity
}
}
const fakeIdentityProvider = () => {
const verifyIdentity = async (data) => { return false }
const FakeIdentityProvider = () => {
const getId = () => { return 'pubKey' }
const signIdentity = (data) => { return `false signature '${data}'` }
return {
getId,
signIdentity,
type: 'fake'
}
}
return {
default: FakeIdentityProvider,
verifyIdentity,
type: 'fake'
}
}
const CustomIdentityProvider = customIdentityProvider()
const FakeIdentityProvider = fakeIdentityProvider()
export { CustomIdentityProvider, FakeIdentityProvider }

View File

@ -3,8 +3,9 @@ import rmrf from 'rimraf'
import { copy } from 'fs-extra' import { copy } from 'fs-extra'
import { toString as uint8ArrayToString } from 'uint8arrays/to-string' import { toString as uint8ArrayToString } from 'uint8arrays/to-string'
import KeyStore, { signMessage, verifyMessage } from '../../src/key-store.js' import KeyStore, { signMessage, verifyMessage } from '../../src/key-store.js'
import { Identities, addIdentityProvider, Identity, PublicKeyIdentityProvider } from '../../src/identities/index.js' import { Identities, identityProviders, addIdentityProvider, Identity, PublicKeyIdentityProvider } from '../../src/identities/index.js'
import testKeysPath from '../fixtures/test-keys-path.js' import testKeysPath from '../fixtures/test-keys-path.js'
import { CustomIdentityProvider, FakeIdentityProvider } from '../fixtures/providers.js'
const type = PublicKeyIdentityProvider.type const type = PublicKeyIdentityProvider.type
const keysPath = './testkeys' const keysPath = './testkeys'
@ -14,6 +15,15 @@ describe('Identities', function () {
await copy(testKeysPath, keysPath) await copy(testKeysPath, keysPath)
}) })
afterEach(async () => {
// reset the identityProviders.
for (const [key] of Object.entries(identityProviders)) {
if (key !== 'publickey') {
delete identityProviders[key]
}
}
})
after(async () => { after(async () => {
await rmrf(keysPath) await rmrf(keysPath)
}) })
@ -213,30 +223,8 @@ describe('Identities', function () {
}) })
it('false signature doesn\'t verify', async () => { it('false signature doesn\'t verify', async () => {
const IP = () => { addIdentityProvider(FakeIdentityProvider)
const verifyIdentity = async (data) => { return false } identity = await identities.createIdentity({ type: FakeIdentityProvider.type })
const FakeIdentityProvider = () => {
const getId = () => { return 'pubKey' }
const signIdentity = (data) => { return `false signature '${data}'` }
return {
getId,
signIdentity,
type: 'fake'
}
}
return {
default: FakeIdentityProvider,
verifyIdentity,
type: 'fake'
}
}
addIdentityProvider(IP())
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)
}) })
@ -345,4 +333,16 @@ describe('Identities', function () {
assert.strictEqual(verified, false) assert.strictEqual(verified, false)
}) })
}) })
describe('manage identity providers', () => {
it('has default identity providers', () => {
assert.deepStrictEqual(identityProviders, { publickey: PublicKeyIdentityProvider })
})
it('can add an identity provider', () => {
addIdentityProvider(CustomIdentityProvider)
assert.deepStrictEqual(identityProviders, { publickey: PublicKeyIdentityProvider, custom: CustomIdentityProvider })
})
})
}) })

View File

@ -1,7 +1,8 @@
import { strictEqual, deepStrictEqual, notStrictEqual } from 'assert' import { strictEqual, deepStrictEqual, notStrictEqual } from 'assert'
import rmrf from 'rimraf' import rmrf from 'rimraf'
import * as IPFS from 'ipfs-core' import * as IPFS from 'ipfs-core'
import OrbitDB, { AccessControllers } from '../src/orbitdb.js' import OrbitDB from '../src/orbitdb.js'
import { accessControllers, addAccessController, removeAccessController } from '../src/access-controllers/index.js'
import config from './config.js' import config from './config.js'
import pathJoin from '../src/utils/path-join.js' import pathJoin from '../src/utils/path-join.js'
@ -38,7 +39,7 @@ describe('Add a custom access controller', function () {
} }
// Remove the added custom database type from OrbitDB import // Remove the added custom database type from OrbitDB import
AccessControllers.remove(type) removeAccessController(type)
await rmrf('./orbitdb') await rmrf('./orbitdb')
await rmrf('./ipfs1') await rmrf('./ipfs1')
@ -51,7 +52,7 @@ describe('Add a custom access controller', function () {
'orbitdb' 'orbitdb'
] ]
deepStrictEqual(Object.keys(AccessControllers.types), expected) deepStrictEqual(Object.keys(accessControllers), expected)
}) })
it('throws and error if custom access controller hasn\'t been added', async () => { it('throws and error if custom access controller hasn\'t been added', async () => {
@ -71,7 +72,7 @@ describe('Add a custom access controller', function () {
describe('Custom access controller', function () { describe('Custom access controller', function () {
before(() => { before(() => {
AccessControllers.add(CustomAccessController) addAccessController(CustomAccessController)
}) })
it('create a database with the custom access controller', async () => { it('create a database with the custom access controller', async () => {
@ -83,7 +84,7 @@ describe('Add a custom access controller', function () {
it('throws and error if custom access controller already exists', async () => { it('throws and error if custom access controller already exists', async () => {
let err let err
try { try {
AccessControllers.add(CustomAccessController) addAccessController(CustomAccessController)
} catch (e) { } catch (e) {
err = e.toString() err = e.toString()
} }
@ -98,7 +99,7 @@ describe('Add a custom access controller', function () {
type type
] ]
deepStrictEqual(Object.keys(AccessControllers.types), expected) deepStrictEqual(Object.keys(accessControllers), expected)
}) })
it('can be removed from supported access controllers', async () => { it('can be removed from supported access controllers', async () => {
@ -107,9 +108,9 @@ describe('Add a custom access controller', function () {
'orbitdb' 'orbitdb'
] ]
AccessControllers.remove(type) removeAccessController(type)
deepStrictEqual(Object.keys(AccessControllers.types), expected) deepStrictEqual(Object.keys(accessControllers), expected)
}) })
}) })
}) })