|
@ -1,23 +1,32 @@ |
|
|
import * as R from "ramda"; |
|
|
import * as R from "ramda"; |
|
|
|
|
|
import { IIdentifier } from "@veramo/core"; |
|
|
|
|
|
import { accessToken, SimpleSigner } from "@/libs/crypto"; |
|
|
|
|
|
import * as didJwt from "did-jwt"; |
|
|
|
|
|
import { Axios, AxiosResponse } from "axios"; |
|
|
|
|
|
|
|
|
export const SCHEMA_ORG_CONTEXT = "https://schema.org"; |
|
|
export const SCHEMA_ORG_CONTEXT = "https://schema.org"; |
|
|
export const SERVICE_ID = "endorser.ch"; |
|
|
export const SERVICE_ID = "endorser.ch"; |
|
|
|
|
|
|
|
|
export interface GenericClaim { |
|
|
export interface AgreeVerifiableCredential { |
|
|
"@context": string; |
|
|
"@context": string; |
|
|
"@type": string; |
|
|
"@type": string; |
|
|
issuedAt: string; |
|
|
|
|
|
// "any" because arbitrary objects can be subject of agreement
|
|
|
// "any" because arbitrary objects can be subject of agreement
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
claim: Record<any, any>; |
|
|
object: Record<any, any>; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export interface AgreeVerifiableCredential { |
|
|
export interface ClaimResult { |
|
|
|
|
|
success: { claimId: string; handleId: string }; |
|
|
|
|
|
error: { code: string; message: string }; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export interface GenericClaim { |
|
|
"@context": string; |
|
|
"@context": string; |
|
|
"@type": string; |
|
|
"@type": string; |
|
|
|
|
|
issuedAt: string; |
|
|
// "any" because arbitrary objects can be subject of agreement
|
|
|
// "any" because arbitrary objects can be subject of agreement
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
|
object: Record<any, any>; |
|
|
claim: Record<any, any>; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
export interface GiveServerRecord { |
|
|
export interface GiveServerRecord { |
|
@ -35,10 +44,10 @@ export interface GiveServerRecord { |
|
|
export interface GiveVerifiableCredential { |
|
|
export interface GiveVerifiableCredential { |
|
|
"@context"?: string; // optional when embedded, eg. in an Agree
|
|
|
"@context"?: string; // optional when embedded, eg. in an Agree
|
|
|
"@type": string; |
|
|
"@type": string; |
|
|
agent: { identifier: string }; |
|
|
agent?: { identifier: string }; |
|
|
description?: string; |
|
|
description?: string; |
|
|
identifier?: string; |
|
|
identifier?: string; |
|
|
object: { amountOfThisGood: number; unitCode: string }; |
|
|
object?: { amountOfThisGood: number; unitCode: string }; |
|
|
recipient: { identifier: string }; |
|
|
recipient: { identifier: string }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
@ -50,6 +59,11 @@ export interface RegisterVerifiableCredential { |
|
|
recipient: { identifier: string }; |
|
|
recipient: { identifier: string }; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export interface InternalError { |
|
|
|
|
|
error: string; // for system logging
|
|
|
|
|
|
userMessage?: string; // for user display
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// This is used to check for hidden info.
|
|
|
// This is used to check for hidden info.
|
|
|
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
|
|
|
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
|
|
|
const HIDDEN_DID = "did:none:HIDDEN"; |
|
|
const HIDDEN_DID = "did:none:HIDDEN"; |
|
@ -69,6 +83,8 @@ export function didInfo(did, identifiers, contacts) { |
|
|
const contact = R.find((c) => c.did === did, contacts); |
|
|
const contact = R.find((c) => c.did === did, contacts); |
|
|
if (contact) { |
|
|
if (contact) { |
|
|
return contact.name || "Someone Unnamed in Contacts"; |
|
|
return contact.name || "Someone Unnamed in Contacts"; |
|
|
|
|
|
} else if (!did) { |
|
|
|
|
|
return "Unpecified Person"; |
|
|
} else if (isHiddenDid(did)) { |
|
|
} else if (isHiddenDid(did)) { |
|
|
return "Someone Not In Network"; |
|
|
return "Someone Not In Network"; |
|
|
} else { |
|
|
} else { |
|
@ -76,3 +92,91 @@ export function didInfo(did, identifiers, contacts) { |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* For result, see https://endorser.ch:3000/api-docs/#/claims/post_api_v2_claim
|
|
|
|
|
|
|
|
|
|
|
|
* @param identity |
|
|
|
|
|
* @param fromDid may be null |
|
|
|
|
|
* @param toDid |
|
|
|
|
|
* @param description may be null; should have this or hours |
|
|
|
|
|
* @param hours may be null; should have this or description |
|
|
|
|
|
*/ |
|
|
|
|
|
export async function createAndSubmitGive( |
|
|
|
|
|
axios: Axios, |
|
|
|
|
|
apiServer: string, |
|
|
|
|
|
identity: IIdentifier, |
|
|
|
|
|
fromDid: string, |
|
|
|
|
|
toDid: string, |
|
|
|
|
|
description: string, |
|
|
|
|
|
hours: number |
|
|
|
|
|
): Promise<AxiosResponse<ClaimResult> | InternalError> { |
|
|
|
|
|
// Make a claim
|
|
|
|
|
|
const vcClaim: GiveVerifiableCredential = { |
|
|
|
|
|
"@context": "https://schema.org", |
|
|
|
|
|
"@type": "GiveAction", |
|
|
|
|
|
recipient: { identifier: toDid }, |
|
|
|
|
|
}; |
|
|
|
|
|
if (fromDid) { |
|
|
|
|
|
vcClaim.agent = { identifier: fromDid }; |
|
|
|
|
|
} |
|
|
|
|
|
if (description) { |
|
|
|
|
|
vcClaim.description = description; |
|
|
|
|
|
} |
|
|
|
|
|
if (hours) { |
|
|
|
|
|
vcClaim.object = { amountOfThisGood: hours, unitCode: "HUR" }; |
|
|
|
|
|
} |
|
|
|
|
|
// Make a payload for the claim
|
|
|
|
|
|
const vcPayload = { |
|
|
|
|
|
vc: { |
|
|
|
|
|
"@context": ["https://www.w3.org/2018/credentials/v1"], |
|
|
|
|
|
type: ["VerifiableCredential"], |
|
|
|
|
|
credentialSubject: vcClaim, |
|
|
|
|
|
}, |
|
|
|
|
|
}; |
|
|
|
|
|
// Create a signature using private key of identity
|
|
|
|
|
|
if (identity.keys[0].privateKeyHex == null) { |
|
|
|
|
|
return new Promise<InternalError>((resolve, reject) => { |
|
|
|
|
|
reject({ |
|
|
|
|
|
error: "No private key", |
|
|
|
|
|
message: |
|
|
|
|
|
"Your identifier " + |
|
|
|
|
|
identity.did + |
|
|
|
|
|
" is not configured correctly. Use a different identifier.", |
|
|
|
|
|
}); |
|
|
|
|
|
}); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
|
|
|
|
const privateKeyHex: string = identity.keys[0].privateKeyHex!; |
|
|
|
|
|
const signer = await SimpleSigner(privateKeyHex); |
|
|
|
|
|
const alg = undefined; |
|
|
|
|
|
// Create a JWT for the request
|
|
|
|
|
|
const vcJwt: string = await didJwt.createJWT(vcPayload, { |
|
|
|
|
|
alg: alg, |
|
|
|
|
|
issuer: identity.did, |
|
|
|
|
|
signer: signer, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
// Make the xhr request payload
|
|
|
|
|
|
|
|
|
|
|
|
const payload = JSON.stringify({ jwtEncoded: vcJwt }); |
|
|
|
|
|
const url = apiServer + "/api/v2/claim"; |
|
|
|
|
|
const token = await accessToken(identity); |
|
|
|
|
|
const headers = { |
|
|
|
|
|
"Content-Type": "application/json", |
|
|
|
|
|
Authorization: "Bearer " + token, |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
return axios.post(url, payload, { headers }); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// from https://stackoverflow.com/a/175787/845494
|
|
|
|
|
|
//
|
|
|
|
|
|
export function isNumeric(str: string): boolean { |
|
|
|
|
|
return !isNaN(+str); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
export function numberOrZero(str: string): number { |
|
|
|
|
|
return isNumeric(str) ? +str : 0; |
|
|
|
|
|
} |
|
|