forked from almarklein/timetagger
-
Notifications
You must be signed in to change notification settings - Fork 0
/
run.py
111 lines (84 loc) · 3.89 KB
/
run.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
"""
Basic script to run timetagger.
You can use this to run timetagger locally. If you want to run it
online, you'd need to take care of authentication.
"""
import logging
from pkg_resources import resource_filename
import asgineer
from timetagger import config
from timetagger.server import (
authenticate,
AuthException,
api_handler_triage,
get_webtoken_unsafe,
create_assets_from_dir,
enable_service_worker,
)
logger = logging.getLogger("asgineer")
# Get sets of assets provided by TimeTagger
common_assets = create_assets_from_dir(resource_filename("timetagger.common", "."))
apponly_assets = create_assets_from_dir(resource_filename("timetagger.app", "."))
image_assets = create_assets_from_dir(resource_filename("timetagger.images", "."))
page_assets = create_assets_from_dir(resource_filename("timetagger.pages", "."))
# Combine into two groups. You could add/replace assets here.
app_assets = dict(**common_assets, **image_assets, **apponly_assets)
web_assets = dict(**common_assets, **image_assets, **page_assets)
# Enable the service worker so the app can be used offline and is installable
enable_service_worker(app_assets)
# Turn asset dicts into handlers. This feature of Asgineer provides
# lightning fast handlers that support compression and HTTP caching.
app_asset_handler = asgineer.utils.make_asset_handler(app_assets, max_age=0)
web_asset_handler = asgineer.utils.make_asset_handler(web_assets, max_age=0)
@asgineer.to_asgi
async def main_handler(request):
"""
The main handler where we delegate to the API or asset handler.
We serve at /timetagger for a few reasons, one being that the service
worker won't interfere with other stuff you might serve on localhost.
"""
if request.path == "/":
return 307, {"Location": "/timetagger/"}, b"" # Redirect
elif request.path.startswith("/timetagger/"):
if request.path.startswith("/timetagger/api/v2/"):
path = request.path[19:].strip("/")
return await api_handler(request, path)
elif request.path.startswith("/timetagger/app/"):
path = request.path[16:].strip("/")
return await app_asset_handler(request, path)
else:
path = request.path[12:].strip("/")
return await web_asset_handler(request, path)
else:
return 404, {}, "only serving at /timetagger/"
async def api_handler(request, path):
"""The default API handler. Designed to be short, so that
applications that implement alternative authentication and/or have
more API endpoints can use this as a starting point.
"""
# Some endpoints do not require authentication
if not path and request.method == "GET":
return 200, {}, "See https://timetagger.readthedocs.io"
elif path == "webtoken_for_localhost":
return await webtoken_for_localhost(request)
# Authenticate and get user db
try:
auth_info, db = await authenticate(request)
except AuthException as err:
return 401, {}, f"unauthorized: {err}"
# Handle endpoints that require authentication
return await api_handler_triage(request, path, auth_info, db)
async def webtoken_for_localhost(request):
"""An authentication handler that provides a webtoken when the
hostname is localhost. If you run TimeTagger on the web, you must
implement your own authentication workflow to provide the client
with a TimeTagger webtoken. See `get_webtoken_unsafe()` for details.
"""
# Establish that we can trust the client
if request.host not in ("localhost", "127.0.0.1", "timetagger.wallace-stock.co.uk"):
return 403, {}, "forbidden: must be on localhost or wallace-stock.co.uk"
# Return the webtoken for the default user
token = await get_webtoken_unsafe("defaultuser")
return 200, {}, dict(token=token)
if __name__ == "__main__":
asgineer.run(main_handler, "uvicorn", config.bind, log_level="warning")