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))
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)
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
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)
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)
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)
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)
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, })
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
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))
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 })
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() })
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)
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 })
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)
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)
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)
def get_user(view): if settings.TEST: return getattr(view.request, 'user', None) else: return Session.get_user(view.request.COOKIES.get('session'))