@liujitcn/kratos-admin-core 0.0.32 → 0.0.34

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 (30) hide show
  1. package/dist/package/declarations/src/locales/generated.d.ts +28 -0
  2. package/dist/package/src/locales/en-US.json +7 -0
  3. package/dist/package/src/locales/ja-JP.json +7 -0
  4. package/dist/package/src/locales/zh-CN.json +7 -0
  5. package/dist/package/src/locales/zh-TW.json +7 -0
  6. package/dist/package/src/rpc/base/v1/config.ts +1 -1
  7. package/dist/package/src/rpc/base/v1/file.ts +1 -1
  8. package/dist/package/src/rpc/base/v1/language.ts +1 -1
  9. package/dist/package/src/rpc/base/v1/login.ts +1 -1
  10. package/dist/package/src/rpc/base/v1/mfa.ts +1 -1
  11. package/dist/package/src/rpc/base/v1/oauth.ts +1 -1
  12. package/dist/package/src/rpc/base/v1/oauth_client.ts +1 -1
  13. package/dist/package/src/rpc/common/v1/common.ts +1 -1
  14. package/dist/package/src/rpc/common/v1/enum.ts +1 -1
  15. package/dist/package/src/rpc/common/v1/types.ts +1 -1
  16. package/dist/package/src/rpc/google/protobuf/empty.ts +1 -1
  17. package/dist/package/src/rpc/google/protobuf/wrappers.ts +1 -1
  18. package/dist/package/src/rpc/system/admin/v1/auth.ts +1 -1
  19. package/dist/package/src/rpc/system/admin/v1/base_dict.ts +1 -1
  20. package/dist/package/src/rpc/system/admin/v1/base_i18n.ts +1 -1
  21. package/dist/package/src/rpc/system/admin/v1/base_tenant.ts +1 -1
  22. package/dist/package/src/rpc/system/admin/v1/common.ts +1 -1
  23. package/dist/package/src/rpc/system/common/v1/common.ts +1 -1
  24. package/dist/package/src/typings/global.d.ts +6 -0
  25. package/dist/package/src/utils/passwordCrypto.ts +168 -11
  26. package/dist/package/src/views/login/components/LoginForm.vue +4 -1
  27. package/dist/package/types/generated/auto-imports.d.ts +2 -0
  28. package/package.json +2 -1
  29. package/types/generated/auto-imports.d.ts +2 -0
  30. package/vite.config.ts +23 -1
@@ -206,6 +206,13 @@ export declare const LOCALE_MESSAGES: {
206
206
  "core.oauth.unknown": string;
207
207
  "core.password.error": string;
208
208
  "core.password.crypto_unsupported": string;
209
+ "core.password.crypto_secure_context": string;
210
+ "core.password.base64_decode_unsupported": string;
211
+ "core.password.base64_encode_unsupported": string;
212
+ "core.password.encrypt_failed": string;
213
+ "core.password.password_too_long": string;
214
+ "core.password.public_key_invalid": string;
215
+ "core.password.random_failed": string;
209
216
  "core.password.required": string;
210
217
  "core.password.verify_title": string;
211
218
  "core.password.verify_label": string;
@@ -534,6 +541,13 @@ export declare const LOCALE_MESSAGES: {
534
541
  "core.oauth.unknown": string;
535
542
  "core.password.error": string;
536
543
  "core.password.crypto_unsupported": string;
544
+ "core.password.crypto_secure_context": string;
545
+ "core.password.base64_decode_unsupported": string;
546
+ "core.password.base64_encode_unsupported": string;
547
+ "core.password.encrypt_failed": string;
548
+ "core.password.password_too_long": string;
549
+ "core.password.public_key_invalid": string;
550
+ "core.password.random_failed": string;
537
551
  "core.password.required": string;
538
552
  "core.password.verify_title": string;
539
553
  "core.password.verify_label": string;
@@ -862,6 +876,13 @@ export declare const LOCALE_MESSAGES: {
862
876
  "core.oauth.unknown": string;
863
877
  "core.password.error": string;
864
878
  "core.password.crypto_unsupported": string;
879
+ "core.password.crypto_secure_context": string;
880
+ "core.password.base64_decode_unsupported": string;
881
+ "core.password.base64_encode_unsupported": string;
882
+ "core.password.encrypt_failed": string;
883
+ "core.password.password_too_long": string;
884
+ "core.password.public_key_invalid": string;
885
+ "core.password.random_failed": string;
865
886
  "core.password.required": string;
866
887
  "core.password.verify_title": string;
867
888
  "core.password.verify_label": string;
@@ -1190,6 +1211,13 @@ export declare const LOCALE_MESSAGES: {
1190
1211
  "core.oauth.unknown": string;
1191
1212
  "core.password.error": string;
1192
1213
  "core.password.crypto_unsupported": string;
1214
+ "core.password.crypto_secure_context": string;
1215
+ "core.password.base64_decode_unsupported": string;
1216
+ "core.password.base64_encode_unsupported": string;
1217
+ "core.password.encrypt_failed": string;
1218
+ "core.password.password_too_long": string;
1219
+ "core.password.public_key_invalid": string;
1220
+ "core.password.random_failed": string;
1193
1221
  "core.password.required": string;
1194
1222
  "core.password.verify_title": string;
1195
1223
  "core.password.verify_label": string;
@@ -201,6 +201,13 @@
201
201
  "core.oauth.unknown": "Unknown provider",
202
202
  "core.password.error": "Password does not meet the active password policy",
203
203
  "core.password.crypto_unsupported": "Password encryption is not supported in this environment",
204
+ "core.password.crypto_secure_context": "Password encryption requires localhost or HTTPS. Please use a secure address.",
205
+ "core.password.base64_decode_unsupported": "Base64 decoding is not supported in this environment",
206
+ "core.password.base64_encode_unsupported": "Base64 encoding is not supported in this environment",
207
+ "core.password.encrypt_failed": "Password encryption failed",
208
+ "core.password.password_too_long": "Password exceeds the supported length",
209
+ "core.password.public_key_invalid": "Invalid public key",
210
+ "core.password.random_failed": "Failed to generate random data",
204
211
  "core.password.required": "Password is required",
205
212
  "core.password.verify_title": "Verify password",
206
213
  "core.password.verify_label": "Current password",
@@ -201,6 +201,13 @@
201
201
  "core.oauth.unknown": "不明な方法です",
202
202
  "core.password.error": "パスワードが現在のパスワードポリシーを満たしていません",
203
203
  "core.password.crypto_unsupported": "現在の環境はパスワード暗号化をサポートしていません",
204
+ "core.password.crypto_secure_context": "パスワード暗号化には localhost または HTTPS が必要です。安全なアドレスでアクセスしてください。",
205
+ "core.password.base64_decode_unsupported": "現在の環境は Base64 デコードをサポートしていません",
206
+ "core.password.base64_encode_unsupported": "現在の環境は Base64 エンコードをサポートしていません",
207
+ "core.password.encrypt_failed": "パスワードの暗号化に失敗しました",
208
+ "core.password.password_too_long": "パスワードの長さが制限を超えています",
209
+ "core.password.public_key_invalid": "公開鍵の形式が無効です",
210
+ "core.password.random_failed": "乱数の生成に失敗しました",
204
211
  "core.password.required": "パスワードを空にすることはできません",
205
212
  "core.password.verify_title": "パスワードの確認",
206
213
  "core.password.verify_label": "現在のパスワード",
@@ -201,6 +201,13 @@
201
201
  "core.oauth.unknown": "未知方式",
202
202
  "core.password.error": "密码不符合当前生效的密码策略",
203
203
  "core.password.crypto_unsupported": "当前环境不支持密码加密",
204
+ "core.password.crypto_secure_context": "当前地址不支持密码加密,请使用 localhost 或 HTTPS 访问",
205
+ "core.password.base64_decode_unsupported": "当前环境不支持 Base64 解码",
206
+ "core.password.base64_encode_unsupported": "当前环境不支持 Base64 编码",
207
+ "core.password.encrypt_failed": "密码加密失败",
208
+ "core.password.password_too_long": "密码长度超出限制",
209
+ "core.password.public_key_invalid": "公钥格式无效",
210
+ "core.password.random_failed": "随机数生成失败",
204
211
  "core.password.required": "密码不能为空",
205
212
  "core.password.verify_title": "验证密码",
206
213
  "core.password.verify_label": "当前密码",
@@ -201,6 +201,13 @@
201
201
  "core.oauth.unknown": "未知方式",
202
202
  "core.password.error": "密碼不符合目前生效的密碼策略",
203
203
  "core.password.crypto_unsupported": "當前環境不支援密碼加密",
204
+ "core.password.crypto_secure_context": "目前地址不支援密碼加密,請使用 localhost 或 HTTPS 訪問",
205
+ "core.password.base64_decode_unsupported": "當前環境不支援 Base64 解碼",
206
+ "core.password.base64_encode_unsupported": "當前環境不支援 Base64 編碼",
207
+ "core.password.encrypt_failed": "密碼加密失敗",
208
+ "core.password.password_too_long": "密碼長度超出限制",
209
+ "core.password.public_key_invalid": "公鑰格式無效",
210
+ "core.password.random_failed": "隨機數生成失敗",
204
211
  "core.password.required": "密碼不能為空",
205
212
  "core.password.verify_title": "驗證密碼",
206
213
  "core.password.verify_label": "當前密碼",
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/config.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/file.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/language.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/login.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/mfa.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/oauth.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: base/v1/oauth_client.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: common/v1/common.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: common/v1/enum.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: common/v1/types.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: google/protobuf/empty.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: google/protobuf/wrappers.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/admin/v1/auth.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/admin/v1/base_dict.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/admin/v1/base_i18n.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/admin/v1/base_tenant.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/admin/v1/common.proto
6
6
 
@@ -1,6 +1,6 @@
1
1
  // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
2
  // versions:
3
- // protoc-gen-ts_proto v2.11.8
3
+ // protoc-gen-ts_proto v2.12.1
4
4
  // protoc unknown
5
5
  // source: system/common/v1/common.proto
6
6
 
@@ -55,6 +55,12 @@ declare interface ViteEnv {
55
55
  VITE_PUBLIC_PATH: string;
56
56
  VITE_API_URL: string;
57
57
  VITE_PROXY: [string, string][];
58
+ /** 是否启用 Vite HTTPS 开发服务。 */
59
+ VITE_HTTPS: boolean;
60
+ /** HTTPS 私钥路径,相对于宿主 apps/admin 目录。 */
61
+ VITE_HTTPS_KEY: string;
62
+ /** HTTPS 证书路径,相对于宿主 apps/admin 目录。 */
63
+ VITE_HTTPS_CERT: string;
58
64
  VITE_CODEINSPECTOR: boolean;
59
65
  }
60
66
 
@@ -1,7 +1,10 @@
1
1
  import { defLoginService } from "../api/base/v1/login";
2
- import { PasswordCryptoScene } from "../rpc/base/v1/login";
2
+ import { PasswordCryptoScene, type PasswordPublicKeyResponse } from "../rpc/base/v1/login";
3
3
  import type { PasswordCrypto } from "../rpc/common/v1/types";
4
4
  import { t } from "../locales";
5
+ import * as portableCrypto from "asmcrypto.js";
6
+
7
+ type PortableCrypto = typeof import("asmcrypto.js");
5
8
 
6
9
  export const PASSWORD_CRYPTO_SCENE = PasswordCryptoScene;
7
10
  export type { PasswordCryptoScene };
@@ -12,7 +15,11 @@ function pemToArrayBuffer(pem: string) {
12
15
  .replace(/-----BEGIN PUBLIC KEY-----/g, "")
13
16
  .replace(/-----END PUBLIC KEY-----/g, "")
14
17
  .replace(/\s/g, "");
15
- const binary = window.atob(base64);
18
+ const decode = globalThis.atob;
19
+ if (!decode) {
20
+ throw new Error(t("core.password.base64_decode_unsupported"));
21
+ }
22
+ const binary = decode(base64);
16
23
  const buffer = new Uint8Array(binary.length);
17
24
  for (let i = 0; i < binary.length; i += 1) {
18
25
  buffer[i] = binary.charCodeAt(i);
@@ -27,18 +34,163 @@ function arrayBufferToBase64(buffer: ArrayBuffer) {
27
34
  for (let i = 0; i < bytes.byteLength; i += 1) {
28
35
  binary += String.fromCharCode(bytes[i]);
29
36
  }
30
- return window.btoa(binary);
37
+ const encode = globalThis.btoa;
38
+ if (!encode) {
39
+ throw new Error(t("core.password.base64_encode_unsupported"));
40
+ }
41
+ return encode(binary);
31
42
  }
32
43
 
33
44
  /** 获取浏览器 WebCrypto 能力。 */
34
45
  function getSubtleCrypto() {
35
46
  const cryptoApi = globalThis.crypto;
36
- if (!cryptoApi?.subtle) {
47
+ const isSecureContext = typeof window === "undefined" || window.isSecureContext;
48
+ if (!cryptoApi?.subtle || !isSecureContext) {
37
49
  throw new Error(t("core.password.crypto_unsupported"));
38
50
  }
39
51
  return cryptoApi;
40
52
  }
41
53
 
54
+ /** 读取 DER 编码节点,用于解析 RSA SubjectPublicKeyInfo。 */
55
+ function readDerNode(data: Uint8Array, offset: number) {
56
+ if (offset + 2 > data.length) {
57
+ throw new Error(t("core.password.public_key_invalid"));
58
+ }
59
+ const tag = data[offset];
60
+ const firstLengthByte = data[offset + 1];
61
+ let length = firstLengthByte;
62
+ let valueOffset = offset + 2;
63
+ if (firstLengthByte & 0x80) {
64
+ const lengthByteCount = firstLengthByte & 0x7f;
65
+ if (lengthByteCount === 0 || valueOffset + lengthByteCount > data.length) {
66
+ throw new Error(t("core.password.public_key_invalid"));
67
+ }
68
+ length = 0;
69
+ for (let index = 0; index < lengthByteCount; index += 1) {
70
+ length = length * 256 + data[valueOffset + index];
71
+ }
72
+ valueOffset += lengthByteCount;
73
+ }
74
+ const end = valueOffset + length;
75
+ if (end > data.length) {
76
+ throw new Error(t("core.password.public_key_invalid"));
77
+ }
78
+ return { tag, value: data.subarray(valueOffset, end), next: end };
79
+ }
80
+
81
+ /** 去除 DER 正整数前导零字节。 */
82
+ function trimDerInteger(value: Uint8Array) {
83
+ let offset = 0;
84
+ while (offset < value.length - 1 && value[offset] === 0) {
85
+ offset += 1;
86
+ }
87
+ return value.subarray(offset);
88
+ }
89
+
90
+ /** 从 PEM SubjectPublicKeyInfo 中提取 RSA 模数和指数。 */
91
+ function parsePortableRsaPublicKey(publicKey: string): [Uint8Array, Uint8Array] {
92
+ const der = new Uint8Array(pemToArrayBuffer(publicKey));
93
+ const subjectPublicKeyInfo = readDerNode(der, 0);
94
+ const algorithm = readDerNode(subjectPublicKeyInfo.value, 0);
95
+ const bitString = readDerNode(subjectPublicKeyInfo.value, algorithm.next);
96
+ if (bitString.tag !== 0x03 || bitString.value[0] !== 0) {
97
+ throw new Error(t("core.password.public_key_invalid"));
98
+ }
99
+ const rsaPublicKey = readDerNode(bitString.value, 1);
100
+ const modulus = readDerNode(rsaPublicKey.value, 0);
101
+ const exponent = readDerNode(rsaPublicKey.value, modulus.next);
102
+ if (modulus.tag !== 0x02 || exponent.tag !== 0x02) {
103
+ throw new Error(t("core.password.public_key_invalid"));
104
+ }
105
+ return [trimDerInteger(modulus.value), trimDerInteger(exponent.value)];
106
+ }
107
+
108
+ /** 计算 RSA-OAEP 所需的 MGF1 掩码。 */
109
+ function generateMgf1Mask(cryptoApi: PortableCrypto, seed: Uint8Array, length: number) {
110
+ const mask = new Uint8Array(length);
111
+ const counter = new Uint8Array(4);
112
+ const hash = new cryptoApi.Sha256();
113
+ const blockCount = Math.ceil(length / hash.HASH_SIZE);
114
+ for (let index = 0; index < blockCount; index += 1) {
115
+ counter[0] = index >>> 24;
116
+ counter[1] = index >>> 16;
117
+ counter[2] = index >>> 8;
118
+ counter[3] = index;
119
+ let block = hash.reset().process(seed).process(counter).finish().result;
120
+ if (!block) {
121
+ throw new Error(t("core.password.encrypt_failed"));
122
+ }
123
+ const target = mask.subarray(index * hash.HASH_SIZE);
124
+ if (block.length > target.length) {
125
+ block = block.subarray(0, target.length);
126
+ }
127
+ target.set(block);
128
+ }
129
+ return mask;
130
+ }
131
+
132
+ /** 生成纯 JavaScript 密码加密所需的随机字节。 */
133
+ function getPortableRandomBytes(length: number) {
134
+ const cryptoApi = globalThis.crypto;
135
+ if (!cryptoApi?.getRandomValues) {
136
+ throw new Error(t("core.password.random_failed"));
137
+ }
138
+ return cryptoApi.getRandomValues(new Uint8Array(length));
139
+ }
140
+
141
+ /** 使用纯 JavaScript 实现兼容缺少 WebCrypto 的密码加密协议。 */
142
+ async function encryptPortablePassword(
143
+ cryptoApi: PortableCrypto,
144
+ password: string,
145
+ publicKeyResponse: PasswordPublicKeyResponse
146
+ ): Promise<PasswordCrypto> {
147
+ const aesKey = getPortableRandomBytes(32);
148
+ const iv = getPortableRandomBytes(12);
149
+ const plaintext = cryptoApi.string_to_bytes(password, true);
150
+ const [modulus, exponent] = parsePortableRsaPublicKey(publicKeyResponse.public_key);
151
+ const hash = new cryptoApi.Sha256();
152
+ const keySize = Math.ceil(new cryptoApi.BigNumber(modulus).bitLength / 8);
153
+ const seed = getPortableRandomBytes(hash.HASH_SIZE);
154
+ const dataBlockLength = keySize - hash.HASH_SIZE - 1;
155
+ const paddingLength = dataBlockLength - aesKey.length - hash.HASH_SIZE - 1;
156
+ if (paddingLength < 0) {
157
+ throw new Error(t("core.password.password_too_long"));
158
+ }
159
+ const encodedMessage = new Uint8Array(keySize);
160
+ const maskedSeed = encodedMessage.subarray(1, hash.HASH_SIZE + 1);
161
+ const dataBlock = encodedMessage.subarray(hash.HASH_SIZE + 1);
162
+ const labelHash = hash.reset().process(new Uint8Array()).finish().result;
163
+ if (!labelHash) {
164
+ throw new Error(t("core.password.encrypt_failed"));
165
+ }
166
+ dataBlock.set(labelHash, 0);
167
+ dataBlock.set(aesKey, hash.HASH_SIZE + paddingLength + 1);
168
+ dataBlock[hash.HASH_SIZE + paddingLength] = 1;
169
+ const dataBlockMask = generateMgf1Mask(cryptoApi, seed, dataBlock.length);
170
+ for (let index = 0; index < dataBlock.length; index += 1) {
171
+ dataBlock[index] ^= dataBlockMask[index];
172
+ }
173
+ const seedMask = generateMgf1Mask(cryptoApi, dataBlock, maskedSeed.length);
174
+ maskedSeed.set(seed);
175
+ for (let index = 0; index < maskedSeed.length; index += 1) {
176
+ maskedSeed[index] ^= seedMask[index];
177
+ }
178
+ const encryptedKey = new cryptoApi.RSA([modulus, exponent]).encrypt(new cryptoApi.BigNumber(encodedMessage)).result;
179
+ if (!encryptedKey) {
180
+ throw new Error(t("core.password.encrypt_failed"));
181
+ }
182
+ const ciphertext = cryptoApi.AES_GCM.encrypt(plaintext, aesKey, iv);
183
+
184
+ return {
185
+ key_id: publicKeyResponse.key_id,
186
+ nonce: publicKeyResponse.nonce,
187
+ algorithm: publicKeyResponse.algorithm,
188
+ encrypted_key: arrayBufferToBase64(encryptedKey.slice().buffer),
189
+ iv: arrayBufferToBase64(iv.buffer),
190
+ ciphertext: arrayBufferToBase64(ciphertext.slice().buffer)
191
+ };
192
+ }
193
+
42
194
  /** 加密单个密码字段,返回后端可解析的密码密文。 */
43
195
  export async function encryptPassword(password: string, scene: PasswordCryptoScene): Promise<PasswordCrypto> {
44
196
  const plainPassword = password.trim();
@@ -46,20 +198,25 @@ export async function encryptPassword(password: string, scene: PasswordCryptoSce
46
198
  throw new Error(t("core.password.required"));
47
199
  }
48
200
 
49
- const cryptoApi = getSubtleCrypto();
50
201
  const publicKeyResponse = await defLoginService.PasswordPublicKey({ scene });
51
- const publicKey = await cryptoApi.subtle.importKey(
202
+ const cryptoApi = globalThis.crypto;
203
+ const isSecureContext = typeof window === "undefined" || window.isSecureContext;
204
+ if (!cryptoApi?.subtle || !isSecureContext) {
205
+ return encryptPortablePassword(portableCrypto, plainPassword, publicKeyResponse);
206
+ }
207
+ const subtleCrypto = getSubtleCrypto();
208
+ const publicKey = await subtleCrypto.subtle.importKey(
52
209
  "spki",
53
210
  pemToArrayBuffer(publicKeyResponse.public_key),
54
211
  { name: "RSA-OAEP", hash: "SHA-256" },
55
212
  false,
56
213
  ["encrypt"]
57
214
  );
58
- const aesKey = await cryptoApi.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt"]);
59
- const rawAesKey = await cryptoApi.subtle.exportKey("raw", aesKey);
60
- const iv = cryptoApi.getRandomValues(new Uint8Array(12));
61
- const ciphertext = await cryptoApi.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, new TextEncoder().encode(plainPassword));
62
- const encryptedKey = await cryptoApi.subtle.encrypt({ name: "RSA-OAEP" }, publicKey, rawAesKey);
215
+ const aesKey = await subtleCrypto.subtle.generateKey({ name: "AES-GCM", length: 256 }, true, ["encrypt"]);
216
+ const rawAesKey = await subtleCrypto.subtle.exportKey("raw", aesKey);
217
+ const iv = subtleCrypto.getRandomValues(new Uint8Array(12));
218
+ const ciphertext = await subtleCrypto.subtle.encrypt({ name: "AES-GCM", iv }, aesKey, new TextEncoder().encode(plainPassword));
219
+ const encryptedKey = await subtleCrypto.subtle.encrypt({ name: "RSA-OAEP" }, publicKey, rawAesKey);
63
220
 
64
221
  return {
65
222
  key_id: publicKeyResponse.key_id,
@@ -766,7 +766,10 @@ const submitLogin = async (captchaToken: string) => {
766
766
  // 1.执行登录接口,挑战态不保存令牌,等待 MFA 完成。
767
767
  const result = await userStore.login(loginRequest);
768
768
  await handleLoginResponse(result);
769
- } catch (_error) {
769
+ } catch (error) {
770
+ if (error instanceof Error && error.message === t("core.password.crypto_secure_context")) {
771
+ ElMessage.error(error.message);
772
+ }
770
773
  await loadPageCaptcha();
771
774
  } finally {
772
775
  loading.value = false;
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable */
2
2
  /* prettier-ignore */
3
+ /* oxlint-disable */
4
+ /* oxfmt-ignore */
3
5
  // @ts-nocheck
4
6
  // noinspection JSUnusedGlobalSymbols
5
7
  // Generated by unplugin-auto-import
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@liujitcn/kratos-admin-core",
3
3
  "private": false,
4
- "version": "0.0.32",
4
+ "version": "0.0.34",
5
5
  "type": "module",
6
6
  "description": "kratos-admin frontend runtime and base modules",
7
7
  "author": {
@@ -26,6 +26,7 @@
26
26
  "@vueuse/core": "^14.3.0",
27
27
  "@wangeditor/editor": "^5.1.23",
28
28
  "@wangeditor/editor-for-vue": "^5.1.12",
29
+ "asmcrypto.js": "2.3.2",
29
30
  "axios": "^1.7.2",
30
31
  "code-inspector-plugin": "^1.5.1",
31
32
  "dayjs": "^1.11.21",
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable */
2
2
  /* prettier-ignore */
3
+ /* oxlint-disable */
4
+ /* oxfmt-ignore */
3
5
  // @ts-nocheck
4
6
  // noinspection JSUnusedGlobalSymbols
5
7
  // Generated by unplugin-auto-import
package/vite.config.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
2
  import { existsSync, readFileSync } from "node:fs";
3
+ import type { ServerOptions } from "node:https";
3
4
  import { createLogger, defineConfig, loadEnv, type ConfigEnv, type Logger, type UserConfig } from "vite";
4
5
  import { dirname, resolve } from "node:path";
5
6
  import { fileURLToPath } from "node:url";
@@ -63,6 +64,8 @@ export function defineAdminViteConfig(options: AdminViteConfigOptions = {}) {
63
64
  pkg: packageInfo,
64
65
  lastBuildTime: dayjs().format("YYYY-MM-DD HH:mm:ss")
65
66
  };
67
+ const coreVariablesPath = resolve(coreSourceRoot, "styles/var.scss").replaceAll("\\", "/");
68
+ const httpsOptions = resolveHttpsOptions(viteEnv, root);
66
69
 
67
70
  return {
68
71
  base: viteEnv.VITE_PUBLIC_PATH,
@@ -75,7 +78,7 @@ export function defineAdminViteConfig(options: AdminViteConfigOptions = {}) {
75
78
  css: {
76
79
  preprocessorOptions: {
77
80
  scss: {
78
- additionalData: `@use "${resolve(coreSourceRoot, "styles/var.scss")}" as *;`
81
+ additionalData: `@use "${coreVariablesPath}" as *;`
79
82
  }
80
83
  }
81
84
  },
@@ -107,6 +110,7 @@ export function defineAdminViteConfig(options: AdminViteConfigOptions = {}) {
107
110
  port: viteEnv.VITE_PORT,
108
111
  open: viteEnv.VITE_OPEN,
109
112
  cors: true,
113
+ https: httpsOptions,
110
114
  proxy: createProxy(viteEnv.VITE_PROXY)
111
115
  },
112
116
  plugins: createVitePlugins(viteEnv, {
@@ -188,6 +192,24 @@ function readDayjsLocaleDependencies(sourceRoot: string): string[] {
188
192
  );
189
193
  }
190
194
 
195
+ /** 根据宿主环境变量加载 Vite HTTPS 证书。 */
196
+ function resolveHttpsOptions(viteEnv: ViteEnv, root: string): ServerOptions | undefined {
197
+ if (!viteEnv.VITE_HTTPS) return undefined;
198
+
199
+ const keyPath = resolve(root, viteEnv.VITE_HTTPS_KEY || "../../../../certs/dev-key.pem");
200
+ const certPath = resolve(root, viteEnv.VITE_HTTPS_CERT || "../../../../certs/dev-cert.pem");
201
+ if (!existsSync(keyPath) || !existsSync(certPath)) {
202
+ throw new Error(
203
+ `VITE_HTTPS 已开启,但未找到证书文件,请先在仓库根目录运行 scripts/generate-dev-cert.sh;期望路径:${keyPath} 和 ${certPath}`
204
+ );
205
+ }
206
+
207
+ return {
208
+ key: readFileSync(keyPath),
209
+ cert: readFileSync(certPath)
210
+ };
211
+ }
212
+
191
213
  /** 解析业务模块在当前宿主中的源码目录。 */
192
214
  function resolvePackageSource(root: string, packageName: string): Pick<AdminSourcePackage, "packageRoot" | "sourceRoot"> {
193
215
  const require = createRequire(resolve(root, "package.json"));