Procházet zdrojové kódy

feat:密码加密传输

韩洋 před 1 měsícem
rodič
revize
2cfb5155d1

+ 2 - 1
classic/src/components/auth/LoginForm.jsx

@@ -23,6 +23,7 @@ import { UserContext } from '../../context/User';
 import { StatusContext } from '../../context/Status';
 import {
   API,
+  encodeToBase64,
   getLogo,
   showError,
   showInfo,
@@ -240,7 +241,7 @@ const LoginForm = () => {
           `/api/user/login?turnstile=${turnstileToken}`,
           {
             username,
-            password,
+            password: encodeToBase64(password),
             tenant_unique_id: getTenantUniqueIdFromPath(),
           },
         );

+ 6 - 1
classic/src/components/auth/RegisterForm.jsx

@@ -21,6 +21,7 @@ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
 import { Link, useNavigate } from 'react-router-dom';
 import {
   API,
+  encodeToBase64,
   getLogo,
   showError,
   showInfo,
@@ -237,7 +238,11 @@ const RegisterForm = () => {
         inputs.aff_code = affCode;
         const res = await API.post(
           `/api/user/register?turnstile=${turnstileToken}`,
-          inputs,
+          {
+            ...inputs,
+            password: encodeToBase64(inputs.password),
+            password2: encodeToBase64(inputs.password2),
+          },
         );
         const { success, message } = res.data;
         if (success) {

+ 3 - 2
classic/src/components/settings/PersonalSetting.jsx

@@ -22,6 +22,7 @@ import { useNavigate } from 'react-router-dom';
 import {
   API,
   copy,
+  encodeToBase64,
   showError,
   showInfo,
   showSuccess,
@@ -429,8 +430,8 @@ const PersonalSetting = () => {
       return;
     }
     const res = await API.put(`/api/user/self`, {
-      original_password: inputs.original_password,
-      password: inputs.set_new_password,
+      original_password: encodeToBase64(inputs.original_password),
+      password: encodeToBase64(inputs.set_new_password),
     });
     const { success, message } = res.data;
     if (success) {

+ 5 - 1
classic/src/components/setup/SetupWizard.jsx

@@ -19,7 +19,7 @@ For commercial licensing, please contact support@quantumnous.com
 
 import React, { useEffect, useState, useRef } from 'react';
 import { Card, Divider, Steps, Form } from '@douyinfe/semi-ui';
-import { API, showError, showNotice } from '../../helpers';
+import { API, encodeToBase64, showError, showNotice } from '../../helpers';
 import { useTranslation } from 'react-i18next';
 
 import StepNavigation from './components/StepNavigation';
@@ -198,6 +198,10 @@ const SetupWizard = () => {
 
     // Remove usageMode as it's not needed by the backend
     delete formValues.usageMode;
+    if (!setupStatus.root_init) {
+      formValues.password = encodeToBase64(formValues.password);
+      formValues.confirmPassword = encodeToBase64(formValues.confirmPassword);
+    }
 
     // 提交表单至后端
     setLoading(true);

+ 12 - 2
classic/src/components/table/users/modals/AddUserModal.jsx

@@ -18,7 +18,12 @@ For commercial licensing, please contact support@quantumnous.com
 */
 
 import React, { useState, useRef } from 'react';
-import { API, showError, showSuccess } from '../../../../helpers';
+import {
+  API,
+  encodeToBase64,
+  showError,
+  showSuccess,
+} from '../../../../helpers';
 import { useIsMobile } from '../../../../hooks/common/useIsMobile';
 import {
   Button,
@@ -53,7 +58,12 @@ const AddUserModal = (props) => {
 
   const submit = async (values) => {
     setLoading(true);
-    const res = await API.post(`/api/user/`, values);
+    const res = await API.post(`/api/user/`, {
+      ...values,
+      password: values.password
+        ? encodeToBase64(values.password)
+        : values.password,
+    });
     const { success, message } = res.data;
     if (success) {
       showSuccess(t('用户账户创建成功!'));

+ 4 - 0
classic/src/components/table/users/modals/EditUserModal.jsx

@@ -21,6 +21,7 @@ import React, { useEffect, useState, useRef } from 'react';
 import { useTranslation } from 'react-i18next';
 import {
   API,
+  encodeToBase64,
   showError,
   showSuccess,
   renderQuota,
@@ -153,6 +154,9 @@ const EditUserModal = (props) => {
     if (userId) {
       payload.id = parseInt(userId);
     }
+    if (payload.password) {
+      payload.password = encodeToBase64(payload.password);
+    }
     const url = userId ? `/api/user/` : `/api/user/self`;
     const res = await API.put(url, payload);
     const { success, message } = res.data;

+ 12 - 4
default/src/features/auth/api.ts

@@ -17,6 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact support@quantumnous.com
 */
 import { api } from '@/lib/api'
+import { encodeToBase64 } from '@/lib/base64'
 
 import type {
   LoginPayload,
@@ -42,7 +43,7 @@ export async function login(payload: LoginPayload) {
     `/api/user/login?turnstile=${turnstile}`,
     {
       username: payload.username,
-      password: payload.password,
+      password: encodeToBase64(payload.password),
       tenant_unique_id: payload.tenant_unique_id ?? '',
     }
   )
@@ -107,9 +108,16 @@ export async function wechatLoginByCode(code: string): Promise<ApiResponse> {
 
 // User registration
 export async function register(payload: RegisterPayload): Promise<ApiResponse> {
-  const res = await api.post(`/api/user/register`, payload, {
-    params: { turnstile: payload.turnstile ?? '' },
-  })
+  const res = await api.post(
+    `/api/user/register`,
+    {
+      ...payload,
+      password: encodeToBase64(payload.password),
+    },
+    {
+      params: { turnstile: payload.turnstile ?? '' },
+    }
+  )
   return res.data
 }
 

+ 8 - 1
default/src/features/profile/api.ts

@@ -17,6 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact support@quantumnous.com
 */
 import { api } from '@/lib/api'
+import { encodeToBase64 } from '@/lib/base64'
 
 import type {
   ApiResponse,
@@ -46,7 +47,13 @@ export async function getUserProfile(): Promise<ApiResponse<UserProfile>> {
 export async function updateUserProfile(
   data: UpdateUserRequest
 ): Promise<ApiResponse> {
-  const res = await api.put('/api/user/self', data)
+  const res = await api.put('/api/user/self', {
+    ...data,
+    password: data.password ? encodeToBase64(data.password) : data.password,
+    original_password: data.original_password
+      ? encodeToBase64(data.original_password)
+      : data.original_password,
+  })
   return res.data
 }
 

+ 3 - 0
default/src/features/setup/api.ts

@@ -17,6 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
 For commercial licensing, please contact support@quantumnous.com
 */
 import { api } from '@/lib/api'
+import { encodeToBase64 } from '@/lib/base64'
 
 import type { SetupFormValues, SetupResponse } from './types'
 
@@ -54,6 +55,8 @@ export function buildSetupPayload(
 
   return {
     ...rest,
+    password: encodeToBase64(rest.password),
+    confirmPassword: encodeToBase64(rest.confirmPassword),
     ...basePayload,
   }
 }

+ 9 - 2
default/src/features/users/api.ts

@@ -18,6 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
 */
 import type { PermissionCatalog } from '@/lib/admin-permissions'
 import { api } from '@/lib/api'
+import { encodeToBase64 } from '@/lib/base64'
 
 import type {
   User,
@@ -84,7 +85,10 @@ export async function getUser(id: number): Promise<ApiResponse<User>> {
 export async function createUser(
   data: UserFormData
 ): Promise<ApiResponse<User>> {
-  const res = await api.post('/api/user/', data)
+  const res = await api.post('/api/user/', {
+    ...data,
+    password: data.password ? encodeToBase64(data.password) : data.password,
+  })
   return res.data
 }
 
@@ -94,7 +98,10 @@ export async function createUser(
 export async function updateUser(
   data: UserFormData & { id: number }
 ): Promise<ApiResponse<Partial<User>>> {
-  const res = await api.put('/api/user/', data)
+  const res = await api.put('/api/user/', {
+    ...data,
+    password: data.password ? encodeToBase64(data.password) : data.password,
+  })
   return res.data
 }
 

+ 57 - 0
default/src/lib/base64.ts

@@ -0,0 +1,57 @@
+/*
+Copyright (C) 2023-2026 QuantumNous
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU Affero General Public License for more details.
+
+You should have received a copy of the GNU Affero General Public License
+along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+For commercial licensing, please contact support@quantumnous.com
+*/
+
+type NodeBufferCtor = {
+  from(input: string, encoding: string): { toString(encoding: string): string }
+}
+
+const toBinaryString = (text: string) => {
+  if (typeof TextEncoder !== 'undefined') {
+    const bytes = new TextEncoder().encode(text)
+    let binary = ''
+
+    bytes.forEach((byte) => {
+      binary += String.fromCharCode(byte)
+    })
+
+    return binary
+  }
+
+  return encodeURIComponent(text).replace(/%([0-9A-F]{2})/g, (_, hex) =>
+    String.fromCharCode(parseInt(hex, 16))
+  )
+}
+
+export const encodeToBase64 = (value: string) => {
+  if (typeof window === 'undefined') {
+    const globalRef = globalThis as typeof globalThis & {
+      Buffer?: NodeBufferCtor
+    }
+
+    if (globalRef.Buffer) {
+      return globalRef.Buffer.from(value, 'utf-8').toString('base64')
+    }
+    if (typeof globalRef.btoa === 'function') {
+      return globalRef.btoa(toBinaryString(value))
+    }
+    throw new Error('Base64 encoding is unavailable in the current environment')
+  }
+
+  return window.btoa(toBinaryString(value))
+}