@turnkey/core
Version:
A core JavaScript web and React Native package for interfacing with Turnkey's infrastructure.
139 lines (134 loc) • 6.49 kB
JavaScript
;
var utils = require('../../utils.js');
var webauthnStamper = require('@turnkey/webauthn-stamper');
var encoding = require('@turnkey/encoding');
var http = require('@turnkey/http');
var uuid = require('uuid');
function _interopNamespaceDefaultOnly (e) { return Object.freeze({ __proto__: null, default: e }); }
let PasskeyStamperModule;
class CrossPlatformPasskeyStamper {
constructor(config) {
/**
* Create a passkey for an end-user, taking care of various lower-level details.
*
* @returns {Promise<Passkey>}
*/
this.createWebPasskey = async (config = {}) => {
const challenge = utils.generateRandomBuffer();
const encodedChallenge = encoding.base64StringToBase64UrlEncodedString(btoa(String.fromCharCode(...new Uint8Array(challenge))));
const authenticatorUserId = utils.generateRandomBuffer();
// WebAuthn credential options options can be found here:
// https://www.w3.org/TR/webauthn-2/#sctn-sample-registration
//
// All pubkey algorithms can be found here: https://www.iana.org/assignments/cose/cose.xhtml#algorithms
// Turnkey only supports ES256 (-7) and RS256 (-257)
//
// The pubkey type only supports one value, "public-key"
// See https://www.w3.org/TR/webauthn-2/#enumdef-publickeycredentialtype for more details
// TODO: consider un-nesting these config params
const webauthnConfig = {
publicKey: {
rp: {
id: config.publicKey?.rp?.id ?? this.config.rpId,
name: config.publicKey?.rp?.name ?? "",
},
challenge: config.publicKey?.challenge ?? challenge,
pubKeyCredParams: config.publicKey?.pubKeyCredParams ?? [
{
type: "public-key",
alg: -7,
},
{
type: "public-key",
alg: -257,
},
],
user: {
id: config.publicKey?.user?.id ?? authenticatorUserId,
name: config.publicKey?.user?.name ?? "Default User",
displayName: config.publicKey?.user?.displayName ?? "Default User",
},
authenticatorSelection: {
authenticatorAttachment: config.publicKey?.authenticatorSelection?.authenticatorAttachment ??
undefined, // default to empty
requireResidentKey: config.publicKey?.authenticatorSelection?.requireResidentKey ??
true,
residentKey: config.publicKey?.authenticatorSelection?.residentKey ?? "required",
userVerification: config.publicKey?.authenticatorSelection?.userVerification ??
"preferred",
},
},
};
const attestation = await http.getWebAuthnAttestation(webauthnConfig);
return {
encodedChallenge: config.publicKey?.challenge
? encoding.base64StringToBase64UrlEncodedString(config.publicKey?.challenge)
: encodedChallenge,
attestation,
};
};
this.createReactNativePasskey = async (config = {}) => {
const { name, displayName, authenticatorName } = config;
const { createPasskey } = PasskeyStamperModule; // We do a 'selective' import when initializing the stamper. This is safe to do here.
if (!createPasskey) {
throw new Error("Ensure you have @turnkey/react-native-passkey-stamper installed and linked correctly. Are you not on React Native?");
}
return await createPasskey({
rp: {
id: this.config.rpId,
name: this.config.rpName ?? "Turnkey",
},
user: {
id: uuid.v4(),
name,
displayName,
},
authenticatorName: authenticatorName ?? name ?? "End-User Passkey",
});
};
// Use init method to set up the stamper based on the platform. It's async, so can't be done in the constructor.
this.config = config;
}
async init() {
if (utils.isWeb()) {
const { default: WindowWrapper } = await Promise.resolve().then(function () { return /*#__PURE__*/_interopNamespaceDefaultOnly(require('../../__polyfills__/window.js')); });
this.stamper = new webauthnStamper.WebauthnStamper({
...this.config,
rpId: this.config.rpId ?? WindowWrapper.location.hostname,
});
}
else if (utils.isReactNative()) {
try {
// Dynamic import to prevent bundling the native module in web environments.
let PasskeyStamper;
try {
PasskeyStamperModule = require("@turnkey/react-native-passkey-stamper");
PasskeyStamper = PasskeyStamperModule.PasskeyStamper;
}
catch {
throw new Error("Please install react-native-passkeys and @turnkey/react-native-passkey-stamper in your app to use passkeys.");
}
this.stamper = new PasskeyStamper({
...this.config,
rpId: this.config.rpId,
allowCredentials: this.config.allowCredentials?.map((cred) => ({
id: encoding.uint8ArrayToHexString(cred.id),
type: cred.type,
transports: cred.transports,
})),
});
}
catch (error) {
throw new Error(`Failed to load passkey stamper for react-native: ${error}`);
}
}
else {
throw new Error("Unsupported platform for passkey stamper");
}
}
async stamp(payload) {
return await this.stamper.stamp(payload);
}
}
exports.CrossPlatformPasskeyStamper = CrossPlatformPasskeyStamper;
//# sourceMappingURL=base.js.map