diff --git a/ecs/jskult-webapp/src/controller/login.py b/ecs/jskult-webapp/src/controller/login.py
index 5c8d904e..9b2c808e 100644
--- a/ecs/jskult-webapp/src/controller/login.py
+++ b/ecs/jskult-webapp/src/controller/login.py
@@ -70,11 +70,22 @@ def login(
jwt_token = login_service.login(request.username, request.password)
except NotAuthorizeException as e:
logger.info(f'ログイン失敗:{e}')
+ # ログイン失敗回数をカウント
+ login_service.increase_login_failed_count(request.username)
+ # ログイン失敗回数を超過した場合はメッセージを変える
+ if login_service.is_login_failed_limit_exceeded(request.username):
+ login_service.on_login_fail_limit_exceeded(request.username)
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_FAILED_LIMIT_EXCEEDED)
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_ERROR)
except JWTTokenVerifyException as e:
logger.info(f'ログイン失敗:{e}')
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
+ # ログイン成功問わず、DBのログイン失敗回数が10回以上あれば、ログアウト画面にリダイレクトする
+ if login_service.is_login_failed_limit_exceeded(request.username):
+ logger.info(f'ログイン失敗回数が10回以上: {request.username}')
+ raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail=constants.LOGOUT_REASON_LOGIN_FAILED_LIMIT_EXCEEDED)
+
verified_token = jwt_token.verify_token()
# 普通の認証だと、`cognito:username`に入る。
user_id = verified_token.user_id
diff --git a/ecs/jskult-webapp/src/db/database.py b/ecs/jskult-webapp/src/db/database.py
index c729faf5..bd828db3 100644
--- a/ecs/jskult-webapp/src/db/database.py
+++ b/ecs/jskult-webapp/src/db/database.py
@@ -189,7 +189,7 @@ class DatabaseClient:
self.__session = None
def to_jst(self):
- self.execute('SET time_zone = "+9:00"')
+ self.execute('SET SESSION time_zone = "Asia/Tokyo"')
def __execute_with_transaction(self, query: str, parameters: dict):
# トランザクションを開始してクエリを実行する
diff --git a/ecs/jskult-webapp/src/model/db/user_master.py b/ecs/jskult-webapp/src/model/db/user_master.py
index 913b415e..d86253ae 100644
--- a/ecs/jskult-webapp/src/model/db/user_master.py
+++ b/ecs/jskult-webapp/src/model/db/user_master.py
@@ -2,7 +2,7 @@ from datetime import datetime
from typing import Optional
from src.model.db.base_db_model import BaseDBModel
-
+from src.system_var import constants
class UserMasterModel(BaseDBModel):
user_id: Optional[str]
@@ -25,6 +25,8 @@ class UserMasterModel(BaseDBModel):
updater: Optional[str]
update_date: Optional[datetime]
mntuser_flg: Optional[str]
+ mntuser_login_failed_cnt: Optional[int]
+ mntuser_last_login_failed_datetime: Optional[datetime]
def is_enable_user(self):
return self.enabled_flg == 'Y'
@@ -34,3 +36,6 @@ class UserMasterModel(BaseDBModel):
def is_groupware_user(self):
return self.mntuser_flg == '0' or self.mntuser_flg is None
+
+ def is_login_failed_limit_exceeded(self):
+ return self.mntuser_login_failed_cnt >= constants.LOGIN_FAIL_LIMIT
\ No newline at end of file
diff --git a/ecs/jskult-webapp/src/repositories/user_master_repository.py b/ecs/jskult-webapp/src/repositories/user_master_repository.py
index 7edde00a..3acbc105 100644
--- a/ecs/jskult-webapp/src/repositories/user_master_repository.py
+++ b/ecs/jskult-webapp/src/repositories/user_master_repository.py
@@ -6,6 +6,19 @@ logger = get_logger('ユーザー取得')
class UserMasterRepository(BaseRepository):
+
+ def to_jst(self):
+ self._database.to_jst()
+
+ def begin(self):
+ self._database.begin()
+
+ def commit(self):
+ self._database.commit()
+
+ def rollback(self):
+ self._database.rollback()
+
FETCH_SQL = """\
SELECT
*
@@ -26,3 +39,46 @@ class UserMasterRepository(BaseRepository):
except Exception as e:
logger.exception(f"DB Error : Exception={e}")
raise e
+
+ def increase_login_failed_count(self, parameter: dict) -> UserMasterModel:
+ try:
+ query = """\
+ UPDATE
+ src05.user_mst
+ SET
+ mntuser_login_failed_cnt =
+ CASE
+ WHEN
+ DATE(mntuser_last_login_failed_datetime) = DATE(CURRENT_TIMESTAMP())
+ THEN
+ mntuser_login_failed_cnt + 1
+ ELSE
+ 1
+ END,
+ mntuser_last_login_failed_datetime = CURRENT_TIMESTAMP()
+ WHERE
+ user_id = :user_id
+ AND
+ mntuser_flg = 1;\
+ """
+ self._database.execute(query, parameter)
+ except Exception as e:
+ logger.exception(f"DB Error : Exception={e}")
+ raise e
+
+ def disable_mnt_user(self, parameter: dict) -> UserMasterModel:
+ try:
+ query = """\
+ UPDATE
+ src05.user_mst
+ SET
+ enabled_flg = 'N'
+ WHERE
+ user_id = :user_id
+ AND
+ mntuser_flg = 1\
+ """
+ self._database.execute(query, parameter)
+ except Exception as e:
+ logger.exception(f"DB Error : Exception={e}")
+ raise e
diff --git a/ecs/jskult-webapp/src/services/login_service.py b/ecs/jskult-webapp/src/services/login_service.py
index aa1f37fd..3a2715b2 100644
--- a/ecs/jskult-webapp/src/services/login_service.py
+++ b/ecs/jskult-webapp/src/services/login_service.py
@@ -49,6 +49,27 @@ class LoginService(BaseService):
user_record: UserMasterModel = self.user_repository.fetch_one({'user_id': user_id})
return user_record
+ def increase_login_failed_count(self, user_id: str):
+
+ try:
+ # セッション内のタイムゾーン変更のため、明示的にトランザクションを開始する
+ self.user_repository.begin()
+ self.user_repository.to_jst()
+ self.user_repository.increase_login_failed_count({'user_id': user_id})
+ self.user_repository.commit()
+ except Exception as e:
+ self.user_repository.rollback()
+ raise e
+
+ def on_login_fail_limit_exceeded(self, user_id: str):
+ self.user_repository.disable_mnt_user({'user_id': user_id})
+
+ def is_login_failed_limit_exceeded(self, user_id: str):
+ user_record: UserMasterModel = self.user_repository.fetch_one({'user_id': user_id})
+ if user_record is None:
+ return False
+ return user_record.is_login_failed_limit_exceeded()
+
def __secret_hash(self, username: str):
# see - https://aws.amazon.com/jp/premiumsupport/knowledge-center/cognito-unable-to-verify-secret-hash/ # noqa
message = bytes(username + environment.COGNITO_CLIENT_ID, 'utf-8')
diff --git a/ecs/jskult-webapp/src/system_var/constants.py b/ecs/jskult-webapp/src/system_var/constants.py
index 3756facb..962ed7d6 100644
--- a/ecs/jskult-webapp/src/system_var/constants.py
+++ b/ecs/jskult-webapp/src/system_var/constants.py
@@ -63,6 +63,7 @@ LOGOUT_REASON_BACKUP_PROCESSING = 'dump_processing'
LOGOUT_REASON_NOT_LOGIN = 'not_login'
LOGOUT_REASON_DB_ERROR = 'db_error'
LOGOUT_REASON_UNEXPECTED = 'unexpected'
+LOGOUT_REASON_LOGIN_FAILED_LIMIT_EXCEEDED = 'login_failed_limit_exceeded'
LOGOUT_REASON_MESSAGE_MAP = {
LOGOUT_REASON_DO_LOGOUT: 'Logoutしました。',
@@ -72,7 +73,8 @@ LOGOUT_REASON_MESSAGE_MAP = {
LOGOUT_REASON_BACKUP_PROCESSING: 'バックアップ取得を開始しました。
日次バッチ更新が終了するまでマスターメンテは使用できません',
LOGOUT_REASON_NOT_LOGIN: 'Loginしてからページにアクセスしてください。',
LOGOUT_REASON_DB_ERROR: 'DB接続に失敗しました。
再度Loginするか、
管理者にお問い合わせください。',
- LOGOUT_REASON_UNEXPECTED: '予期しないエラーが発生しました。
再度Loginするか、
管理者に問い合わせてください。'
+ LOGOUT_REASON_UNEXPECTED: '予期しないエラーが発生しました。
再度Loginするか、
管理者に問い合わせてください。',
+ LOGOUT_REASON_LOGIN_FAILED_LIMIT_EXCEEDED: 'ログイン失敗回数の上限を超えましたので
アカウントをロックしました。
管理者に連絡してください'
}
# 新規施設担当者登録CSV(マスターメンテ)
@@ -207,3 +209,6 @@ DISPLAY_USER_STOP_DIV_SHORT = {
'03': '特定項目停止',
'04': '全DM停止'
}
+
+# ログイン失敗回数上限(保守ユーザー)
+LOGIN_FAIL_LIMIT = 10
\ No newline at end of file