working
This commit is contained in:
parent
a385308f38
commit
9b0a20aa91
6 changed files with 275 additions and 69 deletions
35
.gitignore
vendored
35
.gitignore
vendored
|
|
@ -0,0 +1,35 @@
|
|||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
*.egg
|
||||
|
||||
# Виртуальное окружение
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
|
||||
# База данных и данные приложения
|
||||
captains_log.db
|
||||
*.db
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Nuitka
|
||||
*.build/
|
||||
*.dist/
|
||||
*.onefile-build/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
9
LICENSE
Normal file
9
LICENSE
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Vic
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
16
README.md
Normal file
16
README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Captain's Log
|
||||
|
||||
Приложение для быстрых заметок с шифрованием.
|
||||
|
||||
## Установка и запуск
|
||||
|
||||
1. Клонируйте репозиторий
|
||||
2. Создайте виртуальное окружение: `python -m venv .venv`
|
||||
3. Активируйте его: `.venv\Scripts\activate` (Windows)
|
||||
4. Установите зависимости: `pip install -r requirements.txt`
|
||||
5. Запустите: `python main.py`
|
||||
|
||||
## Первый запуск
|
||||
|
||||
При первом запуске приложение предложит создать мастер-пароль.
|
||||
База данных создается автоматически в папке приложения.
|
||||
22
app/auth.py
22
app/auth.py
|
|
@ -1,9 +1,8 @@
|
|||
"""Модуль аутентификации"""
|
||||
import hashlib
|
||||
import base64
|
||||
import os
|
||||
from app.database import Database
|
||||
|
||||
|
||||
class Auth:
|
||||
def __init__(self, db: Database):
|
||||
self.db = db
|
||||
|
|
@ -34,6 +33,23 @@ class Auth:
|
|||
"""Установить, спрашивать ли пароль при входе"""
|
||||
self.db.set_setting('ask_password_on_startup', '1' if value else '0')
|
||||
|
||||
def reset_password(self):
|
||||
"""Сбросить мастер-пароль"""
|
||||
self.db.set_setting('password_set', '0')
|
||||
self.db.set_setting('master_password_hash', '')
|
||||
self.db.set_setting('ask_password_on_startup', '1')
|
||||
|
||||
def change_password(self, old_password, new_password):
|
||||
"""Изменить мастер-пароль"""
|
||||
if self.is_password_set():
|
||||
if not self.check_password(old_password):
|
||||
return False, "Неверный текущий пароль"
|
||||
|
||||
self.set_master_password(new_password)
|
||||
return True, "Пароль успешно изменен"
|
||||
|
||||
def _hash_password(self, password):
|
||||
"""Хешировать пароль"""
|
||||
"""Хешировать пароль с солью"""
|
||||
# В будущем здесь будет более безопасное хеширование
|
||||
# Пока используем SHA-256 для простоты
|
||||
return hashlib.sha256(password.encode()).hexdigest()
|
||||
|
|
@ -3,18 +3,19 @@ import tkinter as tk
|
|||
from tkinter import ttk, messagebox
|
||||
from app.config import Config
|
||||
|
||||
|
||||
class PasswordDialog(tk.Toplevel):
|
||||
"""Диалог установки/ввода мастер-пароля"""
|
||||
|
||||
def __init__(self, parent, auth, mode='set'):
|
||||
super().__init__(parent)
|
||||
self.auth = auth
|
||||
self.mode = mode # 'set' или 'check'
|
||||
self.mode = mode # 'set', 'check', 'reset'
|
||||
self.result = False
|
||||
|
||||
self.title("Мастер-пароль" if mode == 'set' else "Введите пароль")
|
||||
self.geometry("400x250")
|
||||
self.title("Мастер-пароль" if mode == 'set' else
|
||||
"Введите пароль" if mode == 'check' else
|
||||
"Сброс пароля")
|
||||
self.geometry("400x300" if mode == 'reset' else "400x250")
|
||||
self.resizable(False, False)
|
||||
self.configure(bg=Config.COLORS['bg'])
|
||||
|
||||
|
|
@ -35,8 +36,10 @@ class PasswordDialog(tk.Toplevel):
|
|||
|
||||
if self.mode == 'set':
|
||||
text = "Придумайте мастер-пароль\nдля защиты ваших заметок:"
|
||||
else:
|
||||
elif self.mode == 'check':
|
||||
text = "Введите мастер-пароль:"
|
||||
else: # reset
|
||||
text = "ВНИМАНИЕ!\nЭто сбросит текущий пароль.\nВсе заметки останутся.\n\nДля сброса введите 'RESET':"
|
||||
|
||||
tk.Label(
|
||||
main_frame,
|
||||
|
|
@ -47,6 +50,38 @@ class PasswordDialog(tk.Toplevel):
|
|||
wraplength=350
|
||||
).pack(pady=(0, 15))
|
||||
|
||||
if self.mode == 'reset':
|
||||
# Поле для сброса
|
||||
self.reset_var = tk.StringVar()
|
||||
self.reset_entry = ttk.Entry(
|
||||
main_frame,
|
||||
textvariable=self.reset_var,
|
||||
font=('Arial', 12)
|
||||
)
|
||||
self.reset_entry.pack(fill='x', pady=(0, 15))
|
||||
self.reset_entry.focus()
|
||||
|
||||
tk.Button(
|
||||
main_frame,
|
||||
text="Сбросить пароль",
|
||||
command=self.reset_password,
|
||||
bg=Config.COLORS['danger'],
|
||||
fg=Config.COLORS['fg'],
|
||||
font=('Arial', 11, 'bold'),
|
||||
relief='flat',
|
||||
cursor='hand2'
|
||||
).pack(fill='x', pady=(0, 5))
|
||||
|
||||
tk.Button(
|
||||
main_frame,
|
||||
text="Отмена",
|
||||
command=self.destroy,
|
||||
bg=Config.COLORS['button_bg'],
|
||||
fg=Config.COLORS['fg'],
|
||||
relief='flat',
|
||||
cursor='hand2'
|
||||
).pack(fill='x')
|
||||
else:
|
||||
# Поле ввода пароля
|
||||
self.password_var = tk.StringVar()
|
||||
self.password_entry = ttk.Entry(
|
||||
|
|
@ -117,6 +152,18 @@ class PasswordDialog(tk.Toplevel):
|
|||
cursor='hand2'
|
||||
).pack(fill='x', pady=(0, 5))
|
||||
|
||||
# Кнопка сброса пароля
|
||||
tk.Button(
|
||||
button_frame,
|
||||
text="Забыли пароль?",
|
||||
command=self.show_reset_warning,
|
||||
bg=Config.COLORS['button_bg'],
|
||||
fg=Config.COLORS['fg'],
|
||||
relief='flat',
|
||||
cursor='hand2',
|
||||
font=('Arial', 9)
|
||||
).pack(fill='x', pady=(0, 5))
|
||||
|
||||
if self.mode == 'check':
|
||||
tk.Button(
|
||||
button_frame,
|
||||
|
|
@ -136,6 +183,10 @@ class PasswordDialog(tk.Toplevel):
|
|||
messagebox.showwarning("Ошибка", "Введите пароль")
|
||||
return
|
||||
|
||||
if len(password) < 4:
|
||||
messagebox.showwarning("Ошибка", "Пароль должен быть минимум 4 символа")
|
||||
return
|
||||
|
||||
if password != confirm:
|
||||
messagebox.showwarning("Ошибка", "Пароли не совпадают")
|
||||
return
|
||||
|
|
@ -156,6 +207,32 @@ class PasswordDialog(tk.Toplevel):
|
|||
self.password_var.set('')
|
||||
self.password_entry.focus()
|
||||
|
||||
def reset_password(self):
|
||||
if self.reset_var.get().upper() == 'RESET':
|
||||
self.auth.reset_password()
|
||||
messagebox.showinfo("Успех", "Пароль сброшен.\nПри следующем запуске создайте новый пароль.")
|
||||
self.result = True
|
||||
self.destroy()
|
||||
else:
|
||||
messagebox.showerror("Ошибка", "Введите RESET для подтверждения сброса")
|
||||
self.reset_var.set('')
|
||||
self.reset_entry.focus()
|
||||
|
||||
def show_reset_warning(self):
|
||||
"""Показать предупреждение о сбросе пароля"""
|
||||
if messagebox.askyesno(
|
||||
"Сброс пароля",
|
||||
"Вы уверены, что хотите сбросить пароль?\n\n"
|
||||
"Это действие удалит только пароль, все заметки останутся.\n"
|
||||
"После сброса вы сможете установить новый пароль."
|
||||
):
|
||||
self.destroy()
|
||||
reset_dialog = PasswordDialog(self.master, self.auth, mode='reset')
|
||||
self.master.wait_window(reset_dialog)
|
||||
if reset_dialog.result:
|
||||
self.result = True
|
||||
self.destroy()
|
||||
|
||||
|
||||
class ConfirmDialog(tk.Toplevel):
|
||||
"""Диалог подтверждения действия"""
|
||||
|
|
|
|||
53
main.py
53
main.py
|
|
@ -11,10 +11,63 @@ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|||
|
||||
from app.ui.main_window import MainWindow
|
||||
|
||||
|
||||
def create_gitignore():
|
||||
"""Создать .gitignore если его нет"""
|
||||
gitignore_path = os.path.join(os.path.dirname(__file__), '.gitignore')
|
||||
|
||||
if not os.path.exists(gitignore_path):
|
||||
gitignore_content = """# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
*.egg
|
||||
|
||||
# Виртуальное окружение
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
|
||||
# База данных и данные приложения
|
||||
captains_log.db
|
||||
*.db
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Nuitka
|
||||
*.build/
|
||||
*.dist/
|
||||
*.onefile-build/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
"""
|
||||
try:
|
||||
with open(gitignore_path, 'w') as f:
|
||||
f.write(gitignore_content)
|
||||
except:
|
||||
pass # Игнорируем ошибки при создании .gitignore
|
||||
|
||||
|
||||
def main():
|
||||
"""Точка входа в приложение"""
|
||||
# Создаем .gitignore при первом запуске
|
||||
create_gitignore()
|
||||
|
||||
app = MainWindow()
|
||||
app.mainloop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Loading…
Add table
Add a link
Reference in a new issue