diff --git a/components.d.ts b/components.d.ts index fc2f788..68aa164 100644 --- a/components.d.ts +++ b/components.d.ts @@ -9,8 +9,6 @@ declare module 'vue' { export interface GlobalComponents { NA: typeof import('naive-ui')['NA'] NAlert: typeof import('naive-ui')['NAlert'] - NAnchor: typeof import('naive-ui')['NAnchor'] - NAnchorLink: typeof import('naive-ui')['NAnchorLink'] NAvatar: typeof import('naive-ui')['NAvatar'] NBadge: typeof import('naive-ui')['NBadge'] NButton: typeof import('naive-ui')['NButton'] @@ -26,11 +24,9 @@ declare module 'vue' { NDescriptions: typeof import('naive-ui')['NDescriptions'] NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem'] NDialogProvider: typeof import('naive-ui')['NDialogProvider'] - NDivider: typeof import('naive-ui')['NDivider'] NDrawer: typeof import('naive-ui')['NDrawer'] NDrawerContent: typeof import('naive-ui')['NDrawerContent'] NDropdown: typeof import('naive-ui')['NDropdown'] - NDynamicTags: typeof import('naive-ui')['NDynamicTags'] NEmpty: typeof import('naive-ui')['NEmpty'] NFlex: typeof import('naive-ui')['NFlex'] NForm: typeof import('naive-ui')['NForm'] @@ -38,17 +34,12 @@ declare module 'vue' { NFormItemGi: typeof import('naive-ui')['NFormItemGi'] NFormItemGridItem: typeof import('naive-ui')['NFormItemGridItem'] NGrid: typeof import('naive-ui')['NGrid'] - NH1: typeof import('naive-ui')['NH1'] - NH2: typeof import('naive-ui')['NH2'] NH3: typeof import('naive-ui')['NH3'] NH4: typeof import('naive-ui')['NH4'] - NHr: typeof import('naive-ui')['NHr'] - NIcon: typeof import('naive-ui')['NIcon'] NInput: typeof import('naive-ui')['NInput'] NInputGroup: typeof import('naive-ui')['NInputGroup'] NInputGroupLabel: typeof import('naive-ui')['NInputGroupLabel'] NLayout: typeof import('naive-ui')['NLayout'] - NLayoutContent: typeof import('naive-ui')['NLayoutContent'] NLayoutSider: typeof import('naive-ui')['NLayoutSider'] NLi: typeof import('naive-ui')['NLi'] NList: typeof import('naive-ui')['NList'] @@ -57,12 +48,10 @@ declare module 'vue' { NMessageProvider: typeof import('naive-ui')['NMessageProvider'] NModal: typeof import('naive-ui')['NModal'] NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] - NOl: typeof import('naive-ui')['NOl'] NP: typeof import('naive-ui')['NP'] NPageHeader: typeof import('naive-ui')['NPageHeader'] NPopconfirm: typeof import('naive-ui')['NPopconfirm'] NProgress: typeof import('naive-ui')['NProgress'] - NScrollbar: typeof import('naive-ui')['NScrollbar'] NSelect: typeof import('naive-ui')['NSelect'] NSpace: typeof import('naive-ui')['NSpace'] NSpin: typeof import('naive-ui')['NSpin'] @@ -73,7 +62,6 @@ declare module 'vue' { NTag: typeof import('naive-ui')['NTag'] NText: typeof import('naive-ui')['NText'] NThing: typeof import('naive-ui')['NThing'] - NUl: typeof import('naive-ui')['NUl'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] } diff --git a/package-lock.json b/package-lock.json index 7952ec8..8ad23b2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "json-editor-vue": "^0.17.3", "lodash": "^4.17.21", "rustfs": "^8.0.2", + "node-forge": "^1.3.1", "nuxt": "^3.15.4", "nuxtjs-naive-ui": "^1.0.2", "pinia": "^2.3.0", diff --git a/package.json b/package.json index 68c1188..c7be4c5 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "json-editor-vue": "^0.17.3", "lodash": "^4.17.21", "rustfs": "^8.0.2", + "node-forge": "^1.3.1", "nuxt": "^3.15.4", "nuxtjs-naive-ui": "^1.0.2", "pinia": "^2.3.0", diff --git a/plugins/04.crypto-polyfill.ts b/plugins/04.crypto-polyfill.ts new file mode 100644 index 0000000..d084fc4 --- /dev/null +++ b/plugins/04.crypto-polyfill.ts @@ -0,0 +1,164 @@ +// @ts-nocheck +// This plugin imports the SubtleCrypto polyfill for browser compatibility +import forge from "node-forge"; + +// Polyfill for SubtleCrypto +// https://www.chromium.org/Home/chromium-security/prefer-secure-origins-for-powerful-new-features/ +// This is only needed for non-secure contexts (http://localhost:3000) +// and is used to provide a consistent API for cryptographic operations +// across different environments. It uses the forge library to implement +// the SubtleCrypto API in a way that is compatible with the Web Crypto API. +// The polyfill is loaded dynamically to avoid unnecessary overhead in secure contexts. +export default defineNuxtPlugin(() => { + if (!globalThis.crypto) { + globalThis.crypto = {} as Crypto; + } + + const cryptoObj = globalThis.crypto; + + if (!cryptoObj.subtle) { + cryptoObj.subtle = { + async digest(algorithm: string | Algorithm, data: BufferSource): Promise { + if (algorithm !== "SHA-256" && (algorithm as Algorithm).name !== "SHA-256") { + throw new Error("Only SHA-256 is supported."); + } + const md = forge.md.sha256.create(); + md.update(forge.util.createBuffer(new Uint8Array(data as ArrayBuffer)).getBytes()); + return new Uint8Array(md.digest().bytes().split("").map(c => c.charCodeAt(0))).buffer; + }, + }; + } + + if (!cryptoObj.randomUUID) { + cryptoObj.randomUUID = function () { + const randomBytes = cryptoObj.getRandomValues(new Uint8Array(16)); + randomBytes[6] = (randomBytes[6] & 0x0f) | 0x40; // UUID version 4 + randomBytes[8] = (randomBytes[8] & 0x3f) | 0x80; // UUID variant 1 + return [...randomBytes] + .map((b, i) => + [4, 6, 8, 10].includes(i) ? `-${b.toString(16).padStart(2, "0")}` : b.toString(16).padStart(2, "0") + ) + .join(""); + }; + } + + if (!cryptoObj.getRandomValues) { + cryptoObj.getRandomValues = function (arr: Uint8Array): Uint8Array { + const randomBytes = forge.random.getBytesSync(arr.length); + for (let i = 0; i < arr.length; i++) { + arr[i] = randomBytes.charCodeAt(i); + } + return arr; + }; + } + + if (!cryptoObj.subtle.generateKey) { + cryptoObj.subtle.generateKey = async function ( + algorithm: GenerateKeyParams, + extractable: boolean, + keyUsages: string[] + ): Promise { + if (algorithm.name !== "ECDSA" || algorithm.namedCurve !== "P-256") { + throw new Error("Only ECDSA with P-256 is supported."); + } + const keys = forge.pki.ec.generateKeyPair({ namedCurve: "P-256" }); + return { + privateKey: keys.privateKey as CryptoKey, + publicKey: keys.publicKey as CryptoKey, + }; + }; + } + + if (!cryptoObj.subtle.sign) { + cryptoObj.subtle.sign = async function ( + algorithm: Algorithm, + key: CryptoKey, + data: BufferSource + ): Promise { + if (algorithm.name !== "ECDSA" || algorithm.hash.name !== "SHA-256") { + throw new Error("Only ECDSA with SHA-256 is supported."); + } + const md = forge.md.sha256.create(); + md.update(forge.util.createBuffer(new Uint8Array(data as ArrayBuffer)).getBytes()); + const signature = forge.pki.ec.sign(md, key); + return new Uint8Array(signature.split("").map(c => c.charCodeAt(0))).buffer; + }; + } + + if (!cryptoObj.subtle.verify) { + cryptoObj.subtle.verify = async function ( + algorithm: Algorithm, + key: CryptoKey, + signature: BufferSource, + data: BufferSource + ): Promise { + if (algorithm.name !== "ECDSA" || algorithm.hash.name !== "SHA-256") { + throw new Error("Only ECDSA with SHA-256 is supported."); + } + const md = forge.md.sha256.create(); + md.update(forge.util.createBuffer(new Uint8Array(data as ArrayBuffer)).getBytes()); + return forge.pki.ec.verify(md, key, signature); + }; + } + + if (!cryptoObj.subtle.exportKey) { + cryptoObj.subtle.exportKey = async function ( + format: string, + key: CryptoKey + ): Promise { + if (format === "jwk") { + if ((key as any).type === "private") { + return forge.pki.privateKeyToJwk(key) as JsonWebKey; + } else if ((key as any).type === "public") { + return forge.pki.publicKeyToJwk(key) as JsonWebKey; + } + } + throw new Error("Unsupported export key format or type."); + }; + } + + if (!cryptoObj.subtle.importKey) { + cryptoObj.subtle.importKey = async function ( + format: string, + keyData: JsonWebKey | ArrayBuffer, + algorithm: Algorithm, + extractable: boolean, + keyUsages: string[] + ): Promise { + if (format === "jwk") { + if (keyData.type === "private") { + return forge.pki.jwkToPrivateKey(keyData) as CryptoKey; + } else if (keyData.type === "public") { + return forge.pki.jwkToPublicKey(keyData) as CryptoKey; + } + } + throw new Error("Unsupported import key format or type."); + }; + } + + if (!cryptoObj.subtle.encrypt) { + cryptoObj.subtle.encrypt = async function ( + algorithm: Algorithm, + key: CryptoKey, + data: BufferSource + ): Promise { + throw new Error("Encrypt not implemented"); + }; + } + + if (!cryptoObj.subtle.decrypt) { + cryptoObj.subtle.decrypt = async function ( + algorithm: Algorithm, + key: CryptoKey, + data: BufferSource + ): Promise { + throw new Error("Decrypt not implemented"); + }; + } + + return { + provide: { + crypto: cryptoObj, + }, + }; +})