def login(request, default_next='/', staff_protocol='https'):
    def is_secure_password(pw):
        has = lambda cs: any(char in cs for char in pw)
        return len(pw) >= 8 and has(string.lowercase) and has(string.uppercase) and has(string.digits)

    def valid_slug(raw):
        raw = raw.lstrip('#').strip()
        allowed = string.letters + string.digits + '_'
        def process(c):
            if c in allowed:
                return c
            elif c in string.whitespace:
                return '_'
            else:
                return ''

        return (''.join(map(process, raw)))[:20]

    cookies_to_delete = []
    next_ = get_next(request)

    if request.method == 'GET':
        return r2r_jinja('user/login.html', locals(), request)

    signed_request = request.POST.get(u'signed_request', None)
    facebook_id = request.POST.get(u'facebook_id', None)

    if signed_request and facebook_id:
        user = authenticate(request, facebook_id, signed_request)
        if user is None:
            return r2r_jinja('user/login.html', locals(), request)
        # this is a total hack because we don't care to write a backend for the above authenticate method
        user.backend = settings.AUTHENTICATION_BACKENDS[0]
    else:
        username = valid_slug(request.POST.get('username', ''))
        password = request.POST.get('password')

        if check_rate_limit(request, username):
            message = "Too many retries. Wait a minute and try again."
            return r2r_jinja('user/login.html', locals(), request)

        user = auth.authenticate(username=username, password=password)
        if user is None:
            if User.objects.filter(username=username).exists():
                message = "Incorrect username or password."
            else:
                message = "Incorrect username or password."
            return r2r_jinja('user/login.html', locals(), request)

        if user.is_staff:
            if is_secure_password(password):
                next_ = make_absolute_url(next_ or default_next, protocol=staff_protocol)
            else:
                message = ("User is staff and has an insecure password. Please create a more secure one (8 or more "
                           "characters, mixed case and has numbers). Use password reset to fix this.")
                return r2r_jinja('user/login.html', locals(), request)

    auth.login(request, user)

    try:
        (key, post_data) = after_signup.get_posted_comment(request)
        if post_data:
            next_ = post_comment(request, user, post_data, persist_url=False).details().url
            cookies_to_delete.append(after_signup.make_cookie_key('post_comment'))
    except KeyError:
        pass

    def cleanup(response):
        for k in cookies_to_delete:
            response.delete_cookie(k)
        return response

    if next_:
        next_params = request.GET.copy()
        if 'next' in next_params:
            del next_params['next']
        next_params = '?' + urllib.urlencode(next_params) if next_params else ''
        return cleanup(HttpResponseRedirect(next_ + next_params))
    else:
        return cleanup(HttpResponseRedirect('/'))
def get_signup_context(request, skip_invite_code=None, template="user/signup.django.html",
                       cookies_to_set={}, cookies_to_delete=[]):
    """
    Returns an error context (or dict) if the signup is not successful.

    Returns `None` for successful signups.

    `cookies_to_set` and `cookies_to_delete` should be passed empty so that this functin may append to them.
    `cookies_to_set` is for session cookies, to tie into after_signup.py / after_signup.js.
    """
    skip_invite_code = skip_invite_code or request.GET.get('skip_invite_code', '').lower()
    bypass_copy = settings.SHORT_CODE_COPY.get(skip_invite_code)
    skippable_codes = (['dicksoup', 'angelgate', 'friends_and_family', 'herpderp', 'fffffat', 'buzzfeedbrews']
                       + settings.SHORT_CODES)

    login_url = '/login'
    if request.REQUEST.get('next'):
        next = request.REQUEST['next']
        params = [urllib.urlencode({'next': next})]
        if request.method == 'POST':
            next_params = request.POST.get('next_params', '')
        else:
            next_params = request.GET.copy()
            del next_params['next']
            next_params = urllib.urlencode(next_params)
        if next_params:
            params.append(next_params)
        login_url = login_url + '?' + u'&'.join(params)

    try:
        fb_user, fb_api = util.get_fb_api(request)
    except NotLoggedIntoFacebookError:
        fb_user, fb_api = None, None

    fb_uid = fb_user.get('uid') if fb_user else None
    fb_invite = None
    if request.COOKIES.get('fb_message_id'):
        fb_invite = FacebookInvite.objects.get_or_none(fb_message_id=request.COOKIES.get('fb_message_id'))
        cookies_to_delete.append('fb_message_id')

    if not fb_invite and fb_uid:
        fb_invite = FacebookInvite.get_invite(fb_user.get('uid'))

    if request.method == 'GET':
        return locals()

    username = request.POST.get('username', '')
    password = request.POST.get('password', '')
    email = request.POST.get('email', '')
    if not fb_uid:
        fb_uid = request.POST.get('facebook_id', None)

    def error(message, context=locals()):
        context['message'] = message
        Metrics.signup_form_invalid.record(request)
        return context

    if check_rate_limit(request, username):
        return error("Too many failed signup attempts. Wait a minute and try again.")

    if not password:
        return error("Password required.")
    if not User.validate_password(password):
        return error("Sorry, your password is too short. Please use 5 or more characters.")

    error_msg = User.validate_username(username)
    if error_msg:
        return error(error_msg)

    if not User.validate_email(email):
        return error("Please enter a valid email address.")

    if not User.email_is_unused(email):
        return error("This email address is already in use. Try <a href='/login'>signing in</a> "
                    "or <a href='/password_reset'>resetting</a> your password if you've forgotten it.")

    if fb_uid and not UserInfo.facebook_is_unused(fb_uid):
        return error("This Facebook account is already in use. Try <a href='/login'>signing in</a> "
                    "or <a href='/password_reset'>resetting</a> your password if you've forgotten it.")

    try:
        user = User.objects.create_user(username, email, password)
    except IntegrityError:
        return error("Username taken.")

    if not fb_uid:
        fb_uid = None

    UserInfo(user=user, invite_bypass=skip_invite_code,
             facebook_id=fb_uid, enable_timeline=True).save()

    if fb_invite:
        fb_invite.invitee = user
        fb_invite.save()

    user = auth.authenticate(username=username, password=password)

    # Handle following featured groups and optionally one defined by their short code.
    if skip_invite_code:
        autofollow = settings.SHORT_CODE_AUTOFOLLOW.get(skip_invite_code)
        if autofollow:
            to_follow.append(autofollow)

    economy.grant_daily_free_stickers(request.user, force=True, count=knobs.SIGNUP_FREE_STICKERS)

    # Follow the Canvas account.
    try:
        user.redis.following.sadd(User.objects.get(username=settings.CANVAS_ACCOUNT_USERNAME).id)
    except User.DoesNotExist:
        pass

    # Logged-out remix?
    cookie_key, post_data = after_signup.get_posted_comment(request)
    if post_data:
        post_comment(request, user, post_data)
        cookies_to_delete.append(cookie_key)

    inviter_id = request.session.get('inviter')
    if inviter_id:
        user.kv.inviter = inviter_id
        del request.session['inviter']

        inviter = User.objects.get(pk=inviter_id)
        user.follow(inviter)
        inviter.follow(user)

    # DEPRECATED. Use after_signup.py / after_signup.js now instead.
    extra_info = request.POST.get("info")
    if extra_info:
        extra_info = util.loads(extra_info)

        if extra_info.get('in_flow') == 'yes':
            fact.record('flow_signup', request, {})

        # A user may have come to signup by remixing/replying, and we've got their post data to submit and send them
        # to.
        if not post_data:
            post_data = extra_info.get('post')
            if post_data:
                post_comment(request, user, post_data)

    old_session_key = request.session.session_key

    def _after_signup():
        if fb_api:
            app_requests = fb_api.get_object('/me/apprequests/').get('data', [])
            for app_request in app_requests:
                if id in app_request:
                    fb.delete_object(app_request['id'])

        Metrics.signup.record(request, old_session_key=old_session_key, username=username, email=email)

        if 'failed_signup' in request.session:
            del request.session['failed_signup']
            Metrics.signup_second_try.record(request)

        if template == 'signup/_signup_prompt.html':
            Metrics.signup_prompt.record(request)
        else:
            Metrics.signup_main.record(request)

    bgwork.defer(_after_signup)

    # auth.login starts a new session and copies the session data from the old one to the new one
    auth.login(request, user)

    experiments.migrate_from_request_to_user(request, user)
Exemple #3
0
def signup(request, username, password, email, facebook_access_token=None):
    if check_rate_limit(request, username):
        raise ServiceError("Too many signup attempts. Wait a minute and try again.")

    try:
        return _login(request, password, username=username, email=email)
    except ValidationError:
        pass

    migrated_from_canvas_account = False
    errors = defaultdict(list)

    fb_user = None
    if facebook_access_token:
        fb_user = FacebookUser.create_from_access_token(facebook_access_token)

    def username_taken():
        # Ugly hack.
        for error in errors['username']:
            if 'taken' in error:
                return

        errors['username'].append("Sorry! That username is already taken.")

    if not password:
        errors['password'].append("Please enter a password.")

    if not User.validate_password(password):
        errors['password'].append("Sorry, your password is too short. "
                                  "Please use {} or more characters.".format(User.MINIMUM_PASSWORD_LENGTH))

    if not email:
        errors['email'].append("Please enter your email address.")
    elif not User.validate_email(email):
        errors['email'].append("Please enter a valid email address.")

    username_error = User.validate_username(username)
    if username_error:
        errors['username'].append(username_error)

    if not User.email_is_unused(email):
        errors['email'].append("Sorry! That email address is already being used for an account.")
    elif User.is_username_reserved(username):
        try:
            user = User.migrate_canvas_user(request, username, password, email=email)
        except IntegrityError:
            username_taken()
        except ValidationError:
            errors['username'] = ["""Sorry! This username is taken. Please pick a different username, """ +
                                  """or if you are "{}," enter your password to sign in.""".format(username)]
        else:
            migrated_from_canvas_account = True

    if errors:
        if fb_user:
            fb_user.delete()
        raise ValidationError(errors)

    if not migrated_from_canvas_account:
        try:
            user = User.objects.create_user(username, email, password)
        except IntegrityError:
            username_taken()
            raise ValidationError(errors)

        UserInfo.objects.create(user=user)

    if fb_user:
        fb_user.user = user
        fb_user.save()
        fb_user.notify_friends_of_signup(facebook_access_token)

        user.migrate_facebook_avatar(request, facebook_access_token)

    user = auth.authenticate(username=username, password=password)

    # auth.login starts a new session and copies the session data from the old one to the new one
    auth.login(request, user)

    return {
        'user': PrivateUserDetails.from_id(user.id).to_client(),
        'user_bio': user.userinfo.bio_text,
        'user_subscribed_to_starred': is_subscribed(user, 'starred'),
        'sessionid': request.session.session_key,
        'migrated_from_canvas_account': migrated_from_canvas_account,
    }
Exemple #4
0
def signup(request, username, password, email, facebook_access_token=None):
    if check_rate_limit(request, username):
        raise ServiceError(
            "Too many signup attempts. Wait a minute and try again.")

    try:
        return _login(request, password, username=username, email=email)
    except ValidationError:
        pass

    migrated_from_canvas_account = False
    errors = defaultdict(list)

    fb_user = None
    if facebook_access_token:
        fb_user = FacebookUser.create_from_access_token(facebook_access_token)

    def username_taken():
        # Ugly hack.
        for error in errors['username']:
            if 'taken' in error:
                return

        errors['username'].append("Sorry! That username is already taken.")

    if not password:
        errors['password'].append("Please enter a password.")

    if not User.validate_password(password):
        errors['password'].append("Sorry, your password is too short. "
                                  "Please use {} or more characters.".format(
                                      User.MINIMUM_PASSWORD_LENGTH))

    if not email:
        errors['email'].append("Please enter your email address.")
    elif not User.validate_email(email):
        errors['email'].append("Please enter a valid email address.")

    username_error = User.validate_username(username)
    if username_error:
        errors['username'].append(username_error)

    if not User.email_is_unused(email):
        errors['email'].append(
            "Sorry! That email address is already being used for an account.")
    elif User.is_username_reserved(username):
        try:
            user = User.migrate_canvas_user(request,
                                            username,
                                            password,
                                            email=email)
        except IntegrityError:
            username_taken()
        except ValidationError:
            errors['username'] = [
                """Sorry! This username is taken. Please pick a different username, """
                + """or if you are "{}," enter your password to sign in.""".
                format(username)
            ]
        else:
            migrated_from_canvas_account = True

    if errors:
        if fb_user:
            fb_user.delete()
        raise ValidationError(errors)

    if not migrated_from_canvas_account:
        try:
            user = User.objects.create_user(username, email, password)
        except IntegrityError:
            username_taken()
            raise ValidationError(errors)

        UserInfo.objects.create(user=user)

    if fb_user:
        fb_user.user = user
        fb_user.save()
        fb_user.notify_friends_of_signup(facebook_access_token)

        user.migrate_facebook_avatar(request, facebook_access_token)

    user = auth.authenticate(username=username, password=password)

    # auth.login starts a new session and copies the session data from the old one to the new one
    auth.login(request, user)

    return {
        'user': PrivateUserDetails.from_id(user.id).to_client(),
        'user_bio': user.userinfo.bio_text,
        'user_subscribed_to_starred': is_subscribed(user, 'starred'),
        'sessionid': request.session.session_key,
        'migrated_from_canvas_account': migrated_from_canvas_account,
    }
def signup(request,
           username,
           password,
           email,
           facebook_access_token=None,
           twitter_access_token=None,
           twitter_access_token_secret=None):
    if check_rate_limit(request, username):
        raise ServiceError(
            _("Too many signup attempts. Wait a minute and try again."))

    if not _validate_charset(username):
        raise ValidationError({
            'username': [
                _("Usernames can only contain the letters a-z or A-Z, digits, and underscores."
                  )
            ],
        })

    if not _validate_charset(email):
        raise ValidationError({
            'email': [
                _("Sorry, your email address contains invalid characters. Please remove them and try again."
                  )
            ],
        })

    try:
        return _login(request, password, username=username, email=email)
    except ValidationError:
        pass

    errors = defaultdict(list)

    fb_user = None
    twitter_user = None

    if facebook_access_token:
        fb_user = FacebookUser.get_or_create_from_access_token(
            facebook_access_token)
    elif twitter_access_token and twitter_access_token_secret:
        twitter_user = TwitterUser.get_or_create_from_access_token(
            twitter_access_token, twitter_access_token_secret)

    def username_taken():
        # Ugly hack.
        for error in errors['username']:
            if 'taken' in error:
                return

        errors['username'].append(_("Sorry! That username is already taken."))

    if not password:
        errors['password'].append(_("Please enter a password."))

    if not User.validate_password(password):
        errors['password'].append(
            _(u"Sorry, your password is too short. Please use %(count)d or more characters."
              % {'count': User.MINIMUM_PASSWORD_LENGTH}))

    if not email:
        errors['email'].append(_("Please enter your email address."))
    elif not User.validate_email(email):
        errors['email'].append(_("Please enter a valid email address."))

    username_error = User.validate_username(username)
    if username_error:
        errors['username'].append(username_error)

    if not User.email_is_unused(email):
        errors['email'].append(
            _("Sorry! That email address is already being used for an account. Try signing in instead, or use another email address."
              ))

    if errors:
        if fb_user:
            fb_user.delete()
        raise ValidationError(errors)

    try:
        user = User.objects.create_user(username, email, password)
    except IntegrityError:
        username_taken()
        raise ValidationError(errors)

    ui = UserInfo.objects.create(user=user)
    ui.update_hashes()

    if request.app_version is not None:
        user.kv.signup_app_version.set(request.app_version)

    if fb_user:
        fb_user.user = user
        fb_user.save()

        fb_user.notify_friends_of_signup(facebook_access_token)
        fb_user.respond_to_apprequest_invites(facebook_access_token)

        user.migrate_facebook_avatar(request, facebook_access_token)
    elif twitter_user:
        twitter_user.user = user
        twitter_user.save()

        twitter_user.notify_followers_of_signup(twitter_access_token,
                                                twitter_access_token_secret)
        twitter_user.auto_follow_from_invite(twitter_access_token,
                                             twitter_access_token_secret)

        @bgwork.defer
        def migrate_twitter_avatar():
            user.migrate_twitter_avatar(request, twitter_access_token,
                                        twitter_access_token_secret)

    user = auth.authenticate(username=username, password=password)

    # auth.login starts a new session and copies the session data from the old one to the new one
    auth.login(request, user)

    return {
        'user':
        PrivateUserDetails.from_id(user.id).to_client(),
        'user_bio':
        user.userinfo.bio_text,
        'user_subscribed_to_starred':
        is_subscribed(user, 'starred'),
        'comment_count':
        0,
        'quest_count':
        Quest.all_objects.filter(author=user).count(),
        'sessionid':
        request.session.session_key,
        'migrated_from_canvas_account':
        False,
        'login':
        False,
        'heavy_state_sync':
        heavy_state_sync(request.user,
                         app_version=request.app_version,
                         app_version_tuple=request.app_version_tuple),
    }
Exemple #6
0
def get_signup_context(request,
                       skip_invite_code=None,
                       template="user/signup.django.html",
                       cookies_to_set={},
                       cookies_to_delete=[]):
    """
    Returns an error context (or dict) if the signup is not successful.

    Returns `None` for successful signups.

    `cookies_to_set` and `cookies_to_delete` should be passed empty so that this functin may append to them.
    `cookies_to_set` is for session cookies, to tie into after_signup.py / after_signup.js.
    """
    skip_invite_code = skip_invite_code or request.GET.get(
        'skip_invite_code', '').lower()
    bypass_copy = settings.SHORT_CODE_COPY.get(skip_invite_code)
    skippable_codes = ([
        'dicksoup', 'angelgate', 'friends_and_family', 'herpderp', 'fffffat',
        'buzzfeedbrews'
    ] + settings.SHORT_CODES)

    login_url = '/login'
    if request.REQUEST.get('next'):
        next = request.REQUEST['next']
        params = [urllib.urlencode({'next': next})]
        if request.method == 'POST':
            next_params = request.POST.get('next_params', '')
        else:
            next_params = request.GET.copy()
            del next_params['next']
            next_params = urllib.urlencode(next_params)
        if next_params:
            params.append(next_params)
        login_url = login_url + '?' + u'&'.join(params)

    try:
        fb_user, fb_api = util.get_fb_api(request)
    except NotLoggedIntoFacebookError:
        fb_user, fb_api = None, None

    fb_uid = fb_user.get('uid') if fb_user else None
    fb_invite = None
    if request.COOKIES.get('fb_message_id'):
        fb_invite = FacebookInvite.objects.get_or_none(
            fb_message_id=request.COOKIES.get('fb_message_id'))
        cookies_to_delete.append('fb_message_id')

    if not fb_invite and fb_uid:
        fb_invite = FacebookInvite.get_invite(fb_user.get('uid'))

    if request.method == 'GET':
        return locals()

    username = request.POST.get('username', '')
    password = request.POST.get('password', '')
    email = request.POST.get('email', '')
    if not fb_uid:
        fb_uid = request.POST.get('facebook_id', None)

    code = InviteCode.objects.get_or_none(code=request.POST.get('code'))

    def error(message, context=locals()):
        context['message'] = message
        Metrics.signup_form_invalid.record(request)
        return context

    if check_rate_limit(request, username):
        return error(
            "Too many failed signup attempts. Wait a minute and try again.")

    if not password:
        return error("Password required.")
    if not User.validate_password(password):
        return error(
            "Sorry, your password is too short. Please use 5 or more characters."
        )

    error_msg = User.validate_username(username)
    if error_msg:
        return error(error_msg)

    if not User.validate_email(email):
        return error("Please enter a valid email address.")

    if not User.email_is_unused(email):
        return error(
            "This email address is already in use. Try <a href='/login'>signing in</a> "
            "or <a href='/password_reset'>resetting</a> your password if you've forgotten it."
        )

    if fb_uid and not UserInfo.facebook_is_unused(fb_uid):
        return error(
            "This Facebook account is already in use. Try <a href='/login'>signing in</a> "
            "or <a href='/password_reset'>resetting</a> your password if you've forgotten it."
        )

    try:
        user = User.objects.create_user(username, email, password)
    except IntegrityError:
        return error("Username taken.")

    if not fb_uid:
        fb_uid = None

    UserInfo(user=user,
             invite_bypass=skip_invite_code,
             facebook_id=fb_uid,
             enable_timeline=True).save()

    if code:
        code.invitee = user
        code.save()

    if fb_invite:
        fb_invite.invitee = user
        fb_invite.save()

    user = auth.authenticate(username=username, password=password)

    # Handle following featured groups and optionally one defined by their short code.
    if skip_invite_code:
        autofollow = settings.SHORT_CODE_AUTOFOLLOW.get(skip_invite_code)
        if autofollow:
            to_follow.append(autofollow)

    economy.grant_daily_free_stickers(request.user,
                                      force=True,
                                      count=knobs.SIGNUP_FREE_STICKERS)

    # Follow the Canvas account.
    try:
        user.redis.following.sadd(
            User.objects.get(username=settings.CANVAS_ACCOUNT_USERNAME).id)
    except User.DoesNotExist:
        pass

    # Logged-out remix?
    cookie_key, post_data = after_signup.get_posted_comment(request)
    if post_data:
        post_comment(request, user, post_data)
        cookies_to_delete.append(cookie_key)

    inviter_id = request.session.get('inviter')
    if inviter_id:
        user.kv.inviter = inviter_id
        del request.session['inviter']

        inviter = User.objects.get(pk=inviter_id)
        user.follow(inviter)
        inviter.follow(user)

    # DEPRECATED. Use after_signup.py / after_signup.js now instead.
    extra_info = request.POST.get("info")
    if extra_info:
        extra_info = util.loads(extra_info)

        if extra_info.get('in_flow') == 'yes':
            fact.record('flow_signup', request, {})

        # A user may have come to signup by remixing/replying, and we've got their post data to submit and send them
        # to.
        if not post_data:
            post_data = extra_info.get('post')
            if post_data:
                post_comment(request, user, post_data)

    old_session_key = request.session.session_key

    def _after_signup():
        if fb_api:
            app_requests = fb_api.get_object('/me/apprequests/').get(
                'data', [])
            for app_request in app_requests:
                if id in app_request:
                    fb.delete_object(app_request['id'])

        Metrics.signup.record(request,
                              old_session_key=old_session_key,
                              username=username,
                              email=email)

        if 'failed_signup' in request.session:
            del request.session['failed_signup']
            Metrics.signup_second_try.record(request)

        if template == 'signup/_signup_prompt.html':
            Metrics.signup_prompt.record(request)
        else:
            Metrics.signup_main.record(request)

    bgwork.defer(_after_signup)

    # auth.login starts a new session and copies the session data from the old one to the new one
    auth.login(request, user)

    experiments.migrate_from_request_to_user(request, user)
def signup(request, username, password, email,
           facebook_access_token=None,
           twitter_access_token=None, twitter_access_token_secret=None):
    if check_rate_limit(request, username):
        raise ServiceError(_("Too many signup attempts. Wait a minute and try again."))

    if not _validate_charset(username):
        raise ValidationError({
            'username': [_("Usernames can only contain the letters a-z or A-Z, digits, and underscores.")],
        })

    if not _validate_charset(email):
        raise ValidationError({
            'email': [_("Sorry, your email address contains invalid characters. Please remove them and try again.")],
        })

    try:
        return _login(request, password, username=username, email=email)
    except ValidationError:
        pass

    errors = defaultdict(list)

    fb_user = None
    twitter_user = None

    if facebook_access_token:
        fb_user = FacebookUser.get_or_create_from_access_token(facebook_access_token)
    elif twitter_access_token and twitter_access_token_secret:
        twitter_user = TwitterUser.get_or_create_from_access_token(twitter_access_token, twitter_access_token_secret)

    def username_taken():
        # Ugly hack.
        for error in errors['username']:
            if 'taken' in error:
                return

        errors['username'].append(_("Sorry! That username is already taken."))

    if not password:
        errors['password'].append(_("Please enter a password."))

    if not User.validate_password(password):
        errors['password'].append(_(u"Sorry, your password is too short. Please use %(count)d or more characters." % {'count': User.MINIMUM_PASSWORD_LENGTH}))

    if not email:
        errors['email'].append(_("Please enter your email address."))
    elif not User.validate_email(email):
        errors['email'].append(_("Please enter a valid email address."))

    username_error = User.validate_username(username)
    if username_error:
        errors['username'].append(username_error)

    if not User.email_is_unused(email):
        errors['email'].append(_("Sorry! That email address is already being used for an account. Try signing in instead, or use another email address."))

    if errors:
        if fb_user:
            fb_user.delete()
        raise ValidationError(errors)

    try:
        user = User.objects.create_user(username, email, password)
    except IntegrityError:
        username_taken()
        raise ValidationError(errors)

    ui = UserInfo.objects.create(user=user)
    ui.update_hashes()

    if request.app_version is not None:
        user.kv.signup_app_version.set(request.app_version)

    if fb_user:
        fb_user.user = user
        fb_user.save()

        fb_user.notify_friends_of_signup(facebook_access_token)
        fb_user.respond_to_apprequest_invites(facebook_access_token)

        user.migrate_facebook_avatar(request, facebook_access_token)
    elif twitter_user:
        twitter_user.user = user
        twitter_user.save()

        twitter_user.notify_followers_of_signup(twitter_access_token, twitter_access_token_secret)
        twitter_user.auto_follow_from_invite(twitter_access_token, twitter_access_token_secret)

        @bgwork.defer
        def migrate_twitter_avatar():
            user.migrate_twitter_avatar(request, twitter_access_token, twitter_access_token_secret)

    user = auth.authenticate(username=username, password=password)

    # auth.login starts a new session and copies the session data from the old one to the new one
    auth.login(request, user)

    return {
        'user': PrivateUserDetails.from_id(user.id).to_client(),
        'user_bio': user.userinfo.bio_text,
        'user_subscribed_to_starred': is_subscribed(user, 'starred'),
        'comment_count': 0,
        'quest_count': Quest.all_objects.filter(author=user).count(),
        'sessionid': request.session.session_key,
        'migrated_from_canvas_account': False,
        'login': False,
        'heavy_state_sync': heavy_state_sync(request.user, app_version=request.app_version, app_version_tuple=request.app_version_tuple),
    }