120 lines
3.9 KiB
TypeScript
120 lines
3.9 KiB
TypeScript
|
import { action } from '@ember/object';
|
||
|
import Component from '@glimmer/component';
|
||
|
import { tracked } from '@glimmer/tracking';
|
||
|
import '@simplewebauthn/browser';
|
||
|
import {
|
||
|
startAuthentication,
|
||
|
startRegistration,
|
||
|
} from '@simplewebauthn/browser';
|
||
|
|
||
|
export interface PasskeySignature {
|
||
|
// The arguments accepted by the component
|
||
|
Args: {};
|
||
|
// Any blocks yielded by the component
|
||
|
Blocks: {
|
||
|
default: [];
|
||
|
};
|
||
|
// The element to which `...attributes` is applied in the component template
|
||
|
Element: null;
|
||
|
}
|
||
|
|
||
|
export default class Passkey extends Component<PasskeySignature> {
|
||
|
@tracked username: string = '';
|
||
|
@tracked error: string | undefined;
|
||
|
|
||
|
@action async startLogin() {
|
||
|
try {
|
||
|
// Get login options from your server. Here, we also receive the challenge.
|
||
|
const response = await fetch('/webauthn/passkey/loginBegin', {
|
||
|
method: 'POST',
|
||
|
headers: { 'Content-Type': 'application/json' },
|
||
|
body: JSON.stringify({ username: this.username }),
|
||
|
});
|
||
|
// Check if the login options are ok.
|
||
|
if (!response.ok) {
|
||
|
const msg = await response.json();
|
||
|
throw new Error('Failed to get login options from server: ' + msg);
|
||
|
}
|
||
|
// Convert the login options to JSON.
|
||
|
const options = await response.json();
|
||
|
|
||
|
// This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello).
|
||
|
// A new assertionResponse is created. This also means that the challenge has been signed.
|
||
|
const assertionResponse = await startAuthentication(options.publicKey);
|
||
|
|
||
|
// Send assertionResponse back to server for verification.
|
||
|
const verificationResponse = await fetch(
|
||
|
'/webauthn/passkey/loginFinish',
|
||
|
{
|
||
|
method: 'POST',
|
||
|
credentials: 'same-origin',
|
||
|
headers: {
|
||
|
'Content-Type': 'application/json',
|
||
|
},
|
||
|
body: JSON.stringify(assertionResponse),
|
||
|
},
|
||
|
);
|
||
|
|
||
|
const msg = await verificationResponse.json();
|
||
|
if (verificationResponse.ok) {
|
||
|
this.error = undefined;
|
||
|
} else {
|
||
|
this.error = msg;
|
||
|
}
|
||
|
} catch (error: any) {
|
||
|
this.error = 'Error: ' + error.message;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@action async startRegistration() {
|
||
|
try {
|
||
|
// Get registration options from your server. Here, we also receive the challenge.
|
||
|
const response = await fetch('/webauthn/passkey/registerBegin', {
|
||
|
method: 'POST',
|
||
|
headers: { 'Content-Type': 'application/json' },
|
||
|
body: JSON.stringify({ username: this.username }),
|
||
|
});
|
||
|
|
||
|
// Check if the registration options are ok.
|
||
|
if (!response.ok) {
|
||
|
const msg = await response.json();
|
||
|
throw new Error(
|
||
|
'User already exists or failed to get registration options from server: ' +
|
||
|
msg,
|
||
|
);
|
||
|
}
|
||
|
|
||
|
// Convert the registration options to JSON.
|
||
|
const options = await response.json();
|
||
|
console.log('registration start', options);
|
||
|
|
||
|
// This triggers the browser to display the passkey / WebAuthn modal (e.g. Face ID, Touch ID, Windows Hello).
|
||
|
// A new attestation is created. This also means a new public-private-key pair is created.
|
||
|
const attestationResponse = await startRegistration(options.publicKey);
|
||
|
|
||
|
console.log('Attempting to complete registration', attestationResponse);
|
||
|
// Send attestationResponse back to server for verification and storage.
|
||
|
const verificationResponse = await fetch(
|
||
|
'/webauthn/passkey/registerFinish',
|
||
|
{
|
||
|
method: 'POST',
|
||
|
credentials: 'same-origin',
|
||
|
headers: {
|
||
|
'Content-Type': 'application/json',
|
||
|
},
|
||
|
body: JSON.stringify(attestationResponse),
|
||
|
},
|
||
|
);
|
||
|
|
||
|
const msg = await verificationResponse.json();
|
||
|
if (verificationResponse.ok) {
|
||
|
this.error = undefined;
|
||
|
} else {
|
||
|
this.error = msg;
|
||
|
}
|
||
|
} catch (error: any) {
|
||
|
this.error = 'Error: ' + error.message;
|
||
|
}
|
||
|
}
|
||
|
}
|