feat(api): Add full node configuration to web panel API
This commit is contained in:
@ -93,7 +93,14 @@ async def add_node(body: AddNodeBody):
|
|||||||
body: Request body containing the name and IP of the node.
|
body: Request body containing the name and IP of the node.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
cli_api.add_node(body.name, body.ip)
|
cli_api.add_node(
|
||||||
|
name=body.name,
|
||||||
|
ip=body.ip,
|
||||||
|
port=body.port,
|
||||||
|
sni=body.sni,
|
||||||
|
pinSHA256=body.pinSHA256,
|
||||||
|
obfs=body.obfs
|
||||||
|
)
|
||||||
return DetailResponse(detail=f"Node '{body.name}' added successfully.")
|
return DetailResponse(detail=f"Node '{body.name}' added successfully.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise HTTPException(status_code=400, detail=str(e))
|
raise HTTPException(status_code=400, detail=str(e))
|
||||||
@ -102,7 +109,6 @@ async def add_node(body: AddNodeBody):
|
|||||||
@router.post('/nodes/delete', response_model=DetailResponse, summary='Delete External Node')
|
@router.post('/nodes/delete', response_model=DetailResponse, summary='Delete External Node')
|
||||||
async def delete_node(body: DeleteNodeBody):
|
async def delete_node(body: DeleteNodeBody):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Deletes an external node from the configuration by its name.
|
Deletes an external node from the configuration by its name.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
from pydantic import BaseModel, field_validator, ValidationInfo
|
from pydantic import BaseModel, field_validator, Field
|
||||||
from ipaddress import IPv4Address, IPv6Address, ip_address
|
from ipaddress import ip_address
|
||||||
import re
|
import re
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
def validate_ip_or_domain(v: str) -> str | None:
|
def validate_ip_or_domain(v: str) -> str | None:
|
||||||
if v is None or v.strip() in ['', 'None']:
|
if v is None or v.strip() in ['', 'None']:
|
||||||
@ -34,6 +35,10 @@ class EditInputBody(StatusResponse):
|
|||||||
class Node(BaseModel):
|
class Node(BaseModel):
|
||||||
name: str
|
name: str
|
||||||
ip: str
|
ip: str
|
||||||
|
port: Optional[int] = Field(default=None, ge=1, le=65535)
|
||||||
|
sni: Optional[str] = None
|
||||||
|
pinSHA256: Optional[str] = None
|
||||||
|
obfs: Optional[str] = None
|
||||||
|
|
||||||
@field_validator('ip', mode='before')
|
@field_validator('ip', mode='before')
|
||||||
def check_node_ip(cls, v: str | None):
|
def check_node_ip(cls, v: str | None):
|
||||||
@ -41,6 +46,45 @@ class Node(BaseModel):
|
|||||||
raise ValueError("IP or Domain field cannot be empty.")
|
raise ValueError("IP or Domain field cannot be empty.")
|
||||||
return validate_ip_or_domain(v)
|
return validate_ip_or_domain(v)
|
||||||
|
|
||||||
|
@field_validator('sni', mode='before')
|
||||||
|
def validate_sni_format(cls, v: str | None):
|
||||||
|
if v is None or not v.strip():
|
||||||
|
return None
|
||||||
|
|
||||||
|
v_stripped = v.strip()
|
||||||
|
|
||||||
|
if "://" in v_stripped:
|
||||||
|
raise ValueError("SNI must not contain a protocol (e.g., http://).")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ip_address(v_stripped)
|
||||||
|
raise ValueError("SNI cannot be an IP address.")
|
||||||
|
except ValueError as e:
|
||||||
|
if "SNI cannot be an IP address" in str(e):
|
||||||
|
raise e
|
||||||
|
|
||||||
|
domain_regex = re.compile(
|
||||||
|
r'^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$',
|
||||||
|
re.IGNORECASE
|
||||||
|
)
|
||||||
|
if not domain_regex.match(v_stripped):
|
||||||
|
raise ValueError(f"'{v_stripped}' is not a valid domain name for SNI.")
|
||||||
|
|
||||||
|
return v_stripped
|
||||||
|
|
||||||
|
@field_validator('pinSHA256', mode='before')
|
||||||
|
def validate_pin_format(cls, v: str | None):
|
||||||
|
if v is None or not v.strip():
|
||||||
|
return None
|
||||||
|
|
||||||
|
v_stripped = v.strip().upper()
|
||||||
|
pin_regex = re.compile(r'^([0-9A-F]{2}:){31}[0-9A-F]{2}$')
|
||||||
|
|
||||||
|
if not pin_regex.match(v_stripped):
|
||||||
|
raise ValueError("Invalid SHA256 pin format.")
|
||||||
|
|
||||||
|
return v_stripped
|
||||||
|
|
||||||
class AddNodeBody(Node):
|
class AddNodeBody(Node):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user