5 Commits
v0.6 ... v0.8

Author SHA1 Message Date
Jack Case
f24f3ceee4 remove Base64Str type from altcha
This was decoding the string, which the altcha validator expects to do itself. Now it works.
2025-11-09 18:51:22 +00:00
Jack Case
49377f2e20 add a setting for Altcha secret key 2025-11-09 18:35:16 +00:00
Jack Case
e8189ed832 make the altcha challenge a smidge easier 2025-11-09 18:09:29 +00:00
Jack Case
c0600f527f handle password field of signup form properly, and verify altcha solution 2025-11-09 18:05:44 +00:00
Jack Case
ed6065a6ea add sqlite feature to devcontainer for test DB 2025-11-09 18:05:04 +00:00
5 changed files with 19 additions and 40 deletions

View File

@@ -14,7 +14,8 @@
// Features to add to the dev container. More info: https://containers.dev/features.
"features": {
"ghcr.io/devcontainers/features/docker-in-docker:2": {}
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
"ghcr.io/warrenbuckley/codespace-features/sqlite:1": {}
},
// Use 'forwardPorts' to make a list of ports inside the container available locally.

View File

@@ -1 +0,0 @@
TEMP_HMAC_KEY = "0460de065912d0292df1e7422a5ed2dc362ed56d6bab64fe50b89957463061f3"

View File

@@ -8,7 +8,7 @@ from altcha import Payload as AltchaPayload, verify_solution
from urllib.parse import urlparse, ParseResult
from slopserver.common import TEMP_HMAC_KEY
from slopserver.settings import settings
NAMING_CONVENTION = {
"ix": "ix_%(column_0_label)s",
@@ -73,8 +73,7 @@ def url_validator(urls: list[str]) -> list[ParseResult]:
return parsed_urls
def altcha_validator(altcha_response: AltchaPayload):
# verified = verify_solution(altcha_response, TEMP_HMAC_KEY)
verified = (True, None)
verified = verify_solution(altcha_response, settings.altcha_secret)
if not verified[0]:
raise ValueError(f"altcha verification failed: {verified[1]}")
return None
@@ -86,4 +85,4 @@ class SlopReport(BaseModel):
class SignupForm(BaseModel):
email: EmailStr
password: SecretStr
altcha: Annotated[Base64Str, AfterValidator(altcha_validator)]
altcha: Annotated[str, AfterValidator(altcha_validator)]

View File

@@ -17,7 +17,7 @@ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from fastapi.middleware.cors import CORSMiddleware
from pydantic import AfterValidator, Base64Str
from pydantic_settings import BaseSettings
from sqlalchemy import create_engine
@@ -29,23 +29,15 @@ import jwt
from uuid import uuid4
from slopserver.settings import settings
from slopserver.models import Domain, Path, User
from slopserver.models import SlopReport, SignupForm, altcha_validator
from slopserver.db import select_slop, insert_slop, get_user, create_user
from slopserver.common import TEMP_HMAC_KEY
app = FastAPI()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
class ServerSettings(BaseSettings):
db_url: str = "sqlite+pysqlite:///test_db.sqlite"
token_secret: str = "5bcc778a96b090c3ac1d587bb694a060eaf7bdb5832365f91d5078faf1fff210"
# altcha_secret: str
settings = ServerSettings()
DB_ENGINE = create_engine(settings.db_url)
TOKEN_SECRET = settings.token_secret
@@ -134,14 +126,14 @@ async def signup_form(form_data: Annotated[SignupForm, Form()]):
raise HTTPException(status_code=409, detail="User already exists")
# create user
create_user(form_data.email, get_password_hash(form_data.password), DB_ENGINE)
create_user(form_data.email, get_password_hash(form_data.password.get_secret_value()), DB_ENGINE)
@app.get("/altcha-challenge")
async def altcha_challenge():
options = ChallengeOptions(
expires=datetime.now() + timedelta(minutes=10),
max_number=100000,
hmac_key=TEMP_HMAC_KEY
max_number=80000,
hmac_key=settings.altcha_secret
)
challenge = create_challenge(options)
return challenge
@@ -153,27 +145,6 @@ async def simple_login(username: Annotated[str, Form()], password: Annotated[str
raise HTTPException(status_code=401, detail="Incorrect username or password")
token = generate_auth_token(username)
return {"access_token": token, "token_type": "bearer"}
@app.post("/altcha-challenge")
async def altcha_verify(payload: Annotated[Base64Str, AfterValidator(altcha_validator)]):
# if verified, return a JWT for anonymous API access
expiration = datetime.now() + timedelta(days=30)
uuid = uuid4()
bearer_token = {
"iss": "slopserver",
"exp": int(expiration.timestamp()),
"aud": "slopserver",
"sub": str(uuid),
"client_id": str(uuid),
"iat": int(datetime.now().timestamp()),
"jti": str(uuid)
}
encoded_jwt = jwt.encode(bearer_token, TOKEN_SECRET, ALGO)
return encoded_jwt
if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)

9
slopserver/settings.py Normal file
View File

@@ -0,0 +1,9 @@
from pydantic_settings import BaseSettings
class ServerSettings(BaseSettings):
db_url: str = "sqlite+pysqlite:///test_db.sqlite"
token_secret: str = "5bcc778a96b090c3ac1d587bb694a060eaf7bdb5832365f91d5078faf1fff210"
altcha_secret: str = "0460de065912d0292df1e7422a5ed2dc362ed56d6bab64fe50b89957463061f3"
settings = ServerSettings()