2020-01-20 15:00:56 +01:00
|
|
|
import pyotp
|
|
|
|
from flask import jsonify, request
|
2020-07-04 10:39:38 +02:00
|
|
|
from flask_login import login_user
|
2020-04-25 11:30:09 +02:00
|
|
|
from itsdangerous import Signer
|
2020-01-20 15:00:56 +01:00
|
|
|
|
|
|
|
from app.api.base import api_bp
|
|
|
|
from app.config import FLASK_SECRET
|
2021-10-12 14:36:47 +02:00
|
|
|
from app.db import Session
|
2022-01-20 18:42:11 +01:00
|
|
|
from app.email_utils import send_invalid_totp_login_email
|
2022-03-21 14:23:35 +01:00
|
|
|
from app.extensions import limiter
|
2020-02-05 12:05:26 +01:00
|
|
|
from app.log import LOG
|
2020-01-20 15:00:56 +01:00
|
|
|
from app.models import User, ApiKey
|
|
|
|
|
|
|
|
|
|
|
|
@api_bp.route("/auth/mfa", methods=["POST"])
|
2022-03-21 14:23:35 +01:00
|
|
|
@limiter.limit("10/minute")
|
2020-01-20 15:00:56 +01:00
|
|
|
def auth_mfa():
|
|
|
|
"""
|
|
|
|
Validate the OTP Token
|
|
|
|
Input:
|
|
|
|
mfa_token: OTP token that user enters
|
|
|
|
mfa_key: MFA key obtained in previous auth request, e.g. /api/auth/login
|
|
|
|
device: the device name, used to create an ApiKey associated with this device
|
|
|
|
Output:
|
|
|
|
200 and user info containing:
|
|
|
|
{
|
|
|
|
name: "John Wick",
|
2020-06-09 17:20:37 +02:00
|
|
|
api_key: "a long string",
|
|
|
|
email: "user email"
|
2020-01-20 15:00:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
"""
|
|
|
|
data = request.get_json()
|
|
|
|
if not data:
|
|
|
|
return jsonify(error="request body cannot be empty"), 400
|
|
|
|
|
|
|
|
mfa_token = data.get("mfa_token")
|
|
|
|
mfa_key = data.get("mfa_key")
|
|
|
|
device = data.get("device")
|
|
|
|
|
|
|
|
s = Signer(FLASK_SECRET)
|
|
|
|
try:
|
|
|
|
user_id = int(s.unsign(mfa_key))
|
2020-03-09 09:17:40 +01:00
|
|
|
except Exception:
|
2020-01-20 15:00:56 +01:00
|
|
|
return jsonify(error="Invalid mfa_key"), 400
|
|
|
|
|
|
|
|
user = User.get(user_id)
|
|
|
|
|
|
|
|
if not user:
|
|
|
|
return jsonify(error="Invalid mfa_key"), 400
|
|
|
|
elif not user.enable_otp:
|
|
|
|
return (
|
|
|
|
jsonify(error="This endpoint should only be used by user who enables MFA"),
|
|
|
|
400,
|
|
|
|
)
|
|
|
|
|
|
|
|
totp = pyotp.TOTP(user.otp_secret)
|
2022-12-16 17:54:46 +01:00
|
|
|
if not totp.verify(mfa_token, valid_window=2):
|
2022-01-20 18:42:11 +01:00
|
|
|
send_invalid_totp_login_email(user, "TOTP")
|
2020-01-20 15:00:56 +01:00
|
|
|
return jsonify(error="Wrong TOTP Token"), 400
|
|
|
|
|
2021-01-11 10:22:39 +01:00
|
|
|
ret = {"name": user.name or "", "email": user.email}
|
2020-01-20 15:00:56 +01:00
|
|
|
|
2020-02-05 12:05:26 +01:00
|
|
|
api_key = ApiKey.get_by(user_id=user.id, name=device)
|
|
|
|
if not api_key:
|
|
|
|
LOG.d("create new api key for %s and %s", user, device)
|
|
|
|
api_key = ApiKey.create(user.id, device)
|
2021-10-12 14:36:47 +02:00
|
|
|
Session.commit()
|
2020-02-05 12:05:26 +01:00
|
|
|
|
2020-01-20 15:00:56 +01:00
|
|
|
ret["api_key"] = api_key.code
|
|
|
|
|
2020-07-04 10:39:38 +02:00
|
|
|
# so user is logged in automatically on the web
|
|
|
|
login_user(user)
|
|
|
|
|
2020-01-20 15:00:56 +01:00
|
|
|
return jsonify(**ret), 200
|