diff --git a/src/interfaces/claims.ts b/src/interfaces/claims.ts index cec47b95..c028858b 100644 --- a/src/interfaces/claims.ts +++ b/src/interfaces/claims.ts @@ -1,14 +1,24 @@ -import { GenericVerifiableCredential } from "./common"; +/** + * Types of Claims + * + * Note that these are for the claims that get signed. + * Records that are the latest edited entities are in the records.ts file. + * + */ -export interface AgreeVerifiableCredential { - "@context": string; +import { ClaimObject } from "./common"; + +export interface AgreeActionClaim extends ClaimObject { + "@context": "https://schema.org"; "@type": string; object: Record; } // Note that previous VCs may have additional fields. // https://endorser.ch/doc/html/transactions.html#id4 -export interface GiveVerifiableCredential extends GenericVerifiableCredential { +export interface GiveActionClaim extends ClaimObject { + // context is optional because it might be embedded in another claim, eg. an AgreeAction + "@context"?: "https://schema.org"; "@type": "GiveAction"; agent?: { identifier: string }; description?: string; @@ -16,43 +26,21 @@ export interface GiveVerifiableCredential extends GenericVerifiableCredential { identifier?: string; image?: string; object?: { amountOfThisGood: number; unitCode: string }; - provider?: GenericVerifiableCredential; + provider?: ClaimObject; recipient?: { identifier: string }; - type: string[]; - issuer: string; - issuanceDate: string; - credentialSubject: { - id: string; - type: "GiveAction"; - offeredBy?: { - type: "Person"; - identifier: string; - }; - offeredTo?: { - type: "Person"; - identifier: string; - }; - offeredToProject?: { - type: "Project"; - identifier: string; - }; - offeredToProjectVisibleToDids?: string[]; - offeredToVisibleToDids?: string[]; - offeredByVisibleToDids?: string[]; - amount: { - type: "QuantitativeValue"; - value: number; - unitCode: string; - }; - startTime?: string; - endTime?: string; - }; +} + +export interface JoinActionClaim extends ClaimObject { + agent?: { identifier: string }; + event?: { organizer?: { name: string }; name?: string; startTime?: string }; } // Note that previous VCs may have additional fields. // https://endorser.ch/doc/html/transactions.html#id8 -export interface OfferVerifiableCredential extends GenericVerifiableCredential { +export interface OfferClaim extends ClaimObject { + "@context": "https://schema.org"; "@type": "Offer"; + agent?: { identifier: string }; description?: string; fulfills?: { "@type": string; identifier?: string; lastClaimId?: string }[]; identifier?: string; @@ -67,43 +55,18 @@ export interface OfferVerifiableCredential extends GenericVerifiableCredential { name?: string; }; }; - provider?: GenericVerifiableCredential; + offeredBy?: { + type?: "Person"; + identifier: string; + }; + provider?: ClaimObject; recipient?: { identifier: string }; validThrough?: string; - type: string[]; - issuer: string; - issuanceDate: string; - credentialSubject: { - id: string; - type: "Offer"; - offeredBy?: { - type: "Person"; - identifier: string; - }; - offeredTo?: { - type: "Person"; - identifier: string; - }; - offeredToProject?: { - type: "Project"; - identifier: string; - }; - offeredToProjectVisibleToDids?: string[]; - offeredToVisibleToDids?: string[]; - offeredByVisibleToDids?: string[]; - amount: { - type: "QuantitativeValue"; - value: number; - unitCode: string; - }; - startTime?: string; - endTime?: string; - }; } // Note that previous VCs may have additional fields. // https://endorser.ch/doc/html/transactions.html#id7 -export interface PlanVerifiableCredential extends GenericVerifiableCredential { +export interface PlanActionClaim extends ClaimObject { "@context": "https://schema.org"; "@type": "PlanAction"; name: string; @@ -117,11 +80,18 @@ export interface PlanVerifiableCredential extends GenericVerifiableCredential { } // AKA Registration & RegisterAction -export interface RegisterVerifiableCredential { - "@context": string; +export interface RegisterActionClaim extends ClaimObject { + "@context": "https://schema.org"; "@type": "RegisterAction"; agent: { identifier: string }; identifier?: string; - object: string; + object?: string; participant?: { identifier: string }; } + +export interface TenureClaim extends ClaimObject { + "@context": "https://endorser.ch"; + "@type": "Tenure"; + party?: { identifier: string }; + spatialUnit?: { geo?: { polygon?: string } }; +} diff --git a/src/interfaces/common.ts b/src/interfaces/common.ts index 6ce02803..566db958 100644 --- a/src/interfaces/common.ts +++ b/src/interfaces/common.ts @@ -37,15 +37,19 @@ export interface ErrorResult extends ResultWithType { export interface KeyMeta { did: string; - name?: string; publicKeyHex: string; - mnemonic: string; - derivationPath: string; - registered?: boolean; - profileImageUrl?: string; - identity?: string; // Stringified IIdentifier object from Veramo + derivationPath?: string; passkeyCredIdHex?: string; // The Webauthn credential ID in hex, if this is from a passkey - [key: string]: unknown; +} + +export interface KeyMetaMaybeWithPrivate extends KeyMeta { + mnemonic?: string; // 12 or 24 words encoding the seed + identity?: string; // Stringified IIdentifier object from Veramo +} + +export interface KeyMetaWithPrivate extends KeyMeta { + mnemonic: string; // 12 or 24 words encoding the seed + identity: string; // Stringified IIdentifier object from Veramo } export interface QuantitativeValue extends GenericVerifiableCredential { @@ -53,7 +57,6 @@ export interface QuantitativeValue extends GenericVerifiableCredential { "@context"?: string; amountOfThisGood: number; unitCode: string; - [key: string]: unknown; } export interface AxiosErrorResponse { @@ -87,34 +90,14 @@ export interface CreateAndSubmitClaimResult { handleId?: string; } -export interface PlanSummaryRecord { - handleId: string; - issuer: string; - claim: GenericVerifiableCredential; - [key: string]: unknown; -} - export interface Agent { identifier?: string; did?: string; - [key: string]: unknown; } export interface ClaimObject { "@type": string; "@context"?: string; - fulfills?: Array<{ - "@type": string; - identifier?: string; - [key: string]: unknown; - }>; - object?: GenericVerifiableCredential; - agent?: Agent; - participant?: { - identifier?: string; - [key: string]: unknown; - }; - identifier?: string; [key: string]: unknown; } @@ -125,53 +108,3 @@ export interface VerifiableCredentialClaim { credentialSubject: ClaimObject; [key: string]: unknown; } - -export interface GiveVerifiableCredential extends GenericVerifiableCredential { - "@type": "GiveAction"; - object?: GenericVerifiableCredential; - agent?: Agent; - participant?: { - identifier?: string; - [key: string]: unknown; - }; - fulfills?: Array<{ - "@type": string; - identifier?: string; - [key: string]: unknown; - }>; - [key: string]: unknown; -} - -export interface OfferVerifiableCredential extends GenericVerifiableCredential { - "@type": "Offer"; - object?: GenericVerifiableCredential; - agent?: Agent; - participant?: { - identifier?: string; - [key: string]: unknown; - }; - itemOffered?: { - description?: string; - isPartOf?: { - "@type": string; - identifier: string; - [key: string]: unknown; - }; - [key: string]: unknown; - }; - [key: string]: unknown; -} - -export interface RegisterVerifiableCredential - extends GenericVerifiableCredential { - "@type": "RegisterAction"; - agent: { - identifier: string; - }; - object: string; - participant?: { - identifier: string; - }; - identifier?: string; - [key: string]: unknown; -} diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index db7eed00..5d4b499d 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -13,9 +13,9 @@ export type { export type { // From claims.ts - GiveVerifiableCredential, - OfferVerifiableCredential, - RegisterVerifiableCredential, + GiveActionClaim, + OfferClaim, + RegisterActionClaim, } from "./claims"; export type { diff --git a/src/interfaces/records.ts b/src/interfaces/records.ts index 44d0c651..7a884f0c 100644 --- a/src/interfaces/records.ts +++ b/src/interfaces/records.ts @@ -1,14 +1,14 @@ -import { GiveVerifiableCredential, OfferVerifiableCredential } from "./claims"; +import { GiveActionClaim, OfferClaim } from "./claims"; // a summary record; the VC is found the fullClaim field export interface GiveSummaryRecord { - [x: string]: PropertyKey | undefined | GiveVerifiableCredential; + [x: string]: PropertyKey | undefined | GiveActionClaim; type?: string; agentDid: string; amount: number; amountConfirmed: number; description: string; - fullClaim: GiveVerifiableCredential; + fullClaim: GiveActionClaim; fulfillsHandleId: string; fulfillsPlanHandleId?: string; fulfillsType?: string; @@ -26,7 +26,7 @@ export interface OfferSummaryRecord { amount: number; amountGiven: number; amountGivenConfirmed: number; - fullClaim: OfferVerifiableCredential; + fullClaim: OfferClaim; fulfillsPlanHandleId: string; handleId: string; issuerDid: string; diff --git a/src/libs/crypto/vc/index.ts b/src/libs/crypto/vc/index.ts index ad36c761..b1556ab8 100644 --- a/src/libs/crypto/vc/index.ts +++ b/src/libs/crypto/vc/index.ts @@ -17,7 +17,7 @@ import { didEthLocalResolver } from "./did-eth-local-resolver"; import { PEER_DID_PREFIX, verifyPeerSignature } from "./didPeer"; import { base64urlDecodeString, createDidPeerJwt } from "./passkeyDidPeer"; import { urlBase64ToUint8Array } from "./util"; -import { KeyMeta } from "../../../interfaces/common"; +import { KeyMeta, KeyMetaWithPrivate } from "../../../interfaces/common"; export const ETHR_DID_PREFIX = "did:ethr:"; export const JWT_VERIFY_FAILED_CODE = "JWT_VERIFY_FAILED"; @@ -34,7 +34,7 @@ export function isFromPasskey(keyMeta?: KeyMeta): boolean { } export async function createEndorserJwtForKey( - account: KeyMeta, + account: KeyMetaWithPrivate, payload: object, expiresIn?: number, ) { diff --git a/src/libs/endorserServer.ts b/src/libs/endorserServer.ts index c65983b7..27bc14e5 100644 --- a/src/libs/endorserServer.ts +++ b/src/libs/endorserServer.ts @@ -38,7 +38,14 @@ import { getPasskeyExpirationSeconds, } from "../libs/util"; import { createEndorserJwtForKey } from "../libs/crypto/vc"; -import { KeyMeta } from "../interfaces/common"; +import { + GiveActionClaim, + JoinActionClaim, + OfferClaim, + PlanActionClaim, + RegisterActionClaim, + TenureClaim, +} from "../interfaces/claims"; import { GenericCredWrapper, @@ -46,15 +53,13 @@ import { AxiosErrorResponse, UserInfo, CreateAndSubmitClaimResult, - PlanSummaryRecord, - GiveVerifiableCredential, - OfferVerifiableCredential, - RegisterVerifiableCredential, ClaimObject, VerifiableCredentialClaim, - Agent, QuantitativeValue, + KeyMetaWithPrivate, + KeyMetaMaybeWithPrivate, } from "../interfaces/common"; +import { PlanSummaryRecord } from "../interfaces/records"; import { logger } from "../utils/logger"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; @@ -650,7 +655,7 @@ export async function getNewOffersToUserProjects( * @param lastClaimId supplied when editing a previous claim */ export function hydrateGive( - vcClaimOrig?: GiveVerifiableCredential, + vcClaimOrig?: GiveActionClaim, fromDid?: string, toDid?: string, description?: string, @@ -662,8 +667,8 @@ export function hydrateGive( imageUrl?: string, providerPlanHandleId?: string, lastClaimId?: string, -): GiveVerifiableCredential { - const vcClaim: GiveVerifiableCredential = vcClaimOrig +): GiveActionClaim { + const vcClaim: GiveActionClaim = vcClaimOrig ? R.clone(vcClaimOrig) : { "@context": SCHEMA_ORG_CONTEXT, @@ -694,7 +699,7 @@ export function hydrateGive( // Initialize fulfills array if not present if (!Array.isArray(vcClaim.fulfills)) { - vcClaim.fulfills = []; + vcClaim.fulfills = vcClaim.fulfills ? [vcClaim.fulfills] : []; } // Filter and add fulfills elements @@ -797,7 +802,7 @@ export async function createAndSubmitGive( export async function editAndSubmitGive( axios: Axios, apiServer: string, - fullClaim: GenericCredWrapper, + fullClaim: GenericCredWrapper, issuerDid: string, fromDid?: string, toDid?: string, @@ -838,7 +843,7 @@ export async function editAndSubmitGive( * @param lastClaimId supplied when editing a previous claim */ export function hydrateOffer( - vcClaimOrig?: OfferVerifiableCredential, + vcClaimOrig?: OfferClaim, fromDid?: string, toDid?: string, itemDescription?: string, @@ -848,8 +853,8 @@ export function hydrateOffer( fulfillsProjectHandleId?: string, validThrough?: string, lastClaimId?: string, -): OfferVerifiableCredential { - const vcClaim: OfferVerifiableCredential = vcClaimOrig +): OfferClaim { + const vcClaim: OfferClaim = vcClaimOrig ? R.clone(vcClaimOrig) : { "@context": SCHEMA_ORG_CONTEXT, @@ -857,12 +862,13 @@ export function hydrateOffer( }; if (lastClaimId) { + // this is an edit vcClaim.lastClaimId = lastClaimId; delete vcClaim.identifier; } if (fromDid) { - vcClaim.agent = { identifier: fromDid }; + vcClaim.offeredBy = { identifier: fromDid }; } if (toDid) { vcClaim.recipient = { identifier: toDid }; @@ -870,12 +876,10 @@ export function hydrateOffer( vcClaim.description = conditionDescription || undefined; if (amount && !isNaN(amount)) { - const quantitativeValue: QuantitativeValue = { - "@type": "QuantitativeValue", + vcClaim.includesObject = { amountOfThisGood: amount, unitCode: unitCode || "HUR", }; - vcClaim.object = quantitativeValue; } if (itemDescription || fulfillsProjectHandleId) { @@ -928,7 +932,7 @@ export async function createAndSubmitOffer( undefined, ); return createAndSubmitClaim( - vcClaim as OfferVerifiableCredential, + vcClaim as OfferClaim, issuerDid, apiServer, axios, @@ -938,7 +942,7 @@ export async function createAndSubmitOffer( export async function editAndSubmitOffer( axios: Axios, apiServer: string, - fullClaim: GenericCredWrapper, + fullClaim: GenericCredWrapper, issuerDid: string, itemDescription: string, amount?: number, @@ -961,7 +965,7 @@ export async function editAndSubmitOffer( fullClaim.id, ); return createAndSubmitClaim( - vcClaim as OfferVerifiableCredential, + vcClaim as OfferClaim, issuerDid, apiServer, axios, @@ -1036,7 +1040,7 @@ export async function createAndSubmitClaim( } export async function generateEndorserJwtUrlForAccount( - account: KeyMeta, + account: KeyMetaMaybeWithPrivate, isRegistered: boolean, givenName: string, profileImageUrl: string, @@ -1060,7 +1064,7 @@ export async function generateEndorserJwtUrlForAccount( } // Add the next key -- not recommended for the QR code for such a high resolution - if (isContact) { + if (isContact && account.derivationPath && account.mnemonic) { const newDerivPath = nextDerivationPath(account.derivationPath); const nextPublicHex = deriveAddress(account.mnemonic, newDerivPath)[2]; const nextPublicEncKey = Buffer.from(nextPublicHex, "hex"); @@ -1082,7 +1086,11 @@ export async function createEndorserJwtForDid( expiresIn?: number, ) { const account = await retrieveFullyDecryptedAccount(issuerDid); - return createEndorserJwtForKey(account as KeyMeta, payload, expiresIn); + return createEndorserJwtForKey( + account as KeyMetaWithPrivate, + payload, + expiresIn, + ); } /** @@ -1179,102 +1187,118 @@ export const claimSpecialDescription = ( identifiers: Array, contacts: Array, ) => { - let claim = record.claim; + let claim: + | GenericVerifiableCredential + | GenericCredWrapper = record.claim; if ("claim" in claim) { + // it's a nested GenericCredWrapper claim = claim.claim as GenericVerifiableCredential; } const issuer = didInfo(record.issuer, activeDid, identifiers, contacts); - const claimObj = claim as ClaimObject; - const type = claimObj["@type"] || "UnknownType"; + const type = claim["@type"] || "UnknownType"; if (type === "AgreeAction") { return ( issuer + " agreed with " + - claimSummary(claimObj.object as GenericVerifiableCredential) + claimSummary(claim.object as GenericVerifiableCredential) ); } else if (isAccept(claim)) { return ( issuer + " accepted " + - claimSummary(claimObj.object as GenericVerifiableCredential) + claimSummary(claim.object as GenericVerifiableCredential) ); } else if (type === "GiveAction") { - const giveClaim = claim as GiveVerifiableCredential; - const agent: Agent = giveClaim.agent || { - identifier: undefined, - did: undefined, - }; - const agentDid = agent.did || agent.identifier; - const contactInfo = agentDid - ? didInfo(agentDid, activeDid, identifiers, contacts) - : "someone"; - const offering = giveClaim.object - ? " " + claimSummary(giveClaim.object) + const giveClaim = claim as GiveActionClaim; + // @ts-expect-error because .did may be found in legacy data, before March 2023 + const legacyGiverDid = giveClaim.agent?.did; + const giver = giveClaim.agent?.identifier || legacyGiverDid; + const giverInfo = didInfo(giver, activeDid, identifiers, contacts); + let gaveAmount = giveClaim.object?.amountOfThisGood + ? displayAmount( + giveClaim.object.unitCode as string, + giveClaim.object.amountOfThisGood as number, + ) : ""; - const recipient = giveClaim.participant?.identifier; - const recipientInfo = recipient - ? " to " + didInfo(recipient, activeDid, identifiers, contacts) + if (giveClaim.description) { + if (gaveAmount) { + gaveAmount = gaveAmount + ", and also: "; + } + gaveAmount = gaveAmount + giveClaim.description; + } + if (!gaveAmount) { + gaveAmount = "something not described"; + } + // @ts-expect-error because .did may be found in legacy data, before March 2023 + const legacyRecipDid = giveClaim.recipient?.did; + const gaveRecipientId = giveClaim.recipient?.identifier || legacyRecipDid; + const gaveRecipientInfo = gaveRecipientId + ? " to " + didInfo(gaveRecipientId, activeDid, identifiers, contacts) : ""; - return contactInfo + " gave" + offering + recipientInfo; + return giverInfo + " gave" + gaveRecipientInfo + ": " + gaveAmount; } else if (type === "JoinAction") { - const joinClaim = claim as ClaimObject; - const agent: Agent = joinClaim.agent || { - identifier: undefined, - did: undefined, - }; - const agentDid = agent.did || agent.identifier; - const contactInfo = agentDid - ? didInfo(agentDid, activeDid, identifiers, contacts) - : "someone"; - const object = joinClaim.object as GenericVerifiableCredential; - const objectInfo = object ? " " + claimSummary(object) : ""; - return contactInfo + " joined" + objectInfo; + const joinClaim = claim as JoinActionClaim; + // @ts-expect-error because .did may be found in legacy data, before March 2023 + const legacyDid = joinClaim.agent?.did; + const agent = joinClaim.agent?.identifier || legacyDid; + const contactInfo = didInfo(agent, activeDid, identifiers, contacts); + + let eventOrganizer = + joinClaim.event && + joinClaim.event.organizer && + joinClaim.event.organizer.name; + eventOrganizer = eventOrganizer || ""; + let eventName = joinClaim.event && joinClaim.event.name; + eventName = eventName ? " " + eventName : ""; + let fullEvent = eventOrganizer + eventName; + fullEvent = fullEvent ? " attended the " + fullEvent : ""; + + let eventDate = joinClaim.event && joinClaim.event.startTime; + eventDate = eventDate ? " at " + eventDate : ""; + return contactInfo + fullEvent + eventDate; } else if (isOffer(claim)) { - const offerClaim = claim as OfferVerifiableCredential; - const agent: Agent = offerClaim.agent || { - identifier: undefined, - did: undefined, - }; - const agentDid = agent.did || agent.identifier; - const contactInfo = agentDid - ? didInfo(agentDid, activeDid, identifiers, contacts) - : "someone"; - const offering = offerClaim.object - ? " " + claimSummary(offerClaim.object) - : ""; - const offerRecipientId = offerClaim.participant?.identifier; + const offerClaim = claim as OfferClaim; + const offerer = offerClaim.offeredBy?.identifier; + const contactInfo = didInfo(offerer, activeDid, identifiers, contacts); + let offering = ""; + if (offerClaim.includesObject) { + offering += + " " + + displayAmount( + offerClaim.includesObject.unitCode, + offerClaim.includesObject.amountOfThisGood, + ); + } + if (offerClaim.itemOffered?.description) { + offering += ", saying: " + offerClaim.itemOffered?.description; + } + // @ts-expect-error because .did may be found in legacy data, before March 2023 + const legacyDid = offerClaim.recipient?.did; + const offerRecipientId = offerClaim.recipient?.identifier || legacyDid; const offerRecipientInfo = offerRecipientId ? " to " + didInfo(offerRecipientId, activeDid, identifiers, contacts) : ""; return contactInfo + " offered" + offering + offerRecipientInfo; } else if (type === "PlanAction") { - const planClaim = claim as ClaimObject; - const agent: Agent = planClaim.agent || { - identifier: undefined, - did: undefined, - }; - const agentDid = agent.did || agent.identifier; - const contactInfo = agentDid - ? didInfo(agentDid, activeDid, identifiers, contacts) - : "someone"; - const object = planClaim.object as GenericVerifiableCredential; - const objectInfo = object ? " " + claimSummary(object) : ""; - return contactInfo + " planned" + objectInfo; + const planClaim = claim as PlanActionClaim; + const claimer = planClaim.agent?.identifier || record.issuer; + const claimerInfo = didInfo(claimer, activeDid, identifiers, contacts); + return claimerInfo + " announced a project: " + planClaim.name; } else if (type === "Tenure") { - const tenureClaim = claim as ClaimObject; - const agent: Agent = tenureClaim.agent || { - identifier: undefined, - did: undefined, - }; - const agentDid = agent.did || agent.identifier; - const contactInfo = agentDid - ? didInfo(agentDid, activeDid, identifiers, contacts) - : "someone"; - const object = tenureClaim.object as GenericVerifiableCredential; - const objectInfo = object ? " " + claimSummary(object) : ""; - return contactInfo + " has tenure" + objectInfo; + const tenureClaim = claim as TenureClaim; + // @ts-expect-error because .did may be found in legacy data, before March 2023 + const legacyDid = tenureClaim.party?.did; + const claimer = tenureClaim.party?.identifier || legacyDid; + const contactInfo = didInfo(claimer, activeDid, identifiers, contacts); + const polygon = tenureClaim.spatialUnit?.geo?.polygon || ""; + return ( + contactInfo + + " possesses [" + + polygon.substring(0, polygon.indexOf(" ")) + + "...]" + ); } else { return issuer + " declared " + claimSummary(claim); } @@ -1331,7 +1355,7 @@ export async function createInviteJwt( identifier?: string, expiresIn?: number, // in seconds ): Promise { - const vcClaim: RegisterVerifiableCredential = { + const vcClaim: RegisterActionClaim = { "@context": SCHEMA_ORG_CONTEXT, "@type": "RegisterAction", agent: { identifier: activeDid }, diff --git a/src/libs/util.ts b/src/libs/util.ts index b93f38c5..b969c6e0 100644 --- a/src/libs/util.ts +++ b/src/libs/util.ts @@ -34,10 +34,10 @@ import { containsHiddenDid } from "../libs/endorserServer"; import { GenericCredWrapper, GenericVerifiableCredential, + KeyMetaWithPrivate, } from "../interfaces/common"; import { GiveSummaryRecord } from "../interfaces/records"; -import { OfferVerifiableCredential } from "../interfaces/claims"; -import { KeyMeta } from "../interfaces/common"; +import { OfferClaim } from "../interfaces/claims"; import { createPeerDid } from "../libs/crypto/vc/didPeer"; import { registerCredential } from "../libs/crypto/vc/passkeyDidPeer"; import { logger } from "../utils/logger"; @@ -378,17 +378,19 @@ export function base64ToBlob(base64DataUrl: string, sliceSize = 512) { * @param veriClaim is expected to have fields: claim and issuer */ export function offerGiverDid( - veriClaim: GenericCredWrapper, + veriClaim: GenericCredWrapper, ): string | undefined { - let giver; - const claim = veriClaim.claim as OfferVerifiableCredential; - if ( - claim.credentialSubject.offeredBy?.identifier && - !serverUtil.isHiddenDid(claim.credentialSubject.offeredBy.identifier) - ) { - giver = claim.credentialSubject.offeredBy.identifier; - } else if (veriClaim.issuer && !serverUtil.isHiddenDid(veriClaim.issuer)) { - giver = veriClaim.issuer; + const innerClaim = veriClaim.claim as OfferClaim; + let giver: string | undefined = undefined; + + giver = innerClaim.offeredBy?.identifier; + if (giver && !serverUtil.isHiddenDid(giver)) { + return giver; + } + + giver = veriClaim.issuer; + if (giver && !serverUtil.isHiddenDid(giver)) { + return giver; } return giver; } @@ -400,7 +402,10 @@ export function offerGiverDid( export const canFulfillOffer = ( veriClaim: GenericCredWrapper, ) => { - return veriClaim.claimType === "Offer" && !!offerGiverDid(veriClaim); + return ( + veriClaim.claimType === "Offer" && + !!offerGiverDid(veriClaim as GenericCredWrapper) + ); }; // return object with paths and arrays of DIDs for any keys ending in "VisibleToDid" @@ -469,11 +474,7 @@ export function findAllVisibleToDids( * **/ -export interface AccountKeyInfo - extends Omit, - Omit { - derivationPath?: string; // Make it optional to match Account type -} +export type AccountKeyInfo = Account & KeyMetaWithPrivate; export const retrieveAccountCount = async (): Promise => { let result = 0; @@ -510,12 +511,16 @@ export const retrieveAccountDids = async (): Promise => { return allDids; }; -// This is provided and recommended when the full key is not necessary so that -// future work could separate this info from the sensitive key material. +/** + * This is provided and recommended when the full key is not necessary so that + * future work could separate this info from the sensitive key material. + * + * If you need the private key data, use retrieveFullyDecryptedAccount instead. + */ export const retrieveAccountMetadata = async ( activeDid: string, -): Promise => { - let result: AccountKeyInfo | undefined = undefined; +): Promise => { + let result: Account | undefined = undefined; const platformService = PlatformServiceFactory.getInstance(); const dbAccount = await platformService.dbQuery( `SELECT * FROM accounts WHERE did = ?`, @@ -547,32 +552,16 @@ export const retrieveAccountMetadata = async ( return result; }; -export const retrieveAllAccountsMetadata = async (): Promise => { - const platformService = PlatformServiceFactory.getInstance(); - const dbAccounts = await platformService.dbQuery(`SELECT * FROM accounts`); - const accounts = databaseUtil.mapQueryResultToValues(dbAccounts) as Account[]; - let result = accounts.map((account) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { identity, mnemonic, ...metadata } = account; - return metadata as Account; - }); - if (USE_DEXIE_DB) { - // one of the few times we use accountsDBPromise directly; try to avoid more usage - const accountsDB = await accountsDBPromise; - const array = await accountsDB.accounts.toArray(); - result = array.map((account) => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const { identity, mnemonic, ...metadata } = account; - return metadata as Account; - }); - } - return result; -}; - +/** + * This contains sensitive data. If possible, use retrieveAccountMetadata instead. + * + * @param activeDid + * @returns account info with private key data decrypted + */ export const retrieveFullyDecryptedAccount = async ( activeDid: string, -): Promise => { - let result: AccountKeyInfo | undefined = undefined; +): Promise => { + let result: Account | undefined = undefined; const platformService = PlatformServiceFactory.getInstance(); const dbSecrets = await platformService.dbQuery( `SELECT secretBase64 from secret`, @@ -620,20 +609,26 @@ export const retrieveFullyDecryptedAccount = async ( return result; }; -// let's try and eliminate this -export const retrieveAllFullyDecryptedAccounts = async (): Promise< - Array -> => { +export const retrieveAllAccountsMetadata = async (): Promise => { const platformService = PlatformServiceFactory.getInstance(); - const queryResult = await platformService.dbQuery("SELECT * FROM accounts"); - let allAccounts = databaseUtil.mapQueryResultToValues( - queryResult, - ) as unknown as AccountEncrypted[]; + const dbAccounts = await platformService.dbQuery(`SELECT * FROM accounts`); + const accounts = databaseUtil.mapQueryResultToValues(dbAccounts) as Account[]; + let result = accounts.map((account) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { identity, mnemonic, ...metadata } = account; + return metadata as Account; + }); if (USE_DEXIE_DB) { + // one of the few times we use accountsDBPromise directly; try to avoid more usage const accountsDB = await accountsDBPromise; - allAccounts = (await accountsDB.accounts.toArray()) as AccountEncrypted[]; + const array = await accountsDB.accounts.toArray(); + result = array.map((account) => { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { identity, mnemonic, ...metadata } = account; + return metadata as Account; + }); } - return allAccounts; + return result; }; /** diff --git a/src/views/ClaimView.vue b/src/views/ClaimView.vue index 4f1b42ad..2aeab8f6 100644 --- a/src/views/ClaimView.vue +++ b/src/views/ClaimView.vue @@ -548,11 +548,7 @@ import { db } from "../db/index"; import { logConsoleAndDb } from "../db/databaseUtil"; import { Contact } from "../db/tables/contacts"; import * as serverUtil from "../libs/endorserServer"; -import { - GenericCredWrapper, - OfferVerifiableCredential, - ProviderInfo, -} from "../interfaces"; +import { GenericCredWrapper, OfferClaim, ProviderInfo } from "../interfaces"; import * as libsUtil from "../libs/util"; import { logger } from "../utils/logger"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; @@ -978,7 +974,7 @@ export default class ClaimView extends Vue { openFulfillGiftDialog() { const giver: libsUtil.GiverReceiverInputInfo = { did: libsUtil.offerGiverDid( - this.veriClaim as GenericCredWrapper, + this.veriClaim as GenericCredWrapper, ), }; (this.$refs.customGiveDialog as GiftedDialog).open( diff --git a/src/views/ContactAmountsView.vue b/src/views/ContactAmountsView.vue index 1d4e147d..1d2f3916 100644 --- a/src/views/ContactAmountsView.vue +++ b/src/views/ContactAmountsView.vue @@ -124,7 +124,7 @@ import * as databaseUtil from "../db/databaseUtil"; import { AgreeVerifiableCredential, GiveSummaryRecord, - GiveVerifiableCredential, + GiveActionClaim, } from "../interfaces"; import { createEndorserJwtVcFromClaim, @@ -276,7 +276,7 @@ export default class ContactAmountssView extends Vue { // Make claim // I use clone here because otherwise it gets a Proxy object. // eslint-disable-next-line @typescript-eslint/no-explicit-any - const origClaim: GiveVerifiableCredential = R.clone(record.fullClaim); + const origClaim: GiveActionClaim = R.clone(record.fullClaim); if (record.fullClaim["@context"] == SCHEMA_ORG_CONTEXT) { delete origClaim["@context"]; } diff --git a/src/views/DIDView.vue b/src/views/DIDView.vue index 2ed90148..5f7a1864 100644 --- a/src/views/DIDView.vue +++ b/src/views/DIDView.vue @@ -240,8 +240,8 @@ import * as databaseUtil from "../db/databaseUtil"; import { GenericCredWrapper, GenericVerifiableCredential, - GiveVerifiableCredential, - OfferVerifiableCredential, + GiveActionClaim, + OfferClaim, } from "../interfaces"; import { capitalizeAndInsertSpacesBeforeCaps, @@ -657,7 +657,7 @@ export default class DIDView extends Vue { */ public claimAmount(claim: GenericVerifiableCredential) { if (claim.claimType === "GiveAction") { - const giveClaim = claim.claim as GiveVerifiableCredential; + const giveClaim = claim.claim as GiveActionClaim; if (giveClaim.object?.unitCode && giveClaim.object?.amountOfThisGood) { return displayAmount( giveClaim.object.unitCode, @@ -667,7 +667,7 @@ export default class DIDView extends Vue { return ""; } } else if (claim.claimType === "Offer") { - const offerClaim = claim.claim as OfferVerifiableCredential; + const offerClaim = claim.claim as OfferClaim; if ( offerClaim.includesObject?.unitCode && offerClaim.includesObject?.amountOfThisGood diff --git a/src/views/GiftedDetailsView.vue b/src/views/GiftedDetailsView.vue index 3e8c7eb0..dc9475a6 100644 --- a/src/views/GiftedDetailsView.vue +++ b/src/views/GiftedDetailsView.vue @@ -268,7 +268,7 @@ import { } from "../constants/app"; import { db, retrieveSettingsForActiveAccount } from "../db/index"; import * as databaseUtil from "../db/databaseUtil"; -import { GenericCredWrapper, GiveVerifiableCredential } from "../interfaces"; +import { GenericCredWrapper, GiveActionClaim } from "../interfaces"; import { createAndSubmitGive, didInfo, @@ -311,7 +311,7 @@ export default class GiftedDetails extends Vue { imageUrl = ""; message = ""; offerId = ""; - prevCredToEdit?: GenericCredWrapper; + prevCredToEdit?: GenericCredWrapper; providerProjectId = ""; providerProjectName = "a project"; providedByProject = false; // basically static, based on input; if we allow changing then let's fix things (see below) @@ -328,7 +328,7 @@ export default class GiftedDetails extends Vue { this.prevCredToEdit = (this.$route.query["prevCredToEdit"] as string) ? (JSON.parse( this.$route.query["prevCredToEdit"] as string, - ) as GenericCredWrapper) + ) as GenericCredWrapper) : undefined; } catch (error) { this.$notify( @@ -883,7 +883,7 @@ export default class GiftedDetails extends Vue { ? this.fulfillsProjectId : undefined; const giveClaim = hydrateGive( - this.prevCredToEdit?.claim as GiveVerifiableCredential, + this.prevCredToEdit?.claim as GiveActionClaim, giverDid, recipientDid, this.description, diff --git a/src/views/IdentitySwitcherView.vue b/src/views/IdentitySwitcherView.vue index 143506e8..1c8f6f76 100644 --- a/src/views/IdentitySwitcherView.vue +++ b/src/views/IdentitySwitcherView.vue @@ -115,6 +115,7 @@ import { MASTER_SETTINGS_KEY } from "../db/tables/settings"; import * as databaseUtil from "../db/databaseUtil"; import { retrieveAllAccountsMetadata } from "../libs/util"; import { logger } from "../utils/logger"; +import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; @Component({ components: { QuickNav } }) export default class IdentitySwitcherView extends Vue { @@ -167,10 +168,13 @@ export default class IdentitySwitcherView extends Vue { if (did === "0") { did = undefined; } - await db.open(); - await db.settings.update(MASTER_SETTINGS_KEY, { - activeDid: did, - }); + await databaseUtil.updateDefaultSettings({ activeDid: did }); + if (USE_DEXIE_DB) { + await db.open(); + await db.settings.update(MASTER_SETTINGS_KEY, { + activeDid: did, + }); + } this.$router.push({ name: "account" }); } @@ -182,9 +186,16 @@ export default class IdentitySwitcherView extends Vue { title: "Delete Identity?", text: "Are you sure you want to erase this identity? (There is no undo. You may want to select it and back it up just in case.)", onYes: async () => { - // one of the few times we use accountsDBPromise directly; try to avoid more usage - const accountsDB = await accountsDBPromise; - await accountsDB.accounts.delete(id); + const platformService = PlatformServiceFactory.getInstance(); + await platformService.dbExec( + `DELETE FROM accounts WHERE id = ?`, + [id], + ); + if (USE_DEXIE_DB) { + // one of the few times we use accountsDBPromise directly; try to avoid more usage + const accountsDB = await accountsDBPromise; + await accountsDB.accounts.delete(id); + } this.otherIdentities = this.otherIdentities.filter( (ident) => ident.id !== id, ); diff --git a/src/views/OfferDetailsView.vue b/src/views/OfferDetailsView.vue index cfd7cc73..1c88fbba 100644 --- a/src/views/OfferDetailsView.vue +++ b/src/views/OfferDetailsView.vue @@ -182,7 +182,7 @@ import QuickNav from "../components/QuickNav.vue"; import TopMessage from "../components/TopMessage.vue"; import { NotificationIface, USE_DEXIE_DB } from "../constants/app"; import { db, retrieveSettingsForActiveAccount } from "../db/index"; -import { GenericCredWrapper, OfferVerifiableCredential } from "../interfaces"; +import { GenericCredWrapper, OfferClaim } from "../interfaces"; import { createAndSubmitOffer, didInfo, @@ -268,7 +268,7 @@ export default class OfferDetailsView extends Vue { /** Offer ID for editing */ offerId = ""; /** Previous offer data for editing */ - prevCredToEdit?: GenericCredWrapper; + prevCredToEdit?: GenericCredWrapper; /** Project ID if offer is for project */ projectId = ""; /** Project name display */ @@ -330,7 +330,7 @@ export default class OfferDetailsView extends Vue { this.prevCredToEdit = (this.$route.query["prevCredToEdit"] as string) ? (JSON.parse( this.$route.query["prevCredToEdit"] as string, - ) as GenericCredWrapper) + ) as GenericCredWrapper) : undefined; } catch (error: unknown) { this.$notify( @@ -768,7 +768,7 @@ export default class OfferDetailsView extends Vue { : undefined; const projectId = this.offeredToProject ? this.projectId : undefined; const offerClaim = hydrateOffer( - this.prevCredToEdit?.claim as OfferVerifiableCredential, + this.prevCredToEdit?.claim as OfferClaim, this.activeDid, recipientDid, this.descriptionOfItem, diff --git a/src/views/ProjectViewView.vue b/src/views/ProjectViewView.vue index e3876af1..23718f3a 100644 --- a/src/views/ProjectViewView.vue +++ b/src/views/ProjectViewView.vue @@ -613,9 +613,9 @@ import { GenericVerifiableCredential, GenericCredWrapper, GiveSummaryRecord, - GiveVerifiableCredential, + GiveActionClaim, OfferSummaryRecord, - OfferVerifiableCredential, + OfferClaim, PlanSummaryRecord, } from "../interfaces"; import GiftedDialog from "../components/GiftedDialog.vue"; @@ -1269,7 +1269,7 @@ export default class ProjectViewView extends Vue { } checkIsFulfillable(offer: OfferSummaryRecord) { - const offerRecord: GenericCredWrapper = { + const offerRecord: GenericCredWrapper = { ...serverUtil.BLANK_GENERIC_SERVER_RECORD, claim: offer.fullClaim, claimType: "Offer", @@ -1279,13 +1279,13 @@ export default class ProjectViewView extends Vue { } onClickFulfillGiveToOffer(offer: OfferSummaryRecord) { - const offerRecord: GenericCredWrapper = { + const offerClaimCred: GenericCredWrapper = { ...serverUtil.BLANK_GENERIC_SERVER_RECORD, claim: offer.fullClaim, issuer: offer.offeredByDid, }; const giver: libsUtil.GiverReceiverInputInfo = { - did: libsUtil.offerGiverDid(offerRecord), + did: libsUtil.offerGiverDid(offerClaimCred), }; (this.$refs.giveDialogToThis as GiftedDialog).open( giver, @@ -1327,7 +1327,7 @@ export default class ProjectViewView extends Vue { * @param confirmerIdList optional list of DIDs who confirmed; if missing, doesn't do a full server check */ checkIsConfirmable(give: GiveSummaryRecord, confirmerIdList?: string[]) { - const giveDetails: GenericCredWrapper = { + const giveDetails: GenericCredWrapper = { ...serverUtil.BLANK_GENERIC_SERVER_RECORD, claim: give.fullClaim, claimType: "GiveAction", diff --git a/src/views/ShareMyContactInfoView.vue b/src/views/ShareMyContactInfoView.vue index a32dde26..445c2775 100644 --- a/src/views/ShareMyContactInfoView.vue +++ b/src/views/ShareMyContactInfoView.vue @@ -49,7 +49,7 @@ import TopMessage from "../components/TopMessage.vue"; import { NotificationIface, APP_SERVER, USE_DEXIE_DB } from "../constants/app"; import * as databaseUtil from "../db/databaseUtil"; import { db, retrieveSettingsForActiveAccount } from "../db/index"; -import { retrieveAccountMetadata } from "../libs/util"; +import { retrieveFullyDecryptedAccount } from "../libs/util"; import { generateEndorserJwtUrlForAccount } from "../libs/endorserServer"; import { PlatformServiceFactory } from "@/services/PlatformServiceFactory"; import { logger } from "../utils/logger"; @@ -75,7 +75,7 @@ export default class ShareMyContactInfoView extends Vue { const isRegistered = !!settings.isRegistered; const profileImageUrl = settings.profileImageUrl || ""; - const account = await retrieveAccountMetadata(activeDid); + const account = await retrieveFullyDecryptedAccount(activeDid); const platformService = PlatformServiceFactory.getInstance(); const contactQueryResult = await platformService.dbQuery( diff --git a/test-playwright/60-new-activity.spec.ts b/test-playwright/60-new-activity.spec.ts index 255e51af..0856e575 100644 --- a/test-playwright/60-new-activity.spec.ts +++ b/test-playwright/60-new-activity.spec.ts @@ -44,7 +44,6 @@ test('New offers for another user', async ({ page }) => { // as user 1, go to the home page and check that two offers are shown as new await switchToUser(page, user01Did); await page.goto('./'); - // await page.getByTestId('closeOnboardingAndFinish').click(); let offerNumElem = page.getByTestId('newDirectOffersActivityNumber'); await expect(offerNumElem).toHaveText('2');