Actions
이슈 #313
진행중
CP
JL
17-4. 노드 삭제 API
이슈 #313:
17-4. 노드 삭제 API
Chris Park이(가) 약 7시간 전에 추가함.
설명
DELETE /api/nodes/:id
CP Chris Park이(가) 7달 전에 변경 Actions #1
- 프로젝트을(를) 14에서 Alphea App(으)로 변경되었습니다.
- 목표버전을(를) 6월 중간 버전(으)로 지정되었습니다.
JL James Lee이(가) 7달 전에 변경 Actions #2
- 상태을(를) 신규에서 해결(으)로 변경되었습니다.
17-4. 노드 삭제 API 구현 완료¶
NodeService.DeleteNode RPC 를 구현했습니다. 이번 포팅에서 첫 번째 JWT 인증이 필요한 보호 RPC 입니다.
-
Repository:
alphea-ai-edge -
Commit: commit:server|d9668e6 —
feat: add NodeService DeleteNode with JWT-authenticated soft delete - 관련 이슈: #254 (새 마이그레이션), #310/#311 (read 쿼리 필터 추가), #312 (upsert 재등록 un-archive 연동)
Proto 정의 (레거시에 없던 신규 RPC)¶
rpc DeleteNode(DeleteNodeRequest) returns (DeleteNodeResponse);
message DeleteNodeRequest {
string node_id = 1;
}
message DeleteNodeResponse {
bool deleted = 1;
}
인증 플로우¶
- 클라이언트가
Authorization: Bearer <node-jwt>헤더와 함께DeleteNode호출 -
domain::auth::require_auth가 메타데이터에서 토큰 추출 →verify_access_token→sub(지갑 주소) 반환 -
nodes::get_by_id로 노드 조회 (archived 는 NotFound) - 소유권 체크:
node.owner_wallet_address와 JWTsub대소문자 무관 비교 - 불일치 →
PermissionDenied - 일치 →
nodes::archive로archived_at = NOW()세팅 (물리 삭제 아님) DeleteNodeResponse { deleted: true }
Soft Delete 설계¶
물리 삭제 대신 archived_at 컬럼을 세팅하는 soft delete 방식입니다.
-
읽기 쿼리 필터:
list,get_by_id,count_filtered모두WHERE archived_at IS NULL조건 추가 → archived 된 노드는 공개 API 에서 완전히 숨김 -
Partial index:
CREATE INDEX ... WHERE archived_at IS NOT NULL— archived 서브셋만 인덱싱해서 인덱스 크기 최소화 (활성 노드 조회는 기존 인덱스로 커버) -
재등록 = 자동 un-archive:
upsert_registered가 ON CONFLICT 시archived_at = NULL로 리셋. 사용자가 동일node_id로 재등록하면 삭제됐던 노드가 자동으로 복원됨 (UX 관점에서 자연스러움)
인증 헬퍼 (domain::auth::require_auth)¶
pub fn require_auth<T>(req: &Request<T>, jwt_secret: &str) -> Result<String, Status> {
let token = req
.metadata()
.get("authorization")
.and_then(|v| v.to_str().ok())
.and_then(|v| v.strip_prefix("Bearer "))
.ok_or_else(|| Status::unauthenticated("missing authorization header"))?;
let payload = verify_access_token(token, jwt_secret)
.map_err(|_| Status::unauthenticated("invalid token"))?;
Ok(payload.sub)
}
결정: 전역 Tower Layer 인터셉터 대신 핸들러별 명시적 호출 방식 채택.
- Pro: 구현이 단순하고, allow-list 실수로 public 만들어버리는 버그가 원천 차단됨 (auth 는 opt-in)
- Con: 핸들러마다 한 줄씩 반복됨
- 보호 RPC 가 많아지면 Tower Layer 기반 전역 인터셉터로 교체 가능. 현재는
DeleteNode하나뿐이므로 과도한 추상화 불필요
레거시의 전역 allow-list + 컨텍스트 주입 방식과는 다르지만, 이번 포팅에서 Rust idiom 에 더 부합합니다. #256 에 JWT 인터셉터 추후 작업으로 기록된 내용과 같은 맥락입니다.
E2E 검증 결과 (examples/node_delete.rs)¶
→ owner: 0x17941d5c72db95ca0998ecae7a87814f903a7479
→ stranger: 0x6c2ae6a68287b3acae6168acfb2906cd1ba931de
→ node_id: edge-del-b44951cf
✓ owner registered node (token: eyJhbGciOiJIUzI1NiIsInR5...)
✓ stranger blocked: PermissionDenied — caller does not own this node
✓ unauth blocked: Unauthenticated — missing authorization header
✓ owner deleted: deleted=true
✓ GetNode post-delete: NotFound — node not found
✓ ListNodes filtered out archived row (total active nodes: 3)
✓ re-registered node is active again: status=2
7개 시나리오 전부 통과:
| # | 시나리오 | 결과 |
|---|---|---|
| 1 | owner 가 edge 노드 등록 | node JWT 발급 |
| 2 | stranger JWT 로 owner 노드 삭제 시도 | PermissionDenied |
| 3 |
authorization 헤더 없이 삭제 시도 |
Unauthenticated |
| 4 | owner JWT 로 삭제 | deleted: true |
| 5 | 삭제 후 GetNode
|
NotFound |
| 6 | 삭제 후 ListNodes
|
총 3건 (archived 제외) |
| 7 | 동일 node_id 재등록 |
un-archive, status=2 (ACTIVE)
|
알려진 한계¶
-
Delete 되돌리기: 현재는 "재등록 = un-archive" 로만 복원 가능. 명시적
RestoreNodeRPC 는 없음 (필요 시 추후 추가) -
Audit trail: 누가 언제 archive 했는지는 기록되지 않음. 필요 시
archived_at외에archived_by같은 컬럼 추가 가능 - Archive 전 running jobs: 현재는 무조건 archive. 실행 중인 작업이 있는 노드도 즉시 삭제됨. 실제 운영에서는 draining 과정이 필요할 수 있음 (후속 작업 대상)
JL James Lee이(가) 7달 전에 변경 Actions #4
DeleteNode 핸들러를 전역 JWT 인터셉터로 전환¶
commit:server|b2c3d29
#375 에서 도입한 AuthLayer 전역 미들웨어가 Bearer JWT 를 검증하고 AuthContext 를 request extensions 에 주입하도록 변경되면서, DeleteNode 는 더 이상 핸들러 진입 시 JWT 를 재검증하지 않습니다.
변경 전 → 변경 후¶
// BEFORE
let caller = require_auth(&request, &self.state.cfg.jwt_secret)?;
// AFTER — O(1) extensions 조회
let caller = caller_wallet(&request)?;
owner 체크 (JWT sub vs nodes.owner_wallet_address, case-insensitive) 와 soft-delete (archived_at) 로직은 그대로.
회귀 검증¶
examples/node_delete.rs 7개 시나리오 전부 통과 — 특히:
- stranger JWT 로 타인의 node 삭제 시도 →
PermissionDenied: caller does not own this node - 토큰 없음 →
Unauthenticated: missing authorization header(이 메시지는 이제 미들웨어에서 반환) - owner soft-delete →
deleted=true,GetNode는NotFound,ListNodes는 제외 - re-register 시
archived_at=NULL로 복구
관련: #375
Actions