프로젝트

일반

사용자정보

Actions

이슈 #375

진행중
JL JL

JWT 전역 인터셉터 구현 (alphea-ai-edge)

이슈 #375: JWT 전역 인터셉터 구현 (alphea-ai-edge)

James Lee이(가) 약 7시간 전에 추가함.

상태:
해결
우선순위:
높음
담당자:
목표버전:
시작일:
2026/04/15
완료일:
진척도:

0%

추정시간:

설명

배경

alphea-ai-edge (Rust + tonic gRPC 포팅 프로젝트) 에서 현재 JWT 인증은 핸들러별 명시적 체크 방식을 사용합니다. 각 보호 RPC 가 domain::auth::require_auth(&request, &self.state.cfg.jwt_secret) 를 직접 호출합니다 (#313, #314 에서 사용됨).

이 방식은 보호 RPC 가 1~2개일 때는 단순해서 좋지만, 다음 조건이 만족되면 전역 Tower Layer 인터셉터로 전환할 필요가 있습니다:

  • 보호 RPC 가 5개 이상으로 늘어날 때
  • 여러 서비스에 걸쳐 일관된 인증 정책이 필요할 때
  • Access Log, 메트릭 같은 다른 전역 미들웨어와 체이닝이 필요할 때

레거시 Go 프로젝트 (alphea-rpc-next) 는 global AuthInterceptor + allow-list 방식을 사용했으며, 이 이슈는 그 패턴을 Rust 로 포팅합니다.

현재 상태 (2026-04-15 기준)

보호 RPC 목록

서비스 RPC 현재 인증 방식
NodeService DeleteNode require_auth + owner 체크 (handler/node.rs)
WalletService GetReceipts resolve_wallet (wraps require_auth)
WalletService GetEarningsSummary resolve_wallet

공개 RPC 목록

서비스 RPC
SystemService Health, Info
AuthService Challenge, Verify, GenerateKeypair
NodeService ListNodes, GetNode, NodeChallenge, RegisterNode

인증 헬퍼 (현재)

// src/domain/auth/mod.rs
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)
}

목표

기능 요구사항

  1. 전역 적용: 모든 gRPC 요청이 자동으로 인증 레이어를 통과
  2. Allow-list 기반 예외: 공개 RPC 는 인증 없이 통과
  3. 컨텍스트 주입: 인증 성공 시 호출자 정보 (wallet, token_type, scope) 를 request.extensions() 에 주입
  4. 핸들러 단순화: 보호 RPC 는 extensions 에서 호출자 읽기만 하면 되도록
  5. Token type 검증: 특정 RPC 가 node 토큰을 요구할 수 있도록 (ConnectNode 용)
  6. 에러 응답 표준화: 모든 auth 실패는 Status::unauthenticated 또는 Status::permission_denied 로 통일

비기능 요구사항

  • 성능: public RPC 는 추가 지연 없음, 보호 RPC 는 JWT 검증 한 번만
  • 관찰성: 인증 실패/성공 이벤트를 tracing 스팬으로 기록
  • 테스트 가능성: 인터셉터 로직 단위 테스트 가능 (Tower Service mock)
  • 기존 코드 호환: 마이그레이션 기간 동안 기존 require_auth 와 공존 가능

구현 방식

아키텍처 선택: Tower Layer + Service

tonic 의 두 가지 미들웨어 옵션 중:

방식 URI 접근 비동기 추천
tonic::service::Interceptor ❌ (메타데이터만) ❌ 간단한 헤더 변형만
Tower Layer + Service ✅ (HTTP path) ✅ 이 작업에 필수

URI path (/alphea.v1.NodeService/DeleteNode) 를 allow-list 와 매칭해야 하므로 Tower Layer 방식이 필수입니다.

모듈 구조

새 파일 src/server/auth_middleware.rs:

use std::collections::HashSet;
use std::pin::Pin;
use std::sync::Arc;
use std::task::{Context, Poll};

use http::{Request as HttpRequest, Response as HttpResponse};
use tonic::body::BoxBody;
use tonic::{Code, Status};
use tower::{Layer, Service};
use tracing::{debug, warn};

use crate::domain::auth::{JwtPayload, verify_access_token};

/// Information about the authenticated caller, injected into request
/// extensions so handlers can read it without re-validating the token.
#[derive(Debug, Clone)]
pub struct AuthContext {
    pub wallet: String,
    pub token_type: String,    // "app" or "node"
    pub scopes: Vec<String>,
}

#[derive(Clone)]
pub struct AuthLayer {
    jwt_secret: Arc<String>,
    public_paths: Arc<HashSet<&'static str>>,
}

impl AuthLayer {
    pub fn new(jwt_secret: String) -> Self {
        Self {
            jwt_secret: Arc::new(jwt_secret),
            public_paths: Arc::new(public_path_set()),
        }
    }
}

impl<S> Layer<S> for AuthLayer {
    type Service = AuthMiddleware<S>;
    fn layer(&self, inner: S) -> Self::Service {
        AuthMiddleware {
            inner,
            jwt_secret: self.jwt_secret.clone(),
            public_paths: self.public_paths.clone(),
        }
    }
}

#[derive(Clone)]
pub struct AuthMiddleware<S> {
    inner: S,
    jwt_secret: Arc<String>,
    public_paths: Arc<HashSet<&'static str>>,
}

type BoxFuture<T> = Pin<Box<dyn std::future::Future<Output = T> + Send>>;

impl<S, B> Service<HttpRequest<B>> for AuthMiddleware<S>
where
    S: Service<HttpRequest<B>, Response = HttpResponse<BoxBody>> + Clone + Send + 'static,
    S::Future: Send + 'static,
    B: Send + 'static,
{
    type Response = S::Response;
    type Error = S::Error;
    type Future = BoxFuture<Result<Self::Response, Self::Error>>;

    fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
        self.inner.poll_ready(cx)
    }

    fn call(&mut self, mut req: HttpRequest<B>) -> Self::Future {
        let path = req.uri().path().to_string();
        let is_public = self.public_paths.contains(path.as_str());

        if is_public {
            // Fast path — no token check.
            let fut = self.inner.call(req);
            return Box::pin(fut);
        }

        // Protected path — validate JWT before forwarding.
        let auth_header = req
            .headers()
            .get("authorization")
            .and_then(|v| v.to_str().ok())
            .and_then(|v| v.strip_prefix("Bearer "))
            .map(str::to_string);

        let token = match auth_header {
            Some(t) => t,
            None => {
                debug!(%path, "auth middleware: missing bearer token");
                return Box::pin(async move {
                    Ok(status_to_http_response(Status::unauthenticated(
                        "missing authorization header",
                    )))
                });
            }
        };

        let payload = match verify_access_token(&token, &self.jwt_secret) {
            Ok(p) => p,
            Err(err) => {
                debug!(%path, %err, "auth middleware: invalid token");
                return Box::pin(async move {
                    Ok(status_to_http_response(Status::unauthenticated("invalid token")))
                });
            }
        };

        // Inject AuthContext so handlers can read the caller.
        req.extensions_mut().insert(AuthContext {
            wallet: payload.sub.clone(),
            token_type: payload.typ.clone(),
            scopes: payload.scope.clone(),
        });

        let fut = self.inner.call(req);
        Box::pin(fut)
    }
}

fn status_to_http_response(status: Status) -> HttpResponse<BoxBody> {
    // tonic provides Status::to_http() in 0.13; falls back to manual construction.
    status.to_http()
}

Allow-list 구성

fn public_path_set() -> HashSet<&'static str> {
    [
        // System
        "/alphea.v1.SystemService/Health",
        "/alphea.v1.SystemService/Info",
        // Auth (all public — entry points to the authenticated world)
        "/alphea.v1.AuthService/Challenge",
        "/alphea.v1.AuthService/Verify",
        "/alphea.v1.AuthService/GenerateKeypair",
        // Node public reads + registration
        "/alphea.v1.NodeService/ListNodes",
        "/alphea.v1.NodeService/GetNode",
        "/alphea.v1.NodeService/NodeChallenge",
        "/alphea.v1.NodeService/RegisterNode",
        // User (public lookup)
        "/alphea.v1.UserService/GetUser",  // ← 포팅 후 추가
        // gRPC reflection
        "/grpc.reflection.v1.ServerReflection/ServerReflectionInfo",
        "/grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo",
    ]
    .into_iter()
    .collect()
}

정책: "default deny" — allow-list 에 없으면 인증 필요. 새 RPC 추가 시 명시적으로 등록해야 공개됨 → 실수로 보호 해제되는 것을 원천 차단.

핸들러에서 읽기

새 헬퍼 src/domain/auth/mod.rs:

use crate::server::auth_middleware::AuthContext;

pub fn caller_from<T>(req: &Request<T>) -> Result<&AuthContext, Status> {
    req.extensions()
        .get::<AuthContext>()
        .ok_or_else(|| Status::unauthenticated("missing auth context"))
}

pub fn caller_wallet<T>(req: &Request<T>) -> Result<String, Status> {
    Ok(caller_from(req)?.wallet.clone())
}

핸들러 예시 (Before / After):

// BEFORE — 현재 DeleteNode
async fn delete_node(&self, request: Request<DeleteNodeRequest>) -> ... {
    let caller = require_auth(&request, &self.state.cfg.jwt_secret)?;
    // ...
}

// AFTER — 전역 인터셉터 적용
async fn delete_node(&self, request: Request<DeleteNodeRequest>) -> ... {
    let caller = caller_wallet(&request)?;  // O(1) extensions lookup
    // ...
}

서버 등록

src/server.rs:

Server::builder()
    .layer(AuthLayer::new(cfg.jwt_secret.clone()))  // ← 추가
    .add_service(SystemServiceServer::new(...))
    .add_service(AuthServiceServer::new(...))
    // ...
    .serve(addr)
    .await?;

Server::layer() 는 모든 등록된 서비스에 공통 적용되며, 서비스별 개별 레이어도 가능.

Node token 특수 처리

레거시는 NodeService.ConnectNode 에서 typ == "node" 토큰을 추가로 요구합니다. bidi streaming 이라 핸들러 내부에서 직접 검증하는 패턴이 유지될 수 있으나, 전역 인터셉터에 token type 요구사항 을 메타데이터로 추가할 수도 있습니다:

struct AuthRequirement {
    path: &'static str,
    required_token_type: Option<&'static str>,  // None = any type
}

// ConnectNode → required_token_type: Some("node")
// 나머지 보호 RPC → required_token_type: None (app 또는 node 모두 허용)

초기 구현에서는 단순 allow-list 만 구현하고, token type 강제는 ConnectNode 포팅 시 별도 이슈로 처리 권장.

마이그레이션 전략

단계별 전환 (기존 핸들러와 공존):

Phase 1 — 레이어 도입 + 기존 유지

  1. AuthLayer 추가, 모든 서비스에 적용
  2. 기존 require_auth 호출하는 핸들러도 그대로 유지
  3. 이유: AuthLayer 가 token 검증을 통과시켜도 require_auth 는 한 번 더 검증할 뿐 — 에러 경로가 중복되지만 기능적 문제 없음
  4. 이 단계에서 allow-list 만 맞추면 public RPC 는 정상 동작, 보호 RPC 는 이중 검증 됨

Phase 2 — 핸들러 전환

  1. 보호 RPC 하나씩 require_auth → caller_wallet(&request) 로 교체
  2. RPC 단위로 점진적 이행 → 각 교체마다 E2E 테스트
  3. 교체 완료: DeleteNode, GetReceipts, GetEarningsSummary

Phase 3 — 정리

  1. require_auth, resolve_wallet 의 JWT 검증 부분을 제거 (AuthContext 기반으로 재작성)
  2. resolve_wallet → caller_must_match(request, requested_wallet) 로 간소화
  3. 문서 업데이트 (CLAUDE.md, wallet handler 주석)

테스트 방안

단위 테스트 (src/server/auth_middleware.rs)

  • Mock inner service — tower::service_fn 으로 echo 서비스 구성
  • 시나리오:
    • public path + 토큰 없음 → 200 (통과)
    • public path + 유효 토큰 → 200 (통과, AuthContext 주입은 선택)
    • 보호 path + 토큰 없음 → gRPC Status Unauthenticated
    • 보호 path + 만료 토큰 → Unauthenticated
    • 보호 path + 잘못된 서명 → Unauthenticated
    • 보호 path + 유효 토큰 → inner service 로 전달 + extensions 에 AuthContext 주입 확인

E2E 테스트

기존 examples/ 스크립트 들을 그대로 통과해야 함:

  • auth_roundtrip.rs
  • node_register.rs
  • node_delete.rs (핵심 — 인증 경로 변경 후에도 동일 결과)
  • wallet_earnings.rs

추가 시나리오:

  • tests/auth_middleware_integration.rs — 전역 인터셉터 적용 서버에 대한 public/protected 호출 혼합 검증

회귀 방지

  • cargo test 에 단위 테스트 포함
  • cargo clippy -- -D warnings 로 경고 차단
  • CI 파이프라인 추가 (별도 이슈)

완료 기준 (Definition of Done)

  • src/server/auth_middleware.rs 구현 (AuthLayer, AuthMiddleware, AuthContext)
  • Allow-list 정의 (현재 6개 공개 RPC + reflection)
  • src/server.rs 에 .layer(AuthLayer::new(...)) 등록
  • domain::auth::caller_from / caller_wallet 헬퍼 추가
  • 단위 테스트 5개 이상 (시나리오별)
  • 기존 DeleteNode, GetReceipts, GetEarningsSummary 핸들러를 새 헬퍼로 전환
  • 기존 examples/*.rs 전부 통과 (회귀 없음)
  • CLAUDE.md 에 새 인증 패턴 문서화
  • 향후 보호 RPC 추가 시 참고할 수 있도록 한 줄 가이드

리스크 및 주의사항

  1. tonic-reflection public path: gRPC reflection 엔드포인트를 allow-list 에 빠뜨리면 grpcurl 이 인증 실패. 현재 이미 public 이지만 명시적 등록 필요.
  2. Status::to_http() API 안정성: tonic 0.13 API. 향후 업그레이드 시 체크.
  3. Tower body type: tonic::body::BoxBody 와 http::Body trait bound 가 까다로워 제네릭 Service 구현에 시간 소요 가능. 초기에 axum::body::Body 와 혼용 금지.
  4. AuthContext extensions 타입 충돌: 향후 다른 미들웨어 (Access Log, Rate Limit) 가 같은 extensions 를 사용할 때 타입 구분 필요.
  5. 성능: public path 체크가 매 요청마다 실행됨 → HashSet<&'static str> 은 O(1) 이므로 문제 없음.

관련 이슈

  • #256 사용자 인증 API (JWT) — 이번 이슈의 선행 작업. JWT 발급/검증 로직 구현됨
  • #313 노드 삭제 API — 현재 핸들러별 require_auth 사용 중. 전환 대상
  • #314 ALP 수익 계산 API — GetReceipts, GetEarningsSummary 가 resolve_wallet 사용 중. 전환 대상
  • #351 API 보안 점검 (JWT. Rate Limit) — Rate Limit 미들웨어를 동일한 Tower Layer 패턴으로 추가 예정. 이 이슈에서 패턴을 확립하면 Rate Limit 도 일관된 방식으로 추가 가능

예상 작업량

  • Phase 1 (레이어 도입): 2~3 시간
  • Phase 2 (핸들러 전환): 1~2 시간
  • Phase 3 (정리 + 문서): 1 시간
  • 총: 4~6 시간 (단위 테스트 포함)

참고 코드 / 링크

  • 레거시 Go 구현: ~/Works/alphea-rpc-next/alphea-rpc/internal/server/interceptors.go (lines 110-188) — AuthInterceptor + public RPC list
  • tonic 0.13 Tower middleware 예제: https://github.com/hyperium/tonic/tree/master/examples/src/tower
  • Rust crate: tower, tower-http, http 는 이미 Cargo.lock 에 존재 (tonic 이 의존)

연결된 일감 7 (7 건 진행 중 — 모두 미완료)

다음 일감과 관련됨: 이슈 #256: 8-4. 사용자 인증 API (JWT)해결James Lee2026/04/282026/04/30

Actions
다음 일감과 관련됨: 이슈 #313: 17-4. 노드 삭제 API해결James Lee2026/05/192026/05/22

Actions
다음 일감과 관련됨: 이슈 #314: 17-5. ALP 수익 계산 API해결James Lee2026/05/192026/05/23

Actions
다음 일감과 관련됨: 이슈 #432: Engine + ConnectNode 포팅 — Synth / Dispatcher / Scheduler / VNodeCreditor + bidi 스트림 완성해결James Lee2026/04/172026/04/17

Actions
다음 일감과 관련됨: 이슈 #434: [Go backport] Supabase GoTrue 인증 전환 — Phase 1: 인프라 + 인증 코어해결Henry Park2026/05/11

Actions
다음에 진행할 일감: 이슈 #351: 25-2. API 보안 점검 (JWT. Rate Limit)신규James Lee2026/04/162026/04/16

Actions
다음에 진행할 일감: 이슈 #379: NodeService.Heartbeat 구현 (alphea-ai-edge)해결James Lee2026/04/16

Actions

JL James Lee이(가) 7달 전에 변경 Actions #1

  • 다음 일감과 관련됨:에 이슈 #256: 8-4. 사용자 인증 API (JWT)이(가) 추가되었습니다.

JL James Lee이(가) 7달 전에 변경 Actions #2

  • 다음 일감과 관련됨:에 이슈 #313: 17-4. 노드 삭제 API이(가) 추가되었습니다.

JL James Lee이(가) 7달 전에 변경 Actions #3

  • 다음 일감과 관련됨:에 이슈 #314: 17-5. ALP 수익 계산 API이(가) 추가되었습니다.

JL James Lee이(가) 7달 전에 변경 Actions #4

  • 다음에 진행할 일감:에 이슈 #351: 25-2. API 보안 점검 (JWT. Rate Limit)이(가) 추가되었습니다.

JL James Lee이(가) 7달 전에 변경 Actions #5

  • 상태을(를) 신규에서 진행(으)로 변경되었습니다.

Phase 1 + Phase 2 완료 (전역 레이어 도입 + 기존 보호 핸들러 전환)

commit:server|b2c3d29

변경 내용

  • src/server/auth_middleware.rs (신규) — AuthLayer(Tower Layer) + AuthMiddleware(Service) + AuthContext(request extension)
    • HTTP URI path 기준 allow-list 매칭 → default deny 정책
    • Bearer JWT 검증 후 AuthContext { wallet, token_type, scopes } 를 http::Request::extensions_mut() 에 주입 (tonic 의 Request::from_http_parts 가 extensions 를 그대로 보존)
    • 공개 경로: SystemService/{Health,Info}, AuthService/{Challenge,Verify,GenerateKeypair}, NodeService/{ListNodes,GetNode,NodeChallenge,RegisterNode}, gRPC reflection (v1 + v1alpha)
  • src/server/mod.rs — Server::builder().layer(AuthLayer::new(state.cfg.jwt_secret.clone())) 등록 (이 때문에 src/server.rs → src/server/mod.rs 구조 전환)
  • src/domain/auth/mod.rs — caller(req) / caller_wallet(req) 헬퍼 추가 (O(1) extensions 조회). 기존 require_auth 는 deprecated 표시 후 유지 (#351 Rate Limit 도입 시 일괄 제거 예정)
  • 핸들러 전환: NodeService::DeleteNode (#313), WalletService::{GetReceipts, GetEarningsSummary} (#314) — JWT 재검증 없이 caller_wallet() 만 호출
  • Cargo.toml — tower, tower-layer, tower-service, http, http-body, pin-project-lite direct dep 추가 (기존 transitive 를 명시화)
  • src/lib.rs — #![allow(clippy::result_large_err)] 추가 (tonic::Status 176 bytes 로 크레이트 전반에서 트리거되는 것을 한 곳에서 silence)

검증

  • cargo test — 36개 통과 (AuthMiddleware 단위 테스트 6개 포함)
    • public path bypass
    • 보호 path 토큰 없음 → Unauthenticated
    • 보호 path 잘못된 토큰 포맷 → Unauthenticated
    • 다른 secret 으로 서명된 토큰 → Unauthenticated
    • 유효 토큰 → AuthContext 주입 확인 (wallet, token_type="app", scopes 포함)
    • allow-list 정합성 (공개 RPC 는 포함, 보호 RPC 는 미포함)
  • cargo clippy --all-targets -- -D warnings — 경고 0
  • 실서버 E2E 4종 통과: auth_roundtrip, node_register, node_delete (stranger/unauth 차단 재확인), wallet_earnings (stranger/unauth 차단 재확인)

DoD 체크리스트

  • src/server/auth_middleware.rs (AuthLayer, AuthMiddleware, AuthContext)
  • Allow-list 정의 (현재 9개 공개 RPC + reflection 2종)
  • Server::builder().layer(AuthLayer::new(...)) 등록
  • caller / caller_wallet 헬퍼
  • 단위 테스트 6개 (시나리오별)
  • DeleteNode, GetReceipts, GetEarningsSummary 를 새 헬퍼로 전환
  • 기존 examples/*.rs 전부 회귀 없음 통과
  • Phase 3 — require_auth 제거 : 새 보호 RPC (#351 Rate Limit, #355 등) 가 추가되어 이중검증 경로가 많아질 때 일괄 제거. 현재는 deprecated 표시로만 남겨 두고 이 이슈는 진행 상태 유지.

Node token 특수 처리

레거시의 ConnectNode 가 typ == "node" 토큰을 요구하는 부분은 이번 범위에서 제외. ConnectNode 포팅 시 별도 이슈에서 per-path required_token_type 확장 예정.

JL James Lee이(가) 7달 전에 변경 Actions #6

  • 상태을(를) 진행에서 해결(으)로 변경되었습니다.

DoD 완료 — 해결 처리

commit:server|e8e2cd5

CLAUDE.md 에 "gRPC Auth Pattern" 섹션을 추가하여 DoD 의 남은 2개 항목을 충족.

추가된 문서 내용

  • 전역 AuthLayer 가 JWT 를 처리하고 AuthContext 를 주입한다는 정책 명시
  • Allow-list 위치 (public_path_set()) 와 Caller 헬퍼 (caller, caller_wallet) 위치 안내
  • 한 줄 가이드 — 새 RPC 추가 시:
    • 공개 RPC → public_path_set() 에 path 한 줄 추가, 핸들러 인증 코드 불필요
    • 보호 RPC → let caller = caller_wallet(&request)?; 만 호출
    • 소유권 체크 → caller_wallet 결과와 리소스 owner 필드 eq_ignore_ascii_case 비교

최종 DoD 체크리스트

  • src/server/auth_middleware.rs 구현 (AuthLayer, AuthMiddleware, AuthContext)
  • Allow-list 정의 (9개 공개 RPC + reflection 2종)
  • .layer(AuthLayer::new(...)) 등록
  • caller / caller_wallet 헬퍼
  • 단위 테스트 6개
  • DeleteNode, GetReceipts, GetEarningsSummary 핸들러 전환
  • examples/*.rs 전부 회귀 없음 통과
  • CLAUDE.md 에 새 인증 패턴 문서화
  • 향후 보호 RPC 추가 시 참고할 한 줄 가이드

후속 작업 (이 이슈 범위 외)

  • Phase 3 — require_auth 제거: Migration Strategy 에 계획된 정리 단계. 보호 RPC 가 더 추가된 시점(#351 Rate Limit 또는 신규 보호 RPC 포팅)에 별도 이슈로 일괄 처리.
  • Token type 강제 (node vs app): ConnectNode 포팅 시 별도 이슈에서 required_token_type 확장.

JL James Lee이(가) 7달 전에 변경 Actions #7

  • 다음에 진행할 일감:에 이슈 #379: NodeService.Heartbeat 구현 (alphea-ai-edge)이(가) 추가되었습니다.

JL James Lee이(가) 6달 전에 변경 Actions #8

  • 다음 일감과 관련됨:에 이슈 #432: Engine + ConnectNode 포팅 — Synth / Dispatcher / Scheduler / VNodeCreditor + bidi 스트림 완성이(가) 추가되었습니다.

JL James Lee이(가) 6달 전에 변경 Actions #9

  • 다음 일감과 관련됨:에 이슈 #434: [Go backport] Supabase GoTrue 인증 전환 — Phase 1: 인프라 + 인증 코어이(가) 추가되었습니다.

JL James Lee이(가) 6달 전에 변경 Actions #10

Go 원본 인터셉터 와 GoTrue 토큰 호환

commit:server|03c6c64 (이슈 #434)

본 이슈는 Rust 측의 Tower Layer 포팅이었고, Go 원본에는 이미 internal/server/interceptors.go 에 AuthInterceptor 와 allow-list 가 존재했다. 이번 Phase 1 백포트에서 다음을 업데이트:

  • 토큰 컨텍스트를 단일 스트링(wallet_address) 에서 구조체 AuthContext{UserID, NodeID, Email, TokenType, Wallet} 로 확장
  • verifyFn(token) (wallet string, err error) 시그니쳐를 VerifyFunc func(token) (*AuthContext, error) 로 교체
  • UserIDFromContext / NodeIDFromContext / AuthFromContext 헬퍼 도입, 기존 WalletFromContext 는 hose backward-compat 로 남겨두고 deprecated 표시
  • public allow-list 에서 제거된 Auth RPC 항목 제거: Challenge / Verify / Login / SetPassword / EmailRegister / EmailRecover

GoTrue 호환 검증

internal/domain/auth/jwt_test.go 에 GoTrue 답식 의 user 토큰(sub=UUID, email, aud, role, no typ) 이 통과하고 TokenType()="user" 로 인식되는지 확인하는 단위 테스트 추가.

Actions

내보내기 PDF Atom