auth via email

This commit is contained in:
knst-kotov 2021-08-25 11:31:51 +03:00
parent 3c538d3724
commit 805db9814a
8 changed files with 60 additions and 20 deletions

View File

@ -20,6 +20,7 @@ bson = "*"
python-frontmatter = "*"
bs4 = "*"
psycopg2 = "*"
requests = "*"
[dev-packages]

View File

@ -8,10 +8,11 @@ from starlette.requests import HTTPConnection
from auth.credentials import AuthCredentials, AuthUser
from auth.token import Token
from auth.authorize import Authorize
from exceptions import InvalidToken, OperationNotAllowed
from orm import User
from redis import redis
from settings import JWT_AUTH_HEADER
from settings import JWT_AUTH_HEADER, EMAIL_TOKEN_LIFE_SPAN
class _Authenticate:
@ -68,6 +69,25 @@ class JWTAuthenticate(AuthenticationBackend):
scopes = User.get_permission(user_id=payload.user_id)
return AuthCredentials(user_id=payload.user_id, scopes=scopes, logged_in=True), AuthUser(user_id=payload.user_id)
class EmailAuthenticate:
@staticmethod
async def get_email_token(user):
token = await Authorize.authorize(
user,
device="email",
life_span=EMAIL_TOKEN_LIFE_SPAN
)
return token
@staticmethod
async def authenticate(token):
payload = await _Authenticate.verify(token)
if payload is None:
return
if payload.device != "email":
return;
auth_token = Authorize.authorize(payload.user)
return (auth_token, payload.user)
def login_required(func):
@wraps(func)

View File

@ -8,14 +8,14 @@ from auth.validations import User
class Authorize:
@staticmethod
async def authorize(user: User, device: str = "pc", auto_delete=True) -> str:
async def authorize(user: User, device: str = "pc", life_span = JWT_LIFE_SPAN, auto_delete=True) -> str:
"""
:param user:
:param device:
:param auto_delete: Whether the expiration is automatically deleted, the default is True
:return:
"""
exp = datetime.utcnow() + timedelta(seconds=JWT_LIFE_SPAN)
exp = datetime.utcnow() + timedelta(seconds=life_span)
token = Token.encode(user, exp=exp, device=device)
await redis.execute("SET", f"{user.id}-{token}", "True")
if auto_delete:
@ -37,13 +37,3 @@ class Authorize:
async def revoke_all(user: User):
tokens = await redis.execute("KEYS", f"{user.id}-*")
await redis.execute("DEL", *tokens)
@staticmethod
async def confirm(token: str):
try:
# NOTE: auth_token and email_token are different
payload = Token.decode(token) # TODO: check to decode here the proper way
auth_token = self.authorize(payload.user)
return auth_token, payload.user
except:
pass

27
auth/email.py Normal file
View File

@ -0,0 +1,27 @@
import requests
from auth.authenticate import EmailAuthenticate
from settings import MAILGUN_API_KEY, MAILGUN_DOMAIN
MAILGUN_API_URL = "https://api.mailgun.net/v3/%s/messages" % (MAILGUN_DOMAIN)
MAILGUN_FROM = "postmaster <postmaster@%s>" % (MAILGUN_DOMAIN)
AUTH_URL = "https://localhost:8080/auth"
async def send_auth_email(user):
token = await EmailAuthenticate.get_email_token(user)
to = "%s <%s>" % (user.username, user.email)
text = "%s&token=%s" % (AUTH_URL, token)
response = requests.post(
MAILGUN_API_URL,
auth = ("api", MAILGUN_API_KEY),
data = {
"from": MAILGUN_FROM,
"to": to,
"subject": "authorize log in",
"text": text
}
)
response.raise_for_status()

View File

@ -64,5 +64,5 @@ async def oauth_authorize(request):
"username" : profile["name"]
}
user = Identity.identity_oauth(user_input)
token = await Authorize.authorize(user, device="pc", auto_delete=False)
token = await Authorize.authorize(user, device="pc")
return PlainTextResponse(token)

View File

@ -5,6 +5,7 @@ from auth.authorize import Authorize
from auth.identity import Identity
from auth.password import Password
from auth.validations import CreateUser
from auth.email import send_auth_email
from orm import User
from orm.base import local_session
from resolvers.base import mutation, query
@ -29,11 +30,8 @@ async def register(*_, email: str, password: str = ""):
create_user = CreateUser(**inp)
create_user.username = email.split('@')[0]
if not password:
# NOTE: 1 hour confirm_token expire
confirm_token = Token.encode(create_user, datetime.now() + timedelta(hours = 1) , "email")
# TODO: sendAuthEmail(confirm_token)
# без пароля не возвращаем, а высылаем токен на почту
#
user = User.create(**create_user.dict())
await send_auth_email(user)
return { "user": user }
else:
create_user.password = Password.encode(create_user.password)

View File

@ -58,7 +58,7 @@ type Mutation {
confirmEmail(token: String!): AuthResult!
requestPasswordReset(email: String!): Boolean!
confirmPasswordReset(token: String!): Boolean!
registerUser(email: String!, password: String!): AuthResult!
registerUser(email: String!, password: String): AuthResult!
# updatePassword(password: String!, token: String!): Token!
# invalidateAllTokens: Boolean!
# invalidateTokenById(id: Int!): Boolean!

View File

@ -8,8 +8,12 @@ JWT_ALGORITHM = "HS256"
JWT_SECRET_KEY = "8f1bd7696ffb482d8486dfbc6e7d16dd-secret-key"
JWT_LIFE_SPAN = 24 * 60 * 60 # seconds
JWT_AUTH_HEADER = "Auth"
EMAIL_TOKEN_LIFE_SPAN = 1 * 60 * 60 # seconds
REDIS_URL = environ.get("REDIS_URL") or "redis://127.0.0.1"
MAILGUN_API_KEY = environ.get("MAILGUN_API_KEY")
MAILGUN_DOMAIN = "sandbox6afe2b71cd354c8fa59e0b868c20a23b.mailgun.org"
OAUTH_PROVIDERS = ("GITHUB", "FACEBOOK", "GOOGLE")
OAUTH_CLIENTS = {}
for provider in OAUTH_PROVIDERS: