mirror of
https://github.com/revanced/revanced-releases-api.git
synced 2025-04-29 22:14:28 +02:00
fix: fix token revogation
This commit is contained in:
parent
07800c4d62
commit
2d3e62addf
@ -6,4 +6,4 @@ RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/
|
|||||||
|
|
||||||
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||||
&& apt-get -y install --no-install-recommends python3-venv python-is-python3 micro \
|
&& apt-get -y install --no-install-recommends python3-venv python-is-python3 micro \
|
||||||
unzip zip build-essential python3-dev redis-tools
|
unzip zip build-essential python3-dev redis-tools
|
||||||
|
@ -19,4 +19,4 @@ docker run -d --name redis-stack -p 6379:6379 -p 8001:8001 redis/redis-stack:lat
|
|||||||
|
|
||||||
printf "Installing dependencies...\n"
|
printf "Installing dependencies...\n"
|
||||||
|
|
||||||
poetry install --all-extras
|
poetry install --all-extras
|
||||||
|
2
.github/dependabot.yml
vendored
2
.github/dependabot.yml
vendored
@ -3,4 +3,4 @@ updates:
|
|||||||
- package-ecosystem: "pip"
|
- package-ecosystem: "pip"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
2
.github/workflows/mypy.yml
vendored
2
.github/workflows/mypy.yml
vendored
@ -23,4 +23,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
checkName: 'mypy'
|
checkName: 'mypy'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@ -153,4 +153,4 @@ cython_debug/
|
|||||||
|
|
||||||
# PROJECT SPECIFIC
|
# PROJECT SPECIFIC
|
||||||
setup_env.sh
|
setup_env.sh
|
||||||
admin_info.json
|
admin_info.json
|
||||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -1,3 +1,3 @@
|
|||||||
{
|
{
|
||||||
"python.analysis.typeCheckingMode": "off"
|
"python.analysis.typeCheckingMode": "off"
|
||||||
}
|
}
|
||||||
|
@ -20,4 +20,4 @@ RUN apt update && \
|
|||||||
apt-get install build-essential libffi-dev -y \
|
apt-get install build-essential libffi-dev -y \
|
||||||
&& pip install --no-cache-dir -r requirements.txt
|
&& pip install --no-cache-dir -r requirements.txt
|
||||||
|
|
||||||
CMD [ "python3", "./run.py" ]
|
CMD [ "python3", "./run.py" ]
|
||||||
|
@ -8,4 +8,4 @@
|
|||||||
|
|
||||||
## Reporting a Vulnerability
|
## Reporting a Vulnerability
|
||||||
|
|
||||||
To report a vulnerability, please open an Issue in our issue tracker here on GitHub.
|
To report a vulnerability, please open an Issue in our issue tracker here on GitHub.
|
||||||
|
@ -99,4 +99,4 @@ class Announcements:
|
|||||||
return False
|
return False
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
|
from datetime import timedelta
|
||||||
import os
|
import os
|
||||||
import toml
|
import toml
|
||||||
|
from datetime import timedelta
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from fastapi_paseto_auth import AuthPASETO
|
||||||
|
|
||||||
config: dict = toml.load("config.toml")
|
config: dict = toml.load("config.toml")
|
||||||
|
|
||||||
class PasetoSettings(BaseModel):
|
class PasetoSettings(BaseModel):
|
||||||
authpaseto_secret_key: str = os.environ['SECRET_KEY']
|
authpaseto_secret_key: str = os.environ['SECRET_KEY']
|
||||||
authpaseto_access_token_expires: int | bool = config['auth']['access_token_expires']
|
authpaseto_access_token_expires: int | bool = config['auth']['access_token_expires']
|
||||||
|
authpaseto_denylist_enabled: bool = True
|
||||||
|
@ -266,7 +266,13 @@ class Clients:
|
|||||||
banned: bool = False
|
banned: bool = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.redis_tokens.set(token, '')
|
if type(config['auth']['access_token_expires']) is bool:
|
||||||
|
await self.redis_tokens.set(name=token, value="", nx=True)
|
||||||
|
else:
|
||||||
|
await self.redis_tokens.set(name=token,
|
||||||
|
value="",
|
||||||
|
nx=True,
|
||||||
|
ex=config['auth']['access_token_expires'])
|
||||||
await self.UserLogger.log("BAN_TOKEN", None, token)
|
await self.UserLogger.log("BAN_TOKEN", None, token)
|
||||||
banned = True
|
banned = True
|
||||||
except aioredis.RedisError as e:
|
except aioredis.RedisError as e:
|
||||||
@ -275,52 +281,25 @@ class Clients:
|
|||||||
|
|
||||||
return banned
|
return banned
|
||||||
|
|
||||||
async def is_token_banned(self, token: str) -> bool:
|
|
||||||
"""Check if a token is banned
|
|
||||||
|
|
||||||
Args:
|
|
||||||
token (str): Token to check
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True if the token is banned, False otherwise
|
|
||||||
"""
|
|
||||||
|
|
||||||
banned: bool = True
|
|
||||||
|
|
||||||
try:
|
|
||||||
banned = await self.redis_tokens.exists(token)
|
|
||||||
await self.UserLogger.log("CHECK_TOKEN", None, token)
|
|
||||||
except aioredis.RedisError as e:
|
|
||||||
await self.UserLogger.log("CHECK_TOKEN", e)
|
|
||||||
raise e
|
|
||||||
|
|
||||||
return banned
|
|
||||||
|
|
||||||
async def auth_checks(self, client_id: str, token: str) -> bool:
|
async def auth_checks(self, client_id: str, token: str) -> bool:
|
||||||
"""Check if a client exists, is active and the token isn't banned
|
"""Check if a client exists, is active and the token isn't banned
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
client_id (str): UUID of the client
|
client_id (str): UUID of the client
|
||||||
secret (str): Secret of the client
|
secret (str): Secret of the client
|
||||||
|
token (str): Token JTI
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: True if the client exists, is active
|
bool: True if the client exists, is active
|
||||||
and the token isn't banned, False otherwise
|
and the token isn't banned, False otherwise
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if await self.exists(client_id):
|
if await self.exists(client_id) and await self.is_active(client_id):
|
||||||
if await self.is_active(client_id):
|
return True
|
||||||
if not await self.is_token_banned(token):
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
else:
|
|
||||||
if not await self.is_token_banned(token):
|
|
||||||
await self.ban_token(token)
|
|
||||||
return False
|
|
||||||
else:
|
else:
|
||||||
await self.ban_token(token)
|
if not await self.redis_tokens.exists(token):
|
||||||
return False
|
await self.ban_token(token)
|
||||||
|
return False
|
||||||
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -348,4 +327,4 @@ class Clients:
|
|||||||
await self.UserLogger.log("CREATE_ADMIN", e)
|
await self.UserLogger.log("CREATE_ADMIN", e)
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
return created
|
return created
|
||||||
|
@ -186,4 +186,4 @@ class Releases:
|
|||||||
|
|
||||||
return payload
|
return payload
|
||||||
else:
|
else:
|
||||||
raise Exception("Invalid organization.")
|
raise Exception("Invalid organization.")
|
||||||
|
13
app/main.py
13
app/main.py
@ -3,6 +3,7 @@
|
|||||||
import os
|
import os
|
||||||
import toml
|
import toml
|
||||||
import binascii
|
import binascii
|
||||||
|
from redis import Redis
|
||||||
|
|
||||||
from fastapi import FastAPI, Request, status
|
from fastapi import FastAPI, Request, status
|
||||||
from fastapi.responses import JSONResponse, UJSONResponse
|
from fastapi.responses import JSONResponse, UJSONResponse
|
||||||
@ -21,6 +22,7 @@ from fastapi_paseto_auth.exceptions import AuthPASETOException
|
|||||||
|
|
||||||
import app.controllers.Auth as Auth
|
import app.controllers.Auth as Auth
|
||||||
from app.controllers.Clients import Clients
|
from app.controllers.Clients import Clients
|
||||||
|
|
||||||
from app.utils.RedisConnector import RedisConnector
|
from app.utils.RedisConnector import RedisConnector
|
||||||
|
|
||||||
import app.models.GeneralErrors as GeneralErrors
|
import app.models.GeneralErrors as GeneralErrors
|
||||||
@ -100,6 +102,15 @@ def get_config() -> Auth.PasetoSettings:
|
|||||||
"""
|
"""
|
||||||
return Auth.PasetoSettings()
|
return Auth.PasetoSettings()
|
||||||
|
|
||||||
|
@AuthPASETO.token_in_denylist_loader
|
||||||
|
def check_if_token_in_denylist(decrypted_token):
|
||||||
|
redis = Redis(host=os.environ['REDIS_URL'],
|
||||||
|
port=os.environ['REDIS_PORT'],
|
||||||
|
db=config['tokens']['database'],
|
||||||
|
decode_responses=True)
|
||||||
|
|
||||||
|
return redis.exists(decrypted_token["jti"])
|
||||||
|
|
||||||
# Setup custom error handlers
|
# Setup custom error handlers
|
||||||
|
|
||||||
@app.exception_handler(AuthPASETOException)
|
@app.exception_handler(AuthPASETOException)
|
||||||
@ -155,4 +166,4 @@ async def startup() -> None:
|
|||||||
FastAPICache.init(RedisBackend(RedisConnector.connect(config['cache']['database'])),
|
FastAPICache.init(RedisBackend(RedisConnector.connect(config['cache']['database'])),
|
||||||
prefix="fastapi-cache")
|
prefix="fastapi-cache")
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
@ -43,4 +43,4 @@ class AnnouncementDeleted(BaseModel):
|
|||||||
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||||
"""
|
"""
|
||||||
|
|
||||||
deleted: bool
|
deleted: bool
|
||||||
|
@ -21,4 +21,3 @@ class ClientAuthModel(BaseModel):
|
|||||||
|
|
||||||
id: str
|
id: str
|
||||||
secret: str
|
secret: str
|
||||||
|
|
||||||
|
@ -68,4 +68,4 @@ class MirrorAlreadyExistsError(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
error: str = "Conflict"
|
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."
|
message: str = "A mirror already exists for the organization, repository, and version provided. Please use the PUT method to update the mirror."
|
||||||
|
@ -49,4 +49,4 @@ class MirrorDeletedResponseModel(BaseModel):
|
|||||||
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||||
"""
|
"""
|
||||||
deleted: bool
|
deleted: bool
|
||||||
key: str
|
key: str
|
||||||
|
@ -74,4 +74,4 @@ class ChangelogsResponseFields(BaseModel):
|
|||||||
sha: str
|
sha: str
|
||||||
author: str
|
author: str
|
||||||
message: str
|
message: str
|
||||||
html_url: str
|
html_url: str
|
||||||
|
@ -97,4 +97,13 @@ class ChangelogsResponseModel(BaseModel):
|
|||||||
|
|
||||||
repository: str
|
repository: str
|
||||||
path: str
|
path: str
|
||||||
commits: list[ ResponseFields.ChangelogsResponseFields ]
|
commits: list[ ResponseFields.ChangelogsResponseFields ]
|
||||||
|
|
||||||
|
class RevokedTokenResponse(BaseModel):
|
||||||
|
"""Implements the response fields for token invalidation.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||||
|
"""
|
||||||
|
|
||||||
|
revoked: bool
|
||||||
|
@ -6,11 +6,14 @@ import app.models.ClientModels as ClientModels
|
|||||||
import app.models.GeneralErrors as GeneralErrors
|
import app.models.GeneralErrors as GeneralErrors
|
||||||
import app.models.ResponseModels as ResponseModels
|
import app.models.ResponseModels as ResponseModels
|
||||||
|
|
||||||
router = APIRouter()
|
router = APIRouter(
|
||||||
|
prefix="/auth",
|
||||||
|
tags=['Authentication']
|
||||||
|
)
|
||||||
clients = Clients()
|
clients = Clients()
|
||||||
config: dict = load_config()
|
config: dict = load_config()
|
||||||
|
|
||||||
@router.post('/auth', response_model=ResponseModels.ClientAuthTokenResponse, status_code=status.HTTP_200_OK, tags=['Authentication'])
|
@router.post('/', response_model=ResponseModels.ClientAuthTokenResponse, status_code=status.HTTP_200_OK)
|
||||||
async def auth(request: Request, response: Response, client: ClientModels.ClientAuthModel, Authorize: AuthPASETO = Depends()) -> dict:
|
async def auth(request: Request, response: Response, client: ClientModels.ClientAuthModel, Authorize: AuthPASETO = Depends()) -> dict:
|
||||||
"""Authenticate a client and get an auth token.
|
"""Authenticate a client and get an auth token.
|
||||||
|
|
||||||
@ -20,7 +23,7 @@ async def auth(request: Request, response: Response, client: ClientModels.Client
|
|||||||
|
|
||||||
admin_claim: dict[str, bool]
|
admin_claim: dict[str, bool]
|
||||||
|
|
||||||
if await clients.exists(client.id):
|
if await clients.exists(client.id) and await clients.is_active(client.id):
|
||||||
authenticated: bool = await clients.authenticate(client.id, client.secret)
|
authenticated: bool = await clients.authenticate(client.id, client.secret)
|
||||||
|
|
||||||
if not authenticated:
|
if not authenticated:
|
||||||
@ -45,4 +48,17 @@ async def auth(request: Request, response: Response, client: ClientModels.Client
|
|||||||
"error": GeneralErrors.Unauthorized().error,
|
"error": GeneralErrors.Unauthorized().error,
|
||||||
"message": GeneralErrors.Unauthorized().message
|
"message": GeneralErrors.Unauthorized().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()):
|
||||||
|
Authorize.paseto_required()
|
||||||
|
|
||||||
|
if await clients.ban_token(Authorize.get_jti()):
|
||||||
|
return {"revoked": True}
|
||||||
|
else:
|
||||||
|
raise HTTPException(status_code=500, detail={
|
||||||
|
"error": GeneralErrors.InternalServerError().error,
|
||||||
|
"message": GeneralErrors.InternalServerError().message
|
||||||
|
}
|
||||||
|
)
|
||||||
|
@ -128,7 +128,7 @@ async def update_client(request: Request, response: Response, client_id: str, Au
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@router.patch('/client/{client_id}/status', response_model=ResponseModels.ClientStatusResponse, status_code=status.HTTP_200_OK)
|
@router.patch('/{client_id}/status', response_model=ResponseModels.ClientStatusResponse, status_code=status.HTTP_200_OK)
|
||||||
async def client_status(request: Request, response: Response, client_id: str, active: bool, Authorize: AuthPASETO = Depends()) -> dict:
|
async def client_status(request: Request, response: Response, client_id: str, active: bool, Authorize: AuthPASETO = Depends()) -> dict:
|
||||||
"""Activate or deactivate a client
|
"""Activate or deactivate a client
|
||||||
|
|
||||||
@ -144,11 +144,11 @@ async def client_status(request: Request, response: Response, client_id: str, ac
|
|||||||
|
|
||||||
if 'admin' in Authorize.get_token_payload():
|
if 'admin' in Authorize.get_token_payload():
|
||||||
admin_claim = {"admin": Authorize.get_token_payload()['admin']}
|
admin_claim = {"admin": Authorize.get_token_payload()['admin']}
|
||||||
|
print("admin claim: ", admin_claim)
|
||||||
if ( await clients.auth_checks(Authorize.get_subject(), Authorize.get_jti()) and
|
if ( await clients.auth_checks(Authorize.get_subject(), Authorize.get_jti()) and
|
||||||
( admin_claim['admin'] == True or
|
( admin_claim['admin'] == True or
|
||||||
current_user == client_id ) ):
|
current_user == client_id ) ):
|
||||||
|
print("client exists")
|
||||||
if await clients.exists(client_id):
|
if await clients.exists(client_id):
|
||||||
if await clients.status(client_id, active):
|
if await clients.status(client_id, active):
|
||||||
return {"id": client_id, "active": active}
|
return {"id": client_id, "active": active}
|
||||||
@ -159,6 +159,7 @@ async def client_status(request: Request, response: Response, client_id: str, ac
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
print("Client does not exist")
|
||||||
raise HTTPException(status_code=404, detail={
|
raise HTTPException(status_code=404, detail={
|
||||||
"error": GeneralErrors.ClientNotFound().error,
|
"error": GeneralErrors.ClientNotFound().error,
|
||||||
"message": GeneralErrors.ClientNotFound().message
|
"message": GeneralErrors.ClientNotFound().message
|
||||||
@ -169,4 +170,4 @@ async def client_status(request: Request, response: Response, client_id: str, ac
|
|||||||
"error": GeneralErrors.Unauthorized().error,
|
"error": GeneralErrors.Unauthorized().error,
|
||||||
"message": GeneralErrors.Unauthorized().message
|
"message": GeneralErrors.Unauthorized().message
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -18,4 +18,4 @@ async def contributors(request: Request, response: Response) -> dict:
|
|||||||
Returns:
|
Returns:
|
||||||
json: list of contributors
|
json: list of contributors
|
||||||
"""
|
"""
|
||||||
return await releases.get_contributors(config['app']['repositories'])
|
return await releases.get_contributors(config['app']['repositories'])
|
||||||
|
@ -136,4 +136,4 @@ async def delete_mirror(request: Request, response: Response, org: str, repo: st
|
|||||||
"error": GeneralErrors.Unauthorized().error,
|
"error": GeneralErrors.Unauthorized().error,
|
||||||
"message": GeneralErrors.Unauthorized().message
|
"message": GeneralErrors.Unauthorized().message
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -9,4 +9,4 @@ async def ping(request: Request, response: Response) -> None:
|
|||||||
Returns:
|
Returns:
|
||||||
None
|
None
|
||||||
"""
|
"""
|
||||||
return None
|
return None
|
||||||
|
@ -27,4 +27,4 @@ class Generators:
|
|||||||
Returns:
|
Returns:
|
||||||
int: A timestamp
|
int: A timestamp
|
||||||
"""
|
"""
|
||||||
return int(time.time())
|
return int(time.time())
|
||||||
|
@ -29,4 +29,4 @@ class HTTPXClient:
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return httpx_client
|
return httpx_client
|
||||||
|
@ -60,9 +60,9 @@ class AnnouncementsLogger:
|
|||||||
key (str): Key used in the operation
|
key (str): Key used in the operation
|
||||||
"""
|
"""
|
||||||
if type(result) is RedisError:
|
if type(result) is RedisError:
|
||||||
logger.error(f"[User] REDIS {operation} - Failed with error: {result}")
|
logger.error(f"[ANNOUNCEMENT] REDIS {operation} - Failed with error: {result}")
|
||||||
else:
|
else:
|
||||||
logger.info(f"[User] REDIS {operation} {key} - OK")
|
logger.info(f"[ANNOUNCEMENT] REDIS {operation} {key} - OK")
|
||||||
|
|
||||||
class MirrorsLogger:
|
class MirrorsLogger:
|
||||||
async def log(self, operation: str, result: RedisError | None = None, key: str = "") -> None:
|
async def log(self, operation: str, result: RedisError | None = None, key: str = "") -> None:
|
||||||
@ -73,6 +73,6 @@ class MirrorsLogger:
|
|||||||
key (str): Key used in the operation
|
key (str): Key used in the operation
|
||||||
"""
|
"""
|
||||||
if type(result) is RedisError:
|
if type(result) is RedisError:
|
||||||
logger.error(f"[User] REDIS {operation} - Failed with error: {result}")
|
logger.error(f"[MIRRORS] REDIS {operation} - Failed with error: {result}")
|
||||||
else:
|
else:
|
||||||
logger.info(f"[User] REDIS {operation} {key} - OK")
|
logger.info(f"[MIRRORS] REDIS {operation} {key} - OK")
|
||||||
|
@ -20,4 +20,4 @@ class RedisConnector:
|
|||||||
def connect(database: str) -> aioredis.Redis:
|
def connect(database: str) -> aioredis.Redis:
|
||||||
"""Connect to Redis"""
|
"""Connect to Redis"""
|
||||||
redis_url = f"{redis_config['url']}:{redis_config['port']}/{database}"
|
redis_url = f"{redis_config['url']}:{redis_config['port']}/{database}"
|
||||||
return aioredis.from_url(redis_url, encoding="utf-8", decode_responses=True)
|
return aioredis.from_url(redis_url, encoding="utf-8", decode_responses=True)
|
||||||
|
@ -13,17 +13,16 @@ description = """
|
|||||||
|
|
||||||
* Rate Limiting - 60 requests per minute
|
* Rate Limiting - 60 requests per minute
|
||||||
* Cache - 5 minutes
|
* Cache - 5 minutes
|
||||||
* Token duration - 1 hour
|
* Token duration - 1 year
|
||||||
* Token refresh - 30 days
|
|
||||||
|
|
||||||
### Additional Notes
|
### Additional Notes
|
||||||
|
|
||||||
1. Breaking changes are to be expected
|
1. Breaking changes are to be expected
|
||||||
2. Client side caching is adviced to avoid unnecessary requests
|
2. Client side caching is advised to avoid unnecessary requests
|
||||||
3. Abuse of the API will result in IP blocks
|
3. Abuse of the API will result in IP blocks
|
||||||
|
|
||||||
"""
|
"""
|
||||||
version = "0.9 RC2"
|
version = "1.0.0"
|
||||||
|
|
||||||
[license]
|
[license]
|
||||||
|
|
||||||
|
@ -30,4 +30,4 @@ services:
|
|||||||
|
|
||||||
networks:
|
networks:
|
||||||
infra:
|
infra:
|
||||||
external: true
|
external: true
|
||||||
|
@ -31,4 +31,4 @@ services:
|
|||||||
|
|
||||||
networks:
|
networks:
|
||||||
infra:
|
infra:
|
||||||
external: true
|
external: true
|
||||||
|
4
mypy.ini
4
mypy.ini
@ -71,4 +71,8 @@ ignore_missing_imports = True
|
|||||||
|
|
||||||
[mypy-gunicorn.*]
|
[mypy-gunicorn.*]
|
||||||
# No stubs available
|
# No stubs available
|
||||||
|
ignore_missing_imports = True
|
||||||
|
|
||||||
|
[mypy-asgiref.*]
|
||||||
|
# No stubs available
|
||||||
ignore_missing_imports = True
|
ignore_missing_imports = True
|
6
poetry.lock
generated
6
poetry.lock
generated
@ -723,7 +723,7 @@ python-versions = ">=3.5"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "types-redis"
|
name = "types-redis"
|
||||||
version = "4.3.21.1"
|
version = "4.3.21.2"
|
||||||
description = "Typing stubs for redis"
|
description = "Typing stubs for redis"
|
||||||
category = "dev"
|
category = "dev"
|
||||||
optional = false
|
optional = false
|
||||||
@ -1478,8 +1478,8 @@ toolz = [
|
|||||||
{file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"},
|
{file = "toolz-0.12.0.tar.gz", hash = "sha256:88c570861c440ee3f2f6037c4654613228ff40c93a6c25e0eba70d17282c6194"},
|
||||||
]
|
]
|
||||||
types-redis = [
|
types-redis = [
|
||||||
{file = "types-redis-4.3.21.1.tar.gz", hash = "sha256:493814829643fc04a14595eda6ccd69bdc0606477541ccda54238ce3f60bc993"},
|
{file = "types-redis-4.3.21.2.tar.gz", hash = "sha256:ab542249a47d3903b94162e6395ae6be0cc0f562fd184ed51a0a34d8a7021a39"},
|
||||||
{file = "types_redis-4.3.21.1-py3-none-any.whl", hash = "sha256:65b8c842f406932218f8ce636f75e5a03cb6b382d3922cb3e5f87e127e6d434d"},
|
{file = "types_redis-4.3.21.2-py3-none-any.whl", hash = "sha256:eda5fd9e80f453143902db5547ca6506a3af44476ad080db7d18840da532ab19"},
|
||||||
]
|
]
|
||||||
types-toml = [
|
types-toml = [
|
||||||
{file = "types-toml-0.10.8.tar.gz", hash = "sha256:b7e7ea572308b1030dc86c3ba825c5210814c2825612ec679eb7814f8dd9295a"},
|
{file = "types-toml-0.10.8.tar.gz", hash = "sha256:b7e7ea572308b1030dc86c3ba825c5210814c2825612ec679eb7814f8dd9295a"},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user