mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-10-05 20:28:23 +00:00
better passwrod setting
This commit is contained in:
+42
-1
@@ -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 |
@@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user