forked from github/plane
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:
parent
02111d779b
commit
592fe94cb4
@ -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
|
@ -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
|
||||||
|
@ -79,6 +79,7 @@ from .auth_extended import (
|
|||||||
|
|
||||||
|
|
||||||
from .authentication import (
|
from .authentication import (
|
||||||
|
SignUpEndpoint,
|
||||||
SignInEndpoint,
|
SignInEndpoint,
|
||||||
SignOutEndpoint,
|
SignOutEndpoint,
|
||||||
MagicSignInEndpoint,
|
MagicSignInEndpoint,
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
@ -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"
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user