mirror of
https://github.com/revanced/revanced-polling-api.git
synced 2025-04-29 22:24:26 +02:00
feat(ballot): change ballot format
This commit is contained in:
parent
c8e203f740
commit
9df0161f68
@ -8,7 +8,7 @@ config: dict = load_config()
|
||||
class Ballot:
|
||||
"""Implements a ballot for ReVanced Polling API."""
|
||||
|
||||
redis = RedisConnector.connect(config['tokens']['database'])
|
||||
redis = RedisConnector.connect(config['ballots']['database'])
|
||||
|
||||
BallotLogger = Logger.BallotLogger()
|
||||
|
||||
@ -34,3 +34,24 @@ class Ballot:
|
||||
raise e
|
||||
|
||||
return stored
|
||||
|
||||
async def exists(self, discord_hashed_id: str):
|
||||
"""Check if the ballot exists.
|
||||
|
||||
Args:
|
||||
discord_hashed_id (str): Discord hashed ID of the voter
|
||||
|
||||
Returns:
|
||||
bool: True if the ballot exists, False otherwise
|
||||
"""
|
||||
|
||||
exists: bool = False
|
||||
|
||||
try:
|
||||
if await self.redis.exists(discord_hashed_id):
|
||||
exists = True
|
||||
except aioredis.RedisError as e:
|
||||
await self.BallotLogger.log("BALLOT_EXISTS", e)
|
||||
raise e
|
||||
|
||||
return exists
|
||||
|
@ -2,6 +2,7 @@ from redis import asyncio as aioredis
|
||||
import app.utils.Logger as Logger
|
||||
from app.dependencies import load_config
|
||||
from app.utils.RedisConnector import RedisConnector
|
||||
import app.controllers.Ballot as Ballot
|
||||
|
||||
config: dict = load_config()
|
||||
|
||||
@ -10,6 +11,7 @@ class Clients:
|
||||
"""Implements a client for ReVanced Polling API."""
|
||||
|
||||
redis = RedisConnector.connect(config['tokens']['database'])
|
||||
ballot = Ballot.Ballot()
|
||||
|
||||
UserLogger = Logger.UserLogger()
|
||||
|
||||
@ -41,3 +43,46 @@ class Clients:
|
||||
|
||||
return banned
|
||||
|
||||
async def is_token_banned(self, token: str) -> bool:
|
||||
"""Check if the token is banned
|
||||
|
||||
Args:
|
||||
token (str): Token to check
|
||||
|
||||
Returns:
|
||||
bool: True if the token is banned, False otherwise
|
||||
"""
|
||||
|
||||
banned: bool = False
|
||||
|
||||
try:
|
||||
if await self.redis.exists(token):
|
||||
banned = True
|
||||
except aioredis.RedisError as e:
|
||||
await self.UserLogger.log("IS_TOKEN_BANNED", e)
|
||||
raise e
|
||||
|
||||
return banned
|
||||
|
||||
async def voted(self, token: str, discord_id: str) -> bool:
|
||||
"""Check if the user already voted
|
||||
|
||||
Args:
|
||||
token (str): Token to check
|
||||
|
||||
Returns:
|
||||
bool: True if the user voted, False otherwise
|
||||
"""
|
||||
|
||||
voted: bool = False
|
||||
|
||||
try:
|
||||
if (await self.is_token_banned(token) or
|
||||
await self.ballot.exists(discord_id)):
|
||||
|
||||
voted = True
|
||||
except aioredis.RedisError as e:
|
||||
await self.UserLogger.log("AUTH_CHECKS", e)
|
||||
raise e
|
||||
|
||||
return voted
|
||||
|
@ -1,4 +1,3 @@
|
||||
from collections import deque
|
||||
from pydantic import BaseModel
|
||||
|
||||
class BallotFields(BaseModel):
|
||||
|
@ -1,4 +1,5 @@
|
||||
from pydantic import BaseModel
|
||||
from app.models.BallotFields import BallotFields
|
||||
|
||||
class BallotModel(BaseModel):
|
||||
"""Implements the fields for the ballots.
|
||||
@ -7,4 +8,4 @@ class BallotModel(BaseModel):
|
||||
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||
"""
|
||||
|
||||
vote: str
|
||||
votes: list[BallotFields]
|
||||
|
@ -10,35 +10,25 @@ class InternalServerError(BaseModel):
|
||||
error: str = "Internal Server Error"
|
||||
message: str = "An internal server error occurred. Please try again later."
|
||||
|
||||
class AnnouncementNotFound(BaseModel):
|
||||
"""Implements the response fields for when an item is not found.
|
||||
class Conflict(BaseModel):
|
||||
"""Implements the response fields for when a conflict occurs.
|
||||
|
||||
Args:
|
||||
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||
"""
|
||||
|
||||
error: str = "Not Found"
|
||||
message: str = "No announcement was found."
|
||||
error: str = "Conflict"
|
||||
message: str = "User already voted on this ballot."
|
||||
|
||||
class ClientNotFound(BaseModel):
|
||||
"""Implements the response fields for when a client is not found.
|
||||
class PreconditionFailed(BaseModel):
|
||||
"""Implements the response fields for when a precondition fails.
|
||||
|
||||
Args:
|
||||
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||
"""
|
||||
|
||||
error: str = "Not Found"
|
||||
message: str = "No client matches the given ID"
|
||||
|
||||
class IdNotProvided(BaseModel):
|
||||
"""Implements the response fields for when the id is not provided.
|
||||
|
||||
Args:
|
||||
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||
"""
|
||||
|
||||
error: str = "Bad Request"
|
||||
message: str = "Missing client id"
|
||||
error: str = "Precondition Failed"
|
||||
message: str = "User is not eligible to vote on this ballot."
|
||||
|
||||
class Unauthorized(BaseModel):
|
||||
"""Implements the response fields for when the client is unauthorized.
|
||||
@ -49,23 +39,4 @@ class Unauthorized(BaseModel):
|
||||
|
||||
error: str = "Unauthorized"
|
||||
message: str = "The client is unauthorized to access this resource"
|
||||
|
||||
class MirrorNotFoundError(BaseModel):
|
||||
"""Implements the response fields for when a mirror is not found.
|
||||
|
||||
Args:
|
||||
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||
"""
|
||||
|
||||
error: str = "Not Found"
|
||||
message: str = "No mirror was found for the organization, repository, and version provided."
|
||||
|
||||
class MirrorAlreadyExistsError(BaseModel):
|
||||
"""Implements the response fields for when a mirror already exists.
|
||||
|
||||
Args:
|
||||
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||
"""
|
||||
|
||||
error: str = "Conflict"
|
||||
message: str = "A mirror already exists for the organization, repository, and version provided. Please use the PUT method to update the mirror."
|
||||
|
@ -4,6 +4,7 @@ from fastapi_paseto_auth import AuthPASETO
|
||||
from fastapi import APIRouter, Request, Response, Depends, status, HTTPException, Header
|
||||
from app.dependencies import load_config
|
||||
from app.controllers.Clients import Clients
|
||||
from app.controllers.Ballot import Ballot
|
||||
import app.models.ClientModels as ClientModels
|
||||
import app.models.GeneralErrors as GeneralErrors
|
||||
import app.models.ResponseModels as ResponseModels
|
||||
@ -13,6 +14,7 @@ router = APIRouter(
|
||||
tags=['Authentication']
|
||||
)
|
||||
clients = Clients()
|
||||
ballot = Ballot()
|
||||
config: dict = load_config()
|
||||
|
||||
@router.post('/', response_model=ResponseModels.ClientAuthTokenResponse, status_code=status.HTTP_200_OK)
|
||||
@ -37,13 +39,19 @@ async def auth(request: Request, response: Response, client: ClientModels.Client
|
||||
}
|
||||
)
|
||||
else:
|
||||
user_claims: dict[str, str] = {}
|
||||
user_claims['discord_id_hash'] = client.discord_id_hash
|
||||
access_token = Authorize.create_access_token(subject=client.id,
|
||||
user_claims=user_claims,
|
||||
fresh=True)
|
||||
|
||||
return {"access_token": access_token}
|
||||
if not ballot.exists(client.discord_id_hash):
|
||||
user_claims: dict[str, str] = {}
|
||||
user_claims['discord_id_hash'] = client.discord_id_hash
|
||||
access_token = Authorize.create_access_token(subject=client.id,
|
||||
user_claims=user_claims,
|
||||
fresh=True)
|
||||
return {"access_token": access_token}
|
||||
else:
|
||||
raise HTTPException(status_code=412, detail={
|
||||
"error": GeneralErrors.PreconditionFailed().error,
|
||||
"message": GeneralErrors.PreconditionFailed().message
|
||||
}
|
||||
)
|
||||
else:
|
||||
raise HTTPException(status_code=401, detail={
|
||||
"error": GeneralErrors.Unauthorized().error,
|
||||
@ -51,6 +59,38 @@ async def auth(request: Request, response: Response, client: ClientModels.Client
|
||||
}
|
||||
)
|
||||
|
||||
@router.put("/exchange", response_model=ResponseModels.ClientAuthTokenResponse, status_code=status.HTTP_200_OK)
|
||||
async def exchange_token(request: Request, response: Response, Authorize: AuthPASETO = Depends(), Authorization: str = Header(None)) -> dict:
|
||||
"""Exchange a token for a new one.
|
||||
|
||||
Returns:
|
||||
access_token: auth token
|
||||
|
||||
"""
|
||||
Authorize.paseto_required()
|
||||
|
||||
user_claims: dict[str, str | bool] = {}
|
||||
user_claims['discord_id_hash'] = Authorize.get_user_claims()['discord_id_hash']
|
||||
user_claims['is_exchange_token'] = True
|
||||
access_token = Authorize.create_access_token(subject=Authorize.get_subject(),
|
||||
user_claims=user_claims,
|
||||
fresh=True)
|
||||
if not ballot.exists(Authorize.get_subject()):
|
||||
if await clients.ban_token(Authorize.get_jti()):
|
||||
return {"access_token": access_token}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail={
|
||||
"error": GeneralErrors.InternalServerError().error,
|
||||
"message": GeneralErrors.InternalServerError().message
|
||||
}
|
||||
)
|
||||
else:
|
||||
raise HTTPException(status_code=412, detail={
|
||||
"error": GeneralErrors.PreconditionFailed().error,
|
||||
"message": GeneralErrors.PreconditionFailed().message
|
||||
}
|
||||
)
|
||||
|
||||
@router.delete("/revoke", response_model=ResponseModels.RevokedTokenResponse, status_code=status.HTTP_200_OK)
|
||||
async def revoke_token(request: Request, response: Response, Authorize: AuthPASETO = Depends(), Authorization: str = Header(None)) -> dict:
|
||||
"""Revoke a token.
|
||||
@ -69,3 +109,4 @@ async def revoke_token(request: Request, response: Response, Authorize: AuthPASE
|
||||
"message": GeneralErrors.InternalServerError().message
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -5,11 +5,14 @@ from app.models.BallotModel import BallotModel
|
||||
import app.models.GeneralErrors as GeneralErrors
|
||||
import app.models.ResponseModels as ResponseModels
|
||||
import app.controllers.Ballot as Ballot
|
||||
import app.controllers.Clients as Clients
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
ballot_controller = Ballot.Ballot()
|
||||
|
||||
client = Clients.Clients()
|
||||
|
||||
config: dict = load_config()
|
||||
|
||||
@router.post('/ballot', response_model=ResponseModels.BallotCastedResponse,
|
||||
@ -24,15 +27,30 @@ async def cast_ballot(request: Request, response: Response,
|
||||
"""
|
||||
Authorize.paseto_required()
|
||||
|
||||
discord_hashed_id: str = Authorize.get_paseto_claims()['discord_hashed_id']
|
||||
|
||||
stored: bool = await ballot_controller.store(discord_hashed_id, ballot.vote)
|
||||
if (Authorize.get_paseto_claims()['is_exchange_token'] and
|
||||
not client.voted(
|
||||
Authorize.get_jti(),
|
||||
Authorize.get_paseto_claims()['discord_hashed_id']
|
||||
)):
|
||||
|
||||
if stored:
|
||||
return {"created": stored}
|
||||
stored: bool = await ballot_controller.store(
|
||||
Authorize.get_paseto_claims()['discord_hashed_id'],
|
||||
ballot.vote
|
||||
)
|
||||
|
||||
if stored:
|
||||
await client.ban_token(Authorize.get_jti())
|
||||
return {"created": stored}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail={
|
||||
"error": GeneralErrors.InternalServerError().error,
|
||||
"message": GeneralErrors.InternalServerError().message
|
||||
}
|
||||
)
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail={
|
||||
"error": GeneralErrors.InternalServerError().error,
|
||||
"message": GeneralErrors.InternalServerError().message
|
||||
}
|
||||
raise HTTPException(status_code=401, detail={
|
||||
"error": GeneralErrors.Unauthorized().error,
|
||||
"message": GeneralErrors.Unauthorized().message
|
||||
}
|
||||
)
|
||||
|
Loading…
x
Reference in New Issue
Block a user