mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-10 14:27:00 +08:00
fix(security): move account recovery to local CLI, remove unauthenticated reset endpoints
Unauthenticated POST /api/reset-password and /api/reset-account were a remotely exploitable auth-bypass on public-facing deployments. The confirm phrase was embedded in the frontend and echoed back by the API, so it was friction, not authentication: anyone who knew the account email could reset the password, log in, and obtain a valid JWT. Recovery now runs as local CLI commands that operate directly on the database without starting the HTTP server: nofx reset-password --email you@example.com nofx reset-account These require shell/file access to the host, which a remote attacker does not have, so recovery stays safe even when NOFX is exposed to the public internet. - cli.go: new reset-password / reset-account subcommands (hidden password input on a TTY, --password/stdin for scripting, min 8 chars) - main.go: dispatch subcommands before the server starts (backward compatible with the legacy `nofx <dbpath>` arg) - api: remove public /reset-password and /reset-account routes, their handlers, and the public confirm-phrase constants - web: replace the self-service reset form with CLI instructions; drop the AuthContext resetPassword call and the LoginPage reset-account call (en/zh/id) - telegram: refresh the bot allowlist comment
This commit is contained in:
@@ -33,10 +33,6 @@ interface AuthContextType {
|
||||
betaCode?: string,
|
||||
mode?: UserMode
|
||||
) => Promise<{ success: boolean; message?: string }>
|
||||
resetPassword: (
|
||||
email: string,
|
||||
newPassword: string
|
||||
) => Promise<{ success: boolean; message?: string }>
|
||||
logout: () => void
|
||||
isLoading: boolean
|
||||
}
|
||||
@@ -259,36 +255,9 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
}
|
||||
|
||||
const resetPassword = async (email: string, newPassword: string) => {
|
||||
try {
|
||||
const response = await fetch('/api/reset-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
email,
|
||||
new_password: newPassword,
|
||||
// Server-side guard against accidental/drive-by triggers.
|
||||
// Phrase must match handler_user.go resetPasswordConfirmPhrase.
|
||||
confirm: 'I_UNDERSTAND_THIS_RESETS_MY_PASSWORD',
|
||||
}),
|
||||
})
|
||||
|
||||
const data = await response.json()
|
||||
|
||||
if (response.ok) {
|
||||
return { success: true, message: data.message }
|
||||
} else {
|
||||
return { success: false, message: data.error }
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Password reset failed, please try again',
|
||||
}
|
||||
}
|
||||
}
|
||||
// NOTE: in-browser password reset was removed. Recovery now runs as a local
|
||||
// CLI command on the server (`nofx reset-password`), so it cannot be triggered
|
||||
// remotely. The reset-password page shows the operator how to run it.
|
||||
|
||||
const logout = () => {
|
||||
const savedToken = localStorage.getItem('auth_token')
|
||||
@@ -316,7 +285,6 @@ export function AuthProvider({ children }: { children: React.ReactNode }) {
|
||||
login,
|
||||
loginAdmin,
|
||||
register,
|
||||
resetPassword,
|
||||
logout,
|
||||
isLoading,
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user