diff --git a/api/tacticalrmm/accounts/management/commands/generate_barcode.py b/api/tacticalrmm/accounts/management/commands/generate_barcode.py new file mode 100644 index 00000000..c48cdb8d --- /dev/null +++ b/api/tacticalrmm/accounts/management/commands/generate_barcode.py @@ -0,0 +1,22 @@ +import pyotp +import subprocess +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = "Generates barcode for Google Authenticator" + + def add_arguments(self, parser): + parser.add_argument("code", type=str) + parser.add_argument("username", type=str) + + def handle(self, *args, **kwargs): + code = kwargs["code"] + username = kwargs["username"] + + url = pyotp.totp.TOTP(code).provisioning_uri( + username, issuer_name="Tactical RMM" + ) + subprocess.run(f'qr "{url}"', shell=True) + self.stdout.write(self.style.SUCCESS("Scan the barcode above with your google authenticator app")) + self.stdout.write(self.style.SUCCESS(f"If that doesn't work you may manually enter the key: {code}")) diff --git a/api/tacticalrmm/accounts/management/commands/generate_totp.py b/api/tacticalrmm/accounts/management/commands/generate_totp.py new file mode 100644 index 00000000..39c4a092 --- /dev/null +++ b/api/tacticalrmm/accounts/management/commands/generate_totp.py @@ -0,0 +1,9 @@ +import pyotp +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + help = "Generates TOTP Random Base 32" + + def handle(self, *args, **kwargs): + self.stdout.write(pyotp.random_base32()) diff --git a/api/tacticalrmm/accounts/views.py b/api/tacticalrmm/accounts/views.py index 619ee300..1f5cecff 100644 --- a/api/tacticalrmm/accounts/views.py +++ b/api/tacticalrmm/accounts/views.py @@ -17,6 +17,15 @@ from rest_framework.decorators import ( from accounts.models import User +class CheckCreds(KnoxLoginView): + + permission_classes = (AllowAny,) + + def post(self, request, format=None): + serializer = AuthTokenSerializer(data=request.data) + serializer.is_valid(raise_exception=True) + return Response("ok") + class LoginView(KnoxLoginView): @@ -24,9 +33,8 @@ class LoginView(KnoxLoginView): def post(self, request, format=None): token = request.data["twofactor"] - # totp = pyotp.TOTP(settings.TWO_FACTOR_OTP) - # if totp.verify(token, valid_window=1): - if token == "sekret": + totp = pyotp.TOTP(settings.TWO_FACTOR_OTP) + if totp.verify(token, valid_window=1): serializer = AuthTokenSerializer(data=request.data) serializer.is_valid(raise_exception=True) user = serializer.validated_data["user"] @@ -41,9 +49,8 @@ class LoginView(KnoxLoginView): @permission_classes((IsAuthenticated,)) def installer_twofactor(request): token = request.data["twofactorToken"] - # totp = pyotp.TOTP(settings.TWO_FACTOR_OTP) - # if totp.verify(token, valid_window=1): - if token == "sekret": + totp = pyotp.TOTP(settings.TWO_FACTOR_OTP) + if totp.verify(token, valid_window=1): return Response("ok") else: return Response("bad 2 factor code", status=status.HTTP_400_BAD_REQUEST) diff --git a/api/tacticalrmm/requirements.txt b/api/tacticalrmm/requirements.txt index 3f07da70..f9290517 100644 --- a/api/tacticalrmm/requirements.txt +++ b/api/tacticalrmm/requirements.txt @@ -23,6 +23,7 @@ pycparser==2.19 pyotp==2.3.0 pyparsing==2.4.6 pytz==2019.3 +qrcode==6.1 redis==3.3.11 requests==2.22.0 six==1.14.0 diff --git a/api/tacticalrmm/tacticalrmm/urls.py b/api/tacticalrmm/tacticalrmm/urls.py index c0e62f04..73ac63e5 100644 --- a/api/tacticalrmm/tacticalrmm/urls.py +++ b/api/tacticalrmm/tacticalrmm/urls.py @@ -2,10 +2,11 @@ from django.contrib import admin from django.urls import path, include from django.conf import settings from knox import views as knox_views -from accounts.views import LoginView, installer_twofactor +from accounts.views import LoginView, CheckCreds, installer_twofactor urlpatterns = [ path(settings.ADMIN_URL, admin.site.urls), + path("checkcreds/", CheckCreds.as_view()), path("login/", LoginView.as_view()), path("logout/", knox_views.LogoutView.as_view()), path("logoutall/", knox_views.LogoutAllView.as_view()), diff --git a/install.sh b/install.sh index 5432e523..45a68eaa 100755 --- a/install.sh +++ b/install.sh @@ -44,8 +44,6 @@ echo -ne "${YELLOW}Enter your username for meshcentral${NC}: " read meshusername echo -ne "${YELLOW}Enter your email address for let's encrypt renewal notifications${NC}: " read letsemail -echo -ne "${YELLOW}Please use google authenticator and enter TOTP code${NC}: " -read twofactor print_green 'Creating saltapi user' @@ -157,6 +155,30 @@ sudo chown ${USER}:${USER} /var/log/celery git clone https://github.com/wh1te909/tacticalrmm.git /home/${USER}/rmm/ sudo chown ${USER}:www-data -R /home/${USER}/rmm/api/tacticalrmm + + +print_green 'Installing the backend' + +cd /home/${USER}/rmm/api +python3.7 -m venv env +source /home/${USER}/rmm/api/env/bin/activate +cd /home/${USER}/rmm/api/tacticalrmm +pip install --upgrade pip +pip install -r /home/${USER}/rmm/api/tacticalrmm/requirements.txt +python manage.py migrate +python manage.py collectstatic +printf >&2 "${YELLOW}%0.s*${NC}" {1..80} +printf >&2 "\n" +printf >&2 "${YELLOW}Please create your login for the RMM website and django admin${NC}\n" +printf >&2 "${YELLOW}%0.s*${NC}" {1..80} +printf >&2 "\n" +echo -ne "Username: " +read djangousername +python manage.py createsuperuser --username ${djangousername} --email ${letsemail} +RANDBASE=$(python manage.py generate_totp) +python manage.py generate_barcode ${RANDBASE} ${djangousername} +deactivate + localvars="$(cat << EOF SECRET_KEY = "${DJANGO_SEKRET}" @@ -210,29 +232,11 @@ SALT_USERNAME = "saltapi" SALT_PASSWORD = "${SALTPW}" MESH_USERNAME = "${meshusername}" MESH_SITE = "https://${meshdomain}" -TWO_FACTOR_OTP = "${twofactor}" +TWO_FACTOR_OTP = "${RANDBASE}" EOF )" echo "${localvars}" > /home/${USER}/rmm/api/tacticalrmm/tacticalrmm/local_settings.py -print_green 'Installing the backend' - -cd /home/${USER}/rmm/api -python3.7 -m venv env -source /home/${USER}/rmm/api/env/bin/activate -cd /home/${USER}/rmm/api/tacticalrmm -pip install --upgrade pip -pip install -r /home/${USER}/rmm/api/tacticalrmm/requirements.txt -python manage.py migrate -python manage.py collectstatic -printf >&2 "${YELLOW}%0.s*${NC}" {1..80} -printf >&2 "\n" -printf >&2 "${YELLOW}Please create your login for the RMM website and django admin${NC}\n" -printf >&2 "${YELLOW}%0.s*${NC}" {1..80} -printf >&2 "\n" -python manage.py createsuperuser -deactivate - uwsgini="$(cat << EOF [uwsgi] diff --git a/web/src/store/store.js b/web/src/store/store.js index b70fae96..85818694 100644 --- a/web/src/store/store.js +++ b/web/src/store/store.js @@ -169,7 +169,7 @@ export const store = new Vuex.Store({ timeout: 1000, textColor: "white", icon: "fas fa-times-circle", - message: "Invalid credentials" + message: "Bad token" }); reject(error); }); diff --git a/web/src/views/Login.vue b/web/src/views/Login.vue index 3b2c268c..07060bb8 100644 --- a/web/src/views/Login.vue +++ b/web/src/views/Login.vue @@ -3,8 +3,8 @@
-
Tactical Techs RMM
- +
Tactical RMM
+