Skip to content

feat(web-console): support username and password authentication via env vars#93

Merged
bbbugg merged 3 commits intomainfrom
feat/web-console-auth
Feb 27, 2026
Merged

feat(web-console): support username and password authentication via env vars#93
bbbugg merged 3 commits intomainfrom
feat/web-console-auth

Conversation

@iBenzene
Copy link
Member

简介

此 PR 增强了网页控制台的登录功能,新增了支持通过只读环境变量实现用户名和密码登录的机制。

变更内容

  • 后端支持:在 .env 中新增了 WEB_CONSOLE_USERNAMEWEB_CONSOLE_PASSWORD 的支持。
  • 动态表单:增加了 /api/auth/config 接口,使前端能依据服务端配置动态渲染输入框(仅密码、用户名+密码、仅 API 密钥)。
  • 向下兼容:如果未设置这两个环境变量,系统将完美降级,继续使用原有的 API_KEYS 逻辑进行登录。
  • 国际化:补充了新增 UI 元素的中文与英文提示语。
  • 文档更新:同步更新了所有 README.env.example

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enhances the web console authentication system by adding support for username and password authentication via environment variables (WEB_CONSOLE_USERNAME and WEB_CONSOLE_PASSWORD). The implementation maintains backward compatibility with the existing API_KEYS authentication method and provides a dynamic login form that adapts based on the server configuration.

Changes:

  • Added username/password authentication via environment variables with three modes: API key only, password only, or username+password
  • Implemented /api/auth/config endpoint to enable dynamic form rendering on the frontend
  • Added internationalization strings for new UI elements (username/password placeholders and error messages)
  • Updated documentation in README files and .env.example to describe the new authentication options

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
ui/locales/zh.json Added Chinese translations for new authentication UI elements (login heading, credentials error, username/password placeholders)
ui/locales/en.json Added English translations for new authentication UI elements (login heading, credentials error, username/password placeholders)
ui/app/pages/LoginPage.vue Modified login form to dynamically render username/password fields based on server config fetched from /api/auth/config endpoint
src/routes/AuthRoutes.js Added /api/auth/config endpoint and implemented multi-mode authentication logic supporting username+password, password-only, or API key modes
README_EN.md Documented new WEB_CONSOLE_USERNAME and WEB_CONSOLE_PASSWORD environment variables in configuration table
README.md Documented new WEB_CONSOLE_USERNAME and WEB_CONSOLE_PASSWORD environment variables in configuration table (Chinese)
.env.example Added example configuration entries for WEB_CONSOLE_USERNAME and WEB_CONSOLE_PASSWORD with usage comments

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +156 to +163
if (expectedUsername && expectedPassword) {
if (username === expectedUsername && submittedPassword === expectedPassword) {
authSuccess = true;
}
} else if (!expectedUsername && expectedPassword) {
if (submittedPassword === expectedPassword) {
authSuccess = true;
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authentication logic uses direct string comparison for username and password validation, which is vulnerable to timing attacks. An attacker could potentially use timing differences to determine valid usernames or passwords character by character. Consider using a constant-time comparison function for security-sensitive string comparisons.

Copilot uses AI. Check for mistakes.
Comment on lines +152 to +165
const submittedPassword = password || apiKey;
const expectedUsername = process.env.WEB_CONSOLE_USERNAME;
const expectedPassword = process.env.WEB_CONSOLE_PASSWORD;

if (expectedUsername && expectedPassword) {
if (username === expectedUsername && submittedPassword === expectedPassword) {
authSuccess = true;
}
} else if (!expectedUsername && expectedPassword) {
if (submittedPassword === expectedPassword) {
authSuccess = true;
}
} else {
if (submittedPassword && this.config.apiKeys.includes(submittedPassword)) {
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When only the password is set (without username), the authentication logic falls back to accepting the password in the 'password' field OR 'apiKey' field (via the submittedPassword variable on line 152). This creates an ambiguous authentication flow where the same credential can be submitted via different field names. Consider enforcing that only the appropriate field name is used for each authentication mode to prevent confusion and potential security issues.

Suggested change
const submittedPassword = password || apiKey;
const expectedUsername = process.env.WEB_CONSOLE_USERNAME;
const expectedPassword = process.env.WEB_CONSOLE_PASSWORD;
if (expectedUsername && expectedPassword) {
if (username === expectedUsername && submittedPassword === expectedPassword) {
authSuccess = true;
}
} else if (!expectedUsername && expectedPassword) {
if (submittedPassword === expectedPassword) {
authSuccess = true;
}
} else {
if (submittedPassword && this.config.apiKeys.includes(submittedPassword)) {
const expectedUsername = process.env.WEB_CONSOLE_USERNAME;
const expectedPassword = process.env.WEB_CONSOLE_PASSWORD;
// Username + password mode: require credentials in username/password fields
if (expectedUsername && expectedPassword) {
if (username === expectedUsername && password === expectedPassword) {
authSuccess = true;
}
// Password-only mode: require shared secret in password field
} else if (!expectedUsername && expectedPassword) {
if (password === expectedPassword) {
authSuccess = true;
}
// API key mode: require API key in apiKey field
} else {
if (apiKey && this.config.apiKeys.includes(apiKey)) {

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +26
WEB_CONSOLE_USERNAME=

# Web console password (optional)
# If only password is set, the login page will only ask for the password.
# If neither is set, the system falls back to API_KEYS for console login.
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation states that WEB_CONSOLE_USERNAME is used "with password" but doesn't clarify what happens if WEB_CONSOLE_USERNAME is set without WEB_CONSOLE_PASSWORD. Based on the backend logic, setting only WEB_CONSOLE_USERNAME has no effect - the system will fall back to API_KEYS. Consider adding explicit documentation stating that WEB_CONSOLE_USERNAME requires WEB_CONSOLE_PASSWORD to be set, or that WEB_CONSOLE_USERNAME alone is ignored.

Suggested change
WEB_CONSOLE_USERNAME=
# Web console password (optional)
# If only password is set, the login page will only ask for the password.
# If neither is set, the system falls back to API_KEYS for console login.
# If only username is set (without password), it is ignored and the system
# falls back to API_KEYS for console login.
WEB_CONSOLE_USERNAME=
# Web console password (optional)
# If only password is set, the login page will only ask for the password.
# If neither is set (or only username is set), the system falls back to API_KEYS
# for console login.

Copilot uses AI. Check for mistakes.
Comment on lines +150 to +162
const { apiKey, username, password } = req.body;
let authSuccess = false;
const submittedPassword = password || apiKey;
const expectedUsername = process.env.WEB_CONSOLE_USERNAME;
const expectedPassword = process.env.WEB_CONSOLE_PASSWORD;

if (expectedUsername && expectedPassword) {
if (username === expectedUsername && submittedPassword === expectedPassword) {
authSuccess = true;
}
} else if (!expectedUsername && expectedPassword) {
if (submittedPassword === expectedPassword) {
authSuccess = true;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The username and password values from environment variables and request body are not trimmed before comparison. This means that leading or trailing whitespace in credentials will cause authentication to fail. While environment variables typically don't have leading/trailing whitespace, HTML form inputs can include it if users accidentally add spaces. Consider trimming the input values before comparison, or document that exact string matching (including whitespace) is required.

Copilot uses AI. Check for mistakes.
Comment on lines +153 to +162
const expectedUsername = process.env.WEB_CONSOLE_USERNAME;
const expectedPassword = process.env.WEB_CONSOLE_PASSWORD;

if (expectedUsername && expectedPassword) {
if (username === expectedUsername && submittedPassword === expectedPassword) {
authSuccess = true;
}
} else if (!expectedUsername && expectedPassword) {
if (submittedPassword === expectedPassword) {
authSuccess = true;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The password is stored in plain text in environment variables and compared directly without hashing. While environment variables are a common practice for credentials, the authentication system should ideally use hashed passwords with a salt for better security. Consider implementing password hashing (e.g., bcrypt, scrypt, or argon2) to protect credentials even if environment variables are exposed.

Copilot uses AI. Check for mistakes.
Comment on lines +84 to +95
try {
const res = await fetch("/api/auth/config");
if (res.ok) {
const data = await res.json();
requireUsername.value = data.requireUsername;
requirePassword.value = data.requirePassword;
}
} catch (err) {
console.error("Failed to load auth config", err);
} finally {
configLoaded.value = true;
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The auth config fetch failure is only logged to the console but doesn't provide any user-visible feedback. If the config endpoint fails to load, the user will see a blank login form (since v-if="configLoaded" will eventually become true but with default false values for requireUsername and requirePassword). Consider displaying an error message to the user when the config fails to load, or implementing a retry mechanism.

Copilot uses AI. Check for mistakes.
Comment on lines +156 to +168
if (expectedUsername && expectedPassword) {
if (username === expectedUsername && submittedPassword === expectedPassword) {
authSuccess = true;
}
} else if (!expectedUsername && expectedPassword) {
if (submittedPassword === expectedPassword) {
authSuccess = true;
}
} else {
if (submittedPassword && this.config.apiKeys.includes(submittedPassword)) {
authSuccess = true;
}
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authentication logic doesn't handle the edge case where WEB_CONSOLE_USERNAME is set but WEB_CONSOLE_PASSWORD is not set. In this scenario, the code will fall through to the API_KEYS check (line 165) without any username validation. This could lead to unexpected behavior where a username field appears in the UI but is ignored during authentication. Consider adding validation to ensure that if WEB_CONSOLE_USERNAME is set, WEB_CONSOLE_PASSWORD must also be set, or handle this case explicitly.

Copilot uses AI. Check for mistakes.
Comment on lines +150 to +154
const { apiKey, username, password } = req.body;
let authSuccess = false;
const submittedPassword = password || apiKey;
const expectedUsername = process.env.WEB_CONSOLE_USERNAME;
const expectedPassword = process.env.WEB_CONSOLE_PASSWORD;
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no input validation or sanitization on the username and password fields received from the request body. While these are only compared against environment variables (not used in database queries or command execution), it's a best practice to validate input length and potentially log/block suspicious inputs. Consider adding basic validation such as maximum length limits to prevent potential DoS attacks through extremely long input strings.

Copilot uses AI. Check for mistakes.
</svg>
</button>
<div class="login-content">
<div v-if="configLoaded" class="login-content">
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the config is loading, the login form content is hidden with v-if="configLoaded". This provides a good UX to prevent form submission before config loads. However, there's no loading indicator shown to the user during this time. Consider adding a loading spinner or message to indicate that the page is initializing, especially on slower network connections where the fetch might take noticeable time.

Copilot uses AI. Check for mistakes.
@bbbugg bbbugg merged commit 74ee193 into main Feb 27, 2026
TonyDG233 added a commit to TonyDG233/Ark_AIStudioToAPI that referenced this pull request Feb 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants