auth next part
parent
949ea9fdcf
commit
b04eba9bc4
|
@ -1,17 +1,22 @@
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
from sqlalchemy.ext.asyncio import (AsyncEngine, AsyncSession,
|
from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker
|
||||||
async_sessionmaker)
|
|
||||||
|
|
||||||
from api.application.abstractions.uow import UnitOfWork
|
from api.application.abstractions.uow import UnitOfWork
|
||||||
|
from api.application.protocols.date_time import DateTimeProvider
|
||||||
from api.application.protocols.password_hasher import PasswordHasher
|
from api.application.protocols.password_hasher import PasswordHasher
|
||||||
from api.application.usecase.auth.create_user import CreateUser
|
from api.application.usecase.auth.create_user import CreateUser
|
||||||
from api.domain.user.repository import UserRepository
|
from api.domain.user.repository import UserRepository
|
||||||
from api.infrastructure.dependencies.adapters import (create_engine,
|
from api.infrastructure.dependencies.adapters import (
|
||||||
create_session_maker,
|
create_engine,
|
||||||
new_session,
|
create_session_maker,
|
||||||
new_unit_of_work)
|
new_session,
|
||||||
|
new_unit_of_work,
|
||||||
|
)
|
||||||
from api.infrastructure.dependencies.configs import app_settings
|
from api.infrastructure.dependencies.configs import app_settings
|
||||||
from api.infrastructure.dependencies.protocols import get_password_hasher
|
from api.infrastructure.dependencies.protocols import (
|
||||||
|
get_date_time_provider,
|
||||||
|
get_password_hasher,
|
||||||
|
)
|
||||||
from api.infrastructure.dependencies.repositories import get_user_repository
|
from api.infrastructure.dependencies.repositories import get_user_repository
|
||||||
from api.infrastructure.dependencies.usecases import provide_create_user
|
from api.infrastructure.dependencies.usecases import provide_create_user
|
||||||
from api.infrastructure.settings import Settings
|
from api.infrastructure.settings import Settings
|
||||||
|
@ -26,6 +31,7 @@ def init_dependencies(app: FastAPI) -> None:
|
||||||
|
|
||||||
app.dependency_overrides[UnitOfWork] = new_unit_of_work
|
app.dependency_overrides[UnitOfWork] = new_unit_of_work
|
||||||
|
|
||||||
|
app.dependency_overrides[DateTimeProvider] = get_date_time_provider
|
||||||
app.dependency_overrides[PasswordHasher] = get_password_hasher
|
app.dependency_overrides[PasswordHasher] = get_password_hasher
|
||||||
|
|
||||||
app.dependency_overrides[UserRepository] = get_user_repository
|
app.dependency_overrides[UserRepository] = get_user_repository
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
from fastapi import FastAPI
|
from fastapi import FastAPI
|
||||||
|
|
||||||
from api.presentation.routers import (auth_router, healthcheck_router,
|
from api.presentation.routers import auth_router, healthcheck_router, user_router
|
||||||
user_router)
|
|
||||||
|
|
||||||
|
|
||||||
def init_routers(app: FastAPI) -> None:
|
def init_routers(app: FastAPI) -> None:
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
from .auth_request import UserCreateRequest
|
from .auth_request import LoginRequest, UserCreateRequest
|
||||||
|
|
||||||
__all__ = ("UserCreateRequest",)
|
__all__ = ("UserCreateRequest", "LoginRequest")
|
||||||
|
|
|
@ -6,3 +6,9 @@ class UserCreateRequest:
|
||||||
name: str
|
name: str
|
||||||
email: str
|
email: str
|
||||||
password: str
|
password: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class LoginRequest:
|
||||||
|
email: str
|
||||||
|
password: str
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class AuthenticationResponse:
|
||||||
|
id: UUID
|
||||||
|
name: str
|
||||||
|
email: str
|
|
@ -0,0 +1,7 @@
|
||||||
|
from datetime import datetime
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
|
||||||
|
class DateTimeProvider(Protocol):
|
||||||
|
def get_current_time(self) -> datetime:
|
||||||
|
raise NotImplementedError
|
|
@ -0,0 +1,11 @@
|
||||||
|
from typing import Protocol
|
||||||
|
|
||||||
|
from api.domain.user.model import UserId
|
||||||
|
|
||||||
|
|
||||||
|
class JwtTokenProcessor(Protocol):
|
||||||
|
def generate_token(self, user_id: UserId) -> str:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def validate_token(self, token: str) -> UserId | None:
|
||||||
|
raise NotImplementedError
|
|
@ -0,0 +1,30 @@
|
||||||
|
from api.application.contracts.auth.auth_request import LoginRequest
|
||||||
|
from api.application.contracts.auth.auth_response import AuthenticationResponse
|
||||||
|
from api.application.protocols.password_hasher import PasswordHasher
|
||||||
|
from api.domain.user.error import UserInvalidCredentialsError
|
||||||
|
from api.domain.user.repository import UserRepository
|
||||||
|
|
||||||
|
|
||||||
|
class LoginUser:
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
user_repository: UserRepository,
|
||||||
|
password_hasher: PasswordHasher,
|
||||||
|
) -> None:
|
||||||
|
self.user_repository = user_repository
|
||||||
|
self.hasher = password_hasher
|
||||||
|
|
||||||
|
async def __call__(self, request: LoginRequest) -> AuthenticationResponse:
|
||||||
|
user = await self.user_repository.get_user(filter={"email": request.email})
|
||||||
|
error = UserInvalidCredentialsError("Email or password is incorrect")
|
||||||
|
if user is None:
|
||||||
|
raise error
|
||||||
|
|
||||||
|
if not self.hasher.verify_password(request.password, user.hashed_password):
|
||||||
|
raise error
|
||||||
|
|
||||||
|
return AuthenticationResponse(
|
||||||
|
id=user.id.value,
|
||||||
|
name=user.name.value,
|
||||||
|
email=user.email.value,
|
||||||
|
)
|
|
@ -0,0 +1,37 @@
|
||||||
|
from datetime import timedelta
|
||||||
|
from uuid import UUID
|
||||||
|
|
||||||
|
from jose import JWTError
|
||||||
|
from jose.jwt import decode, encode
|
||||||
|
|
||||||
|
from api.application.protocols.date_time import DateTimeProvider
|
||||||
|
from api.application.protocols.jwt import JwtTokenProcessor
|
||||||
|
from api.domain.user.model import UserId
|
||||||
|
from api.infrastructure.auth.jwt_settings import JwtSettings
|
||||||
|
|
||||||
|
|
||||||
|
class JoseJwtTokenProcessor(JwtTokenProcessor):
|
||||||
|
def __init__(self, jwt_options: JwtSettings, date_time_provider: DateTimeProvider) -> None:
|
||||||
|
self.jwt_options = jwt_options
|
||||||
|
self.date_time_provider = date_time_provider
|
||||||
|
|
||||||
|
def generate_token(self, user_id: UserId) -> str:
|
||||||
|
issued_at = self.date_time_provider.get_current_time()
|
||||||
|
expiration_time = issued_at + timedelta(hours=self.jwt_options.expires_in)
|
||||||
|
|
||||||
|
claims = {
|
||||||
|
"iat": issued_at,
|
||||||
|
"exp": expiration_time,
|
||||||
|
"sub": str(user_id.value),
|
||||||
|
}
|
||||||
|
|
||||||
|
return encode(claims, self.jwt_options.secret, self.jwt_options.algorithm)
|
||||||
|
|
||||||
|
def validate_token(self, token: str) -> UserId | None:
|
||||||
|
try:
|
||||||
|
payload = decode(token, self.jwt_options.secret, [self.jwt_options.algorithm])
|
||||||
|
|
||||||
|
return UserId(UUID(payload["sub"]))
|
||||||
|
|
||||||
|
except (JWTError, ValueError, KeyError):
|
||||||
|
return None
|
|
@ -0,0 +1,8 @@
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class JwtSettings:
|
||||||
|
secret: str
|
||||||
|
expires_in: int = field(default=2)
|
||||||
|
algorithm: str = field(default="HS256")
|
|
@ -0,0 +1,26 @@
|
||||||
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from enum import Enum
|
||||||
|
|
||||||
|
from api.application.protocols.date_time import DateTimeProvider
|
||||||
|
|
||||||
|
|
||||||
|
class Timezone(Enum):
|
||||||
|
UTC = timezone.utc
|
||||||
|
GMT = timezone(timedelta(hours=0))
|
||||||
|
CET = timezone(timedelta(hours=1))
|
||||||
|
EET = timezone(timedelta(hours=2))
|
||||||
|
MSK = timezone(timedelta(hours=3))
|
||||||
|
IST = timezone(timedelta(hours=5, minutes=30))
|
||||||
|
WIB = timezone(timedelta(hours=7))
|
||||||
|
CST = timezone(timedelta(hours=8))
|
||||||
|
JST = timezone(timedelta(hours=9))
|
||||||
|
AEST = timezone(timedelta(hours=10))
|
||||||
|
NZST = timezone(timedelta(hours=12))
|
||||||
|
|
||||||
|
|
||||||
|
class SystemDateTimeProvider(DateTimeProvider):
|
||||||
|
def __init__(self, tz: Timezone) -> None:
|
||||||
|
self.tz = tz
|
||||||
|
|
||||||
|
def get_current_time(self) -> datetime:
|
||||||
|
return datetime.now(tz=self.tz.value)
|
|
@ -3,6 +3,7 @@ from functools import lru_cache
|
||||||
|
|
||||||
import yaml # type: ignore
|
import yaml # type: ignore
|
||||||
|
|
||||||
|
from api.infrastructure.auth.jwt_settings import JwtSettings
|
||||||
from api.infrastructure.persistence.db_setings import DBSettings
|
from api.infrastructure.persistence.db_setings import DBSettings
|
||||||
from api.infrastructure.settings import Settings
|
from api.infrastructure.settings import Settings
|
||||||
|
|
||||||
|
@ -27,4 +28,9 @@ def app_settings() -> Settings:
|
||||||
pg_port=int(config_data["db"]["port"]),
|
pg_port=int(config_data["db"]["port"]),
|
||||||
pg_db=config_data["db"]["database"],
|
pg_db=config_data["db"]["database"],
|
||||||
),
|
),
|
||||||
|
jwt=JwtSettings(
|
||||||
|
secret=config_data["jwt"]["secret_key"],
|
||||||
|
expires_in=int(config_data["jwt"]["expires_in"]),
|
||||||
|
algorithm=config_data["jwt"]["algorithm"],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,6 +1,12 @@
|
||||||
|
from api.application.protocols.date_time import DateTimeProvider
|
||||||
from api.application.protocols.password_hasher import PasswordHasher
|
from api.application.protocols.password_hasher import PasswordHasher
|
||||||
|
from api.infrastructure.date_time import SystemDateTimeProvider, Timezone
|
||||||
from api.infrastructure.security.password_hasher import Pbkdf2PasswordHasher
|
from api.infrastructure.security.password_hasher import Pbkdf2PasswordHasher
|
||||||
|
|
||||||
|
|
||||||
def get_password_hasher() -> PasswordHasher:
|
def get_password_hasher() -> PasswordHasher:
|
||||||
return Pbkdf2PasswordHasher()
|
return Pbkdf2PasswordHasher()
|
||||||
|
|
||||||
|
|
||||||
|
def get_date_time_provider() -> DateTimeProvider:
|
||||||
|
return SystemDateTimeProvider(Timezone.UTC)
|
||||||
|
|
|
@ -14,6 +14,4 @@ def provide_create_user(
|
||||||
uow: Annotated[UnitOfWork, Depends(Stub(UnitOfWork))],
|
uow: Annotated[UnitOfWork, Depends(Stub(UnitOfWork))],
|
||||||
password_hasher: Annotated[PasswordHasher, Depends(Stub(PasswordHasher))],
|
password_hasher: Annotated[PasswordHasher, Depends(Stub(PasswordHasher))],
|
||||||
) -> CreateUser:
|
) -> CreateUser:
|
||||||
return CreateUser(
|
return CreateUser(uow=uow, user_repository=user_repository, password_hasher=password_hasher)
|
||||||
uow=uow, user_repository=user_repository, password_hasher=password_hasher
|
|
||||||
)
|
|
||||||
|
|
|
@ -2,7 +2,6 @@ from sqlalchemy import text
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from api.domain.user import User, UserRepository
|
from api.domain.user import User, UserRepository
|
||||||
from api.infrastructure.persistence.models.user import UserModel
|
|
||||||
|
|
||||||
|
|
||||||
class SqlAlchemyUserRepository(UserRepository):
|
class SqlAlchemyUserRepository(UserRepository):
|
||||||
|
@ -10,12 +9,6 @@ class SqlAlchemyUserRepository(UserRepository):
|
||||||
self.session = session
|
self.session = session
|
||||||
|
|
||||||
async def create_user(self, user: User) -> None:
|
async def create_user(self, user: User) -> None:
|
||||||
# stmt = insert(UserModel).values(
|
|
||||||
# id=user.id.value,
|
|
||||||
# name=user.name.value,
|
|
||||||
# email=user.email.value,
|
|
||||||
# hashed_password=user.hashed_password,
|
|
||||||
# )
|
|
||||||
stmt = text(
|
stmt = text(
|
||||||
"""INSERT INTO users (id, name, email, hashed_password)
|
"""INSERT INTO users (id, name, email, hashed_password)
|
||||||
VALUES(:id, :name, :email, :hashed_password)
|
VALUES(:id, :name, :email, :hashed_password)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from api.infrastructure.auth.jwt_settings import JwtSettings
|
||||||
from api.infrastructure.persistence.db_setings import DBSettings
|
from api.infrastructure.persistence.db_setings import DBSettings
|
||||||
|
|
||||||
|
|
||||||
@dataclass()
|
@dataclass()
|
||||||
class Settings:
|
class Settings:
|
||||||
db: DBSettings
|
db: DBSettings
|
||||||
|
jwt: JwtSettings
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
from typing import Annotated
|
from typing import Annotated
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
from fastapi import APIRouter, Depends, Response
|
||||||
|
from fastapi.security import OAuth2PasswordRequestForm
|
||||||
|
|
||||||
from api.application.contracts.auth import UserCreateRequest
|
from api.application.contracts.auth import LoginRequest, UserCreateRequest
|
||||||
|
from api.application.contracts.auth.auth_response import AuthenticationResponse
|
||||||
|
from api.application.protocols.jwt import JwtTokenProcessor
|
||||||
|
from api.application.usecase.auth.auth_user import LoginUser
|
||||||
from api.application.usecase.auth.create_user import CreateUser
|
from api.application.usecase.auth.create_user import CreateUser
|
||||||
|
from api.domain.user.model import UserId
|
||||||
from api.infrastructure.dependencies.stub import Stub
|
from api.infrastructure.dependencies.stub import Stub
|
||||||
|
|
||||||
auth_router = APIRouter(prefix="/auth", tags=["Auth"])
|
auth_router = APIRouter(prefix="/auth", tags=["Auth"])
|
||||||
|
@ -15,3 +20,22 @@ async def create_user(
|
||||||
usecase: Annotated[CreateUser, Depends(Stub(CreateUser))],
|
usecase: Annotated[CreateUser, Depends(Stub(CreateUser))],
|
||||||
) -> None:
|
) -> None:
|
||||||
return await usecase.execute(request)
|
return await usecase.execute(request)
|
||||||
|
|
||||||
|
|
||||||
|
@auth_router.post("/login", response_model=AuthenticationResponse)
|
||||||
|
async def login(
|
||||||
|
response: Response,
|
||||||
|
login_request: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||||
|
login_interactor: Annotated[LoginUser, Depends(Stub(LoginUser))],
|
||||||
|
token_processor: Annotated[JwtTokenProcessor, Depends(Stub(JwtTokenProcessor))],
|
||||||
|
) -> AuthenticationResponse:
|
||||||
|
user = await login_interactor(
|
||||||
|
LoginRequest(
|
||||||
|
email=login_request.username,
|
||||||
|
password=login_request.password,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
token = token_processor.generate_token(UserId(user.id))
|
||||||
|
response.set_cookie(key="access_token", value=f"Bearer {token}", httponly=True)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
from typing import Annotated
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends
|
|
||||||
|
|
||||||
from api.application.contracts.user import UserResponse
|
from api.application.contracts.user import UserResponse
|
||||||
from api.infrastructure.dependencies.stub import Stub
|
|
||||||
|
|
||||||
user_router = APIRouter(prefix="/users", tags=["Users"])
|
user_router = APIRouter(prefix="/users", tags=["Users"])
|
||||||
|
|
||||||
|
|
|
@ -4,3 +4,8 @@ db:
|
||||||
database: "serviceman_db"
|
database: "serviceman_db"
|
||||||
user: "demo_user"
|
user: "demo_user"
|
||||||
password: "user_pass"
|
password: "user_pass"
|
||||||
|
|
||||||
|
jwt:
|
||||||
|
secret_key: "abra-cadabra"
|
||||||
|
algorithm: "HS256"
|
||||||
|
expires_in: 2
|
||||||
|
|
|
@ -347,6 +347,24 @@ files = [
|
||||||
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
|
{file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ecdsa"
|
||||||
|
version = "0.18.0"
|
||||||
|
description = "ECDSA cryptographic signature library (pure python)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
files = [
|
||||||
|
{file = "ecdsa-0.18.0-py2.py3-none-any.whl", hash = "sha256:80600258e7ed2f16b9aa1d7c295bd70194109ad5a30fdee0eaeefef1d4c559dd"},
|
||||||
|
{file = "ecdsa-0.18.0.tar.gz", hash = "sha256:190348041559e21b22a1d65cee485282ca11a6f81d503fddb84d5017e9ed1e49"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
six = ">=1.9.0"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
gmpy = ["gmpy"]
|
||||||
|
gmpy2 = ["gmpy2"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "exceptiongroup"
|
name = "exceptiongroup"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
@ -600,6 +618,19 @@ files = [
|
||||||
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
{file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jose"
|
||||||
|
version = "1.0.0"
|
||||||
|
description = "An implementation of the JOSE draft"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "jose-1.0.0.tar.gz", hash = "sha256:8436c3617cd94e1ba97828fbb1ce27c129f66c78fb855b4bb47e122b5f345fba"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pycrypto = ">=2.6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "magic-filter"
|
name = "magic-filter"
|
||||||
version = "1.0.12"
|
version = "1.0.12"
|
||||||
|
@ -949,6 +980,27 @@ nodeenv = ">=0.11.1"
|
||||||
pyyaml = ">=5.1"
|
pyyaml = ">=5.1"
|
||||||
virtualenv = ">=20.10.0"
|
virtualenv = ">=20.10.0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pyasn1"
|
||||||
|
version = "0.6.0"
|
||||||
|
description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "pyasn1-0.6.0-py2.py3-none-any.whl", hash = "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473"},
|
||||||
|
{file = "pyasn1-0.6.0.tar.gz", hash = "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pycrypto"
|
||||||
|
version = "2.6.1"
|
||||||
|
description = "Cryptographic modules for Python."
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "pycrypto-2.6.1.tar.gz", hash = "sha256:f2ce1e989b272cfcb677616763e0a2e7ec659effa67a88aa92b3a65528f60a3c"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pydantic"
|
name = "pydantic"
|
||||||
version = "2.5.3"
|
version = "2.5.3"
|
||||||
|
@ -1107,6 +1159,27 @@ tomli = {version = ">=1", markers = "python_version < \"3.11\""}
|
||||||
[package.extras]
|
[package.extras]
|
||||||
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "python-jose"
|
||||||
|
version = "3.3.0"
|
||||||
|
description = "JOSE implementation in Python"
|
||||||
|
optional = false
|
||||||
|
python-versions = "*"
|
||||||
|
files = [
|
||||||
|
{file = "python-jose-3.3.0.tar.gz", hash = "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a"},
|
||||||
|
{file = "python_jose-3.3.0-py2.py3-none-any.whl", hash = "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
ecdsa = "!=0.15"
|
||||||
|
pyasn1 = "*"
|
||||||
|
rsa = "*"
|
||||||
|
|
||||||
|
[package.extras]
|
||||||
|
cryptography = ["cryptography (>=3.4.0)"]
|
||||||
|
pycrypto = ["pyasn1", "pycrypto (>=2.6.0,<2.7.0)"]
|
||||||
|
pycryptodome = ["pyasn1", "pycryptodome (>=3.3.1,<4.0.0)"]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pyyaml"
|
name = "pyyaml"
|
||||||
version = "6.0.1"
|
version = "6.0.1"
|
||||||
|
@ -1132,7 +1205,6 @@ files = [
|
||||||
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
|
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
|
||||||
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
|
||||||
|
@ -1167,6 +1239,20 @@ files = [
|
||||||
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
{file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rsa"
|
||||||
|
version = "4.9"
|
||||||
|
description = "Pure-Python RSA implementation"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.6,<4"
|
||||||
|
files = [
|
||||||
|
{file = "rsa-4.9-py3-none-any.whl", hash = "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7"},
|
||||||
|
{file = "rsa-4.9.tar.gz", hash = "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
pyasn1 = ">=0.1.3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "setuptools"
|
name = "setuptools"
|
||||||
version = "69.2.0"
|
version = "69.2.0"
|
||||||
|
@ -1183,6 +1269,17 @@ docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments
|
||||||
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "six"
|
||||||
|
version = "1.16.0"
|
||||||
|
description = "Python 2 and 3 compatibility utilities"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
|
||||||
|
files = [
|
||||||
|
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
|
||||||
|
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "sniffio"
|
name = "sniffio"
|
||||||
version = "1.3.1"
|
version = "1.3.1"
|
||||||
|
@ -1476,4 +1573,4 @@ multidict = ">=4.0"
|
||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "15cfd4e717fc2a2ca5dc844e5174d992f7fd8739bc2a056f2bbbc352e91018d4"
|
content-hash = "cb90b25778130dc1445290f65ee84f37029b7db4838a16b510a9f2efaf5ae841"
|
||||||
|
|
|
@ -7,6 +7,8 @@ readme = "README.md"
|
||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.10"
|
python = "^3.10"
|
||||||
|
jose = "^1.0.0"
|
||||||
|
python-jose = "^3.3.0"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue