Skip to content

Commit 479f9c7

Browse files
author
root
committed
Initial
0 parents  commit 479f9c7

5 files changed

Lines changed: 1209 additions & 0 deletions

File tree

Dockerfile

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Python app
2+
FROM python:3.12-slim-bookworm
3+
4+
ENV POETRY_VERSION=2.1.3
5+
ENV PYTHONBUFFERED=1
6+
ENV POETRY_INSTALLER_MAX_WORKERS=1
7+
ENV POETRY_VIRTUALENVS_IN_PROJECT=false
8+
ENV POETRY_VIRTUALENVS_CREATE=false
9+
10+
RUN apt update \
11+
&& apt install -y \
12+
curl \
13+
libffi-dev \
14+
&& curl -sSL https://install.python-poetry.org | python - --version ${POETRY_VERSION} \
15+
&& apt remove -y --autoremove --purge curl libffi-dev \
16+
&& apt clean && rm -rf /var/lib/apt/lists/*
17+
18+
ENV PATH="/root/.local/bin:$PATH"
19+
20+
WORKDIR /code
21+
22+
COPY ./pyproject.toml ./poetry.lock /code/
23+
RUN poetry install --no-interaction --no-root --only=main
24+
25+
ARG BUILD_COMMIT_SHA
26+
ENV BUILD_COMMIT_SHA ${BUILD_COMMIT_SHA:-}
27+
28+
#RUN if [ "${BUILD_COMMIT_SHA}" = "localdev" ]; then \
29+
# poetry install --no-interaction --no-root --only=dev; \
30+
# fi
31+
32+
COPY . /code
33+
ENV PYTHONUNBUFFERED=0
34+
35+
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]
36+

compose.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
services:
2+
auth-proxy:
3+
pull_policy: never
4+
restart: unless-stopped
5+
image: auth-proxy
6+
build:
7+
context: ./
8+
args:
9+
- BUILD_COMMIT_SHA=localdev
10+
#volumes:
11+
#- "./:/code/"
12+
ports:
13+
- "8456:80"
14+
15+
#command: [
16+
#"uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80", "--reload",
17+
#]
18+
environment:
19+
- AUTHENTIK_TOKEN

main.py

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import os
2+
from contextlib import asynccontextmanager
3+
from datetime import datetime
4+
from typing import Annotated, Any
5+
6+
import httpx
7+
8+
from fastapi import FastAPI, HTTPException
9+
from fastapi_utilities import repeat_every
10+
from pydantic import AliasPath, BaseModel, Field
11+
from pydantic_settings import BaseSettings
12+
13+
14+
def transform_card_number_to_unique(card_number: str):
15+
"""
16+
For new, longer card ID format, we need to transform it to the old format
17+
New card ID is returned in the exact byte order that's on card, not reversed while shorter format was reversed
18+
to match the decimal representation printed on key fobs, so it can be just converted to hex and put in LDAP.
19+
"""
20+
if len(card_number) == 8:
21+
return card_number[4:6] + card_number[2:4] + card_number[0:2]
22+
else:
23+
return card_number
24+
25+
26+
def transform_card_number_to_mifare(card_number: str):
27+
"""
28+
For new, longer card ID format, we need to transform it to the old format
29+
New card ID is returned in the exact byte order that's on card, not reversed while shorter format was reversed
30+
to match the decimal representation printed on key fobs, so it can be just converted to hex and put in LDAP.
31+
"""
32+
if len(card_number) == 6:
33+
return card_number[4:6] + card_number[2:4] + card_number[0:2] + "00"
34+
else:
35+
return card_number
36+
37+
38+
class User(BaseModel):
39+
uid: Annotated[str, Field(validation_alias="username")]
40+
mifare_card_ids: Annotated[list[str], Field(validation_alias=AliasPath("attributes", "mifareCardId"))] = []
41+
unique_card_ids: Annotated[list[str], Field(validation_alias=AliasPath("attributes", "uniquecardId"))] = []
42+
membership_expiration: Annotated[int, Field(validation_alias=AliasPath("attributes", "membershipExpirationTimestamp"))]
43+
44+
45+
class Settings(BaseSettings):
46+
authentik_token: str = ...
47+
48+
49+
config = Settings()
50+
51+
52+
users: list[User] = []
53+
users_by_card: dict[str, User] = {}
54+
users_last_success_run: datetime | None
55+
users_last_failed_run: datetime | None
56+
users_last_failed_reason: Any
57+
58+
59+
@asynccontextmanager
60+
async def lifespan(app: FastAPI):
61+
await fetch_users()
62+
yield
63+
64+
65+
app = FastAPI(lifespan=lifespan)
66+
67+
68+
def auth():
69+
return
70+
71+
72+
async def fetch() -> list[User]:
73+
async with httpx.AsyncClient(
74+
headers={"Authorization": f"Bearer {config.authentik_token}"},
75+
timeout=15.0,
76+
) as client:
77+
response = await client.get("https://auth.apps.hskrk.pl/api/v3/core/users/?attributes={\"membershipExpirationTimestamp__gt\": 1734998400}&page_size=200")
78+
return [
79+
User(**u)
80+
for u in response.json()['results']
81+
]
82+
83+
84+
@repeat_every(seconds=300)
85+
async def fetch_users():
86+
global users
87+
global users_by_card
88+
users = await fetch()
89+
users_by_card = {
90+
**{
91+
mifare.lower(): user
92+
for user in users for mifare in user.mifare_card_ids
93+
},
94+
**{
95+
transform_card_number_to_unique(mifare).lower(): user
96+
for user in users for mifare in user.mifare_card_ids
97+
},
98+
**{
99+
unique.lower(): user
100+
for user in users for unique in user.unique_card_ids
101+
},
102+
**{
103+
transform_card_number_to_mifare(unique).lower(): user
104+
for user in users for unique in user.unique_card_ids
105+
},
106+
}
107+
108+
109+
@app.get("/users/-/stats")
110+
async def get_user_stats():
111+
return {
112+
"users": {
113+
"count": len(users),
114+
},
115+
"cards": {
116+
"count": len(users_by_card),
117+
},
118+
}
119+
120+
121+
@app.get("/users/-/by-card/{card_id}")
122+
async def get_user_by_card(card_id: str):
123+
user = users_by_card.get(card_id.lower())
124+
if user is None:
125+
raise HTTPException(status_code=404, detail="Item not found")
126+
return user

0 commit comments

Comments
 (0)