예제 #1
0
파일: models.py 프로젝트: matts1/Kno
    def test_sessions(self):
        id = Session.create(self.user())
        session = Session.get_user(id)
        self.assertEqual(session, self.user())

        session.expiry = timezone.now()
        session.save()
        Session.delete_old()

        self.assertIsNone(Session.get_user(session))
예제 #2
0
def debug_random_login(request):
    if not settings.DEBUG:
        raise AccessDenied(title="Эта фича доступна только при DEBUG=true")

    slug = "random_" + random_string()
    user, is_created = User.objects.get_or_create(
        slug=slug,
        defaults=dict(
            patreon_id=random_string(),
            membership_platform_type=User.MEMBERSHIP_PLATFORM_PATREON,
            email=slug + "@random.dev",
            full_name="%s %d y.o. Developer" % (random.choice(["Максим", "Олег"]), random.randint(18, 101)),
            company="Acme Corp.",
            position=random.choice(["Подниматель пингвинов", "Опускатель серверов", "Коллектор пивных бутылок"]),
            balance=10000,
            membership_started_at=datetime.utcnow(),
            membership_expires_at=datetime.utcnow() + timedelta(days=365 * 100),
            created_at=datetime.utcnow(),
            updated_at=datetime.utcnow(),
            is_email_verified=True,
            moderation_status=User.MODERATION_STATUS_APPROVED,
        ),
    )

    if is_created:
        Post.upsert_user_intro(user, "Интро как интро, аппрув прошло :Р", is_visible=True)

    session = Session.create_for_user(user)

    return set_session_cookie(redirect("profile", user.slug), user, session)
예제 #3
0
    def post(self, **kwargs):

        session = kwargs['session']

        data = request.get_json()

        password = data['password']
        nickname = data['nickname']

        user = session.query(User).filter(User.nickname == nickname).first()

        if not user:
            return {'status': 'failed'}, 400

        check_password = verify_password(user.password, password)

        if nickname == user.nickname and check_password and user.is_active:

            token = jwt.encode(
                {
                    'user_id':
                    user.id,
                    'exp':
                    datetime.datetime.utcnow() +
                    datetime.timedelta(minutes=60),
                }, app.config.get('SECRET_KEY'))

            new_token = Session(user_id=user.id, session_token=token)
            session.add(new_token)
            session.commit()
            return {'token': token.decode('UTF-8')}, 201

        return {'status': 'failed'}, 400
예제 #4
0
    def _decorator(*args, **kwargs):
        from members.models import Member
        from organizations.models import Organization

        session = Session.find_by_access(request.args.get('key'))

        if not session:
            return 'Session has expired.', 401

        if not request.args.get('organization'):
            return 'Organization is required.', 401

        member = Member.query.filter(Member.profile_id == session.profile_id) \
                              .join(Member.organization) \
                              .filter(Organization.name == request.args.get('organization')) \
                              .first()

        if not member:
            return abort(404)

        g.member = member
        g.organization = member.organization
        g.application = member.organization.application

        return view_func(*args, **kwargs)
예제 #5
0
def debug_dev_login(request):
    if not settings.DEBUG:
        raise AccessDenied(title="Эта фича доступна только при DEBUG=true")

    user, is_created = User.objects.get_or_create(
        slug="dev",
        defaults=dict(
            patreon_id="123456",
            membership_platform_type=User.MEMBERSHIP_PLATFORM_PATREON,
            email="*****@*****.**",
            full_name="Senior 23 y.o. Developer",
            company="FAANG",
            position="Team Lead конечно",
            balance=10000,
            membership_started_at=datetime.utcnow(),
            membership_expires_at=datetime.utcnow() +
            timedelta(days=365 * 100),
            created_at=datetime.utcnow(),
            updated_at=datetime.utcnow(),
            is_email_verified=True,
            moderation_status=User.MODERATION_STATUS_APPROVED,
            roles=["god"],
        ),
    )

    if is_created:
        Post.upsert_user_intro(user, "Очень плохое интро", is_visible=True)

    session = Session.create_for_user(user)

    return set_session_cookie(redirect("profile", user.slug), user, session)
예제 #6
0
def email_login_code(request):
    email = request.GET.get("email")
    code = request.GET.get("code")
    if not email or not code:
        return redirect("login")

    goto = request.GET.get("goto")
    email = email.lower().strip()
    code = code.lower().strip()

    user = Code.check_code(recipient=email, code=code)
    session = Session.create_for_user(user)

    if not user.is_email_verified:
        # save 1 click and verify email
        user.is_email_verified = True
        user.save()

    if user.deleted_at:
        # cancel user deletion
        user.deleted_at = None
        user.save()

    redirect_to = reverse("profile", args=[user.slug]) if not goto else goto
    response = redirect(redirect_to)
    return set_session_cookie(response, user, session)
예제 #7
0
def debug_login(request, user_slug):
    if not (settings.DEBUG or settings.TESTS_RUN):
        raise AccessDenied(title="Эта фича доступна только при DEBUG=true")

    user = get_object_or_404(User, slug=user_slug)
    session = Session.create_for_user(user)

    return set_session_cookie(redirect("profile", user.slug), user, session)
예제 #8
0
def email_login(request):
    if request.method != "POST":
        return redirect("login")

    goto = request.POST.get("goto")
    email_or_login = request.POST.get("email_or_login")
    if not email_or_login:
        return redirect("login")

    email_or_login = email_or_login.strip()

    if "|-" in email_or_login:
        # secret_hash login
        email_part, secret_hash_part = email_or_login.split("|-", 1)
        user = User.objects.filter(email=email_part,
                                   secret_hash=secret_hash_part).first()
        if not user:
            return render(
                request, "error.html", {
                    "title":
                    "Такого юзера нет 🤔",
                    "message":
                    "Пользователь с таким кодом не найден. "
                    "Попробуйте авторизоваться по обычной почте или юзернейму.",
                })

        session = Session.create_for_user(user)
        redirect_to = reverse("profile", args=[user.slug
                                               ]) if not goto else goto
        response = redirect(redirect_to)
        return set_session_cookie(response, user, session)
    else:
        # email/nickname login
        user = User.objects.filter(
            Q(email=email_or_login.lower()) | Q(slug=email_or_login)).first()
        if not user:
            return render(
                request, "error.html", {
                    "title":
                    "Такого юзера нет 🤔",
                    "message":
                    "Пользователь с такой почтой не найден в списке членов Клуба. "
                    "Попробуйте другую почту или никнейм. "
                    "Если совсем ничего не выйдет, напишите нам, попробуем помочь.",
                })

        code = Code.create_for_user(user=user,
                                    recipient=user.email,
                                    length=settings.AUTH_CODE_LENGTH)
        async_task(send_auth_email, user, code)
        async_task(notify_user_auth, user, code)

        return render(request, "auth/email.html", {
            "email": user.email,
            "goto": goto,
        })
예제 #9
0
    def authorise(self):
        if not self.user:
            raise ValueError('Missed `user` property to use this method')

        session = Session.create_for_user(self.user)
        self.cookies["token"] = session.token
        self.cookies["token"]["expires"] = datetime.utcnow() + timedelta(days=30)
        self.cookies["token"]['httponly'] = True
        self.cookies["token"]['secure'] = True

        return self
예제 #10
0
 def initialize(self):
     self.db = dbSession
     try:
         session_id = self.get_secure_cookie('session_id').decode()
         self.session = self.db.query(Session).filter_by(
             session_key=session_id).first()
         if self.session.expire_date < datetime.datetime.utcnow():
             self.db.delete(self.session)
             self.db.commit()
             raise SessionExpired('Session Expired')
     except (AttributeError, SessionExpired):
         session_id = str(uuid.uuid4())
         self.set_secure_cookie('session_id', session_id)
         self.session = Session(session_key=session_id,
                                expire_date=datetime.datetime.utcnow() +
                                datetime.timedelta(days=1))
예제 #11
0
def validate_email(token):
    ae = AccountEmail.find_by_token(token)
    if not ae:
        abort(404)

    body = request.get_json(silent=True)
    if not body:
        body = {}

    account_id = ae.account_id
    ae.validate()

    from auth.models import Session
    ot = Session(account_id).save(True)
    return jsonify({
        'success': True,
        'token': ot.token
    })
예제 #12
0
def login():
    """
    Authenticate the user via the provided login/password
    """
    form = AuthForm.load(request)
    form.validate()

    account = Account.find_by_email(form.email.data)
    if not account:
        form.error('email', 'Invalid email/password credentials provided.')

    if not account.verify_password(form.password.data):
        form.error('email', 'Invalid email/password credentials provided.')

    ot = Session(account.id).save(True)
    return jsonify({
        'success': True,
        'token': ot.token,
        'account': account.serialize()
    })
예제 #13
0
    def _decorator(*args, **kwargs):
        from members.models import Member
        from organizations.models import Organization

        if 'app-key' not in request.headers:
            response = jsonify({'code': 401, 'message': 'App-Key header is required.'})
            response.status_code = 401
            return response

        if 'auth-token' not in request.headers:
            response = jsonify({'code': 401, 'message': 'Auth-Token header is required.'})
            response.status_code = 401
            return response

        if 'organization' not in request.headers:
            response = jsonify({'code': 401, 'message': 'Organization header is required.'})
            response.status_code = 401
            return response

        session = Session.find_by_token(request.headers['auth-token'])

        if not session or session.profile.application.app_key != request.headers['app-key']:
            response = jsonify({'code': 401, 'message': 'Session has expired.'})
            response.status_code = 401
            return response

        member = Member.query.filter(Member.profile_id == session.profile_id) \
                              .join(Member.organization) \
                              .filter(Organization.name == request.headers['organization']) \
                              .first()

        if not member:
            response = jsonify({'code': 401, 'message': 'Organization not found.'})
            response.status_code = 401
            return response

        g.member = member
        g.organization = member.organization
        g.application = member.organization.application

        return view_func(*args, **kwargs)
예제 #14
0
def lost_password():
    """
    Send a one time login link to authenticate the user.
    The link will contain an Session token that can be used directly from the app.
    """
    form = LostPasswordForm.load(request)
    form.validate()

    account = Account.find_by_email(form.email.data)
    if account:
        ot = Session(account.id)
        ot.save(True)
        ot.send()

    return jsonify({
        'success': True
    })
예제 #15
0
        def wrapper(*args, **kwargs):
            try:
                auth = request.headers.get('Authorization', None)
                assert auth is not None
                if level == 'bearer':
                    assert auth[0:6].lower() == 'bearer'

                if auth[0:6].lower() == 'bearer':
                    """
                    Authorization: Bearer XXXXXXXX
                    Is for access from the web application
                    """
                    from auth.models import Session
                    session = Session.find_by_token(auth[7:])
                    assert session is not None
                    g.account = session.account
                elif auth[0:5].lower() == 'basic':
                    """
                    Authorization: Basic sk_xxxxxx
                    Is for access from the API
                    """

                    passwd = None
                    if auth[6:8] == 'sk':
                        passwd = auth[6:]
                    else:
                        try:
                            credentials = base64.b64decode(
                                auth[6:].encode('utf-8')).decode('utf-8')
                        except Exception:
                            raise AssertionError('Invalid credential provided')

                        assert credentials.find(':') > -1
                        assert credentials.count(':') == 1
                        passwd = credentials.split(':')[
                            1]  # Authorization : api:sk_xxxxx
                        assert passwd[0:2] == 'sk'

                    from accounts.models import ApiKey
                    account = ApiKey.account_by_token(passwd)
                    assert account is not None
                    g.account = account
            except AssertionError as e:
                print(e)
                abort(401)

            remote_addr = request.remote_addr
            if request.headers.getlist("X-Forwarded-For"):
                remote_addr = request.headers.getlist("X-Forwarded-For")[0]

            key = 'rate-limit/{0}/{1}'.format(remote_addr, request.endpoint)
            ratelimit = RateLimit(key, limit)
            g._view_rate_limit = ratelimit
            if ratelimit.over_limit:
                return make_response(
                    jsonify({
                        'success':
                        False,
                        'error':
                        'You have been rate limited. Please wait {0} seconds.'.
                        format(ratelimit.reset - int(time.time())),
                        'code':
                        429
                    }), 429)

            if auth[0:5].lower() != 'basic' and g.get('_ga') is not None:
                g._ga.set_uid(g.account.uuid)

            return view_func(*args, **kwargs)
예제 #16
0
def patreon_oauth_callback(request):
    code = request.GET.get("code")
    if not code:
        return render(request, "error.html", {
            "title": "Что-то сломалось между нами и патреоном",
            "message": "Так бывает. Попробуйте залогиниться еще раз"
        })

    try:
        auth_data = patreon.fetch_auth_data(code)
        user_data = patreon.fetch_user_data(auth_data["access_token"])
    except PatreonException as ex:
        if "invalid_grant" in str(ex):
            return render(request, "error.html", {
                "title": "Тут такое дело 😭",
                "message": "Авторизация патреона — говно. "
                           "Она не сразу понимает, что вы стали патроном и отдаёт "
                           "статус «отказано» в первые несколько минут, а иногда и часов. "
                           "Я уже написал им в саппорт, но пока вам надо немного подождать и авторизоваться снова. "
                           "Если долго не будет пускать — напишите мне в личку на патреоне."
            })

        return render(request, "error.html", {
            "message": "Не получилось загрузить ваш профиль с серверов патреона. "
                       "Попробуйте еще раз, наверняка оно починится. "
                       f"Но если нет, то вот текст ошибки, с которым можно пожаловаться мне в личку:",
            "data": str(ex)
        })

    membership = patreon.parse_active_membership(user_data)
    if not membership:
        return render(request, "error.html", {
            "title": "Надо быть патроном, чтобы состоять в Клубе",
            "message": "Кажется, вы не патроните <a href=\"https://www.patreon.com/join/vas3k\">@vas3k</a>. "
                       "А это одно из основных требований для входа в Клуб.<br><br>"
                       "Ещё иногда бывает, что ваш банк отказывает патреону в снятии денег. "
                       "Проверьте, всё ли там у них в порядке."
        })

    now = datetime.utcnow()

    # get user by patreon_id or email
    user = User.objects.filter(Q(patreon_id=membership.user_id) | Q(email=membership.email.lower())).first()
    if not user:
        # user is new, create it
        try:
            user = User.objects.create(
                patreon_id=membership.user_id,
                email=membership.email.lower(),
                full_name=membership.full_name[:120],
                avatar=upload_image_from_url(membership.image) if membership.image else None,
                membership_platform_type=User.MEMBERSHIP_PLATFORM_PATREON,
                membership_started_at=membership.started_at,
                membership_expires_at=membership.expires_at,
                balance=membership.lifetime_support_cents / 100,
                created_at=now,
                updated_at=now,
                is_email_verified=False,
            )
        except IntegrityError:
            return render(request, "error.html", {
                "title": "💌 Придётся войти через почту",
                "message": "Пользователь с таким имейлом уже зарегистрирован, но не через патреон. "
                           "Чтобы защититься от угона аккаунтов через подделку почты на патреоне, "
                           "нам придётся сейчас попросить вас войти через почту."
            })
    else:
        # user exists
        if user.deleted_at:
            return render(request, "error.html", {
                "title": "💀 Аккаунт был удалён",
                "message": "Войти через этот патреон больше не получится"
            })

        # update membership dates
        user.balance = membership.lifetime_support_cents / 100  # TODO: remove when the real money comes in
        if membership.expires_at > user.membership_expires_at:
            user.membership_expires_at = membership.expires_at

    user.membership_platform_data = {
        "access_token": auth_data["access_token"],
        "refresh_token": auth_data["refresh_token"],
    }
    user.save()

    # create a new session token to authorize the user
    session = Session.create_for_user(user)
    redirect_to = reverse("profile", args=[user.slug])
    state = request.GET.get("state")
    if state:
        state_dict = dict(parse_qsl(state))
        if "goto" in state_dict:
            redirect_to = state_dict["goto"]

    response = redirect(redirect_to)
    return set_session_cookie(response, user, session)
예제 #17
0
def patreon_oauth_callback(request):
    code = request.GET.get("code")
    if not code:
        return render(
            request,
            "error.html", {
                "title": "Что-то сломалось между нами и патреоном",
                "message": "Так бывает. Попробуйте залогиниться еще раз"
            },
            status=500)

    try:
        auth_data = patreon.fetch_auth_data(code)
        user_data = patreon.fetch_user_data(auth_data["access_token"])
    except PatreonException as ex:
        if "invalid_grant" in str(ex):
            return render(
                request,
                "error.html", {
                    "title":
                    "Тут такое дело 😭",
                    "message":
                    "Авторизация патреона — говно. "
                    "Она не сразу понимает, что вы стали патроном и отдаёт "
                    "статус «отказано» в первые несколько минут, а иногда и часов. "
                    "Я уже написал им в саппорт, но пока вам надо немного подождать и авторизоваться снова. "
                    "Если долго не будет пускать — напишите мне в личку на патреоне."
                },
                status=503)

        return render(
            request,
            "error.html", {
                "message":
                "Не получилось загрузить ваш профиль с серверов патреона. "
                "Попробуйте еще раз, наверняка оно починится. "
                f"Но если нет, то вот текст ошибки, с которым можно пожаловаться мне в личку:",
                "data":
                str(ex)
            },
            status=504)

    membership = patreon.parse_active_membership(user_data)
    if not membership:
        return render(
            request,
            "error.html", {
                "title":
                "Надо быть патроном, чтобы состоять в Клубе",
                "message":
                "Кажется, вы не патроните <a href=\"https://www.patreon.com/join/vas3k\">@vas3k</a>. "
                "А это одно из основных требований для входа в Клуб.<br><br>"
                "Ещё иногда бывает, что ваш банк отказывает патреону в снятии денег. "
                "Проверьте, всё ли там у них в порядке."
            },
            status=402)

    now = datetime.utcnow()

    # get user by patreon_id or email
    user = User.objects.filter(
        Q(patreon_id=membership.user_id)
        | Q(email=membership.email.lower())).first()
    if not user:
        # user is new, do not allow patreon users to register
        return render(
            request,
            "error.html", {
                "title":
                "🤕 Регистрироваться через Патреон больше нельзя",
                "message":
                "Возможность входа через Патреон осталась только для легаси-юзеров, "
                "но создавать новые аккаунты в Клубе через него больше нельзя. "
                "Через Патреон регистрируется очень много виртуалов и прочих анонимов, "
                "так как им это дешево. Мы же устали их ловить и выгонять, "
                "потому решили полностью прикрыть регистрацию."
            },
            status=400)

    else:
        # user exists
        if user.deleted_at:
            return render(
                request,
                "error.html", {
                    "title": "💀 Аккаунт был удалён",
                    "message": "Войти через этот патреон больше не получится"
                },
                status=404)

        # update membership dates
        user.balance = membership.lifetime_support_cents / 100
        if membership.expires_at > user.membership_expires_at:
            user.membership_expires_at = membership.expires_at

    user.membership_platform_data = {
        "access_token": auth_data["access_token"],
        "refresh_token": auth_data["refresh_token"],
    }
    user.save()

    # create a new session token to authorize the user
    session = Session.create_for_user(user)
    redirect_to = reverse("profile", args=[user.slug])
    state = request.GET.get("state")
    if state:
        state_dict = dict(parse_qsl(state))
        if "goto" in state_dict:
            redirect_to = state_dict["goto"]

    response = redirect(redirect_to)
    return set_session_cookie(response, user, session)
예제 #18
0
파일: views.py 프로젝트: matts1/Kno
def get_user(view):
    if settings.TEST:
        return getattr(view.request, 'user', None)
    else:
        return Session.get_user(view.request.COOKIES.get('session'))