From bd6b518aae61608ddc2d82b43ccc283dc95b9c54 Mon Sep 17 00:00:00 2001
From: Junjie <fallin.jie@qq.com>
Date: 星期三, 11 三月 2026 13:59:33 +0800
Subject: [PATCH] #

---
 src/main/webapp/static/js/webauthn-utils.js |  141 +++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 141 insertions(+), 0 deletions(-)

diff --git a/src/main/webapp/static/js/webauthn-utils.js b/src/main/webapp/static/js/webauthn-utils.js
new file mode 100644
index 0000000..a54eb60
--- /dev/null
+++ b/src/main/webapp/static/js/webauthn-utils.js
@@ -0,0 +1,141 @@
+(function (window) {
+    "use strict";
+
+    function base64UrlToArrayBuffer(base64Url) {
+        var value = String(base64Url || "").replace(/-/g, "+").replace(/_/g, "/");
+        var padding = value.length % 4;
+        if (padding) {
+            value += new Array(5 - padding).join("=");
+        }
+        var binary = window.atob(value);
+        var bytes = new Uint8Array(binary.length);
+        for (var i = 0; i < binary.length; i++) {
+            bytes[i] = binary.charCodeAt(i);
+        }
+        return bytes.buffer;
+    }
+
+    function arrayBufferToBase64Url(buffer) {
+        var bytes = buffer instanceof Uint8Array ? buffer : new Uint8Array(buffer || []);
+        var binary = "";
+        for (var i = 0; i < bytes.length; i++) {
+            binary += String.fromCharCode(bytes[i]);
+        }
+        return window.btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
+    }
+
+    function normalizeArray(value) {
+        return Array.isArray(value) ? value : [];
+    }
+
+    function toCreationOptions(payload) {
+        var publicKey = {
+            challenge: base64UrlToArrayBuffer(payload.challenge),
+            rp: {
+                id: payload.rpId,
+                name: payload.rpName || "WCS"
+            },
+            user: {
+                id: base64UrlToArrayBuffer(payload.userId),
+                name: payload.userName,
+                displayName: payload.userDisplayName || payload.userName
+            },
+            pubKeyCredParams: normalizeArray(payload.pubKeyCredParams),
+            timeout: Number(payload.timeout || 60000),
+            attestation: payload.attestation || "none",
+            authenticatorSelection: payload.authenticatorSelection || {
+                residentKey: "preferred",
+                userVerification: "required"
+            }
+        };
+        var excludeCredentials = normalizeArray(payload.excludeCredentials).map(function (item) {
+            return {
+                type: item.type || "public-key",
+                id: base64UrlToArrayBuffer(item.id),
+                transports: normalizeArray(item.transports)
+            };
+        });
+        if (excludeCredentials.length) {
+            publicKey.excludeCredentials = excludeCredentials;
+        }
+        return { publicKey: publicKey };
+    }
+
+    function toRequestOptions(payload) {
+        var publicKey = {
+            challenge: base64UrlToArrayBuffer(payload.challenge),
+            rpId: payload.rpId,
+            timeout: Number(payload.timeout || 60000),
+            userVerification: payload.userVerification || "required"
+        };
+        var allowCredentials = normalizeArray(payload.allowCredentials).map(function (item) {
+            return {
+                type: item.type || "public-key",
+                id: base64UrlToArrayBuffer(item.id),
+                transports: normalizeArray(item.transports)
+            };
+        });
+        if (allowCredentials.length) {
+            publicKey.allowCredentials = allowCredentials;
+        }
+        return { publicKey: publicKey };
+    }
+
+    function ensureSupported() {
+        if (!window.isSecureContext) {
+            throw new Error("secure-context");
+        }
+        if (!window.PublicKeyCredential || !window.navigator || !window.navigator.credentials) {
+            throw new Error("not-supported");
+        }
+    }
+
+    async function register(payload) {
+        ensureSupported();
+        var credential = await window.navigator.credentials.create(toCreationOptions(payload));
+        if (!credential || !credential.response) {
+            throw new Error("create-empty");
+        }
+        var response = credential.response;
+        if (typeof response.getPublicKey !== "function" || typeof response.getAuthenticatorData !== "function") {
+            throw new Error("extension-unsupported");
+        }
+        var publicKey = response.getPublicKey();
+        var authenticatorData = response.getAuthenticatorData();
+        if (!publicKey || !authenticatorData) {
+            throw new Error("public-key-missing");
+        }
+        return {
+            credentialId: arrayBufferToBase64Url(credential.rawId),
+            clientDataJSON: arrayBufferToBase64Url(response.clientDataJSON),
+            authenticatorData: arrayBufferToBase64Url(authenticatorData),
+            publicKey: arrayBufferToBase64Url(publicKey),
+            publicKeyAlgorithm: response.getPublicKeyAlgorithm(),
+            transports: JSON.stringify(typeof response.getTransports === "function" ? response.getTransports() || [] : [])
+        };
+    }
+
+    async function authenticate(payload) {
+        ensureSupported();
+        var credential = await window.navigator.credentials.get(toRequestOptions(payload));
+        if (!credential || !credential.response) {
+            throw new Error("get-empty");
+        }
+        var response = credential.response;
+        return {
+            credentialId: arrayBufferToBase64Url(credential.rawId),
+            clientDataJSON: arrayBufferToBase64Url(response.clientDataJSON),
+            authenticatorData: arrayBufferToBase64Url(response.authenticatorData),
+            signature: arrayBufferToBase64Url(response.signature),
+            userHandle: response.userHandle ? arrayBufferToBase64Url(response.userHandle) : ""
+        };
+    }
+
+    window.WCS_WEBAUTHN = {
+        isSupported: function () {
+            return !!(window.isSecureContext && window.PublicKeyCredential && window.navigator && window.navigator.credentials);
+        },
+        register: register,
+        authenticate: authenticate
+    };
+})(window);

--
Gitblit v1.9.1