case 4678 - passkey sign-in and management

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-01 18:46:27 -07:00
parent 2240a3b61f
commit 3cde5bb54a
5 changed files with 294 additions and 0 deletions

View File

@@ -126,6 +126,16 @@
>
<v-icon>$sockiSignIn</v-icon>
</v-btn>
<!-- case 4678: passkey sign-in, offered where the browser and page allow it -->
<v-btn
v-if="passkeySupported"
class="ml-4"
aria-label="Passkey"
data-cy="passkeybutton"
@click="passkeyLogin()"
>
<v-icon>$sockiFingerprint</v-icon>
</v-btn>
</v-col>
</v-row>
</form>
@@ -183,6 +193,7 @@
</template>
<script>
import { processLogin, processLogout } from "../api/authutil";
import passkey from "../api/passkey";
import sockeyeVersion from "../api/sockeye-version";
export default {
data() {
@@ -201,6 +212,7 @@ export default {
tt: null,
pinError: null,
loggedInWithKnownPassword: false,
passkeySupported: passkey.supported(),
hasSmallLogo: false,
hasMediumLogo: false,
hasLargeLogo: false,
@@ -380,6 +392,45 @@ export default {
//move focus to password
document.getElementsByName("password")[0].focus();
},
//case 4678
async passkeyLogin() {
const vm = this;
vm.errorBadCreds = false;
vm.formState.errorBoxMessage = null;
try {
let res = await window.$gz.api.upsert(
"auth/passkey/login-options",
{},
true
);
if (res.error) {
throw new Error(window.$gz.errorHandler.errorToString(res, vm));
}
const answer = await passkey.get(res.data.options);
res = await window.$gz.api.upsert(
"auth/passkey/login",
{ id: res.data.id, response: answer },
true
);
if (res.error) {
throw new Error(window.$gz.errorHandler.errorToString(res, vm));
}
await this.step2(res);
} catch (error) {
//the user closed the browser's passkey prompt
if (error.name == "NotAllowedError" || error.name == "AbortError") {
return;
}
if (
error.message &&
error.message.includes("ErrorUserNotAuthenticated")
) {
vm.errorBadCreds = true;
return;
}
vm.formState.errorBoxMessage = error.message;
}
},
async login() {
const vm = this;