fix: remove hardcoded default superuser password (#13822)

* fix: remove hardcoded default superuser password

* fix: harden legacy superuser credential handling

* fix: update superuser CI paths

* [autofix.ci] apply automated fixes

* fix: have images default auto_login to false

have images default auto_login to false

* fix: reject legacy superuser password

* fix: align docker auth setup docs

* docs: fix custom docker auth examples

* docs: include auth env in docker upgrade examples

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Adam Aghili <Adam.Aghili@ibm.com>
This commit is contained in:
Eric Hare
2026-06-25 13:27:42 -07:00
committed by GitHub
parent cf5c37f293
commit c6dbca308d
44 changed files with 630 additions and 222 deletions

View File

@ -98,8 +98,8 @@ LANGFLOW_REMOVE_API_KEYS=
LANGFLOW_CACHE_TYPE=
# Set LANGFLOW_AUTO_LOGIN to false if you want to disable auto login
# and use the login form to login. LANGFLOW_SUPERUSER and LANGFLOW_SUPERUSER_PASSWORD
# must be set if AUTO_LOGIN is set to false
# and use the login form to login. LANGFLOW_SUPERUSER_PASSWORD
# must be set if AUTO_LOGIN is set to false. LANGFLOW_SUPERUSER is optional.
# Values: true, false
LANGFLOW_AUTO_LOGIN=

View File

@ -201,7 +201,7 @@
"filename": "docker_example/docker-compose.yml",
"hashed_secret": "e80c4f90316c87b6b24d03890493c8d1c7c1c99d",
"is_verified": false,
"line_number": 24,
"line_number": 25,
"is_secret": false
}
],
@ -2837,7 +2837,7 @@
"filename": "src/backend/tests/conftest.py",
"hashed_secret": "8bb6118f8fd6935ad0876a3be34a717d32708ffd",
"is_verified": false,
"line_number": 515,
"line_number": 519,
"is_secret": false
},
{
@ -2845,7 +2845,7 @@
"filename": "src/backend/tests/conftest.py",
"hashed_secret": "61fbb5a12cd7b1f1fe1624120089efc0cd299e43",
"is_verified": false,
"line_number": 725,
"line_number": 729,
"is_secret": false
}
],
@ -2939,16 +2939,6 @@
"is_secret": false
}
],
"src/backend/tests/locust/langflow_setup_test.py": [
{
"type": "Secret Keyword",
"filename": "src/backend/tests/locust/langflow_setup_test.py",
"hashed_secret": "e80c4f90316c87b6b24d03890493c8d1c7c1c99d",
"is_verified": false,
"line_number": 205,
"is_secret": false
}
],
"src/backend/tests/unit/agentic/flows/test_langflow_assistant.py": [
{
"type": "Secret Keyword",
@ -3943,7 +3933,7 @@
"filename": "src/backend/tests/unit/test_auth_settings.py",
"hashed_secret": "0352a8acc949c7df21fec16e566ba9a74e797a97",
"is_verified": false,
"line_number": 65,
"line_number": 71,
"is_secret": false
},
{
@ -3951,7 +3941,7 @@
"filename": "src/backend/tests/unit/test_auth_settings.py",
"hashed_secret": "920f8f5815b381ea692e9e7c2f7119f2b1aa620a",
"is_verified": false,
"line_number": 77,
"line_number": 83,
"is_secret": false
},
{
@ -3959,7 +3949,7 @@
"filename": "src/backend/tests/unit/test_auth_settings.py",
"hashed_secret": "81f344a7686a80b4c5293e8fdc0b0160c82c06a8",
"is_verified": false,
"line_number": 83,
"line_number": 89,
"is_secret": false
}
],
@ -4044,7 +4034,7 @@
"filename": "src/backend/tests/unit/test_setup_superuser_flow.py",
"hashed_secret": "6b7065a54fddbb64d6fbe6c4ce55cc668e8a3511",
"is_verified": false,
"line_number": 192,
"line_number": 348,
"is_secret": false
}
],
@ -6267,7 +6257,7 @@
"filename": "src/frontend/tests/core/features/user-flow-state-cleanup.spec.ts",
"hashed_secret": "66904568acfa1e59b8b072cbe602e0ba3688019c",
"is_verified": false,
"line_number": 41,
"line_number": 46,
"is_secret": false
}
],
@ -9287,5 +9277,5 @@
}
]
},
"generated_at": "2026-06-24T23:54:39Z"
"generated_at": "2026-06-25T20:04:57Z"
}

View File

@ -132,5 +132,6 @@ WORKDIR /app
ENV LANGFLOW_HOST=0.0.0.0
ENV LANGFLOW_PORT=7860
ENV LANGFLOW_AUTO_LOGIN=false
CMD ["langflow", "run"]

View File

@ -111,5 +111,6 @@ WORKDIR /app
ENV LANGFLOW_HOST=0.0.0.0
ENV LANGFLOW_PORT=7860
ENV LANGFLOW_AUTO_LOGIN=false
CMD ["python", "-m", "langflow", "run", "--backend-only"]

View File

@ -134,5 +134,6 @@ WORKDIR /app
ENV LANGFLOW_HOST=0.0.0.0
ENV LANGFLOW_PORT=7860
ENV LANGFLOW_AUTO_LOGIN=false
CMD ["langflow-base", "run"]

View File

@ -121,6 +121,7 @@ WORKDIR /app
ENV LANGFLOW_HOST=0.0.0.0
ENV LANGFLOW_PORT=7860
ENV LANGFLOW_EVENT_DELIVERY=polling
ENV LANGFLOW_AUTO_LOGIN=false
USER 1000
CMD ["python", "-m", "langflow", "run", "--host", "0.0.0.0", "--backend-only"]

View File

@ -122,5 +122,6 @@ WORKDIR /app
ENV LANGFLOW_HOST=0.0.0.0
ENV LANGFLOW_PORT=7860
ENV LANGFLOW_AUTO_LOGIN=false
CMD ["langflow", "run"]

View File

@ -21,7 +21,15 @@ This guide will help you get LangFlow up and running using Docker and Docker Com
cd langflow/docker_example
```
3. Run the Docker Compose file:
3. Create a `.env` file with the LangFlow admin password:
```sh
LANGFLOW_SUPERUSER_PASSWORD=replace-with-a-strong-password
```
The default admin username is `langflow`.
4. Run the Docker Compose file:
```sh
docker compose up
@ -40,6 +48,7 @@ The `langflow` service uses the `langflowai/langflow:latest` Docker image and ex
Environment variables:
- `LANGFLOW_DATABASE_URL`: The connection string for the PostgreSQL database.
- `LANGFLOW_SUPERUSER_PASSWORD`: The initial admin password. This value is required in `.env`.
- `LANGFLOW_CONFIG_DIR`: The directory where LangFlow stores logs, file storage, monitor data, and secret keys.
Volumes:

View File

@ -8,6 +8,7 @@ services:
- postgres
environment:
- LANGFLOW_DATABASE_URL=postgresql://langflow:langflow@postgres:5432/langflow
- LANGFLOW_SUPERUSER_PASSWORD=${LANGFLOW_SUPERUSER_PASSWORD:?set LANGFLOW_SUPERUSER_PASSWORD in .env}
# This variable defines where the logs, file storage, monitor data and secret keys are stored.
- LANGFLOW_CONFIG_DIR=/app/langflow
volumes:

View File

@ -11,7 +11,7 @@ who = requests.get(f"{base}/api/v1/users/whoami", headers=headers, timeout=30)
who.raise_for_status()
user_id = who.json()["id"]
# Must differ from the current password (default superuser is often langflow/langflow).
# Must differ from the current password.
payload = {"password": "DocsExampleResetPass2025!"}
response = requests.patch(

View File

@ -20,17 +20,17 @@ This option provides more control over the configuration, including a persistent
* [Create a custom Langflow image](#customize-the-langflow-docker-image): Use a Dockerfile to package a custom Langflow Docker image that includes your own code, custom dependencies, or other modifications.
* [Upgrade the Langflow Docker image](#upgrade-the-langflow-docker-image): Upgrade to a newer image without losing your database or flows by using persistent volumes and replacing only the container.
## Quickstart: Start a Langflow container with default values {#quickstart}
## Quickstart: Start a local Langflow container {#quickstart}
With Docker installed and running on your system, run the following command:
```shell
docker run -p 7860:7860 langflowai/langflow:latest
docker run -p 7860:7860 -e LANGFLOW_AUTO_LOGIN=true langflowai/langflow:latest
```
Then, access Langflow at `http://localhost:7860/`.
This container runs a pre-built Docker image with default settings.
This starts a pre-built Docker image with automatic login enabled for local development.
For more control over the configuration, see [Clone the repo and run the Langflow Docker container](#clone).
## Clone the repo and run the Langflow Docker container {#clone}
@ -57,13 +57,21 @@ The complete Docker Compose configuration is available in `docker_example/docker
cd langflow/docker_example
```
3. Run the Docker Compose file:
3. Create a `.env` file with the Langflow admin password:
```text
LANGFLOW_SUPERUSER_PASSWORD=replace-with-a-strong-password
```
The default admin username is `langflow`.
4. Run the Docker Compose file:
```shell
docker compose up
```
4. Access Langflow at `http://localhost:7860/`.
5. Access Langflow at `http://localhost:7860/`.
### Customize your deployment
@ -82,6 +90,7 @@ For example, to configure the container's database credentials using a `.env` fi
# Langflow configuration
LANGFLOW_DATABASE_URL=postgresql://myuser:mypassword@postgres:5432/langflow
LANGFLOW_CONFIG_DIR=/app/langflow
LANGFLOW_SUPERUSER_PASSWORD=replace-with-a-strong-password
```
2. Modify the `docker-compose.yml` file to reference the `.env` file for both the `langflow` and `postgres` services:
@ -140,7 +149,7 @@ This Dockerfile uses the official Langflow image as the base, creates a director
```bash
docker build -t myuser/langflow-custom:1.0.0 .
docker run -p 7860:7860 myuser/langflow-custom:1.0.0
docker run -p 7860:7860 -e LANGFLOW_AUTO_LOGIN=true myuser/langflow-custom:1.0.0
```
5. Push your image to Docker Hub (optional):
@ -210,7 +219,7 @@ In this example, Langflow expects `memory.py` to exist in the `/helpers` directo
```bash
docker build -t myuser/langflow-custom:1.0.0 .
docker run -p 7860:7860 myuser/langflow-custom:1.0.0
docker run -p 7860:7860 -e LANGFLOW_AUTO_LOGIN=true myuser/langflow-custom:1.0.0
```
This approach can be adapted for any other components or custom code you want to add to Langflow by modifying the file paths and component names.
@ -229,6 +238,7 @@ For example, this Docker Compose file uses a bind mount for Langflow data (`./la
image: langflowai/langflow:1.8.0
environment:
- LANGFLOW_CONFIG_DIR=/app/langflow
- LANGFLOW_SUPERUSER_PASSWORD=${LANGFLOW_SUPERUSER_PASSWORD}
volumes:
- ./langflow-data:/app/langflow
postgres:
@ -270,7 +280,7 @@ For example, this Docker Compose file uses a bind mount for Langflow data (`./la
With `docker run`, use the same volume mount and the new image tag:
```bash
docker run -p 7860:7860 -v langflow-data:/app/langflow langflowai/langflow:1.8.0
docker run -p 7860:7860 -v langflow-data:/app/langflow -e LANGFLOW_SUPERUSER_PASSWORD=replace-with-a-strong-password langflowai/langflow:1.8.0
```
This approach keeps the persistent volumes separate from the Langflow container, so you can upgrade the Langflow application without losing data.
@ -278,4 +288,4 @@ This approach keeps the persistent volumes separate from the Langflow container,
If you need to upgrade to a custom image based on a Langflow release, such as to add `uv` in `1.8.0`, first build a derived image from the official image, and then follow the same steps above.
Set the custom image in your compose file or `docker run`, and then pull and restart.
For a minimal Dockerfile that adds `uv` to the 1.8.0 image, see the [release notes](/release-notes) (“Docker image no longer includes uv or uvx”).
For a minimal Dockerfile that adds `uv` to the 1.8.0 image, see the [release notes](/release-notes) (“Docker image no longer includes uv or uvx”).

View File

@ -157,9 +157,9 @@ For JWT authentication configuration, including algorithm selection and key mana
This variable controls whether authentication is required to access your Langflow server, including the visual editor, API, and Langflow CLI:
* If `LANGFLOW_AUTO_LOGIN=False`, automatic login is disabled. Users must sign in to the visual editor, authenticate as a superuser to run certain Langflow CLI commands, and use a Langflow API key for Langflow API requests.
If `false`, the Langflow team recommends that you also explicitly set [`LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD`](#langflow-superuser) to avoid using the insecure default values.
If `false`, you must explicitly set [`LANGFLOW_SUPERUSER_PASSWORD`](#langflow-superuser) before startup. Set [`LANGFLOW_SUPERUSER`](#langflow-superuser) only if you want to override the default superuser username.
* If `LANGFLOW_AUTO_LOGIN=True` (default), all API requests require authentication with a Langflow API key, but the visual editor automatically signs in all users as superusers, and Langflow uses _only_ the default [superuser credentials](/api-keys-and-authentication#langflow-superuser).
* If `LANGFLOW_AUTO_LOGIN=True` (default), all API requests require authentication with a Langflow API key, but the visual editor automatically signs in all users as superusers, and Langflow uses the configured superuser username with a configured or generated bootstrap password.
All users access the same visual editor environment without password protection, they can run all Langflow CLI commands as superusers, and Langflow automatically authenticates internal requests between the backend and frontend based on the users' superuser privileges.
If you also want to bypass authentication for Langflow API requests in addition to other bypassed authentication, see [`LANGFLOW_SKIP_AUTH_AUTO_LOGIN`](/api-keys-and-authentication#langflow-skip-auth-auto-login).
@ -201,10 +201,11 @@ LANGFLOW_SUPERUSER=administrator
LANGFLOW_SUPERUSER_PASSWORD=securepassword
```
They are required if `LANGFLOW_AUTO_LOGIN=False`.
Otherwise, they aren't relevant.
`LANGFLOW_SUPERUSER_PASSWORD` is required if `LANGFLOW_AUTO_LOGIN=False`. `LANGFLOW_SUPERUSER` is optional and defaults to `langflow`.
`LANGFLOW_SUPERUSER_PASSWORD` can't use the legacy default value `langflow`; startup fails closed instead.
When `LANGFLOW_AUTO_LOGIN=True`, `LANGFLOW_SUPERUSER` selects the auto-login account username and `LANGFLOW_SUPERUSER_PASSWORD` is optional. If the password isn't set, Langflow generates an unknown bootstrap password for the auto-login account.
When you [start a Langflow server with authentication enabled](#start-a-langflow-server-with-authentication-enabled), if these variables are required but _not_ set, then Langflow uses the default values of `langflow` and `langflow`.
When you [start a Langflow server with authentication enabled](#start-a-langflow-server-with-authentication-enabled), if the required password is _not_ set, then startup fails instead of creating a default password.
These defaults don't apply when using the Langflow CLI command [`langflow superuser`](/configuration-cli#langflow-superuser).
### LANGFLOW_SECRET_KEY {#langflow-secret-key}
@ -510,7 +511,7 @@ Additionally, you must sign in as a superuser to manage users and [create a Lang
Your `.env` file can have other environment variables.
This example focuses on authentication variables.
2. Set `LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD` to your desired superuser credentials.
2. Set `LANGFLOW_SUPERUSER_PASSWORD` to your desired superuser password. Optionally set `LANGFLOW_SUPERUSER` to override the default `langflow` username.
For a one-time test, you can use basic credentials like `administrator` and `password`.
Strong, securely-stored credentials are recommended in genuine development and production environments.
@ -538,8 +539,8 @@ Additionally, you must sign in as a superuser to manage users and [create a Lang
uv run langflow run --env-file .env
```
Starting Langflow with a `.env` file automatically authenticates you as the superuser set in `LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD`.
If you don't explicitly set these variables, the default values are `langflow` and `langflow` for system auto-login.
Starting Langflow with this `.env` file creates or uses the configured superuser.
If `LANGFLOW_SUPERUSER_PASSWORD` isn't set, startup fails instead of creating a default password.
6. Verify the server is running. The default location is `http://localhost:7860`.
@ -551,7 +552,7 @@ Next, you can add users to your Langflow server to collaborate with others on fl
If you aren't using the default location, replace `localhost:7860` with your server's address.
2. Log in with the superuser credentials you set in your `.env` (`LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD`).
2. Log in with the superuser credentials from your `.env`: the `LANGFLOW_SUPERUSER` username, or `langflow` if unset, and the `LANGFLOW_SUPERUSER_PASSWORD` password.
3. To manage users on your server, navigate to `/admin`, such as `http://localhost:7860/admin`, click your profile icon, and then click **Admin Page**.

View File

@ -73,7 +73,7 @@ For more information, see [Deploy Langflow on Docker](/deployment-docker).
2. Pull the latest [Langflow Docker image](https://hub.docker.com/r/langflowai/langflow) and start it:
```bash
docker run -p 7860:7860 langflowai/langflow:latest
docker run -p 7860:7860 -e LANGFLOW_AUTO_LOGIN=true langflowai/langflow:latest
```
3. To access Langflow, navigate to `http://localhost:7860/`.
@ -199,4 +199,4 @@ To reinstall Langflow and all of its dependencies, run `uv pip install langflow
* [Quickstart](/get-started-quickstart): Build and run your first flow in minutes.
* [Build flows](/concepts-flows): Learn about building flows.
* [Troubleshoot Langflow](/troubleshoot): Get help with common Langflow install and startup issues.
* [Troubleshoot Langflow](/troubleshoot): Get help with common Langflow install and startup issues.

View File

@ -67,6 +67,11 @@ For all changes, see the [Changelog](https://github.com/langflow-ai/langflow/rel
For more information, see [Langflow Extensions overview](../Develop/extensions-overview.mdx).
- Default superuser password removed
Langflow no longer creates or accepts the legacy `langflow`/`langflow` default superuser credentials.
If `LANGFLOW_AUTO_LOGIN=False`, set `LANGFLOW_SUPERUSER_PASSWORD` to a strong password before startup.
### New features and enhancements
- **Langflow Assistant**: build complete flows

View File

@ -11,7 +11,7 @@ who = requests.get(f"{base}/api/v1/users/whoami", headers=headers, timeout=30)
who.raise_for_status()
user_id = who.json()["id"]
# Must differ from the current password (default superuser is often langflow/langflow).
# Must differ from the current password.
payload = {"password": "DocsExampleResetPass2025!"}
response = requests.patch(

View File

@ -20,17 +20,17 @@ This option provides more control over the configuration, including a persistent
* [Create a custom Langflow image](#customize-the-langflow-docker-image): Use a Dockerfile to package a custom Langflow Docker image that includes your own code, custom dependencies, or other modifications.
* [Upgrade the Langflow Docker image](#upgrade-the-langflow-docker-image): Upgrade to a newer image without losing your database or flows by using persistent volumes and replacing only the container.
## Quickstart: Start a Langflow container with default values {#quickstart}
## Quickstart: Start a local Langflow container {#quickstart}
With Docker installed and running on your system, run the following command:
```shell
docker run -p 7860:7860 langflowai/langflow:latest
docker run -p 7860:7860 -e LANGFLOW_AUTO_LOGIN=true langflowai/langflow:latest
```
Then, access Langflow at `http://localhost:7860/`.
This container runs a pre-built Docker image with default settings.
This starts a pre-built Docker image with automatic login enabled for local development.
For more control over the configuration, see [Clone the repo and run the Langflow Docker container](#clone).
## Clone the repo and run the Langflow Docker container {#clone}
@ -57,13 +57,21 @@ The complete Docker Compose configuration is available in `docker_example/docker
cd langflow/docker_example
```
3. Run the Docker Compose file:
3. Create a `.env` file with the Langflow admin password:
```text
LANGFLOW_SUPERUSER_PASSWORD=replace-with-a-strong-password
```
The default admin username is `langflow`.
4. Run the Docker Compose file:
```shell
docker compose up
```
4. Access Langflow at `http://localhost:7860/`.
5. Access Langflow at `http://localhost:7860/`.
### Customize your deployment
@ -82,6 +90,7 @@ For example, to configure the container's database credentials using a `.env` fi
# Langflow configuration
LANGFLOW_DATABASE_URL=postgresql://myuser:mypassword@postgres:5432/langflow
LANGFLOW_CONFIG_DIR=/app/langflow
LANGFLOW_SUPERUSER_PASSWORD=replace-with-a-strong-password
```
2. Modify the `docker-compose.yml` file to reference the `.env` file for both the `langflow` and `postgres` services:
@ -140,7 +149,7 @@ This Dockerfile uses the official Langflow image as the base, creates a director
```bash
docker build -t myuser/langflow-custom:1.0.0 .
docker run -p 7860:7860 myuser/langflow-custom:1.0.0
docker run -p 7860:7860 -e LANGFLOW_AUTO_LOGIN=true myuser/langflow-custom:1.0.0
```
5. Push your image to Docker Hub (optional):
@ -210,7 +219,7 @@ In this example, Langflow expects `memory.py` to exist in the `/helpers` directo
```bash
docker build -t myuser/langflow-custom:1.0.0 .
docker run -p 7860:7860 myuser/langflow-custom:1.0.0
docker run -p 7860:7860 -e LANGFLOW_AUTO_LOGIN=true myuser/langflow-custom:1.0.0
```
This approach can be adapted for any other components or custom code you want to add to Langflow by modifying the file paths and component names.
@ -229,6 +238,7 @@ For example, this Docker Compose file uses a bind mount for Langflow data (`./la
image: langflowai/langflow:1.8.0
environment:
- LANGFLOW_CONFIG_DIR=/app/langflow
- LANGFLOW_SUPERUSER_PASSWORD=${LANGFLOW_SUPERUSER_PASSWORD}
volumes:
- ./langflow-data:/app/langflow
postgres:
@ -270,7 +280,7 @@ For example, this Docker Compose file uses a bind mount for Langflow data (`./la
With `docker run`, use the same volume mount and the new image tag:
```bash
docker run -p 7860:7860 -v langflow-data:/app/langflow langflowai/langflow:1.8.0
docker run -p 7860:7860 -v langflow-data:/app/langflow -e LANGFLOW_SUPERUSER_PASSWORD=replace-with-a-strong-password langflowai/langflow:1.8.0
```
This approach keeps the persistent volumes separate from the Langflow container, so you can upgrade the Langflow application without losing data.
@ -278,4 +288,4 @@ This approach keeps the persistent volumes separate from the Langflow container,
If you need to upgrade to a custom image based on a Langflow release, such as to add `uv` in `1.8.0`, first build a derived image from the official image, and then follow the same steps above.
Set the custom image in your compose file or `docker run`, and then pull and restart.
For a minimal Dockerfile that adds `uv` to the 1.8.0 image, see the [release notes](/release-notes) (“Docker image no longer includes uv or uvx”).
For a minimal Dockerfile that adds `uv` to the 1.8.0 image, see the [release notes](/release-notes) (“Docker image no longer includes uv or uvx”).

View File

@ -157,9 +157,9 @@ For JWT authentication configuration, including algorithm selection and key mana
This variable controls whether authentication is required to access your Langflow server, including the visual editor, API, and Langflow CLI:
* If `LANGFLOW_AUTO_LOGIN=False`, automatic login is disabled. Users must sign in to the visual editor, authenticate as a superuser to run certain Langflow CLI commands, and use a Langflow API key for Langflow API requests.
If `false`, the Langflow team recommends that you also explicitly set [`LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD`](#langflow-superuser) to avoid using the insecure default values.
If `false`, you must explicitly set [`LANGFLOW_SUPERUSER_PASSWORD`](#langflow-superuser) before startup. Set [`LANGFLOW_SUPERUSER`](#langflow-superuser) only if you want to override the default superuser username.
* If `LANGFLOW_AUTO_LOGIN=True` (default), all API requests require authentication with a Langflow API key, but the visual editor automatically signs in all users as superusers, and Langflow uses _only_ the default [superuser credentials](/api-keys-and-authentication#langflow-superuser).
* If `LANGFLOW_AUTO_LOGIN=True` (default), all API requests require authentication with a Langflow API key, but the visual editor automatically signs in all users as superusers, and Langflow uses the configured superuser username with a configured or generated bootstrap password.
All users access the same visual editor environment without password protection, they can run all Langflow CLI commands as superusers, and Langflow automatically authenticates internal requests between the backend and frontend based on the users' superuser privileges.
If you also want to bypass authentication for Langflow API requests in addition to other bypassed authentication, see [`LANGFLOW_SKIP_AUTH_AUTO_LOGIN`](/api-keys-and-authentication#langflow-skip-auth-auto-login).
@ -201,10 +201,11 @@ LANGFLOW_SUPERUSER=administrator
LANGFLOW_SUPERUSER_PASSWORD=securepassword
```
They are required if `LANGFLOW_AUTO_LOGIN=False`.
Otherwise, they aren't relevant.
`LANGFLOW_SUPERUSER_PASSWORD` is required if `LANGFLOW_AUTO_LOGIN=False`. `LANGFLOW_SUPERUSER` is optional and defaults to `langflow`.
`LANGFLOW_SUPERUSER_PASSWORD` can't use the legacy default value `langflow`; startup fails closed instead.
When `LANGFLOW_AUTO_LOGIN=True`, `LANGFLOW_SUPERUSER` selects the auto-login account username and `LANGFLOW_SUPERUSER_PASSWORD` is optional. If the password isn't set, Langflow generates an unknown bootstrap password for the auto-login account.
When you [start a Langflow server with authentication enabled](#start-a-langflow-server-with-authentication-enabled), if these variables are required but _not_ set, then Langflow uses the default values of `langflow` and `langflow`.
When you [start a Langflow server with authentication enabled](#start-a-langflow-server-with-authentication-enabled), if the required password is _not_ set, then startup fails instead of creating a default password.
These defaults don't apply when using the Langflow CLI command [`langflow superuser`](/configuration-cli#langflow-superuser).
### LANGFLOW_SECRET_KEY {#langflow-secret-key}
@ -510,7 +511,7 @@ Additionally, you must sign in as a superuser to manage users and [create a Lang
Your `.env` file can have other environment variables.
This example focuses on authentication variables.
2. Set `LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD` to your desired superuser credentials.
2. Set `LANGFLOW_SUPERUSER_PASSWORD` to your desired superuser password. Optionally set `LANGFLOW_SUPERUSER` to override the default `langflow` username.
For a one-time test, you can use basic credentials like `administrator` and `password`.
Strong, securely-stored credentials are recommended in genuine development and production environments.
@ -538,8 +539,8 @@ Additionally, you must sign in as a superuser to manage users and [create a Lang
uv run langflow run --env-file .env
```
Starting Langflow with a `.env` file automatically authenticates you as the superuser set in `LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD`.
If you don't explicitly set these variables, the default values are `langflow` and `langflow` for system auto-login.
Starting Langflow with this `.env` file creates or uses the configured superuser.
If `LANGFLOW_SUPERUSER_PASSWORD` isn't set, startup fails instead of creating a default password.
6. Verify the server is running. The default location is `http://localhost:7860`.
@ -551,7 +552,7 @@ Next, you can add users to your Langflow server to collaborate with others on fl
If you aren't using the default location, replace `localhost:7860` with your server's address.
2. Log in with the superuser credentials you set in your `.env` (`LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD`).
2. Log in with the superuser credentials from your `.env`: the `LANGFLOW_SUPERUSER` username, or `langflow` if unset, and the `LANGFLOW_SUPERUSER_PASSWORD` password.
3. To manage users on your server, navigate to `/admin`, such as `http://localhost:7860/admin`, click your profile icon, and then click **Admin Page**.

View File

@ -73,7 +73,7 @@ For more information, see [Deploy Langflow on Docker](/deployment-docker).
2. Pull the latest [Langflow Docker image](https://hub.docker.com/r/langflowai/langflow) and start it:
```bash
docker run -p 7860:7860 langflowai/langflow:latest
docker run -p 7860:7860 -e LANGFLOW_AUTO_LOGIN=true langflowai/langflow:latest
```
3. To access Langflow, navigate to `http://localhost:7860/`.
@ -199,4 +199,4 @@ To reinstall Langflow and all of its dependencies, run `uv pip install langflow
* [Quickstart](/get-started-quickstart): Build and run your first flow in minutes.
* [Build flows](/concepts-flows): Learn about building flows.
* [Troubleshoot Langflow](/troubleshoot): Get help with common Langflow install and startup issues.
* [Troubleshoot Langflow](/troubleshoot): Get help with common Langflow install and startup issues.

View File

@ -67,6 +67,11 @@ For all changes, see the [Changelog](https://github.com/langflow-ai/langflow/rel
For more information, see [Langflow Extensions overview](../Develop/extensions-overview.mdx).
- Default superuser password removed
Langflow no longer creates or accepts the legacy `langflow`/`langflow` default superuser credentials.
If `LANGFLOW_AUTO_LOGIN=False`, set `LANGFLOW_SUPERUSER_PASSWORD` to a strong password before startup.
### New features and enhancements
- **Langflow Assistant**: build complete flows

View File

@ -41,7 +41,7 @@ from fastapi import HTTPException
from httpx import HTTPError
from jwt import InvalidTokenError
from lfx.log.logger import configure, logger
from lfx.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD
from lfx.services.settings.constants import DEFAULT_SUPERUSER
from multiprocess import cpu_count
from multiprocess.context import Process
from packaging import version as pkg_version
@ -58,7 +58,7 @@ from langflow.services.auth.utils import get_current_user_from_access_token
from langflow.services.database.models.api_key.crud import check_key
from langflow.services.database.service import UnsupportedPostgreSQLVersionError, check_postgresql_version_sync
from langflow.services.deps import get_db_service, get_settings_service, is_settings_service_initialized, session_scope
from langflow.services.utils import initialize_services
from langflow.services.utils import get_auto_login_superuser_password, initialize_services
from langflow.utils.version import fetch_latest_version, get_version_info
from langflow.utils.version import is_pre_release as langflow_is_pre_release
@ -844,10 +844,10 @@ def print_banner(host: str, port: int, protocol: str) -> None:
@app.command()
def superuser(
username: str = typer.Option(
None, help="Username for the superuser. Defaults to 'langflow' when AUTO_LOGIN is enabled."
None, help="Username for the superuser. Defaults to the configured AUTO_LOGIN superuser."
),
password: str = typer.Option(
None, help="Password for the superuser. Defaults to 'langflow' when AUTO_LOGIN is enabled."
None, help="Password for the superuser. Ignored when AUTO_LOGIN generates the bootstrap password."
),
log_level: str = typer.Option("error", help="Logging level.", envvar="LANGFLOW_LOG_LEVEL"),
auth_token: str = typer.Option(
@ -856,7 +856,7 @@ def superuser(
) -> None:
"""Create a superuser.
When AUTO_LOGIN is enabled, uses default credentials.
When AUTO_LOGIN is enabled, uses configured or generated bootstrap credentials.
In production mode, requires authentication.
"""
configure(log_level=log_level)
@ -866,7 +866,7 @@ def superuser(
async def _create_superuser(username: str, password: str, auth_token: str | None):
"""Create a superuser."""
await initialize_services()
await initialize_services(skip_superuser_setup=True)
settings_service = get_settings_service()
# Check if superuser creation via CLI is enabled
@ -876,9 +876,7 @@ async def _create_superuser(username: str, password: str, auth_token: str | None
raise typer.Exit(1)
if settings_service.auth_settings.AUTO_LOGIN:
# Force default credentials for AUTO_LOGIN mode
username = DEFAULT_SUPERUSER
password = DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
username = getattr(settings_service.auth_settings, "SUPERUSER", None) or DEFAULT_SUPERUSER
else:
# Production mode - prompt for credentials if not provided
if not username:
@ -890,8 +888,6 @@ async def _create_superuser(username: str, password: str, auth_token: str | None
existing_superusers = []
async with session_scope() as session:
# Note that the default superuser is created by the initialize_services() function,
# but leaving this check here in case we change that behavior
existing_superusers = await get_all_superusers(session)
is_first_setup = len(existing_superusers) == 0
@ -905,6 +901,7 @@ async def _create_superuser(username: str, password: str, auth_token: str | None
typer.echo("2. Run this command again with --auth-token")
raise typer.Exit(1)
password = get_auto_login_superuser_password(settings_service.auth_settings)
typer.echo(f"AUTO_LOGIN enabled. Creating default superuser '{username}'...")
# Do not echo the default password to avoid exposing it in logs.
# AUTO_LOGIN is false - production mode
@ -1065,11 +1062,15 @@ def api_key(
async with session_scope() as session:
from langflow.services.database.models.user.model import User
stmt = select(User).where(User.username == DEFAULT_SUPERUSER)
superuser_username = auth_settings.SUPERUSER or DEFAULT_SUPERUSER
stmt = select(User).where(
User.username == superuser_username,
User.is_superuser == True, # noqa: E712
)
superuser = (await session.exec(stmt)).first()
if not superuser:
typer.echo(
"Default superuser not found. This command requires a superuser and AUTO_LOGIN to be enabled."
"Auto-login superuser not found. This command requires a superuser and AUTO_LOGIN to be enabled."
)
return None
from langflow.services.database.models.api_key.crud import create_api_key, delete_api_key

View File

@ -49,7 +49,6 @@ from langflow.services.database.models.folder.constants import (
)
from langflow.services.database.models.folder.model import Folder, FolderCreate, FolderRead
from langflow.services.deps import (
get_auth_service,
get_settings_service,
get_storage_service,
get_variable_service,
@ -1318,15 +1317,29 @@ async def initialize_auto_login_default_superuser() -> None:
settings_service = get_settings_service()
if not settings_service.auth_settings.AUTO_LOGIN:
return
# In AUTO_LOGIN mode, always use the default credentials for initial bootstrapping
# without persisting the password in memory after setup.
from lfx.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD
# In AUTO_LOGIN mode, bootstrap with a configured password if the operator
# provided one; otherwise generate an unknown password for the default user.
from lfx.services.settings.constants import DEFAULT_SUPERUSER
username = DEFAULT_SUPERUSER
password = DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
from langflow.services.database.models.user.crud import get_user_by_username
from langflow.services.utils import get_auto_login_superuser_password, get_or_create_super_user
username = settings_service.auth_settings.SUPERUSER or DEFAULT_SUPERUSER
password = get_auto_login_superuser_password(settings_service.auth_settings)
async with session_scope() as async_session:
super_user = await get_auth_service().create_super_user(username, password, db=async_session)
super_user = await get_or_create_super_user(
async_session,
username,
password,
is_default=True,
rotate_legacy_default_password=True,
)
if super_user is None:
super_user = await get_user_by_username(async_session, username)
if super_user is None or not super_user.is_superuser:
msg = "Auto-login superuser was not initialized."
raise RuntimeError(msg)
await get_variable_service().initialize_user_variables(super_user.id, async_session)
# Initialize agentic variables if agentic experience is enabled
from langflow.api.utils.mcp.agentic_mcp import initialize_agentic_user_variables

View File

@ -13,6 +13,7 @@ from fastapi import HTTPException, Request, WebSocketException, status
from jwt import InvalidTokenError
from lfx.log.logger import logger
from lfx.services.auth.base import BaseAuthService
from lfx.services.settings.constants import DEFAULT_SUPERUSER, LEGACY_DEFAULT_SUPERUSER_PASSWORD
from sqlalchemy.exc import IntegrityError
from langflow.helpers.user import get_user_by_flow_id_or_endpoint_name
@ -693,6 +694,20 @@ class AuthService(BaseAuthService):
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Waiting for approval")
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Inactive user")
auth_settings = self.settings.auth_settings
auto_login_superuser = auth_settings.SUPERUSER or DEFAULT_SUPERUSER
legacy_superuser_usernames = {DEFAULT_SUPERUSER, auto_login_superuser}
if username in legacy_superuser_usernames and password == LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value():
if request and request.client:
logger.warning(
"Login failed: legacy default superuser password is disabled",
auth_event="login_failed",
reason="legacy_default_password_disabled",
auth_id=str(user.id),
client_ip=request.client.host,
)
return None
if not self.verify_password(password, user.password):
if request and request.client:
logger.warning(

View File

@ -4,10 +4,14 @@ import asyncio
from enum import Enum
from importlib import import_module
from pathlib import Path
from secrets import token_urlsafe
from typing import TYPE_CHECKING
from lfx.log.logger import logger
from lfx.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD
from lfx.services.settings.constants import (
DEFAULT_SUPERUSER,
LEGACY_DEFAULT_SUPERUSER_PASSWORD,
)
from lfx.services.settings.feature_flags import FEATURE_FLAGS
from sqlalchemy import delete
from sqlalchemy import exc as sqlalchemy_exc
@ -37,7 +41,58 @@ class SetupSuperuserResult(str, Enum):
SUPERUSER_UNCHANGED = "superuser_unchanged"
async def get_or_create_super_user(session: AsyncSession, username, password, is_default):
def _secret_value(secret) -> str:
if not secret:
return ""
if hasattr(secret, "get_secret_value"):
return secret.get_secret_value()
return str(secret)
def get_auto_login_superuser_password(auth_settings) -> str:
configured_password = _secret_value(auth_settings.SUPERUSER_PASSWORD)
legacy_password = LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
if configured_password and configured_password != legacy_password:
return configured_password
return token_urlsafe(32)
async def _get_superuser_by_username(session: AsyncSession, username: str):
from langflow.services.database.models.user.model import User
stmt = select(User).where(
User.username == username,
User.is_superuser == True, # noqa: E712
)
result = await session.exec(stmt)
return result.first()
async def _rotate_legacy_default_superuser_password(session: AsyncSession, user, replacement_password: str) -> bool:
legacy_password = LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
if not legacy_password or not user.password:
return False
auth = get_auth_service()
if not auth.verify_password(legacy_password, user.password):
return False
user.password = auth.get_password_hash(replacement_password)
session.add(user)
await session.commit()
await session.refresh(user)
await logger.awarning("Rotated legacy default superuser password.")
return True
async def get_or_create_super_user(
session: AsyncSession,
username,
password,
is_default,
*,
rotate_legacy_default_password: bool = False,
):
from langflow.services.database.models.user.model import User
stmt = select(User).where(User.username == username)
@ -46,6 +101,8 @@ async def get_or_create_super_user(session: AsyncSession, username, password, is
auth = get_auth_service()
if user and user.is_superuser:
if rotate_legacy_default_password:
await _rotate_legacy_default_superuser_password(session, user, password)
return None # Superuser already exists
if user and is_default:
@ -89,8 +146,14 @@ async def setup_superuser(settings_service: SettingsService, session: AsyncSessi
from filelock import FileLock
username = DEFAULT_SUPERUSER
password = DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
username = settings_service.auth_settings.SUPERUSER or DEFAULT_SUPERUSER
configured_password = _secret_value(settings_service.auth_settings.SUPERUSER_PASSWORD)
if configured_password == LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value():
await logger.awarning(
"Ignoring legacy default LANGFLOW_SUPERUSER_PASSWORD in AUTO_LOGIN mode; "
"generated a random bootstrap password instead."
)
password = get_auto_login_superuser_password(settings_service.auth_settings)
# Use file lock similar to starter projects
lock_file = Path(gettempdir()) / "langflow_auto_login_superuser.lock"
@ -99,7 +162,13 @@ async def setup_superuser(settings_service: SettingsService, session: AsyncSessi
try:
with lock:
# Create user and initialize all related resources
super_user = await get_or_create_super_user(session, username, password, is_default=True)
super_user = await get_or_create_super_user(
session,
username,
password,
is_default=True,
rotate_legacy_default_password=True,
)
if super_user: # Only initialize if user was created
from langflow.initial_setup.setup import get_or_create_default_folder
from langflow.services.deps import get_variable_service
@ -116,6 +185,11 @@ async def setup_superuser(settings_service: SettingsService, session: AsyncSessi
_ = await get_or_create_default_folder(session, super_user.id)
await logger.adebug("Auto-login superuser initialized successfully")
return SetupSuperuserResult.AUTO_LOGIN_INITIALIZED
existing_superuser = await _get_superuser_by_username(session, username)
if existing_superuser is None:
msg = "AUTO_LOGIN is enabled but the configured bootstrap user is not a superuser."
await logger.aerror(msg)
raise RuntimeError(msg)
return SetupSuperuserResult.AUTO_LOGIN_ALREADY_SATISFIED
except TimeoutError as exc:
# Another worker may be handling it - but a stale/abandoned lock or dead holder
@ -125,13 +199,7 @@ async def setup_superuser(settings_service: SettingsService, session: AsyncSessi
"(another worker may hold it, or the lock file may be stale). "
"Verifying whether the default superuser exists.",
)
from langflow.services.database.models.user.model import User
stmt = select(User).where(
User.username == username,
User.is_superuser == True, # noqa: E712
)
exists = (await session.exec(stmt)).first() is not None
exists = (await _get_superuser_by_username(session, username)) is not None
if not exists:
msg = (
"AUTO_LOGIN is enabled but the default superuser was not initialized: "
@ -141,12 +209,13 @@ async def setup_superuser(settings_service: SettingsService, session: AsyncSessi
await logger.aerror(msg)
raise RuntimeError(msg) from exc
return SetupSuperuserResult.AUTO_LOGIN_LOCK_TIMEOUT_SUPERUSER_PRESENT
finally:
settings_service.auth_settings.reset_credentials()
# Remove the default superuser if it exists
await teardown_superuser(settings_service, session)
# If AUTO_LOGIN is disabled, attempt to use configured credentials
# or fall back to default credentials if none are provided.
# If AUTO_LOGIN is disabled, require configured credentials.
username = settings_service.auth_settings.SUPERUSER or DEFAULT_SUPERUSER
password = (settings_service.auth_settings.SUPERUSER_PASSWORD or DEFAULT_SUPERUSER_PASSWORD).get_secret_value()
password = _secret_value(settings_service.auth_settings.SUPERUSER_PASSWORD)
await logger.adebug(f"Setup superuser: username={username}, has_password={bool(password)}")
@ -155,12 +224,21 @@ async def setup_superuser(settings_service: SettingsService, session: AsyncSessi
await logger.aerror(f"Missing credentials: username={username}, password={'set' if password else 'not set'}")
raise ValueError(msg)
is_default = (username == DEFAULT_SUPERUSER) and (password == DEFAULT_SUPERUSER_PASSWORD.get_secret_value())
if password == LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value():
msg = "LANGFLOW_SUPERUSER_PASSWORD cannot use the legacy default password"
await logger.aerror(msg)
raise ValueError(msg)
is_default = (username == DEFAULT_SUPERUSER) and (password == LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value())
try:
await logger.adebug(f"Creating/getting superuser: username={username}, is_default={is_default}")
user = await get_or_create_super_user(
session=session, username=username, password=password, is_default=is_default
session=session,
username=username,
password=password,
is_default=is_default,
rotate_legacy_default_password=True,
)
if user is not None:
await logger.adebug("Superuser created successfully.")
@ -581,7 +659,7 @@ def register_builtin_deployment_mappers() -> None:
logger.info("Skipping Watsonx Orchestrate deployment mapper registration: %s", exc)
async def initialize_services(*, fix_migration: bool = False) -> None:
async def initialize_services(*, fix_migration: bool = False, skip_superuser_setup: bool = False) -> None:
"""Initialize all the services needed."""
from langflow.helpers.windows_postgres_helper import configure_windows_postgres_event_loop
@ -602,13 +680,14 @@ async def initialize_services(*, fix_migration: bool = False) -> None:
await initialize_database(fix_migration=fix_migration)
db_service = get_db_service()
await db_service.initialize_alembic_log_file()
async with session_scope() as session:
settings_service = get_service(ServiceType.SETTINGS_SERVICE)
await setup_superuser(settings_service, session)
try:
await get_db_service().assign_orphaned_flows_to_superuser()
except sqlalchemy_exc.IntegrityError as exc:
await logger.awarning(f"Error assigning orphaned flows to the superuser: {exc!s}")
settings_service = get_service(ServiceType.SETTINGS_SERVICE)
if not skip_superuser_setup:
async with session_scope() as session:
await setup_superuser(settings_service, session)
try:
await get_db_service().assign_orphaned_flows_to_superuser()
except sqlalchemy_exc.IntegrityError as exc:
await logger.awarning(f"Error assigning orphaned flows to the superuser: {exc!s}")
async with session_scope() as session:
await clean_transactions(settings_service, session)

View File

@ -318,6 +318,8 @@ def distributed_client_fixture(
db_path = Path(db_dir) / "test.db"
monkeypatch.setenv("LANGFLOW_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "false")
monkeypatch.setenv("LANGFLOW_SUPERUSER", "langflow")
monkeypatch.setenv("LANGFLOW_SUPERUSER_PASSWORD", "test-superuser-password")
# monkeypatch langflow.services.task.manager.USE_CELERY to True
# monkeypatch.setattr(manager, "USE_CELERY", True)
monkeypatch.setattr(celery_app, "celery_app", celery_app.make_celery("langflow", Config))
@ -470,6 +472,8 @@ async def client_fixture(
db_path = Path(db_dir) / "test.db"
monkeypatch.setenv("LANGFLOW_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "false")
monkeypatch.setenv("LANGFLOW_SUPERUSER", "langflow")
monkeypatch.setenv("LANGFLOW_SUPERUSER_PASSWORD", "test-superuser-password")
monkeypatch.setenv("DO_NOT_TRACK", "true")
if "load_flows" in request.keywords:
shutil.copyfile(

View File

@ -108,7 +108,7 @@ python langflow_setup_test.py --list-flows
This will:
- Use default Langflow credentials (langflow/langflow)
- Use `LANGFLOW_SUPERUSER` / `LANGFLOW_SUPERUSER_PASSWORD` or AUTO_LOGIN
- Generate API keys
- Upload a real starter project flow
- Provide credentials for load testing
@ -185,7 +185,7 @@ Use with: `--shape ramp100` or `--shape stepramp`
1. **Health Check**: Verify Langflow is running
2. **Flow Selection**: Choose from 40+ real starter project flows
3. **Authentication**: Login with default credentials (langflow/langflow)
3. **Authentication**: Login with configured credentials or AUTO_LOGIN
4. **API Key Generation**: Create API key for load testing
5. **Flow Upload**: Upload the selected starter project flow
6. **Credential Export**: Provide environment variables for testing
@ -240,7 +240,7 @@ The test tracks:
### Common Issues
1. **Setup Failed**: Ensure Langflow is accessible and not in read-only mode
2. **Authentication Errors**: Verify default credentials (langflow/langflow) are enabled
2. **Authentication Errors**: Set `LANGFLOW_SUPERUSER` and `LANGFLOW_SUPERUSER_PASSWORD`, or enable `LANGFLOW_AUTO_LOGIN`
3. **Flow Creation Failed**: Verify the user has permission to create flows
4. **Connection Errors**: Check network connectivity and firewall settings
5. **Status Code 0 Errors**: Usually indicates connection overload - reduce user count or spawn rate

View File

@ -19,10 +19,39 @@ Usage:
import argparse
import asyncio
import json
import os
import sys
import time
async def authenticate_setup_client(client) -> tuple[str, str, str]:
"""Return an access token using explicit credentials or AUTO_LOGIN."""
configured_username = os.environ.get("LANGFLOW_SUPERUSER", "")
password = os.environ.get("LANGFLOW_SUPERUSER_PASSWORD", "")
if configured_username and not password:
raise RuntimeError("Set LANGFLOW_SUPERUSER_PASSWORD when LANGFLOW_SUPERUSER is set, or unset both.")
if password:
username = configured_username or "langflow"
login_response = await client.post(
"/api/v1/login",
data={"username": username, "password": password},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
if login_response.status_code != 200:
raise RuntimeError(f"Login failed: {login_response.status_code} - {login_response.text}")
return login_response.json()["access_token"], username, "<redacted>"
auto_login_response = await client.get("/api/v1/auto_login")
if auto_login_response.status_code != 200:
raise RuntimeError(
"Authentication failed. Set LANGFLOW_SUPERUSER_PASSWORD and optionally LANGFLOW_SUPERUSER, "
"or enable LANGFLOW_AUTO_LOGIN."
)
return auto_login_response.json()["access_token"], "auto-login", ""
async def get_starter_projects_from_api(host: str, access_token: str) -> list[dict]:
"""Get starter projects from Langflow API."""
import httpx
@ -200,14 +229,10 @@ async def setup_langflow_environment(host: str, flow_name: str | None = None, in
print("Install with: pip install httpx")
sys.exit(1)
# Configuration - use default Langflow credentials
username = "langflow"
password = "langflow"
setup_state = {
"host": host,
"username": username,
"password": password,
"username": None,
"password": None,
"user_id": None,
"access_token": None,
"api_key": None,
@ -228,36 +253,21 @@ async def setup_langflow_environment(host: str, flow_name: str | None = None, in
print(f" ❌ Health check failed: {e}")
raise
# Step 2: Skip user creation, use default credentials
print("2. Using default Langflow credentials...")
print(f" ✅ Using username: {username}")
# Step 3: Login to get JWT token
print("3. Authenticating...")
login_data = {
"username": username,
"password": password,
}
# Step 2: Authenticate with configured credentials or AUTO_LOGIN
print("2. Authenticating...")
try:
login_response = await client.post(
"/api/v1/login",
data=login_data, # OAuth2PasswordRequestForm expects form data
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
if login_response.status_code != 200:
raise Exception(f"Login failed: {login_response.status_code} - {login_response.text}")
tokens = login_response.json()
setup_state["access_token"] = tokens["access_token"]
access_token, username, password = await authenticate_setup_client(client)
setup_state["access_token"] = access_token
setup_state["username"] = username
setup_state["password"] = password
print(" ✅ Authentication successful")
except Exception as e:
print(f" ❌ Authentication failed: {e}")
raise
# Step 4: Create API key
print("4. Creating API key...")
# Step 3: Create API key
print("3. Creating API key...")
headers = {"Authorization": f"Bearer {setup_state['access_token']}"}
try:
@ -274,8 +284,8 @@ async def setup_langflow_environment(host: str, flow_name: str | None = None, in
print(f" ❌ API key creation failed: {e}")
raise
# Step 5: Select and load flow from API
print("5. Selecting starter project flow...")
# Step 4: Select and load flow from API
print("4. Selecting starter project flow...")
# Flow selection logic
selected_flow_name = None
@ -315,8 +325,8 @@ async def setup_langflow_environment(host: str, flow_name: str | None = None, in
print(f" ✅ Selected flow: {setup_state['flow_name']}")
print(f" Description: {flow_data.get('description', 'No description')}")
# Step 6: Upload the selected flow
print(f"6. Uploading flow: {setup_state['flow_name']}...")
# Step 5: Upload the selected flow
print(f"5. Uploading flow: {setup_state['flow_name']}...")
try:
# Prepare flow data for upload
@ -469,10 +479,6 @@ Examples:
print("Install with: pip install httpx")
sys.exit(1)
# Quick authentication to access the API
username = "langflow"
password = "langflow"
async with httpx.AsyncClient(base_url=args.host, timeout=30.0) as client:
# Health check
try:
@ -483,24 +489,15 @@ Examples:
print(f"❌ Cannot connect to Langflow at {args.host}: {e}")
sys.exit(1)
# Login to get access token
# Authenticate to get access token
try:
login_data = {"username": username, "password": password}
login_response = await client.post(
"/api/v1/login",
data=login_data,
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
if login_response.status_code != 200:
raise Exception(f"Authentication failed: {login_response.status_code}")
tokens = login_response.json()
access_token = tokens["access_token"]
access_token, _, _ = await authenticate_setup_client(client)
except Exception as e:
print(f"❌ Authentication failed: {e}")
print("Make sure Langflow is running with default credentials (langflow/langflow)")
print(
"Set LANGFLOW_SUPERUSER_PASSWORD and optionally LANGFLOW_SUPERUSER, or enable LANGFLOW_AUTO_LOGIN."
)
sys.exit(1)
# Get flows from API

View File

@ -180,6 +180,8 @@ async def files_client_fixture(
db_path = Path(db_dir) / "test.db"
monkeypatch.setenv("LANGFLOW_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "false")
monkeypatch.setenv("LANGFLOW_SUPERUSER", "langflow")
monkeypatch.setenv("LANGFLOW_SUPERUSER_PASSWORD", "test-superuser-password")
from lfx.services.manager import get_service_manager
get_service_manager().factories.clear()

View File

@ -114,6 +114,8 @@ async def files_client_fixture(
db_path = Path(db_dir) / "test.db"
monkeypatch.setenv("LANGFLOW_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "false")
monkeypatch.setenv("LANGFLOW_SUPERUSER", "langflow")
monkeypatch.setenv("LANGFLOW_SUPERUSER_PASSWORD", "test-superuser-password")
from lfx.services.manager import get_service_manager
get_service_manager().factories.clear()
@ -784,6 +786,8 @@ async def s3_files_client_fixture(
db_path = Path(db_dir) / "test_s3.db"
monkeypatch.setenv("LANGFLOW_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "false")
monkeypatch.setenv("LANGFLOW_SUPERUSER", "langflow")
monkeypatch.setenv("LANGFLOW_SUPERUSER_PASSWORD", "test-superuser-password")
# Configure S3 storage
monkeypatch.setenv("LANGFLOW_STORAGE_TYPE", "s3")
monkeypatch.setenv(

View File

@ -19,6 +19,7 @@ from langflow.services.auth.exceptions import (
from langflow.services.auth.service import AuthService
from langflow.services.database.models.user.model import User
from lfx.services.settings.auth import AuthSettings
from lfx.services.settings.constants import DEFAULT_SUPERUSER, LEGACY_DEFAULT_SUPERUSER_PASSWORD
from pydantic import SecretStr
@ -275,6 +276,72 @@ async def test_authenticate_with_credentials_auto_login_skip_rejects_inactive_su
await auth_service.authenticate_with_credentials(token=None, api_key=None, db=AsyncMock())
@pytest.mark.anyio
async def test_authenticate_user_rejects_legacy_default_password_in_auto_login(
auth_service: AuthService,
auth_settings: AuthSettings,
):
auth_settings.AUTO_LOGIN = True
auth_settings.SUPERUSER = DEFAULT_SUPERUSER
legacy_password = LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
default_superuser = User(
id=uuid4(),
username=DEFAULT_SUPERUSER,
password=auth_service.get_password_hash(legacy_password),
is_active=True,
is_superuser=True,
)
with patch("langflow.services.auth.service.get_user_by_username", new=AsyncMock(return_value=default_superuser)):
result = await auth_service.authenticate_user(DEFAULT_SUPERUSER, legacy_password, AsyncMock())
assert result is None
@pytest.mark.anyio
async def test_authenticate_user_rejects_legacy_default_password_when_auto_login_false(
auth_service: AuthService,
auth_settings: AuthSettings,
):
auth_settings.AUTO_LOGIN = False
auth_settings.SUPERUSER = DEFAULT_SUPERUSER
legacy_password = LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
default_superuser = User(
id=uuid4(),
username=DEFAULT_SUPERUSER,
password=auth_service.get_password_hash(legacy_password),
is_active=True,
is_superuser=True,
)
with patch("langflow.services.auth.service.get_user_by_username", new=AsyncMock(return_value=default_superuser)):
result = await auth_service.authenticate_user(DEFAULT_SUPERUSER, legacy_password, AsyncMock())
assert result is None
@pytest.mark.anyio
async def test_authenticate_user_rejects_legacy_default_username_after_superuser_override(
auth_service: AuthService,
auth_settings: AuthSettings,
):
auth_settings.AUTO_LOGIN = True
auth_settings.SUPERUSER = "custom_admin"
legacy_password = LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
default_superuser = User(
id=uuid4(),
username=DEFAULT_SUPERUSER,
password=auth_service.get_password_hash(legacy_password),
is_active=True,
is_superuser=True,
)
with patch("langflow.services.auth.service.get_user_by_username", new=AsyncMock(return_value=default_superuser)):
result = await auth_service.authenticate_user(DEFAULT_SUPERUSER, legacy_password, AsyncMock())
assert result is None
@pytest.mark.anyio
async def test_create_refresh_token_requires_refresh_type(auth_service: AuthService):
invalid_refresh = auth_service.create_token({"sub": str(uuid4()), "type": "access"}, timedelta(minutes=1))

View File

@ -2,7 +2,7 @@ from pathlib import Path
import pytest
from lfx.services.settings.auth import AuthSettings
from lfx.services.settings.constants import DEFAULT_SUPERUSER
from lfx.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD
from pydantic import SecretStr, ValidationError
@ -13,7 +13,7 @@ def test_superuser_password_is_secretstr(auto_login, tmp_path: Path):
assert isinstance(settings.SUPERUSER_PASSWORD, SecretStr)
def test_auto_login_true_forces_default_and_scrubs_password(tmp_path: Path):
def test_auto_login_true_preserves_configured_credentials_and_scrubs_password(tmp_path: Path):
cfg_dir = tmp_path.as_posix()
settings = AuthSettings(
CONFIG_DIR=cfg_dir,
@ -21,15 +21,21 @@ def test_auto_login_true_forces_default_and_scrubs_password(tmp_path: Path):
SUPERUSER="custom",
SUPERUSER_PASSWORD=SecretStr("_changed"),
)
# Validator forces default username and scrubs password
assert settings.SUPERUSER == DEFAULT_SUPERUSER
assert settings.SUPERUSER == "custom"
assert isinstance(settings.SUPERUSER_PASSWORD, SecretStr)
assert settings.SUPERUSER_PASSWORD.get_secret_value() == "langflow"
assert settings.SUPERUSER_PASSWORD.get_secret_value() == "_changed"
# reset_credentials keeps default username (AUTO_LOGIN on) and keeps password scrubbed
# reset_credentials preserves the username and scrubs the password even in AUTO_LOGIN mode.
settings.reset_credentials()
assert settings.SUPERUSER == "custom"
assert settings.SUPERUSER_PASSWORD.get_secret_value() == ""
def test_default_superuser_password_is_empty(tmp_path: Path):
cfg_dir = tmp_path.as_posix()
settings = AuthSettings(CONFIG_DIR=cfg_dir)
assert settings.SUPERUSER == DEFAULT_SUPERUSER
assert settings.SUPERUSER_PASSWORD.get_secret_value() == "langflow"
assert settings.SUPERUSER_PASSWORD.get_secret_value() == DEFAULT_SUPERUSER_PASSWORD.get_secret_value() == ""
def test_auto_login_false_preserves_username_and_scrubs_password_on_reset(tmp_path: Path):

View File

@ -131,8 +131,8 @@ class TestSuperuserCommand:
@pytest.mark.skip(reason="Skip -- default superuser is created by initialize_services() function")
@pytest.mark.asyncio
async def test_auto_login_forces_default_credentials(self, client):
"""Test AUTO_LOGIN=true forces default credentials."""
async def test_auto_login_uses_bootstrap_superuser(self, client):
"""Test AUTO_LOGIN=true bootstraps the configured superuser."""
# Since client fixture already creates default user, we need to test in a clean DB scenario
# But that's why this test is skipped - the behavior is already handled by initialize_services

View File

@ -3,8 +3,10 @@ from unittest.mock import AsyncMock, patch
import pytest
from langflow.services.auth.exceptions import InvalidTokenError
from langflow.services.database.models.user import User
from langflow.services.deps import get_auth_service, session_scope
from langflow.services.deps import get_auth_service, get_settings_service, session_scope
from lfx.services.settings.constants import DEFAULT_SUPERUSER, LEGACY_DEFAULT_SUPERUSER_PASSWORD
from sqlalchemy.exc import IntegrityError
from sqlmodel import select
@pytest.fixture
@ -47,6 +49,44 @@ async def test_login_unsuccessful_wrong_password(client, test_user, async_sessio
assert response.json()["detail"] == "Incorrect username or password"
async def test_login_rejects_legacy_default_superuser_password_when_auto_login_enabled(client):
settings = get_settings_service()
original_auto_login = settings.auth_settings.AUTO_LOGIN
original_superuser = settings.auth_settings.SUPERUSER
legacy_password = LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
settings.auth_settings.AUTO_LOGIN = True
settings.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
async with session_scope() as session:
stmt = select(User).where(User.username == DEFAULT_SUPERUSER)
user = (await session.exec(stmt)).first()
if user is None:
user = User(
username=DEFAULT_SUPERUSER,
password=get_auth_service().get_password_hash(legacy_password),
is_active=True,
is_superuser=True,
)
session.add(user)
await session.commit()
else:
user.password = get_auth_service().get_password_hash(legacy_password)
user.is_active = True
user.is_superuser = True
await session.commit()
try:
response = await client.post(
"api/v1/login",
data={"username": DEFAULT_SUPERUSER, "password": legacy_password},
)
assert response.status_code == 401
assert response.json()["detail"] == "Incorrect username or password"
finally:
settings.auth_settings.AUTO_LOGIN = original_auto_login
settings.auth_settings.SUPERUSER = original_superuser
async def test_session_endpoint_unauthenticated(client):
"""Test /session endpoint returns authenticated=False for unauthenticated requests."""
response = await client.get("api/v1/session")

View File

@ -240,6 +240,8 @@ class TestCLISubprocessIntegration:
LANGFLOW_DATABASE_URL=sqlite:///{db_path}
LANGFLOW_AUTO_SAVING=false
LANGFLOW_AUTO_LOGIN=false
LANGFLOW_SUPERUSER=langflow
LANGFLOW_SUPERUSER_PASSWORD=test-superuser-password
LANGFLOW_LOG_LEVEL=ERROR
""".strip()
)

View File

@ -1,10 +1,17 @@
from datetime import datetime, timezone
import filelock
import pytest
from langflow.services.auth.utils import verify_password
from langflow.services.database.models.user.model import User
from langflow.services.deps import get_auth_service, get_settings_service, session_scope
from langflow.services.utils import SetupSuperuserResult, setup_superuser, teardown_superuser
from lfx.services.settings.constants import DEFAULT_SUPERUSER, DEFAULT_SUPERUSER_PASSWORD
from lfx.services.settings.constants import (
DEFAULT_SUPERUSER,
DEFAULT_SUPERUSER_PASSWORD,
LEGACY_DEFAULT_SUPERUSER_PASSWORD,
)
from pydantic import SecretStr
from sqlmodel import select
_MOCK_AUTO_LOGIN_LOCK_TIMEOUT_MSG = "mock lock timeout"
@ -24,6 +31,8 @@ async def initialized_services(monkeypatch, tmp_path):
db_path = tmp_path / "test.db"
monkeypatch.setenv("LANGFLOW_DATABASE_URL", f"sqlite:///{db_path}")
monkeypatch.setenv("LANGFLOW_AUTO_LOGIN", "false")
monkeypatch.setenv("LANGFLOW_SUPERUSER", DEFAULT_SUPERUSER)
monkeypatch.setenv("LANGFLOW_SUPERUSER_PASSWORD", "test-superuser-password")
get_service_manager().factories.clear()
get_service_manager().services.clear()
@ -54,6 +63,110 @@ async def test_initialize_services_creates_default_superuser_when_auto_login_tru
user = (await session.exec(stmt)).first()
assert user is not None
assert user.is_superuser is True
assert verify_password("test-superuser-password", user.password) is True
assert verify_password(LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value(), user.password) is False
@pytest.mark.asyncio
@pytest.mark.timeout(30)
async def test_setup_superuser_auto_login_rotates_legacy_default_password(initialized_services): # noqa: ARG001
"""AUTO_LOGIN startup rotates old langflow/langflow hashes left by previous releases."""
settings = get_settings_service()
settings.auth_settings.AUTO_LOGIN = True
settings.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
settings.auth_settings.SUPERUSER_PASSWORD = DEFAULT_SUPERUSER_PASSWORD
legacy_password = LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
async with session_scope() as session:
stmt = select(User).where(User.username == DEFAULT_SUPERUSER)
user = (await session.exec(stmt)).first()
if user is None:
user = User(
username=DEFAULT_SUPERUSER,
password=get_auth_service().get_password_hash(legacy_password),
is_superuser=True,
is_active=True,
)
session.add(user)
await session.flush()
else:
user.password = get_auth_service().get_password_hash(legacy_password)
user.is_superuser = True
await session.commit()
async with session_scope() as session:
result = await setup_superuser(settings, session)
assert result == SetupSuperuserResult.AUTO_LOGIN_ALREADY_SATISFIED
async with session_scope() as session:
user = (await session.exec(select(User).where(User.username == DEFAULT_SUPERUSER))).first()
assert user is not None
assert verify_password(legacy_password, user.password) is False
@pytest.mark.asyncio
@pytest.mark.timeout(30)
async def test_setup_superuser_rotates_legacy_default_password_when_auto_login_false(
initialized_services, # noqa: ARG001
):
"""Authenticated startup rotates old langflow/langflow hashes to the configured password."""
settings = get_settings_service()
settings.auth_settings.AUTO_LOGIN = False
settings.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
settings.auth_settings.SUPERUSER_PASSWORD = SecretStr("rotated-production-password")
legacy_password = LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
async with session_scope() as session:
stmt = select(User).where(User.username == DEFAULT_SUPERUSER)
user = (await session.exec(stmt)).first()
if user is None:
user = User(
username=DEFAULT_SUPERUSER,
password=get_auth_service().get_password_hash(legacy_password),
is_superuser=True,
is_active=True,
)
session.add(user)
await session.flush()
user.password = get_auth_service().get_password_hash(legacy_password)
user.is_superuser = True
user.is_active = True
user.last_login_at = datetime.now(timezone.utc)
await session.commit()
async with session_scope() as session:
result = await setup_superuser(settings, session)
assert result == SetupSuperuserResult.SUPERUSER_UNCHANGED
async with session_scope() as session:
user = (await session.exec(select(User).where(User.username == DEFAULT_SUPERUSER))).first()
assert user is not None
assert verify_password("rotated-production-password", user.password) is True
assert verify_password(legacy_password, user.password) is False
@pytest.mark.asyncio
@pytest.mark.timeout(30)
async def test_setup_superuser_auto_login_fails_when_username_is_not_superuser(initialized_services): # noqa: ARG001
"""AUTO_LOGIN setup must not accept an existing non-superuser bootstrap username."""
settings = get_settings_service()
settings.auth_settings.AUTO_LOGIN = True
settings.auth_settings.SUPERUSER = "regular-user"
settings.auth_settings.SUPERUSER_PASSWORD = DEFAULT_SUPERUSER_PASSWORD
async with session_scope() as session:
user = User(
username="regular-user",
password=get_auth_service().get_password_hash("regular-password"),
is_superuser=False,
is_active=True,
)
session.add(user)
await session.commit()
async with session_scope() as session:
with pytest.raises(RuntimeError, match="not a superuser"):
await setup_superuser(settings, session)
@pytest.mark.asyncio
@ -71,7 +184,7 @@ async def test_teardown_superuser_removes_default_if_never_logged(initialized_se
if not user:
user = User(
username=DEFAULT_SUPERUSER,
password=DEFAULT_SUPERUSER_PASSWORD.get_secret_value(),
password=get_auth_service().get_password_hash("test-superuser-password"),
is_superuser=True,
is_active=True,
)
@ -106,7 +219,7 @@ async def test_teardown_superuser_preserves_logged_in_default(initialized_servic
if not user:
user = User(
username=DEFAULT_SUPERUSER,
password=DEFAULT_SUPERUSER_PASSWORD.get_secret_value(),
password=get_auth_service().get_password_hash("test-superuser-password"),
is_superuser=True,
is_active=True,
)
@ -132,8 +245,8 @@ async def test_teardown_superuser_preserves_logged_in_default(initialized_servic
@pytest.mark.asyncio
@pytest.mark.timeout(30)
async def test_setup_superuser_with_no_configured_credentials(initialized_services): # noqa: ARG001
"""Test setup_superuser behavior when no superuser credentials are configured."""
async def test_setup_superuser_with_no_configured_credentials_fails_closed(initialized_services): # noqa: ARG001
"""AUTO_LOGIN=False must not create a superuser from predictable fallback credentials."""
settings = get_settings_service()
settings.auth_settings.AUTO_LOGIN = False
settings.auth_settings.SUPERUSER = ""
@ -141,23 +254,66 @@ async def test_setup_superuser_with_no_configured_credentials(initialized_servic
settings.auth_settings.SUPERUSER_PASSWORD = ""
async with session_scope() as session:
# This should create a default superuser since no credentials are provided
result = await setup_superuser(settings, session)
assert result == SetupSuperuserResult.SUPERUSER_CREATED
with pytest.raises(ValueError, match="Username and password must be set"):
await setup_superuser(settings, session)
# Verify default superuser was created
stmt = select(User).where(User.username == DEFAULT_SUPERUSER)
user = (await session.exec(stmt)).first()
@pytest.mark.parametrize("username", [DEFAULT_SUPERUSER, "custom_admin"])
@pytest.mark.asyncio
@pytest.mark.timeout(30)
async def test_setup_superuser_rejects_legacy_default_password_when_auto_login_false(
initialized_services, # noqa: ARG001
username,
):
"""Authenticated startup must not create a superuser with the legacy default password."""
settings = get_settings_service()
settings.auth_settings.AUTO_LOGIN = False
settings.auth_settings.SUPERUSER = username
settings.auth_settings.SUPERUSER_PASSWORD = LEGACY_DEFAULT_SUPERUSER_PASSWORD
async with session_scope() as session:
with pytest.raises(ValueError, match="legacy default password"):
await setup_superuser(settings, session)
async with session_scope() as session:
user = (await session.exec(select(User).where(User.username == username))).first()
assert user is None
@pytest.mark.asyncio
@pytest.mark.timeout(30)
async def test_setup_superuser_auto_login_ignores_configured_legacy_default_password(
initialized_services, # noqa: ARG001
):
"""AUTO_LOGIN startup must not persist langflow/langflow even when env config provides it."""
settings = get_settings_service()
settings.auth_settings.AUTO_LOGIN = True
settings.auth_settings.SUPERUSER = DEFAULT_SUPERUSER
settings.auth_settings.SUPERUSER_PASSWORD = LEGACY_DEFAULT_SUPERUSER_PASSWORD
legacy_password = LEGACY_DEFAULT_SUPERUSER_PASSWORD.get_secret_value()
async with session_scope() as session:
user = (await session.exec(select(User).where(User.username == DEFAULT_SUPERUSER))).first()
assert user is not None
user.password = get_auth_service().get_password_hash(legacy_password)
user.is_superuser = True
user.is_active = True
await session.commit()
async with session_scope() as session:
assert await setup_superuser(settings, session) == SetupSuperuserResult.AUTO_LOGIN_ALREADY_SATISFIED
async with session_scope() as session:
user = (await session.exec(select(User).where(User.username == DEFAULT_SUPERUSER))).first()
assert user is not None
assert user.is_superuser is True
assert verify_password(legacy_password, user.password) is False
@pytest.mark.asyncio
@pytest.mark.timeout(30)
async def test_setup_superuser_with_custom_credentials(initialized_services): # noqa: ARG001
"""Test setup_superuser behavior with custom superuser credentials."""
from pydantic import SecretStr
settings = get_settings_service()
settings.auth_settings.AUTO_LOGIN = False
settings.auth_settings.SUPERUSER = "custom_admin"
@ -257,7 +413,7 @@ async def test_setup_superuser_auto_login_lock_timeout_ok_when_superuser_exists(
async with session_scope() as session:
await get_auth_service().create_super_user(
DEFAULT_SUPERUSER,
DEFAULT_SUPERUSER_PASSWORD.get_secret_value(),
"test-superuser-password",
db=session,
)

View File

@ -118,6 +118,8 @@ export default defineConfig({
env: {
LANGFLOW_DATABASE_URL: "sqlite:///./temp",
LANGFLOW_AUTO_LOGIN: "true",
LANGFLOW_SUPERUSER: "langflow",
LANGFLOW_SUPERUSER_PASSWORD: "test-superuser-password", // pragma: allowlist secret
LANGFLOW_DEACTIVATE_TRACING: "true",
LANGFLOW_LOG_LEVEL: "ERROR",
DO_NOT_TRACK: "true",

View File

@ -50,7 +50,7 @@ test(
.fill(TEXTS.authDefaultCredential);
await page
.getByPlaceholder(TEXTS.placeholderPassword)
.fill(TEXTS.authDefaultCredential);
.fill(TEXTS.authDefaultPassword);
await page.evaluate(() => {
sessionStorage.removeItem("testMockAutoLogin");
@ -280,7 +280,7 @@ test(
.fill(TEXTS.authDefaultCredential);
await page
.getByPlaceholder(TEXTS.placeholderPassword)
.fill(TEXTS.authDefaultCredential);
.fill(TEXTS.authDefaultPassword);
await page.evaluate(() => {
sessionStorage.removeItem("testMockAutoLogin");

View File

@ -32,7 +32,7 @@ async function setupAutoLoginOff(page: Page): Promise<void> {
.fill(TEXTS.authDefaultCredential);
await page
.getByPlaceholder(TEXTS.placeholderPassword)
.fill(TEXTS.authDefaultCredential);
.fill(TEXTS.authDefaultPassword);
await page.evaluate(() => {
sessionStorage.removeItem("testMockAutoLogin");

View File

@ -56,7 +56,7 @@ test(
.fill(TEXTS.authDefaultCredential);
await page
.getByPlaceholder(TEXTS.placeholderPassword)
.fill(TEXTS.authDefaultCredential);
.fill(TEXTS.authDefaultPassword);
await page.evaluate(() => {
sessionStorage.removeItem("testMockAutoLogin");
});
@ -186,7 +186,7 @@ test(
.fill(TEXTS.authDefaultCredential);
await page
.getByPlaceholder(TEXTS.placeholderPassword)
.fill(TEXTS.authDefaultCredential);
.fill(TEXTS.authDefaultPassword);
await page.evaluate(() => {
sessionStorage.removeItem("testMockAutoLogin");
});

View File

@ -41,7 +41,7 @@ test(
.fill(TEXTS.authDefaultCredential);
await page
.getByPlaceholder(TEXTS.placeholderPassword)
.fill(TEXTS.authDefaultCredential);
.fill(TEXTS.authDefaultPassword);
await page.evaluate(() => {
sessionStorage.removeItem("testMockAutoLogin");

View File

@ -41,7 +41,7 @@ export const addNewUserAndLogin = async (page: Page) => {
.fill(TEXTS.authDefaultCredential);
await page
.getByPlaceholder(TEXTS.placeholderPassword)
.fill(TEXTS.authDefaultCredential);
.fill(TEXTS.authDefaultPassword);
await page.evaluate(() => {
sessionStorage.removeItem("testMockAutoLogin");

View File

@ -56,7 +56,7 @@ export const TEXTS = {
// ─── Toasts / status messages ───────────────────────────────────────
toastProjectDeleted: "Project deleted successfully",
toastApiKeySaved: "Success! Your API Key has been saved.",
toastApiKeySaved: "Success! Your API Key has been saved.", // pragma: allowlist secret
/** Sub-string emitted by the build-flow log when a build finishes
* successfully — used as `text=built successfully` in 36 specs. */
toastBuiltSuccessfully: "built successfully",
@ -64,15 +64,17 @@ export const TEXTS = {
// ─── Auth / login screen ────────────────────────────────────────────
/** Visible on the sign-in route when LANGFLOW_AUTO_LOGIN=false. */
authSignInHeader: "sign in to langflow",
/** Default seeded username/password ("langflow"/"langflow"). */
/** Default seeded username. */
authDefaultCredential: "langflow",
/** Explicit Playwright-only superuser password seeded in playwright.config.ts. */
authDefaultPassword: "test-superuser-password", // pragma: allowlist secret
// ─── Form placeholders (getByPlaceholder) ───────────────────────────
placeholderUsername: "Username",
placeholderPassword: "Password",
placeholderPassword: "Password", // pragma: allowlist secret
placeholderMessage: "Message",
placeholderEmpty: "Empty",
placeholderApiKey: "Insert your API Key",
placeholderApiKey: "Insert your API Key", // pragma: allowlist secret
placeholderSendMessage: "Send a message...",
placeholderVariableName: "Enter a name for the variable...",

View File

@ -8,6 +8,6 @@ export const loginLangflow = async (page: Page) => {
.fill(TEXTS.authDefaultCredential);
await page
.getByPlaceholder(TEXTS.placeholderPassword)
.fill(TEXTS.authDefaultCredential);
.fill(TEXTS.authDefaultPassword);
await page.getByRole("button", { name: TEXTS.signIn }).click();
};

View File

@ -71,7 +71,7 @@ class AuthSettings(BaseSettings):
AUTO_LOGIN: bool = Field(
default=True, # TODO: Set to False in v2.0
description=(
"Enable automatic login with default credentials. "
"Enable automatic login with a configured or generated bootstrap account. "
"SECURITY WARNING: This bypasses authentication and should only be used in development environments. "
"Set to False in production. This will default to False in v2.0."
),
@ -185,27 +185,6 @@ class AuthSettings(BaseSettings):
# Preserve the configured username but scrub the password from memory to avoid plaintext exposure.
self.SUPERUSER_PASSWORD = SecretStr("")
# If autologin is true, then we need to set the credentials to
# the default values
# so we need to validate the superuser and superuser_password
# fields
@field_validator("SUPERUSER", "SUPERUSER_PASSWORD", mode="before")
@classmethod
def validate_superuser(cls, value, info):
# When AUTO_LOGIN is enabled, force superuser to use default values.
if info.data.get("AUTO_LOGIN"):
logger.debug("Auto login is enabled, forcing superuser to use default values")
if info.field_name == "SUPERUSER":
if value != DEFAULT_SUPERUSER:
logger.debug("Resetting superuser to default value")
return DEFAULT_SUPERUSER
if info.field_name == "SUPERUSER_PASSWORD":
if value != DEFAULT_SUPERUSER_PASSWORD.get_secret_value():
logger.debug("Resetting superuser password to default value")
return DEFAULT_SUPERUSER_PASSWORD
return value
@field_validator("SECRET_KEY", mode="before")
@classmethod
def get_secret_key(cls, value, info):

View File

@ -1,7 +1,9 @@
from pydantic import SecretStr
DEFAULT_SUPERUSER = "langflow"
DEFAULT_SUPERUSER_PASSWORD = SecretStr("langflow")
DEFAULT_SUPERUSER_PASSWORD = SecretStr("")
# Only used to detect and rotate credentials created by older releases.
LEGACY_DEFAULT_SUPERUSER_PASSWORD = SecretStr("langflow")
VARIABLES_TO_GET_FROM_ENVIRONMENT = [
"COMPOSIO_API_KEY",