Skip to content

Commit 2a934ce

Browse files
Merge pull request #6 from RIKEN-RCCS/feafure/estimated
modified otp things
2 parents 7d26f85 + 4c6b515 commit 2a934ce

4 files changed

Lines changed: 180 additions & 11 deletions

File tree

‎result_server/app.py‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
# Import Flask and route blueprints
1414
from flask import Flask, render_template, current_app
15+
from flask_session import Session
1516
from routes.receive import receive_bp
1617
from routes.results import results_bp
1718
from routes.upload_tgz import upload_bp
@@ -43,11 +44,38 @@ def create_app(prefix="", base_dir=None):
4344

4445
# --- セッションCookieのセキュリティ設定 ---
4546
app.config.update(
47+
SESSION_TYPE='filesystem',
48+
SESSION_FILE_DIR=os.path.join(base_dir, "flask_session"),
49+
SESSION_PERMANENT=True,
50+
SESSION_USE_SIGNER=True,
4651
SESSION_COOKIE_SECURE=True, # HTTPS必須
4752
SESSION_COOKIE_HTTPONLY=True, # JSからのアクセス禁止
4853
SESSION_COOKIE_SAMESITE="Strict", # もしくは "Lax"
4954
PERMANENT_SESSION_LIFETIME=timedelta(minutes=30), # セッション寿命を短めに
5055
)
56+
Session(app)
57+
58+
59+
# Redis 接続
60+
import redis
61+
redis_url = os.environ.get("REDIS_URL", "redis://localhost:6379/0")
62+
r_conn = redis.from_url(redis_url, decode_responses=True)
63+
64+
# 環境ごとの prefix
65+
if prefix == "/dev":
66+
app.config["SESSION_COOKIE_NAME"] = "session_dev"
67+
key_prefix = "dev:"
68+
else:
69+
app.config["SESSION_COOKIE_NAME"] = "session_main"
70+
key_prefix = "main:"
71+
72+
# OTP Manager 初期化
73+
import utils.otp_redis_manager as otp_redis_manager
74+
otp_redis_manager.init_redis(r_conn, key_prefix)
75+
76+
# 他でもredisを使う場合、
77+
#app.redis = r_conn
78+
#app.redis_prefix = key_prefix
5179

5280

5381

‎result_server/routes/results.py‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
redirect, url_for, flash, abort, send_from_directory
55
)
66
from utils.results_loader import load_results_table, load_estimated_results_table
7-
from utils.otp_manager import send_otp, verify_otp, get_affiliations
7+
#from utils.otp_manager import get_affiliations
8+
from utils.otp_redis_manager import send_otp, verify_otp, invalidate_otp, is_allowed, get_affiliations
89
from utils.result_file import load_result_file, get_file_confidential_tags
910

1011
results_bp = Blueprint("results", __name__)
@@ -51,30 +52,29 @@ def handle_otp_post(session_key_authenticated, session_key_email, route_name):
5152

5253
if email and not otp:
5354
success, msg = send_otp(email)
54-
if success:
55-
flash("OTPをメールに送信しました")
55+
flash(msg)
56+
if success and is_allowed(email):
57+
session.clear()
5658
session[session_key_email] = email
5759
session["otp_stage"] = "otp"
5860
else:
59-
flash(msg)
60-
session.pop(session_key_email, None)
61+
session.clear()
6162
session["otp_stage"] = "email"
6263
return redirect(url_for(route_name))
6364

6465
elif otp:
6566
otp_email = session.get(session_key_email)
6667
if otp_email and verify_otp(otp_email, otp):
68+
session.clear()
6769
session[session_key_authenticated] = True
6870
flash("認証成功")
69-
session.pop("otp_stage", None)
7071
else:
71-
flash("OTP認証失敗")
72-
session.pop(session_key_email, None)
73-
session.pop(session_key_authenticated, None)
74-
session["otp_stage"] = "email"
72+
session.clear()
73+
flash("認証失敗")
7574
return redirect(url_for(route_name))
7675

7776

77+
7878
def render_confidential_table(template_name, public_only, session_key_authenticated, session_key_email, loader_func=None):
7979
authenticated = session.get(session_key_authenticated, False)
8080
otp_email = session.get(session_key_email)
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
import redis
2+
import random
3+
import string
4+
import time
5+
from typing import List, Tuple
6+
from datetime import datetime, timedelta
7+
from email.mime.text import MIMEText
8+
import smtplib
9+
import json
10+
import os
11+
12+
# -------------------------------
13+
# Redis 接続 (初期化は後で)
14+
# -------------------------------
15+
r = None
16+
prefix = "" # app から渡す
17+
18+
def init_redis(redis_conn, key_prefix: str = ""):
19+
global r, prefix
20+
r = redis_conn
21+
prefix = key_prefix
22+
23+
# -------------------------------
24+
# メール許可・所属
25+
# -------------------------------
26+
with open("config/allowed_emails.json", encoding="utf-8") as f:
27+
_ALLOWED = json.load(f)
28+
29+
def is_allowed(email: str) -> bool:
30+
return email in _ALLOWED
31+
32+
def get_affiliations(email: str) -> List[str]:
33+
if not is_allowed(email):
34+
return []
35+
aff = _ALLOWED[email]
36+
if isinstance(aff, list):
37+
return [str(a).strip() for a in aff if str(a).strip()]
38+
if isinstance(aff, str):
39+
return [a.strip() for a in aff.split(",") if a.strip()]
40+
return []
41+
42+
# -------------------------------
43+
# Gmail SMTP 設定
44+
# -------------------------------
45+
SMTP_SERVER = os.environ.get("SMTP_SERVER", "smtp.gmail.com")
46+
SMTP_PORT = int(os.environ.get("SMTP_PORT", 587))
47+
SMTP_USER = os.environ.get("SMTP_USER")
48+
SMTP_PASS = os.environ.get("SMTP_PASS")
49+
50+
# -------------------------------
51+
# OTP 設定
52+
# -------------------------------
53+
OTP_TTL_SECONDS = 5 * 60
54+
MAX_OTP_ATTEMPTS = 5
55+
56+
def generate_otp_code(length=6) -> str:
57+
return ''.join(random.choices(string.digits, k=length))
58+
59+
def send_otp(email: str) -> Tuple[bool, str]:
60+
"""OTP を生成して Redis に保存してメール送信"""
61+
public_message = "メールを確認してください。"
62+
if not is_allowed(email):
63+
return True, public_message
64+
65+
code = generate_otp_code()
66+
now = int(time.time())
67+
key = f"{prefix}:otp:{email}"
68+
69+
r.hmset(key, {
70+
"code": code,
71+
"expires_at": now + OTP_TTL_SECONDS,
72+
"attempts_left": MAX_OTP_ATTEMPTS,
73+
})
74+
r.expire(key, OTP_TTL_SECONDS)
75+
76+
# メール送信
77+
msg = MIMEText(
78+
f"あなたの認証コード (OTP) は {code} です。\n"
79+
f"有効期限は {datetime.fromtimestamp(now + OTP_TTL_SECONDS).strftime('%H:%M:%S')} までです。"
80+
)
81+
msg["Subject"] = "OTP 認証コード"
82+
msg["From"] = SMTP_USER
83+
msg["To"] = email
84+
85+
try:
86+
with smtplib.SMTP(SMTP_SERVER, SMTP_PORT) as server:
87+
server.starttls()
88+
server.login(SMTP_USER, SMTP_PASS)
89+
server.sendmail(SMTP_USER, [email], msg.as_string())
90+
except Exception as e:
91+
return False, f"メール送信失敗: {e}"
92+
93+
return True, public_message
94+
95+
def verify_otp(email: str, code: str) -> bool:
96+
key = f"{prefix}:otp:{email}"
97+
data = r.hgetall(key)
98+
now = int(time.time())
99+
if not data:
100+
return False
101+
102+
if now > int(data.get("expires_at", 0)):
103+
r.delete(key)
104+
return False
105+
106+
attempts_left = int(data.get("attempts_left", MAX_OTP_ATTEMPTS))
107+
if attempts_left <= 0:
108+
r.delete(key)
109+
return False
110+
111+
if code == data.get("code"):
112+
r.delete(key)
113+
return True
114+
else:
115+
attempts_left -= 1
116+
if attempts_left <= 0:
117+
r.delete(key)
118+
else:
119+
r.hset(key, "attempts_left", attempts_left)
120+
return False
121+
122+
def invalidate_otp(email: str):
123+
r.delete(f"{prefix}:otp:{email}")
124+

‎scripts/matrix_generate.sh‎

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,19 @@ OUTPUT_FILE=".gitlab-ci.generated.yml"
77

88
source ./scripts/job_functions.sh
99

10+
CODE_FILTER=""
11+
SYSTEM_FILTER=""
12+
13+
while [[ $# -gt 0 ]]; do
14+
case $1 in
15+
code=*) CODE_FILTER="${1#code=}" ;;
16+
system=*) SYSTEM_FILTER="${1#system=}" ;;
17+
*) echo "Unknown argument: $1"; exit 1 ;;
18+
esac
19+
shift
20+
done
21+
22+
1023
echo "# Auto-generated GitLab CI configuration" > "$OUTPUT_FILE"
1124
echo "
1225
stages:
@@ -20,11 +33,15 @@ stages:
2033
for listfile in programs/*/list.csv; do
2134
program_dir=$(dirname "$listfile")
2235
program=$(basename "$program_dir")
23-
36+
37+
[[ -n "$CODE_FILTER" && "$program" != "$CODE_FILTER" ]] && continue
38+
2439
while IFS=, read -r system mode queue_group nodes numproc_node nthreads elapse; do
2540
[[ "$system" == "system" ]] && continue # skip header
2641
[[ "$system" == *"#"* ]] && continue # skip #
2742

43+
[[ -n "$SYSTEM_FILTER" && "$system" != "$SYSTEM_FILTER" ]] && continue
44+
2845
job_prefix="${program}_${system}_N${nodes}_P${numproc_node}_T${nthreads}"
2946
program_path="$program_dir"
3047

0 commit comments

Comments
 (0)