feat: add computer name-based silent login (无感登录)
Implement automatic authentication based on computer name to enable passwordless login for registered computers with fallback to manual authentication. Changes: - Add SessionManager.login_by_computer_name() for silent login attempt - Add BIPUsersDAO.authenticate_by_computer_name() for computer name lookup - Update BIPUsersDAO.create_user() to support optional computer_name parameter - Update main_ui.py to try silent login before showing login dialog - Display current computer name in login dialog for user reference Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -63,6 +63,28 @@ class SessionManager:
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def login_by_computer_name(self) -> bool:
|
||||||
|
"""
|
||||||
|
Attempt silent login using computer name
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
True if login successful, False otherwise
|
||||||
|
"""
|
||||||
|
import socket
|
||||||
|
from db.bip_users_dao import BIPUsersDAO
|
||||||
|
|
||||||
|
computer_name = socket.gethostname()
|
||||||
|
dao = BIPUsersDAO()
|
||||||
|
user_info = dao.authenticate_by_computer_name(computer_name)
|
||||||
|
|
||||||
|
if user_info:
|
||||||
|
self._current_user = {
|
||||||
|
'username': user_info['username'],
|
||||||
|
'user_type': user_info['user_type']
|
||||||
|
}
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def logout(self):
|
def logout(self):
|
||||||
"""Logout the current user and clear session"""
|
"""Logout the current user and clear session"""
|
||||||
self._current_user = None
|
self._current_user = None
|
||||||
|
|||||||
@@ -49,6 +49,44 @@ class BIPUsersDAO(BaseDAO):
|
|||||||
}
|
}
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def authenticate_by_computer_name(self, computer_name: str) -> Optional[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Authenticate a user using computer name (silent login)
|
||||||
|
|
||||||
|
Args:
|
||||||
|
computer_name: The computer name to authenticate
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Dict with user info if authentication successful, None otherwise
|
||||||
|
Returns: {id, username, user_type}
|
||||||
|
"""
|
||||||
|
table_name = self._convert_sql('[dbo].[BIPUsers]')
|
||||||
|
placeholder = self._get_placeholder()
|
||||||
|
|
||||||
|
# Note: Column name is 'ComputerNmae' (typo in database schema)
|
||||||
|
if self._db_type == DatabaseType.MYSQL:
|
||||||
|
sql = f"""
|
||||||
|
SELECT ID, UserName, UserType
|
||||||
|
FROM {table_name}
|
||||||
|
WHERE ComputerNmae = {placeholder}
|
||||||
|
"""
|
||||||
|
else:
|
||||||
|
sql = f"""
|
||||||
|
SELECT [ID], [UserName], [UserType]
|
||||||
|
FROM {table_name}
|
||||||
|
WHERE [ComputerNmae] = {placeholder}
|
||||||
|
"""
|
||||||
|
|
||||||
|
with get_connection() as db:
|
||||||
|
results = db.execute_query(sql, (computer_name,))
|
||||||
|
if results:
|
||||||
|
return {
|
||||||
|
'id': results[0]['ID'],
|
||||||
|
'username': results[0]['UserName'],
|
||||||
|
'user_type': results[0]['UserType']
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
def get_all_users(self) -> List[Dict[str, Any]]:
|
def get_all_users(self) -> List[Dict[str, Any]]:
|
||||||
"""
|
"""
|
||||||
Get all users from the database
|
Get all users from the database
|
||||||
@@ -84,14 +122,15 @@ class BIPUsersDAO(BaseDAO):
|
|||||||
for row in results
|
for row in results
|
||||||
]
|
]
|
||||||
|
|
||||||
def create_user(self, username: str, password: str, user_type: str) -> bool:
|
def create_user(self, username: str, password: str, user_type: str, computer_name: str = '') -> bool:
|
||||||
"""
|
"""
|
||||||
Create a new user
|
Create a new user with optional computer name for silent login
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
username: The username (must be unique)
|
username: The username (must be unique)
|
||||||
password: The password (plain text for internal tool)
|
password: The password (plain text for internal tool)
|
||||||
user_type: User type ('Admin', 'User', or 'Guest')
|
user_type: User type ('Admin', 'User', or 'Guest')
|
||||||
|
computer_name: Optional computer name for silent login (default: '')
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if successful, False otherwise
|
True if successful, False otherwise
|
||||||
@@ -101,19 +140,35 @@ class BIPUsersDAO(BaseDAO):
|
|||||||
|
|
||||||
# 根据数据库类型选择列名格式
|
# 根据数据库类型选择列名格式
|
||||||
if self._db_type == DatabaseType.MYSQL:
|
if self._db_type == DatabaseType.MYSQL:
|
||||||
|
if computer_name:
|
||||||
|
sql = f"""
|
||||||
|
INSERT INTO {table_name} (UserName, Password, UserType, ComputerNmae)
|
||||||
|
VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder})
|
||||||
|
"""
|
||||||
|
params = (username, password, user_type, computer_name)
|
||||||
|
else:
|
||||||
sql = f"""
|
sql = f"""
|
||||||
INSERT INTO {table_name} (UserName, Password, UserType)
|
INSERT INTO {table_name} (UserName, Password, UserType)
|
||||||
VALUES ({placeholder}, {placeholder}, {placeholder})
|
VALUES ({placeholder}, {placeholder}, {placeholder})
|
||||||
"""
|
"""
|
||||||
|
params = (username, password, user_type)
|
||||||
|
else:
|
||||||
|
if computer_name:
|
||||||
|
sql = f"""
|
||||||
|
INSERT INTO {table_name} ([UserName], [Password], [UserType], [ComputerNmae])
|
||||||
|
VALUES ({placeholder}, {placeholder}, {placeholder}, {placeholder})
|
||||||
|
"""
|
||||||
|
params = (username, password, user_type, computer_name)
|
||||||
else:
|
else:
|
||||||
sql = f"""
|
sql = f"""
|
||||||
INSERT INTO {table_name} ([UserName], [Password], [UserType])
|
INSERT INTO {table_name} ([UserName], [Password], [UserType])
|
||||||
VALUES ({placeholder}, {placeholder}, {placeholder})
|
VALUES ({placeholder}, {placeholder}, {placeholder})
|
||||||
"""
|
"""
|
||||||
|
params = (username, password, user_type)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with get_connection() as db:
|
with get_connection() as db:
|
||||||
db.execute_update(sql, (username, password, user_type))
|
db.execute_update(sql, params)
|
||||||
return True
|
return True
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error creating user: {e}")
|
print(f"Error creating user: {e}")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
"""
|
"""
|
||||||
Login Dialog - Modal dialog for user authentication
|
Login Dialog - Modal dialog for user authentication
|
||||||
"""
|
"""
|
||||||
|
import socket
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import ttk, messagebox
|
from tkinter import ttk, messagebox
|
||||||
from typing import Optional, Tuple
|
from typing import Optional, Tuple
|
||||||
@@ -37,7 +38,7 @@ class LoginDialog:
|
|||||||
"""Create the login dialog UI"""
|
"""Create the login dialog UI"""
|
||||||
self.dialog = tk.Toplevel(self.parent)
|
self.dialog = tk.Toplevel(self.parent)
|
||||||
self.dialog.title("ERP 自动化工具 - 登录")
|
self.dialog.title("ERP 自动化工具 - 登录")
|
||||||
self.dialog.geometry("400x250")
|
self.dialog.geometry("400x280")
|
||||||
self.dialog.resizable(False, False)
|
self.dialog.resizable(False, False)
|
||||||
|
|
||||||
# Center the dialog on parent
|
# Center the dialog on parent
|
||||||
@@ -52,7 +53,7 @@ class LoginDialog:
|
|||||||
parent_height = self.parent.winfo_height()
|
parent_height = self.parent.winfo_height()
|
||||||
|
|
||||||
dialog_width = 400
|
dialog_width = 400
|
||||||
dialog_height = 250
|
dialog_height = 280
|
||||||
x = parent_x + (parent_width - dialog_width) // 2
|
x = parent_x + (parent_width - dialog_width) // 2
|
||||||
y = parent_y + (parent_height - dialog_height) // 2
|
y = parent_y + (parent_height - dialog_height) // 2
|
||||||
self.dialog.geometry(f"{dialog_width}x{dialog_height}+{x}+{y}")
|
self.dialog.geometry(f"{dialog_width}x{dialog_height}+{x}+{y}")
|
||||||
@@ -78,7 +79,16 @@ class LoginDialog:
|
|||||||
text="请登录",
|
text="请登录",
|
||||||
font=('', 16, 'bold')
|
font=('', 16, 'bold')
|
||||||
)
|
)
|
||||||
title_label.pack(pady=(0, 20))
|
title_label.pack(pady=(0, 10))
|
||||||
|
|
||||||
|
# Computer name display
|
||||||
|
computer_name_label = ttk.Label(
|
||||||
|
main_frame,
|
||||||
|
text=f"当前计算机: {socket.gethostname()}",
|
||||||
|
font=('', 9),
|
||||||
|
foreground='gray'
|
||||||
|
)
|
||||||
|
computer_name_label.pack(pady=(0, 15))
|
||||||
|
|
||||||
# Username field
|
# Username field
|
||||||
username_frame = ttk.Frame(main_frame)
|
username_frame = ttk.Frame(main_frame)
|
||||||
|
|||||||
Reference in New Issue
Block a user