This commit is contained in:
Vic Sergeev 2026-07-04 16:14:16 +03:00
parent a385308f38
commit 9b0a20aa91
6 changed files with 275 additions and 69 deletions

35
.gitignore vendored
View file

@ -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
View 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
View 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`
## Первый запуск
При первом запуске приложение предложит создать мастер-пароль.
База данных создается автоматически в папке приложения.

View file

@ -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()

View file

@ -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
View file

@ -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()