chore: enable/disable signup in self hosted environments (#1271)

* dev: new onboarding workflow for self hosted instance

* dev: additional flag on user creation

* dev: segregate sign up and sign in endpoint

* dev: update sign in endpoint for not existing users
This commit is contained in:
pablohashescobar 2023-06-16 18:23:39 +05:30 committed by GitHub
parent 02111d779b
commit 592fe94cb4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 174 additions and 95 deletions

View File

@ -65,4 +65,6 @@ NGINX_PORT=80
DEFAULT_EMAIL="captain@plane.so" DEFAULT_EMAIL="captain@plane.so"
DEFAULT_PASSWORD="password123" DEFAULT_PASSWORD="password123"
# SignUps
ENABLE_SIGNUP="1"
# Auto generated and Required that will be generated from setup.sh # Auto generated and Required that will be generated from setup.sh

View File

@ -5,6 +5,7 @@ from django.urls import path
from plane.api.views import ( from plane.api.views import (
# Authentication # Authentication
SignUpEndpoint,
SignInEndpoint, SignInEndpoint,
SignOutEndpoint, SignOutEndpoint,
MagicSignInEndpoint, MagicSignInEndpoint,
@ -154,6 +155,7 @@ urlpatterns = [
# Social Auth # Social Auth
path("social-auth/", OauthEndpoint.as_view(), name="oauth"), path("social-auth/", OauthEndpoint.as_view(), name="oauth"),
# Auth # Auth
path("sign-up/", SignUpEndpoint.as_view(), name="sign-up"),
path("sign-in/", SignInEndpoint.as_view(), name="sign-in"), path("sign-in/", SignInEndpoint.as_view(), name="sign-in"),
path("sign-out/", SignOutEndpoint.as_view(), name="sign-out"), path("sign-out/", SignOutEndpoint.as_view(), name="sign-out"),
# Magic Sign In/Up # Magic Sign In/Up

View File

@ -79,6 +79,7 @@ from .auth_extended import (
from .authentication import ( from .authentication import (
SignUpEndpoint,
SignInEndpoint, SignInEndpoint,
SignOutEndpoint, SignOutEndpoint,
MagicSignInEndpoint, MagicSignInEndpoint,

View File

@ -36,6 +36,99 @@ def get_tokens_for_user(user):
) )
class SignUpEndpoint(BaseAPIView):
permission_classes = (AllowAny,)
def post(self, request):
try:
if not settings.ENABLE_SIGNUP:
return Response(
{
"error": "New account creation is disabled. Please contact your site administrator"
},
status=status.HTTP_400_BAD_REQUEST,
)
email = request.data.get("email", False)
password = request.data.get("password", False)
## Raise exception if any of the above are missing
if not email or not password:
return Response(
{"error": "Both email and password are required"},
status=status.HTTP_400_BAD_REQUEST,
)
email = email.strip().lower()
try:
validate_email(email)
except ValidationError as e:
return Response(
{"error": "Please provide a valid email address."},
status=status.HTTP_400_BAD_REQUEST,
)
# Check if the user already exists
if User.objects.filter(email=email).exists():
return Response(
{"error": "User already exist please sign in"},
status=status.HTTP_400_BAD_REQUEST,
)
user = User.objects.create(email=email, username=uuid.uuid4().hex)
user.set_password(password)
# settings last actives for the user
user.last_active = timezone.now()
user.last_login_time = timezone.now()
user.last_login_ip = request.META.get("REMOTE_ADDR")
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.token_updated_at = timezone.now()
user.save()
serialized_user = UserSerializer(user).data
access_token, refresh_token = get_tokens_for_user(user)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
"user": serialized_user,
}
# Send Analytics
if settings.ANALYTICS_BASE_API:
_ = requests.post(
settings.ANALYTICS_BASE_API,
headers={
"Content-Type": "application/json",
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
},
json={
"event_id": uuid.uuid4().hex,
"event_data": {
"medium": "email",
},
"user": {"email": email, "id": str(user.id)},
"device_ctx": {
"ip": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get("HTTP_USER_AGENT"),
},
"event_type": "SIGN_UP",
},
)
return Response(data, status=status.HTTP_200_OK)
except Exception as e:
capture_exception(e)
return Response(
{"error": "Something went wrong please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)
class SignInEndpoint(BaseAPIView): class SignInEndpoint(BaseAPIView):
permission_classes = (AllowAny,) permission_classes = (AllowAny,)
@ -63,108 +156,69 @@ class SignInEndpoint(BaseAPIView):
user = User.objects.filter(email=email).first() user = User.objects.filter(email=email).first()
# Sign up Process
if user is None: if user is None:
user = User.objects.create(email=email, username=uuid.uuid4().hex) return Response(
user.set_password(password) {
"error": "Sorry, we could not find a user with the provided credentials. Please try again."
},
status=status.HTTP_403_FORBIDDEN,
)
# settings last actives for the user # Sign up Process
user.last_active = timezone.now() if not user.check_password(password):
user.last_login_time = timezone.now() return Response(
user.last_login_ip = request.META.get("REMOTE_ADDR") {
user.last_login_uagent = request.META.get("HTTP_USER_AGENT") "error": "Sorry, we could not find a user with the provided credentials. Please try again."
user.token_updated_at = timezone.now() },
user.save() status=status.HTTP_403_FORBIDDEN,
)
if not user.is_active:
return Response(
{
"error": "Your account has been deactivated. Please contact your site administrator."
},
status=status.HTTP_403_FORBIDDEN,
)
serialized_user = UserSerializer(user).data serialized_user = UserSerializer(user).data
access_token, refresh_token = get_tokens_for_user(user) # settings last active for the user
user.last_active = timezone.now()
user.last_login_time = timezone.now()
user.last_login_ip = request.META.get("REMOTE_ADDR")
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.token_updated_at = timezone.now()
user.save()
data = { access_token, refresh_token = get_tokens_for_user(user)
"access_token": access_token, # Send Analytics
"refresh_token": refresh_token, if settings.ANALYTICS_BASE_API:
"user": serialized_user, _ = requests.post(
} settings.ANALYTICS_BASE_API,
headers={
# Send Analytics "Content-Type": "application/json",
if settings.ANALYTICS_BASE_API: "X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
_ = requests.post( },
settings.ANALYTICS_BASE_API, json={
headers={ "event_id": uuid.uuid4().hex,
"Content-Type": "application/json", "event_data": {
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY, "medium": "email",
}, },
json={ "user": {"email": email, "id": str(user.id)},
"event_id": uuid.uuid4().hex, "device_ctx": {
"event_data": { "ip": request.META.get("REMOTE_ADDR"),
"medium": "email", "user_agent": request.META.get("HTTP_USER_AGENT"),
},
"user": {"email": email, "id": str(user.id)},
"device_ctx": {
"ip": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get("HTTP_USER_AGENT"),
},
"event_type": "SIGN_UP",
}, },
) "event_type": "SIGN_IN",
},
)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
"user": serialized_user,
}
return Response(data, status=status.HTTP_200_OK) return Response(data, status=status.HTTP_200_OK)
# Sign in Process
else:
if not user.check_password(password):
return Response(
{
"error": "Sorry, we could not find a user with the provided credentials. Please try again."
},
status=status.HTTP_403_FORBIDDEN,
)
if not user.is_active:
return Response(
{
"error": "Your account has been deactivated. Please contact your site administrator."
},
status=status.HTTP_403_FORBIDDEN,
)
serialized_user = UserSerializer(user).data
# settings last active for the user
user.last_active = timezone.now()
user.last_login_time = timezone.now()
user.last_login_ip = request.META.get("REMOTE_ADDR")
user.last_login_uagent = request.META.get("HTTP_USER_AGENT")
user.token_updated_at = timezone.now()
user.save()
access_token, refresh_token = get_tokens_for_user(user)
# Send Analytics
if settings.ANALYTICS_BASE_API:
_ = requests.post(
settings.ANALYTICS_BASE_API,
headers={
"Content-Type": "application/json",
"X-Auth-Token": settings.ANALYTICS_SECRET_KEY,
},
json={
"event_id": uuid.uuid4().hex,
"event_data": {
"medium": "email",
},
"user": {"email": email, "id": str(user.id)},
"device_ctx": {
"ip": request.META.get("REMOTE_ADDR"),
"user_agent": request.META.get("HTTP_USER_AGENT"),
},
"event_type": "SIGN_IN",
},
)
data = {
"access_token": access_token,
"refresh_token": refresh_token,
"user": serialized_user,
}
return Response(data, status=status.HTTP_200_OK)
except Exception as e: except Exception as e:
capture_exception(e) capture_exception(e)

View File

@ -2,7 +2,7 @@
import jwt import jwt
from datetime import date, datetime from datetime import date, datetime
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from uuid import uuid4
# Django imports # Django imports
from django.db import IntegrityError from django.db import IntegrityError
from django.db.models import Prefetch from django.db.models import Prefetch
@ -249,6 +249,17 @@ class InviteWorkspaceEndpoint(BaseAPIView):
email__in=[email.get("email") for email in emails] email__in=[email.get("email") for email in emails]
).select_related("workspace") ).select_related("workspace")
# create the user if signup is disabled
if settings.DOCKERIZED and not settings.ENABLE_SIGNUP:
_ = User.objects.bulk_create([
User(
email=email.get("email"),
password=str(uuid4().hex),
is_password_autoset=True
)
for email in emails
], batch_size=100)
for invitation in workspace_invitations: for invitation in workspace_invitations:
workspace_invitation.delay( workspace_invitation.delay(
invitation.email, invitation.email,

View File

@ -91,3 +91,5 @@ CELERY_RESULT_BACKEND = os.environ.get("REDIS_URL")
CELERY_BROKER_URL = os.environ.get("REDIS_URL") CELERY_BROKER_URL = os.environ.get("REDIS_URL")
GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False) GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False)
ENABLE_SIGNUP = os.environ.get("ENABLE_SIGNUP", "1") == "1"

View File

@ -258,3 +258,6 @@ else:
CELERY_BROKER_URL = broker_url CELERY_BROKER_URL = broker_url
GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False) GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False)
ENABLE_SIGNUP = os.environ.get("ENABLE_SIGNUP", "1") == "1"

View File

@ -211,3 +211,5 @@ CELERY_RESULT_BACKEND = broker_url
CELERY_BROKER_URL = broker_url CELERY_BROKER_URL = broker_url
GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False) GITHUB_ACCESS_TOKEN = os.environ.get("GITHUB_ACCESS_TOKEN", False)
ENABLE_SIGNUP = os.environ.get("ENABLE_SIGNUP", "1") == "1"

View File

@ -54,6 +54,7 @@ services:
DEFAULT_EMAIL: ${DEFAULT_EMAIL} DEFAULT_EMAIL: ${DEFAULT_EMAIL}
DEFAULT_PASSWORD: ${DEFAULT_PASSWORD} DEFAULT_PASSWORD: ${DEFAULT_PASSWORD}
USE_MINIO: ${USE_MINIO} USE_MINIO: ${USE_MINIO}
ENABLE_SIGNUP: ${ENABLE_SIGNUP}
depends_on: depends_on:
- plane-db - plane-db
- plane-redis - plane-redis
@ -91,6 +92,7 @@ services:
DEFAULT_EMAIL: ${DEFAULT_EMAIL:-captain@plane.so} DEFAULT_EMAIL: ${DEFAULT_EMAIL:-captain@plane.so}
DEFAULT_PASSWORD: ${DEFAULT_PASSWORD:-password123} DEFAULT_PASSWORD: ${DEFAULT_PASSWORD:-password123}
USE_MINIO: ${USE_MINIO} USE_MINIO: ${USE_MINIO}
ENABLE_SIGNUP: ${ENABLE_SIGNUP}
depends_on: depends_on:
- plane-api - plane-api
- plane-db - plane-db