initial
This commit is contained in:
commit
cb10765ff0
1 changed files with 173 additions and 0 deletions
173
tresetter.py
Normal file
173
tresetter.py
Normal file
|
@ -0,0 +1,173 @@
|
|||
from pathlib import Path
|
||||
import string
|
||||
import random
|
||||
import logging
|
||||
import json
|
||||
import uuid
|
||||
from subprocess import Popen, CalledProcessError
|
||||
|
||||
import redis
|
||||
from fastapi import FastAPI, HTTPException, Cookie
|
||||
from fastapi.responses import JSONResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from pydantic import BaseModel, BaseSettings
|
||||
|
||||
|
||||
code_dir = Path(".")
|
||||
redis_params = {
|
||||
"host": "localhost",
|
||||
# "password": "foobar",
|
||||
}
|
||||
|
||||
|
||||
class Settings(BaseSettings):
|
||||
app_name: str = "tResetter"
|
||||
validate_login_exe: str
|
||||
change_password_exe: str
|
||||
redis_params: dict = {
|
||||
"host": "localhost"
|
||||
}
|
||||
expire_time: int = 60 * 20
|
||||
|
||||
|
||||
settings = Settings()
|
||||
app = FastAPI()
|
||||
|
||||
app.mount("/static", StaticFiles(directory=str(code_dir / "static")))
|
||||
|
||||
logger = logging.getLogger("server")
|
||||
|
||||
|
||||
def create_session(content) -> str:
|
||||
session_id = str(uuid.uuid4())
|
||||
set_session(session_id, content)
|
||||
return session_id
|
||||
|
||||
|
||||
def set_session(session_id: str, content) -> bool:
|
||||
r = redis.Redis(**redis_params)
|
||||
serialized = json.dumps(content)
|
||||
ret = r.set(session_id, serialized)
|
||||
r.expire(session_id, settings.expire_time)
|
||||
|
||||
|
||||
def get_session(session_id: str, renew=True):
|
||||
if session_id is None:
|
||||
raise HTTPException(status_code=400)
|
||||
r = redis.Redis(**redis_params)
|
||||
serialized = r.get(session_id)
|
||||
logger.error("%s", repr(serialized))
|
||||
if serialized is None:
|
||||
raise HTTPException(status_code=401)
|
||||
if renew:
|
||||
r.expire(session_id, settings.expire_time)
|
||||
return json.loads(serialized)
|
||||
|
||||
|
||||
def delete_session(session_id: str):
|
||||
r = redis.Redis(**redis_params)
|
||||
r.delete(session_id)
|
||||
|
||||
|
||||
class LoginData(BaseModel):
|
||||
username: str
|
||||
password: str
|
||||
|
||||
|
||||
class ChangeData(BaseModel):
|
||||
password: str
|
||||
|
||||
class SuccessData(BaseModel):
|
||||
success: bool = True
|
||||
|
||||
|
||||
@app.get("/")
|
||||
async def home():
|
||||
# XXX: read index.html
|
||||
return "Ciao!"
|
||||
|
||||
|
||||
def validate(username, password):
|
||||
p = Popen(
|
||||
[settings.validate_login_exe],
|
||||
env={"VERIRY_USERNAME": username, "VERIFY_PASSWORD": password},
|
||||
)
|
||||
try:
|
||||
p.communicate()
|
||||
except CalledProcessError:
|
||||
return False
|
||||
|
||||
return p.returncode == 0
|
||||
|
||||
|
||||
@app.post("/login")
|
||||
async def login(req: LoginData):
|
||||
|
||||
ok = validate(req.username, req.password)
|
||||
if not ok:
|
||||
raise HTTPException(status_code=401, detail="Authentication error")
|
||||
session_id = create_session(
|
||||
{
|
||||
"username": req.username,
|
||||
}
|
||||
)
|
||||
response = JSONResponse(
|
||||
content={
|
||||
"status": "ok",
|
||||
"username": req.username,
|
||||
}
|
||||
)
|
||||
response.set_cookie(key="session_id", value=session_id)
|
||||
return response
|
||||
|
||||
|
||||
@app.get("/whoami")
|
||||
async def whoami(session_id: str = Cookie(None)):
|
||||
session = get_session(session_id)
|
||||
return JSONResponse(content={"username": session["username"]})
|
||||
|
||||
|
||||
@app.post("/logout")
|
||||
async def logout(session_id: str = Cookie(None)):
|
||||
get_session(session_id)
|
||||
delete_session(session_id)
|
||||
return "OKI"
|
||||
|
||||
|
||||
def password_generate():
|
||||
symbols = list(string.ascii_lowercase) + list(string.digits)
|
||||
return "".join(random.choices(symbols, k=10))
|
||||
|
||||
|
||||
@app.post("/generate")
|
||||
async def generate(session_id: str = Cookie(None)):
|
||||
session = get_session(session_id)
|
||||
session["proposed_password"] = password_generate()
|
||||
set_session(session_id, session)
|
||||
|
||||
return JSONResponse(content={"password": session["proposed_password"]})
|
||||
|
||||
|
||||
@app.post("/change")
|
||||
async def change(req: ChangeData, session_id: str = Cookie(None)):
|
||||
session = get_session(session_id)
|
||||
if "proposed_password" not in session:
|
||||
raise HTTPException(status_code=400, detail="You must generate it first")
|
||||
if req.password != session["proposed_password"]:
|
||||
raise HTTPException(status_code=409)
|
||||
|
||||
p = Popen(
|
||||
[settings.change_password_exe],
|
||||
env={"CHANGE_USERNAME": session["username"], "CHANGE_PASSWORD": req.password},
|
||||
)
|
||||
|
||||
try:
|
||||
p.communicate()
|
||||
except CalledProcessError:
|
||||
fail = True
|
||||
else:
|
||||
fail = p.returncode != 0
|
||||
if fail:
|
||||
return SuccessData(success=False)
|
||||
|
||||
return SuccessData()
|
Loading…
Reference in a new issue