Build reusable bot framework
Some checks failed
CI / test (push) Has been cancelled
CI / compose-smoke (push) Has been cancelled

This commit is contained in:
Chelsea Lee
2026-07-19 21:53:24 -05:00
parent 7ecc1107b2
commit fbdf33e894
66 changed files with 8428 additions and 0 deletions

180
core/auth.py Normal file
View File

@@ -0,0 +1,180 @@
import datetime
import os
import bcrypt
import jwt
from jwt.exceptions import ExpiredSignatureError, InvalidTokenError
import core.api_keys as apiKeys
import core.postgres as postgres
import core.users as users
DEFAULT_TOKEN_LIFETIME = datetime.timedelta(hours=1)
def decodeLoginToken(loginToken):
secret = os.getenv("JWT_SECRET")
if not secret or not isinstance(loginToken, str):
return None
try:
payload = jwt.decode(loginToken, secret, algorithms=["HS256"])
except (ExpiredSignatureError, InvalidTokenError):
return None
userUUID = payload.get("sub")
if not isinstance(userUUID, str) or not users.doesUserUUIDExist(userUUID):
return None
return payload
def createLoginToken(userUUID, name=None, expiresIn=None, extraClaims=None):
secret = os.getenv("JWT_SECRET")
if not secret:
raise RuntimeError("JWT_SECRET is required")
if not userUUID or not users.doesUserUUIDExist(userUUID):
raise ValueError("user does not exist")
now = datetime.datetime.now(datetime.timezone.utc)
if expiresIn is None:
expiresIn = DEFAULT_TOKEN_LIFETIME
elif isinstance(expiresIn, (int, float)):
expiresIn = datetime.timedelta(seconds=expiresIn)
if not isinstance(expiresIn, datetime.timedelta) or expiresIn.total_seconds() <= 0:
raise ValueError("token lifetime must be positive")
payload = {
"sub": str(userUUID),
"name": name if name is not None else users.getUserFirstName(userUUID),
"iat": now,
"exp": now + expiresIn,
}
if extraClaims is not None:
if not isinstance(extraClaims, dict):
raise ValueError("extra claims must be an object")
protectedClaims = {"sub", "iat", "exp"}
payload.update(
{
key: value
for key, value in extraClaims.items()
if key not in protectedClaims
}
)
return jwt.encode(payload, secret, algorithm="HS256")
def verifyLoginToken(login_token, username=False, userUUID=False):
if username:
userUUID = users.getUserUUID(username)
if userUUID:
decodedToken = decodeLoginToken(login_token)
if decodedToken and decodedToken.get("sub") == str(userUUID):
return True
return False
def getUserpasswordHash(userUUID):
user = postgres.select_one("users", {"id": userUUID})
if user:
pw_hash = user.get("password_hashed")
if isinstance(pw_hash, memoryview):
return bytes(pw_hash)
return pw_hash
return None
def getLoginToken(username, password):
if not isinstance(username, str) or not isinstance(password, str):
return False
userUUID = users.getUserUUID(username)
if userUUID:
formattedPass = password.encode("utf-8")
if not formattedPass or len(formattedPass) > users.MAX_PASSWORD_BYTES:
return False
usersHashedPassword = getUserpasswordHash(userUUID)
if not usersHashedPassword:
return False
try:
if bcrypt.checkpw(formattedPass, usersHashedPassword):
return createLoginToken(userUUID)
except (TypeError, ValueError):
return False
return False
def unregisterUser(userUUID, password):
pw_hash = getUserpasswordHash(userUUID)
if not pw_hash or not isinstance(password, str):
return False
formattedPassword = password.encode("utf-8")
if not formattedPassword or len(formattedPassword) > users.MAX_PASSWORD_BYTES:
return False
try:
if bcrypt.checkpw(formattedPassword, pw_hash):
return users.deleteUser(userUUID)
except (TypeError, ValueError):
return False
return False
def getBearerToken(authorization):
if not isinstance(authorization, str):
return None
scheme, separator, token = authorization.strip().partition(" ")
if not separator or scheme.lower() != "bearer" or not token.strip():
return None
return token.strip()
def authenticateBearerToken(
authorization,
requiredScopes=None,
allowUser=True,
allowService=True,
):
token = getBearerToken(authorization)
if not token:
return None
if token.count(".") == 2:
payload = decodeLoginToken(token)
if payload:
if not allowUser or requiredScopes:
return None
return {
"type": "user",
"authentication": "jwt",
"user_uuid": payload["sub"],
"service_name": None,
"api_key_id": None,
"scopes": [],
"can_manage_api_keys": True,
"claims": payload,
}
principal = apiKeys.authenticateApiKey(token, requiredScopes=requiredScopes)
if not principal:
return None
if principal["type"] == apiKeys.USER_KEY_TYPE and not allowUser:
return None
if principal["type"] == apiKeys.SERVICE_KEY_TYPE and not allowService:
return None
return principal
def isUserPrincipal(principal, userUUID=None, requireLogin=False):
if not principal or principal.get("type") != "user":
return False
if userUUID is not None and str(principal.get("user_uuid")) != str(userUUID):
return False
if requireLogin and principal.get("authentication") != "jwt":
return False
return True
def hasServiceScope(principal, scope):
if not principal or principal.get("type") != "service":
return False
scopes = principal.get("scopes") or []
return "*" in scopes or scope in scopes