Skip to content

Commit

Permalink
WIP started adding the ability to create an account from an organization
Browse files Browse the repository at this point in the history
Signed-off-by: Trey <[email protected]>
  • Loading branch information
TreyWW committed Sep 14, 2024
1 parent 738978b commit f80abd2
Show file tree
Hide file tree
Showing 11 changed files with 254 additions and 82 deletions.
3 changes: 1 addition & 2 deletions backend/api/base/modal.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,8 +142,7 @@ def open_modal(request: WebRequest, modal_name, context_type=None, context_value
elif modal_name == "invoices_to_destination":
if existing_client := request.GET.get("client"):
context["existing_client_id"] = existing_client
elif modal_name == "generate_api_key" or modal_name == "edit_team_member_permissions":
permissions = SCOPE_DESCRIPTIONS
elif modal_name in ["generate_api_key", "edit_team_member_permissions", "team_create_user"]:
# example
# "clients": {
# "description": "Access customer details",
Expand Down
76 changes: 76 additions & 0 deletions backend/api/teams/create_user.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from django.contrib import messages
from django.shortcuts import render
from django.urls import reverse
from django.utils.crypto import get_random_string

from backend.decorators import web_require_scopes
from backend.models import Organization, User, TeamMemberPermission
from backend.service.permissions.scopes import get_permissions_from_request
from backend.types.emails import SingleEmailInput
from backend.types.requests import WebRequest
from settings.helpers import send_email


@web_require_scopes("team:invite", True, True)
def create_user_view(request: WebRequest):
team_id = request.POST.get("team_id", "")

team: Organization | None = Organization.objects.filter(id=team_id).first()

if not team:
messages.error("This team does not exist")
return render(request, "base/toast.html")

if not team.is_owner(request.user):
messages.error(request, "Only the team owner can create users")
return render(request, "base/toast.html")

first_name = request.POST.get("first_name", "")
last_name = request.POST.get("last_name", "")
email = request.POST.get("email", "")
permissions: list = get_permissions_from_request(request)

if not email:
messages.error(request, "Please enter a valid user email")
return render(request, "base/toast.html")

if User.objects.filter(email=email).exists():
messages.error(request, "This user already exists, invite them instead!")
return render(request, "base/toast.html")

temporary_password = get_random_string(length=8)

user: User = User.objects.create_user(email=email, first_name=first_name, last_name=last_name, username=email)
user.set_password(temporary_password)
user.awaiting_email_verification = False
user.save()

send_email(
SingleEmailInput(
destination=email,
subject="MyFinances | You have been invited to join an organization",
content=f"""
Hi {user.first_name or "User"},
You have been invited by {request.user.email} to join the organization {team.name}.
Your account email is: {email}
Your temporary password is: {temporary_password}
We suggest that you change your password as soon as you login, however no other user including the organization have
access to this password.
Upon login, you will be added to the \"{team.name}\" organization. However, if required, you may leave at any point.
Login to your new account using this link:
{request.build_absolute_uri(reverse("auth:login manual"))}
""",
)
)

team.members.add(user)

TeamMemberPermission.objects.create(user=user, team=team, scopes=permissions)

messages.success(request, "User was created successfully. They have been emailed instructions.")
return render(request, "base/toast.html")
18 changes: 18 additions & 0 deletions backend/migrations/0060_user_require_change_password.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.1 on 2024-09-14 21:55

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("backend", "0059_alter_invoicerecurringprofile_managers_and_more"),
]

operations = [
migrations.AddField(
model_name="user",
name="require_change_password",
field=models.BooleanField(default=False),
),
]
1 change: 1 addition & 0 deletions backend/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ class User(AbstractUser):
stripe_customer_id = models.CharField(max_length=255, null=True, blank=True)
entitlements = models.JSONField(null=True, blank=True, default=list) # list of strings e.g. ["invoices"]
awaiting_email_verification = models.BooleanField(default=True)
require_change_password = models.BooleanField(default=False) # does user need to change password upon next login

class Role(models.TextChoices):
# NAME DJANGO ADMIN NAME
Expand Down
Empty file.
14 changes: 9 additions & 5 deletions backend/views/core/auth/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,15 @@ def login_manual(request: HtmxAnyHttpRequest): # HTMX POST

response = HttpResponse(status=200)

try:
resolve(redirect_url)
response["HX-Redirect"] = redirect_url
except Resolver404:
response["HX-Redirect"] = "/dashboard/"
if user.require_change_password:
messages.warning(request, "You have been requested by an administrator to change your account password.")
response["HX-Redirect"] = reverse("settings:change_password")
else:
try:
resolve(redirect_url)
response["HX-Redirect"] = redirect_url
except Resolver404:
response["HX-Redirect"] = "/dashboard/"

return response

Expand Down
27 changes: 27 additions & 0 deletions frontend/templates/components/permissions/selector.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{% load strfilters %}
<div class="collapse collapse-arrow border select-bordered mt-4">
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">Permissions</div>
<div class="collapse-content">
<ul class="block">
{% for group in permissions %}
<li class="border-t-2 mt-2 p-2 flex items-center">
<div class="flex-auto">
<strong>{{ group.name | title | split:"_" | join:" " }}</strong>
<div class="text text-sm">{{ group.description }}</div>
</div>
<select class="select select-bordered inline-block position-relative"
name="permission_{{ group.name }}">
<option value="none">No Access</option>
{% for item, readable in group.options.items %}
<option value="{{ item }}"
{% if group.name|add:":"|add:item in user_current_scopes %}selected{% endif %}>
{{ readable }}
</option>
{% endfor %}
</select>
</li>
{% endfor %}
</ul>
</div>
</div>
27 changes: 1 addition & 26 deletions frontend/templates/modals/edit_team_member_permissions.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,7 @@
<span class="label-text-alt text-error">Please enter a valid name for your key</span>
</label>
</div>
<div class="collapse collapse-arrow border select-bordered mt-4">
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">Permissions</div>
<div class="collapse-content">
<ul class="block">
{% for group in permissions %}
<li class="border-t-2 mt-2 p-2 flex items-center">
<div class="flex-auto">
<strong>{{ group.name | title | split:"_" | join:" " }}</strong>
<div class="text text-sm">{{ group.description }}</div>
</div>
<select class="select select-bordered inline-block position-relative"
name="permission_{{ group.name }}">
<option value="none">No Access</option>
{% for item, readable in group.options.items %}
<option value="{{ item }}"
{% if group.name|add:":"|add:item in user_current_scopes %}selected{% endif %}>
{{ readable }}
</option>
{% endfor %}
</select>
</li>
{% endfor %}
</ul>
</div>
</div>
{% include "components/permissions/selector.html" %}
<div class="form-control"></div>
<div class="modal-action">
<button type="submit"
Expand Down
62 changes: 62 additions & 0 deletions frontend/templates/modals/team_create_user.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{% component_block "modal" id="modal_invite_user" start_open="true" title="Create User Account" %}
{% fill "content" %}
<form class="py-4"
id="modal_invite_user-form"
hx-post="{% url 'api:teams:invite' %}"
hx-swap="none">
{% csrf_token %}
<div class="form-control w-full">
<label class="label justify-start">
Users' Email
<span class="tooltip tooltip-right ml-2" data-tip="The email address of the user you are about to create">
<i class="fa fa-info-circle"></i>
</span>
<span class="required_star">*</span>
</label>
<input id="modal_input-email"
name="email"
type="email"
class="input input-block input-bordered"
required>
</div>
<div class="flex flex-row gap-4 w-full">
<div>
<label class="label justify-start">
Users' First Name
<span class="required_star">*</span>
</label>
<input id="modal_input-email"
name="first_name"
type="text"
class="input input-block input-bordered"
required>
</div>
<div>
<label class="label justify-start">
Users' Last Name
<span class="required_star">*</span>
</label>
<input id="modal_input-email"
name="last_name"
type="text"
class="input input-block input-bordered"
required>
</div>
</div>
<input type="hidden" name="team_id" value="{{ team_id }}">
{% include "components/permissions/selector.html" %}
<div class="modal-action">
<button type="submit"
id="modal_invite_user-submit"
class="btn btn-primary"
x-on:invite_user_error.window="document.getElementById('modal_invite_user').close()">
Send Invite
</button>
<button type="button"
_="on click call #modal_invite_user.close()"
class="btn">Cancel
</button>
</div>
</form>
{% endfill %}
{% endcomponent_block %}
7 changes: 4 additions & 3 deletions frontend/templates/pages/invoices/dashboard/manage.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
<div class=" text-3xl w-full grid grid-cols-4">
<div hx-boost="true">
<a href="{% url "invoices:single:dashboard" %}"
class="btn btn-sm btn-outline btn-secondary me-3 float-left">Back to list</a>
class="btn btn-sm btn-outline btn-secondary me-3 float-left">Back to list
</a>
</div>
<h1 class="text-center col-span-2">Invoice #{{ invoice.id }}</h1>
<div>
Expand Down Expand Up @@ -57,7 +58,7 @@ <h1 class="text-center col-span-2">Invoice #{{ invoice.id }}</h1>
</div>
<!-- Mark As Button -->
<div class="dropdown dropdown-right dropdown-hover">
<div class="m-1 btn btn-primary" tabindex="0" role="button">
<div class="m-1 btn btn-primary btn-outline" tabindex="0" role="button">
<i class="fa-solid fa-flag"></i>
Mark As
</div>
Expand Down Expand Up @@ -86,7 +87,7 @@ <h1 class="text-center col-span-2">Invoice #{{ invoice.id }}</h1>
</li>
</ul>
</div>
<button class="btn btn-secondary"
<button class="btn btn-secondary btn-outline"
hx-trigger="click once"
hx-swap="beforeend"
hx-target="#modal_container"
Expand Down
Loading

0 comments on commit f80abd2

Please sign in to comment.