1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-07-24 15:49:42 +02:00

Fix/fix block registration (#1059)

* fix disable button

* add backend env for restricting registration

* update state management

* add allow_signup to app info

* move allow_signup to backend only

* cleanup docker-compose

* potential darkmode fix

* fix missing variable

* add banner on login page

* use random bools for tests

* fix initial state bug

* fix state reset
This commit is contained in:
Hayden 2022-03-15 17:34:53 -08:00 committed by GitHub
parent 3c2744a3da
commit 13e157827c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 107 additions and 52 deletions

View file

@ -12,7 +12,6 @@ services:
ports:
- 9091:3000
environment:
- ALLOW_SIGNUP=true
- API_URL=http://mealie-api:9000
# =====================================
@ -45,6 +44,8 @@ services:
ports:
- 9092:9000
environment:
ALLOW_SIGNUP: "false"
DB_ENGINE: sqlite # Optional: 'sqlite', 'postgres'
# =====================================
# Postgres Config

View file

@ -15,6 +15,7 @@
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
| API_DOCS | True | Turns on/off access to the API documentation locally. |
| TZ | UTC | Must be set to get correct date/time on the server |
| ALLOW_SIGNUP | true | Allow user sign-up without token (should match frontend env) |
@ -60,4 +61,4 @@ Changing the webworker settings may cause unforeseen memory leak issues with Mea
| LDAP_AUTH_ENABLED | False | Authenticate via an external LDAP server in addidion to built-in Mealie auth |
| LDAP_SERVER_URL | None | LDAP server URL (e.g. ldap://ldap.example.com) |
| LDAP_BIND_TEMPLATE | None | Templated DN for users, `{}` will be replaced with the username (e.g. `cn={},dc=example,dc=com`) |
| LDAP_ADMIN_FILTER | None | Optional LDAP filter, which tells Mealie the LDAP user is an admin (e.g. `(memberOf=cn=admins,dc=example,dc=com)`) |
| LDAP_ADMIN_FILTER | None | Optional LDAP filter, which tells Mealie the LDAP user is an admin (e.g. `(memberOf=cn=admins,dc=example,dc=com)`) |

View file

@ -4,12 +4,11 @@
### General
| Variables | Default | Description |
| ------------ | :--------------------: | ----------------------------------- |
| ALLOW_SIGNUP | true | Allows anyone to sign-up for Mealie |
| API_URL | http://mealie-api:9000 | URL to proxy API requests |
| Variables | Default | Description |
| --------- | :--------------------: | ------------------------- |
| API_URL | http://mealie-api:9000 | URL to proxy API requests |
### Themeing
### Themeing
Setting the following environmental variables will change the theme of the frontend. Note that the themes are the same for all users. This is a break-change when migration from v0.x.x -> 1.x.x.
| Variables | Default | Description |

View file

@ -18,13 +18,12 @@ services:
- mealie-api
environment:
# Set Frontend ENV Variables Here
- ALLOW_SIGNUP=true
- API_URL=http://mealie-api:9000 # (1)
restart: always
ports:
- "9925:3000" # (2)
volumes:
- mealie-data:/app/data/ # (3)
- mealie-data:/app/data/ # (3)
mealie-api:
image: hkotel/mealie:api-nightly
container_name: mealie-api
@ -34,6 +33,7 @@ services:
- mealie-data:/app/data/
environment:
# Set Backend ENV Variables Here
- ALLOW_SIGNUP=true
- PUID=1000
- PGID=1000
- TZ=America/Anchorage
@ -64,7 +64,7 @@ volumes:
<!-- Updating This? Be Sure to also update the SQLite Annotations -->
1. Whoa whoa whoa, what is this nonsense? The API_URL is the URL the frontend container uses to proxy api requests to the backend server. In this example, the name `mealie-api` resolves to the `mealie-api` container which runs the API server on port 9000. This allows you to access the API without exposing an additional port on the host.
1. Whoa whoa whoa, what is this nonsense? The API_URL is the URL the frontend container uses to proxy api requests to the backend server. In this example, the name `mealie-api` resolves to the `mealie-api` container which runs the API server on port 9000. This allows you to access the API without exposing an additional port on the host.
<br/> <br/> **Note** that both containers must be on the same docker-network for this to work.
2. To access the mealie interface you only need to expose port 3000 on the mealie-frontend container. Here we expose port 9925 on the host, feel free to change this to any port you like.
3. Mounting the data directory to the frontend is now required to access the images/assets directory. This can be mounted read-only. Internally the frontend containers runs a Caddy proxy server that serves the assets requested to reduce load on the backend API.
3. Mounting the data directory to the frontend is now required to access the images/assets directory. This can be mounted read-only. Internally the frontend containers runs a Caddy proxy server that serves the assets requested to reduce load on the backend API.

View file

@ -1,6 +1,6 @@
# Installing with SQLite
SQLite is a popular, open source, self-contained, zero-configuration database that is the ideal choice for Mealie when you have 1-20 Users. Below is a ready to use docker-compose.yaml file for deploying Mealie on your server.
SQLite is a popular, open source, self-contained, zero-configuration database that is the ideal choice for Mealie when you have 1-20 Users. Below is a ready to use docker-compose.yaml file for deploying Mealie on your server.
**For Environmental Variable Configuration See:**
@ -16,7 +16,6 @@ services:
container_name: mealie-frontend
environment:
# Set Frontend ENV Variables Here
- ALLOW_SIGNUP=true
- API_URL=http://mealie-api:9000 # (1)
restart: always
ports:
@ -30,6 +29,7 @@ services:
- mealie-data:/app/data/
environment:
# Set Backend ENV Variables Here
- ALLOW_SIGNUP=true
- PUID=1000
- PGID=1000
- TZ=America/Anchorage
@ -45,7 +45,7 @@ volumes:
<!-- Updating This? Be Sure to also update the Postgres Annotations -->
1. Whoa whoa whoa, what is this nonsense? The API_URL is the URL the frontend container uses to proxy api requests to the backend server. In this example, the name `mealie-api` resolves to the `mealie-api` container which runs the API server on port 9000. This allows you to access the API without exposing an additional port on the host.
1. Whoa whoa whoa, what is this nonsense? The API_URL is the URL the frontend container uses to proxy api requests to the backend server. In this example, the name `mealie-api` resolves to the `mealie-api` container which runs the API server on port 9000. This allows you to access the API without exposing an additional port on the host.
<br/> <br/> **Note** that both containers must be on the same docker-network for this to work.
2. To access the mealie interface you only need to expose port 3000 on the mealie-frontend container. Here we expose port 9925 on the host, feel free to change this to any port you like.
3. Mounting the data directory to the frontend is now required to access the images/assets directory. This can be mounted read-only. Internally the frontend containers runs a Caddy proxy server that serves the assets requested to reduce load on the backend API.
3. Mounting the data directory to the frontend is now required to access the images/assets directory. This can be mounted read-only. Internally the frontend containers runs a Caddy proxy server that serves the assets requested to reduce load on the backend API.

View file

@ -1,2 +1,3 @@
export { useAppInfo } from "./use-app-info";
export { useStaticRoutes } from "./static-routes";
export { useAdminApi, useUserApi } from "./api-client";

View file

@ -0,0 +1,11 @@
import { Ref, useAsync } from "@nuxtjs/composition-api";
import { useAsyncKey } from "../use-utils";
import { AppInfo } from "~/types/api-types/admin";
export function useAppInfo(): Ref<AppInfo | null> {
return useAsync(async () => {
// We use fetch here to reduce need for additional dependencies
const data = await fetch("/api/app/about").then((res) => res.json());
return data as AppInfo;
}, useAsyncKey());
}

View file

@ -2,6 +2,12 @@
<v-app dark>
<TheSnackbar />
<v-banner v-if="isDemo" sticky>
<div class="text-center">
<b> This is a Demo for version: {{ version }} </b> | Username: changeme@email.com | Password: demo
</div>
</v-banner>
<v-main>
<v-scroll-x-transition>
<Nuxt />
@ -11,9 +17,23 @@
</template>
<script lang="ts">
import { defineComponent } from "@nuxtjs/composition-api";
import { computed, defineComponent } from "@nuxtjs/composition-api";
import TheSnackbar from "~/components/Layout/TheSnackbar.vue";
import { useAppInfo } from "~/composables/api";
export default defineComponent({
components: { TheSnackbar },
setup() {
const appInfo = useAppInfo();
const isDemo = computed(() => appInfo?.value?.demoStatus || false);
const version = computed(() => appInfo?.value?.version || "unknown");
return {
appInfo,
isDemo,
version,
};
},
});
</script>

View file

@ -26,7 +26,6 @@ export default {
env: {
GLOBAL_MIDDLEWARE: process.env.GLOBAL_MIDDLEWARE || null,
ALLOW_SIGNUP: process.env.ALLOW_SIGNUP || true,
},
router: {
@ -220,10 +219,6 @@ export default {
publicRuntimeConfig: {
GLOBAL_MIDDLEWARE: process.env.GLOBAL_MIDDLEWARE || null,
ALLOW_SIGNUP: process.env.ALLOW_SIGNUP || true,
envProps: {
allowSignup: process.env.ALLOW_SIGNUP || true,
},
SUB_PATH: process.env.SUB_PATH || "",
axios: {
browserBaseURL: process.env.SUB_PATH || "",

View file

@ -4,7 +4,7 @@
fluid
class="d-flex justify-center align-center"
:class="{
'bg-off-white': !$vuetify.theme.dark,
'bg-off-white': !$vuetify.theme.dark && !isDark,
}"
>
<v-card tag="section" class="d-flex flex-column align-center" width="600px">
@ -108,6 +108,8 @@
<script lang="ts">
import { defineComponent, ref, useContext, computed, reactive } from "@nuxtjs/composition-api";
import { useDark } from "@vueuse/core";
import { useAppInfo } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { useToggleDarkMode } from "~/composables/use-utils";
export default defineComponent({
@ -115,9 +117,9 @@ export default defineComponent({
setup() {
const toggleDark = useToggleDarkMode();
const isDark = useDark();
const { $auth } = useContext();
const context = useContext();
const form = reactive({
email: "",
@ -127,7 +129,9 @@ export default defineComponent({
const loggingIn = ref(false);
const allowSignup = computed(() => context.env.ALLOW_SIGNUP as boolean);
const appInfo = useAppInfo();
const allowSignup = computed(() => appInfo.value?.allowSignup || false);
async function authenticate() {
if (form.email.length === 0 || form.password.length === 0) {
@ -148,6 +152,7 @@ export default defineComponent({
// See https://github.com/nuxt-community/axios-module/issues/550
// Import $axios from useContext()
// if ($axios.isAxiosError(error) && error.response?.status === 401) {
// @ts-ignore - see above
if (error.response?.status === 401) {
alert.error("Invalid Credentials");
} else {
@ -158,6 +163,7 @@ export default defineComponent({
}
return {
isDark,
form,
loggingIn,
allowSignup,

View file

@ -6,8 +6,8 @@
<v-form ref="domRegisterForm" @submit.prevent="register()">
<div class="d-flex justify-center my-2">
<v-btn-toggle v-model="joinGroup" mandatory tile group color="primary">
<v-btn :value="false" small @click="joinGroup = false"> Create a Group </v-btn>
<v-btn :value="true" small @click="joinGroup = true"> Join a Group </v-btn>
<v-btn :value="false" small @click="toggleJoinGroup"> Create a Group </v-btn>
<v-btn :value="true" small @click="toggleJoinGroup"> Join a Group </v-btn>
</v-btn-toggle>
</div>
<v-text-field
@ -99,12 +99,12 @@
</template>
<script lang="ts">
import { computed, defineComponent, reactive, toRefs, ref, useRouter, watch } from "@nuxtjs/composition-api";
import { computed, defineComponent, reactive, toRefs, ref, useRouter } from "@nuxtjs/composition-api";
import { validators } from "@/composables/use-validators";
import { useUserApi } from "~/composables/api";
import { alert } from "~/composables/use-toast";
import { useRouterQuery } from "@/composables/use-router";
import { VForm} from "~/types/vuetify";
import { useRouteQuery } from "@/composables/use-router";
import { VForm } from "~/types/vuetify";
export default defineComponent({
layout: "basic",
@ -117,18 +117,22 @@ export default defineComponent({
});
const allowSignup = computed(() => process.env.AllOW_SIGNUP);
const token = useRouterQuery("token");
const token = useRouteQuery("token");
watch(token, (newToken) => {
if (newToken) {
form.groupToken = newToken;
}
});
if (token) {
if (token.value) {
state.joinGroup = true;
}
function toggleJoinGroup() {
if (state.joinGroup) {
state.joinGroup = false;
token.value = "";
} else {
state.joinGroup = true;
form.group = "";
}
}
const domRegisterForm = ref<VForm | null>(null);
const form = reactive({
@ -163,6 +167,7 @@ export default defineComponent({
return {
token,
toggleJoinGroup,
domRegisterForm,
validators,
allowSignup,

View file

@ -9,6 +9,7 @@ export interface AdminAboutInfo {
production: boolean;
version: string;
demoStatus: boolean;
allowSignup: boolean;
versionLatest: string;
apiPort: number;
apiDocs: boolean;
@ -29,6 +30,7 @@ export interface AppInfo {
production: boolean;
version: string;
demoStatus: boolean;
allowSignup: boolean;
}
export interface AppStatistics {
totalRecipes: number;

View file

@ -11,9 +11,12 @@ export interface ErrorResponse {
exception?: string;
}
export interface FileTokenResponse {
file_token: string;
fileToken: string;
}
export interface SuccessResponse {
message: string;
error?: boolean;
}
export interface ValidationResponse {
valid?: boolean;
}

View file

@ -32,6 +32,8 @@ class AppSettings(BaseSettings):
TOKEN_TIME: int = 48 # Time in Hours
SECRET: str
ALLOW_SIGNUP: bool = True
@property
def DOCS_URL(self) -> str | None:
return "/docs" if self.API_DOCS else None
@ -119,8 +121,8 @@ def app_settings_constructor(data_dir: Path, production: bool, env_file: Path, e
directly, but rather through this factory function.
"""
app_settings = AppSettings(
_env_file=env_file,
_env_file_encoding=env_encoding,
_env_file=env_file, # type: ignore
_env_file_encoding=env_encoding, # type: ignore
**{"SECRET": determine_secrets(data_dir, production)},
)

View file

@ -25,6 +25,7 @@ class AdminAboutController(BaseAdminController):
db_type=settings.DB_ENGINE,
db_url=settings.DB_URL_PUBLIC,
default_group=settings.DEFAULT_GROUP,
allow_signup=settings.ALLOW_SIGNUP,
)
@router.get("/statistics", response_model=AppStatistics)

View file

@ -15,4 +15,5 @@ async def get_app_info():
version=APP_VERSION,
demo_status=settings.IS_DEMO,
production=settings.PRODUCTION,
allow_signup=settings.ALLOW_SIGNUP,
)

View file

@ -1,7 +1,9 @@
from fastapi import APIRouter, status
from fastapi import APIRouter, HTTPException, status
from mealie.core.config import get_app_settings
from mealie.repos.all_repositories import get_repositories
from mealie.routes._base import BasePublicController, controller
from mealie.schema.response import ErrorResponse
from mealie.schema.user.registration import CreateUserRegistration
from mealie.schema.user.user import UserOut
from mealie.services.user_services.registration_service import RegistrationService
@ -13,5 +15,12 @@ router = APIRouter(prefix="/register")
class RegistrationController(BasePublicController):
@router.post("", response_model=UserOut, status_code=status.HTTP_201_CREATED)
def register_new_user(self, data: CreateUserRegistration):
settings = get_app_settings()
if not settings.ALLOW_SIGNUP and data.group_token is None or data.group_token == "":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail=ErrorResponse.respond("User Registration is Disabled")
)
registration_service = RegistrationService(self.deps.logger, get_repositories(self.deps.session))
return registration_service.register_user(data)

View file

@ -1,5 +1,3 @@
from pathlib import Path
from fastapi_camelcase import CamelModel
@ -15,6 +13,7 @@ class AppInfo(CamelModel):
production: bool
version: str
demo_status: bool
allow_signup: bool
class AdminAboutInfo(AppInfo):
@ -22,7 +21,7 @@ class AdminAboutInfo(AppInfo):
api_port: int
api_docs: bool
db_type: str
db_url: Path
db_url: str | None
default_group: str

View file

@ -16,9 +16,8 @@ def test_get_preferences(api_client: TestClient, unique_user: TestUser) -> None:
preferences = response.json()
# Spot Check Defaults
assert preferences["recipePublic"] is True
assert preferences["recipeShowNutrition"] is False
assert preferences["recipePublic"] in {True, False}
assert preferences["recipeShowNutrition"] in {True, False}
def test_preferences_in_group(api_client: TestClient, unique_user: TestUser) -> None:
@ -31,8 +30,8 @@ def test_preferences_in_group(api_client: TestClient, unique_user: TestUser) ->
assert group["preferences"] is not None
# Spot Check
assert group["preferences"]["recipePublic"] is True
assert group["preferences"]["recipeShowNutrition"] is False
assert group["preferences"]["recipePublic"] in {True, False}
assert group["preferences"]["recipeShowNutrition"] in {True, False}
def test_update_preferences(api_client: TestClient, unique_user: TestUser) -> None:

View file

@ -16,13 +16,13 @@ def random_bool() -> bool:
return bool(random.getrandbits(1))
def user_registration_factory() -> CreateUserRegistration:
def user_registration_factory(advanced=None, private=None) -> CreateUserRegistration:
return CreateUserRegistration(
group=random_string(),
email=random_email(),
username=random_string(),
password="fake-password",
password_confirm="fake-password",
advanced=False,
private=False,
advanced=advanced or random_bool(),
private=private or random_bool(),
)