Browse Source

Merge branch 'master' into notification-request-permission-dialog

kb/add-usage-guide
anomalist 1 year ago
parent
commit
0af05b4b0d
  1. 3
      README.md
  2. 20
      project.task.yaml
  3. 4
      src/db/index.ts
  4. 8
      src/libs/crypto/index.ts
  5. 8
      src/router/index.ts
  6. 35
      src/views/ImportAccountView.vue
  7. 163
      src/views/ImportDerivedAccountView.vue
  8. 10
      src/views/SeedBackupView.vue
  9. 28
      src/views/StartView.vue

3
README.md

@ -11,6 +11,9 @@ npm run serve
``` ```
### Compiles and minifies for production ### Compiles and minifies for production
If you are deploying in a subdirectory, add it to `publicPath` in vue.config.js, eg: `publicPath: "/app/time-tracker/",`
``` ```
npm run build npm run build
``` ```

20
project.task.yaml

@ -15,9 +15,9 @@ tasks:
- 08 Scan QR code to import into contacts assignee:matthew - 08 Scan QR code to import into contacts assignee:matthew
- SEE: https://github.com/gruhn/vue-qrcode-reader - SEE: https://github.com/gruhn/vue-qrcode-reader
- Show pop-up or some message confirming that settings & contacts download has been initiated/finished assignee:matthew - 01 Show pop-up or some message confirming that settings & contacts download has been initiated/finished assignee:matthew
- Ensure each action sent to the server has a confirmation - eg registration (ie a toast something that dismisses after 5-10s) - 01 Ensure each action sent to the server has a confirmation - eg registration (ie a toast something that dismisses after 5-10s)
- SEE: https://github.com/emmanuelsw/notiwind assignee:jose - SEE: https://github.com/emmanuelsw/notiwind assignee:jose
- Home Feed & Quick Give screen : - Home Feed & Quick Give screen :
@ -27,6 +27,7 @@ tasks:
- 24 Move to Vite assignee:matthew - 24 Move to Vite assignee:matthew
- .5 include the hash of the latest commit, and maybe a version
- .5 add link to further project / people when a project pays ahead - .5 add link to further project / people when a project pays ahead
- .5 add project ID to the URL, to make a project publicly-accessible - .5 add project ID to the URL, to make a project publicly-accessible
- .5 remove edit from project page for projects owned by others - .5 remove edit from project page for projects owned by others
@ -36,9 +37,18 @@ tasks:
- .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent - .2 fix rate limit verbiage (with the new one-per-day allowance) assignee:trent
- .2 move 'switch identity' to the advanced section - .2 move 'switch identity' to the advanced section
- .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164 - .1 remove the logic to exclude beforeId in list of plans after server has commit 26b25af605e715600d4f12b6416ed9fd7142d164
- .2 in SeedBackupView, don't load the mnemonic and keep it in memory; only load it when they click "show"
- Discuss whether the remaining tasks are worthwhile before MVP release. - Discuss whether the remaining tasks are worthwhile before MVP release.
- 04 allow user to download claims, mine + ones I can see about me from others
- .5 change the derivation path, and regenerate test IDs
- 02 allow user to create new DIDs from the same seed phrase (ie. increment derivation path)
- .5 on ProjectView page, show immediate feedback when a gift is given (on list?) -- and consider the same for Home & Contacts pages
- .5 customize favicon
- .5 Do we want to combine first name & last name?
- .2 Show a warning if both giver and recipient are the same (but still allow?)
- contacts v+ : - contacts v+ :
- 01 Import all the non-sensitive data (ie. contacts & settings). - 01 Import all the non-sensitive data (ie. contacts & settings).
- .2 show error to user when adding a duplicate contact - .2 show error to user when adding a duplicate contact
@ -51,12 +61,6 @@ tasks:
- maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version") - maybe - allow type annotations in World.js & landmarks.js (since we get this error - "Types are not supported by current JavaScript version")
- 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie) - 08 convert to cleaner implementation (maybe Drie -- https://github.com/janvorisek/drie)
- .5 on ProjectView page, show immediate feedback when a gift is given (on list?) -- and consider the same for Home & Contacts pages
- .5 customize favicon
- 04 allow user to download claims, mine + ones I can see about me from others
- Do we want to combine first name & last name?
- Show a warning if both giver and recipient are the same (but still allow?)
- Release Minimum Viable Product : - Release Minimum Viable Product :
- 08 thorough testing for errors & edge cases - 08 thorough testing for errors & edge cases
- Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot). - Turn off stats-world or ensure it's usable (eg. cannot zoom out too far and lose world, cannot screenshot).

4
src/db/index.ts

@ -28,12 +28,12 @@ type NonsensitiveTables = {
* https://9to5answer.com/how-to-bypass-warning-unexpected-any-specify-a-different-type-typescript-eslint-no-explicit-any * https://9to5answer.com/how-to-bypass-warning-unexpected-any-specify-a-different-type-typescript-eslint-no-explicit-any
*/ */
export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T; export type SensitiveDexie<T extends unknown = SensitiveTables> = BaseDexie & T;
export const accountsDB = new BaseDexie("KickStartAccounts") as SensitiveDexie; export const accountsDB = new BaseDexie("TimeSafariAccounts") as SensitiveDexie;
const SensitiveSchemas = Object.assign({}, AccountsSchema); const SensitiveSchemas = Object.assign({}, AccountsSchema);
export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> = export type NonsensitiveDexie<T extends unknown = NonsensitiveTables> =
BaseDexie & T; BaseDexie & T;
export const db = new BaseDexie("KickStart") as NonsensitiveDexie; export const db = new BaseDexie("TimeSafari") as NonsensitiveDexie;
const NonsensitiveSchemas = Object.assign({}, ContactsSchema, SettingsSchema); const NonsensitiveSchemas = Object.assign({}, ContactsSchema, SettingsSchema);
/** /**

8
src/libs/crypto/index.ts

@ -7,6 +7,8 @@ import { HDNode } from "@ethersproject/hdnode";
import * as didJwt from "did-jwt"; import * as didJwt from "did-jwt";
import * as u8a from "uint8arrays"; import * as u8a from "uint8arrays";
export const DEFAULT_ROOT_DERIVATION_PATH = "m/76798669'/0'/0'/0'";
/** /**
* *
* *
@ -47,17 +49,17 @@ export const newIdentifier = (
*/ */
export const deriveAddress = ( export const deriveAddress = (
mnemonic: string, mnemonic: string,
derivationPath: string = DEFAULT_ROOT_DERIVATION_PATH,
): [string, string, string, string] => { ): [string, string, string, string] => {
const UPORT_ROOT_DERIVATION_PATH = "m/7696500'/0'/0'/0'";
mnemonic = mnemonic.trim().toLowerCase(); mnemonic = mnemonic.trim().toLowerCase();
const hdnode: HDNode = HDNode.fromMnemonic(mnemonic); const hdnode: HDNode = HDNode.fromMnemonic(mnemonic);
const rootNode: HDNode = hdnode.derivePath(UPORT_ROOT_DERIVATION_PATH); const rootNode: HDNode = hdnode.derivePath(derivationPath);
const privateHex = rootNode.privateKey.substring(2); // original starts with '0x' const privateHex = rootNode.privateKey.substring(2); // original starts with '0x'
const publicHex = rootNode.publicKey.substring(2); // original starts with '0x' const publicHex = rootNode.publicKey.substring(2); // original starts with '0x'
const address = rootNode.address; const address = rootNode.address;
return [address, privateHex, publicHex, UPORT_ROOT_DERIVATION_PATH]; return [address, privateHex, publicHex, derivationPath];
}; };
/** /**

8
src/router/index.ts

@ -91,6 +91,14 @@ const routes: Array<RouteRecordRaw> = [
/* webpackChunkName: "import-account" */ "../views/ImportAccountView.vue" /* webpackChunkName: "import-account" */ "../views/ImportAccountView.vue"
), ),
}, },
{
path: "/import-derive",
name: "import-derive",
component: () =>
import(
/* webpackChunkName: "import-derive" */ "../views/ImportDerivedAccountView.vue"
),
},
{ {
path: "/new-edit-account", path: "/new-edit-account",
name: "new-edit-account", name: "new-edit-account",

35
src/views/ImportAccountView.vue

@ -24,6 +24,24 @@
v-model="mnemonic" v-model="mnemonic"
/> />
{{ mnemonic }} {{ mnemonic }}
<h3
class="text-sm uppercase font-semibold mb-3"
@click="showAdvanced = !showAdvanced"
>
Advanced
</h3>
<div v-if="showAdvanced">
Enter a custom derivation path
<input
type="text"
class="block w-full rounded border border-slate-400 mb-4 px-3 py-2"
v-model="derivationPath"
/>
For previous uPort or Endorser users,
<a @click="derivationPath = UPORT_DERIVATION_PATH" class="text-blue-500">
click here to use that value.
</a>
</div>
<div class="mt-8"> <div class="mt-8">
<button <button
@click="from_mnemonic()" @click="from_mnemonic()"
@ -44,7 +62,11 @@
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { deriveAddress, newIdentifier } from "../libs/crypto"; import {
DEFAULT_ROOT_DERIVATION_PATH,
deriveAddress,
newIdentifier,
} from "../libs/crypto";
import { accountsDB, db } from "@/db"; import { accountsDB, db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings"; import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@ -52,11 +74,14 @@ import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
components: {}, components: {},
}) })
export default class ImportAccountView extends Vue { export default class ImportAccountView extends Vue {
UPORT_DERIVATION_PATH = "m/7696500'/0'/0'/0'";
mnemonic = ""; mnemonic = "";
address = ""; address = "";
privateHex = ""; privateHex = "";
publicHex = ""; publicHex = "";
derivationPath = ""; derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
showAdvanced = false;
public onCancelClick() { public onCancelClick() {
this.$router.back(); this.$router.back();
@ -65,8 +90,10 @@ export default class ImportAccountView extends Vue {
public async from_mnemonic() { public async from_mnemonic() {
const mne: string = this.mnemonic.trim().toLowerCase(); const mne: string = this.mnemonic.trim().toLowerCase();
if (this.mnemonic.trim().length > 0) { if (this.mnemonic.trim().length > 0) {
[this.address, this.privateHex, this.publicHex, this.derivationPath] = [this.address, this.privateHex, this.publicHex] = deriveAddress(
deriveAddress(mne); mne,
this.derivationPath,
);
const newId = newIdentifier( const newId = newIdentifier(
this.address, this.address,

163
src/views/ImportDerivedAccountView.vue

@ -0,0 +1,163 @@
<template>
<section id="Content" class="p-6 pb-24">
<!-- Breadcrumb -->
<div id="ViewBreadcrumb" class="mb-8">
<h1 class="text-lg text-center font-light relative px-7">
<!-- Cancel -->
<button
@click="$router.go(-1)"
class="text-lg text-center px-2 py-1 absolute -left-2 -top-1"
>
<fa icon="chevron-left"></fa>
</button>
Derive from Existing Identity
</h1>
</div>
<!-- Import Account Form -->
<div>
<p class="text-center text-xl mb-4 font-light">
Will increment the maximum derivation path from the existing seed.
</p>
<p v-if="didArrays.length > 1">
Choose existing DIDs from same seed phrase to compute derivation.
</p>
<ul class="mb-4">
<li
class="block bg-slate-100 rounded-md flex items-center px-4 py-3 mb-2"
v-for="dids in didArrays"
:key="dids[0]"
@click="switchAccount(dids[0])"
>
<fa
v-if="dids[0] == selectedArrayFirstDid"
icon="circle"
class="fa-fw text-blue-400 text-xl mr-3"
></fa>
<fa
v-else
icon="circle"
class="fa-fw text-slate-400 text-xl mr-3"
></fa>
<span class="overflow-hidden">
<div class="text-sm text-slate-500 truncate">
<code>{{ dids.join(",") }}</code>
</div>
</span>
</li>
</ul>
</div>
<div class="mt-8">
<button
@click="incrementDerivation()"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2"
>
Increment and Import
</button>
<button
@click="onCancelClick()"
type="button"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
>
Cancel
</button>
</div>
</section>
</template>
<script lang="ts">
import { Component, Vue } from "vue-facing-decorator";
import {
DEFAULT_ROOT_DERIVATION_PATH,
deriveAddress,
newIdentifier,
} from "../libs/crypto";
import { accountsDB, db } from "@/db";
import { MASTER_SETTINGS_KEY } from "@/db/tables/settings";
@Component({
components: {},
})
export default class ImportAccountView extends Vue {
derivationPath = DEFAULT_ROOT_DERIVATION_PATH;
didArrays: Array<Array<string>> = [];
selectedArrayFirstDid = "";
async mounted() {
await accountsDB.open();
const accounts = await accountsDB.accounts.toArray();
const seedDids = {};
accounts.forEach((account) => {
const prevDids = seedDids[account.mnemonic] || [];
seedDids[account.mnemonic] = prevDids.concat([account.did]);
});
this.didArrays = Object.values(seedDids);
this.selectedArrayFirstDid = this.didArrays[0][0];
}
public onCancelClick() {
this.$router.back();
}
public switchAccount(did: string) {
this.selectedArrayFirstDid = did;
}
public async incrementDerivation() {
await accountsDB.open();
// find the maximum derivation path for the selected DIDs
const selectedArray: Array<string> = this.didArrays.find(
(dids) => dids[0] === this.selectedArrayFirstDid,
);
const allMatchingAccounts = await accountsDB.accounts
.where("did")
.anyOf(...selectedArray)
.toArray();
const accountWithMaxDeriv = allMatchingAccounts[0];
allMatchingAccounts.slice(1).forEach((account) => {
if (account.derivationPath > accountWithMaxDeriv.derivationPath) {
accountWithMaxDeriv.derivationPath = account.derivationPath;
}
});
// increment the last number in that max derivation path
let lastStr = accountWithMaxDeriv.derivationPath.split("/").slice(-1)[0];
if (lastStr.endsWith("'")) {
lastStr = lastStr.slice(0, -1);
}
const lastNum = parseInt(lastStr, 10);
const newLastNum = lastNum + 1;
const newDerivPath = accountWithMaxDeriv.derivationPath
.split("/")
.slice(0, -1)
.concat([newLastNum.toString() + "'"])
.join("/");
const mne: string = accountWithMaxDeriv.mnemonic;
const [address, privateHex, publicHex] = deriveAddress(mne, newDerivPath);
const newId = newIdentifier(address, publicHex, privateHex, newDerivPath);
try {
await accountsDB.accounts.add({
dateCreated: new Date().toISOString(),
derivationPath: newDerivPath,
did: newId.did,
identity: JSON.stringify(newId),
mnemonic: mne,
publicKeyHex: newId.keys[0].publicKeyHex,
});
// record that as the active DID
await db.open();
db.settings.update(MASTER_SETTINGS_KEY, {
activeDid: newId.did,
});
this.$router.push({ name: "account" });
} catch (err) {
console.error("Error saving mnemonic & updating settings:", err);
}
}
}
</script>

10
src/views/SeedBackupView.vue

@ -28,6 +28,14 @@
<i>Don't take a screenshot or send it to any online service.</i> <i>Don't take a screenshot or send it to any online service.</i>
</p> </p>
<p v-if="numAccounts > 1">
<b class="text-orange-600">Note:</b> You have more than one identity
stored in this browser. If they are all based on the same seed as the
current identity, this one backup is sufficient; however, if you have
different seeds for other identities, you will have to back them up
separately.
</p>
<div class="bg-slate-100 rounded-md overflow-hidden p-4 mb-4"> <div class="bg-slate-100 rounded-md overflow-hidden p-4 mb-4">
<button <button
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md"
@ -60,6 +68,7 @@ import QuickNav from "@/components/QuickNav";
@Component({ components: { AlertMessage, QuickNav } }) @Component({ components: { AlertMessage, QuickNav } })
export default class SeedBackupView extends Vue { export default class SeedBackupView extends Vue {
activeAccount = null; activeAccount = null;
numAccounts = 0;
showSeed = false; showSeed = false;
alertMessage = ""; alertMessage = "";
alertTitle = ""; alertTitle = "";
@ -73,6 +82,7 @@ export default class SeedBackupView extends Vue {
await accountsDB.open(); await accountsDB.open();
const accounts = await accountsDB.accounts.toArray(); const accounts = await accountsDB.accounts.toArray();
this.numAccounts = accounts.length;
this.activeAccount = R.find((acc) => acc.did === activeDid, accounts); this.activeAccount = R.find((acc) => acc.did === activeDid, accounts);
} catch (err) { } catch (err) {
console.error("Got an error loading an identity:", err); console.error("Got an error loading an identity:", err);

28
src/views/StartView.vue

@ -10,30 +10,46 @@
<div class="mt-8"> <div class="mt-8">
<p class="text-center text-xl mb-4 font-light"> <p class="text-center text-xl mb-4 font-light">
Do you already have an identity to import? Do you have an identity to import?
</p> </p>
<a <a
@click="onClickYes()" @click="onClickYes()"
class="block w-full text-center text-lg font-bold uppercase bg-blue-600 text-white px-2 py-3 rounded-md mb-2" class="block w-full text-center text-lg uppercase bg-blue-600 text-white px-2 py-3 rounded-md"
> >
No No
</a> </a>
<a <a
@click="onClickNo()" @click="onClickNo()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md" class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mt-2"
>Yes</a
> >
Yes
</a>
<a
v-if="numAccounts > 0"
@click="onClickDerive()"
class="block w-full text-center text-md uppercase bg-slate-500 text-white px-1.5 py-2 rounded-md mt-2"
>
Derive from Existing Account
</a>
</div> </div>
</section> </section>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Component, Vue } from "vue-facing-decorator"; import { Component, Vue } from "vue-facing-decorator";
import { accountsDB } from "@/db";
@Component({ @Component({
components: {}, components: {},
}) })
export default class StartView extends Vue { export default class StartView extends Vue {
numAccounts = 0;
async mounted() {
await accountsDB.open();
this.numAccounts = await accountsDB.accounts.count();
}
public onClickYes() { public onClickYes() {
this.$router.push({ name: "new-identifier" }); this.$router.push({ name: "new-identifier" });
} }
@ -41,5 +57,9 @@ export default class StartView extends Vue {
public onClickNo() { public onClickNo() {
this.$router.push({ name: "import-account" }); this.$router.push({ name: "import-account" });
} }
public onClickDerive() {
this.$router.push({ name: "import-derive" });
}
} }
</script> </script>

Loading…
Cancel
Save