Feature disable password login (#378)
* Add admin toggle to disable password login * Update src/backend/database/routes/users.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update src/ui/main-axios.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update src/ui/Desktop/Admin/AdminSettings.tsx Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update src/backend/database/routes/users.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> * Update src/backend/database/routes/users.ts Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --------- Co-authored-by: ZacharyZcR <zacharyzcr1984@gmail.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
This commit is contained in:
@@ -847,6 +847,23 @@ router.post("/login", async (req, res) => {
|
|||||||
return res.status(400).json({ error: "Invalid username or password" });
|
return res.status(400).json({ error: "Invalid username or password" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const row = db.$client
|
||||||
|
.prepare("SELECT value FROM settings WHERE key = 'allow_password_login'")
|
||||||
|
.get();
|
||||||
|
if (row && (row as { value: string }).value !== "true") {
|
||||||
|
return res
|
||||||
|
.status(403)
|
||||||
|
.json({ error: "Password authentication is currently disabled" });
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
authLogger.error("Failed to check password login status", {
|
||||||
|
operation: "login_check",
|
||||||
|
error: e,
|
||||||
|
});
|
||||||
|
return res.status(500).json({ error: "Failed to check login status" });
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await db
|
const user = await db
|
||||||
.select()
|
.select()
|
||||||
@@ -1095,6 +1112,43 @@ router.patch("/registration-allowed", authenticateJWT, async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Route: Get password login allowed status (public - needed for login page)
|
||||||
|
// GET /users/password-login-allowed
|
||||||
|
router.get("/password-login-allowed", async (req, res) => {
|
||||||
|
try {
|
||||||
|
const row = db.$client
|
||||||
|
.prepare("SELECT value FROM settings WHERE key = 'allow_password_login'")
|
||||||
|
.get();
|
||||||
|
res.json({ allowed: row ? (row as { value: string }).value === "true" : true });
|
||||||
|
} catch (err) {
|
||||||
|
authLogger.error("Failed to get password login allowed", err);
|
||||||
|
res.status(500).json({ error: "Failed to get password login allowed" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Route: Set password login allowed status (admin only)
|
||||||
|
// PATCH /users/password-login-allowed
|
||||||
|
router.patch("/password-login-allowed", authenticateJWT, async (req, res) => {
|
||||||
|
const userId = (req as any).userId;
|
||||||
|
try {
|
||||||
|
const user = await db.select().from(users).where(eq(users.id, userId));
|
||||||
|
if (!user || user.length === 0 || !user[0].is_admin) {
|
||||||
|
return res.status(403).json({ error: "Not authorized" });
|
||||||
|
}
|
||||||
|
const { allowed } = req.body;
|
||||||
|
if (typeof allowed !== "boolean") {
|
||||||
|
return res.status(400).json({ error: "Invalid value for allowed" });
|
||||||
|
}
|
||||||
|
db.$client
|
||||||
|
.prepare("UPDATE settings SET value = ? WHERE key = 'allow_password_login'")
|
||||||
|
.run(allowed ? "true" : "false");
|
||||||
|
res.json({ allowed });
|
||||||
|
} catch (err) {
|
||||||
|
authLogger.error("Failed to set password login allowed", err);
|
||||||
|
res.status(500).json({ error: "Failed to set password login allowed" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Route: Delete user account
|
// Route: Delete user account
|
||||||
// DELETE /users/delete-account
|
// DELETE /users/delete-account
|
||||||
router.delete("/delete-account", authenticateJWT, async (req, res) => {
|
router.delete("/delete-account", authenticateJWT, async (req, res) => {
|
||||||
|
|||||||
@@ -410,10 +410,12 @@
|
|||||||
"general": "General",
|
"general": "General",
|
||||||
"userRegistration": "User Registration",
|
"userRegistration": "User Registration",
|
||||||
"allowNewAccountRegistration": "Allow new account registration",
|
"allowNewAccountRegistration": "Allow new account registration",
|
||||||
|
"allowPasswordLogin": "Allow username/password login",
|
||||||
"missingRequiredFields": "Missing required fields: {{fields}}",
|
"missingRequiredFields": "Missing required fields: {{fields}}",
|
||||||
"oidcConfigurationUpdated": "OIDC configuration updated successfully!",
|
"oidcConfigurationUpdated": "OIDC configuration updated successfully!",
|
||||||
"failedToFetchOidcConfig": "Failed to fetch OIDC configuration",
|
"failedToFetchOidcConfig": "Failed to fetch OIDC configuration",
|
||||||
"failedToFetchRegistrationStatus": "Failed to fetch registration status",
|
"failedToFetchRegistrationStatus": "Failed to fetch registration status",
|
||||||
|
"failedToFetchPasswordLoginStatus": "Failed to fetch password login status",
|
||||||
"failedToFetchUsers": "Failed to fetch users",
|
"failedToFetchUsers": "Failed to fetch users",
|
||||||
"oidcConfigurationDisabled": "OIDC configuration disabled successfully!",
|
"oidcConfigurationDisabled": "OIDC configuration disabled successfully!",
|
||||||
"failedToUpdateOidcConfig": "Failed to update OIDC configuration",
|
"failedToUpdateOidcConfig": "Failed to update OIDC configuration",
|
||||||
|
|||||||
@@ -396,10 +396,12 @@
|
|||||||
"general": "常规",
|
"general": "常规",
|
||||||
"userRegistration": "用户注册",
|
"userRegistration": "用户注册",
|
||||||
"allowNewAccountRegistration": "允许新账户注册",
|
"allowNewAccountRegistration": "允许新账户注册",
|
||||||
|
"allowPasswordLogin": "允许用户名/密码登录",
|
||||||
"missingRequiredFields": "缺少必填字段:{{fields}}",
|
"missingRequiredFields": "缺少必填字段:{{fields}}",
|
||||||
"oidcConfigurationUpdated": "OIDC 配置更新成功!",
|
"oidcConfigurationUpdated": "OIDC 配置更新成功!",
|
||||||
"failedToFetchOidcConfig": "获取 OIDC 配置失败",
|
"failedToFetchOidcConfig": "获取 OIDC 配置失败",
|
||||||
"failedToFetchRegistrationStatus": "获取注册状态失败",
|
"failedToFetchRegistrationStatus": "获取注册状态失败",
|
||||||
|
"failedToFetchPasswordLoginStatus": "获取密码登录状态失败",
|
||||||
"failedToFetchUsers": "获取用户列表失败",
|
"failedToFetchUsers": "获取用户列表失败",
|
||||||
"oidcConfigurationDisabled": "OIDC 配置禁用成功!",
|
"oidcConfigurationDisabled": "OIDC 配置禁用成功!",
|
||||||
"failedToUpdateOidcConfig": "更新 OIDC 配置失败",
|
"failedToUpdateOidcConfig": "更新 OIDC 配置失败",
|
||||||
|
|||||||
@@ -37,8 +37,10 @@ import { useConfirmation } from "@/hooks/use-confirmation.ts";
|
|||||||
import {
|
import {
|
||||||
getOIDCConfig,
|
getOIDCConfig,
|
||||||
getRegistrationAllowed,
|
getRegistrationAllowed,
|
||||||
|
getPasswordLoginAllowed,
|
||||||
getUserList,
|
getUserList,
|
||||||
updateRegistrationAllowed,
|
updateRegistrationAllowed,
|
||||||
|
updatePasswordLoginAllowed,
|
||||||
updateOIDCConfig,
|
updateOIDCConfig,
|
||||||
disableOIDCConfig,
|
disableOIDCConfig,
|
||||||
makeUserAdmin,
|
makeUserAdmin,
|
||||||
@@ -62,6 +64,9 @@ export function AdminSettings({
|
|||||||
const [allowRegistration, setAllowRegistration] = React.useState(true);
|
const [allowRegistration, setAllowRegistration] = React.useState(true);
|
||||||
const [regLoading, setRegLoading] = React.useState(false);
|
const [regLoading, setRegLoading] = React.useState(false);
|
||||||
|
|
||||||
|
const [allowPasswordLogin, setAllowPasswordLogin] = React.useState(true);
|
||||||
|
const [passwordLoginLoading, setPasswordLoginLoading] = React.useState(false);
|
||||||
|
|
||||||
const [oidcConfig, setOidcConfig] = React.useState({
|
const [oidcConfig, setOidcConfig] = React.useState({
|
||||||
client_id: "",
|
client_id: "",
|
||||||
client_secret: "",
|
client_secret: "",
|
||||||
@@ -141,6 +146,27 @@ export function AdminSettings({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (isElectron()) {
|
||||||
|
const serverUrl = (window as any).configuredServerUrl;
|
||||||
|
if (!serverUrl) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getPasswordLoginAllowed()
|
||||||
|
.then((res) => {
|
||||||
|
if (typeof res?.allowed === "boolean") {
|
||||||
|
setAllowPasswordLogin(res.allowed);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
if (err.code !== "NO_SERVER_CONFIGURED") {
|
||||||
|
toast.error(t("admin.failedToFetchPasswordLoginStatus"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const fetchUsers = async () => {
|
const fetchUsers = async () => {
|
||||||
if (isElectron()) {
|
if (isElectron()) {
|
||||||
const serverUrl = (window as any).configuredServerUrl;
|
const serverUrl = (window as any).configuredServerUrl;
|
||||||
@@ -172,6 +198,16 @@ export function AdminSettings({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleTogglePasswordLogin = async (checked: boolean) => {
|
||||||
|
setPasswordLoginLoading(true);
|
||||||
|
try {
|
||||||
|
await updatePasswordLoginAllowed(checked);
|
||||||
|
setAllowPasswordLogin(checked);
|
||||||
|
} finally {
|
||||||
|
setPasswordLoginLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleOIDCConfigSubmit = async (e: React.FormEvent) => {
|
const handleOIDCConfigSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setOidcLoading(true);
|
setOidcLoading(true);
|
||||||
@@ -483,6 +519,14 @@ export function AdminSettings({
|
|||||||
/>
|
/>
|
||||||
{t("admin.allowNewAccountRegistration")}
|
{t("admin.allowNewAccountRegistration")}
|
||||||
</label>
|
</label>
|
||||||
|
<label className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={allowPasswordLogin}
|
||||||
|
onCheckedChange={handleTogglePasswordLogin}
|
||||||
|
disabled={passwordLoginLoading}
|
||||||
|
/>
|
||||||
|
{t("admin.allowPasswordLogin")}
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
@@ -1605,6 +1605,15 @@ export async function getRegistrationAllowed(): Promise<{ allowed: boolean }> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPasswordLoginAllowed(): Promise<{ allowed: boolean }> {
|
||||||
|
try {
|
||||||
|
const response = await authApi.get("/users/password-login-allowed");
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
handleApiError(error, "check password login status");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function getOIDCConfig(): Promise<any> {
|
export async function getOIDCConfig(): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const response = await authApi.get("/users/oidc-config");
|
const response = await authApi.get("/users/oidc-config");
|
||||||
@@ -1752,6 +1761,19 @@ export async function updateRegistrationAllowed(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function updatePasswordLoginAllowed(
|
||||||
|
allowed: boolean,
|
||||||
|
): Promise<{ allowed: boolean }> {
|
||||||
|
try {
|
||||||
|
const response = await authApi.patch("/users/password-login-allowed", {
|
||||||
|
allowed,
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
handleApiError(error, "update password login allowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function updateOIDCConfig(config: any): Promise<any> {
|
export async function updateOIDCConfig(config: any): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const response = await authApi.post("/users/oidc-config", config);
|
const response = await authApi.post("/users/oidc-config", config);
|
||||||
|
|||||||
Reference in New Issue
Block a user