better passwrod setting

This commit is contained in:
9qeklajc
2025-10-24 15:22:45 +02:00
parent fac8db9cfb
commit e146b4a08a
3 changed files with 191 additions and 25 deletions
+42 -1
View File
@@ -169,10 +169,22 @@ class SettingsUpdate(BaseModel):
__root__: dict[str, object] __root__: dict[str, object]
class PasswordUpdate(BaseModel):
current_password: str
new_password: str
@admin_router.patch("/api/settings", dependencies=[Depends(require_admin_api)]) @admin_router.patch("/api/settings", dependencies=[Depends(require_admin_api)])
async def update_settings(request: Request, update: SettingsUpdate) -> dict: async def update_settings(request: Request, update: SettingsUpdate) -> dict:
# Remove sensitive fields from general settings update
settings_data = update.__root__.copy()
sensitive_fields = ["admin_password", "upstream_api_key", "nsec"]
for field in sensitive_fields:
if field in settings_data:
del settings_data[field]
async with create_session() as session: async with create_session() as session:
new_settings = await SettingsService.update(update.__root__, session) new_settings = await SettingsService.update(settings_data, session)
data = new_settings.dict() data = new_settings.dict()
if "upstream_api_key" in data: if "upstream_api_key" in data:
data["upstream_api_key"] = "[REDACTED]" if data["upstream_api_key"] else "" data["upstream_api_key"] = "[REDACTED]" if data["upstream_api_key"] else ""
@@ -183,6 +195,35 @@ async def update_settings(request: Request, update: SettingsUpdate) -> dict:
return data return data
@admin_router.patch("/api/password", dependencies=[Depends(require_admin_api)])
async def update_password(request: Request, password_update: PasswordUpdate) -> dict:
# Verify current password
try:
current_settings = SettingsService.get()
current_password = current_settings.admin_password
except Exception:
current_password = os.getenv("ADMIN_PASSWORD", "")
if not current_password:
raise HTTPException(status_code=500, detail="Admin password not configured")
if password_update.current_password != current_password:
raise HTTPException(status_code=401, detail="Current password is incorrect")
# Validate new password
new_password = password_update.new_password.strip()
if len(new_password) < 6:
raise HTTPException(
status_code=400, detail="New password must be at least 6 characters"
)
# Update password
async with create_session() as session:
await SettingsService.update({"admin_password": new_password}, session)
return {"ok": True, "message": "Password updated successfully"}
class SetupRequest(BaseModel): class SetupRequest(BaseModel):
password: str password: str
Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 15 KiB

+149 -24
View File
@@ -24,7 +24,6 @@ interface SettingsData {
description?: string; description?: string;
npub?: string; npub?: string;
nsec?: string; nsec?: string;
admin_password?: string;
upstream_api_key?: string; upstream_api_key?: string;
http_url?: string; http_url?: string;
onion_url?: string; onion_url?: string;
@@ -32,6 +31,12 @@ interface SettingsData {
[key: string]: unknown; [key: string]: unknown;
} }
interface PasswordData {
current_password: string;
new_password: string;
confirm_password: string;
}
export function AdminSettings() { export function AdminSettings() {
const [settings, setSettings] = useState<SettingsData>({}); const [settings, setSettings] = useState<SettingsData>({});
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@@ -39,6 +44,13 @@ export function AdminSettings() {
const [error, setError] = useState<string>(''); const [error, setError] = useState<string>('');
const [showSecrets, setShowSecrets] = useState(false); const [showSecrets, setShowSecrets] = useState(false);
const [newMint, setNewMint] = useState(''); const [newMint, setNewMint] = useState('');
const [passwordData, setPasswordData] = useState<PasswordData>({
current_password: '',
new_password: '',
confirm_password: '',
});
const [passwordError, setPasswordError] = useState<string>('');
const [passwordSaving, setPasswordSaving] = useState(false);
useEffect(() => { useEffect(() => {
loadSettings(); loadSettings();
@@ -78,6 +90,45 @@ export function AdminSettings() {
} }
}; };
const handlePasswordUpdate = async () => {
try {
setPasswordSaving(true);
setPasswordError('');
if (passwordData.new_password !== passwordData.confirm_password) {
setPasswordError('New passwords do not match');
return;
}
if (passwordData.new_password.length < 6) {
setPasswordError('New password must be at least 6 characters');
return;
}
const { apiClient } = await import('@/lib/api/client');
await apiClient.patch('/admin/api/password', {
current_password: passwordData.current_password,
new_password: passwordData.new_password,
});
setPasswordData({
current_password: '',
new_password: '',
confirm_password: '',
});
toast.success('Password updated successfully');
} catch (err) {
const message =
err instanceof Error ? err.message : 'Failed to update password';
setPasswordError(message);
toast.error(message);
} finally {
setPasswordSaving(false);
}
};
const handleInputChange = (field: string, value: string | boolean) => { const handleInputChange = (field: string, value: string | boolean) => {
setSettings((prev) => ({ setSettings((prev) => ({
...prev, ...prev,
@@ -246,11 +297,6 @@ export function AdminSettings() {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className='space-y-4'> <CardContent className='space-y-4'>
{renderSecretField(
'admin_password',
'Admin Password',
'Enter admin password'
)}
{renderSecretField( {renderSecretField(
'upstream_api_key', 'upstream_api_key',
'Upstream API Key', 'Upstream API Key',
@@ -258,7 +304,6 @@ export function AdminSettings() {
)} )}
</CardContent> </CardContent>
</Card> </Card>
{/* Cashu Mints */} {/* Cashu Mints */}
<Card> <Card>
<CardHeader> <CardHeader>
@@ -307,24 +352,104 @@ export function AdminSettings() {
)} )}
</CardContent> </CardContent>
</Card> </Card>
</div>
<Card className='mt-6'> <Card className='mt-6'>
<CardFooter className='flex justify-between'> <CardFooter className='flex justify-between'>
<Button <Button
variant='outline' variant='outline'
onClick={loadSettings} onClick={loadSettings}
disabled={loading || saving} disabled={loading || saving}
> >
<RefreshCw className='mr-2 h-4 w-4' /> <RefreshCw className='mr-2 h-4 w-4' />
Reload Reload
</Button> </Button>
<Button onClick={handleSave} disabled={loading || saving}> <Button onClick={handleSave} disabled={loading || saving}>
<Save className='mr-2 h-4 w-4' /> <Save className='mr-2 h-4 w-4' />
{saving ? 'Saving...' : 'Save Settings'} {saving ? 'Saving...' : 'Save Settings'}
</Button> </Button>
</CardFooter> </CardFooter>
</Card> </Card>
{/* Password Change */}
<Card>
<CardHeader>
<CardTitle>Change Admin Password</CardTitle>
<CardDescription>
Update your admin password for enhanced security
</CardDescription>
</CardHeader>
<CardContent className='space-y-4'>
{passwordError && (
<Alert variant='destructive'>
<AlertCircle className='h-4 w-4' />
<AlertDescription>{passwordError}</AlertDescription>
</Alert>
)}
<div className='space-y-2'>
<Label htmlFor='current_password'>Current Password</Label>
<Input
id='current_password'
type='password'
value={passwordData.current_password}
onChange={(e) =>
setPasswordData((prev) => ({
...prev,
current_password: e.target.value,
}))
}
placeholder='Enter current password'
/>
</div>
<div className='space-y-2'>
<Label htmlFor='new_password'>New Password</Label>
<Input
id='new_password'
type='password'
value={passwordData.new_password}
onChange={(e) =>
setPasswordData((prev) => ({
...prev,
new_password: e.target.value,
}))
}
placeholder='Enter new password (min 6 characters)'
/>
</div>
<div className='space-y-2'>
<Label htmlFor='confirm_password'>Confirm New Password</Label>
<Input
id='confirm_password'
type='password'
value={passwordData.confirm_password}
onChange={(e) =>
setPasswordData((prev) => ({
...prev,
confirm_password: e.target.value,
}))
}
placeholder='Confirm new password'
/>
</div>
</CardContent>
<CardFooter>
<Button
onClick={handlePasswordUpdate}
disabled={
passwordSaving ||
!passwordData.current_password ||
!passwordData.new_password ||
!passwordData.confirm_password
}
>
<Save className='mr-2 h-4 w-4' />
{passwordSaving ? 'Updating...' : 'Update Password'}
</Button>
</CardFooter>
</Card>
</div>
</> </>
); );
} }