feat: 로그인 추가
All checks were successful
CI Build / build (push) Successful in 4s

This commit is contained in:
Ayuriel 2025-03-31 11:32:02 +09:00
parent c01ea3208a
commit c9d4ef99af
6 changed files with 226 additions and 22 deletions

View File

@ -90,6 +90,23 @@ Streamlit 앱 실행:
streamlit run src/app.py --server.port=20000
```
### 인증 설정
애플리케이션은 streamlit-authenticator를 사용하여 로그인 기능을 제공합니다. 기본 계정은 다음과 같습니다:
- 관리자: 아이디 `admin`, 비밀번호 `adminpass`
- 일반 사용자: 아이디 `user`, 비밀번호 `userpass`
비밀번호 변경 또는 계정 추가를 위한 steps:
1. `src/config/generate_credentials.py` 파일을 수정하여 원하는 계정 정보 입력
2. 아래 명령어 실행:
```
cd src/config
python generate_credentials.py
```
3. 생성된 `credentials.yaml` 파일을 확인하여 적용
## 모듈
### 데이터 수집

7
requirements.txt Normal file
View File

@ -0,0 +1,7 @@
streamlit
pandas
pymysql
sqlalchemy
python-dotenv
PyYAML
streamlit-authenticator>=0.2.2

View File

@ -1,7 +1,11 @@
"""
Main Streamlit application for the Quant Manager.
"""
import os
import yaml
import streamlit as st
import streamlit_authenticator as stauth
from yaml.loader import SafeLoader
from ui.pages.data_page import render_data_page
from ui.pages.multi_factor_page import render_multi_factor_page
from ui.pages.quality_page import render_quality_page
@ -18,8 +22,45 @@ st.set_page_config(
# Define the sidebar navigation
def main():
"""Main application function."""
# Load authentication configuration
config_path = os.path.join(os.path.dirname(__file__), 'config', 'credentials.yaml')
if os.path.exists(config_path):
with open(config_path) as file:
config = yaml.load(file, Loader=SafeLoader)
# Create authenticator object
authenticator = stauth.Authenticate(
config['credentials'],
config['cookie']['name'],
config['cookie']['key'],
config['cookie']['expiry_days'],
)
# Initialize variables
name = None
# Create login widget with error handling
try:
authenticator.login()
except Exception as e:
st.error(f"로그인 처리 중 오류가 발생했습니다: {str(e)}")
st.info("authenticator.login() 호출 문제가 발생했습니다. 구성 파일을 확인하세요.")
# Handle authentication status
if st.session_state['authentication_status'] == False:
st.error('아이디 또는 비밀번호가 올바르지 않습니다.')
if st.session_state['authentication_status'] is None:
st.warning('로그인이 필요합니다.')
# Display application if authenticated
if st.session_state['authentication_status']:
# Create sidebar navigation
st.sidebar.title("콴트 매니저")
st.sidebar.title(f"콴트 매니저 - {st.session_state["name"]}")
# Add logout button
authenticator.logout('로그아웃', 'sidebar')
# Navigation options
pages = {
@ -41,6 +82,9 @@ def main():
"© 2023-2025 콴트 매니저\n\n"
"한국 주식 시장을 위한 퀀트 투자 도구"
)
else:
st.error("인증 설정 파일을 찾을 수 없습니다. config/credentials.yaml 파일을 확인하세요.")
st.info(f"찾을 파일 경로: {config_path}")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,10 @@
cookie:
expiry_days: 30
key: 37A93A1A98AC9E896CBB21DD1F84FABC
name: quant_manager_auth
credentials:
usernames:
zephyrdark:
email: zephyrdark@gamil.com
name: ayuriel
password: $2b$12$nqVU.Mw8lErQU59TxuGWLebfImar1O9zirjpc.waN/3LNYzc2o4.6

View File

@ -0,0 +1,51 @@
"""
Generate a credentials.yaml file with hashed passwords for streamlit-authenticator.
This script should be run once to set up authentication credentials.
"""
import yaml
import streamlit_authenticator as stauth
from yaml.loader import SafeLoader
# Create credentials
usernames = ["zephyrdark"]
names = ["ayuriel"]
passwords = [""] # Replace with secure passwords
# Hash passwords
try:
# For newer versions of streamlit-authenticator
hashed_passwords = [stauth.Hasher([passwords[0]]).generate()[0]]
except:
# For older versions of streamlit-authenticator
hashed_passwords = [stauth.Hasher().hash(passwords[0])]
# Create config dictionary
config = {
'credentials': {
'usernames': {}
},
'cookie': {
'name': 'quant_manager_auth',
'key': '37A93A1A98AC9E896CBB21DD1F84FABC', # Key should be a string that is 32 bytes long
'expiry_days': 30
},
'preauthorized': {
'emails': []
}
}
# Add user credentials
for i, username in enumerate(usernames):
config['credentials']['usernames'][username] = {
'name': names[i],
'password': hashed_passwords[i],
'email': f"{username}@gamil.com" # Replace with actual emails if needed
}
# Write to YAML file
with open('credentials.yaml', 'w') as file:
yaml.dump(config, file, default_flow_style=False)
print("Credentials file has been generated successfully!")
print("Update the usernames, passwords, and other information as needed.")
print("Then move the credentials.yaml file to the config directory.")

View File

@ -0,0 +1,75 @@
"""
Update password for an existing user in credentials.yaml.
"""
import os
import yaml
import getpass
import streamlit_authenticator as stauth
from yaml.loader import SafeLoader
def main():
# Get the path to credentials.yaml
script_dir = os.path.dirname(os.path.abspath(__file__))
config_path = os.path.join(script_dir, 'credentials.yaml')
# Check if credentials file exists
if not os.path.exists(config_path):
print(f"Error: {config_path} not found.")
return
# Load the credentials file
with open(config_path, 'r') as file:
config = yaml.load(file, Loader=SafeLoader)
# Get all usernames
usernames = list(config['credentials']['usernames'].keys())
if not usernames:
print("No users found in credentials file.")
return
# Print available users
print("Available users:")
for i, username in enumerate(usernames, 1):
print(f"{i}. {username}")
try:
# Get user selection
choice = int(input("\nSelect user number to update password: "))
if choice < 1 or choice > len(usernames):
print("Invalid selection.")
return
selected_username = usernames[choice - 1]
# Get new password
new_password = getpass.getpass("Enter new password: ")
confirm_password = getpass.getpass("Confirm new password: ")
if new_password != confirm_password:
print("Passwords do not match.")
return
if not new_password:
print("Password cannot be empty.")
return
# Hash the new password
hashed_password = stauth.Hasher([new_password]).generate()[0]
# Update the password
config['credentials']['usernames'][selected_username]['password'] = hashed_password
# Save the updated config
with open(config_path, 'w') as file:
yaml.dump(config, file, default_flow_style=False)
print(f"Password updated successfully for {selected_username}.")
except (ValueError, IndexError):
print("Invalid input.")
except Exception as e:
print(f"An error occurred: {e}")
if __name__ == "__main__":
main()