Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
},
"workspaces": [
"packages/*"
]
],
"packageManager": "yarn@1.22.19+sha1.4ba7fc5c6e704fce2066ecbfb0b0d8976fe62447"
}
4 changes: 2 additions & 2 deletions packages/account-node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@
"dependencies": {
"@verida/account": "^4.4.2-4.4.2-pr1.0",
"@verida/did-client": "^4.4.2-4.4.2-pr1.0",
"@verida/did-document": "^4.4.1",
"@verida/encryption-utils": "^4.0.0",
"@verida/keyring": "^4.4.0",
"@verida/types": "^4.4.0",
"@verida/vda-common": "^4.4.0",
"axios": "^0.27.2",
"did-resolver": "^4.0.1"
"did-resolver": "^4.0.1",
"ethers": "^5.8.0"
},
"devDependencies": {
"did-jwt": "5.7.0",
Expand Down
9 changes: 4 additions & 5 deletions packages/account-node/src/authTypes/VeridaDatabase.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import Axios from "axios";
import AutoAccount from "../auto";
import { AuthType } from '@verida/account'
import { Account } from "@verida/account";
import { ServiceEndpoint } from 'did-resolver'
Expand All @@ -8,13 +7,13 @@ import { ContextAuthorizationError, SecureContextPublicKey, VeridaDatabaseAuthCo
export default class VeridaDatabaseAuthType extends AuthType {

protected contextAuth?: VeridaDatabaseAuthContext
protected account: AutoAccount
protected account: Account
// 5 second request timeout
protected timeout: number = 10000

public constructor(account: Account, contextName: string, serviceEndpoint: ServiceEndpoint, signKey: SecureContextPublicKey) {
super(account, contextName, serviceEndpoint, signKey)
this.account = <AutoAccount> account
this.account = account
}

public async getAuthContext(config: VeridaDatabaseAuthTypeConfig = {
Expand Down Expand Up @@ -138,7 +137,7 @@ export default class VeridaDatabaseAuthType extends AuthType {

const consentMessage = `Invalidate device for this application context: "${this.contextName}"?\n\n${did.toLowerCase()}\n${deviceId}`
const signature = await this.account.sign(consentMessage)

try {
const response = await this.getAxios(this.contextName).post(`${contextAuth.endpointUri}auth/invalidateDeviceId`, {
did,
Expand Down Expand Up @@ -175,4 +174,4 @@ export default class VeridaDatabaseAuthType extends AuthType {
return Axios.create(config);
}

}
}
294 changes: 11 additions & 283 deletions packages/account-node/src/auto.ts
Original file line number Diff line number Diff line change
@@ -1,295 +1,23 @@
import { StorageLink, DIDStorageConfig } from '@verida/storage-link'
import { Keyring } from '@verida/keyring'
import { Account } from '@verida/account'

import { DIDClient, Wallet } from '@verida/did-client'
import EncryptionUtils from "@verida/encryption-utils"
import VeridaDatabaseAuthType from "./authTypes/VeridaDatabase"
import { AccountConfig, AccountNodeConfig, AuthContext, BlockchainAnchor, SecureContextConfig, SecureContextEndpointType, SecureContextServices, VdaDidEndpointResponses, VeridaDatabaseAuthTypeConfig } from '@verida/types'
import { NodeSelector, NodeSelectorConfig, NodeSelectorParams } from './nodeSelector'
import { ServiceEndpoint } from 'did-resolver'
import { AccountConfig, AccountNodeConfig } from '@verida/types'
import { VeridaDidWallet } from '@verida/did-client'
import { DefaultNetworkBlockchainAnchors } from '@verida/vda-common'

export function buildContextConsentMessage(did: string, contextName: string) {
const lowerCaseDid = did.toLowerCase()
return `Do you wish to unlock this storage context: "${contextName}"?\n\n${lowerCaseDid}`
}
import { WalletAccount, WalletAccountConfig } from './wallet-account'

/**
* An Authenticator that automatically signs everything
*/
export default class AutoAccount extends Account {

private didClient: DIDClient

private wallet: Wallet
private _did: string
protected accountConfig?: AccountConfig
protected autoConfig: AccountNodeConfig
protected contextAuths: Record<string, Record<string, VeridaDatabaseAuthType>> = {}
protected defaultNodes: string[] = []

export default class AutoAccount extends WalletAccount {
constructor(autoConfig: AccountNodeConfig, accountConfig?: AccountConfig) {
super()
this.accountConfig = accountConfig
this.autoConfig = autoConfig

const blockchain = DefaultNetworkBlockchainAnchors[autoConfig.network]
this.wallet = new Wallet(autoConfig.privateKey, blockchain.toString())
this._did = this.wallet.did

this.didClient = new DIDClient({
...autoConfig.didClientConfig,
network: autoConfig.network
})
}

public getDIDClient(): DIDClient {
return this.didClient
}

public setAccountConfig(accountConfig: AccountConfig) {
this.accountConfig = accountConfig
}

public getAccountConfig(): AccountConfig | undefined {
return this.accountConfig
}

public getAutoConfig(): AccountNodeConfig {
return this.autoConfig
}

public async keyring(contextName: string): Promise<Keyring> {
const did = await this.did()
const consentMessage = buildContextConsentMessage(did, contextName)
const signature = await this.sign(consentMessage)
return new Keyring(signature)
}

// returns a compact JWS
public async sign(message: string): Promise<string> {
return EncryptionUtils.signData(message, this.wallet.privateKeyBuffer)
}
const { privateKey, ...config } = autoConfig

public async did(): Promise<string> {
return this._did
}

public async loadDefaultStorageNodes(countryCode?: string, numNodes: number = 3, config: NodeSelectorParams = {}): Promise<void> {
const nodeUris = await this.getDefaultNodes(countryCode, numNodes, config)

this.accountConfig = {
defaultDatabaseServer: {
type: 'VeridaDatabase',
endpointUri: nodeUris
},
defaultMessageServer: {
type: 'VeridaMessage',
endpointUri: nodeUris
},
defaultNotificationServer: {
type: 'VeridaNotification',
endpointUri: config.notificationEndpoints! ? config.notificationEndpoints! : []
}
}
}
const blockchain = DefaultNetworkBlockchainAnchors[config.network]
const veridaDidWallet = VeridaDidWallet.fromPrivateKeyOrMnemonic(privateKey, blockchain)

private async getDefaultNodes(countryCode?: string, numNodes: number = 3, config: NodeSelectorParams = {}): Promise<ServiceEndpoint[]> {
if (this.defaultNodes && this.defaultNodes.length) {
return this.defaultNodes
const walletAccountConfig: WalletAccountConfig = {
...config,
veridaDidWallet
}

config.network = this.autoConfig.network
config.defaultTimeout = config.defaultTimeout ? config.defaultTimeout : 5000
config.notificationEndpoints = config.notificationEndpoints ? config.notificationEndpoints : []

const nodeSelector = new NodeSelector(<NodeSelectorConfig> config)
const nodeUris = await nodeSelector.selectEndpointUris(countryCode, numNodes)
this.defaultNodes = nodeUris

return this.defaultNodes
}

public async storageConfig(contextName: string, forceCreate?: boolean): Promise<SecureContextConfig | undefined> {
await this.ensureAuthenticated()

let did = await this.did()
let storageConfig = await StorageLink.getLink(this.autoConfig.network, this.didClient, did, contextName, true)

if (storageConfig && storageConfig.isLegacyDid) {
this._did = this._did.replace('polpos', 'mainnet')
did = this._did
}

// Create the storage config if it doesn't exist and force create is specified
if (!storageConfig && forceCreate) {
if (!this.accountConfig) {
await this.loadDefaultStorageNodes(this.autoConfig.countryCode)
}

const endpoints: SecureContextServices = {
databaseServer: this.accountConfig!.defaultDatabaseServer,
messageServer: this.accountConfig!.defaultMessageServer
}

if (this.accountConfig!.defaultStorageServer) {
endpoints.storageServer = this.accountConfig!.defaultStorageServer
}

if (this.accountConfig!.defaultNotificationServer) {
endpoints.notificationServer = this.accountConfig!.defaultNotificationServer
}

storageConfig = await DIDStorageConfig.generate(this, contextName, endpoints)

// Need to determine if this is a legacy DID
try {
const didDocument = await this.didClient.get(did)
storageConfig.isLegacyDid = didDocument.id.match('mainnet') ? true : false

if (storageConfig.isLegacyDid) {
this._did = this._did.replace('polpos', 'mainnet')
}
} catch (err: any) {
// DID may not exist, which means it's not a legacy DID, so no action required
if (!err.message.match('notFound')) {
// Unknown error, so rethrow
throw err
}
}

await this.linkStorage(storageConfig)
}

return storageConfig
}

/**
* Link storage to this user
*
* @param storageConfig
*/
public async linkStorage(storageConfig: SecureContextConfig): Promise<boolean> {
await this.ensureAuthenticated()
const keyring = await this.keyring(storageConfig.id)
const result = await StorageLink.setLink(this.autoConfig.network, this.didClient, storageConfig, keyring, this.wallet.privateKey)

for (let i in result) {
const response = result[i]
if (response.status !== 'success') {
return false
}
}

return true
}

/**
* Unlink storage for this user
*
* @param contextName
*/
public async unlinkStorage(contextName: string): Promise<boolean> {
await this.ensureAuthenticated()
let result = await StorageLink.unlink(this.autoConfig.network, this.didClient, contextName)
if (!result) {
return false
}

result = <VdaDidEndpointResponses> result
for (let i in result) {
const response = result[i]
if (response.status !== 'success') {
return false
}
}

return true
}

/**
* Link storage context service endpoint
*
*/
public async linkStorageContextService(contextName: string, endpointType: SecureContextEndpointType, serverType: string, endpointUris: string[]): Promise<boolean> {
await this.ensureAuthenticated()
const result = await StorageLink.setContextService(this.autoConfig.network, this.didClient, contextName, endpointType, serverType, endpointUris)

for (let i in result) {
const response = result[i]
if (response.status !== 'success') {
return false
}
}

return true
}

public async getAuthContext(contextName: string, contextConfig: SecureContextConfig, authConfig: VeridaDatabaseAuthTypeConfig, authType: string = "database"): Promise<AuthContext> {
if (typeof(authConfig.force) == 'undefined') {
authConfig.force = false
}

if (typeof(authConfig.endpointUri) == 'undefined') {
throw new Error('Endpoint must be specified when getting auth context')
}

const endpointUri = authConfig.endpointUri

// Use existing context auth instance if it exists
if (this.contextAuths[contextName] && this.contextAuths[contextName][endpointUri] && !authConfig.force && !authConfig.invalidAccessToken) {
return this.contextAuths[contextName][endpointUri].getAuthContext()
}

const signKey = contextConfig.publicKeys.signKey

// @todo: Currently hard code database server, need to support other service types in the future
const serviceEndpoint = contextConfig.services.databaseServer

if (serviceEndpoint.type == "VeridaDatabase") {
if (!this.contextAuths[contextName]) {
this.contextAuths[contextName] = {}
}

const authType = new VeridaDatabaseAuthType(this, contextName, endpointUri, signKey)
this.contextAuths[contextName][endpointUri] = authType

return authType.getAuthContext(authConfig)
}

throw new Error(`Unknown auth context type (${authType})`)
}

public async disconnectDevice(contextName: string, deviceId: string="Test device"): Promise<boolean> {
if (!this.contextAuths[contextName]) {
throw new Error(`Context not connected ${contextName}`)
}

let success = true
const contextAuths = this.contextAuths[contextName]
for (let i in contextAuths) {
if (!(await contextAuths[i].disconnectDevice(deviceId))) {
success = false
}
}

return success
}

public async ensureAuthenticated() {
if (!this.didClient.authenticated()) {
if (!this.autoConfig.didClientConfig.didEndpoints) {
const nodeUris = await this.getDefaultNodes(this.autoConfig.countryCode)
this.autoConfig.didClientConfig.didEndpoints = nodeUris.map((item) => `${item}did/`)
}

this.didClient.authenticate(
this.wallet.privateKey,
this.autoConfig.didClientConfig.callType,
this.autoConfig.didClientConfig.web3Config,
this.autoConfig.didClientConfig.didEndpoints!
)
}
super(walletAccountConfig, accountConfig)
}
}
23 changes: 2 additions & 21 deletions packages/account-node/src/contextAccount.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,8 @@
import { AccountConfig, AccountNodeConfig } from "@verida/types";
import LimitedAccount from "./limited";
import { Keyring } from "@verida/keyring";

export default class ContextAccount extends LimitedAccount {

private contextDid: string

constructor(autoConfig: AccountNodeConfig, did: string, contextName: string, accountConfig?: AccountConfig) {
super(autoConfig, accountConfig)
this.contextDid = did.toLowerCase()
this.signingContexts = [contextName]
}

public async keyring(contextName: string): Promise<Keyring> {
if (this.signingContexts.indexOf(contextName) == -1) {
throw new Error(`Account does not support context: ${contextName}`)
}

return new Keyring(this.autoConfig.privateKey)
}

public async did(): Promise<string> {
return this.contextDid
super(autoConfig, accountConfig, [contextName])
}

}
}
Loading