cache the passkey JWANT access token for multiple signatures

This commit is contained in:
2024-07-19 12:44:54 -06:00
parent cd0a31e6f5
commit b2ebc2992b
11 changed files with 196 additions and 219 deletions

View File

@@ -152,7 +152,7 @@
<div class="text-blue-500 text-sm font-bold">
<router-link :to="{ path: '/did/' + encodeURIComponent(activeDid) }">
Activity
Your Activity
</router-link>
</div>
</div>
@@ -216,7 +216,6 @@
<div class="mb-2 font-bold">Location</div>
<router-link
:to="{ name: 'search-area' }"
v-if="activeDid"
class="block w-full text-center text-m bg-gradient-to-b from-slate-400 to-slate-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-1.5 py-2 rounded-md mb-2 mt-6"
>
Set Search Area
@@ -622,6 +621,26 @@
</button>
</div>
<div class="flex justify-between">
<span>
<span class="text-slate-500 text-sm font-bold mb-2">
Passkey Expiration Minutes
</span>
<br />
<span class="text-sm ml-2">
{{ passkeyExpirationDescription }}
</span>
</span>
<div class="relative ml-2">
<input
type="number"
class="border border-slate-400 rounded px-2 py-2 text-center w-20"
v-model="passkeyExpirationMinutes"
@change="updatePasskeyExpiration"
/>
</div>
</div>
<label
for="toggleShowGeneralAdvanced"
class="flex items-center justify-between cursor-pointer mt-4"
@@ -667,7 +686,7 @@ import ImageMethodDialog from "@/components/ImageMethodDialog.vue";
import QuickNav from "@/components/QuickNav.vue";
import TopMessage from "@/components/TopMessage.vue";
import {
AppString,
AppString, DEFAULT_ENDORSER_API_SERVER,
DEFAULT_IMAGE_API_SERVER,
DEFAULT_PUSH_SERVER,
IMAGE_TYPE_PROFILE,
@@ -675,14 +694,20 @@ import {
} from "@/constants/app";
import { db, accountsDB } from "@/db/index";
import { Account } from "@/db/tables/accounts";
import { MASTER_SETTINGS_KEY, Settings } from "@/db/tables/settings";
import { accessToken } from "@/libs/crypto";
import {
DEFAULT_PASSKEY_EXPIRATION_MINUTES,
MASTER_SETTINGS_KEY,
Settings,
} from "@/db/tables/settings";
import {
clearPasskeyToken,
ErrorResponse,
EndorserRateLimits,
ImageRateLimits,
fetchEndorserRateLimits,
fetchImageRateLimits,
getHeaders,
ImageRateLimits,
tokenExpiryTimeDescription,
} from "@/libs/endorserServer";
import { getAccount } from "@/libs/util";
@@ -713,6 +738,9 @@ export default class AccountViewView extends Vue {
limitsMessage = "";
loadingLimits = false;
notificationMaybeChanged = false;
passkeyExpirationDescription = "";
passkeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
previousPasskeyExpirationMinutes = DEFAULT_PASSKEY_EXPIRATION_MINUTES;
profileImageUrl?: string;
publicHex = "";
publicBase64 = "";
@@ -745,12 +773,32 @@ export default class AccountViewView extends Vue {
await this.initializeState();
await this.processIdentity();
this.passkeyExpirationDescription = tokenExpiryTimeDescription();
/**
* Beware! I've seen where this "ready" never resolves.
*/
const registration = await navigator.serviceWorker.ready;
this.subscription = await registration.pushManager.getSubscription();
this.isSubscribed = !!this.subscription;
console.log("Got to the end of 'mounted' call.");
/**
* Beware! I've seen where we never get to this point because "ready" never resolves.
*/
} catch (error) {
console.error("Mount error:", error);
this.handleError(error);
console.error(
"Telling user to clear cache at page create because:",
error,
);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Loading Account",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
}
}
@@ -780,6 +828,10 @@ export default class AccountViewView extends Vue {
this.showContactGives = !!settings?.showContactGivesInline;
this.hideRegisterPromptOnNewContact =
!!settings?.hideRegisterPromptOnNewContact;
this.passkeyExpirationMinutes =
(settings?.passkeyExpirationMinutes as number) ??
DEFAULT_PASSKEY_EXPIRATION_MINUTES;
this.previousPasskeyExpirationMinutes = this.passkeyExpirationMinutes;
this.showGeneralAdvanced = !!settings?.showGeneralAdvanced;
this.showShortcutBvc = !!settings?.showShortcutBvc;
this.warnIfProdServer = !!settings?.warnIfProdServer;
@@ -835,11 +887,11 @@ 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(this.activeDid);
await this.checkLimitsFor(this.activeDid);
} else if (account?.publicKeyHex) {
this.publicHex = account.publicKeyHex as string;
this.publicBase64 = Buffer.from(this.publicHex, "hex").toString("base64");
this.checkLimitsFor(this.activeDid);
await this.checkLimitsFor(this.activeDid);
}
}
@@ -868,75 +920,18 @@ export default class AccountViewView extends Vue {
this.notificationMaybeChanged = true;
}
/**
* Handles errors and updates the component's state accordingly.
* @param {Error} err - The error object.
*/
handleError(err: unknown) {
if (
err instanceof Error &&
err.message ===
"Attempted to load account records with no identifier available."
) {
this.limitsMessage = "No identifier.";
} else {
console.error("Telling user to clear cache at page create because:", err);
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Loading Account",
text: "Clear your cache and start over (after data backup).",
},
-1,
);
}
}
public async updateShowContactAmounts() {
try {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
showContactGivesInline: this.showContactGives,
});
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating Contact Setting",
text: "The setting may not have saved. Try again, maybe after restarting the app.",
},
-1,
);
console.error(
"Telling user to try again after contact-amounts setting update because:",
err,
);
}
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
showContactGivesInline: this.showContactGives,
});
}
public async updateShowGeneralAdvanced() {
try {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
showGeneralAdvanced: this.showGeneralAdvanced,
});
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating Advanced Setting",
text: "The setting may not have saved. Try again, maybe after restarting the app.",
},
-1,
);
console.error(
"Telling user to try again after general-advanced setting update because:",
err,
);
}
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
showGeneralAdvanced: this.showGeneralAdvanced,
});
}
public async updateWarnIfProdServer(newSetting: boolean) {
@@ -963,71 +958,35 @@ export default class AccountViewView extends Vue {
}
public async updateWarnIfTestServer(newSetting: boolean) {
try {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
warnIfTestServer: newSetting,
});
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating Test Warning",
text: "The setting may not have saved. Try again, maybe after restarting the app.",
},
-1,
);
console.error(
"Telling user to try again after test-server-warning setting update because:",
err,
);
}
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
warnIfTestServer: newSetting,
});
}
public async toggleHideRegisterPromptOnNewContact() {
const newSetting = !this.hideRegisterPromptOnNewContact;
try {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
hideRegisterPromptOnNewContact: newSetting,
});
this.hideRegisterPromptOnNewContact = newSetting;
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating Setting",
text: "The setting may not have saved. Try again, maybe after restarting the app.",
},
-1,
);
console.error("Telling user to try again because:", err);
}
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
hideRegisterPromptOnNewContact: newSetting,
});
this.hideRegisterPromptOnNewContact = newSetting;
}
public async updatePasskeyExpiration() {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
passkeyExpirationMinutes: this.passkeyExpirationMinutes,
});
clearPasskeyToken();
this.passkeyExpirationDescription = tokenExpiryTimeDescription();
}
public async updateShowShortcutBvc(newSetting: boolean) {
try {
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
showShortcutBvc: newSetting,
});
} catch (err) {
this.$notify(
{
group: "alert",
type: "danger",
title: "Error Updating BVC Shortcut Setting",
text: "The setting may not have saved. Try again, maybe after restarting the app.",
},
-1,
);
console.error(
"Telling user to try again after BVC-shortcut setting update because:",
err,
);
}
await db.open();
await db.settings.update(MASTER_SETTINGS_KEY, {
showShortcutBvc: newSetting,
});
}
/**
@@ -1220,7 +1179,7 @@ export default class AccountViewView extends Vue {
// the user was not known to be registered, but now they are (because we got no error) so let's record it
try {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
await db.settings.update(MASTER_SETTINGS_KEY, {
isRegistered: true,
});
this.isRegistered = true;
@@ -1247,7 +1206,7 @@ export default class AccountViewView extends Vue {
try {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
await db.settings.update(MASTER_SETTINGS_KEY, {
isRegistered: false,
});
this.isRegistered = false;
@@ -1272,8 +1231,8 @@ export default class AccountViewView extends Vue {
(data?.error?.message as string) || "Bad server response.";
console.error(
"Got bad response retrieving limits, which usually means user isn't registered.",
error,
);
//console.error(error);
} else {
this.limitsMessage = "Got an error retrieving limits.";
console.error("Got some error retrieving limits:", error);
@@ -1350,7 +1309,7 @@ export default class AccountViewView extends Vue {
async onClickSaveApiServer() {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
await db.settings.update(MASTER_SETTINGS_KEY, {
apiServer: this.apiServerInput,
});
this.apiServer = this.apiServerInput;
@@ -1358,7 +1317,7 @@ export default class AccountViewView extends Vue {
async onClickSavePushServer() {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
await db.settings.update(MASTER_SETTINGS_KEY, {
webPushServer: this.webPushServerInput,
});
this.webPushServer = this.webPushServerInput;
@@ -1377,7 +1336,7 @@ export default class AccountViewView extends Vue {
(this.$refs.imageMethodDialog as ImageMethodDialog).open(
async (imgUrl) => {
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
await db.settings.update(MASTER_SETTINGS_KEY, {
profileImageUrl: imgUrl,
});
this.profileImageUrl = imgUrl;
@@ -1407,16 +1366,13 @@ export default class AccountViewView extends Vue {
return;
}
try {
const token = await accessToken(this.activeDid);
const headers = await getHeaders(this.activeDid);
this.passkeyExpirationDescription = tokenExpiryTimeDescription();
const response = await this.axios.delete(
DEFAULT_IMAGE_API_SERVER +
"/image/" +
encodeURIComponent(this.profileImageUrl),
{
headers: {
Authorization: `Bearer ${token}`,
},
},
{ headers },
);
if (response.status === 204) {
// don't bother with a notification
@@ -1436,7 +1392,7 @@ export default class AccountViewView extends Vue {
}
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
await db.settings.update(MASTER_SETTINGS_KEY, {
profileImageUrl: undefined,
});
@@ -1448,7 +1404,7 @@ export default class AccountViewView extends Vue {
console.error("The image was already deleted:", error);
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
await db.settings.update(MASTER_SETTINGS_KEY, {
profileImageUrl: undefined,
});