-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat: add initial guardian recovery views * feat: add confirm guardian view * feat: improve guardian view with edge cases * fix: remove commented code * feat: improve component imports * feat: add logout icon in desktop breakpoint * fix: naming * fix: pnpm lock * fix: wrong nav component import * fix: add missing package to cspell * feat: add recover account views * feat: add unknown account page * feat: improve account init recovery start * feat: reorganize routes with typed routes * feat: add recovery process warning when logged in * feat: add account not ready page * fix: confirm-guardian page * feat: add base guardian recovery module * chore: update contracts submodule * chore: update contracts submodule * chore: update contracts submodule * feat: add sso account validation * Update packages/auth-server/pages/recovery/guardian/index.vue Co-authored-by: Lukasz Romanowski <[email protected]> * feat: add integration with /recovery/guardian/find-account * feat: integrate contracts in guardians settings page * feat: set proper path to confirm-guardian page * feat: add guardian confirmation integration * feat: add ui improvements * feat: address pr comments * feat: update contracts submodule * chore: update contract submodule * feat: add integration to confirm recovery view * feat: add integration with cancel recovery (#53) * feat: add integration with cancel recovery * feat: update contracts submodule and abi * feat: add verify recovery view on the main page * chore: update contracts * feat: add missing nuxt config * feat: improve confirm guardian flow * chore: update contracts package * feat: execute pending recovery on login (#57) * feat: execute pending recovery on login * feat: move recovery client to sdk * feat: add account-not-ready view * chore: fix pnpm lock --------- Co-authored-by: aon <[email protected]> --------- Co-authored-by: aon <[email protected]> Co-authored-by: Lukasz Romanowski <[email protected]>
- Loading branch information
1 parent
ba0fcdf
commit 36dc3c1
Showing
66 changed files
with
7,082 additions
and
632 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,3 +5,4 @@ wagmi | |
cbor | ||
levischuck | ||
ofetch | ||
reown |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
68 changes: 68 additions & 0 deletions
68
packages/auth-server/components/account-recovery/AccountSelect.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
<template> | ||
<div class="relative"> | ||
<select | ||
:id="id" | ||
v-model="selectedValue" | ||
class="w-full px-4 py-3 bg-white dark:bg-neutral-900 border border-neutral-200 dark:border-neutral-800 rounded-zk text-neutral-900 dark:text-neutral-100 appearance-none cursor-pointer focus:outline-none focus:ring-2 focus:ring-primary-500 dark:focus:ring-primary-400 disabled:opacity-50 disabled:cursor-not-allowed truncate pr-8" | ||
:class="{ | ||
'border-error-500 dark:border-error-400': error, | ||
}" | ||
:disabled="disabled || !accounts.length" | ||
> | ||
<option | ||
value="" | ||
disabled | ||
> | ||
{{ accounts.length ? 'Select an account' : 'No accounts found' }} | ||
</option> | ||
<option | ||
v-for="account in accounts" | ||
:key="account" | ||
:value="account" | ||
> | ||
{{ account }} | ||
</option> | ||
</select> | ||
|
||
<div class="absolute inset-y-0 right-0 flex items-center px-4 pointer-events-none"> | ||
<ZkIcon icon="arrow_drop_down" /> | ||
</div> | ||
|
||
<!-- Error messages --> | ||
<div | ||
v-if="error && messages?.length" | ||
class="mt-2 space-y-1" | ||
> | ||
<p | ||
v-for="(message, index) in messages" | ||
:key="index" | ||
class="text-sm text-error-500 dark:text-error-400" | ||
> | ||
{{ message }} | ||
</p> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import type { Address } from "viem"; | ||
import { computed } from "vue"; | ||
const props = defineProps<{ | ||
id?: string; | ||
modelValue: string; | ||
accounts: Address[]; | ||
error?: boolean; | ||
messages?: string[]; | ||
disabled?: boolean; | ||
}>(); | ||
const emit = defineEmits<{ | ||
(e: "update:modelValue", value: string): void; | ||
}>(); | ||
const selectedValue = computed({ | ||
get: () => props.modelValue, | ||
set: (value) => emit("update:modelValue", value), | ||
}); | ||
</script> |
110 changes: 110 additions & 0 deletions
110
packages/auth-server/components/account-recovery/AddRecoveryMethodModal.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
<template> | ||
<Dialog | ||
ref="modalRef" | ||
content-class="min-w-[700px] min-h-[500px]" | ||
description-class="flex-1 mb-0 flex text-base" | ||
close-class="h-8 max-h-8" | ||
:title="title" | ||
> | ||
<template #trigger> | ||
<slot name="trigger"> | ||
<Button | ||
class="w-full lg:w-auto" | ||
type="primary" | ||
> | ||
Add Recovery Method | ||
</Button> | ||
</slot> | ||
</template> | ||
|
||
<template #submit> | ||
<div /> | ||
</template> | ||
|
||
<template #cancel> | ||
<div /> | ||
</template> | ||
|
||
<!-- Method Selection Step --> | ||
<div | ||
v-if="currentStep === 'select-method'" | ||
class="space-y-6 text-left flex-1 flex flex-col" | ||
> | ||
<div class="flex flex-col gap-6 items-center flex-1 justify-center max-w-md mx-auto w-full"> | ||
<div class="text-center"> | ||
<p class="text-xl font-medium mb-2"> | ||
Choose a Recovery Method | ||
</p> | ||
<p class="text-base text-gray-600 dark:text-gray-400"> | ||
Select how you'd like to recover your account if you lose access | ||
</p> | ||
</div> | ||
|
||
<div class="flex flex-col gap-5 w-full max-w-xs"> | ||
<Button | ||
class="w-full" | ||
@click="selectMethod('guardian')" | ||
> | ||
Recover with Guardian | ||
</Button> | ||
|
||
<div class="flex w-full flex-col gap-2"> | ||
<Button | ||
disabled | ||
class="w-full" | ||
> | ||
Recover with Email | ||
</Button> | ||
<span class="text-sm text-gray-500 text-center"> | ||
Coming soon... | ||
</span> | ||
</div> | ||
</div> | ||
</div> | ||
</div> | ||
|
||
<GuardianFlow | ||
v-if="currentStep === 'guardian'" | ||
:close-modal="closeModal" | ||
@back="currentStep = 'select-method'" | ||
/> | ||
</Dialog> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import { ref } from "vue"; | ||
import GuardianFlow from "~/components/account-recovery/guardian-flow/Root.vue"; | ||
import Button from "~/components/zk/button.vue"; | ||
import Dialog from "~/components/zk/dialog.vue"; | ||
type Step = "select-method" | "guardian" | "email"; | ||
const currentStep = ref<Step>("select-method"); | ||
const modalRef = ref<InstanceType<typeof Dialog>>(); | ||
const emit = defineEmits<{ | ||
(e: "closed"): void; | ||
}>(); | ||
function closeModal() { | ||
emit("closed"); | ||
modalRef.value?.close(); | ||
} | ||
const title = computed(() => { | ||
switch (currentStep.value) { | ||
case "select-method": | ||
return "Add Recovery Method"; | ||
case "guardian": | ||
return "Guardian Recovery Setup"; | ||
case "email": | ||
return "Email Recovery Setup"; | ||
default: | ||
throw new Error("Invalid step"); | ||
} | ||
}); | ||
function selectMethod(method: "guardian" | "email") { | ||
currentStep.value = method; | ||
} | ||
</script> |
124 changes: 124 additions & 0 deletions
124
packages/auth-server/components/account-recovery/PasskeyGenerationFlow.vue
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
<template> | ||
<div class="w-full max-w-md flex flex-col gap-6"> | ||
<!-- Generate Passkeys Step --> | ||
<div | ||
v-if="currentStep === generatePasskeysStep" | ||
class="w-full max-w-md flex flex-col gap-6" | ||
> | ||
<p class="text-center text-neutral-700 dark:text-neutral-300"> | ||
Generate new passkeys to secure your account | ||
</p> | ||
|
||
<ZkButton | ||
class="w-full" | ||
:loading="registerInProgress" | ||
@click="handleGeneratePasskeys" | ||
> | ||
Generate Passkeys | ||
</ZkButton> | ||
|
||
<ZkButton | ||
type="secondary" | ||
class="w-full" | ||
@click="$emit('back')" | ||
> | ||
Back | ||
</ZkButton> | ||
</div> | ||
|
||
<!-- Confirmation Step --> | ||
<div | ||
v-if="currentStep === confirmationStep" | ||
class="w-full max-w-md flex flex-col gap-6" | ||
> | ||
<div class="flex flex-col gap-4 text-center text-neutral-700 dark:text-neutral-300"> | ||
<p> | ||
Your passkeys have been generated successfully. | ||
</p> | ||
<p> | ||
Please share the following url with your guardian to complete the recovery process: | ||
</p> | ||
</div> | ||
|
||
<div class="w-full items-center gap-2 p-4 bg-neutral-100 dark:bg-neutral-900 rounded-zk"> | ||
<a | ||
:href="recoveryUrl" | ||
target="_blank" | ||
class="text-sm text-neutral-800 dark:text-neutral-100 break-all hover:text-neutral-900 dark:hover:text-neutral-400 leading-relaxed underline underline-offset-4 decoration-neutral-400 hover:decoration-neutral-900 dark:decoration-neutral-600 dark:hover:decoration-neutral-400" | ||
> | ||
{{ recoveryUrl }} | ||
</a> | ||
<common-copy-to-clipboard | ||
:text="recoveryUrl ?? ''" | ||
class="!inline-flex ml-1" | ||
/> | ||
</div> | ||
|
||
<p class="text-sm text-center text-neutral-600 dark:text-neutral-400"> | ||
You'll be able to access your account once your guardian confirms the recovery. | ||
</p> | ||
|
||
<ZkLink | ||
type="primary" | ||
href="/" | ||
class="w-full" | ||
> | ||
Back to Home | ||
</ZkLink> | ||
</div> | ||
</div> | ||
</template> | ||
|
||
<script setup lang="ts"> | ||
import type { RegisterNewPasskeyReturnType } from "zksync-sso/client/passkey"; | ||
const props = defineProps<{ | ||
currentStep: number; | ||
generatePasskeysStep: number; | ||
confirmationStep: number; | ||
address: string; | ||
newPasskey: RegisterNewPasskeyReturnType | null; | ||
registerInProgress: boolean; | ||
}>(); | ||
const emit = defineEmits<{ | ||
(e: "back"): void; | ||
(e: "update:newPasskey", value: RegisterNewPasskeyReturnType): void; | ||
(e: "update:currentStep", value: number): void; | ||
}>(); | ||
const runtimeConfig = useRuntimeConfig(); | ||
const appUrl = runtimeConfig.public.appUrl; | ||
const { registerPasskey } = usePasskeyRegister(); | ||
const recoveryUrl = computedAsync(async () => { | ||
const queryParams = new URLSearchParams(); | ||
const credentialId = props.newPasskey?.credentialId ?? ""; | ||
const credentialPublicKey = uint8ArrayToHex(props.newPasskey?.credentialPublicKey ?? new Uint8Array()) ?? ""; | ||
queryParams.set("credentialId", credentialId); | ||
queryParams.set("credentialPublicKey", credentialPublicKey); | ||
queryParams.set("accountAddress", props.address); | ||
// Create checksum from concatenated credential data | ||
const dataToHash = `${props.address}:${credentialId}:${credentialPublicKey}`; | ||
const fullHash = new Uint8Array(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(dataToHash))); | ||
const shortHash = fullHash.slice(0, 8); // Take first 8 bytes of the hash | ||
const checksum = uint8ArrayToHex(shortHash); | ||
queryParams.set("checksum", checksum); | ||
return `${appUrl}/recovery/guardian/confirm-recovery?${queryParams.toString()}`; | ||
}); | ||
const handleGeneratePasskeys = async () => { | ||
const result = await registerPasskey(); | ||
if (!result) { | ||
throw new Error("Failed to register passkey"); | ||
} | ||
emit("update:newPasskey", result); | ||
emit("update:currentStep", props.confirmationStep); | ||
}; | ||
</script> |
Oops, something went wrong.