mirror of
https://github.com/danswer-ai/danswer.git
synced 2025-03-30 04:31:49 +02:00
Add anonymous user to main
Anonymous user
This commit is contained in:
commit
1291b3d930
@ -30,13 +30,16 @@ def load_no_auth_user_preferences(store: KeyValueStore) -> UserPreferences:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def fetch_no_auth_user(store: KeyValueStore) -> UserInfo:
|
def fetch_no_auth_user(
|
||||||
|
store: KeyValueStore, *, anonymous_user_enabled: bool | None = None
|
||||||
|
) -> UserInfo:
|
||||||
return UserInfo(
|
return UserInfo(
|
||||||
id=NO_AUTH_USER_ID,
|
id=NO_AUTH_USER_ID,
|
||||||
email=NO_AUTH_USER_EMAIL,
|
email=NO_AUTH_USER_EMAIL,
|
||||||
is_active=True,
|
is_active=True,
|
||||||
is_superuser=False,
|
is_superuser=False,
|
||||||
is_verified=True,
|
is_verified=True,
|
||||||
role=UserRole.ADMIN,
|
role=UserRole.BASIC if anonymous_user_enabled else UserRole.ADMIN,
|
||||||
preferences=load_no_auth_user_preferences(store),
|
preferences=load_no_auth_user_preferences(store),
|
||||||
|
is_anonymous_user=anonymous_user_enabled,
|
||||||
)
|
)
|
||||||
|
@ -69,6 +69,7 @@ from onyx.configs.constants import AuthType
|
|||||||
from onyx.configs.constants import DANSWER_API_KEY_DUMMY_EMAIL_DOMAIN
|
from onyx.configs.constants import DANSWER_API_KEY_DUMMY_EMAIL_DOMAIN
|
||||||
from onyx.configs.constants import DANSWER_API_KEY_PREFIX
|
from onyx.configs.constants import DANSWER_API_KEY_PREFIX
|
||||||
from onyx.configs.constants import MilestoneRecordType
|
from onyx.configs.constants import MilestoneRecordType
|
||||||
|
from onyx.configs.constants import OnyxRedisLocks
|
||||||
from onyx.configs.constants import PASSWORD_SPECIAL_CHARS
|
from onyx.configs.constants import PASSWORD_SPECIAL_CHARS
|
||||||
from onyx.configs.constants import UNNAMED_KEY_PLACEHOLDER
|
from onyx.configs.constants import UNNAMED_KEY_PLACEHOLDER
|
||||||
from onyx.db.api_key import fetch_user_for_api_key
|
from onyx.db.api_key import fetch_user_for_api_key
|
||||||
@ -84,7 +85,7 @@ from onyx.db.models import AccessToken
|
|||||||
from onyx.db.models import OAuthAccount
|
from onyx.db.models import OAuthAccount
|
||||||
from onyx.db.models import User
|
from onyx.db.models import User
|
||||||
from onyx.db.users import get_user_by_email
|
from onyx.db.users import get_user_by_email
|
||||||
from onyx.server.utils import BasicAuthenticationError
|
from onyx.redis.redis_pool import get_redis_client
|
||||||
from onyx.utils.logger import setup_logger
|
from onyx.utils.logger import setup_logger
|
||||||
from onyx.utils.telemetry import create_milestone_and_report
|
from onyx.utils.telemetry import create_milestone_and_report
|
||||||
from onyx.utils.telemetry import optional_telemetry
|
from onyx.utils.telemetry import optional_telemetry
|
||||||
@ -98,6 +99,11 @@ from shared_configs.contextvars import CURRENT_TENANT_ID_CONTEXTVAR
|
|||||||
logger = setup_logger()
|
logger = setup_logger()
|
||||||
|
|
||||||
|
|
||||||
|
class BasicAuthenticationError(HTTPException):
|
||||||
|
def __init__(self, detail: str):
|
||||||
|
super().__init__(status_code=status.HTTP_403_FORBIDDEN, detail=detail)
|
||||||
|
|
||||||
|
|
||||||
def is_user_admin(user: User | None) -> bool:
|
def is_user_admin(user: User | None) -> bool:
|
||||||
if AUTH_TYPE == AuthType.DISABLED:
|
if AUTH_TYPE == AuthType.DISABLED:
|
||||||
return True
|
return True
|
||||||
@ -138,6 +144,20 @@ def user_needs_to_be_verified() -> bool:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def anonymous_user_enabled() -> bool:
|
||||||
|
if MULTI_TENANT:
|
||||||
|
return False
|
||||||
|
|
||||||
|
redis_client = get_redis_client(tenant_id=None)
|
||||||
|
value = redis_client.get(OnyxRedisLocks.ANONYMOUS_USER_ENABLED)
|
||||||
|
|
||||||
|
if value is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
assert isinstance(value, bytes)
|
||||||
|
return int(value.decode("utf-8")) == 1
|
||||||
|
|
||||||
|
|
||||||
def verify_email_is_invited(email: str) -> None:
|
def verify_email_is_invited(email: str) -> None:
|
||||||
whitelist = get_invited_users()
|
whitelist = get_invited_users()
|
||||||
if not whitelist:
|
if not whitelist:
|
||||||
@ -690,30 +710,36 @@ async def double_check_user(
|
|||||||
user: User | None,
|
user: User | None,
|
||||||
optional: bool = DISABLE_AUTH,
|
optional: bool = DISABLE_AUTH,
|
||||||
include_expired: bool = False,
|
include_expired: bool = False,
|
||||||
|
allow_anonymous_access: bool = False,
|
||||||
) -> User | None:
|
) -> User | None:
|
||||||
if optional:
|
if optional:
|
||||||
|
return user
|
||||||
|
|
||||||
|
if user is not None:
|
||||||
|
# If user attempted to authenticate, verify them, do not default
|
||||||
|
# to anonymous access if it fails.
|
||||||
|
if user_needs_to_be_verified() and not user.is_verified:
|
||||||
|
raise BasicAuthenticationError(
|
||||||
|
detail="Access denied. User is not verified.",
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
user.oidc_expiry
|
||||||
|
and user.oidc_expiry < datetime.now(timezone.utc)
|
||||||
|
and not include_expired
|
||||||
|
):
|
||||||
|
raise BasicAuthenticationError(
|
||||||
|
detail="Access denied. User's OIDC token has expired.",
|
||||||
|
)
|
||||||
|
|
||||||
|
return user
|
||||||
|
|
||||||
|
if allow_anonymous_access:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if user is None:
|
raise BasicAuthenticationError(
|
||||||
raise BasicAuthenticationError(
|
detail="Access denied. User is not authenticated.",
|
||||||
detail="Access denied. User is not authenticated.",
|
)
|
||||||
)
|
|
||||||
|
|
||||||
if user_needs_to_be_verified() and not user.is_verified:
|
|
||||||
raise BasicAuthenticationError(
|
|
||||||
detail="Access denied. User is not verified.",
|
|
||||||
)
|
|
||||||
|
|
||||||
if (
|
|
||||||
user.oidc_expiry
|
|
||||||
and user.oidc_expiry < datetime.now(timezone.utc)
|
|
||||||
and not include_expired
|
|
||||||
):
|
|
||||||
raise BasicAuthenticationError(
|
|
||||||
detail="Access denied. User's OIDC token has expired.",
|
|
||||||
)
|
|
||||||
|
|
||||||
return user
|
|
||||||
|
|
||||||
|
|
||||||
async def current_user_with_expired_token(
|
async def current_user_with_expired_token(
|
||||||
@ -728,6 +754,14 @@ async def current_limited_user(
|
|||||||
return await double_check_user(user)
|
return await double_check_user(user)
|
||||||
|
|
||||||
|
|
||||||
|
async def current_chat_accesssible_user(
|
||||||
|
user: User | None = Depends(optional_user),
|
||||||
|
) -> User | None:
|
||||||
|
return await double_check_user(
|
||||||
|
user, allow_anonymous_access=anonymous_user_enabled()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def current_user(
|
async def current_user(
|
||||||
user: User | None = Depends(optional_user),
|
user: User | None = Depends(optional_user),
|
||||||
) -> User | None:
|
) -> User | None:
|
||||||
|
@ -279,6 +279,7 @@ class OnyxRedisLocks:
|
|||||||
|
|
||||||
SLACK_BOT_LOCK = "da_lock:slack_bot"
|
SLACK_BOT_LOCK = "da_lock:slack_bot"
|
||||||
SLACK_BOT_HEARTBEAT_PREFIX = "da_heartbeat:slack_bot"
|
SLACK_BOT_HEARTBEAT_PREFIX = "da_heartbeat:slack_bot"
|
||||||
|
ANONYMOUS_USER_ENABLED = "anonymous_user_enabled"
|
||||||
|
|
||||||
|
|
||||||
class OnyxRedisSignals:
|
class OnyxRedisSignals:
|
||||||
|
@ -5,6 +5,7 @@ from fastapi.dependencies.models import Dependant
|
|||||||
from starlette.routing import BaseRoute
|
from starlette.routing import BaseRoute
|
||||||
|
|
||||||
from onyx.auth.users import current_admin_user
|
from onyx.auth.users import current_admin_user
|
||||||
|
from onyx.auth.users import current_chat_accesssible_user
|
||||||
from onyx.auth.users import current_curator_or_admin_user
|
from onyx.auth.users import current_curator_or_admin_user
|
||||||
from onyx.auth.users import current_limited_user
|
from onyx.auth.users import current_limited_user
|
||||||
from onyx.auth.users import current_user
|
from onyx.auth.users import current_user
|
||||||
@ -109,6 +110,7 @@ def check_router_auth(
|
|||||||
or depends_fn == current_curator_or_admin_user
|
or depends_fn == current_curator_or_admin_user
|
||||||
or depends_fn == api_key_dep
|
or depends_fn == api_key_dep
|
||||||
or depends_fn == current_user_with_expired_token
|
or depends_fn == current_user_with_expired_token
|
||||||
|
or depends_fn == current_chat_accesssible_user
|
||||||
or depends_fn == control_plane_dep
|
or depends_fn == control_plane_dep
|
||||||
or depends_fn == current_cloud_superuser
|
or depends_fn == current_cloud_superuser
|
||||||
):
|
):
|
||||||
|
@ -14,6 +14,7 @@ from pydantic import BaseModel
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from onyx.auth.users import current_admin_user
|
from onyx.auth.users import current_admin_user
|
||||||
|
from onyx.auth.users import current_chat_accesssible_user
|
||||||
from onyx.auth.users import current_curator_or_admin_user
|
from onyx.auth.users import current_curator_or_admin_user
|
||||||
from onyx.auth.users import current_user
|
from onyx.auth.users import current_user
|
||||||
from onyx.background.celery.celery_utils import get_deletion_attempt_snapshot
|
from onyx.background.celery.celery_utils import get_deletion_attempt_snapshot
|
||||||
@ -1055,7 +1056,7 @@ class BasicCCPairInfo(BaseModel):
|
|||||||
|
|
||||||
@router.get("/connector-status")
|
@router.get("/connector-status")
|
||||||
def get_basic_connector_indexing_status(
|
def get_basic_connector_indexing_status(
|
||||||
_: User = Depends(current_user),
|
_: User = Depends(current_chat_accesssible_user),
|
||||||
db_session: Session = Depends(get_session),
|
db_session: Session = Depends(get_session),
|
||||||
) -> list[BasicCCPairInfo]:
|
) -> list[BasicCCPairInfo]:
|
||||||
cc_pairs = get_connector_credential_pairs(db_session, eager_load_connector=True)
|
cc_pairs = get_connector_credential_pairs(db_session, eager_load_connector=True)
|
||||||
|
@ -10,6 +10,7 @@ from pydantic import BaseModel
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from onyx.auth.users import current_admin_user
|
from onyx.auth.users import current_admin_user
|
||||||
|
from onyx.auth.users import current_chat_accesssible_user
|
||||||
from onyx.auth.users import current_curator_or_admin_user
|
from onyx.auth.users import current_curator_or_admin_user
|
||||||
from onyx.auth.users import current_limited_user
|
from onyx.auth.users import current_limited_user
|
||||||
from onyx.auth.users import current_user
|
from onyx.auth.users import current_user
|
||||||
@ -323,7 +324,7 @@ def get_image_generation_tool(
|
|||||||
|
|
||||||
@basic_router.get("")
|
@basic_router.get("")
|
||||||
def list_personas(
|
def list_personas(
|
||||||
user: User | None = Depends(current_user),
|
user: User | None = Depends(current_chat_accesssible_user),
|
||||||
db_session: Session = Depends(get_session),
|
db_session: Session = Depends(get_session),
|
||||||
include_deleted: bool = False,
|
include_deleted: bool = False,
|
||||||
persona_ids: list[int] = Query(None),
|
persona_ids: list[int] = Query(None),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from onyx import __version__
|
from onyx import __version__
|
||||||
|
from onyx.auth.users import anonymous_user_enabled
|
||||||
from onyx.auth.users import user_needs_to_be_verified
|
from onyx.auth.users import user_needs_to_be_verified
|
||||||
from onyx.configs.app_configs import AUTH_TYPE
|
from onyx.configs.app_configs import AUTH_TYPE
|
||||||
from onyx.server.manage.models import AuthTypeResponse
|
from onyx.server.manage.models import AuthTypeResponse
|
||||||
@ -18,7 +19,9 @@ def healthcheck() -> StatusResponse:
|
|||||||
@router.get("/auth/type")
|
@router.get("/auth/type")
|
||||||
def get_auth_type() -> AuthTypeResponse:
|
def get_auth_type() -> AuthTypeResponse:
|
||||||
return AuthTypeResponse(
|
return AuthTypeResponse(
|
||||||
auth_type=AUTH_TYPE, requires_verification=user_needs_to_be_verified()
|
auth_type=AUTH_TYPE,
|
||||||
|
requires_verification=user_needs_to_be_verified(),
|
||||||
|
anonymous_user_enabled=anonymous_user_enabled(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ from fastapi import Query
|
|||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from onyx.auth.users import current_admin_user
|
from onyx.auth.users import current_admin_user
|
||||||
from onyx.auth.users import current_user
|
from onyx.auth.users import current_chat_accesssible_user
|
||||||
from onyx.db.engine import get_session
|
from onyx.db.engine import get_session
|
||||||
from onyx.db.llm import fetch_existing_llm_providers
|
from onyx.db.llm import fetch_existing_llm_providers
|
||||||
from onyx.db.llm import fetch_provider
|
from onyx.db.llm import fetch_provider
|
||||||
@ -189,7 +189,7 @@ def set_provider_as_default(
|
|||||||
|
|
||||||
@basic_router.get("/provider")
|
@basic_router.get("/provider")
|
||||||
def list_llm_provider_basics(
|
def list_llm_provider_basics(
|
||||||
user: User | None = Depends(current_user),
|
user: User | None = Depends(current_chat_accesssible_user),
|
||||||
db_session: Session = Depends(get_session),
|
db_session: Session = Depends(get_session),
|
||||||
) -> list[LLMProviderDescriptor]:
|
) -> list[LLMProviderDescriptor]:
|
||||||
return [
|
return [
|
||||||
|
@ -37,6 +37,7 @@ class AuthTypeResponse(BaseModel):
|
|||||||
# specifies whether the current auth setup requires
|
# specifies whether the current auth setup requires
|
||||||
# users to have verified emails
|
# users to have verified emails
|
||||||
requires_verification: bool
|
requires_verification: bool
|
||||||
|
anonymous_user_enabled: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
class UserPreferences(BaseModel):
|
class UserPreferences(BaseModel):
|
||||||
@ -61,6 +62,7 @@ class UserInfo(BaseModel):
|
|||||||
current_token_expiry_length: int | None = None
|
current_token_expiry_length: int | None = None
|
||||||
is_cloud_superuser: bool = False
|
is_cloud_superuser: bool = False
|
||||||
organization_name: str | None = None
|
organization_name: str | None = None
|
||||||
|
is_anonymous_user: bool | None = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_model(
|
def from_model(
|
||||||
@ -70,6 +72,7 @@ class UserInfo(BaseModel):
|
|||||||
expiry_length: int | None = None,
|
expiry_length: int | None = None,
|
||||||
is_cloud_superuser: bool = False,
|
is_cloud_superuser: bool = False,
|
||||||
organization_name: str | None = None,
|
organization_name: str | None = None,
|
||||||
|
is_anonymous_user: bool | None = None,
|
||||||
) -> "UserInfo":
|
) -> "UserInfo":
|
||||||
return cls(
|
return cls(
|
||||||
id=str(user.id),
|
id=str(user.id),
|
||||||
@ -96,6 +99,7 @@ class UserInfo(BaseModel):
|
|||||||
current_token_created_at=current_token_created_at,
|
current_token_created_at=current_token_created_at,
|
||||||
current_token_expiry_length=expiry_length,
|
current_token_expiry_length=expiry_length,
|
||||||
is_cloud_superuser=is_cloud_superuser,
|
is_cloud_superuser=is_cloud_superuser,
|
||||||
|
is_anonymous_user=is_anonymous_user,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -28,6 +28,7 @@ from onyx.auth.noauth_user import fetch_no_auth_user
|
|||||||
from onyx.auth.noauth_user import set_no_auth_user_preferences
|
from onyx.auth.noauth_user import set_no_auth_user_preferences
|
||||||
from onyx.auth.schemas import UserRole
|
from onyx.auth.schemas import UserRole
|
||||||
from onyx.auth.schemas import UserStatus
|
from onyx.auth.schemas import UserStatus
|
||||||
|
from onyx.auth.users import anonymous_user_enabled
|
||||||
from onyx.auth.users import current_admin_user
|
from onyx.auth.users import current_admin_user
|
||||||
from onyx.auth.users import current_curator_or_admin_user
|
from onyx.auth.users import current_curator_or_admin_user
|
||||||
from onyx.auth.users import current_user
|
from onyx.auth.users import current_user
|
||||||
@ -484,13 +485,15 @@ def verify_user_logged_in(
|
|||||||
# NOTE: this does not use `current_user` / `current_admin_user` because we don't want
|
# NOTE: this does not use `current_user` / `current_admin_user` because we don't want
|
||||||
# to enforce user verification here - the frontend always wants to get the info about
|
# to enforce user verification here - the frontend always wants to get the info about
|
||||||
# the current user regardless of if they are currently verified
|
# the current user regardless of if they are currently verified
|
||||||
|
|
||||||
if user is None:
|
if user is None:
|
||||||
# if auth type is disabled, return a dummy user with preferences from
|
# if auth type is disabled, return a dummy user with preferences from
|
||||||
# the key-value store
|
# the key-value store
|
||||||
if AUTH_TYPE == AuthType.DISABLED:
|
if AUTH_TYPE == AuthType.DISABLED:
|
||||||
store = get_kv_store()
|
store = get_kv_store()
|
||||||
return fetch_no_auth_user(store)
|
return fetch_no_auth_user(store)
|
||||||
|
if anonymous_user_enabled():
|
||||||
|
store = get_kv_store()
|
||||||
|
return fetch_no_auth_user(store, anonymous_user_enabled=True)
|
||||||
|
|
||||||
raise BasicAuthenticationError(detail="User Not Authenticated")
|
raise BasicAuthenticationError(detail="User Not Authenticated")
|
||||||
if user.oidc_expiry and user.oidc_expiry < datetime.now(timezone.utc):
|
if user.oidc_expiry and user.oidc_expiry < datetime.now(timezone.utc):
|
||||||
|
@ -19,6 +19,7 @@ from PIL import Image
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
from onyx.auth.users import current_chat_accesssible_user
|
||||||
from onyx.auth.users import current_limited_user
|
from onyx.auth.users import current_limited_user
|
||||||
from onyx.auth.users import current_user
|
from onyx.auth.users import current_user
|
||||||
from onyx.chat.chat_utils import create_chat_chain
|
from onyx.chat.chat_utils import create_chat_chain
|
||||||
@ -145,7 +146,7 @@ def update_chat_session_model(
|
|||||||
def get_chat_session(
|
def get_chat_session(
|
||||||
session_id: UUID,
|
session_id: UUID,
|
||||||
is_shared: bool = False,
|
is_shared: bool = False,
|
||||||
user: User | None = Depends(current_user),
|
user: User | None = Depends(current_chat_accesssible_user),
|
||||||
db_session: Session = Depends(get_session),
|
db_session: Session = Depends(get_session),
|
||||||
) -> ChatSessionDetailResponse:
|
) -> ChatSessionDetailResponse:
|
||||||
user_id = user.id if user is not None else None
|
user_id = user.id if user is not None else None
|
||||||
@ -200,7 +201,7 @@ def get_chat_session(
|
|||||||
@router.post("/create-chat-session")
|
@router.post("/create-chat-session")
|
||||||
def create_new_chat_session(
|
def create_new_chat_session(
|
||||||
chat_session_creation_request: ChatSessionCreationRequest,
|
chat_session_creation_request: ChatSessionCreationRequest,
|
||||||
user: User | None = Depends(current_limited_user),
|
user: User | None = Depends(current_chat_accesssible_user),
|
||||||
db_session: Session = Depends(get_session),
|
db_session: Session = Depends(get_session),
|
||||||
) -> CreateChatSessionID:
|
) -> CreateChatSessionID:
|
||||||
user_id = user.id if user is not None else None
|
user_id = user.id if user is not None else None
|
||||||
@ -333,7 +334,7 @@ async def is_connected(request: Request) -> Callable[[], bool]:
|
|||||||
def handle_new_chat_message(
|
def handle_new_chat_message(
|
||||||
chat_message_req: CreateChatMessageRequest,
|
chat_message_req: CreateChatMessageRequest,
|
||||||
request: Request,
|
request: Request,
|
||||||
user: User | None = Depends(current_limited_user),
|
user: User | None = Depends(current_chat_accesssible_user),
|
||||||
_rate_limit_check: None = Depends(check_token_rate_limits),
|
_rate_limit_check: None = Depends(check_token_rate_limits),
|
||||||
is_connected_func: Callable[[], bool] = Depends(is_connected),
|
is_connected_func: Callable[[], bool] = Depends(is_connected),
|
||||||
tenant_id: str = Depends(get_current_tenant_id),
|
tenant_id: str = Depends(get_current_tenant_id),
|
||||||
|
@ -11,7 +11,7 @@ from sqlalchemy import func
|
|||||||
from sqlalchemy import select
|
from sqlalchemy import select
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
from onyx.auth.users import current_user
|
from onyx.auth.users import current_chat_accesssible_user
|
||||||
from onyx.db.engine import get_session_context_manager
|
from onyx.db.engine import get_session_context_manager
|
||||||
from onyx.db.engine import get_session_with_tenant
|
from onyx.db.engine import get_session_with_tenant
|
||||||
from onyx.db.models import ChatMessage
|
from onyx.db.models import ChatMessage
|
||||||
@ -31,7 +31,7 @@ TOKEN_BUDGET_UNIT = 1_000
|
|||||||
|
|
||||||
|
|
||||||
def check_token_rate_limits(
|
def check_token_rate_limits(
|
||||||
user: User | None = Depends(current_user),
|
user: User | None = Depends(current_chat_accesssible_user),
|
||||||
) -> None:
|
) -> None:
|
||||||
# short circuit if no rate limits are set up
|
# short circuit if no rate limits are set up
|
||||||
# NOTE: result of `any_rate_limit_exists` is cached, so this call is fast 99% of the time
|
# NOTE: result of `any_rate_limit_exists` is cached, so this call is fast 99% of the time
|
||||||
|
@ -44,6 +44,7 @@ class Settings(BaseModel):
|
|||||||
maximum_chat_retention_days: int | None = None
|
maximum_chat_retention_days: int | None = None
|
||||||
gpu_enabled: bool | None = None
|
gpu_enabled: bool | None = None
|
||||||
product_gating: GatingType = GatingType.NONE
|
product_gating: GatingType = GatingType.NONE
|
||||||
|
anonymous_user_enabled: bool | None = None
|
||||||
|
|
||||||
|
|
||||||
class UserSettings(Settings):
|
class UserSettings(Settings):
|
||||||
|
@ -1,21 +1,38 @@
|
|||||||
from typing import cast
|
|
||||||
|
|
||||||
from onyx.configs.constants import KV_SETTINGS_KEY
|
from onyx.configs.constants import KV_SETTINGS_KEY
|
||||||
|
from onyx.configs.constants import OnyxRedisLocks
|
||||||
from onyx.key_value_store.factory import get_kv_store
|
from onyx.key_value_store.factory import get_kv_store
|
||||||
from onyx.key_value_store.interface import KvKeyNotFoundError
|
from onyx.redis.redis_pool import get_redis_client
|
||||||
from onyx.server.settings.models import Settings
|
from onyx.server.settings.models import Settings
|
||||||
|
from shared_configs.configs import MULTI_TENANT
|
||||||
|
|
||||||
|
|
||||||
def load_settings() -> Settings:
|
def load_settings() -> Settings:
|
||||||
dynamic_config_store = get_kv_store()
|
if MULTI_TENANT:
|
||||||
try:
|
# If multi-tenant, anonymous user is always false
|
||||||
settings = Settings(**cast(dict, dynamic_config_store.load(KV_SETTINGS_KEY)))
|
anonymous_user_enabled = False
|
||||||
except KvKeyNotFoundError:
|
else:
|
||||||
settings = Settings()
|
redis_client = get_redis_client(tenant_id=None)
|
||||||
dynamic_config_store.store(KV_SETTINGS_KEY, settings.model_dump())
|
value = redis_client.get(OnyxRedisLocks.ANONYMOUS_USER_ENABLED)
|
||||||
|
if value is not None:
|
||||||
|
assert isinstance(value, bytes)
|
||||||
|
anonymous_user_enabled = int(value.decode("utf-8")) == 1
|
||||||
|
else:
|
||||||
|
# Default to False
|
||||||
|
anonymous_user_enabled = False
|
||||||
|
# Optionally store the default back to Redis
|
||||||
|
redis_client.set(OnyxRedisLocks.ANONYMOUS_USER_ENABLED, "0")
|
||||||
|
|
||||||
|
settings = Settings(anonymous_user_enabled=anonymous_user_enabled)
|
||||||
return settings
|
return settings
|
||||||
|
|
||||||
|
|
||||||
def store_settings(settings: Settings) -> None:
|
def store_settings(settings: Settings) -> None:
|
||||||
|
if not MULTI_TENANT and settings.anonymous_user_enabled is not None:
|
||||||
|
# Only non-multi-tenant scenario can set the anonymous user enabled flag
|
||||||
|
redis_client = get_redis_client(tenant_id=None)
|
||||||
|
redis_client.set(
|
||||||
|
OnyxRedisLocks.ANONYMOUS_USER_ENABLED,
|
||||||
|
"1" if settings.anonymous_user_enabled else "0",
|
||||||
|
)
|
||||||
|
|
||||||
get_kv_store().store(KV_SETTINGS_KEY, settings.model_dump())
|
get_kv_store().store(KV_SETTINGS_KEY, settings.model_dump())
|
||||||
|
73
backend/tests/integration/common_utils/managers/settings.py
Normal file
73
backend/tests/integration/common_utils/managers/settings.py
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
from typing import Any
|
||||||
|
from typing import Dict
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from tests.integration.common_utils.constants import API_SERVER_URL
|
||||||
|
from tests.integration.common_utils.constants import GENERAL_HEADERS
|
||||||
|
from tests.integration.common_utils.test_models import DATestSettings
|
||||||
|
from tests.integration.common_utils.test_models import DATestUser
|
||||||
|
|
||||||
|
|
||||||
|
class SettingsManager:
|
||||||
|
@staticmethod
|
||||||
|
def get_settings(
|
||||||
|
user_performing_action: DATestUser | None = None,
|
||||||
|
) -> tuple[Dict[str, Any], str]:
|
||||||
|
headers = (
|
||||||
|
user_performing_action.headers
|
||||||
|
if user_performing_action
|
||||||
|
else GENERAL_HEADERS
|
||||||
|
)
|
||||||
|
headers.pop("Content-Type", None)
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
f"{API_SERVER_URL}/api/manage/admin/settings",
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not response.ok:
|
||||||
|
return (
|
||||||
|
{},
|
||||||
|
f"Failed to get settings - {response.json().get('detail', 'Unknown error')}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.json(), ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_settings(
|
||||||
|
settings: DATestSettings,
|
||||||
|
user_performing_action: DATestUser | None = None,
|
||||||
|
) -> tuple[Dict[str, Any], str]:
|
||||||
|
headers = (
|
||||||
|
user_performing_action.headers
|
||||||
|
if user_performing_action
|
||||||
|
else GENERAL_HEADERS
|
||||||
|
)
|
||||||
|
headers.pop("Content-Type", None)
|
||||||
|
|
||||||
|
payload = settings.model_dump()
|
||||||
|
response = requests.patch(
|
||||||
|
f"{API_SERVER_URL}/api/manage/admin/settings",
|
||||||
|
json=payload,
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not response.ok:
|
||||||
|
return (
|
||||||
|
{},
|
||||||
|
f"Failed to update settings - {response.json().get('detail', 'Unknown error')}",
|
||||||
|
)
|
||||||
|
|
||||||
|
return response.json(), ""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_setting(
|
||||||
|
key: str,
|
||||||
|
user_performing_action: DATestUser | None = None,
|
||||||
|
) -> Optional[Any]:
|
||||||
|
settings, error = SettingsManager.get_settings(user_performing_action)
|
||||||
|
if error:
|
||||||
|
return None
|
||||||
|
return settings.get(key)
|
@ -1,3 +1,4 @@
|
|||||||
|
from enum import Enum
|
||||||
from typing import Any
|
from typing import Any
|
||||||
from uuid import UUID
|
from uuid import UUID
|
||||||
|
|
||||||
@ -150,3 +151,18 @@ class StreamedResponse(BaseModel):
|
|||||||
relevance_summaries: list[dict[str, Any]] | None = None
|
relevance_summaries: list[dict[str, Any]] | None = None
|
||||||
tool_result: Any | None = None
|
tool_result: Any | None = None
|
||||||
user: str | None = None
|
user: str | None = None
|
||||||
|
|
||||||
|
|
||||||
|
class DATestGatingType(str, Enum):
|
||||||
|
FULL = "full"
|
||||||
|
PARTIAL = "partial"
|
||||||
|
NONE = "none"
|
||||||
|
|
||||||
|
|
||||||
|
class DATestSettings(BaseModel):
|
||||||
|
"""General settings"""
|
||||||
|
|
||||||
|
maximum_chat_retention_days: int | None = None
|
||||||
|
gpu_enabled: bool | None = None
|
||||||
|
product_gating: DATestGatingType = DATestGatingType.NONE
|
||||||
|
anonymous_user_enabled: bool | None = None
|
||||||
|
@ -0,0 +1,14 @@
|
|||||||
|
from tests.integration.common_utils.managers.settings import SettingsManager
|
||||||
|
from tests.integration.common_utils.managers.user import UserManager
|
||||||
|
from tests.integration.common_utils.test_models import DATestSettings
|
||||||
|
from tests.integration.common_utils.test_models import DATestUser
|
||||||
|
|
||||||
|
|
||||||
|
def test_limited(reset: None) -> None:
|
||||||
|
"""Verify that with a limited role key, limited endpoints are accessible and
|
||||||
|
others are not."""
|
||||||
|
|
||||||
|
# Creating an admin user (first user created is automatically an admin)
|
||||||
|
admin_user: DATestUser = UserManager.create(name="admin_user")
|
||||||
|
SettingsManager.update_settings(DATestSettings(anonymous_user_enabled=True))
|
||||||
|
print(admin_user.headers)
|
@ -10,6 +10,7 @@ import { DefaultDropdown, Option } from "@/components/Dropdown";
|
|||||||
import React, { useContext, useState, useEffect } from "react";
|
import React, { useContext, useState, useEffect } from "react";
|
||||||
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
||||||
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
|
import { usePaidEnterpriseFeaturesEnabled } from "@/components/settings/usePaidEnterpriseFeaturesEnabled";
|
||||||
|
import { Modal } from "@/components/Modal";
|
||||||
|
|
||||||
export function Checkbox({
|
export function Checkbox({
|
||||||
label,
|
label,
|
||||||
@ -102,6 +103,7 @@ function IntegerInput({
|
|||||||
|
|
||||||
export function SettingsForm() {
|
export function SettingsForm() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const [showConfirmModal, setShowConfirmModal] = useState(false);
|
||||||
const [settings, setSettings] = useState<Settings | null>(null);
|
const [settings, setSettings] = useState<Settings | null>(null);
|
||||||
const [chatRetention, setChatRetention] = useState("");
|
const [chatRetention, setChatRetention] = useState("");
|
||||||
const { popup, setPopup } = usePopup();
|
const { popup, setPopup } = usePopup();
|
||||||
@ -171,11 +173,22 @@ export function SettingsForm() {
|
|||||||
fieldName: keyof Settings,
|
fieldName: keyof Settings,
|
||||||
checked: boolean
|
checked: boolean
|
||||||
) {
|
) {
|
||||||
const updates: { fieldName: keyof Settings; newValue: any }[] = [
|
if (fieldName === "anonymous_user_enabled" && checked) {
|
||||||
{ fieldName, newValue: checked },
|
setShowConfirmModal(true);
|
||||||
];
|
} else {
|
||||||
|
const updates: { fieldName: keyof Settings; newValue: any }[] = [
|
||||||
|
{ fieldName, newValue: checked },
|
||||||
|
];
|
||||||
|
updateSettingField(updates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleConfirmAnonymousUsers() {
|
||||||
|
const updates: { fieldName: keyof Settings; newValue: any }[] = [
|
||||||
|
{ fieldName: "anonymous_user_enabled", newValue: true },
|
||||||
|
];
|
||||||
updateSettingField(updates);
|
updateSettingField(updates);
|
||||||
|
setShowConfirmModal(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSetChatRetention() {
|
function handleSetChatRetention() {
|
||||||
@ -205,10 +218,41 @@ export function SettingsForm() {
|
|||||||
handleToggleSettingsField("auto_scroll", e.target.checked)
|
handleToggleSettingsField("auto_scroll", e.target.checked)
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
<Checkbox
|
||||||
|
label="Anonymous Users"
|
||||||
|
sublabel="If set, users will not be required to sign in to use Danswer."
|
||||||
|
checked={settings.anonymous_user_enabled}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleToggleSettingsField("anonymous_user_enabled", e.target.checked)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
{showConfirmModal && (
|
||||||
|
<Modal
|
||||||
|
width="max-w-3xl w-full"
|
||||||
|
onOutsideClick={() => setShowConfirmModal(false)}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-4">
|
||||||
|
<h2 className="text-xl font-bold">Enable Anonymous Users</h2>
|
||||||
|
<p>
|
||||||
|
Are you sure you want to enable anonymous users? This will allow
|
||||||
|
anyone to use Danswer without signing in.
|
||||||
|
</p>
|
||||||
|
<div className="flex justify-end gap-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => setShowConfirmModal(false)}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
<Button onClick={handleConfirmAnonymousUsers}>Confirm</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
|
||||||
{isEnterpriseEnabled && (
|
{isEnterpriseEnabled && (
|
||||||
<>
|
<>
|
||||||
<Title className="mb-4">Chat Settings</Title>
|
<Title className="mt-8 mb-4">Chat Settings</Title>
|
||||||
<IntegerInput
|
<IntegerInput
|
||||||
label="Chat Retention"
|
label="Chat Retention"
|
||||||
sublabel="Enter the maximum number of days you would like Onyx to retain chat messages. Leaving this field empty will cause Onyx to never delete chat messages."
|
sublabel="Enter the maximum number of days you would like Onyx to retain chat messages. Leaving this field empty will cause Onyx to never delete chat messages."
|
||||||
|
@ -5,6 +5,7 @@ export enum GatingType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Settings {
|
export interface Settings {
|
||||||
|
anonymous_user_enabled: boolean;
|
||||||
maximum_chat_retention_days: number | null;
|
maximum_chat_retention_days: number | null;
|
||||||
notifications: Notification[];
|
notifications: Notification[];
|
||||||
needs_reindexing: boolean;
|
needs_reindexing: boolean;
|
||||||
|
@ -26,72 +26,68 @@ const ForgotPasswordPage: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<AuthFlowContainer>
|
<AuthFlowContainer>
|
||||||
<div className="flex flex-col w-full justify-center">
|
<div className="flex flex-col w-full justify-center">
|
||||||
<CardSection className="mt-4 w-full">
|
<div className="flex">
|
||||||
{" "}
|
<Title className="mb-2 mx-auto font-bold">Forgot Password</Title>
|
||||||
<div className="flex">
|
</div>
|
||||||
<Title className="mb-2 mx-auto font-bold">Forgot Password</Title>
|
{isWorking && <Spinner />}
|
||||||
</div>
|
{popup}
|
||||||
{isWorking && <Spinner />}
|
<Formik
|
||||||
{popup}
|
initialValues={{
|
||||||
<Formik
|
email: "",
|
||||||
initialValues={{
|
}}
|
||||||
email: "",
|
validationSchema={Yup.object().shape({
|
||||||
}}
|
email: Yup.string().email().required(),
|
||||||
validationSchema={Yup.object().shape({
|
})}
|
||||||
email: Yup.string().email().required(),
|
onSubmit={async (values) => {
|
||||||
})}
|
setIsWorking(true);
|
||||||
onSubmit={async (values) => {
|
try {
|
||||||
setIsWorking(true);
|
await forgotPassword(values.email);
|
||||||
try {
|
setPopup({
|
||||||
await forgotPassword(values.email);
|
type: "success",
|
||||||
setPopup({
|
message: "Password reset email sent. Please check your inbox.",
|
||||||
type: "success",
|
});
|
||||||
message:
|
} catch (error) {
|
||||||
"Password reset email sent. Please check your inbox.",
|
const errorMessage =
|
||||||
});
|
error instanceof Error
|
||||||
} catch (error) {
|
? error.message
|
||||||
const errorMessage =
|
: "An error occurred. Please try again.";
|
||||||
error instanceof Error
|
setPopup({
|
||||||
? error.message
|
type: "error",
|
||||||
: "An error occurred. Please try again.";
|
message: errorMessage,
|
||||||
setPopup({
|
});
|
||||||
type: "error",
|
} finally {
|
||||||
message: errorMessage,
|
setIsWorking(false);
|
||||||
});
|
}
|
||||||
} finally {
|
}}
|
||||||
setIsWorking(false);
|
>
|
||||||
}
|
{({ isSubmitting }) => (
|
||||||
}}
|
<Form className="w-full flex flex-col items-stretch mt-2">
|
||||||
>
|
<TextFormField
|
||||||
{({ isSubmitting }) => (
|
name="email"
|
||||||
<Form className="w-full flex flex-col items-stretch mt-2">
|
label="Email"
|
||||||
<TextFormField
|
type="email"
|
||||||
name="email"
|
placeholder="email@yourcompany.com"
|
||||||
label="Email"
|
/>
|
||||||
type="email"
|
|
||||||
placeholder="email@yourcompany.com"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="mx-auto w-full"
|
className="mx-auto w-full"
|
||||||
>
|
>
|
||||||
Reset Password
|
Reset Password
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Text className="mt-4 mx-auto">
|
<Text className="mt-4 mx-auto">
|
||||||
<Link href="/auth/login" className="text-link font-medium">
|
<Link href="/auth/login" className="text-link font-medium">
|
||||||
Back to Login
|
Back to Login
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</CardSection>
|
|
||||||
</div>
|
</div>
|
||||||
</AuthFlowContainer>
|
</AuthFlowContainer>
|
||||||
);
|
);
|
||||||
|
@ -12,6 +12,7 @@ import { Spinner } from "@/components/Spinner";
|
|||||||
import { set } from "lodash";
|
import { set } from "lodash";
|
||||||
import { NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED } from "@/lib/constants";
|
import { NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED } from "@/lib/constants";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useUser } from "@/components/user/UserProvider";
|
||||||
|
|
||||||
export function EmailPasswordForm({
|
export function EmailPasswordForm({
|
||||||
isSignup = false,
|
isSignup = false,
|
||||||
@ -24,6 +25,7 @@ export function EmailPasswordForm({
|
|||||||
referralSource?: string;
|
referralSource?: string;
|
||||||
nextUrl?: string | null;
|
nextUrl?: string | null;
|
||||||
}) {
|
}) {
|
||||||
|
const { user } = useUser();
|
||||||
const { popup, setPopup } = usePopup();
|
const { popup, setPopup } = usePopup();
|
||||||
const [isWorking, setIsWorking] = useState(false);
|
const [isWorking, setIsWorking] = useState(false);
|
||||||
return (
|
return (
|
||||||
@ -116,24 +118,29 @@ export function EmailPasswordForm({
|
|||||||
name="password"
|
name="password"
|
||||||
label="Password"
|
label="Password"
|
||||||
type="password"
|
type="password"
|
||||||
|
includeForgotPassword={
|
||||||
|
NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && !isSignup
|
||||||
|
}
|
||||||
placeholder="**************"
|
placeholder="**************"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && !isSignup && (
|
|
||||||
<Link
|
|
||||||
href="/auth/forgot-password"
|
|
||||||
className="text-sm text-link font-medium whitespace-nowrap"
|
|
||||||
>
|
|
||||||
Forgot Password?
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="mx-auto w-full"
|
className="mx-auto !py-4 w-full"
|
||||||
>
|
>
|
||||||
{isSignup ? "Sign Up" : "Log In"}
|
{isSignup ? "Sign Up" : "Log In"}
|
||||||
</Button>
|
</Button>
|
||||||
|
{user?.is_anonymous_user && (
|
||||||
|
<Link
|
||||||
|
href="/chat"
|
||||||
|
className="text-xs text-blue-500 cursor-pointer text-center w-full text-link font-medium mx-auto"
|
||||||
|
>
|
||||||
|
<span className="hover:border-b hover:border-dotted hover:border-blue-500">
|
||||||
|
or continue as guest
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
|
@ -17,6 +17,8 @@ import { getSecondsUntilExpiration } from "@/lib/time";
|
|||||||
import AuthFlowContainer from "@/components/auth/AuthFlowContainer";
|
import AuthFlowContainer from "@/components/auth/AuthFlowContainer";
|
||||||
import CardSection from "@/components/admin/CardSection";
|
import CardSection from "@/components/admin/CardSection";
|
||||||
import { NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED } from "@/lib/constants";
|
import { NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED } from "@/lib/constants";
|
||||||
|
import { SettingsContext } from "@/components/settings/SettingsProvider";
|
||||||
|
import { useContext } from "react";
|
||||||
|
|
||||||
const Page = async (props: {
|
const Page = async (props: {
|
||||||
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
|
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
@ -43,7 +45,7 @@ const Page = async (props: {
|
|||||||
|
|
||||||
// simply take the user to the home page if Auth is disabled
|
// simply take the user to the home page if Auth is disabled
|
||||||
if (authTypeMetadata?.authType === "disabled") {
|
if (authTypeMetadata?.authType === "disabled") {
|
||||||
return redirect("/");
|
return redirect("/chat");
|
||||||
}
|
}
|
||||||
|
|
||||||
// if user is already logged in, take them to the main app page
|
// if user is already logged in, take them to the main app page
|
||||||
@ -51,12 +53,13 @@ const Page = async (props: {
|
|||||||
if (
|
if (
|
||||||
currentUser &&
|
currentUser &&
|
||||||
currentUser.is_active &&
|
currentUser.is_active &&
|
||||||
|
!currentUser.is_anonymous_user &&
|
||||||
(secondsTillExpiration === null || secondsTillExpiration > 0)
|
(secondsTillExpiration === null || secondsTillExpiration > 0)
|
||||||
) {
|
) {
|
||||||
if (authTypeMetadata?.requiresVerification && !currentUser.is_verified) {
|
if (authTypeMetadata?.requiresVerification && !currentUser.is_verified) {
|
||||||
return redirect("/auth/waiting-on-verification");
|
return redirect("/auth/waiting-on-verification");
|
||||||
}
|
}
|
||||||
return redirect("/");
|
return redirect("/chat");
|
||||||
}
|
}
|
||||||
|
|
||||||
// get where to send the user to authenticate
|
// get where to send the user to authenticate
|
||||||
@ -74,67 +77,36 @@ const Page = async (props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthFlowContainer>
|
<div className="flex flex-col ">
|
||||||
<div className="absolute top-10x w-full">
|
<AuthFlowContainer authState="login">
|
||||||
<HealthCheckBanner />
|
<div className="absolute top-10x w-full">
|
||||||
</div>
|
<HealthCheckBanner />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col w-full justify-center">
|
<div className="flex flex-col w-full justify-center">
|
||||||
{authUrl && authTypeMetadata && (
|
{authUrl && authTypeMetadata && (
|
||||||
<>
|
<>
|
||||||
<h2 className="text-center text-xl text-strong font-bold">
|
<h2 className="text-center text-xl text-strong font-bold">
|
||||||
<LoginText />
|
|
||||||
</h2>
|
|
||||||
|
|
||||||
<SignInButton
|
|
||||||
authorizeUrl={authUrl}
|
|
||||||
authType={authTypeMetadata?.authType}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{authTypeMetadata?.authType === "cloud" && (
|
|
||||||
<div className="mt-4 w-full justify-center">
|
|
||||||
<div className="flex items-center w-full my-4">
|
|
||||||
<div className="flex-grow border-t border-gray-300"></div>
|
|
||||||
<span className="px-4 text-gray-500">or</span>
|
|
||||||
<div className="flex-grow border-t border-gray-300"></div>
|
|
||||||
</div>
|
|
||||||
<EmailPasswordForm shouldVerify={true} nextUrl={nextUrl} />
|
|
||||||
|
|
||||||
<div className="flex mt-4 justify-between">
|
|
||||||
<Link
|
|
||||||
href={`/auth/signup${
|
|
||||||
searchParams?.next ? `?next=${searchParams.next}` : ""
|
|
||||||
}`}
|
|
||||||
className="text-link font-medium"
|
|
||||||
>
|
|
||||||
Create an account
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
{NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && (
|
|
||||||
<Link
|
|
||||||
href="/auth/forgot-password"
|
|
||||||
className="text-link font-medium"
|
|
||||||
>
|
|
||||||
Reset Password
|
|
||||||
</Link>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{authTypeMetadata?.authType === "basic" && (
|
|
||||||
<CardSection className="mt-4 w-96">
|
|
||||||
<div className="flex">
|
|
||||||
<Title className="mb-2 mx-auto font-bold">
|
|
||||||
<LoginText />
|
<LoginText />
|
||||||
</Title>
|
</h2>
|
||||||
</div>
|
|
||||||
<EmailPasswordForm nextUrl={nextUrl} />
|
<SignInButton
|
||||||
<div className="flex flex-col gap-y-2 items-center">
|
authorizeUrl={authUrl}
|
||||||
<Text className="mt-4 ">
|
authType={authTypeMetadata?.authType}
|
||||||
Don't have an account?{" "}
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{authTypeMetadata?.authType === "cloud" && (
|
||||||
|
<div className="mt-4 w-full justify-center">
|
||||||
|
<div className="flex items-center w-full my-4">
|
||||||
|
<div className="flex-grow border-t border-gray-300"></div>
|
||||||
|
<span className="px-4 text-gray-500">or</span>
|
||||||
|
<div className="flex-grow border-t border-gray-300"></div>
|
||||||
|
</div>
|
||||||
|
<EmailPasswordForm shouldVerify={true} nextUrl={nextUrl} />
|
||||||
|
|
||||||
|
<div className="flex mt-4 justify-between">
|
||||||
<Link
|
<Link
|
||||||
href={`/auth/signup${
|
href={`/auth/signup${
|
||||||
searchParams?.next ? `?next=${searchParams.next}` : ""
|
searchParams?.next ? `?next=${searchParams.next}` : ""
|
||||||
@ -143,12 +115,33 @@ const Page = async (props: {
|
|||||||
>
|
>
|
||||||
Create an account
|
Create an account
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
|
||||||
|
{NEXT_PUBLIC_FORGOT_PASSWORD_ENABLED && (
|
||||||
|
<Link
|
||||||
|
href="/auth/forgot-password"
|
||||||
|
className="text-link font-medium"
|
||||||
|
>
|
||||||
|
Reset Password
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardSection>
|
)}
|
||||||
)}
|
|
||||||
</div>
|
{authTypeMetadata?.authType === "basic" && (
|
||||||
</AuthFlowContainer>
|
<>
|
||||||
|
<div className="flex">
|
||||||
|
<Title className="mb-2 mx-auto text-xl text-strong font-bold">
|
||||||
|
<LoginText />
|
||||||
|
</Title>
|
||||||
|
</div>
|
||||||
|
<EmailPasswordForm nextUrl={nextUrl} />
|
||||||
|
<div className="flex flex-col gap-y-2 items-center"></div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</AuthFlowContainer>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,87 +28,84 @@ const ResetPasswordPage: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<AuthFlowContainer>
|
<AuthFlowContainer>
|
||||||
<div className="flex flex-col w-full justify-center">
|
<div className="flex flex-col w-full justify-center">
|
||||||
<CardSection className="mt-4 w-full">
|
<div className="flex">
|
||||||
<div className="flex">
|
<Title className="mb-2 mx-auto font-bold">Reset Password</Title>
|
||||||
<Title className="mb-2 mx-auto font-bold">Reset Password</Title>
|
</div>
|
||||||
</div>
|
{isWorking && <Spinner />}
|
||||||
{isWorking && <Spinner />}
|
{popup}
|
||||||
{popup}
|
<Formik
|
||||||
<Formik
|
initialValues={{
|
||||||
initialValues={{
|
password: "",
|
||||||
password: "",
|
confirmPassword: "",
|
||||||
confirmPassword: "",
|
}}
|
||||||
}}
|
validationSchema={Yup.object().shape({
|
||||||
validationSchema={Yup.object().shape({
|
password: Yup.string().required("Password is required"),
|
||||||
password: Yup.string().required("Password is required"),
|
confirmPassword: Yup.string()
|
||||||
confirmPassword: Yup.string()
|
.oneOf([Yup.ref("password"), undefined], "Passwords must match")
|
||||||
.oneOf([Yup.ref("password"), undefined], "Passwords must match")
|
.required("Confirm Password is required"),
|
||||||
.required("Confirm Password is required"),
|
})}
|
||||||
})}
|
onSubmit={async (values) => {
|
||||||
onSubmit={async (values) => {
|
if (!token) {
|
||||||
if (!token) {
|
setPopup({
|
||||||
setPopup({
|
type: "error",
|
||||||
type: "error",
|
message: "Invalid or missing reset token.",
|
||||||
message: "Invalid or missing reset token.",
|
});
|
||||||
});
|
return;
|
||||||
return;
|
}
|
||||||
}
|
setIsWorking(true);
|
||||||
setIsWorking(true);
|
try {
|
||||||
try {
|
await resetPassword(token, values.password);
|
||||||
await resetPassword(token, values.password);
|
setPopup({
|
||||||
setPopup({
|
type: "success",
|
||||||
type: "success",
|
message: "Password reset successfully. Redirecting to login...",
|
||||||
message:
|
});
|
||||||
"Password reset successfully. Redirecting to login...",
|
setTimeout(() => {
|
||||||
});
|
redirect("/auth/login");
|
||||||
setTimeout(() => {
|
}, 1000);
|
||||||
redirect("/auth/login");
|
} catch (error) {
|
||||||
}, 1000);
|
setPopup({
|
||||||
} catch (error) {
|
type: "error",
|
||||||
setPopup({
|
message: "An error occurred. Please try again.",
|
||||||
type: "error",
|
});
|
||||||
message: "An error occurred. Please try again.",
|
} finally {
|
||||||
});
|
setIsWorking(false);
|
||||||
} finally {
|
}
|
||||||
setIsWorking(false);
|
}}
|
||||||
}
|
>
|
||||||
}}
|
{({ isSubmitting }) => (
|
||||||
>
|
<Form className="w-full flex flex-col items-stretch mt-2">
|
||||||
{({ isSubmitting }) => (
|
<TextFormField
|
||||||
<Form className="w-full flex flex-col items-stretch mt-2">
|
name="password"
|
||||||
<TextFormField
|
label="New Password"
|
||||||
name="password"
|
type="password"
|
||||||
label="New Password"
|
placeholder="Enter your new password"
|
||||||
type="password"
|
/>
|
||||||
placeholder="Enter your new password"
|
<TextFormField
|
||||||
/>
|
name="confirmPassword"
|
||||||
<TextFormField
|
label="Confirm New Password"
|
||||||
name="confirmPassword"
|
type="password"
|
||||||
label="Confirm New Password"
|
placeholder="Confirm your new password"
|
||||||
type="password"
|
/>
|
||||||
placeholder="Confirm your new password"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
className="mx-auto w-full"
|
className="mx-auto w-full"
|
||||||
>
|
>
|
||||||
Reset Password
|
Reset Password
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
</Formik>
|
||||||
<div className="flex">
|
<div className="flex">
|
||||||
<Text className="mt-4 mx-auto">
|
<Text className="mt-4 mx-auto">
|
||||||
<Link href="/auth/login" className="text-link font-medium">
|
<Link href="/auth/login" className="text-link font-medium">
|
||||||
Back to Login
|
Back to Login
|
||||||
</Link>
|
</Link>
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
</CardSection>
|
|
||||||
</div>
|
</div>
|
||||||
</AuthFlowContainer>
|
</AuthFlowContainer>
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,6 @@ import Link from "next/link";
|
|||||||
import { SignInButton } from "../login/SignInButton";
|
import { SignInButton } from "../login/SignInButton";
|
||||||
import AuthFlowContainer from "@/components/auth/AuthFlowContainer";
|
import AuthFlowContainer from "@/components/auth/AuthFlowContainer";
|
||||||
import ReferralSourceSelector from "./ReferralSourceSelector";
|
import ReferralSourceSelector from "./ReferralSourceSelector";
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
|
|
||||||
const Page = async (props: {
|
const Page = async (props: {
|
||||||
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
|
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
|
||||||
@ -39,13 +38,13 @@ const Page = async (props: {
|
|||||||
|
|
||||||
// simply take the user to the home page if Auth is disabled
|
// simply take the user to the home page if Auth is disabled
|
||||||
if (authTypeMetadata?.authType === "disabled") {
|
if (authTypeMetadata?.authType === "disabled") {
|
||||||
return redirect("/");
|
return redirect("/chat");
|
||||||
}
|
}
|
||||||
|
|
||||||
// if user is already logged in, take them to the main app page
|
// if user is already logged in, take them to the main app page
|
||||||
if (currentUser && currentUser.is_active) {
|
if (currentUser && currentUser.is_active && !currentUser.is_anonymous_user) {
|
||||||
if (!authTypeMetadata?.requiresVerification || currentUser.is_verified) {
|
if (!authTypeMetadata?.requiresVerification || currentUser.is_verified) {
|
||||||
return redirect("/");
|
return redirect("/chat");
|
||||||
}
|
}
|
||||||
return redirect("/auth/waiting-on-verification");
|
return redirect("/auth/waiting-on-verification");
|
||||||
}
|
}
|
||||||
@ -53,7 +52,7 @@ const Page = async (props: {
|
|||||||
|
|
||||||
// only enable this page if basic login is enabled
|
// only enable this page if basic login is enabled
|
||||||
if (authTypeMetadata?.authType !== "basic" && !cloud) {
|
if (authTypeMetadata?.authType !== "basic" && !cloud) {
|
||||||
return redirect("/");
|
return redirect("/chat");
|
||||||
}
|
}
|
||||||
|
|
||||||
let authUrl: string | null = null;
|
let authUrl: string | null = null;
|
||||||
@ -62,7 +61,7 @@ const Page = async (props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AuthFlowContainer>
|
<AuthFlowContainer authState="signup">
|
||||||
<HealthCheckBanner />
|
<HealthCheckBanner />
|
||||||
|
|
||||||
<>
|
<>
|
||||||
@ -95,21 +94,6 @@ const Page = async (props: {
|
|||||||
shouldVerify={authTypeMetadata?.requiresVerification}
|
shouldVerify={authTypeMetadata?.requiresVerification}
|
||||||
nextUrl={nextUrl}
|
nextUrl={nextUrl}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="flex">
|
|
||||||
<Text className="mt-4 mx-auto">
|
|
||||||
Already have an account?{" "}
|
|
||||||
<Link
|
|
||||||
href={{
|
|
||||||
pathname: "/auth/login",
|
|
||||||
query: { ...searchParams },
|
|
||||||
}}
|
|
||||||
className="text-link font-medium"
|
|
||||||
>
|
|
||||||
Log In
|
|
||||||
</Link>
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
</AuthFlowContainer>
|
</AuthFlowContainer>
|
||||||
|
@ -23,7 +23,7 @@ export default async function Page() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!authTypeMetadata?.requiresVerification || currentUser?.is_verified) {
|
if (!authTypeMetadata?.requiresVerification || currentUser?.is_verified) {
|
||||||
return redirect("/");
|
return redirect("/chat");
|
||||||
}
|
}
|
||||||
|
|
||||||
return <Verify user={currentUser} />;
|
return <Verify user={currentUser} />;
|
||||||
|
@ -27,13 +27,13 @@ export default async function Page() {
|
|||||||
|
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
if (authTypeMetadata?.authType === "disabled") {
|
if (authTypeMetadata?.authType === "disabled") {
|
||||||
return redirect("/");
|
return redirect("/chat");
|
||||||
}
|
}
|
||||||
return redirect("/auth/login");
|
return redirect("/auth/login");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!authTypeMetadata?.requiresVerification || currentUser.is_verified) {
|
if (!authTypeMetadata?.requiresVerification || currentUser.is_verified) {
|
||||||
return redirect("/");
|
return redirect("/chat");
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -1661,6 +1661,7 @@ export function ChatPage({
|
|||||||
setShowDocSidebar: setShowHistorySidebar,
|
setShowDocSidebar: setShowHistorySidebar,
|
||||||
setToggled: removeToggle,
|
setToggled: removeToggle,
|
||||||
mobile: settings?.isMobile,
|
mobile: settings?.isMobile,
|
||||||
|
isAnonymousUser: user?.is_anonymous_user,
|
||||||
});
|
});
|
||||||
|
|
||||||
const autoScrollEnabled =
|
const autoScrollEnabled =
|
||||||
@ -2229,6 +2230,7 @@ export function ChatPage({
|
|||||||
toggleSidebar={toggleSidebar}
|
toggleSidebar={toggleSidebar}
|
||||||
currentChatSession={selectedChatSession}
|
currentChatSession={selectedChatSession}
|
||||||
documentSidebarToggled={documentSidebarToggled}
|
documentSidebarToggled={documentSidebarToggled}
|
||||||
|
hideUserDropdown={user?.is_anonymous_user}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@ -2767,12 +2769,6 @@ export function ChatPage({
|
|||||||
setFiltersToggled(false);
|
setFiltersToggled(false);
|
||||||
setDocumentSidebarToggled(true);
|
setDocumentSidebarToggled(true);
|
||||||
}}
|
}}
|
||||||
removeFilters={() => {
|
|
||||||
filterManager.setSelectedSources([]);
|
|
||||||
filterManager.setSelectedTags([]);
|
|
||||||
filterManager.setSelectedDocumentSets([]);
|
|
||||||
setDocumentSidebarToggled(false);
|
|
||||||
}}
|
|
||||||
showConfigureAPIKey={() =>
|
showConfigureAPIKey={() =>
|
||||||
setShowApiKeyModal(true)
|
setShowApiKeyModal(true)
|
||||||
}
|
}
|
||||||
@ -2782,7 +2778,6 @@ export function ChatPage({
|
|||||||
selectedDocuments={selectedDocuments}
|
selectedDocuments={selectedDocuments}
|
||||||
// assistant stuff
|
// assistant stuff
|
||||||
selectedAssistant={liveAssistant}
|
selectedAssistant={liveAssistant}
|
||||||
setSelectedAssistant={onAssistantChange}
|
|
||||||
setAlternativeAssistant={setAlternativeAssistant}
|
setAlternativeAssistant={setAlternativeAssistant}
|
||||||
alternativeAssistant={alternativeAssistant}
|
alternativeAssistant={alternativeAssistant}
|
||||||
// end assistant stuff
|
// end assistant stuff
|
||||||
@ -2790,7 +2785,6 @@ export function ChatPage({
|
|||||||
setMessage={setMessage}
|
setMessage={setMessage}
|
||||||
onSubmit={onSubmit}
|
onSubmit={onSubmit}
|
||||||
filterManager={filterManager}
|
filterManager={filterManager}
|
||||||
llmOverrideManager={llmOverrideManager}
|
|
||||||
files={currentMessageFiles}
|
files={currentMessageFiles}
|
||||||
setFiles={setCurrentMessageFiles}
|
setFiles={setCurrentMessageFiles}
|
||||||
toggleFilters={
|
toggleFilters={
|
||||||
@ -2798,7 +2792,6 @@ export function ChatPage({
|
|||||||
}
|
}
|
||||||
handleFileUpload={handleImageUpload}
|
handleFileUpload={handleImageUpload}
|
||||||
textAreaRef={textAreaRef}
|
textAreaRef={textAreaRef}
|
||||||
chatSessionId={chatSessionIdRef.current!}
|
|
||||||
/>
|
/>
|
||||||
{enterpriseSettings &&
|
{enterpriseSettings &&
|
||||||
enterpriseSettings.custom_lower_disclaimer_content && (
|
enterpriseSettings.custom_lower_disclaimer_content && (
|
||||||
|
@ -3,8 +3,7 @@ import { FiPlusCircle, FiPlus, FiInfo, FiX, FiSearch } from "react-icons/fi";
|
|||||||
import { ChatInputOption } from "./ChatInputOption";
|
import { ChatInputOption } from "./ChatInputOption";
|
||||||
import { Persona } from "@/app/admin/assistants/interfaces";
|
import { Persona } from "@/app/admin/assistants/interfaces";
|
||||||
|
|
||||||
import { FilterManager, LlmOverrideManager } from "@/lib/hooks";
|
import { FilterManager } from "@/lib/hooks";
|
||||||
import { SelectedFilterDisplay } from "./SelectedFilterDisplay";
|
|
||||||
import { useChatContext } from "@/components/context/ChatContext";
|
import { useChatContext } from "@/components/context/ChatContext";
|
||||||
import { getFinalLLM } from "@/lib/llm/utils";
|
import { getFinalLLM } from "@/lib/llm/utils";
|
||||||
import { ChatFileType, FileDescriptor } from "../interfaces";
|
import { ChatFileType, FileDescriptor } from "../interfaces";
|
||||||
@ -37,9 +36,9 @@ import FiltersDisplay from "./FilterDisplay";
|
|||||||
const MAX_INPUT_HEIGHT = 200;
|
const MAX_INPUT_HEIGHT = 200;
|
||||||
|
|
||||||
interface ChatInputBarProps {
|
interface ChatInputBarProps {
|
||||||
removeFilters: () => void;
|
|
||||||
removeDocs: () => void;
|
removeDocs: () => void;
|
||||||
openModelSettings: () => void;
|
openModelSettings: () => void;
|
||||||
|
showDocs: () => void;
|
||||||
showConfigureAPIKey: () => void;
|
showConfigureAPIKey: () => void;
|
||||||
selectedDocuments: OnyxDocument[];
|
selectedDocuments: OnyxDocument[];
|
||||||
message: string;
|
message: string;
|
||||||
@ -47,41 +46,34 @@ interface ChatInputBarProps {
|
|||||||
stopGenerating: () => void;
|
stopGenerating: () => void;
|
||||||
onSubmit: () => void;
|
onSubmit: () => void;
|
||||||
filterManager: FilterManager;
|
filterManager: FilterManager;
|
||||||
llmOverrideManager: LlmOverrideManager;
|
|
||||||
chatState: ChatState;
|
chatState: ChatState;
|
||||||
showDocs: () => void;
|
|
||||||
alternativeAssistant: Persona | null;
|
alternativeAssistant: Persona | null;
|
||||||
// assistants
|
// assistants
|
||||||
selectedAssistant: Persona;
|
selectedAssistant: Persona;
|
||||||
setSelectedAssistant: (assistant: Persona) => void;
|
|
||||||
setAlternativeAssistant: (alternativeAssistant: Persona | null) => void;
|
setAlternativeAssistant: (alternativeAssistant: Persona | null) => void;
|
||||||
|
|
||||||
files: FileDescriptor[];
|
files: FileDescriptor[];
|
||||||
setFiles: (files: FileDescriptor[]) => void;
|
setFiles: (files: FileDescriptor[]) => void;
|
||||||
handleFileUpload: (files: File[]) => void;
|
handleFileUpload: (files: File[]) => void;
|
||||||
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
textAreaRef: React.RefObject<HTMLTextAreaElement>;
|
||||||
chatSessionId?: string;
|
|
||||||
toggleFilters?: () => void;
|
toggleFilters?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatInputBar({
|
export function ChatInputBar({
|
||||||
removeFilters,
|
|
||||||
removeDocs,
|
removeDocs,
|
||||||
openModelSettings,
|
openModelSettings,
|
||||||
showConfigureAPIKey,
|
|
||||||
showDocs,
|
showDocs,
|
||||||
|
showConfigureAPIKey,
|
||||||
selectedDocuments,
|
selectedDocuments,
|
||||||
message,
|
message,
|
||||||
setMessage,
|
setMessage,
|
||||||
stopGenerating,
|
stopGenerating,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
filterManager,
|
filterManager,
|
||||||
llmOverrideManager,
|
|
||||||
chatState,
|
chatState,
|
||||||
|
|
||||||
// assistants
|
// assistants
|
||||||
selectedAssistant,
|
selectedAssistant,
|
||||||
setSelectedAssistant,
|
|
||||||
setAlternativeAssistant,
|
setAlternativeAssistant,
|
||||||
|
|
||||||
files,
|
files,
|
||||||
@ -89,7 +81,6 @@ export function ChatInputBar({
|
|||||||
handleFileUpload,
|
handleFileUpload,
|
||||||
textAreaRef,
|
textAreaRef,
|
||||||
alternativeAssistant,
|
alternativeAssistant,
|
||||||
chatSessionId,
|
|
||||||
toggleFilters,
|
toggleFilters,
|
||||||
}: ChatInputBarProps) {
|
}: ChatInputBarProps) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -156,6 +156,7 @@ export default async function RootLayout({
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (productGating === GatingType.FULL) {
|
if (productGating === GatingType.FULL) {
|
||||||
return getPageContent(
|
return getPageContent(
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen">
|
<div className="flex flex-col items-center justify-center min-h-screen">
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default function NotFound() {
|
export default function NotFound() {
|
||||||
redirect("/chat");
|
redirect("/auth/login");
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default async function Page() {
|
export default async function Page() {
|
||||||
redirect("/chat");
|
redirect("/auth/login");
|
||||||
}
|
}
|
||||||
|
@ -59,9 +59,11 @@ const DropdownOption: React.FC<DropdownOptionProps> = ({
|
|||||||
export function UserDropdown({
|
export function UserDropdown({
|
||||||
page,
|
page,
|
||||||
toggleUserSettings,
|
toggleUserSettings,
|
||||||
|
hideUserDropdown,
|
||||||
}: {
|
}: {
|
||||||
page?: pageType;
|
page?: pageType;
|
||||||
toggleUserSettings?: () => void;
|
toggleUserSettings?: () => void;
|
||||||
|
hideUserDropdown?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { user, isCurator } = useUser();
|
const { user, isCurator } = useUser();
|
||||||
const [userInfoVisible, setUserInfoVisible] = useState(false);
|
const [userInfoVisible, setUserInfoVisible] = useState(false);
|
||||||
@ -114,6 +116,7 @@ export function UserDropdown({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const showAdminPanel = !user || user.role === UserRole.ADMIN;
|
const showAdminPanel = !user || user.role === UserRole.ADMIN;
|
||||||
|
|
||||||
const showCuratorPanel = user && isCurator;
|
const showCuratorPanel = user && isCurator;
|
||||||
const showLogout =
|
const showLogout =
|
||||||
user && !checkUserIsNoAuthUser(user.id) && !LOGOUT_DISABLED;
|
user && !checkUserIsNoAuthUser(user.id) && !LOGOUT_DISABLED;
|
||||||
@ -183,6 +186,12 @@ export function UserDropdown({
|
|||||||
notifications={notifications || []}
|
notifications={notifications || []}
|
||||||
refreshNotifications={refreshNotifications}
|
refreshNotifications={refreshNotifications}
|
||||||
/>
|
/>
|
||||||
|
) : hideUserDropdown ? (
|
||||||
|
<DropdownOption
|
||||||
|
onClick={() => router.push("/auth/login")}
|
||||||
|
icon={<UserIcon className="h-5 w-5 my-auto mr-2" />}
|
||||||
|
label="Log In"
|
||||||
|
/>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{customNavItems.map((item, i) => (
|
{customNavItems.map((item, i) => (
|
||||||
@ -251,6 +260,7 @@ export function UserDropdown({
|
|||||||
label="User Settings"
|
label="User Settings"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<DropdownOption
|
<DropdownOption
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setUserInfoVisible(true);
|
setUserInfoVisible(true);
|
||||||
|
@ -35,7 +35,7 @@ export async function Layout({ children }: { children: React.ReactNode }) {
|
|||||||
return redirect("/auth/login");
|
return redirect("/auth/login");
|
||||||
}
|
}
|
||||||
if (user.role === UserRole.BASIC) {
|
if (user.role === UserRole.BASIC) {
|
||||||
return redirect("/");
|
return redirect("/chat");
|
||||||
}
|
}
|
||||||
if (!user.is_verified && requiresVerification) {
|
if (!user.is_verified && requiresVerification) {
|
||||||
return redirect("/auth/waiting-on-verification");
|
return redirect("/auth/waiting-on-verification");
|
||||||
|
@ -29,6 +29,7 @@ import { useRef, useState } from "react";
|
|||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import { EditIcon } from "@/components/icons/icons";
|
import { EditIcon } from "@/components/icons/icons";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export function SectionHeader({
|
export function SectionHeader({
|
||||||
children,
|
children,
|
||||||
@ -143,6 +144,7 @@ export function TextFormField({
|
|||||||
small,
|
small,
|
||||||
removeLabel,
|
removeLabel,
|
||||||
min,
|
min,
|
||||||
|
includeForgotPassword,
|
||||||
onChange,
|
onChange,
|
||||||
width,
|
width,
|
||||||
vertical,
|
vertical,
|
||||||
@ -169,6 +171,7 @@ export function TextFormField({
|
|||||||
explanationLink?: string;
|
explanationLink?: string;
|
||||||
small?: boolean;
|
small?: boolean;
|
||||||
min?: number;
|
min?: number;
|
||||||
|
includeForgotPassword?: boolean;
|
||||||
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
|
||||||
width?: string;
|
width?: string;
|
||||||
vertical?: boolean;
|
vertical?: boolean;
|
||||||
@ -238,7 +241,7 @@ export function TextFormField({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{subtext && <SubLabel>{subtext}</SubLabel>}
|
{subtext && <SubLabel>{subtext}</SubLabel>}
|
||||||
<div className={`w-full flex ${includeRevert && "gap-x-2"}`}>
|
<div className={`w-full flex ${includeRevert && "gap-x-2"} relative`}>
|
||||||
<Field
|
<Field
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
min={min}
|
min={min}
|
||||||
@ -269,6 +272,14 @@ export function TextFormField({
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
autoComplete={autoCompleteDisabled ? "off" : undefined}
|
autoComplete={autoCompleteDisabled ? "off" : undefined}
|
||||||
/>
|
/>
|
||||||
|
{includeForgotPassword && (
|
||||||
|
<Link
|
||||||
|
href="/auth/forgot-password"
|
||||||
|
className="absolute right-3 top-1/2 mt-[3px] transform -translate-y-1/2 text-xs text-blue-500 cursor-pointer"
|
||||||
|
>
|
||||||
|
Forgot password?
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{explanationText && (
|
{explanationText && (
|
||||||
|
@ -1,16 +1,41 @@
|
|||||||
|
import Link from "next/link";
|
||||||
import { Logo } from "../logo/Logo";
|
import { Logo } from "../logo/Logo";
|
||||||
|
|
||||||
export default function AuthFlowContainer({
|
export default function AuthFlowContainer({
|
||||||
children,
|
children,
|
||||||
|
authState,
|
||||||
}: {
|
}: {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
|
authState?: "signup" | "login";
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center min-h-screen bg-background">
|
<div className="p-4 flex flex-col items-center justify-center min-h-screen bg-background">
|
||||||
<div className="w-full max-w-md bg-black pt-8 pb-4 px-8 mx-4 gap-y-4 bg-white flex items-center flex-col rounded-xl shadow-lg border border-bacgkround-100">
|
<div className="w-full max-w-md bg-black pt-8 pb-6 px-8 mx-4 gap-y-4 bg-white flex items-center flex-col rounded-xl shadow-lg border border-bacgkround-100">
|
||||||
<Logo width={70} height={70} />
|
<Logo width={70} height={70} />
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
{authState === "login" && (
|
||||||
|
<div className="text-sm mt-4 text-center w-full text-neutral-900 font-medium mx-auto">
|
||||||
|
Don't have an account?{" "}
|
||||||
|
<Link
|
||||||
|
href="/auth/signup"
|
||||||
|
className=" underline transition-colors duration-200"
|
||||||
|
>
|
||||||
|
Create one
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{authState === "signup" && (
|
||||||
|
<div className="text-sm mt-4 text-center w-full text-neutral-900 font-medium mx-auto">
|
||||||
|
Already have an account?{" "}
|
||||||
|
<Link
|
||||||
|
href="/auth/login"
|
||||||
|
className=" underline transition-colors duration-200"
|
||||||
|
>
|
||||||
|
Log In
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ export default function FunctionalHeader({
|
|||||||
sidebarToggled,
|
sidebarToggled,
|
||||||
documentSidebarToggled,
|
documentSidebarToggled,
|
||||||
toggleUserSettings,
|
toggleUserSettings,
|
||||||
|
hideUserDropdown,
|
||||||
}: {
|
}: {
|
||||||
reset?: () => void;
|
reset?: () => void;
|
||||||
page: pageType;
|
page: pageType;
|
||||||
@ -30,6 +31,7 @@ export default function FunctionalHeader({
|
|||||||
setSharingModalVisible?: (value: SetStateAction<boolean>) => void;
|
setSharingModalVisible?: (value: SetStateAction<boolean>) => void;
|
||||||
toggleSidebar?: () => void;
|
toggleSidebar?: () => void;
|
||||||
toggleUserSettings?: () => void;
|
toggleUserSettings?: () => void;
|
||||||
|
hideUserDropdown?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const settings = useContext(SettingsContext);
|
const settings = useContext(SettingsContext);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -106,7 +108,7 @@ export default function FunctionalHeader({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="absolute right-0 mobile:top-2 desktop:top-0 flex">
|
<div className="absolute right-0 mobile:top-2 desktop:top-0 flex">
|
||||||
{setSharingModalVisible && (
|
{setSharingModalVisible && !hideUserDropdown && (
|
||||||
<div
|
<div
|
||||||
onClick={() => setSharingModalVisible(true)}
|
onClick={() => setSharingModalVisible(true)}
|
||||||
className="mobile:hidden mr-2 my-auto rounded cursor-pointer hover:bg-hover-light"
|
className="mobile:hidden mr-2 my-auto rounded cursor-pointer hover:bg-hover-light"
|
||||||
@ -114,8 +116,10 @@ export default function FunctionalHeader({
|
|||||||
<FiShare2 size="18" />
|
<FiShare2 size="18" />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="mobile:hidden flex my-auto">
|
<div className="mobile:hidden flex my-auto">
|
||||||
<UserDropdown
|
<UserDropdown
|
||||||
|
hideUserDropdown={hideUserDropdown}
|
||||||
page={page}
|
page={page}
|
||||||
toggleUserSettings={toggleUserSettings}
|
toggleUserSettings={toggleUserSettings}
|
||||||
/>
|
/>
|
||||||
|
@ -7,6 +7,7 @@ interface UseSidebarVisibilityProps {
|
|||||||
setShowDocSidebar: Dispatch<SetStateAction<boolean>>;
|
setShowDocSidebar: Dispatch<SetStateAction<boolean>>;
|
||||||
mobile?: boolean;
|
mobile?: boolean;
|
||||||
setToggled?: () => void;
|
setToggled?: () => void;
|
||||||
|
isAnonymousUser?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useSidebarVisibility = ({
|
export const useSidebarVisibility = ({
|
||||||
@ -16,11 +17,15 @@ export const useSidebarVisibility = ({
|
|||||||
setToggled,
|
setToggled,
|
||||||
showDocSidebar,
|
showDocSidebar,
|
||||||
mobile,
|
mobile,
|
||||||
|
isAnonymousUser,
|
||||||
}: UseSidebarVisibilityProps) => {
|
}: UseSidebarVisibilityProps) => {
|
||||||
const xPosition = useRef(0);
|
const xPosition = useRef(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleEvent = (event: MouseEvent) => {
|
const handleEvent = (event: MouseEvent) => {
|
||||||
|
if (isAnonymousUser) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const currentXPosition = event.clientX;
|
const currentXPosition = event.clientX;
|
||||||
xPosition.current = currentXPosition;
|
xPosition.current = currentXPosition;
|
||||||
|
|
||||||
|
@ -2652,7 +2652,7 @@ export const OpenIcon = ({
|
|||||||
<path
|
<path
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
d="M7 13.5a9.26 9.26 0 0 0-5.61-2.95a1 1 0 0 1-.89-1V1.5A1 1 0 0 1 1.64.51A9.3 9.3 0 0 1 7 3.43zm0 0a9.26 9.26 0 0 1 5.61-2.95a1 1 0 0 0 .89-1V1.5a1 1 0 0 0-1.14-.99A9.3 9.3 0 0 0 7 3.43z"
|
d="M7 13.5a9.26 9.26 0 0 0-5.61-2.95a1 1 0 0 1-.89-1V1.5A1 1 0 0 1 1.64.51A9.3 9.3 0 0 1 7 3.43zm0 0a9.26 9.26 0 0 1 5.61-2.95a1 1 0 0 0 .89-1V1.5a1 1 0 0 0-1.14-.99A9.3 9.3 0 0 0 7 3.43z"
|
||||||
/>
|
/>
|
||||||
@ -2676,7 +2676,7 @@ export const DexpandTwoIcon = ({
|
|||||||
<path
|
<path
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
d="m.5 13.5l5-5m-4 0h4v4m8-12l-5 5m4 0h-4v-4"
|
d="m.5 13.5l5-5m-4 0h4v4m8-12l-5 5m4 0h-4v-4"
|
||||||
/>
|
/>
|
||||||
@ -2700,7 +2700,7 @@ export const ExpandTwoIcon = ({
|
|||||||
<path
|
<path
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
d="m8.5 5.5l5-5m-4 0h4v4m-8 4l-5 5m4 0h-4v-4"
|
d="m8.5 5.5l5-5m-4 0h4v4m-8 4l-5 5m4 0h-4v-4"
|
||||||
/>
|
/>
|
||||||
@ -2724,7 +2724,7 @@ export const DownloadCSVIcon = ({
|
|||||||
<path
|
<path
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
d="M.5 10.5v1a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1M4 6l3 3.5L10 6M7 9.5v-9"
|
d="M.5 10.5v1a2 2 0 0 0 2 2h9a2 2 0 0 0 2-2v-1M4 6l3 3.5L10 6M7 9.5v-9"
|
||||||
/>
|
/>
|
||||||
@ -2748,7 +2748,7 @@ export const UserIcon = ({
|
|||||||
<path
|
<path
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
stroke-linecap="round"
|
strokeLinecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
d="M19.618 21.25c0-3.602-4.016-6.53-7.618-6.53c-3.602 0-7.618 2.928-7.618 6.53M12 11.456a4.353 4.353 0 1 0 0-8.706a4.353 4.353 0 0 0 0 8.706"
|
d="M19.618 21.25c0-3.602-4.016-6.53-7.618-6.53c-3.602 0-7.618 2.928-7.618 6.53M12 11.456a4.353 4.353 0 1 0 0-8.706a4.353 4.353 0 0 0 0 8.706"
|
||||||
|
@ -49,10 +49,13 @@ export async function fetchSettingsSS(): Promise<CombinedSettings | null> {
|
|||||||
maximum_chat_retention_days: null,
|
maximum_chat_retention_days: null,
|
||||||
notifications: [],
|
notifications: [],
|
||||||
needs_reindexing: false,
|
needs_reindexing: false,
|
||||||
|
anonymous_user_enabled: false,
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`fetchStandardSettingsSS failed: status=${results[0].status} body=${await results[0].text()}`
|
`fetchStandardSettingsSS failed: status=${
|
||||||
|
results[0].status
|
||||||
|
} body=${await results[0].text()}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -64,7 +67,9 @@ export async function fetchSettingsSS(): Promise<CombinedSettings | null> {
|
|||||||
if (!results[1].ok) {
|
if (!results[1].ok) {
|
||||||
if (results[1].status !== 403 && results[1].status !== 401) {
|
if (results[1].status !== 403 && results[1].status !== 401) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`fetchEnterpriseSettingsSS failed: status=${results[1].status} body=${await results[1].text()}`
|
`fetchEnterpriseSettingsSS failed: status=${
|
||||||
|
results[1].status
|
||||||
|
} body=${await results[1].text()}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -77,7 +82,9 @@ export async function fetchSettingsSS(): Promise<CombinedSettings | null> {
|
|||||||
if (!results[2].ok) {
|
if (!results[2].ok) {
|
||||||
if (results[2].status !== 403) {
|
if (results[2].status !== 403) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`fetchCustomAnalyticsScriptSS failed: status=${results[2].status} body=${await results[2].text()}`
|
`fetchCustomAnalyticsScriptSS failed: status=${
|
||||||
|
results[2].status
|
||||||
|
} body=${await results[2].text()}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -86,7 +86,9 @@ export async function fetchChatData(searchParams: {
|
|||||||
const foldersResponse = results[7] as Response | null;
|
const foldersResponse = results[7] as Response | null;
|
||||||
|
|
||||||
const authDisabled = authTypeMetadata?.authType === "disabled";
|
const authDisabled = authTypeMetadata?.authType === "disabled";
|
||||||
if (!authDisabled && !user) {
|
|
||||||
|
// TODO Validate need
|
||||||
|
if (!authDisabled && !user && !authTypeMetadata?.anonymousUserEnabled) {
|
||||||
const headersList = await headers();
|
const headersList = await headers();
|
||||||
const fullUrl = headersList.get("x-url") || "/chat";
|
const fullUrl = headersList.get("x-url") || "/chat";
|
||||||
const searchParamsString = new URLSearchParams(
|
const searchParamsString = new URLSearchParams(
|
||||||
@ -95,6 +97,7 @@ export async function fetchChatData(searchParams: {
|
|||||||
const redirectUrl = searchParamsString
|
const redirectUrl = searchParamsString
|
||||||
? `${fullUrl}?${searchParamsString}`
|
? `${fullUrl}?${searchParamsString}`
|
||||||
: fullUrl;
|
: fullUrl;
|
||||||
|
|
||||||
return redirect(`/auth/login?next=${encodeURIComponent(redirectUrl)}`);
|
return redirect(`/auth/login?next=${encodeURIComponent(redirectUrl)}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,6 +62,7 @@ export interface User {
|
|||||||
oidc_expiry?: Date;
|
oidc_expiry?: Date;
|
||||||
is_cloud_superuser?: boolean;
|
is_cloud_superuser?: boolean;
|
||||||
organization_name: string | null;
|
organization_name: string | null;
|
||||||
|
is_anonymous_user?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MinimalUserSnapshot {
|
export interface MinimalUserSnapshot {
|
||||||
|
@ -8,6 +8,7 @@ export interface AuthTypeMetadata {
|
|||||||
authType: AuthType;
|
authType: AuthType;
|
||||||
autoRedirect: boolean;
|
autoRedirect: boolean;
|
||||||
requiresVerification: boolean;
|
requiresVerification: boolean;
|
||||||
|
anonymousUserEnabled: boolean | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getAuthTypeMetadataSS = async (): Promise<AuthTypeMetadata> => {
|
export const getAuthTypeMetadataSS = async (): Promise<AuthTypeMetadata> => {
|
||||||
@ -16,8 +17,11 @@ export const getAuthTypeMetadataSS = async (): Promise<AuthTypeMetadata> => {
|
|||||||
throw new Error("Failed to fetch data");
|
throw new Error("Failed to fetch data");
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: { auth_type: string; requires_verification: boolean } =
|
const data: {
|
||||||
await res.json();
|
auth_type: string;
|
||||||
|
requires_verification: boolean;
|
||||||
|
anonymous_user_enabled: boolean | null;
|
||||||
|
} = await res.json();
|
||||||
|
|
||||||
let authType: AuthType;
|
let authType: AuthType;
|
||||||
|
|
||||||
@ -35,12 +39,14 @@ export const getAuthTypeMetadataSS = async (): Promise<AuthTypeMetadata> => {
|
|||||||
authType,
|
authType,
|
||||||
autoRedirect: true,
|
autoRedirect: true,
|
||||||
requiresVerification: data.requires_verification,
|
requiresVerification: data.requires_verification,
|
||||||
|
anonymousUserEnabled: data.anonymous_user_enabled,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
authType,
|
authType,
|
||||||
autoRedirect: false,
|
autoRedirect: false,
|
||||||
requiresVerification: data.requires_verification,
|
requiresVerification: data.requires_verification,
|
||||||
|
anonymousUserEnabled: data.anonymous_user_enabled,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user