diff --git a/CHANGELOG.md b/CHANGELOG.md index a48e0e4..6892981 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - ✨ — The HTTP method used by checks is now configurable - ♻️ — Refactor some agent code - 💄 — Filter form on domains list (#66) +- ✨ — Add "Remember me" checkbox on login (#65) ## 0.5.0 diff --git a/argos/config-example.yaml b/argos/config-example.yaml index f0d081e..1a69034 100644 --- a/argos/config-example.yaml +++ b/argos/config-example.yaml @@ -14,9 +14,19 @@ general: # Can be "production", "dev", "test". # If not present, default value is "production" env: "production" - # to get a good string for cookie_secret, run: + # To get a good string for cookie_secret, run: # openssl rand -hex 32 cookie_secret: "foo_bar_baz" + + # Session duration + # Use m for minutes, h for hours, d for days + # w for weeks, mo for months, y for years + # If not present, default value is "7d" + session_duration: "7d" + # Session opened with "Remember me" checked + # If not present, the "Remember me" feature is not available + # remember_me_duration: "1mo" + # Default delay for checks. # Can be superseeded in domain configuration. # For ex., to run checks every minute: diff --git a/argos/schemas/config.py b/argos/schemas/config.py index 07b1c97..9d70709 100644 --- a/argos/schemas/config.py +++ b/argos/schemas/config.py @@ -175,16 +175,31 @@ class DbSettings(BaseModel): class General(BaseModel): """Frequency for the checks and alerts""" - cookie_secret: str - frequency: int db: DbSettings env: Environment = "production" + cookie_secret: str + session_duration: int = 10080 # 7 days + remember_me_duration: Optional[int] = None + frequency: int root_path: str = "" alerts: Alert mail: Optional[Mail] = None gotify: Optional[List[GotifyUrl]] = None apprise: Optional[Dict[str, List[str]]] = None + @field_validator("session_duration", mode="before") + def parse_session_duration(cls, value): + """Convert the configured session duration to minutes""" + return string_to_duration(value, "minutes") + + @field_validator("remember_me_duration", mode="before") + def parse_remember_me_duration(cls, value): + """Convert the configured session duration with remember me feature to minutes""" + if value: + return string_to_duration(value, "minutes") + + return None + @field_validator("frequency", mode="before") def parse_frequency(cls, value): """Convert the configured frequency to minutes""" diff --git a/argos/server/routes/views.py b/argos/server/routes/views.py index 6dc5ace..4028555 100644 --- a/argos/server/routes/views.py +++ b/argos/server/routes/views.py @@ -28,7 +28,11 @@ SEVERITY_LEVELS = {"ok": 1, "warning": 2, "critical": 3, "unknown": 4} @route.get("/login") -async def login_view(request: Request, msg: str | None = None): +async def login_view( + request: Request, + msg: str | None = None, + config: Config = Depends(get_config), +): token = request.cookies.get("access-token") if token is not None and token != "": manager = request.app.state.manager @@ -44,7 +48,14 @@ async def login_view(request: Request, msg: str | None = None): else: msg = None - return templates.TemplateResponse("login.html", {"request": request, "msg": msg}) + return templates.TemplateResponse( + "login.html", + { + "request": request, + "msg": msg, + "remember": config.general.remember_me_duration, + }, + ) @route.post("/login") @@ -52,6 +63,8 @@ async def post_login( request: Request, db: Session = Depends(get_db), data: OAuth2PasswordRequestForm = Depends(), + rememberme: Annotated[str | None, Form()] = None, + config: Config = Depends(get_config), ): username = data.username user = await queries.get_user(db, username) @@ -70,14 +83,22 @@ async def post_login( db.commit() manager = request.app.state.manager - token = manager.create_access_token( - data={"sub": username}, expires=timedelta(days=7) - ) + session_duration = config.general.session_duration + if config.general.remember_me_duration is not None and rememberme == "on": + session_duration = config.general.remember_me_duration + delta = timedelta(minutes=session_duration) + token = manager.create_access_token(data={"sub": username}, expires=delta) response = RedirectResponse( request.url_for("get_severity_counts_view"), status_code=status.HTTP_303_SEE_OTHER, ) - manager.set_cookie(response, token) + response.set_cookie( + key=manager.cookie_name, + value=token, + httponly=True, + samesite="strict", + expires=int(delta.total_seconds()), + ) return response diff --git a/argos/server/templates/login.html b/argos/server/templates/login.html index b397115..b7a698f 100644 --- a/argos/server/templates/login.html +++ b/argos/server/templates/login.html @@ -16,6 +16,14 @@ name="password" type="password" form="login"> + {% if remember is not none %} + + {% endif %}