Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[PUI] Login broken with MFA enabled, template missing #8690

Open
2 of 6 tasks
sur5r opened this issue Dec 17, 2024 · 6 comments · May be fixed by #8720
Open
2 of 6 tasks

[PUI] Login broken with MFA enabled, template missing #8690

sur5r opened this issue Dec 17, 2024 · 6 comments · May be fixed by #8720
Assignees
Labels
bug Identifies a bug which needs to be addressed Platform UI Related to the React based User Interface
Milestone

Comments

@sur5r
Copy link
Contributor

sur5r commented Dec 17, 2024

Please verify that this bug has NOT been raised before.

  • I checked and didn't find a similar issue

Describe the bug*

24f433c broken MFA login via PUI as accounts/base.html and dependencies got removed.

Steps to Reproduce

  1. Try and log in with 2FA enabled
  2. Instead of token prompt, experience HTTP 500

Expected behaviour

  1. Try and log in with 2FA enabled
  2. Get prompted for token
  3. Be logged in

Partial revert of 24f433c restores 2FA login:

diff --git a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py
index 81d9b483e..0f7093722 100644
--- a/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py
+++ b/src/backend/InvenTree/InvenTree/templatetags/inventree_extras.py
@@ -1,5 +1,6 @@
 """This module provides template tags for extra functionality, over and above the built-in Django tags."""
 
+import os
 import logging
 from datetime import date, datetime
 
@@ -487,3 +488,38 @@ def admin_url(user, table, pk):
             pass
 
     return url
+
+@register.simple_tag()
+def get_color_theme_css(user):
+    """Return the custom theme .css file for the selected user."""
+    user_theme_name = get_user_color_theme(user)
+    # Build path to CSS sheet
+    inventree_css_sheet = os.path.join('css', 'color-themes', user_theme_name + '.css')
+
+    # Build static URL
+    inventree_css_static_url = os.path.join(settings.STATIC_URL, inventree_css_sheet)
+
+    return inventree_css_static_url
+
+
+@register.simple_tag()
+def get_user_color_theme(user): 
+    """Get current user color theme."""
+    from common.models import ColorTheme
+
+    try: 
+        if not user.is_authenticated:
+            return 'default'
+    except Exception:
+        return 'default'
+
+    try:
+        user_theme = ColorTheme.objects.filter(user_obj=user).get()
+        user_theme_name = user_theme.name
+        if not user_theme_name or not ColorTheme.is_valid_choice(user_theme):
+            user_theme_name = 'default'
+    except ColorTheme.DoesNotExist:
+        user_theme_name = 'default'
+
+    return user_theme_name
+
diff --git a/src/backend/InvenTree/common/models.py b/src/backend/InvenTree/common/models.py
index ff7411883..0beb58dd4 100644
--- a/src/backend/InvenTree/common/models.py
+++ b/src/backend/InvenTree/common/models.py
@@ -2257,3 +2257,57 @@ class BarcodeScanResult(InvenTree.models.InvenTreeModel):
         help_text=_('Was the barcode scan successful?'),
         default=False,
     )
+
+
+class ColorTheme(models.Model):
+    """Color Theme Setting."""
+
+    name = models.CharField(max_length=20, default='', blank=True)
+
+    user = models.CharField(max_length=150, unique=True)
+    user_obj = models.ForeignKey(User, on_delete=models.CASCADE, blank=True, null=True)
+
+    @classmethod
+    def get_color_themes_choices(cls):
+        """Get all color themes from static folder."""
+        color_theme_dir = (
+            django_settings.STATIC_COLOR_THEMES_DIR
+            if django_settings.STATIC_COLOR_THEMES_DIR.exists()
+            else django_settings.BASE_DIR.joinpath(
+                'InvenTree', 'static', 'css', 'color-themes'
+            )    
+        )    
+
+        if not color_theme_dir.exists():
+            logger.error(f'Theme directory "{color_theme_dir}" does not exist')
+            return []
+
+        # Get files list from css/color-themes/ folder
+        files_list = [] 
+
+        for file in color_theme_dir.iterdir():
+            files_list.append([file.stem, file.suffix])
+
+        # Get color themes choices (CSS sheets)
+        choices = [
+            (file_name.lower(), _(file_name.replace('-', ' ').title()))
+            for file_name, file_ext in files_list
+            if file_ext == '.css'
+        ]    
+
+        return choices
+
+    @classmethod
+    def is_valid_choice(cls, user_color_theme):
+        """Check if color theme is valid choice."""
+        try: 
+            user_color_theme_name = user_color_theme.name
+        except AttributeError:
+            return False
+
+        for color_theme in cls.get_color_themes_choices():
+            if user_color_theme_name == color_theme[0]:
+                return True 
+
+        return False
+
diff --git a/src/backend/InvenTree/templates/account/base.html b/src/backend/InvenTree/templates/account/base.html
new file mode 100644
index 000000000..cec314513
--- /dev/null
+++ b/src/backend/InvenTree/templates/account/base.html
@@ -0,0 +1,121 @@
+{% load static %}
+{% load i18n %}
+{% load inventree_extras %}
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+
+<!-- Required meta tags -->
+<meta charset="utf-8">
+<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
+
+<!-- Favicon -->
+<link rel="apple-touch-icon" sizes="57x57" href="{% static 'img/favicon/apple-icon-57x57.png' %}">
+<link rel="apple-touch-icon" sizes="60x60" href="{% static 'img/favicon/apple-icon-60x60.png' %}">
+<link rel="apple-touch-icon" sizes="72x72" href="{% static 'img/favicon/apple-icon-72x72.png' %}">
+<link rel="apple-touch-icon" sizes="76x76" href="{% static 'img/favicon/apple-icon-76x76.png' %}">
+<link rel="apple-touch-icon" sizes="114x114" href="{% static 'img/favicon/apple-icon-114x114.png' %}">
+<link rel="apple-touch-icon" sizes="120x120" href="{% static 'img/favicon/apple-icon-120x120.png' %}">
+<link rel="apple-touch-icon" sizes="144x144" href="{% static 'img/favicon/apple-icon-144x144.png' %}">
+<link rel="apple-touch-icon" sizes="152x152" href="{% static 'img/favicon/apple-icon-152x152.png' %}">
+<link rel="apple-touch-icon" sizes="180x180" href="{% static 'img/favicon/apple-icon-180x180.png' %}">
+<link rel="icon" type="image/png" sizes="192x192"  href="{% static 'img/favicon/android-icon-192x192.png' %}">
+<link rel="icon" type="image/png" sizes="32x32" href="{% static 'img/favicon/favicon-32x32.png' %}">
+<link rel="icon" type="image/png" sizes="96x96" href="{% static 'img/favicon/favicon-96x96.png' %}">
+<link rel="icon" type="image/png" sizes="16x16" href="{% static 'img/favicon/favicon-16x16.png' %}">
+<link rel="manifest" href="{% static 'img/favicon/manifest.json' %}">
+<meta name="msapplication-TileColor" content="#ffffff">
+<meta name="msapplication-TileImage" content="{% static 'img/favicon/ms-icon-144x144.png' %}">
+<meta name="theme-color" content="#ffffff">
+
+
+<!-- CSS -->
+<link rel="stylesheet" href="{% static 'fontawesome/css/brands.css' %}">
+<link rel="stylesheet" href="{% static 'fontawesome/css/solid.css' %}">
+<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
+<link rel="stylesheet" href="{% static 'select2/css/select2.css' %}">
+<link rel="stylesheet" href="{% static 'select2/css/select2-bootstrap-5-theme.css' %}">
+<link rel="stylesheet" href="{% static 'css/inventree.css' %}">
+
+<link rel="stylesheet" href="{% get_color_theme_css request.user %}">
+
+<title>
+    {% inventree_title %} | {% block head_title %}{% endblock head_title %}
+</title>
+
+{% block extra_head %}
+{% endblock extra_head %}
+</head>
+
+<body class='login-screen' style='background: url({% inventree_splash %}); background-size: cover;'>
+
+    <div class='container-fluid'>
+        <div class='notification-area' id='alerts'>
+            <!-- Div for displayed alerts -->
+        </div>
+    </div>
+
+    <div class='main body-wrapper login-screen d-flex'>
+
+        <div class='login-container'>
+        <div class="row">
+            <div class='container-fluid'>
+
+                <div class='clearfix content-heading login-header d-flex flex-wrap'>
+                    <img class="pull-left" src="{% inventree_logo %}" alt='{% trans "InvenTree logo" %}' width="60" height="60"/>
+                    {% include "spacer.html" %}
+                    <span class='float-right'><h3>{% inventree_title %}</h3></span>
+                </div>
+            </div>
+                <div class='container-fluid'>
+                    <hr>
+                    {% block content %}
+                    {% endblock content %}
+                </div>
+        </div>
+
+        </div>
+
+        {% block extra_body %}
+        {% endblock extra_body %}
+    </div>
+
+
+
+<!-- general JS -->
+{% include "third_party_js.html" %}
+
+<script type='text/javascript' src='{% static "script/inventree/inventree.js" %}'></script>
+<script type='text/javascript' src='{% static "script/inventree/message.js" %}'></script>
+
+<script type='text/javascript'>
+
+$(document).ready(function () {
+
+    {% if messages %}
+    {% for message in messages %}
+    showMessage("{{ message }}");
+    {% endfor %}
+    {% endif %}
+
+    showCachedAlerts();
+
+    // Add brand icons for SSO providers, if available
+    $('.socialaccount_provider').each(function(i, obj) {
+        var el = $(this);
+        var tag = el.attr('brand_name');
+
+        var icon = window.FontAwesome.icon({prefix: 'fab', iconName: tag});
+
+        if (icon) {
+            el.prepend(`<span class='fab fa-${tag}'></span>&nbsp;`);
+        }
+    });
+
+});
+
+</script>
+
+</body>
+</html>
diff --git a/src/backend/InvenTree/templates/third_party_js.html b/src/backend/InvenTree/templates/third_party_js.html
new file mode 100644
index 000000000..b4d52b748
--- /dev/null
+++ b/src/backend/InvenTree/templates/third_party_js.html
@@ -0,0 +1,39 @@
+{% load static %}
+
+<!-- jquery -->
+<script type="text/javascript" src="{% static 'script/jquery_3.3.1_jquery.min.js' %}"></script>
+<script type='text/javascript' src="{% static 'script/jquery.form.min.js' %}"></script>
+<script type='text/javascript' src="{% static 'script/jquery-ui/jquery-ui.min.js' %}"></script>
+
+<!-- Bootstrap-->
+<script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
+
+<!-- Bootstrap Table -->
+<script defer type='text/javascript' src="{% static 'script/bootstrap/bootstrap-treeview.js' %}"></script>
+<script defer type='text/javascript' src='{% static "treegrid/js/jquery.treegrid.js" %}'></script>
+<script defer type='text/javascript' src='{% static "treegrid/js/jquery.treegrid.bootstrap3.js" %}'></script>
+<script defer type='text/javascript' src="{% static 'bootstrap-table/bootstrap-table.min.js' %}"></script>
+<script defer type='text/javascript' src='{% static "bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.min.js" %}'></script>
+<script defer type='text/javascript' src='{% static "bootstrap-table/extensions/filter-control/bootstrap-table-filter-control.min.js" %}'></script>
+<script defer type='text/javascript' src='{% static "bootstrap-table/extensions/treegrid/bootstrap-table-treegrid.min.js" %}'></script>
+<script defer type='text/javascript' src='{% static "bootstrap-table/extensions/custom-view/bootstrap-table-custom-view.min.js" %}'></script>
+
+<!-- fontawesome -->
+<script defer type='text/javascript' src="{% static 'fontawesome/js/solid.min.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'fontawesome/js/regular.min.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'fontawesome/js/brands.min.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'fontawesome/js/fontawesome.min.js' %}"></script>
+
+<!-- 3rd party general js -->
+<script defer type="text/javascript" src="{% static 'fullcalendar/main.min.js' %}"></script>
+<script defer type="text/javascript" src="{% static 'fullcalendar/locales-all.min.js' %}"></script>
+<script defer type="text/javascript" src="{% static 'select2/js/select2.full.min.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'script/moment.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'script/chart.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'script/chartjs-adapter-moment.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'easymde/easymde.min.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'script/html5-qrcode.min.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'script/qrcode.min.js' %}"></script>
+<script defer type='text/javascript' src="{% static 'script/purify.min.js' %}"></script>

Deployment Method

  • Docker
  • Package
  • Bare metal
  • Other - added info in Steps to Reproduce

Version Information

InvenTree-Version: 0.18.0 dev
Django Version: 4.2.17
Commit Hash: ff69cf6
Commit Date: 2024-12-17
Commit Branch: master
Database: postgresql
Debug-Mode: False
Deployed using Docker: False
Platform: Linux-6.1.0-13-cloud-amd64-x86_64-with-glibc2.36
Installer: GIT
nullActive plugins: [{"name":"InvenTreeBarcode","slug":"inventreebarcode","version":"2.1.0"},{"name":"InvenTreeCoreNotificationsPlugin","slug":"inventreecorenotificationsplugin","version":"1.0.0"},{"name":"InvenTreeCurrencyExchange","slug":"inventreecurrencyexchange","version":"1.0.0"},{"name":"InvenTreeLabel","slug":"inventreelabel","version":"1.1.0"},{"name":"InvenTreeLabelMachine","slug":"inventreelabelmachine","version":"1.0.0"},{"name":"InvenTreeLabelSheet","slug":"inventreelabelsheet","version":"1.0.0"},{"name":"DigiKeyPlugin","slug":"digikeyplugin","version":"1.0.0"},{"name":"LCSCPlugin","slug":"lcscplugin","version":"1.0.0"},{"name":"MouserPlugin","slug":"mouserplugin","version":"1.0.0"},{"name":"TMEPlugin","slug":"tmeplugin","version":"1.0.0"},{"name":"Brother Labels","slug":"brother","version":"1.0.0"}]

Please verify if you can reproduce this bug on the demo site.

  • I can reproduce this bug on the demo site.

Relevant log output

@sur5r sur5r added bug Identifies a bug which needs to be addressed question This is a question triage:not-checked Item was not checked by the core team labels Dec 17, 2024
@SchrodingersGat
Copy link
Member

@sur5r can you please share the error messages you are seeing?

@sur5r
Copy link
Contributor Author

sur5r commented Dec 18, 2024

Hrmpf, I tried 4 times putting everything in here, but github always destroyed it when previewing it.

So here's the abbreviated version. Please ping me again if you need the tracebacks, I will attach them as individual files then.

  1. django.template.exceptions.TemplateDoesNotExist: account/base.html
    -> Restore account/base.html
  2. django.template.exceptions.TemplateSyntaxError: Invalid block tag on line 41: 'get_color_theme_css'. Did you forget to register or load this tag?
    -> Restore first templatetag
  3. NameError: name 'get_user_color_theme' is not defined
    -> Restore second templatetag
  4. ImportError: cannot import name 'ColorTheme' from 'common.models' (/home/inventree/src/src/backend/InvenTree/common/models.py)
    -> Restore class
  5. NameError: name 'os' is not defined
    -> Add import os to templatetag file
  6. django.template.exceptions.TemplateDoesNotExist: third_party_js.html
    -> Restore third_party_js.html

@SchrodingersGat
Copy link
Member

Right so it looks like the original issue is from this file:

https://github.com/inventree/InvenTree/blob/master/src/backend/InvenTree/templates/403_csrf.html

Which is inheriting from a template which no longer exists:

{% extends "account/base.html" %}

@matmair any insights on this one?

@SchrodingersGat SchrodingersGat added this to the 0.17.1 milestone Dec 18, 2024
@SchrodingersGat SchrodingersGat added old user interface Issues with Old User interface Platform UI Related to the React based User Interface and removed triage:not-checked Item was not checked by the core team labels Dec 18, 2024
@matmair
Copy link
Member

matmair commented Dec 18, 2024

I can see an easy fix without re-introducing custom themes. Will submit today

@matmair matmair removed question This is a question old user interface Issues with Old User interface labels Dec 18, 2024
@matmair matmair self-assigned this Dec 18, 2024
matmair added a commit to matmair/InvenTree that referenced this issue Dec 18, 2024
@matmair matmair linked a pull request Dec 18, 2024 that will close this issue
@matmair
Copy link
Member

matmair commented Dec 18, 2024

@sur5r please try and provide feedback over on #8720; there will be a follow up for #8708 to add better MFA controls to PUI

@sur5r
Copy link
Contributor Author

sur5r commented Dec 19, 2024

@matmair Thanks! Tried and works!

Sidenote: Are there plans to bring back the wrapping paper background to the login page? I found that a really nice touch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Identifies a bug which needs to be addressed Platform UI Related to the React based User Interface
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants