diff --git a/Makefile b/Makefile
index 7b15133c..e81dbd0d 100644
--- a/Makefile
+++ b/Makefile
@@ -3,7 +3,7 @@
PROJECT := ahriman
-FILES := AUTHORS COPYING CONFIGURING.md README.md docs package src setup.cfg setup.py
+FILES := AUTHORS COPYING README.md docs package src setup.cfg setup.py
TARGET_FILES := $(addprefix $(PROJECT)/, $(FILES))
IGNORE_FILES := package/archlinux src/.mypy_cache
diff --git a/docs/configuration.md b/docs/configuration.md
index 4d113643..7f3f2827 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -26,6 +26,7 @@ Base authorization settings.
* `allow_read_only` - allow requesting read only pages without authorization, boolean, required.
* `allowed_paths` - URI paths (exact match) which can be accessed without authorization, space separated list of strings, optional.
* `allowed_paths_groups` - URI paths prefixes which can be accessed without authorization, space separated list of strings, optional.
+* `max_age` - parameter which controls both cookie expiration and token expiration inside the service, integer, optional, default is 7 days.
* `salt` - password hash salt, string, required in case if authorization enabled (automatically generated by `create-user` subcommand).
## `auth:*` groups
@@ -124,5 +125,6 @@ Web server settings. If any of `host`/`port` is not set, web integration will be
* `host` - host to bind, string, optional.
* `password` - password to authorize in web service in order to update service status, string, required in case if authorization enabled.
* `port` - port to bind, int, optional.
+* `static_path` - path to directory with static files, string, required.
* `templates` - path to templates directory, string, required.
* `username` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.
diff --git a/package/etc/ahriman.ini b/package/etc/ahriman.ini
index 70adc524..f4c22a7a 100644
--- a/package/etc/ahriman.ini
+++ b/package/etc/ahriman.ini
@@ -11,6 +11,7 @@ root = /
[auth]
target = disabled
allow_read_only = yes
+max_age = 604800
[build]
archbuild_flags =
@@ -49,4 +50,5 @@ chunk_size = 8388608
[web]
host = 127.0.0.1
+static_path = /usr/share/ahriman/static
templates = /usr/share/ahriman
\ No newline at end of file
diff --git a/package/lib/systemd/system/ahriman-web@.service b/package/lib/systemd/system/ahriman-web@.service
index b399b162..77273c75 100644
--- a/package/lib/systemd/system/ahriman-web@.service
+++ b/package/lib/systemd/system/ahriman-web@.service
@@ -8,8 +8,5 @@ ExecStart=/usr/bin/ahriman --architecture %i web
User=ahriman
Group=ahriman
-KillSignal=SIGQUIT
-SuccessExitStatus=SIGQUIT
-
[Install]
WantedBy=multi-user.target
\ No newline at end of file
diff --git a/package/share/ahriman/build-status.jinja2 b/package/share/ahriman/build-status.jinja2
index 35520e01..d1cc2a7a 100644
--- a/package/share/ahriman/build-status.jinja2
+++ b/package/share/ahriman/build-status.jinja2
@@ -5,6 +5,8 @@
+
+
{% include "utils/style.jinja2" %}
diff --git a/package/share/ahriman/static/favicon.ico b/package/share/ahriman/static/favicon.ico
new file mode 100644
index 00000000..e0c066d4
Binary files /dev/null and b/package/share/ahriman/static/favicon.ico differ
diff --git a/setup.py b/setup.py
index 4104b16f..8e0eb5f1 100644
--- a/setup.py
+++ b/setup.py
@@ -74,6 +74,9 @@ setup(
"package/share/ahriman/build-status/package-actions-modals.jinja2",
"package/share/ahriman/build-status/package-actions-script.jinja2",
]),
+ ("share/ahriman/static", [
+ "package/share/ahriman/static/favicon.ico",
+ ]),
("share/ahriman/utils", [
"package/share/ahriman/utils/bootstrap-scripts.jinja2",
"package/share/ahriman/utils/style.jinja2",
diff --git a/src/ahriman/core/auth/auth.py b/src/ahriman/core/auth/auth.py
index 70de402e..c33982e9 100644
--- a/src/ahriman/core/auth/auth.py
+++ b/src/ahriman/core/auth/auth.py
@@ -36,8 +36,8 @@ class Auth:
:cvar ALLOWED_PATHS_GROUPS: URI paths prefixes which can be accessed without authorization, predefined
"""
- ALLOWED_PATHS = {"/", "/favicon.ico", "/index.html"}
- ALLOWED_PATHS_GROUPS = {"/user-api"}
+ ALLOWED_PATHS = {"/", "/index.html"}
+ ALLOWED_PATHS_GROUPS = {"/static", "/user-api"}
def __init__(self, configuration: Configuration, provider: AuthSettings = AuthSettings.Disabled) -> None:
"""
@@ -51,6 +51,7 @@ class Auth:
self.allowed_paths_groups = set(configuration.getlist("auth", "allowed_paths_groups"))
self.allowed_paths_groups.update(self.ALLOWED_PATHS_GROUPS)
self.enabled = provider.is_enabled
+ self.max_age = configuration.getint("auth", "max_age", fallback=7 * 24 * 3600)
@classmethod
def load(cls: Type[Auth], configuration: Configuration) -> Auth:
diff --git a/src/ahriman/web/middlewares/auth_handler.py b/src/ahriman/web/middlewares/auth_handler.py
index b8b51fd1..93a211b8 100644
--- a/src/ahriman/web/middlewares/auth_handler.py
+++ b/src/ahriman/web/middlewares/auth_handler.py
@@ -95,7 +95,7 @@ def setup_auth(application: web.Application, validator: Auth) -> web.Application
"""
fernet_key = fernet.Fernet.generate_key()
secret_key = base64.urlsafe_b64decode(fernet_key)
- storage = EncryptedCookieStorage(secret_key, cookie_name='API_SESSION')
+ storage = EncryptedCookieStorage(secret_key, cookie_name="API_SESSION", max_age=validator.max_age)
setup_session(application, storage)
authorization_policy = AuthorizationPolicy(validator)
diff --git a/src/ahriman/web/routes.py b/src/ahriman/web/routes.py
index 17c22d5c..980d2d1a 100644
--- a/src/ahriman/web/routes.py
+++ b/src/ahriman/web/routes.py
@@ -18,6 +18,7 @@
# along with this program. If not, see .
#
from aiohttp.web import Application
+from pathlib import Path
from ahriman.web.views.index import IndexView
from ahriman.web.views.service.add import AddView
@@ -31,7 +32,7 @@ from ahriman.web.views.user.login import LoginView
from ahriman.web.views.user.logout import LogoutView
-def setup_routes(application: Application) -> None:
+def setup_routes(application: Application, static_path: Path) -> None:
"""
setup all defined routes
@@ -64,10 +65,13 @@ def setup_routes(application: Application) -> None:
POST /user-api/v1/logout logout from service
:param application: web application instance
+ :param static_path: path to static files directory
"""
application.router.add_get("/", IndexView, allow_head=True)
application.router.add_get("/index.html", IndexView, allow_head=True)
+ application.router.add_static("/static", static_path, follow_symlinks=True)
+
application.router.add_post("/service-api/v1/add", AddView)
application.router.add_post("/service-api/v1/remove", RemoveView)
diff --git a/src/ahriman/web/web.py b/src/ahriman/web/web.py
index 36a2b989..94e56c5f 100644
--- a/src/ahriman/web/web.py
+++ b/src/ahriman/web/web.py
@@ -84,7 +84,7 @@ def setup_service(architecture: str, configuration: Configuration, spawner: Spaw
application.middlewares.append(exception_handler(application.logger))
application.logger.info("setup routes")
- setup_routes(application)
+ setup_routes(application, configuration.getpath("web", "static_path"))
application.logger.info("setup templates")
aiohttp_jinja2.setup(application, loader=jinja2.FileSystemLoader(configuration.getpath("web", "templates")))
diff --git a/tests/ahriman/core/upload/test_s3.py b/tests/ahriman/core/upload/test_s3.py
index c8b3af81..110ed0e3 100644
--- a/tests/ahriman/core/upload/test_s3.py
+++ b/tests/ahriman/core/upload/test_s3.py
@@ -62,6 +62,7 @@ def test_get_local_files(s3: S3, resource_path_root: Path) -> None:
Path("web/templates/build-status/login-modal.jinja2"),
Path("web/templates/build-status/package-actions-modals.jinja2"),
Path("web/templates/build-status/package-actions-script.jinja2"),
+ Path("web/templates/static/favicon.ico"),
Path("web/templates/utils/bootstrap-scripts.jinja2"),
Path("web/templates/utils/style.jinja2"),
Path("web/templates/build-status.jinja2"),
diff --git a/tests/ahriman/web/middlewares/test_auth_handler.py b/tests/ahriman/web/middlewares/test_auth_handler.py
index 3640889a..d1789a42 100644
--- a/tests/ahriman/web/middlewares/test_auth_handler.py
+++ b/tests/ahriman/web/middlewares/test_auth_handler.py
@@ -88,14 +88,11 @@ async def test_auth_handler_write(auth: Auth, mocker: MockerFixture) -> None:
check_permission_mock.assert_called_with(aiohttp_request, UserAccess.Write, aiohttp_request.path)
-def test_setup_auth(
- application_with_auth: web.Application,
- configuration: Configuration,
- mocker: MockerFixture) -> None:
+def test_setup_auth(application_with_auth: web.Application, auth: Auth, mocker: MockerFixture) -> None:
"""
must setup authorization
"""
aiohttp_security_setup_mock = mocker.patch("aiohttp_security.setup")
- application = setup_auth(application_with_auth, configuration)
+ application = setup_auth(application_with_auth, auth)
assert application.get("validator") is not None
aiohttp_security_setup_mock.assert_called_once()
diff --git a/tests/ahriman/web/test_routes.py b/tests/ahriman/web/test_routes.py
index 009344c4..b22d0bdd 100644
--- a/tests/ahriman/web/test_routes.py
+++ b/tests/ahriman/web/test_routes.py
@@ -1,11 +1,12 @@
from aiohttp import web
+from ahriman.core.configuration import Configuration
from ahriman.web.routes import setup_routes
-def test_setup_routes(application: web.Application) -> None:
+def test_setup_routes(application: web.Application, configuration: Configuration) -> None:
"""
must generate non empty list of routes
"""
- setup_routes(application)
+ setup_routes(application, configuration.getpath("web", "static_path"))
assert application.router.routes()
diff --git a/tests/ahriman/web/views/test_views_index.py b/tests/ahriman/web/views/test_views_index.py
index cda12b7b..61342667 100644
--- a/tests/ahriman/web/views/test_views_index.py
+++ b/tests/ahriman/web/views/test_views_index.py
@@ -26,3 +26,11 @@ async def test_get_without_auth(client: TestClient) -> None:
response = await client.get("/")
assert response.status == 200
assert await response.text()
+
+
+async def test_get_static(client: TestClient) -> None:
+ """
+ must return static files
+ """
+ response = await client.get("/static/favicon.ico")
+ assert response.status == 200
diff --git a/tests/testresources/core/ahriman.ini b/tests/testresources/core/ahriman.ini
index 0c568715..95343437 100644
--- a/tests/testresources/core/ahriman.ini
+++ b/tests/testresources/core/ahriman.ini
@@ -59,4 +59,5 @@ secret_key =
[web]
host = 127.0.0.1
+static_path = ../web/templates/static
templates = ../web/templates
\ No newline at end of file