Volver al inicio

Rutas dinámicas net/http: implementación de DeadDrop

El artículo analiza la implementación de rutas dinámicas y autenticación con cookies en net/http puro para el servicio DeadDrop. Parseo manual de rutas, tokens HMAC, arquitectura de casos de uso. Ciclo completo con pruebas.

net/http: rutas dinámicas y autenticación con cookies en DeadDrop
Advertisement 728x90

Rutas Dinámicas y Autenticación con Cookies en net/http: Implementando DeadDrop

Continuando nuestra serie sobre net/http puro, implementamos enrutamiento dinámico para el servicio DeadDrop. Los usuarios podrán ver secretos a través de enlaces como /secret/{id}. El mux estándar no admite parámetros de ruta hasta Go 1.22, así que escribimos nuestro propio analizador.

El manejador se registra en /secret/ con una barra diagonal final — esto captura todas las rutas hijas. Extraemos el ID manualmente de r.URL.Path.

func extractIDFromPath(path string) (string, bool) {
    const prefix = "/secret/"

    if !strings.HasPrefix(path, prefix) {
        return "", false
    }

    id := strings.TrimPrefix(path, prefix)
    if id == "" || strings.Contains(id, "/") {
        return "", false
    }

    return id, true
}

La función verifica el prefijo, lo recorta y excluye IDs inválidos (vacíos o que contienen /). Esto proporciona protección básica contra solicitudes malformadas.

Google AdInline article slot

SecretHandler con Inyección de Dependencias

Creamos una estructura de manejador para inyección de dependencias:

type SecretHandler struct {
    // marcador de posición para DI
}

func NewSecretHandler() *SecretHandler {
    return &SecretHandler{}
}

func (h *SecretHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    id, ok := url.ExtractIDFromPath(r.URL.Path)
    if !ok {
        http.NotFound(w, r)
        return
    }
    w.Header().Set("Content-Type", "text/plain; charset=utf-8")
    w.WriteHeader(http.StatusOK)
    w.Write([]byte("Secreto solicitado con ID: " + id))
}

Regístralo en router.go: mux.Handle("/secret/", secretHandler). Prueba casos límite:

  • /secret/12345/ → 404
  • /secret/ → 404
  • /secret/axQ/233 → 404
  • /secret/ad31H234 → OK con ID

Caso de Uso para Recuperar un Secreto

Define la solicitud y respuesta:

Google AdInline article slot
type GetSecretRequest struct {
    ID       string
    Password string
}

type GetSecretResponse struct {
    ID        string
    Message   string
    FileName  string
    FileExt   string
    HasFile   bool
    ExpiresAt time.Time
}

Lógica del caso de uso:

  • Recupera el secreto del almacenamiento
  • Verifica la expiración (time.After()), elimina los expirados
  • Valida la contraseña mediante CheckPasswordHash
  • Devuelve los datos o un error

Autenticación con Cookies

Para proteger las descargas de archivos, implementamos autenticación mediante cookies. Después de una verificación exitosa de la contraseña, establece una cookie de sesión con un token de acceso cifrado.

  • Ventajas: sin estado, escalable, sin almacenamiento de sesión
  • Parámetros de la cookie: HttpOnly, Secure, SameSite=Strict

Al solicitar /download/{id}:

Google AdInline article slot
  • Extrae el ID de la ruta
  • Lee la cookie auth_token
  • Decodifica y verifica el token (ID + hash de contraseña + nonce)
  • Si es válido — sirve el archivo

Estructura del Token de Acceso

El token se forma como base64(ID + ":" + hash + ":" + nonce), donde:

  • hash = HMAC-SHA256(ID + contraseña, claveSecreta)
  • nonce — un número de un solo uso para proteger contra ataques de repetición

Ejemplo de validación:

func validateDownloadToken(id, token string) bool {
    // decodifica el token
    // extrae el hash y el nonce
    // recalcula HMAC
    // compara
    return true // o false
}

Flujo Completo del Secreto

  • Creación: POST /create → genera ID, hashea contraseña, guarda
  • Visualización: GET /secret/{id} → formulario de entrada de contraseña
  • Autenticación: POST contraseña → cookie + redirección
  • Descarga: GET /download/{id} → verifica cookie → archivo
  • Limpieza: TTL expirado → eliminación

Prototipo de interfaz: página con campo de contraseña, botón "Abrir". Después del éxito — contenido + enlace al archivo.

Pruebas de Enrutamiento

| Ruta de Prueba | Expectativa | Resultado |

|-----------|-------------|--------|

| /secret/123/ | 404 | 404 |

| /secret/ | 404 | 404 |

| /secret/id/with/slash | 404 | 404 |

| /secret/valid123 | OK: "ID: valid123" | OK |

Los casos límite cubren la seguridad del analizador.

Puntos Clave

  • Rutas dinámicas: análisis manual de r.URL.Path sin enrutadores de terceros
  • Seguridad: validación de ID, verificaciones de barras diagonales, TTL para secretos
  • Autenticación: cookie con token HMAC en lugar de sesiones
  • Arquitectura: caso de uso + DI, lógica limpia fuera de los manejadores
  • Escalabilidad: sin estado, listo para agrupamiento

— Editorial Team

Advertisement 728x90

Leer después