Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/new login #8120

Merged
merged 130 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
130 commits
Select commit Hold shift + click to select a range
42e6bf5
feat: add email templates for login and reset password
douxc Aug 28, 2024
ccc0ec8
feat: add email code login
ZhouhaoJiang Aug 29, 2024
da684eb
feat: update register logic
ZhouhaoJiang Aug 29, 2024
ede775c
feat: add reset password api
ZhouhaoJiang Aug 29, 2024
3c747bc
feat: add system future config
ZhouhaoJiang Aug 29, 2024
83bf1c9
fix: email code login redirect
ZhouhaoJiang Aug 29, 2024
c3b18d0
fix: string error
ZhouhaoJiang Aug 29, 2024
865395a
fix: send_reset_password_email limit email error
ZhouhaoJiang Aug 30, 2024
eadf75a
feat: add login account not found
ZhouhaoJiang Sep 2, 2024
955e287
feat: add oauth account not found
ZhouhaoJiang Sep 2, 2024
8a014bd
feat: update invite workspace service
ZhouhaoJiang Sep 2, 2024
4938e82
feat: remove activate password param
ZhouhaoJiang Sep 2, 2024
cbdbfb8
feat: reformat
ZhouhaoJiang Sep 2, 2024
c84f004
feat: add oauth invite redict
ZhouhaoJiang Sep 2, 2024
2c7cb54
fix: oauth AccountNotFound
ZhouhaoJiang Sep 2, 2024
910f9b7
feat: remove redict signin
ZhouhaoJiang Sep 2, 2024
0da352a
fix: get_authorization_url invite token
ZhouhaoJiang Sep 2, 2024
f342995
feat: remove extra judgement
ZhouhaoJiang Sep 2, 2024
b290dca
feat: update oauth invite_token redirect url
ZhouhaoJiang Sep 2, 2024
7136714
feat: add activate token
ZhouhaoJiang Sep 2, 2024
7e48aa6
feat: add invitation email judgment
ZhouhaoJiang Sep 2, 2024
0eefd3a
feat: change EMAIL_CODE_LOGIN_TOKEN_EXPIRY_HOURS
ZhouhaoJiang Sep 3, 2024
943259c
feat: add AccountPasswordError
ZhouhaoJiang Sep 3, 2024
d319913
feat: update invite workspace member email password login logic
ZhouhaoJiang Sep 3, 2024
78b1aab
feat: add NotAllowedCreateWorkspace and NotAllowedRegister
ZhouhaoJiang Sep 3, 2024
187d932
feat: update register invite member logic
ZhouhaoJiang Sep 3, 2024
a7b6a24
feat: update docker env example
ZhouhaoJiang Sep 3, 2024
1ae9667
feat: update reset-password redirect url
ZhouhaoJiang Sep 3, 2024
34cd7b9
feat: change login no count redirect
ZhouhaoJiang Sep 4, 2024
bd4bf3f
feat: change login AccountNotFound response
ZhouhaoJiang Sep 4, 2024
8c05f1c
feat: reformat
ZhouhaoJiang Sep 4, 2024
a34908d
feat: update activate
ZhouhaoJiang Sep 9, 2024
d5427b8
feat: remove self host judgement
ZhouhaoJiang Sep 9, 2024
00566af
fix: reformat
ZhouhaoJiang Sep 9, 2024
076ce11
feat: add OAuth invite token check
ZhouhaoJiang Sep 9, 2024
3742a27
fix: code reformat
ZhouhaoJiang Sep 9, 2024
1f8bad9
feat: update env example
ZhouhaoJiang Sep 9, 2024
b46bb3e
feat: add ALLOW_REGISTER judgement in _generate_account
ZhouhaoJiang Sep 9, 2024
ef98837
fix: OAuth error when not allow register
ZhouhaoJiang Sep 9, 2024
97ecacc
feat: dev/reformat
ZhouhaoJiang Sep 9, 2024
358a5f6
feat: add OAuthCallback redirect
ZhouhaoJiang Sep 9, 2024
4893631
fix: oauth error when not allowed create workspace
ZhouhaoJiang Sep 9, 2024
3f954e2
feat: register service WorkSpaceNotAllowedCreateError
ZhouhaoJiang Sep 10, 2024
57a2534
feat: add workspace not found
ZhouhaoJiang Sep 10, 2024
4d1efbe
feat: add not ALLOW_CREATE_WORKSPACE
ZhouhaoJiang Sep 10, 2024
ca5b294
feat: add NotAllowedCreateWorkspace
ZhouhaoJiang Sep 10, 2024
148747c
fix: raise
ZhouhaoJiang Sep 10, 2024
38ea1dd
feat: update SystemFeatureModel
ZhouhaoJiang Sep 11, 2024
dc0caab
fix: email code login tenant check
ZhouhaoJiang Sep 12, 2024
8c2f381
feat: add create_owner_tenant_if_not_exist WorkSpaceNotAllowedCreateE…
ZhouhaoJiang Sep 12, 2024
b6abd5b
Merge branch 'main' into feat/new-login
ZhouhaoJiang Sep 12, 2024
edfbcd4
fix: lint error
ZhouhaoJiang Sep 12, 2024
ce94a61
fix: N817 CamelCase ElementTree imported as acronym ET
ZhouhaoJiang Sep 12, 2024
dc04431
chore: apply pep8-naming rules for naming
ZhouhaoJiang Sep 12, 2024
0b43634
chore: remove param
ZhouhaoJiang Sep 12, 2024
753f802
chore: remove not use import
ZhouhaoJiang Sep 12, 2024
619eeec
Merge branch 'main' into feat/new-login
ZhouhaoJiang Sep 12, 2024
6221ae4
chore: noqa S701
ZhouhaoJiang Sep 12, 2024
82ebe88
chore: noqa SIM115
ZhouhaoJiang Sep 12, 2024
514f839
feat: update EmailOrPasswordMismatchError
ZhouhaoJiang Sep 14, 2024
cd277aa
Merge branch 'main' into feat/new-login
ZhouhaoJiang Sep 14, 2024
18adee6
chore: PLR6201 Use a set literal when testing for membership
ZhouhaoJiang Sep 14, 2024
20dc555
feat: remove env example
ZhouhaoJiang Sep 14, 2024
61e4b3c
feat: update mismatch description
ZhouhaoJiang Sep 14, 2024
bc02585
feat: update message
ZhouhaoJiang Sep 14, 2024
3e83be9
feat: add fulfill_login_params_from_env
ZhouhaoJiang Sep 14, 2024
b529ba9
chore: format
ZhouhaoJiang Sep 14, 2024
75fb4ca
fix: spell
ZhouhaoJiang Sep 14, 2024
346cae9
feat: add email code login rate limiter
ZhouhaoJiang Sep 23, 2024
799ff30
feat: add email password limit
ZhouhaoJiang Sep 23, 2024
f6f6bb1
Merge branch 'main' into feat/new-login
ZhouhaoJiang Sep 23, 2024
9223b0e
chore: style lint
ZhouhaoJiang Sep 23, 2024
d58726c
feat: add login limit error
ZhouhaoJiang Sep 23, 2024
4494eea
feat: update email_code_login_rate_limiter
ZhouhaoJiang Sep 23, 2024
34c97d6
chore: change password type
ZhouhaoJiang Sep 26, 2024
0cbef25
chore: change email template style
ZhouhaoJiang Sep 26, 2024
5066233
feat: email api add password param
ZhouhaoJiang Sep 26, 2024
9e4ee2b
feat: add EmailPasswordLoginLimitError
ZhouhaoJiang Sep 26, 2024
b249f2b
feat: update raise error
ZhouhaoJiang Sep 26, 2024
8059706
chore: style lint
ZhouhaoJiang Sep 26, 2024
456e7a4
feat: change email language
ZhouhaoJiang Sep 26, 2024
3801378
feat: change valid_password error resp
ZhouhaoJiang Sep 27, 2024
133beea
fix: change type error
ZhouhaoJiang Sep 27, 2024
62f1408
feat: change error resp
ZhouhaoJiang Sep 27, 2024
d3d0d64
chore: change invite member template
ZhouhaoJiang Sep 27, 2024
9a69180
feat: self host not create workspace
ZhouhaoJiang Sep 27, 2024
161d725
feat: add self host user auto join
ZhouhaoJiang Sep 27, 2024
51e7c17
fix: tenant search
ZhouhaoJiang Sep 27, 2024
6e5fd67
fix: query error
ZhouhaoJiang Sep 27, 2024
7ad6c32
feat: auto update user status
ZhouhaoJiang Sep 27, 2024
86b182f
feat: add email code login invite token
ZhouhaoJiang Sep 29, 2024
d481a1b
chore: lint
ZhouhaoJiang Sep 29, 2024
cd88f27
Merge branch 'main' into feat/new-login
ZhouhaoJiang Sep 29, 2024
067955c
feat: remove self-host
ZhouhaoJiang Sep 29, 2024
7e4d105
feat: add setup only
ZhouhaoJiang Sep 29, 2024
5165894
feat: add invite menmber workspace all create
ZhouhaoJiang Sep 29, 2024
91d3e43
feat: change ALLOW_CREATE_WORKSPACE false
ZhouhaoJiang Sep 29, 2024
01d1cae
fix: unique_tenant_account_join
ZhouhaoJiang Sep 30, 2024
f629710
chore: lint
ZhouhaoJiang Sep 30, 2024
98aa540
fix: login password judgement
ZhouhaoJiang Sep 30, 2024
9f36156
feat: add ip email code ligin limit
ZhouhaoJiang Sep 30, 2024
c2e40d5
Merge branch 'main' into feat/new-login-refresh-token
ZhouhaoJiang Oct 14, 2024
ad6ade0
feat: add refresh token
ZhouhaoJiang Oct 14, 2024
6674f12
feat: update login type info
ZhouhaoJiang Oct 11, 2024
fd23e63
feat: update login language;
ZhouhaoJiang Oct 15, 2024
b105998
feat: update EmailPasswordLoginLimitError
ZhouhaoJiang Oct 15, 2024
808e8e0
feat: update email template
ZhouhaoJiang Oct 15, 2024
38c5a57
feat: update reset password template
ZhouhaoJiang Oct 16, 2024
2738b55
fix: not allow create workspace resp
ZhouhaoJiang Oct 16, 2024
2cdb2eb
fix: logout account AnonymousUserMixin
ZhouhaoJiang Oct 16, 2024
f4bd465
feat: update invite member
ZhouhaoJiang Oct 16, 2024
dd1ca37
feat: update ALLOW_REGISTER default value
ZhouhaoJiang Oct 16, 2024
5c8d149
feat: update fulfill_params_from_enterprise
ZhouhaoJiang Oct 17, 2024
abe6a1b
Merge branch 'main' into feat/new-login
ZhouhaoJiang Oct 17, 2024
be4b62d
feat: add init login type
ZhouhaoJiang Oct 17, 2024
07ec7e0
feat: update frozen
ZhouhaoJiang Oct 17, 2024
d848eff
chore: remove extra config and init login type
ZhouhaoJiang Oct 17, 2024
c1f0595
chore: update MAX_VARIABLE_SIZE
ZhouhaoJiang Oct 17, 2024
8965498
chore: update MAX_VARIABLE_SIZE
ZhouhaoJiang Oct 17, 2024
f926865
feat: update control
ZhouhaoJiang Oct 17, 2024
4dd823d
fix: _fulfill_login_params_from_env error
ZhouhaoJiang Oct 17, 2024
91477e1
feat: add class variable
ZhouhaoJiang Oct 18, 2024
50435b8
feat: update mail template;
ZhouhaoJiang Oct 18, 2024
9f7a929
feat: add new guy redirect resets
ZhouhaoJiang Oct 18, 2024
d2a65d3
feat: update resets
ZhouhaoJiang Oct 18, 2024
380c40c
chore: lint
ZhouhaoJiang Oct 18, 2024
e78634e
feat: ignore error
ZhouhaoJiang Oct 18, 2024
2b6fb0b
feat: update account not found
ZhouhaoJiang Oct 18, 2024
9f72fdf
feat: optimizing code
ZhouhaoJiang Oct 18, 2024
85ad6a3
refactor.
GarfieldDai Oct 18, 2024
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 api/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -326,4 +326,4 @@ POSITION_TOOL_EXCLUDES=

POSITION_PROVIDER_PINS=
POSITION_PROVIDER_INCLUDES=
POSITION_PROVIDER_EXCLUDES=
POSITION_PROVIDER_EXCLUDES=
48 changes: 45 additions & 3 deletions api/configs/feature/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
from typing import Annotated, Optional

from pydantic import AliasChoices, Field, HttpUrl, NegativeInt, NonNegativeInt, PositiveInt, computed_field
from typing import Annotated, Literal, Optional

from pydantic import (
AliasChoices,
Field,
HttpUrl,
NegativeInt,
NonNegativeInt,
PositiveFloat,
PositiveInt,
computed_field,
)
from pydantic_settings import BaseSettings

from configs.feature.hosted_service import HostedServiceConfig
Expand Down Expand Up @@ -473,6 +482,11 @@ class MailConfig(BaseSettings):
default=False,
)

EMAIL_SEND_IP_LIMIT_PER_MINUTE: PositiveInt = Field(
description="Maximum number of emails allowed to be sent from the same IP address in a minute",
default=50,
)


class RagEtlConfig(BaseSettings):
"""
Expand Down Expand Up @@ -614,6 +628,33 @@ def POSITION_TOOL_EXCLUDES_SET(self) -> set[str]:
return {item.strip() for item in self.POSITION_TOOL_EXCLUDES.split(",") if item.strip() != ""}


class LoginConfig(BaseSettings):
ENABLE_EMAIL_CODE_LOGIN: bool = Field(
description="whether to enable email code login",
default=False,
)
ENABLE_EMAIL_PASSWORD_LOGIN: bool = Field(
description="whether to enable email password login",
default=True,
)
ENABLE_SOCIAL_OAUTH_LOGIN: bool = Field(
description="whether to enable github/google oauth login",
default=False,
)
EMAIL_CODE_LOGIN_TOKEN_EXPIRY_HOURS: PositiveFloat = Field(
description="expiry time in hours for email code login token",
default=1 / 12,
)
ALLOW_REGISTER: bool = Field(
description="whether to enable register",
default=False,
)
ALLOW_CREATE_WORKSPACE: bool = Field(
description="whether to enable create workspace",
default=False,
)


class FeatureConfig(
# place the configs in alphabet order
AppExecutionConfig,
Expand All @@ -639,6 +680,7 @@ class FeatureConfig(
UpdateConfig,
WorkflowConfig,
WorkspaceConfig,
LoginConfig,
# hosted services config
HostedServiceConfig,
CeleryBeatConfig,
Expand Down
38 changes: 19 additions & 19 deletions api/controllers/console/auth/activate.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import base64
import datetime
import secrets

from flask import request
from flask_restful import Resource, reqparse

from constants.languages import supported_language
from controllers.console import api
from controllers.console.error import AlreadyActivateError
from extensions.ext_database import db
from libs.helper import StrLen, email, timezone
from libs.password import hash_password, valid_password
from models.account import AccountStatus
from services.account_service import RegisterService
from libs.helper import StrLen, email, extract_remote_ip, timezone
from models.account import AccountStatus, Tenant
from services.account_service import AccountService, RegisterService


class ActivateCheckApi(Resource):
Expand All @@ -27,8 +25,18 @@ def get(self):
token = args["token"]

invitation = RegisterService.get_invitation_if_token_valid(workspaceId, reg_email, token)

return {"is_valid": invitation is not None, "workspace_name": invitation["tenant"].name if invitation else None}
if invitation:
data = invitation.get("data", {})
tenant: Tenant = invitation.get("tenant", None)
workspace_name = tenant.name if tenant else None
workspace_id = tenant.id if tenant else None
invitee_email = data.get("email") if data else None
return {
"is_valid": invitation is not None,
"data": {"workspace_name": workspace_name, "workspace_id": workspace_id, "email": invitee_email},
}
else:
return {"is_valid": False}


class ActivateApi(Resource):
Expand All @@ -38,7 +46,6 @@ def post(self):
parser.add_argument("email", type=email, required=False, nullable=True, location="json")
parser.add_argument("token", type=str, required=True, nullable=False, location="json")
parser.add_argument("name", type=StrLen(30), required=True, nullable=False, location="json")
parser.add_argument("password", type=valid_password, required=True, nullable=False, location="json")
parser.add_argument(
"interface_language", type=supported_language, required=True, nullable=False, location="json"
)
Expand All @@ -54,23 +61,16 @@ def post(self):
account = invitation["account"]
account.name = args["name"]

# generate password salt
salt = secrets.token_bytes(16)
base64_salt = base64.b64encode(salt).decode()

# encrypt password with salt
password_hashed = hash_password(args["password"], salt)
base64_password_hashed = base64.b64encode(password_hashed).decode()
account.password = base64_password_hashed
account.password_salt = base64_salt
account.interface_language = args["interface_language"]
account.timezone = args["timezone"]
account.interface_theme = "light"
account.status = AccountStatus.ACTIVE.value
account.initialized_at = datetime.datetime.now(datetime.timezone.utc).replace(tzinfo=None)
db.session.commit()

return {"result": "success"}
token_pair = AccountService.login(account, ip_address=extract_remote_ip(request))

return {"result": "success", "data": token_pair.model_dump()}


api.add_resource(ActivateCheckApi, "/activate/check")
Expand Down
26 changes: 25 additions & 1 deletion api/controllers/console/auth/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,29 @@ class InvalidTokenError(BaseHTTPException):

class PasswordResetRateLimitExceededError(BaseHTTPException):
error_code = "password_reset_rate_limit_exceeded"
description = "Password reset rate limit exceeded. Try again later."
description = "Too many password reset emails have been sent. Please try again in 1 minutes."
code = 429


class EmailCodeError(BaseHTTPException):
error_code = "email_code_error"
description = "Email code is invalid or expired."
code = 400


class EmailOrPasswordMismatchError(BaseHTTPException):
ZhouhaoJiang marked this conversation as resolved.
Show resolved Hide resolved
error_code = "email_or_password_mismatch"
description = "The email or password is mismatched."
code = 400


class EmailPasswordLoginLimitError(BaseHTTPException):
error_code = "email_code_login_limit"
description = "Too many incorrect password attempts. Please try again later."
code = 429


class EmailCodeLoginRateLimitExceededError(BaseHTTPException):
error_code = "email_code_login_rate_limit_exceeded"
description = "Too many login emails have been sent. Please try again in 5 minutes."
code = 429
92 changes: 63 additions & 29 deletions api/controllers/console/auth/forgot_password.py
Original file line number Diff line number Diff line change
@@ -1,65 +1,82 @@
import base64
import logging
import secrets

from flask import request
from flask_restful import Resource, reqparse

from constants.languages import languages
from controllers.console import api
from controllers.console.auth.error import (
EmailCodeError,
InvalidEmailError,
InvalidTokenError,
PasswordMismatchError,
PasswordResetRateLimitExceededError,
)
from controllers.console.error import EmailSendIpLimitError, NotAllowedRegister
from controllers.console.setup import setup_required
from events.tenant_event import tenant_was_created
from extensions.ext_database import db
from libs.helper import email as email_validate
from libs.helper import email, extract_remote_ip
from libs.password import hash_password, valid_password
from models.account import Account
from services.account_service import AccountService
from services.errors.account import RateLimitExceededError
from services.account_service import AccountService, TenantService
from services.errors.workspace import WorkSpaceNotAllowedCreateError
from services.feature_service import FeatureService


class ForgotPasswordSendEmailApi(Resource):
@setup_required
def post(self):
parser = reqparse.RequestParser()
parser.add_argument("email", type=str, required=True, location="json")
parser.add_argument("email", type=email, required=True, location="json")
parser.add_argument("language", type=str, required=False, location="json")
args = parser.parse_args()

email = args["email"]

if not email_validate(email):
raise InvalidEmailError()
ip_address = extract_remote_ip(request)
if AccountService.is_email_send_ip_limit(ip_address):
raise EmailSendIpLimitError()

account = Account.query.filter_by(email=email).first()

if account:
try:
AccountService.send_reset_password_email(account=account)
except RateLimitExceededError:
logging.warning(f"Rate limit exceeded for email: {account.email}")
raise PasswordResetRateLimitExceededError()
if args["language"] is not None and args["language"] == "zh-Hans":
language = "zh-Hans"
else:
language = "en-US"

account = Account.query.filter_by(email=args["email"]).first()
token = None
if account is None:
if FeatureService.get_system_features().is_allow_register:
token = AccountService.send_reset_password_email(email=args["email"], language=language)
return {"result": "fail", "data": token, "code": "account_not_found"}
else:
raise NotAllowedRegister()
else:
# Return success to avoid revealing email registration status
logging.warning(f"Attempt to reset password for unregistered email: {email}")
token = AccountService.send_reset_password_email(account=account, email=args["email"], language=language)

return {"result": "success"}
return {"result": "success", "data": token}


class ForgotPasswordCheckApi(Resource):
@setup_required
def post(self):
parser = reqparse.RequestParser()
parser.add_argument("email", type=str, required=True, location="json")
parser.add_argument("code", type=str, required=True, location="json")
parser.add_argument("token", type=str, required=True, nullable=False, location="json")
args = parser.parse_args()
token = args["token"]

reset_data = AccountService.get_reset_password_data(token)
user_email = args["email"]

if reset_data is None:
return {"is_valid": False, "email": None}
return {"is_valid": True, "email": reset_data.get("email")}
token_data = AccountService.get_reset_password_data(args["token"])
if token_data is None:
raise InvalidTokenError()

if user_email != token_data.get("email"):
raise InvalidEmailError()

if args["code"] != token_data.get("code"):
raise EmailCodeError()

return {"is_valid": True, "email": token_data.get("email")}


class ForgotPasswordResetApi(Resource):
Expand Down Expand Up @@ -92,9 +109,26 @@ def post(self):
base64_password_hashed = base64.b64encode(password_hashed).decode()

account = Account.query.filter_by(email=reset_data.get("email")).first()
account.password = base64_password_hashed
account.password_salt = base64_salt
db.session.commit()
if account:
account.password = base64_password_hashed
account.password_salt = base64_salt
db.session.commit()
tenant = TenantService.get_join_tenants(account)
if not tenant and not FeatureService.get_system_features().is_allow_create_workspace:
tenant = TenantService.create_tenant(f"{account.name}'s Workspace")
TenantService.create_tenant_member(tenant, account, role="owner")
account.current_tenant = tenant
tenant_was_created.send(tenant)
else:
try:
account = AccountService.create_account_and_tenant(
email=reset_data.get("email"),
name=reset_data.get("email"),
password=password_confirm,
interface_language=languages[0],
)
except WorkSpaceNotAllowedCreateError:
pass

return {"result": "success"}

Expand Down
Loading