mirror of
https://github.com/mealie-recipes/mealie.git
synced 2026-02-05 15:33:10 -05:00
fix: disable invitations when password login is disabled (#6781)
This commit is contained in:
@@ -35,6 +35,7 @@
|
|||||||
{{ $t("general.create") }}
|
{{ $t("general.create") }}
|
||||||
</BaseButton>
|
</BaseButton>
|
||||||
<BaseButton
|
<BaseButton
|
||||||
|
v-if="$appInfo.allowPasswordLogin"
|
||||||
class="mr-2"
|
class="mr-2"
|
||||||
color="info"
|
color="info"
|
||||||
:icon="$globals.icons.link"
|
:icon="$globals.icons.link"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<ToggleState tag="article">
|
<ToggleState tag="article">
|
||||||
<template #activator="{ toggle, state }">
|
<template #activator="{ toggle, state }">
|
||||||
<v-btn
|
<v-btn
|
||||||
v-if="!state"
|
v-if="!state && $appInfo.allowPasswordLogin"
|
||||||
color="info"
|
color="info"
|
||||||
class="mt-2 mb-n3"
|
class="mt-2 mb-n3"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
@@ -37,7 +37,7 @@
|
|||||||
{{ $t("settings.change-password") }}
|
{{ $t("settings.change-password") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn
|
<v-btn
|
||||||
v-else
|
v-else-if="$appInfo.allowPasswordLogin"
|
||||||
color="info"
|
color="info"
|
||||||
class="mt-2 mb-n3"
|
class="mt-2 mb-n3"
|
||||||
@click="toggle"
|
@click="toggle"
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
class="mt-10"
|
class="mt-10"
|
||||||
:title="$t('settings.change-password')"
|
:title="$t('settings.change-password')"
|
||||||
/>
|
/>
|
||||||
<v-card variant="outlined">
|
<v-card variant="outlined" style="border-color: lightgrey;">
|
||||||
<v-card-text class="pb-0">
|
<v-card-text class="pb-0">
|
||||||
<v-form ref="passChange">
|
<v-form ref="passChange">
|
||||||
<v-text-field
|
<v-text-field
|
||||||
|
|||||||
@@ -295,6 +295,7 @@ export default defineNuxtComponent({
|
|||||||
async setup() {
|
async setup() {
|
||||||
const i18n = useI18n();
|
const i18n = useI18n();
|
||||||
const $auth = useMealieAuth();
|
const $auth = useMealieAuth();
|
||||||
|
const { $appInfo } = useNuxtApp();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const groupSlug = computed(() => route.params.groupSlug || $auth.user.value?.groupSlug || "");
|
const groupSlug = computed(() => route.params.groupSlug || $auth.user.value?.groupSlug || "");
|
||||||
|
|
||||||
@@ -302,7 +303,18 @@ export default defineNuxtComponent({
|
|||||||
title: i18n.t("settings.profile"),
|
title: i18n.t("settings.profile"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const user = computed<UserOut | null>(() => $auth.user.value);
|
const user = computed<UserOut | null>(() => {
|
||||||
|
const authUser = $auth.user.value;
|
||||||
|
if (!authUser) return null;
|
||||||
|
|
||||||
|
// Override canInvite if password login is disabled
|
||||||
|
const canInvite = !$appInfo.allowPasswordLogin ? false : authUser.canInvite;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...authUser,
|
||||||
|
canInvite,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const inviteDialog = ref(false);
|
const inviteDialog = ref(false);
|
||||||
const api = useUserApi();
|
const api = useUserApi();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from typing import Annotated
|
|||||||
|
|
||||||
from fastapi import APIRouter, Header, HTTPException, status
|
from fastapi import APIRouter, Header, HTTPException, status
|
||||||
|
|
||||||
|
from mealie.core.config import get_app_settings
|
||||||
from mealie.core.security import url_safe_token
|
from mealie.core.security import url_safe_token
|
||||||
from mealie.routes._base import BaseUserController, controller
|
from mealie.routes._base import BaseUserController, controller
|
||||||
from mealie.schema.household.invite_token import (
|
from mealie.schema.household.invite_token import (
|
||||||
@@ -21,10 +22,23 @@ router = APIRouter(prefix="/households/invitations", tags=["Households: Invitati
|
|||||||
class GroupInvitationsController(BaseUserController):
|
class GroupInvitationsController(BaseUserController):
|
||||||
@router.get("", response_model=list[ReadInviteToken])
|
@router.get("", response_model=list[ReadInviteToken])
|
||||||
def get_invite_tokens(self):
|
def get_invite_tokens(self):
|
||||||
|
if not self.user.admin:
|
||||||
|
raise HTTPException(
|
||||||
|
status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Only admins can list invite tokens",
|
||||||
|
)
|
||||||
|
|
||||||
return self.repos.group_invite_tokens.page_all(PaginationQuery(page=1, per_page=-1)).items
|
return self.repos.group_invite_tokens.page_all(PaginationQuery(page=1, per_page=-1)).items
|
||||||
|
|
||||||
@router.post("", response_model=ReadInviteToken, status_code=status.HTTP_201_CREATED)
|
@router.post("", response_model=ReadInviteToken, status_code=status.HTTP_201_CREATED)
|
||||||
def create_invite_token(self, body: CreateInviteToken):
|
def create_invite_token(self, body: CreateInviteToken):
|
||||||
|
settings = get_app_settings()
|
||||||
|
if not settings.ALLOW_PASSWORD_LOGIN:
|
||||||
|
raise HTTPException(
|
||||||
|
status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Invitation tokens are disabled when password login is not allowed",
|
||||||
|
)
|
||||||
|
|
||||||
if not self.user.can_invite:
|
if not self.user.can_invite:
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status.HTTP_403_FORBIDDEN,
|
status.HTTP_403_FORBIDDEN,
|
||||||
@@ -51,6 +65,19 @@ class GroupInvitationsController(BaseUserController):
|
|||||||
invite: EmailInvitation,
|
invite: EmailInvitation,
|
||||||
accept_language: Annotated[str | None, Header()] = None,
|
accept_language: Annotated[str | None, Header()] = None,
|
||||||
):
|
):
|
||||||
|
settings = get_app_settings()
|
||||||
|
if not settings.ALLOW_PASSWORD_LOGIN:
|
||||||
|
raise HTTPException(
|
||||||
|
status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="Invitation email are disabled when password login is not allowed",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not self.user.can_invite:
|
||||||
|
raise HTTPException(
|
||||||
|
status.HTTP_403_FORBIDDEN,
|
||||||
|
detail="This user can't send email invitations",
|
||||||
|
)
|
||||||
|
|
||||||
email_service = EmailService(locale=accept_language)
|
email_service = EmailService(locale=accept_language)
|
||||||
url = f"{self.settings.BASE_URL}/register?token={invite.token}"
|
url = f"{self.settings.BASE_URL}/register?token={invite.token}"
|
||||||
|
|
||||||
|
|||||||
@@ -9,17 +9,17 @@ from tests.utils.fixture_schemas import TestUser
|
|||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="function")
|
@pytest.fixture(scope="function")
|
||||||
def invite(api_client: TestClient, unique_user: TestUser) -> None:
|
def invite(api_client: TestClient, admin_user: TestUser) -> None:
|
||||||
# Test Creation
|
# Test Creation
|
||||||
r = api_client.post(api_routes.households_invitations, json={"uses": 2}, headers=unique_user.token)
|
r = api_client.post(api_routes.households_invitations, json={"uses": 2}, headers=admin_user.token)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
invitation = r.json()
|
invitation = r.json()
|
||||||
return invitation["token"]
|
return invitation["token"]
|
||||||
|
|
||||||
|
|
||||||
def test_get_all_invitation(api_client: TestClient, unique_user: TestUser, invite: str) -> None:
|
def test_get_all_invitation(api_client: TestClient, admin_user: TestUser, invite: str) -> None:
|
||||||
# Get All Invites
|
# Get All Invites
|
||||||
r = api_client.get(api_routes.households_invitations, headers=unique_user.token)
|
r = api_client.get(api_routes.households_invitations, headers=admin_user.token)
|
||||||
|
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
|
|
||||||
@@ -28,8 +28,8 @@ def test_get_all_invitation(api_client: TestClient, unique_user: TestUser, invit
|
|||||||
assert len(items) == 1
|
assert len(items) == 1
|
||||||
|
|
||||||
for item in items:
|
for item in items:
|
||||||
assert item["groupId"] == unique_user.group_id
|
assert item["groupId"] == admin_user.group_id
|
||||||
assert item["householdId"] == unique_user.household_id
|
assert item["householdId"] == admin_user.household_id
|
||||||
assert item["token"] == invite
|
assert item["token"] == invite
|
||||||
|
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ def register_user(api_client: TestClient, invite: str):
|
|||||||
return registration, response
|
return registration, response
|
||||||
|
|
||||||
|
|
||||||
def test_group_invitation_link(api_client: TestClient, unique_user: TestUser, invite: str):
|
def test_group_invitation_link(api_client: TestClient, admin_user: TestUser, invite: str):
|
||||||
registration, r = register_user(api_client, invite)
|
registration, r = register_user(api_client, invite)
|
||||||
assert r.status_code == 201
|
assert r.status_code == 201
|
||||||
|
|
||||||
@@ -75,8 +75,8 @@ def test_group_invitation_link(api_client: TestClient, unique_user: TestUser, in
|
|||||||
r = api_client.get(api_routes.users_self, headers={"Authorization": f"Bearer {token}"})
|
r = api_client.get(api_routes.users_self, headers={"Authorization": f"Bearer {token}"})
|
||||||
|
|
||||||
assert r.status_code == 200
|
assert r.status_code == 200
|
||||||
assert r.json()["groupId"] == unique_user.group_id
|
assert r.json()["groupId"] == admin_user.group_id
|
||||||
assert r.json()["householdId"] == unique_user.household_id
|
assert r.json()["householdId"] == admin_user.household_id
|
||||||
|
|
||||||
|
|
||||||
def test_group_invitation_delete_after_uses(api_client: TestClient, invite: str) -> None:
|
def test_group_invitation_delete_after_uses(api_client: TestClient, invite: str) -> None:
|
||||||
|
|||||||
Reference in New Issue
Block a user