Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
e75c655
2fa and login with password working
deepakdinesh1123 Jul 16, 2025
1e35550
feat: auth config fe
shahharsh176 Jul 17, 2025
6605556
Merge remote-tracking branch 'origin/auth_fe' into feat/login_with_code
deepakdinesh1123 Jul 17, 2025
54b8479
login with code working
deepakdinesh1123 Jul 18, 2025
1929838
Merge pull request #501 from Healthlane-Technologies/feat/login_with_…
deepakdinesh1123 Jul 18, 2025
ad715cb
set csrf token on get
deepakdinesh1123 Jul 21, 2025
38124c0
forgot password flow working
deepakdinesh1123 Jul 23, 2025
ebc1dd9
frontend changes
deepakdinesh1123 Jul 23, 2025
64f447b
app auth flow init
deepakdinesh1123 Jul 25, 2025
f6c1e60
test git install
deepakdinesh1123 Jul 23, 2025
50fd4b4
update requirements
deepakdinesh1123 Jul 28, 2025
6f20fff
update python version to fix docker image
deepakdinesh1123 Jul 29, 2025
2205e41
fix authenticate function
deepakdinesh1123 Jul 29, 2025
c45db07
sms and email configuration options added
deepakdinesh1123 Jul 29, 2025
1eafedc
backward compatibility for username based login
deepakdinesh1123 Jul 30, 2025
d517c49
fixes
deepakdinesh1123 Jul 30, 2025
17d667e
mfa message and send_email function fixes
deepakdinesh1123 Jul 31, 2025
70b9e37
user session support and profile modification support
deepakdinesh1123 Aug 13, 2025
629416b
respect two_factor_auth config priority
deepakdinesh1123 Aug 14, 2025
75b9b0e
login settings at user and user role level working
deepakdinesh1123 Aug 19, 2025
7c9d954
nested folder support for a module
shahharsh176 Aug 19, 2025
5a4982e
api data enhancement
deepakdinesh1123 Aug 20, 2025
ebbe8f6
Merge branch 'nested_module_path' into new_auth_with_nested_module_fix
deepakdinesh1123 Aug 20, 2025
9cbd9b2
internal requests fix
deepakdinesh1123 Aug 20, 2025
476f94c
select email and sms in two_factor_auth by default and don't allow us…
deepakdinesh1123 Aug 20, 2025
0c63413
more auth_config validations added
deepakdinesh1123 Aug 22, 2025
dfdbc36
fix validation logic for creating and updating user
deepakdinesh1123 Aug 22, 2025
037202f
change python version in tests
deepakdinesh1123 Aug 26, 2025
cfc431b
fix tests
deepakdinesh1123 Aug 26, 2025
6f62876
remove name uniqueness check
deepakdinesh1123 Aug 26, 2025
88588c6
revert to python 3.10
deepakdinesh1123 Aug 26, 2025
3463888
nested modules support for packages
shahharsh176 Sep 24, 2025
9a7f3b4
Merge pull request #516 from Healthlane-Technologies/nested_module_path
shahharsh176 Sep 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/zango-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.9' # Specify the Python version you need
python-version: '3.10' # Specify the Python version you need

- name: Install Dependencies
run: |
Expand Down
4 changes: 3 additions & 1 deletion backend/requirements/base.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ cookiecutter==2.3.0
coverage==7.6.1
crispy-bootstrap5==0.7
cryptography==44.0.3
django==4.2.15
django==4.2.16
django-axes==6.4.0
django-cachalot==2.6.1
django-celery-beat==2.5.0
Expand All @@ -28,8 +28,10 @@ django-smtp-ssl==1.0
django-storages==1.14
django-tenants==3.6.1
djangorestframework==3.15.2
fido2==2.0.0
flake8==6.1.0
flower==2.0.1
git+https://github.com/Healthlane-Technologies/django-allauth.git@feat/app_auth_flow
GitPython==3.1.43
loguru==0.7.2
lxml==5.3.0
Expand Down
78 changes: 68 additions & 10 deletions backend/setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,51 +5,109 @@

PROJECT_DIR = os.path.dirname(__file__)
REQUIREMENTS_DIR = os.path.join(PROJECT_DIR, "requirements")

README = os.path.join(PROJECT_DIR, "README.md")

PLATFORM_VERSION = "0.6.1"


def get_requirements(env):
with open(os.path.join(REQUIREMENTS_DIR, f"{env}.txt")) as fp:
return [x.strip() for x in fp.read().split("\n") if not x.startswith("#")]
requirements_file = os.path.join(REQUIREMENTS_DIR, f"{env}.txt")

# Check if requirements file exists
if not os.path.exists(requirements_file):
return []

install_requires = get_requirements("base")
with open(requirements_file, encoding="utf-8") as fp:
install_requires = []

for line in fp:
line = line.strip()
if line and not line.startswith("#"):
if line.startswith("git+"):
# Convert git URL to PEP 508 format
if "@" in line:
repo_part, branch_part = line.split("@", 1)
else:
repo_part = line
branch_part = "main"

# Extract package name from git URL
package_name = repo_part.split("/")[-1].replace(".git", "")

# Clean package name (remove any query parameters)
if "?" in package_name:
package_name = package_name.split("?")[0]

# PEP 508 format: package_name @ git+url
pep508_url = f"{package_name} @ {line}"
install_requires.append(pep508_url)
else:
install_requires.append(line)

return install_requires


def get_long_description():
"""Safely read README file"""
try:
with open(README, encoding="utf-8") as f:
return f.read()
except FileNotFoundError:
return "Zango: multi-tenant Django framework for building business apps"


install_requires = get_requirements("base")

setup(
name="zango",
version=PLATFORM_VERSION,
license="Apache License 2.0",
description="Zango: multi-tenant Django framework for building business apps",
long_description=open(README).read(),
long_description=get_long_description(),
long_description_content_type="text/markdown",
author='Zelthy ("Healthlane Technologies")',
author_email="maintainers@zelthy.com",
url="https://github.com/Healthlane-Technologies/zelthy3",
project_urls={
"Bug Reports": "https://github.com/Healthlane-Technologies/zelthy3/issues",
"Source": "https://github.com/Healthlane-Technologies/zelthy3",
"Documentation": "https://github.com/Healthlane-Technologies/zelthy3#readme",
},
package_dir={"": "src"},
packages=find_packages("src"),
package_data={
"zango": [
"cli/project_template/**/*",
"assets/**",
"**/templates/**",
"**/workspace_folder_template/**",
"assets/**/*",
"**/templates/**/*",
"**/workspace_folder_template/**/*",
],
},
include_package_data=True,
install_requires=install_requires,
python_requires=">=3.8",
classifiers=[
"Development Status :: 5 - Production/Stable",
"Framework :: Django",
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Development Status :: 5 - Production/Stable",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
],
keywords="django multi-tenant framework business apps",
entry_points={
"console_scripts": [
"zango=zango.cli:cli",
],
},
license_files=["LICENSE"],
zip_safe=False,
)
Empty file.
Empty file.
8 changes: 8 additions & 0 deletions backend/src/zango/api/app_auth/config/v1/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from django.urls import path

from .views import AppAuthConfigViewAPIV1


urlpatterns = [
path("", AppAuthConfigViewAPIV1.as_view(), name="app-auth-config"),
]
42 changes: 42 additions & 0 deletions backend/src/zango/api/app_auth/config/v1/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.views import APIView

from django.utils.decorators import method_decorator
from django.views.decorators.csrf import ensure_csrf_cookie

from zango.api.platform.tenancy.v1.serializers import AppUserModelSerializerModel
from zango.apps.appauth.auth_backend import KnoxTokenAuthBackend
from zango.core.api import get_api_response
from zango.core.utils import get_auth_priority


@method_decorator(ensure_csrf_cookie, name="dispatch")
class AppAuthConfigViewAPIV1(APIView):
def get_authenticators(self):
if self.request.method == "PUT":
return [KnoxTokenAuthBackend(), SessionAuthentication()]
return super().get_authenticators()

def get_permissions(self):
if self.request.method == "PUT":
return [IsAuthenticated()]
return []

def get(self, request):
response = {"auth_config": get_auth_priority(request=request)}
status = 200
success = True
return get_api_response(success, response, status)

def put(self, request, *args, **kwargs):
serializer = AppUserModelSerializerModel(
request.user, data=request.data, partial=True
)
if not serializer.is_valid():
return get_api_response(False, serializer.errors, 400)
serializer.save()
response = {"auth_config": get_auth_priority(request=request)}
status = 200
success = True
return get_api_response(success, response, status)
Empty file.
Empty file.
35 changes: 35 additions & 0 deletions backend/src/zango/api/app_auth/flows/v1/forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from allauth.headless.internal.restkit import inputs

from django import forms
from django.utils.translation import gettext_lazy as _

from zango.apps.appauth.models import OldPasswords


class BaseSetPasswordForm(forms.Form):
new_password = forms.CharField(
label=_("New password"),
widget=forms.PasswordInput(
attrs={
"class": "form-control",
"placeholder": "Enter your new password",
"autocomplete": "new-password",
}
),
strip=False,
)

def __init__(self, *args, **kwargs):
self.user = kwargs.pop("user")
super().__init__(*args, **kwargs)

def save(self):
self.user.set_password(self.cleaned_data["new_password"])
self.user.save()
obj = OldPasswords.objects.create(user=self.user)
obj.setPasswords(self.user.password)
obj.save()


class PasswordSetForm(BaseSetPasswordForm, inputs.Input):
pass
27 changes: 27 additions & 0 deletions backend/src/zango/api/app_auth/flows/v1/login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import json

from allauth.headless.account.views import LoginView

from zango.core.api import get_api_response


class AppLoginViewAPIV1(LoginView):
def post(self, request, *args, **kwargs):
auth_config = request.tenant.auth_config
if (
not auth_config.get("login_methods", {})
.get("password", {})
.get("enabled", False)
):
return get_api_response(
success=False,
response_content={"message": "Password login is not enabled"},
status=400,
)

resp = super().post(request, *args, **kwargs)
return get_api_response(
success=True,
response_content=json.loads(resp.content.decode("utf-8")),
status=resp.status_code,
)
36 changes: 36 additions & 0 deletions backend/src/zango/api/app_auth/flows/v1/login_with_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import json

from allauth.headless.account.views import ConfirmLoginCodeView, RequestLoginCodeView

from zango.core.api import get_api_response


class RequestLoginCodeViewAPIV1(RequestLoginCodeView):
def post(self, request, *args, **kwargs):
resp = super().post(request, *args, **kwargs)
auth_config = request.tenant.auth_config
if (
not auth_config.get("login_methods", {})
.get("otp", {})
.get("enabled", False)
):
return get_api_response(
success=False,
response_content={"message": "OTP login is not enabled"},
status=400,
)
return get_api_response(
success=True,
response_content=json.loads(resp.content.decode("utf-8")),
status=resp.status_code,
)


class ConfirmLoginCodeViewAPIV1(ConfirmLoginCodeView):
def post(self, request, *args, **kwargs):
resp = super().post(request, *args, **kwargs)
return get_api_response(
success=True,
response_content=json.loads(resp.content.decode("utf-8")),
status=resp.status_code,
)
18 changes: 18 additions & 0 deletions backend/src/zango/api/app_auth/flows/v1/logout.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import json

from allauth.headless.account.views import SessionView

from zango.core.api import get_api_response


class AppLogoutViewAPIV1(SessionView):
def delete(self, request, *args, **kwargs):
resp = super().delete(request, *args, **kwargs)
resp_data = json.loads(resp.content.decode("utf-8"))
if resp.status_code == 401:
resp_data["message"] = "Successfully logged out"
return get_api_response(
success=True,
response_content=resp_data,
status=resp.status_code,
)
Loading
Loading