fix: disable invitations when password login is disabled (#6781)

This commit is contained in:
Arsène Reymond
2026-01-30 21:05:40 +01:00
committed by GitHub
parent 731ee8ae3d
commit c7be4a452a
5 changed files with 53 additions and 13 deletions

View File

@@ -35,6 +35,7 @@
{{ $t("general.create") }}
</BaseButton>
<BaseButton
v-if="$appInfo.allowPasswordLogin"
class="mr-2"
color="info"
:icon="$globals.icons.link"

View File

@@ -26,7 +26,7 @@
<ToggleState tag="article">
<template #activator="{ toggle, state }">
<v-btn
v-if="!state"
v-if="!state && $appInfo.allowPasswordLogin"
color="info"
class="mt-2 mb-n3"
@click="toggle"
@@ -37,7 +37,7 @@
{{ $t("settings.change-password") }}
</v-btn>
<v-btn
v-else
v-else-if="$appInfo.allowPasswordLogin"
color="info"
class="mt-2 mb-n3"
@click="toggle"
@@ -111,7 +111,7 @@
class="mt-10"
:title="$t('settings.change-password')"
/>
<v-card variant="outlined">
<v-card variant="outlined" style="border-color: lightgrey;">
<v-card-text class="pb-0">
<v-form ref="passChange">
<v-text-field

View File

@@ -295,6 +295,7 @@ export default defineNuxtComponent({
async setup() {
const i18n = useI18n();
const $auth = useMealieAuth();
const { $appInfo } = useNuxtApp();
const route = useRoute();
const groupSlug = computed(() => route.params.groupSlug || $auth.user.value?.groupSlug || "");
@@ -302,7 +303,18 @@ export default defineNuxtComponent({
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 api = useUserApi();

View File

@@ -2,6 +2,7 @@ from typing import Annotated
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.routes._base import BaseUserController, controller
from mealie.schema.household.invite_token import (
@@ -21,10 +22,23 @@ router = APIRouter(prefix="/households/invitations", tags=["Households: Invitati
class GroupInvitationsController(BaseUserController):
@router.get("", response_model=list[ReadInviteToken])
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
@router.post("", response_model=ReadInviteToken, status_code=status.HTTP_201_CREATED)
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:
raise HTTPException(
status.HTTP_403_FORBIDDEN,
@@ -51,6 +65,19 @@ class GroupInvitationsController(BaseUserController):
invite: EmailInvitation,
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)
url = f"{self.settings.BASE_URL}/register?token={invite.token}"

View File

@@ -9,17 +9,17 @@ from tests.utils.fixture_schemas import TestUser
@pytest.fixture(scope="function")
def invite(api_client: TestClient, unique_user: TestUser) -> None:
def invite(api_client: TestClient, admin_user: TestUser) -> None:
# 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
invitation = r.json()
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
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
@@ -28,8 +28,8 @@ def test_get_all_invitation(api_client: TestClient, unique_user: TestUser, invit
assert len(items) == 1
for item in items:
assert item["groupId"] == unique_user.group_id
assert item["householdId"] == unique_user.household_id
assert item["groupId"] == admin_user.group_id
assert item["householdId"] == admin_user.household_id
assert item["token"] == invite
@@ -59,7 +59,7 @@ def register_user(api_client: TestClient, invite: str):
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)
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}"})
assert r.status_code == 200
assert r.json()["groupId"] == unique_user.group_id
assert r.json()["householdId"] == unique_user.household_id
assert r.json()["groupId"] == admin_user.group_id
assert r.json()["householdId"] == admin_user.household_id
def test_group_invitation_delete_after_uses(api_client: TestClient, invite: str) -> None: