Files
sonr/pkg/blocks/forms/register_passkey.templ
T

124 lines
4.0 KiB
Templ

package forms
import "github.com/onsonr/sonr/pkg/blocks/cards"
type RegisterPasskeyData struct {
Address string
Handle string
Name string
Challenge string
CreationBlock string
}
templ RegisterPasskey(action, method string, data RegisterPasskeyData) {
<form action={ templ.SafeURL(action) } method={ method } onsubmit="return validateCredential()">
<input type="hidden" name="credential" id="credential-data" required/>
<sl-card class="card-form gap-4 max-w-lg">
<div slot="header">
<div class="w-full py-1">
@cards.SonrProfile(data.Address, data.Name, data.Handle, data.CreationBlock)
</div>
</div>
@passkeyDropzone(data.Address, data.Handle, data.Challenge)
<div slot="footer">
<sl-button type="submit" pill style="width: 100%;" variant="primary">
<sl-icon slot="prefix" name="shield-fill-check"></sl-icon>
Register Vault
<sl-icon slot="suffix" name="arrow-outbound" library="sonr"></sl-icon>
</sl-button>
</div>
<script>
function validateCredential() {
const credentialInput = document.getElementById('credential-data');
if (!credentialInput.value) {
alert('Please create a passkey before submitting');
return false;
}
return true;
}
</script>
<style>
.card-form [slot='footer'] {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>
</sl-card>
</form>
}
templ passkeyDropzone(addr string, userHandle string, challenge string) {
<div class="w-full flex flex-col items-center justify-center gap-4">
<div id="credential-details" class="w-full p-4 border border-neutral-200 rounded-md hidden">
<div class="flex items-center gap-3">
<sl-icon name="fingerprint" library="sonr" style="font-size: 24px;" class="text-primary-500"></sl-icon>
<div class="flex flex-col">
<span class="text-sm font-medium" id="credential-name">Platform Authenticator</span>
<span class="text-xs text-neutral-500" id="credential-id"></span>
</div>
</div>
</div>
<div class="w-full p-4 border-dashed border-2 border-neutral-500 cursor-pointer rounded-md hover:border-neutral-400 transition-colors" onclick={ createPasskey(addr, userHandle, challenge) }>
<div class="flex flex-col items-center gap-2">
<sl-icon slot="prefix" name="passkey" library="sonr" style="font-size: 24px;" class="text-neutral-500"></sl-icon>
<span class="text-neutral-500">Link a passkey to your vault</span>
</div>
</div>
</div>
}
script createPasskey(userId string, userHandle string, challenge string) {
const publicKey = {
challenge: Uint8Array.from(challenge, (c) => c.charCodeAt(0)),
rp: {
name: "Sonr.ID",
},
user: {
// Assuming that userId is ASCII-only
id: Uint8Array.from(userId, (c) => c.charCodeAt(0)),
name: userId,
displayName: userHandle,
},
pubKeyCredParams: [
{
type: "public-key",
alg: -7, // "ES256"
},
{
type: "public-key",
alg: -257, // "RS256"
},
],
authenticatorSelection: {
userVerification: "required",
residentKey: "required",
authenticatorAttachment: "platform",
},
timeout: 60000, // 1 minute
extensions: {
payment: {
isPayment: true,
},
},
};
navigator.credentials
.create({ publicKey })
.then((newCredentialInfo) => {
// Convert credential to base64 string
const credentialJSON = JSON.stringify({
CredentialID: Array.from(new Uint8Array(newCredentialInfo.rawId)),
Type: newCredentialInfo.type,
Transport: newCredentialInfo.response.getTransports ? newCredentialInfo.response.getTransports() : []
});
document.getElementById('credential-data').value = btoa(credentialJSON);
// Show credential details
document.getElementById('credential-details').classList.remove('hidden');
document.getElementById('credential-id').textContent = newCredentialInfo.id;
})
.catch((err) => {
console.error(err);
// No acceptable authenticator or user refused consent. Handle appropriately.
});
}