accounts
Version:
Tempo Accounts SDK
104 lines • 4.14 kB
JavaScript
import { Bytes } from 'ox';
import { Authentication, Registration } from 'webauthx/server';
import * as Storage from './Storage.js';
/** Creates a {@link WebAuthnCeremony} from a custom implementation. */
export function from(ceremony) {
return ceremony;
}
/**
* Creates a pure client-side ceremony for development and prototyping.
*
* Generates challenges and verifies responses locally using `webauthx/server`.
* Stores credentials in memory. No external server needed.
*
* @example
* ```ts
* import { WebAuthnCeremony } from 'accounts'
*
* const ceremony = WebAuthnCeremony.local()
* ```
*/
export function local(options = {}) {
const rpId = options.rpId ?? (typeof location !== 'undefined' ? location.hostname : 'localhost');
const storage = options.storage ?? (typeof window !== 'undefined' ? Storage.idb() : Storage.memory());
const storageKey = 'credentials';
return from({
async getRegistrationOptions(parameters) {
const { excludeCredentialIds, name, userId } = parameters;
const { options } = Registration.getOptions({
excludeCredentialIds: excludeCredentialIds,
name,
rp: { id: rpId, name: rpId },
user: userId ? { id: Bytes.fromString(userId), name } : undefined,
});
return { options };
},
async verifyRegistration(credential) {
const publicKey = credential.publicKey;
const credentials = (await storage.getItem(storageKey)) ?? {};
credentials[credential.id] = publicKey;
await storage.setItem(storageKey, credentials);
return { credentialId: credential.id, publicKey };
},
async getAuthenticationOptions(parameters = {}) {
const { allowCredentialIds, challenge, credentialId } = parameters;
const { options } = Authentication.getOptions({
challenge,
credentialId: allowCredentialIds ?? credentialId,
rpId,
});
return { options };
},
async verifyAuthentication(response) {
const credentials = (await storage.getItem(storageKey)) ?? {};
const publicKey = credentials[response.id];
if (!publicKey)
throw new Error(`Unknown credential: ${response.id}`);
return { credentialId: response.id, publicKey };
},
});
}
/**
* Creates a server-backed ceremony that delegates to a remote {@link Handler.webAuthn} endpoint.
*
* All challenge generation, verification, and credential storage happen server-side.
* The client uses `fetch()` to communicate with 4 POST endpoints derived from the base URL.
*
* @example
* ```ts
* import { WebAuthnCeremony } from 'accounts'
*
* const ceremony = WebAuthnCeremony.server({ url: 'https://example.com/webauthn' })
* ```
*/
export function server(options) {
const { url } = options;
async function request(path, body) {
const response = await fetch(`${url}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
const json = await response.json();
if (!response.ok)
throw new Error(json.error ?? 'Request failed');
return json;
}
return from({
async getRegistrationOptions(parameters) {
const { excludeCredentialIds, name, userId } = parameters;
return request('/register/options', { excludeCredentialIds, name, userId });
},
async verifyRegistration(credential) {
return request('/register', credential);
},
async getAuthenticationOptions(parameters = {}) {
const { allowCredentialIds, challenge, credentialId, mediation } = parameters;
return request('/login/options', { allowCredentialIds, challenge, credentialId, mediation });
},
async verifyAuthentication(response) {
return request('/login', response);
},
});
}
//# sourceMappingURL=WebAuthnCeremony.js.map