add expiration inside JWANT & refactor getHeaders to move toward supporting did:peer

This commit is contained in:
2024-07-09 17:56:48 -06:00
parent 42fde503e3
commit 45f0a14661
17 changed files with 163 additions and 276 deletions

View File

@@ -807,32 +807,6 @@ export default class AccountViewView extends Vue {
}
}
/**
* Asynchronously retrieves headers for HTTP requests.
*
* @param {IIdentifier} identity - The identity object for which to generate the headers.
* @returns {Promise<Record<string,string>>} A Promise that resolves to an object containing the headers.
*
* @throws Will throw an error if unable to generate an access token.
*/
public async getHeaders(
identity: IIdentifier,
): Promise<Record<string, string>> {
try {
const token = await accessToken(identity);
const headers: Record<string, string> = {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
};
return headers;
} catch (error) {
console.error("Failed to get headers:", error);
return Promise.reject(error);
}
}
// call fn, copy text to the clipboard, then redo fn after 2 seconds
doCopyTwoSecRedo(text: string, fn: () => void) {
fn();
@@ -884,7 +858,7 @@ export default class AccountViewView extends Vue {
this.publicHex = identity.keys[0].publicKeyHex;
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
this.derivationPath = identity.keys[0].meta?.derivationPath as string;
this.checkLimitsFor(identity);
this.checkLimitsFor(this.activeDid);
} else {
// Handle the case where any of these are null or undefined
}
@@ -1238,9 +1212,8 @@ export default class AccountViewView extends Vue {
}
async checkLimits() {
const identity = await this.getIdentity(this.activeDid);
if (identity) {
this.checkLimitsFor(identity);
if (this.activeDid) {
this.checkLimitsFor(this.activeDid);
} else {
this.limitsMessage =
"You have no identifier, or your data has been corrupted.";
@@ -1252,7 +1225,7 @@ export default class AccountViewView extends Vue {
*
* Updates component state variables `limits`, `limitsMessage`, and `loadingLimits`.
*/
public async checkLimitsFor(identity: IIdentifier) {
public async checkLimitsFor(did: string) {
this.loadingLimits = true;
this.limitsMessage = "";
@@ -1260,7 +1233,7 @@ export default class AccountViewView extends Vue {
const resp = await fetchEndorserRateLimits(
this.apiServer,
this.axios,
identity,
did,
);
if (resp.status === 200) {
this.endorserLimits = resp.data;
@@ -1288,7 +1261,7 @@ export default class AccountViewView extends Vue {
const imageResp = await fetchImageRateLimits(
this.apiServer,
this.axios,
identity,
did,
);
if (imageResp.status === 200) {
this.imageLimits = imageResp.data;

View File

@@ -29,7 +29,6 @@
</template>
<script lang="ts">
import { RawAxiosRequestHeaders } from "axios";
import { IIdentifier } from "@veramo/core";
import { Component, Vue } from "vue-facing-decorator";
@@ -37,7 +36,6 @@ import GiftedDialog from "@/components/GiftedDialog.vue";
import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import * as serverUtil from "@/libs/endorserServer";
import QuickNav from "@/components/QuickNav.vue";
import { Account } from "@/db/tables/accounts";
@@ -82,18 +80,6 @@ export default class ClaimAddRawView extends Vue {
return identity;
}
public async getHeaders(identity: IIdentifier) {
const headers: RawAxiosRequestHeaders = {
"Content-Type": "application/json",
};
if (identity) {
const token = await accessToken(identity);
headers["Authorization"] = "Bearer " + token;
}
return headers;
}
// similar code is found in ProjectViewView
async submitClaim() {
const fullClaim = JSON.parse(this.claimStr);
const result = await serverUtil.createAndSubmitClaim(

View File

@@ -407,7 +407,7 @@
</template>
<script lang="ts">
import { AxiosError, RawAxiosRequestHeaders } from "axios";
import { AxiosError } from "axios";
import * as yaml from "js-yaml";
import * as R from "ramda";
import { IIdentifier } from "@veramo/core";
@@ -419,7 +419,6 @@ import { NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import * as serverUtil from "@/libs/endorserServer";
import * as libsUtil from "@/libs/util";
import QuickNav from "@/components/QuickNav.vue";
@@ -432,7 +431,6 @@ import { GiverReceiverInputInfo } from "@/libs/endorserServer";
export default class ClaimView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
accountIdentityStr: string = "null";
activeDid = "";
allMyDids: Array<string> = [];
allContacts: Array<Contact> = [];
@@ -485,15 +483,12 @@ export default class ClaimView extends Vue {
const accounts = accountsDB.accounts;
const accountsArr: Array<Account> = await accounts?.toArray();
this.allMyDids = accountsArr.map((acc) => acc.did);
const account = accountsArr.find((acc) => acc.did === this.activeDid);
this.accountIdentityStr = (account?.identity as string) || "null";
const identity = JSON.parse(this.accountIdentityStr);
const pathParam = window.location.pathname.substring("/claim/".length);
let claimId;
if (pathParam) {
claimId = decodeURIComponent(pathParam);
await this.loadClaim(claimId, identity);
await this.loadClaim(claimId, this.activeDid);
} else {
this.$notify(
{
@@ -543,17 +538,6 @@ export default class ClaimView extends Vue {
return identity;
}
public async getHeaders(identity: IIdentifier) {
const headers: RawAxiosRequestHeaders = {
"Content-Type": "application/json",
};
if (identity) {
const token = await accessToken(identity);
headers["Authorization"] = "Bearer " + token;
}
return headers;
}
// Isn't there a better way to make this available to the template?
didInfo(did: string) {
return serverUtil.didInfo(
@@ -564,12 +548,12 @@ export default class ClaimView extends Vue {
);
}
async loadClaim(claimId: string, identity: IIdentifier) {
async loadClaim(claimId: string, userDid: string) {
const urlPath = libsUtil.isGlobalUri(claimId)
? "/api/claim/byHandle/"
: "/api/claim/";
const url = this.apiServer + urlPath + encodeURIComponent(claimId);
const headers = await this.getHeaders(identity);
const headers = await serverUtil.getHeaders(userDid);
try {
const resp = await this.axios.get(url, { headers });
@@ -601,7 +585,7 @@ export default class ClaimView extends Vue {
this.apiServer +
"/api/v2/report/gives?handleId=" +
encodeURIComponent(this.veriClaim.handleId as string);
const giveHeaders = await this.getHeaders(identity);
const giveHeaders = await serverUtil.getHeaders(userDid);
const giveResp = await this.axios.get(giveUrl, {
headers: giveHeaders,
});
@@ -615,7 +599,7 @@ export default class ClaimView extends Vue {
this.apiServer +
"/api/v2/report/offers?handleId=" +
encodeURIComponent(this.veriClaim.handleId as string);
const offerHeaders = await this.getHeaders(identity);
const offerHeaders = await serverUtil.getHeaders(userDid);
const offerResp = await this.axios.get(offerUrl, {
headers: offerHeaders,
});
@@ -631,7 +615,7 @@ export default class ClaimView extends Vue {
this.apiServer +
"/api/report/issuersWhoClaimedOrConfirmed?claimId=" +
encodeURIComponent(serverUtil.stripEndorserPrefix(claimId));
const confirmHeaders = await this.getHeaders(identity);
const confirmHeaders = await serverUtil.getHeaders(userDid);
const response = await this.axios.get(confirmUrl, {
headers: confirmHeaders,
});
@@ -673,15 +657,9 @@ export default class ClaimView extends Vue {
}
async showFullClaim(claimId: string) {
await accountsDB.open();
const accounts = accountsDB.accounts;
const accountsArr: Account[] = await accounts?.toArray();
const account = accountsArr.find((acc) => acc.did === this.activeDid);
const identity = JSON.parse((account?.identity as string) || "null");
const url =
this.apiServer + "/api/claim/full/" + encodeURIComponent(claimId);
const headers = await this.getHeaders(identity);
const headers = await serverUtil.getHeaders(this.activeDid);
try {
const resp = await this.axios.get(url, { headers });
@@ -794,7 +772,7 @@ export default class ClaimView extends Vue {
};
this.$router.push(route).then(async () => {
this.resetThisValues();
await this.loadClaim(claimId, JSON.parse(this.accountIdentityStr));
await this.loadClaim(claimId, this.activeDid);
});
}

View File

@@ -420,7 +420,6 @@ import { isGiveAction } from "@/libs/util";
export default class ClaimView extends Vue {
$notify!: (notification: NotificationIface, timeout?: number) => void;
accountIdentityStr: string = "null";
activeDid = "";
allMyDids: Array<string> = [];
allContacts: Array<Contact> = [];
@@ -471,9 +470,6 @@ export default class ClaimView extends Vue {
const accounts = accountsDB.accounts;
const accountsArr: Array<Account> = await accounts?.toArray();
this.allMyDids = accountsArr.map((acc) => acc.did);
const account = accountsArr.find((acc) => acc.did === this.activeDid);
this.accountIdentityStr = (account?.identity as string) || "null";
const identity = JSON.parse(this.accountIdentityStr);
const pathParam = window.location.pathname.substring(
"/confirm-gift/".length,
@@ -481,7 +477,7 @@ export default class ClaimView extends Vue {
let claimId;
if (pathParam) {
claimId = decodeURIComponent(pathParam);
await this.loadClaim(claimId, identity);
await this.loadClaim(claimId, this.activeDid);
} else {
this.$notify(
{
@@ -546,17 +542,6 @@ export default class ClaimView extends Vue {
return identity;
}
public async getHeaders(identity: IIdentifier) {
const headers: RawAxiosRequestHeaders = {
"Content-Type": "application/json",
};
if (identity) {
const token = await accessToken(identity);
headers["Authorization"] = "Bearer " + token;
}
return headers;
}
// Isn't there a better way to make this available to the template?
didInfo(did: string | undefined) {
return serverUtil.didInfo(
@@ -567,14 +552,14 @@ export default class ClaimView extends Vue {
);
}
async loadClaim(claimId: string, identity: IIdentifier) {
async loadClaim(claimId: string, userDid: string) {
const urlPath = libsUtil.isGlobalUri(claimId)
? "/api/claim/byHandle/"
: "/api/claim/";
const url = this.apiServer + urlPath + encodeURIComponent(claimId);
try {
const headers = await this.getHeaders(identity);
const headers = await serverUtil.getHeaders(userDid);
const resp = await this.axios.get(url, { headers });
// resp.data is:
// - a Jwt from https://api.endorser.ch/api-docs/
@@ -614,7 +599,7 @@ export default class ClaimView extends Vue {
this.apiServer +
"/api/v2/report/gives?handleId=" +
encodeURIComponent(this.veriClaim.handleId as string);
const giveHeaders = await this.getHeaders(identity);
const giveHeaders = await serverUtil.getHeaders(userDid);
const giveResp = await this.axios.get(giveUrl, {
headers: giveHeaders,
});
@@ -685,7 +670,7 @@ export default class ClaimView extends Vue {
this.apiServer +
"/api/report/issuersWhoClaimedOrConfirmed?claimId=" +
encodeURIComponent(serverUtil.stripEndorserPrefix(claimId));
const confirmHeaders = await this.getHeaders(identity);
const confirmHeaders = await serverUtil.getHeaders(userDid);
const response = await this.axios.get(confirmUrl, {
headers: confirmHeaders,
});
@@ -794,7 +779,7 @@ export default class ClaimView extends Vue {
};
this.$router.push(route).then(async () => {
this.resetThisValues();
await this.loadClaim(claimId, JSON.parse(this.accountIdentityStr));
await this.loadClaim(claimId, this.activeDid);
});
}

View File

@@ -120,6 +120,7 @@ import { accessToken, SimpleSigner } from "@/libs/crypto";
import {
AgreeVerifiableCredential,
displayAmount,
getHeaders,
GiveSummaryRecord,
GiveVerifiableCredential,
SCHEMA_ORG_CONTEXT,
@@ -148,7 +149,7 @@ export default class ContactAmountssView extends Vue {
.where("did")
.equals(activeDid)
.first();
const identity = JSON.parse(account?.identity || "null");
const identity = JSON.parse((account?.identity as string) || "null");
if (!identity) {
throw new Error(
@@ -158,15 +159,6 @@ export default class ContactAmountssView extends Vue {
return identity;
}
public async getHeaders(identity: IIdentifier) {
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
return headers;
}
async created() {
try {
await db.open();
@@ -174,8 +166,8 @@ export default class ContactAmountssView extends Vue {
this.contact = (await db.contacts.get(contactDid)) || null;
const settings = await db.settings.get(MASTER_SETTINGS_KEY);
this.activeDid = settings?.activeDid || "";
this.apiServer = settings?.apiServer || "";
this.activeDid = (settings?.activeDid as string) || "";
this.apiServer = (settings?.apiServer as string) || "";
if (this.activeDid && this.contact) {
this.loadGives(this.activeDid, this.contact);
@@ -207,7 +199,7 @@ export default class ContactAmountssView extends Vue {
encodeURIComponent(identity.did) +
"&recipientDid=" +
encodeURIComponent(contact.did);
const headers = await this.getHeaders(identity);
const headers = await getHeaders(activeDid);
const resp = await this.axios.get(url, { headers });
if (resp.status === 200) {
result = resp.data.data;
@@ -234,7 +226,7 @@ export default class ContactAmountssView extends Vue {
encodeURIComponent(contact.did) +
"&recipientDid=" +
encodeURIComponent(identity.did);
const headers2 = await this.getHeaders(identity);
const headers2 = await getHeaders(activeDid);
const resp2 = await this.axios.get(url2, { headers: headers2 });
if (resp2.status === 200) {
result = R.concat(result, resp2.data.data);

View File

@@ -72,17 +72,15 @@
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import { IIdentifier } from "@veramo/core";
import GiftedDialog from "@/components/GiftedDialog.vue";
import QuickNav from "@/components/QuickNav.vue";
import EntityIcon from "@/components/EntityIcon.vue";
import { NotificationIface } from "@/constants/app";
import { db, accountsDB } from "@/db/index";
import { Account, AccountsSchema } from "@/db/tables/accounts";
import { AccountsSchema } from "@/db/tables/accounts";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import { GiverReceiverInputInfo } from "@/libs/endorserServer";
@Component({
@@ -134,32 +132,7 @@ export default class ContactGiftingView extends Vue {
}
}
public async getIdentity(activeDid: string) {
await accountsDB.open();
const account = (await accountsDB.accounts
.where("did")
.equals(activeDid)
.first()) as Account;
const identity = JSON.parse(account?.identity || "null");
if (!identity) {
throw new Error(
"Attempted to load Give records with no identifier available.",
);
}
return identity;
}
public async getHeaders(identity: IIdentifier) {
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
return headers;
}
openDialog(giver: GiverReceiverInputInfo) {
openDialog(giver?: GiverReceiverInputInfo) {
const recipient = this.projectId
? undefined
: { did: this.activeDid, name: "you" };

View File

@@ -137,8 +137,11 @@ export default class ContactQRScanShow extends Vue {
const publicKeyHex = identity.keys[0].publicKeyHex;
const publicEncKey = Buffer.from(publicKeyHex, "hex").toString("base64");
const newDerivPath = nextDerivationPath(account.derivationPath);
const nextPublicHex = deriveAddress(account.mnemonic, newDerivPath)[2];
const newDerivPath = nextDerivationPath(account.derivationPath as string);
const nextPublicHex = deriveAddress(
account.mnemonic as string,
newDerivPath,
)[2];
const nextPublicEncKey = Buffer.from(nextPublicHex, "hex");
const nextPublicEncKeyHash = sha256(nextPublicEncKey);
const nextPublicEncKeyHashBase64 =

View File

@@ -311,12 +311,13 @@ import { AppString, NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import { Contact } from "@/db/tables/contacts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken, getContactPayloadFromJwtUrl } from "@/libs/crypto";
import { getContactPayloadFromJwtUrl } from "@/libs/crypto";
import {
CONTACT_CSV_HEADER,
CONTACT_URL_PREFIX,
GiverReceiverInputInfo,
GiveSummaryRecord,
getHeaders,
isDid,
register,
setVisibilityUtil,
@@ -414,22 +415,6 @@ export default class ContactsView extends Vue {
return identity;
}
public async getHeaders(identity: IIdentifier) {
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
return headers;
}
public async getHeadersAndIdentity(activeDid: string) {
const identity = await this.getIdentity(activeDid);
const headers = await this.getHeaders(identity);
return { headers, identity };
}
async loadGives() {
if (!this.activeDid) {
return;
@@ -481,7 +466,7 @@ export default class ContactsView extends Vue {
};
try {
const { headers } = await this.getHeadersAndIdentity(this.activeDid);
const headers = await getHeaders(this.activeDid);
const givenByUrl =
this.apiServer +
"/api/v2/report/gives?agentDid=" +
@@ -954,8 +939,19 @@ export default class ContactsView extends Vue {
this.apiServer +
"/api/report/canDidExplicitlySeeMe?did=" +
encodeURIComponent(contact.did);
const identity = await this.getIdentity(this.activeDid);
const headers = await this.getHeaders(identity);
const headers = await getHeaders(this.activeDid);
if (!headers["Authorization"]) {
this.$notify(
{
group: "alert",
type: "danger",
title: "No Identity",
text: "There is no identity to use to check visibility.",
},
3000,
);
return;
}
try {
const resp = await this.axios.get(url, { headers });

View File

@@ -316,9 +316,9 @@ export default class GiftedDetails extends Vue {
const identity = await libsUtil.getIdentity(this.activeDid);
const project = await getPlanFromCache(
this.projectId,
identity,
this.axios,
this.apiServer,
identity.did,
);
this.projectName = project?.name
? "the project: " + project.name

View File

@@ -338,12 +338,12 @@ import {
MASTER_SETTINGS_KEY,
Settings,
} from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import {
contactForDid,
containsNonHiddenDid,
didInfoForContact,
fetchEndorserRateLimits,
getHeaders,
getPlanFromCache,
GiverReceiverInputInfo,
GiveSummaryRecord,
@@ -407,16 +407,6 @@ export default class HomeView extends Vue {
showShortcutBvc = false;
userAgentInfo = new UAParser(); // see https://docs.uaparser.js.org/v2/api/ua-parser-js/get-os.html
public async getIdentity(activeDid: string): Promise<IIdentifier | null> {
await accountsDB.open();
const account = (await accountsDB.accounts
.where("did")
.equals(activeDid)
.first()) as Account;
const identity = JSON.parse(account?.identity || "null");
return identity; // may be null
}
async mounted() {
try {
await accountsDB.open();
@@ -440,12 +430,11 @@ export default class HomeView extends Vue {
// someone may have have registered after sharing contact info, so recheck
if (!this.isRegistered && this.activeDid) {
const identity = await this.getIdentity(this.activeDid);
try {
const resp = await fetchEndorserRateLimits(
this.apiServer,
this.axios,
identity as IIdentifier,
this.activeDid,
);
if (resp.status === 200) {
// we just needed to know that they're registered
@@ -497,26 +486,6 @@ export default class HomeView extends Vue {
return "Notification" in window;
}
async buildHeaders() {
const headers: HeadersInit = {
"Content-Type": "application/json",
};
const identity = await this.getIdentity(this.activeDid);
if (this.activeDid) {
if (identity) {
headers["Authorization"] = "Bearer " + (await accessToken(identity));
} else {
throw new Error(
"An ID is chosen but there are no keys for it so it cannot be used to talk with the service. Switch your ID.",
);
}
} else {
// it's OK without auth... we just won't get any identifiers
}
return headers;
}
// only called when a setting was changed
async reloadFeedOnChange() {
await db.open();
@@ -564,7 +533,6 @@ export default class HomeView extends Vue {
if (results.data.length > 0) {
endOfResults = false;
// include the descriptions of the giver and receiver
const identity = await this.getIdentity(this.activeDid);
for (const record: GiveSummaryRecord of results.data) {
// similar code is in endorser-mobile utility.ts
// claim.claim happen for some claims wrapped in a Verifiable Credential
@@ -581,9 +549,9 @@ export default class HomeView extends Vue {
// We should display it immediately and then get the plan later.
const plan = await getPlanFromCache(
record.fulfillsPlanHandleId,
identity,
this.axios,
this.apiServer,
this.activeDid,
);
// check if the record should be filtered out
@@ -672,7 +640,7 @@ export default class HomeView extends Vue {
beforeQuery,
{
method: "GET",
headers: await this.buildHeaders(),
headers: await getHeaders(this.activeDid),
},
);

View File

@@ -245,15 +245,6 @@ export default class NewEditProjectView extends Vue {
return identity;
}
public async getHeaders(identity: IIdentifier) {
const token = await accessToken(identity);
const headers = {
"Content-Type": "application/json",
Authorization: "Bearer " + token,
};
return headers;
}
async mounted() {
await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count();
@@ -460,11 +451,14 @@ export default class NewEditProjectView extends Vue {
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,
});
const vcJwt: string = await didJwt.createJWT(
vcPayload as Partial<didJwt.JWTPayload>,
{
alg: alg,
issuer: identity.did,
signer: signer,
},
);
// Make the xhr request payload

View File

@@ -247,15 +247,13 @@ import QuickNav from "@/components/QuickNav.vue";
import { AppString, NotificationIface } from "@/constants/app";
import { accountsDB, db } from "@/db/index";
import {
createPeerDid,
PeerSetup,
registerCredential,
verifyJwtP256,
verifyJwtSimplewebauthn,
verifyJwtWebCrypto,
} from "@/libs/didPeer";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
import {registerAndSavePasskey} from "@/libs/util";
import { registerAndSavePasskey } from "@/libs/util";
const inputFileNameRef = ref<Blob>();