diff --git a/rhodecode/login/__init__.py b/rhodecode/login/__init__.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/login/__init__.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-2016  RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+
+from rhodecode.config.routing import ADMIN_PREFIX
+
+
+def includeme(config):
+
+    config.add_route(
+        name='login',
+        pattern=ADMIN_PREFIX + '/login')
+    config.add_route(
+        name='logout',
+        pattern=ADMIN_PREFIX + '/logout')
+    config.add_route(
+        name='register',
+        pattern=ADMIN_PREFIX + '/register')
+    config.add_route(
+        name='reset_password',
+        pattern=ADMIN_PREFIX + '/password_reset')
+    config.add_route(
+        name='reset_password_confirmation',
+        pattern=ADMIN_PREFIX + '/password_reset_confirmation')
+
+    # Scan module for configuration decorators.
+    config.scan()
diff --git a/rhodecode/login/views.py b/rhodecode/login/views.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/login/views.py
@@ -0,0 +1,359 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2016-2016  RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import datetime
+import formencode
+import logging
+import urlparse
+import uuid
+
+from pylons import url
+from pyramid.httpexceptions import HTTPFound
+from pyramid.i18n import TranslationStringFactory
+from pyramid.view import view_config
+from recaptcha.client.captcha import submit
+
+from rhodecode.authentication.base import loadplugin
+from rhodecode.lib.auth import (
+    AuthUser, HasPermissionAnyDecorator, CSRFRequired)
+from rhodecode.lib.base import get_ip_addr
+from rhodecode.lib.exceptions import UserCreationError
+from rhodecode.lib.utils2 import safe_str
+from rhodecode.model.db import User
+from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
+from rhodecode.model.login_session import LoginSession
+from rhodecode.model.meta import Session
+from rhodecode.model.settings import SettingsModel
+from rhodecode.model.user import UserModel
+
+
+_ = TranslationStringFactory('rhodecode-enterprise')
+
+log = logging.getLogger(__name__)
+
+
+def _store_user_in_session(session, username, remember=False):
+    user = User.get_by_username(username, case_insensitive=True)
+    auth_user = AuthUser(user.user_id)
+    auth_user.set_authenticated()
+    cs = auth_user.get_cookie_store()
+    session['rhodecode_user'] = cs
+    user.update_lastlogin()
+    Session().commit()
+
+    # If they want to be remembered, update the cookie
+    if remember:
+        _year = (datetime.datetime.now() +
+                 datetime.timedelta(seconds=60 * 60 * 24 * 365))
+        session._set_cookie_expires(_year)
+
+    session.save()
+
+    log.info('user %s is now authenticated and stored in '
+             'session, session attrs %s', username, cs)
+
+    # dumps session attrs back to cookie
+    session._update_cookie_out()
+    # we set new cookie
+    headers = None
+    if session.request['set_cookie']:
+        # send set-cookie headers back to response to update cookie
+        headers = [('Set-Cookie', session.request['cookie_out'])]
+    return headers
+
+
+class LoginView(object):
+
+    def __init__(self, context, request):
+        self.request = request
+        self.context = context
+        self.session = request.session
+        self._rhodecode_user = request.user
+
+    def _validate_came_from(self, came_from):
+        if not came_from:
+            return came_from
+
+        parsed = urlparse.urlparse(came_from)
+        allowed_schemes = ['http', 'https']
+        if parsed.scheme and parsed.scheme not in allowed_schemes:
+            log.error('Suspicious URL scheme detected %s for url %s' %
+                      (parsed.scheme, parsed))
+            came_from = url('home')
+        elif parsed.netloc and self.request.host != parsed.netloc:
+            log.error('Suspicious NETLOC detected %s for url %s server url '
+                      'is: %s' % (parsed.netloc, parsed, self.request.host))
+            came_from = url('home')
+        if any(bad_str in parsed.path for bad_str in ('\r', '\n')):
+            log.error('Header injection detected `%s` for url %s server url ' %
+                      (parsed.path, parsed))
+            came_from = url('home')
+        return came_from
+
+    def _get_came_from(self):
+        _default_came_from = url('home')
+        came_from = self._validate_came_from(
+            safe_str(self.request.GET.get('came_from', '')))
+        return came_from or _default_came_from
+
+    def _get_template_context(self):
+        return {
+            'came_from': self._get_came_from(),
+            'defaults': {},
+            'errors': {},
+        }
+
+    @view_config(
+        route_name='login', request_method='GET',
+        renderer='rhodecode:templates/login.html')
+    def login(self):
+        user = self.request.user
+
+        # redirect if already logged in
+        if user.is_authenticated and not user.is_default and user.ip_allowed:
+            raise HTTPFound(self._get_came_from())
+
+        return self._get_template_context()
+
+    @view_config(
+        route_name='login', request_method='POST',
+        renderer='rhodecode:templates/login.html')
+    def login_post(self):
+        came_from = self._get_came_from()
+        session = self.request.session
+        login_form = LoginForm()()
+
+        try:
+            session.invalidate()
+            form_result = login_form.to_python(self.request.params)
+            # form checks for username/password, now we're authenticated
+            headers = _store_user_in_session(
+                self.session,
+                username=form_result['username'],
+                remember=form_result['remember'])
+            raise HTTPFound(came_from, headers=headers)
+        except formencode.Invalid as errors:
+            defaults = errors.value
+            # remove password from filling in form again
+            del defaults['password']
+            render_ctx = self._get_template_context()
+            render_ctx.update({
+                'errors': errors.error_dict,
+                'defaults': defaults,
+            })
+            return render_ctx
+
+        except UserCreationError as e:
+            # container auth or other auth functions that create users on
+            # the fly can throw this exception signaling that there's issue
+            # with user creation, explanation should be provided in
+            # Exception itself
+            session.flash(e, queue='error')
+
+        # check if we use container plugin, and try to login using it.
+        from rhodecode.authentication.base import authenticate, HTTP_TYPE
+        try:
+            log.debug('Running PRE-AUTH for container based authentication')
+            auth_info = authenticate(
+                '', '', self.request.environ, HTTP_TYPE, skip_missing=True)
+        except UserCreationError as e:
+            log.error(e)
+            session.flash(e, queue='error')
+            # render login, with flash message about limit
+            return self._get_template_context()
+
+        if auth_info:
+            headers = _store_user_in_session(auth_info.get('username'))
+            raise HTTPFound(came_from, headers=headers)
+
+        return self._get_template_context()
+
+    @CSRFRequired()
+    @view_config(route_name='logout', request_method='POST')
+    def logout(self):
+        LoginSession().destroy_user_session()
+        return HTTPFound(url('home'))
+
+    @HasPermissionAnyDecorator(
+        'hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')
+    @view_config(
+        route_name='register', request_method='GET',
+        renderer='rhodecode:templates/register.html',)
+    def register(self):
+        settings = SettingsModel().get_all_settings()
+
+        social_data = self.session.get('rhodecode.social_auth')
+        form_defaults = {}
+        if social_data:
+            password = str(uuid.uuid4())
+            form_defaults = {
+                'username': social_data['user'].get('user_name'),
+                'password': password,
+                'password_confirmation': password,
+                'email': social_data['user'].get('email'),
+            }
+
+        auto_active = 'hg.register.auto_activate' in User.get_default_user()\
+            .AuthUser.permissions['global']
+        captcha_private_key = settings.get('rhodecode_captcha_private_key')
+        render_ctx = self._get_template_context()
+        render_ctx.update({
+            'auto_active': auto_active,
+            'captcha_active': bool(captcha_private_key),
+            'captcha_public_key': settings.get('rhodecode_captcha_public_key'),
+            'defaults': form_defaults,
+            'register_message': settings.get('rhodecode_register_message') or '',
+        })
+        return render_ctx
+
+    @view_config(
+        route_name='register', request_method='POST',
+        renderer='rhodecode:templates/register.html')
+    def register_post(self):
+        social_data = self.session.get('rhodecode.social_auth')
+        settings = SettingsModel().get_all_settings()
+        captcha_private_key = settings.get('rhodecode_captcha_private_key')
+        captcha_active = bool(captcha_private_key)
+        auto_active = 'hg.register.auto_activate' in User.get_default_user()\
+            .AuthUser.permissions['global']
+        register_message = settings.get('rhodecode_register_message') or ''
+
+        register_form = RegisterForm()()
+        try:
+            form_result = register_form.to_python(self.request.params)
+            form_result['active'] = auto_active
+
+            if captcha_active:
+                response = submit(
+                    self.request.params.get('recaptcha_challenge_field'),
+                    self.request.params.get('recaptcha_response_field'),
+                    private_key=captcha_private_key,
+                    remoteip=self.ip_addr)
+                if captcha_active and not response.is_valid:
+                    _value = form_result
+                    _msg = _('bad captcha')
+                    error_dict = {'recaptcha_field': _msg}
+                    raise formencode.Invalid(_msg, _value, None,
+                                             error_dict=error_dict)
+
+            new_user = UserModel().create_registration(form_result)
+            if social_data:
+                plugin_name = 'egg:rhodecode-enterprise-ee#{}'.format(
+                    social_data['credentials.provider']
+                )
+                auth_plugin = loadplugin(plugin_name)
+                if auth_plugin:
+                    auth_plugin.handle_social_data(
+                        self.session, new_user.user_id, social_data)
+            self.session.flash(
+                _('You have successfully registered with RhodeCode'),
+                queue='success')
+            Session().commit()
+
+            redirect_ro = self.request.route_path('login')
+            raise HTTPFound(redirect_ro)
+
+        except formencode.Invalid as errors:
+            del errors.value['password']
+            del errors.value['password_confirmation']
+            render_ctx = self._get_template_context()
+            render_ctx.update({
+                'errors': errors.error_dict,
+                'defaults': errors.value,
+                'register_message': register_message,
+            })
+            return render_ctx
+
+        except UserCreationError as e:
+            # container auth or other auth functions that create users on
+            # the fly can throw this exception signaling that there's issue
+            # with user creation, explanation should be provided in
+            # Exception itself
+            self.session.flash(e, queue='error')
+            render_ctx = self._get_template_context()
+            render_ctx.update({
+                'register_message': register_message,
+            })
+            return render_ctx
+
+    @view_config(
+        route_name='reset_password', request_method=('GET', 'POST'),
+        renderer='rhodecode:templates/password_reset.html')
+    def password_reset(self):
+        settings = SettingsModel().get_all_settings()
+        captcha_private_key = settings.get('rhodecode_captcha_private_key')
+        captcha_active = bool(captcha_private_key)
+        captcha_public_key = settings.get('rhodecode_captcha_public_key')
+
+        render_ctx = {
+            'captcha_active': captcha_active,
+            'captcha_public_key': captcha_public_key,
+            'defaults': {},
+            'errors': {},
+        }
+
+        if self.request.POST:
+            password_reset_form = PasswordResetForm()()
+            try:
+                form_result = password_reset_form.to_python(
+                    self.request.params)
+                if captcha_active:
+                    response = submit(
+                        self.request.params.get('recaptcha_challenge_field'),
+                        self.request.params.get('recaptcha_response_field'),
+                        private_key=captcha_private_key,
+                        remoteip=get_ip_addr(self.request.environ))
+                    if captcha_active and not response.is_valid:
+                        _value = form_result
+                        _msg = _('bad captcha')
+                        error_dict = {'recaptcha_field': _msg}
+                        raise formencode.Invalid(_msg, _value, None,
+                                                 error_dict=error_dict)
+                UserModel().reset_password_link(form_result)
+                self.session.flash(
+                    _('Your password reset link was sent'),
+                    queue='success')
+                return HTTPFound(self.request.route_path('login'))
+
+            except formencode.Invalid as errors:
+                render_ctx.update({
+                    'defaults': errors.value,
+                    'errors': errors.error_dict,
+                })
+
+        return render_ctx
+
+    @view_config(route_name='reset_password_confirmation',
+                 request_method='GET')
+    def password_reset_confirmation(self):
+        if self.request.GET and self.request.GET.get('key'):
+            try:
+                user = User.get_by_auth_token(self.request.GET.get('key'))
+                data = {'email': user.email}
+                UserModel().reset_password(data)
+                self.session.flash(
+                    _('Your password reset was successful, '
+                      'a new password has been sent to your email'),
+                    queue='success')
+            except Exception as e:
+                log.error(e)
+                return HTTPFound(self.request.route_path('reset_password'))
+
+        return HTTPFound(self.request.route_path('login'))