electron-webauthn 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +20 -1
  2. package/dist/index.d.ts +8 -3
  3. package/dist/index.js +78 -3
  4. package/package.json +15 -11
  5. package/dist/additional-objc/ASCPublicKeyCredentialDescriptor.d.ts +0 -12
  6. package/dist/additional-objc/ASCPublicKeyCredentialDescriptor.js +0 -2
  7. package/dist/create/authorization-controller.d.ts +0 -10
  8. package/dist/create/authorization-controller.js +0 -77
  9. package/dist/create/handler.d.ts +0 -43
  10. package/dist/create/handler.js +0 -224
  11. package/dist/create/internal-handler.d.ts +0 -35
  12. package/dist/create/internal-handler.js +0 -207
  13. package/dist/get/authorization-controller.d.ts +0 -5
  14. package/dist/get/authorization-controller.js +0 -37
  15. package/dist/get/handler.d.ts +0 -38
  16. package/dist/get/handler.js +0 -137
  17. package/dist/get/internal-handler.d.ts +0 -24
  18. package/dist/get/internal-handler.js +0 -169
  19. package/dist/helpers/client-data.d.ts +0 -14
  20. package/dist/helpers/client-data.js +0 -36
  21. package/dist/helpers/index.d.ts +0 -8
  22. package/dist/helpers/index.js +0 -48
  23. package/dist/helpers/objc.d.ts +0 -5
  24. package/dist/helpers/objc.js +0 -10
  25. package/dist/helpers/origin.d.ts +0 -19
  26. package/dist/helpers/origin.js +0 -106
  27. package/dist/helpers/presentation.d.ts +0 -1
  28. package/dist/helpers/presentation.js +0 -11
  29. package/dist/helpers/prf.d.ts +0 -5
  30. package/dist/helpers/prf.js +0 -5
  31. package/dist/helpers/public-key.d.ts +0 -1
  32. package/dist/helpers/public-key.js +0 -49
  33. package/dist/helpers/rpid.d.ts +0 -13
  34. package/dist/helpers/rpid.js +0 -59
  35. package/dist/helpers/types.d.ts +0 -1
  36. package/dist/helpers/types.js +0 -1
  37. package/dist/helpers/validation.d.ts +0 -3
  38. package/dist/helpers/validation.js +0 -9
  39. package/dist/list/handler.d.ts +0 -2
  40. package/dist/list/handler.js +0 -65
  41. package/dist/list/types.d.ts +0 -14
  42. package/dist/list/types.js +0 -1
@@ -1,36 +0,0 @@
1
- import { createHash } from "crypto";
2
- import { isSameOrigin, serializeOrigin } from "./origin.js";
3
- import { bufferToBase64Url } from "./index.js";
4
- export function generateWebauthnClientData(type, origin, challenge, topFrameOrigin) {
5
- const serializedOrigin = serializeOrigin(origin);
6
- const clientData = {
7
- type,
8
- challenge: bufferToBase64Url(challenge),
9
- origin: serializedOrigin,
10
- crossOrigin: false,
11
- };
12
- if (topFrameOrigin) {
13
- const sameOrigin = isSameOrigin(origin, topFrameOrigin);
14
- if (!sameOrigin) {
15
- const serializedTopFrameOrigin = serializeOrigin(topFrameOrigin);
16
- clientData.topOrigin = serializedTopFrameOrigin;
17
- clientData.crossOrigin = true;
18
- }
19
- }
20
- return clientData;
21
- }
22
- function clientDataJsonBufferToHash(clientDataJSON) {
23
- if (!Buffer.isBuffer(clientDataJSON)) {
24
- throw new TypeError("clientDataJsonBufferToHash: clientDataJSON must be a Buffer");
25
- }
26
- if (clientDataJSON.length === 0) {
27
- throw new RangeError("clientDataJsonBufferToHash: clientDataJSON is empty");
28
- }
29
- return createHash("sha256").update(clientDataJSON).digest();
30
- }
31
- export function generateClientDataInfo(clientData) {
32
- const clientDataJSON = JSON.stringify(clientData);
33
- const clientDataBuffer = Buffer.from(clientDataJSON, "utf-8");
34
- const clientDataHash = clientDataJsonBufferToHash(clientDataBuffer);
35
- return { clientDataJSON, clientDataBuffer, clientDataHash };
36
- }
@@ -1,8 +0,0 @@
1
- export declare function PromiseWithResolvers<T = void>(): {
2
- promise: Promise<T>;
3
- resolve: (value: T | PromiseLike<T>) => void;
4
- reject: (reason?: unknown) => void;
5
- };
6
- export declare function bufferToBase64Url(buffer: Buffer): string;
7
- export declare function base64UrlToBuffer(b64url: string): Buffer;
8
- export declare function bufferSourceToBuffer(src: BufferSource): Buffer | null;
@@ -1,48 +0,0 @@
1
- export function PromiseWithResolvers() {
2
- let resolve;
3
- let reject;
4
- const promise = new Promise((res, rej) => {
5
- resolve = res;
6
- reject = rej;
7
- });
8
- return { promise, resolve: resolve, reject: reject };
9
- }
10
- export function bufferToBase64Url(buffer) {
11
- const bytes = new Uint8Array(buffer);
12
- let binary = "";
13
- for (let i = 0; i < bytes.length; i++) {
14
- binary += String.fromCharCode(bytes[i]);
15
- }
16
- return btoa(binary)
17
- .replace(/\+/g, "-")
18
- .replace(/\//g, "_")
19
- .replace(/=+$/, "");
20
- }
21
- export function base64UrlToBuffer(b64url) {
22
- if (typeof b64url !== "string")
23
- throw new TypeError("base64Url must be a string");
24
- let b64 = b64url.replace(/-/g, "+").replace(/_/g, "/");
25
- const pad = b64.length % 4;
26
- if (pad === 2)
27
- b64 += "==";
28
- else if (pad === 3)
29
- b64 += "=";
30
- else if (pad !== 0)
31
- throw new Error("Invalid base64url length");
32
- return Buffer.from(b64, "base64");
33
- }
34
- export function bufferSourceToBuffer(src) {
35
- if (!src)
36
- return null;
37
- if (Buffer.isBuffer(src))
38
- return src;
39
- if (src instanceof ArrayBuffer ||
40
- (typeof SharedArrayBuffer !== "undefined" &&
41
- src instanceof SharedArrayBuffer)) {
42
- return Buffer.from(src);
43
- }
44
- if (ArrayBuffer.isView(src)) {
45
- return Buffer.from(src.buffer, src.byteOffset, src.byteLength);
46
- }
47
- return null;
48
- }
@@ -1,5 +0,0 @@
1
- export declare function enumFromValue<T extends Record<string, number>>(enumObj: T, value: number): keyof T | undefined;
2
- export declare function makePromise<T, Args extends unknown[]>(...argsAndFunc: [
3
- ...args: Args,
4
- func: (...args: [...Args, callback: (result: T) => void]) => void
5
- ]): Promise<T>;
@@ -1,10 +0,0 @@
1
- export function enumFromValue(enumObj, value) {
2
- return Object.keys(enumObj).find((key) => enumObj[key] === value);
3
- }
4
- export function makePromise(...argsAndFunc) {
5
- const func = argsAndFunc[argsAndFunc.length - 1];
6
- const args = argsAndFunc.slice(0, -1);
7
- return new Promise((resolve) => {
8
- func(...args, resolve);
9
- });
10
- }
@@ -1,19 +0,0 @@
1
- export declare function serializeOrigin(origin: string): string | null;
2
- type TupleOrigin = {
3
- type: "tuple";
4
- scheme: string;
5
- host: string;
6
- port: number | null;
7
- };
8
- type OpaqueOrigin = {
9
- type: "opaque";
10
- reason?: string;
11
- };
12
- type Origin = TupleOrigin | OpaqueOrigin;
13
- export type OriginCompareOptions = {
14
- allowOpaqueStringEquality?: boolean;
15
- };
16
- export declare function computeOrigin(input: string | URL): Origin;
17
- export declare function isSameOrigin(a: string | URL, b: string | URL, opts?: OriginCompareOptions): boolean;
18
- export declare function isCrossOrigin(a: string | URL, b: string | URL, opts?: OriginCompareOptions): boolean;
19
- export {};
@@ -1,106 +0,0 @@
1
- export function serializeOrigin(origin) {
2
- if (origin === "null" || !origin) {
3
- return null;
4
- }
5
- try {
6
- const url = new URL(origin);
7
- let result = url.protocol;
8
- if (!result.endsWith("://")) {
9
- result = result.replace(/:$/, "") + "://";
10
- }
11
- result += url.hostname;
12
- if (url.port) {
13
- result += ":" + url.port;
14
- }
15
- return result;
16
- }
17
- catch (error) {
18
- return null;
19
- }
20
- }
21
- const DEFAULT_PORT = {
22
- http: 80,
23
- https: 443,
24
- ws: 80,
25
- wss: 443,
26
- ftp: 21,
27
- };
28
- const TUPLE_SCHEMES = new Set(["http", "https", "ws", "wss", "ftp"]);
29
- export function computeOrigin(input) {
30
- if (typeof input === "string" && input.trim().toLowerCase() === "null") {
31
- return { type: "opaque", reason: "explicit 'null' origin string" };
32
- }
33
- const url = toURL(input);
34
- if (!url)
35
- return { type: "opaque", reason: "unparseable URL/origin string" };
36
- const scheme = url.protocol.replace(/:$/, "").toLowerCase();
37
- if (scheme === "blob") {
38
- const embedded = url.href.slice("blob:".length);
39
- const embeddedUrl = toURL(embedded);
40
- return embeddedUrl
41
- ? computeOrigin(embeddedUrl)
42
- : { type: "opaque", reason: "blob: with unparseable embedded URL" };
43
- }
44
- if (!TUPLE_SCHEMES.has(scheme)) {
45
- return { type: "opaque", reason: `non-tuple scheme '${scheme}'` };
46
- }
47
- const host = url.hostname;
48
- if (!host)
49
- return { type: "opaque", reason: "missing hostname" };
50
- const rawPort = url.port ? safeParsePort(url.port) : null;
51
- const port = normalizePort(scheme, rawPort);
52
- return { type: "tuple", scheme, host, port };
53
- }
54
- function normalizePort(scheme, port) {
55
- if (port == null)
56
- return null;
57
- const def = DEFAULT_PORT[scheme];
58
- if (def != null && port === def)
59
- return null;
60
- return port;
61
- }
62
- function safeParsePort(portStr) {
63
- const n = Number(portStr);
64
- if (!Number.isInteger(n) || n < 0 || n > 65535)
65
- return null;
66
- return n;
67
- }
68
- function toURL(input) {
69
- if (input instanceof URL)
70
- return input;
71
- try {
72
- return new URL(input);
73
- }
74
- catch {
75
- return null;
76
- }
77
- }
78
- export function isSameOrigin(a, b, opts = {}) {
79
- const oa = computeOrigin(a);
80
- const ob = computeOrigin(b);
81
- if (oa.type === "tuple" && ob.type === "tuple") {
82
- return (oa.scheme === ob.scheme && oa.host === ob.host && oa.port === ob.port);
83
- }
84
- if (opts.allowOpaqueStringEquality) {
85
- const sa = originString(a);
86
- const sb = originString(b);
87
- return sa != null && sb != null && sa === sb;
88
- }
89
- return false;
90
- }
91
- export function isCrossOrigin(a, b, opts) {
92
- return !isSameOrigin(a, b, opts);
93
- }
94
- function originString(x) {
95
- if (typeof x === "string") {
96
- return serializeOrigin(x);
97
- }
98
- const url = toURL(x);
99
- if (!url) {
100
- return null;
101
- }
102
- const o = computeOrigin(url);
103
- if (o.type === "opaque")
104
- return null;
105
- return `${o.scheme}://${o.host}${o.port == null ? "" : `:${o.port}`}`;
106
- }
@@ -1 +0,0 @@
1
- export declare function createPresentationContextProviderFromNativeWindowHandle(nativeWindowHandle: Buffer): import("objc-js").NobjcObject;
@@ -1,11 +0,0 @@
1
- import { fromPointer } from "objc-js";
2
- import { createDelegate } from "objcjs-types";
3
- export function createPresentationContextProviderFromNativeWindowHandle(nativeWindowHandle) {
4
- return createDelegate("ASAuthorizationControllerPresentationContextProviding", {
5
- presentationAnchorForAuthorizationController$: () => {
6
- const nsView = fromPointer(nativeWindowHandle);
7
- const nsWindow = nsView.window();
8
- return nsWindow;
9
- },
10
- });
11
- }
@@ -1,5 +0,0 @@
1
- export interface PRFInput {
2
- first: Buffer;
3
- second?: Buffer;
4
- }
5
- export declare function createPRFInput(prf: PRFInput): import("objcjs-types/AuthenticationServices")._ASAuthorizationPublicKeyCredentialPRFAssertionInputValues;
@@ -1,5 +0,0 @@
1
- import { ASAuthorizationPublicKeyCredentialPRFAssertionInputValues } from "objcjs-types/AuthenticationServices";
2
- import { NSDataFromBuffer } from "objcjs-types/nsdata";
3
- export function createPRFInput(prf) {
4
- return ASAuthorizationPublicKeyCredentialPRFAssertionInputValues.alloc().initWithSaltInput1$saltInput2$(NSDataFromBuffer(prf.first), prf.second ? NSDataFromBuffer(prf.second) : null);
5
- }
@@ -1 +0,0 @@
1
- export declare function encodeEC2PublicKeyToSPKI(x: bigint, y: bigint): Buffer;
@@ -1,49 +0,0 @@
1
- function encodeBigIntToBuffer(value, byteLength) {
2
- const hex = value.toString(16).padStart(byteLength * 2, "0");
3
- return Buffer.from(hex, "hex");
4
- }
5
- export function encodeEC2PublicKeyToSPKI(x, y) {
6
- const xBuffer = encodeBigIntToBuffer(x, 32);
7
- const yBuffer = encodeBigIntToBuffer(y, 32);
8
- const uncompressedPoint = Buffer.concat([
9
- Buffer.from([0x04]),
10
- xBuffer,
11
- yBuffer,
12
- ]);
13
- const bitString = Buffer.concat([
14
- Buffer.from([0x03]),
15
- Buffer.from([0x42]),
16
- Buffer.from([0x00]),
17
- uncompressedPoint,
18
- ]);
19
- const algorithmIdentifier = Buffer.from([
20
- 0x30,
21
- 0x13,
22
- 0x06,
23
- 0x07,
24
- 0x2a,
25
- 0x86,
26
- 0x48,
27
- 0xce,
28
- 0x3d,
29
- 0x02,
30
- 0x01,
31
- 0x06,
32
- 0x08,
33
- 0x2a,
34
- 0x86,
35
- 0x48,
36
- 0xce,
37
- 0x3d,
38
- 0x03,
39
- 0x01,
40
- 0x07,
41
- ]);
42
- const spki = Buffer.concat([
43
- Buffer.from([0x30]),
44
- Buffer.from([0x59]),
45
- algorithmIdentifier,
46
- bitString,
47
- ]);
48
- return spki;
49
- }
@@ -1,13 +0,0 @@
1
- export type RpIdCheckOptions = {
2
- allowInsecureLocalhost?: boolean;
3
- isPublicSuffix?: (domain: string) => boolean;
4
- };
5
- export type RpIdCheckResult = {
6
- ok: true;
7
- rpId: string;
8
- } | {
9
- ok: false;
10
- rpId: string;
11
- reason: string;
12
- };
13
- export declare function isRpIdAllowedForOrigin(origin: string, rpIdInput?: string, opts?: RpIdCheckOptions): RpIdCheckResult;
@@ -1,59 +0,0 @@
1
- import net from "node:net";
2
- import { domainToASCII } from "node:url";
3
- export function isRpIdAllowedForOrigin(origin, rpIdInput, opts = {}) {
4
- const allowInsecureLocalhost = opts.allowInsecureLocalhost ?? true;
5
- let url;
6
- try {
7
- url = new URL(origin);
8
- }
9
- catch {
10
- return { ok: false, rpId: normalizeRpId(rpIdInput ?? ""), reason: "Invalid origin URL" };
11
- }
12
- const scheme = url.protocol.toLowerCase();
13
- const originHost = normalizeHost(url.hostname);
14
- if (!originHost)
15
- return { ok: false, rpId: normalizeRpId(rpIdInput ?? ""), reason: "Origin has no hostname" };
16
- const originIsIP = net.isIP(originHost) !== 0;
17
- const originIsLocalhost = originHost === "localhost" || originIsIP;
18
- const secureEnough = scheme === "https:" || scheme === "wss:" || (allowInsecureLocalhost && scheme === "http:" && originIsLocalhost);
19
- if (!secureEnough) {
20
- return { ok: false, rpId: normalizeRpId(rpIdInput ?? originHost), reason: "Origin is not a secure context" };
21
- }
22
- const rpId = normalizeRpId(rpIdInput ?? originHost);
23
- if (!rpId)
24
- return { ok: false, rpId, reason: "rpId is empty" };
25
- if (/[/:@]/.test(rpId)) {
26
- return { ok: false, rpId, reason: "rpId must be a hostname only (no scheme/port/path/userinfo)" };
27
- }
28
- const rpIdIsIP = net.isIP(rpId) !== 0;
29
- if (originIsIP) {
30
- if (rpId !== originHost) {
31
- return { ok: false, rpId, reason: "For IP origins, rpId must exactly match the IP" };
32
- }
33
- return { ok: true, rpId };
34
- }
35
- if (rpIdIsIP) {
36
- return { ok: false, rpId, reason: "rpId cannot be an IP if origin host is a domain" };
37
- }
38
- if (!(rpId === originHost || originHost.endsWith("." + rpId))) {
39
- return { ok: false, rpId, reason: "rpId is not equal to or a suffix of the origin hostname" };
40
- }
41
- if (opts.isPublicSuffix) {
42
- if (opts.isPublicSuffix(rpId)) {
43
- return { ok: false, rpId, reason: "rpId is a public suffix (eTLD), which is not allowed" };
44
- }
45
- }
46
- else {
47
- if (!rpId.includes(".") && rpId !== "localhost") {
48
- return { ok: false, rpId, reason: "rpId looks like a public suffix/single-label domain (no PSL check provided)" };
49
- }
50
- }
51
- return { ok: true, rpId };
52
- }
53
- function normalizeHost(hostname) {
54
- const h = (hostname ?? "").trim().toLowerCase().replace(/\.$/, "");
55
- return domainToASCII(h) || "";
56
- }
57
- function normalizeRpId(rpId) {
58
- return normalizeHost(rpId);
59
- }
@@ -1 +0,0 @@
1
- export type AuthenticatorAttachment = "platform" | "cross-platform";
@@ -1 +0,0 @@
1
- export {};
@@ -1,3 +0,0 @@
1
- export declare function isString(value: unknown): value is string;
2
- export declare function isNumber(value: unknown): value is number;
3
- export declare function isObject(value: unknown): boolean;
@@ -1,9 +0,0 @@
1
- export function isString(value) {
2
- return value && typeof value === "string";
3
- }
4
- export function isNumber(value) {
5
- return value && typeof value === "number";
6
- }
7
- export function isObject(value) {
8
- return value && typeof value === "object";
9
- }
@@ -1,2 +0,0 @@
1
- import type { ListPasskeysResult, ListPasskeysError } from "./types.js";
2
- export declare function listPasskeys(relyingPartyId: string): Promise<ListPasskeysResult | ListPasskeysError>;
@@ -1,65 +0,0 @@
1
- import { bufferToBase64Url, PromiseWithResolvers } from "../helpers/index.js";
2
- import { bufferFromNSDataDirect } from "objcjs-types/nsdata";
3
- import { NSStringFromString } from "objcjs-types/helpers";
4
- import { ASAuthorizationWebBrowserPublicKeyCredentialManager, ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationState, } from "objcjs-types/AuthenticationServices";
5
- import { isAtLeast, version, formatVersion, getOSVersion, } from "objcjs-types/osversion";
6
- import { makePromise, enumFromValue } from "../helpers/objc.js";
7
- export async function listPasskeys(relyingPartyId) {
8
- try {
9
- const minVersion = version(13, 3);
10
- if (!isAtLeast(minVersion)) {
11
- const currentVersion = getOSVersion();
12
- throw new Error(`Passkey listing requires macOS 13.3 or later (current: ${formatVersion(currentVersion)})`);
13
- }
14
- const manager = ASAuthorizationWebBrowserPublicKeyCredentialManager.alloc().init();
15
- const authState = manager.authorizationStateForPlatformCredentials();
16
- console.log(`[listPasskeys] Authorization state: ${authState}`);
17
- if (authState ===
18
- ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationState.NotDetermined) {
19
- console.log("[listPasskeys] Authorization not determined, requesting...");
20
- const newStateValue = await makePromise(manager.requestAuthorizationForPublicKeyCredentials$);
21
- const newState = enumFromValue(ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationState, newStateValue);
22
- console.log(`[listPasskeys] Authorization request completed, new state: ${newState}`);
23
- }
24
- else if (authState ===
25
- ASAuthorizationWebBrowserPublicKeyCredentialManagerAuthorizationState.Denied) {
26
- throw new Error("Authorization DENIED - user must grant permission in System Settings > Privacy & Security");
27
- }
28
- else {
29
- console.log("[listPasskeys] Authorization already granted");
30
- }
31
- const rpIdString = NSStringFromString(relyingPartyId);
32
- console.log(`[listPasskeys] Calling platformCredentialsForRelyingParty: ${relyingPartyId}`);
33
- const credentialsArray = await makePromise(rpIdString, manager.platformCredentialsForRelyingParty$completionHandler$);
34
- const count = credentialsArray.count();
35
- console.log(`[listPasskeys] platformCredentials returned ${count} entries`);
36
- const credentials = [];
37
- for (let i = 0; i < count; i++) {
38
- const cred = credentialsArray.objectAtIndex$(i);
39
- const credentialIdData = cred.credentialID();
40
- const userName = cred.name().UTF8String();
41
- const userHandleData = cred.userHandle();
42
- const credentialId = bufferToBase64Url(bufferFromNSDataDirect(credentialIdData));
43
- const userHandle = bufferToBase64Url(bufferFromNSDataDirect(userHandleData));
44
- console.log(`[listPasskeys] Found credential: name=${userName}, id=${credentialId.substring(0, 20)}...`);
45
- credentials.push({
46
- id: credentialId,
47
- rpId: relyingPartyId,
48
- userName,
49
- userHandle,
50
- });
51
- }
52
- console.log(`[listPasskeys] Returning ${credentials.length} results`);
53
- return {
54
- success: true,
55
- credentials,
56
- };
57
- }
58
- catch (error) {
59
- console.error("[listPasskeys] Error:", error);
60
- return {
61
- success: false,
62
- error: error instanceof Error ? error : new Error(String(error)),
63
- };
64
- }
65
- }
@@ -1,14 +0,0 @@
1
- export interface PasskeyCredential {
2
- id: string;
3
- rpId: string;
4
- userName: string;
5
- userHandle: string;
6
- }
7
- export interface ListPasskeysResult {
8
- success: true;
9
- credentials: PasskeyCredential[];
10
- }
11
- export interface ListPasskeysError {
12
- success: false;
13
- error: Error;
14
- }
@@ -1 +0,0 @@
1
- export {};