This commit is contained in:
Igor Lobanov
2023-10-26 22:38:31 +02:00
parent 1c49780cd4
commit c2cc428abe
64 changed files with 631 additions and 626 deletions

View File

@@ -1,17 +1,15 @@
from functools import wraps
from typing import Optional, Tuple
from graphql.type import GraphQLResolveInfo
from sqlalchemy.orm import exc, joinedload
from starlette.authentication import AuthenticationBackend
from starlette.requests import HTTPConnection
from auth.credentials import AuthCredentials, AuthUser
from auth.tokenstorage import SessionToken
from base.exceptions import OperationNotAllowed
from base.orm import local_session
from functools import wraps
from graphql.type import GraphQLResolveInfo
from orm.user import Role, User
from settings import SESSION_TOKEN_HEADER
from sqlalchemy.orm import exc, joinedload
from starlette.authentication import AuthenticationBackend
from starlette.requests import HTTPConnection
from typing import Optional, Tuple
class JWTAuthenticate(AuthenticationBackend):
@@ -19,16 +17,16 @@ class JWTAuthenticate(AuthenticationBackend):
self, request: HTTPConnection
) -> Optional[Tuple[AuthCredentials, AuthUser]]:
if SESSION_TOKEN_HEADER not in request.headers:
return AuthCredentials(scopes={}), AuthUser(user_id=None, username='')
return AuthCredentials(scopes={}), AuthUser(user_id=None, username="")
token = request.headers.get(SESSION_TOKEN_HEADER)
if not token:
print("[auth.authenticate] no token in header %s" % SESSION_TOKEN_HEADER)
return AuthCredentials(scopes={}, error_message=str("no token")), AuthUser(
user_id=None, username=''
user_id=None, username=""
)
if len(token.split('.')) > 1:
if len(token.split(".")) > 1:
payload = await SessionToken.verify(token)
with local_session() as session:
@@ -47,20 +45,21 @@ class JWTAuthenticate(AuthenticationBackend):
return (
AuthCredentials(user_id=payload.user_id, scopes=scopes, logged_in=True),
AuthUser(user_id=user.id, username=''),
AuthUser(user_id=user.id, username=""),
)
except exc.NoResultFound:
pass
return AuthCredentials(scopes={}, error_message=str('Invalid token')), AuthUser(
user_id=None, username=''
return AuthCredentials(scopes={}, error_message=str("Invalid token")), AuthUser(
user_id=None, username=""
)
def login_required(func):
@wraps(func)
async def wrap(parent, info: GraphQLResolveInfo, *args, **kwargs):
# print('[auth.authenticate] login required for %r with info %r' % (func, info)) # debug only
# debug only
# print('[auth.authenticate] login required for %r with info %r' % (func, info))
auth: AuthCredentials = info.context["request"].auth
# print(auth)
if not auth or not auth.logged_in:
@@ -75,7 +74,7 @@ def permission_required(resource, operation, func):
@wraps(func)
async def wrap(parent, info: GraphQLResolveInfo, *args, **kwargs):
print(
'[auth.authenticate] permission_required for %r with info %r' % (func, info)
"[auth.authenticate] permission_required for %r with info %r" % (func, info)
) # debug only
auth: AuthCredentials = info.context["request"].auth
if not auth.logged_in:

View File

@@ -1,6 +1,5 @@
from typing import List, Optional, Text
from pydantic import BaseModel
from typing import List, Optional, Text
# from base.exceptions import Unauthorized

View File

@@ -1,17 +1,17 @@
import requests
from settings import MAILGUN_API_KEY, MAILGUN_DOMAIN
api_url = "https://api.mailgun.net/v3/%s/messages" % (MAILGUN_DOMAIN or 'discours.io')
noreply = "discours.io <noreply@%s>" % (MAILGUN_DOMAIN or 'discours.io')
import requests
api_url = "https://api.mailgun.net/v3/%s/messages" % (MAILGUN_DOMAIN or "discours.io")
noreply = "discours.io <noreply@%s>" % (MAILGUN_DOMAIN or "discours.io")
lang_subject = {"ru": "Подтверждение почты", "en": "Confirm email"}
async def send_auth_email(user, token, lang="ru", template="email_confirmation"):
try:
to = "%s <%s>" % (user.name, user.email)
if lang not in ['ru', 'en']:
lang = 'ru'
if lang not in ["ru", "en"]:
lang = "ru"
subject = lang_subject.get(lang, lang_subject["en"])
template = template + "_" + lang
payload = {
@@ -19,9 +19,9 @@ async def send_auth_email(user, token, lang="ru", template="email_confirmation")
"to": to,
"subject": subject,
"template": template,
"h:X-Mailgun-Variables": "{ \"token\": \"%s\" }" % token,
"h:X-Mailgun-Variables": '{ "token": "%s" }' % token,
}
print('[auth.email] payload: %r' % payload)
print("[auth.email] payload: %r" % payload)
# debug
# print('http://localhost:3000/?modal=auth&mode=confirm-email&token=%s' % token)
response = requests.post(api_url, auth=("api", MAILGUN_API_KEY), data=payload)

View File

@@ -1,16 +1,14 @@
from binascii import hexlify
from hashlib import sha256
from jwt import DecodeError, ExpiredSignatureError
from passlib.hash import bcrypt
from sqlalchemy import or_
from auth.jwtcodec import JWTCodec
from auth.tokenstorage import TokenStorage
# from base.exceptions import InvalidPassword, InvalidToken
from base.orm import local_session
from binascii import hexlify
from hashlib import sha256
from jwt import DecodeError, ExpiredSignatureError
from orm import User
from passlib.hash import bcrypt
from sqlalchemy import or_
from validations.auth import AuthInput
@@ -35,6 +33,7 @@ class Password:
Verify that password hash is equal to specified hash. Hash format:
$2a$10$Ro0CUfOqk6cXEKf3dyaM7OhSCvnwM9s4wIX9JeLapehKK5YdLxKcm
# noqa: W605
\__/\/ \____________________/\_____________________________/
| | Salt Hash
| Cost
@@ -84,7 +83,7 @@ class Identity:
@staticmethod
async def onetime(token: str) -> User:
try:
print('[auth.identity] using one time token')
print("[auth.identity] using one time token")
payload = JWTCodec.decode(token)
if not await TokenStorage.exist(f"{payload.user_id}-{payload.username}-{token}"):
# raise InvalidToken("Login token has expired, please login again")

View File

@@ -1,11 +1,10 @@
from datetime import datetime, timezone
import jwt
from base.exceptions import ExpiredToken, InvalidToken
from datetime import datetime, timezone
from settings import JWT_ALGORITHM, JWT_SECRET_KEY
from validations.auth import AuthInput, TokenPayload
import jwt
class JWTCodec:
@staticmethod
@@ -20,7 +19,7 @@ class JWTCodec:
try:
return jwt.encode(payload, JWT_SECRET_KEY, JWT_ALGORITHM)
except Exception as e:
print('[auth.jwtcodec] JWT encode error %r' % e)
print("[auth.jwtcodec] JWT encode error %r" % e)
@staticmethod
def decode(token: str, verify_exp: bool = True) -> TokenPayload:
@@ -41,12 +40,12 @@ class JWTCodec:
# print('[auth.jwtcodec] debug token %r' % r)
return r
except jwt.InvalidIssuedAtError:
print('[auth.jwtcodec] invalid issued at: %r' % payload)
raise ExpiredToken('check token issued time')
print("[auth.jwtcodec] invalid issued at: %r" % payload)
raise ExpiredToken("check token issued time")
except jwt.ExpiredSignatureError:
print('[auth.jwtcodec] expired signature %r' % payload)
raise ExpiredToken('check token lifetime')
print("[auth.jwtcodec] expired signature %r" % payload)
raise ExpiredToken("check token lifetime")
except jwt.InvalidTokenError:
raise InvalidToken('token is not valid')
raise InvalidToken("token is not valid")
except jwt.InvalidSignatureError:
raise InvalidToken('token is not valid')
raise InvalidToken("token is not valid")

View File

@@ -1,9 +1,8 @@
from authlib.integrations.starlette_client import OAuth
from starlette.responses import RedirectResponse
from auth.identity import Identity
from auth.tokenstorage import TokenStorage
from authlib.integrations.starlette_client import OAuth
from settings import FRONTEND_URL, OAUTH_CLIENTS
from starlette.responses import RedirectResponse
oauth = OAuth()

View File

@@ -1,7 +1,6 @@
from datetime import datetime, timedelta, timezone
from auth.jwtcodec import JWTCodec
from base.redis import redis
from datetime import datetime, timedelta, timezone
from settings import ONETIME_TOKEN_LIFE_SPAN, SESSION_TOKEN_LIFE_SPAN
from validations.auth import AuthInput
@@ -35,7 +34,7 @@ class SessionToken:
class TokenStorage:
@staticmethod
async def get(token_key):
print('[tokenstorage.get] ' + token_key)
print("[tokenstorage.get] " + token_key)
# 2041-user@domain.zn-eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoyMDQxLCJ1c2VybmFtZSI6ImFudG9uLnJld2luK3Rlc3QtbG9hZGNoYXRAZ21haWwuY29tIiwiZXhwIjoxNjcxNzgwNjE2LCJpYXQiOjE2NjkxODg2MTYsImlzcyI6ImRpc2NvdXJzIn0.Nml4oV6iMjMmc6xwM7lTKEZJKBXvJFEIZ-Up1C1rITQ
return await redis.execute("GET", token_key)