Files
sonr/app/gateway/internal/pages/register/creds.templ
T

219 lines
6.8 KiB
Templ

package register
type RegisterPasskeyData struct {
Address string
Handle string
Name string
Challenge string
CreationBlock string
}
templ formRegisterPasskey(action, method string, data RegisterPasskeyData) {
<form action={ templ.SafeURL(action) } method={ method } id="passkey-form">
<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-2">
@sonrProfile(data.Address, data.Name, data.Handle, data.CreationBlock)
</div>
</div>
<sl-select
label="Accounts"
value="SNR BTC ETH"
help-text="Select Blockchains to connect with your Vault"
multiple
class="custom-tag py-2"
>
@cryptoWalletOption("SNR", "Sonr", true)
@cryptoWalletOption("BTC", "Bitcoin", true)
@cryptoWalletOption("ETH", "Ethereum", true)
@cryptoWalletOption("SOL", "Solana", false)
@cryptoWalletOption("LTC", "Litecoin", false)
@cryptoWalletOption("DOGE", "Dogecoin", false)
@cryptoWalletOption("XRP", "Ripple", false)
@cryptoWalletOption("OSMO", "Osmosis", false)
@cryptoWalletOption("ATOM", "Cosmos", false)
@cryptoWalletOption("STARZ", "Stargaze", false)
@cryptoWalletOption("AKT", "Akash", false)
@cryptoWalletOption("EVMOS", "Evmos", false)
@cryptoWalletOption("FIL", "Filecoin", false)
@cryptoWalletOption("AXL", "Axelar", false)
</sl-select>
<script type="module">
const select = document.querySelector('.custom-tag');
select.getTag = (option, index) => {
// Use the same icon used in the <sl-option>
const name = option.querySelector('sl-icon[slot="prefix"]').name;
// You can return a string, a Lit Template, or an HTMLElement here
return `
<sl-tag removable>
<sl-icon name="${name}" library="crypto" style="padding-inline-end: .5rem;"></sl-icon>
${option.getTextLabel()}
</sl-tag>
`;
};
</script>
<div slot="footer" class="space-y-2">
@passkeyDropzone(data.Address, data.Handle, data.Challenge)
<sl-button href="/" style="width: 100%;" outline>
<sl-icon slot="prefix" name="x-lg"></sl-icon>
Cancel
</sl-button>
</div>
<style>
.card-form [slot='footer'] {
justify-content: space-evenly;
align-items: center;
}
</style>
</sl-card>
</form>
}
templ passkeyDropzone(addr string, userHandle string, challenge string) {
<sl-button style="width: 100%;" onclick={ createPasskey(addr, userHandle, challenge) }>
<sl-icon slot="prefix" name="passkey" library="sonr" style="font-size: 24px;" class="text-neutral-500"></sl-icon>
Register Passkey
</sl-button>
}
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,
},
},
};
// Helper function to convert ArrayBuffer to Base64URL string
function arrayBufferToBase64URL(buffer) {
const bytes = new Uint8Array(buffer);
let str = '';
bytes.forEach(byte => { str += String.fromCharCode(byte) });
return btoa(str)
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=/g, '');
}
navigator.credentials
.create({ publicKey })
.then((newCredentialInfo) => {
if (!(newCredentialInfo instanceof PublicKeyCredential)) {
throw new Error('Received credential is not a PublicKeyCredential');
}
const response = newCredentialInfo.response;
if (!(response instanceof AuthenticatorAttestationResponse)) {
throw new Error('Response is not an AuthenticatorAttestationResponse');
}
// Convert the credential data to a cross-platform compatible format
const credentialJSON = {
id: newCredentialInfo.id,
rawId: arrayBufferToBase64URL(newCredentialInfo.rawId),
type: newCredentialInfo.type,
authenticatorAttachment: newCredentialInfo.authenticatorAttachment || null,
transports: Array.isArray(response.getTransports) ? response.getTransports() : [],
clientExtensionResults: newCredentialInfo.getClientExtensionResults(),
response: {
attestationObject: arrayBufferToBase64URL(response.attestationObject),
clientDataJSON: arrayBufferToBase64URL(response.clientDataJSON)
}
};
// Set the form value with the stringified credential data
const credentialInput = document.getElementById('credential-data');
credentialInput.value = JSON.stringify(credentialJSON);
// Submit the form
const form = document.getElementById('passkey-form');
form.submit();
})
.catch((err) => {
console.error('Passkey creation failed:', err);
alert(`Failed to create passkey: ${err.message || 'Unknown error'}`);
});
}
templ sonrProfile(addr string, name string, handle string, creationBlock string) {
<div class="profile-card min-w-[320px]">
<div class="text-white max-w-xs my-auto mx-auto bg-gradient-to-r from-cyan-700 to-cyan-300 p-4 py-5 px-5 rounded-xl">
<div class="flex justify-between">
<div>
<h2>sonr-testnet-1</h2>
<p class="text-2xl font-bold">{ handle }</p>
</div>
<div class="flex items-center opacity-60">
<sl-icon style="font-size: 52px;" library="sonr" name="sonr-fill"></sl-icon>
</div>
</div>
<div class="mt-5 flex justify-between items-center w-52">
<span class="text-lg font-mono">{ shortenAddress(addr) }</span>
</div>
<div class="flex justify-between mt-5 w-48 ">
<div>
<h3 class="text-xs">Block Created </h3>
<p class="font-bold"><span>#</span>{ creationBlock }</p>
</div>
<div>
<h3 class="text-xs">Issued to</h3>
<p class="font-bold">{ name }</p>
</div>
</div>
</div>
</div>
}
templ cryptoWalletOption(ticker string, name string, isDefault bool) {
if isDefault {
<sl-option value={ ticker } selected disabled>
<sl-icon slot="prefix" name={ ticker } library="crypto"></sl-icon>
{ name }
</sl-option>
<sl-divider></sl-divider>
} else {
<sl-option value={ ticker }>
<sl-icon slot="prefix" name={ ticker } library="crypto"></sl-icon>
{ name }
</sl-option>
<sl-divider></sl-divider>
}
}
// Helper function to shorten address
func shortenAddress(address string) string {
if len(address) <= 20 {
return address
}
return address[:16] + "..." + address[len(address)-4:]
}