auth via email
This commit is contained in:
parent
3c538d3724
commit
805db9814a
1
Pipfile
1
Pipfile
|
@ -20,6 +20,7 @@ bson = "*"
|
||||||
python-frontmatter = "*"
|
python-frontmatter = "*"
|
||||||
bs4 = "*"
|
bs4 = "*"
|
||||||
psycopg2 = "*"
|
psycopg2 = "*"
|
||||||
|
requests = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,11 @@ from starlette.requests import HTTPConnection
|
||||||
|
|
||||||
from auth.credentials import AuthCredentials, AuthUser
|
from auth.credentials import AuthCredentials, AuthUser
|
||||||
from auth.token import Token
|
from auth.token import Token
|
||||||
|
from auth.authorize import Authorize
|
||||||
from exceptions import InvalidToken, OperationNotAllowed
|
from exceptions import InvalidToken, OperationNotAllowed
|
||||||
from orm import User
|
from orm import User
|
||||||
from redis import redis
|
from redis import redis
|
||||||
from settings import JWT_AUTH_HEADER
|
from settings import JWT_AUTH_HEADER, EMAIL_TOKEN_LIFE_SPAN
|
||||||
|
|
||||||
|
|
||||||
class _Authenticate:
|
class _Authenticate:
|
||||||
|
@ -68,6 +69,25 @@ class JWTAuthenticate(AuthenticationBackend):
|
||||||
scopes = User.get_permission(user_id=payload.user_id)
|
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)
|
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):
|
def login_required(func):
|
||||||
@wraps(func)
|
@wraps(func)
|
||||||
|
|
|
@ -8,14 +8,14 @@ from auth.validations import User
|
||||||
|
|
||||||
class Authorize:
|
class Authorize:
|
||||||
@staticmethod
|
@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 user:
|
||||||
:param device:
|
:param device:
|
||||||
:param auto_delete: Whether the expiration is automatically deleted, the default is True
|
:param auto_delete: Whether the expiration is automatically deleted, the default is True
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
exp = datetime.utcnow() + timedelta(seconds=JWT_LIFE_SPAN)
|
exp = datetime.utcnow() + timedelta(seconds=life_span)
|
||||||
token = Token.encode(user, exp=exp, device=device)
|
token = Token.encode(user, exp=exp, device=device)
|
||||||
await redis.execute("SET", f"{user.id}-{token}", "True")
|
await redis.execute("SET", f"{user.id}-{token}", "True")
|
||||||
if auto_delete:
|
if auto_delete:
|
||||||
|
@ -37,13 +37,3 @@ class Authorize:
|
||||||
async def revoke_all(user: User):
|
async def revoke_all(user: User):
|
||||||
tokens = await redis.execute("KEYS", f"{user.id}-*")
|
tokens = await redis.execute("KEYS", f"{user.id}-*")
|
||||||
await redis.execute("DEL", *tokens)
|
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
27
auth/email.py
Normal 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()
|
|
@ -64,5 +64,5 @@ async def oauth_authorize(request):
|
||||||
"username" : profile["name"]
|
"username" : profile["name"]
|
||||||
}
|
}
|
||||||
user = Identity.identity_oauth(user_input)
|
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)
|
return PlainTextResponse(token)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from auth.authorize import Authorize
|
||||||
from auth.identity import Identity
|
from auth.identity import Identity
|
||||||
from auth.password import Password
|
from auth.password import Password
|
||||||
from auth.validations import CreateUser
|
from auth.validations import CreateUser
|
||||||
|
from auth.email import send_auth_email
|
||||||
from orm import User
|
from orm import User
|
||||||
from orm.base import local_session
|
from orm.base import local_session
|
||||||
from resolvers.base import mutation, query
|
from resolvers.base import mutation, query
|
||||||
|
@ -29,11 +30,8 @@ async def register(*_, email: str, password: str = ""):
|
||||||
create_user = CreateUser(**inp)
|
create_user = CreateUser(**inp)
|
||||||
create_user.username = email.split('@')[0]
|
create_user.username = email.split('@')[0]
|
||||||
if not password:
|
if not password:
|
||||||
# NOTE: 1 hour confirm_token expire
|
user = User.create(**create_user.dict())
|
||||||
confirm_token = Token.encode(create_user, datetime.now() + timedelta(hours = 1) , "email")
|
await send_auth_email(user)
|
||||||
# TODO: sendAuthEmail(confirm_token)
|
|
||||||
# без пароля не возвращаем, а высылаем токен на почту
|
|
||||||
#
|
|
||||||
return { "user": user }
|
return { "user": user }
|
||||||
else:
|
else:
|
||||||
create_user.password = Password.encode(create_user.password)
|
create_user.password = Password.encode(create_user.password)
|
||||||
|
|
|
@ -58,7 +58,7 @@ type Mutation {
|
||||||
confirmEmail(token: String!): AuthResult!
|
confirmEmail(token: String!): AuthResult!
|
||||||
requestPasswordReset(email: String!): Boolean!
|
requestPasswordReset(email: String!): Boolean!
|
||||||
confirmPasswordReset(token: String!): Boolean!
|
confirmPasswordReset(token: String!): Boolean!
|
||||||
registerUser(email: String!, password: String!): AuthResult!
|
registerUser(email: String!, password: String): AuthResult!
|
||||||
# updatePassword(password: String!, token: String!): Token!
|
# updatePassword(password: String!, token: String!): Token!
|
||||||
# invalidateAllTokens: Boolean!
|
# invalidateAllTokens: Boolean!
|
||||||
# invalidateTokenById(id: Int!): Boolean!
|
# invalidateTokenById(id: Int!): Boolean!
|
||||||
|
|
|
@ -8,8 +8,12 @@ JWT_ALGORITHM = "HS256"
|
||||||
JWT_SECRET_KEY = "8f1bd7696ffb482d8486dfbc6e7d16dd-secret-key"
|
JWT_SECRET_KEY = "8f1bd7696ffb482d8486dfbc6e7d16dd-secret-key"
|
||||||
JWT_LIFE_SPAN = 24 * 60 * 60 # seconds
|
JWT_LIFE_SPAN = 24 * 60 * 60 # seconds
|
||||||
JWT_AUTH_HEADER = "Auth"
|
JWT_AUTH_HEADER = "Auth"
|
||||||
|
EMAIL_TOKEN_LIFE_SPAN = 1 * 60 * 60 # seconds
|
||||||
REDIS_URL = environ.get("REDIS_URL") or "redis://127.0.0.1"
|
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_PROVIDERS = ("GITHUB", "FACEBOOK", "GOOGLE")
|
||||||
OAUTH_CLIENTS = {}
|
OAUTH_CLIENTS = {}
|
||||||
for provider in OAUTH_PROVIDERS:
|
for provider in OAUTH_PROVIDERS:
|
||||||
|
|
Loading…
Reference in New Issue
Block a user