278 lines
8.0 KiB
Go
278 lines
8.0 KiB
Go
|
|
package api
|
|||
|
|
|
|||
|
|
import (
|
|||
|
|
"encoding/base64"
|
|||
|
|
"encoding/json"
|
|||
|
|
"fmt"
|
|||
|
|
"io"
|
|||
|
|
"log"
|
|||
|
|
"net/http"
|
|||
|
|
|
|||
|
|
"VerifactuMidAPI/internal"
|
|||
|
|
"VerifactuMidAPI/internal/cert"
|
|||
|
|
"VerifactuMidAPI/internal/config"
|
|||
|
|
"VerifactuMidAPI/internal/crypto"
|
|||
|
|
"VerifactuMidAPI/internal/formats"
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
type RegisterInput struct {
|
|||
|
|
CertName string `json:"cert_name"`
|
|||
|
|
CertNamePascal string `json:"CertName"`
|
|||
|
|
CertFile string `json:"cert_file"`
|
|||
|
|
CertFilePascal string `json:"CertFile"`
|
|||
|
|
PasswordEncrypted string `json:"password_encrypted"`
|
|||
|
|
PasswordPascal string `json:"PasswordEncrypted"`
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (r RegisterInput) CertNameResolved() string {
|
|||
|
|
if r.CertName != "" {
|
|||
|
|
return r.CertName
|
|||
|
|
}
|
|||
|
|
return r.CertNamePascal
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (r RegisterInput) CertFileResolved() string {
|
|||
|
|
if r.CertFile != "" {
|
|||
|
|
return r.CertFile
|
|||
|
|
}
|
|||
|
|
return r.CertFilePascal
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (r RegisterInput) PasswordResolved() string {
|
|||
|
|
if r.PasswordEncrypted != "" {
|
|||
|
|
return r.PasswordEncrypted
|
|||
|
|
}
|
|||
|
|
return r.PasswordPascal
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
type Handler struct {
|
|||
|
|
cfg *config.Config
|
|||
|
|
cert *cert.Storage
|
|||
|
|
crypto *crypto.KeyPair
|
|||
|
|
facturaSvc *internal.FacturaService
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func New(cfg *config.Config, certStorage *cert.Storage, keyPair *crypto.KeyPair, facturaSvc *internal.FacturaService) *Handler {
|
|||
|
|
return &Handler{
|
|||
|
|
cfg: cfg,
|
|||
|
|
cert: certStorage,
|
|||
|
|
crypto: keyPair,
|
|||
|
|
facturaSvc: facturaSvc,
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (h *Handler) GetPublicKey(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
if r.Method != http.MethodGet {
|
|||
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
pubPEM, err := h.crypto.PublicKeyPEM()
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, "failed to get public key", http.StatusInternalServerError)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write([]byte(fmt.Sprintf(`{"public_key":"%s"}`, base64.StdEncoding.EncodeToString(pubPEM))))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (h *Handler) RegisterCert(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
if r.Method != http.MethodPost {
|
|||
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body, err := io.ReadAll(r.Body)
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, "failed to read body", http.StatusBadRequest)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var input RegisterInput
|
|||
|
|
if err := json.Unmarshal(body, &input); err != nil {
|
|||
|
|
log.Printf("[RegisterCert] ERROR: JSON invalido: %v", err)
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write([]byte(`{"success":false,"error":"invalid_json"}`))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
certName := input.CertNameResolved()
|
|||
|
|
certFileB64 := input.CertFileResolved()
|
|||
|
|
passwordEnc := input.PasswordResolved()
|
|||
|
|
|
|||
|
|
log.Printf("[RegisterCert] Inicio: cert_name=%q, cert_file_len=%d, password_len=%d",
|
|||
|
|
certName, len(certFileB64), len(passwordEnc))
|
|||
|
|
|
|||
|
|
if certName == "" || certFileB64 == "" || passwordEnc == "" {
|
|||
|
|
log.Printf("[RegisterCert] ERROR: campos vacios")
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write([]byte(`{"success":false,"error":"missing_fields"}`))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
certBytes, err := base64.StdEncoding.DecodeString(certFileB64)
|
|||
|
|
if err != nil {
|
|||
|
|
log.Printf("[RegisterCert] ERROR: base64 del cert invalido: %v", err)
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write([]byte(`{"success":false,"error":"invalid_cert_file"}`))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
log.Printf("[RegisterCert] Certificado decodificado: %d bytes", len(certBytes))
|
|||
|
|
|
|||
|
|
decodedPass, err := base64.StdEncoding.DecodeString(passwordEnc)
|
|||
|
|
if err != nil {
|
|||
|
|
log.Printf("[RegisterCert] ERROR: base64 contrasena invalido: %v", err)
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write([]byte(`{"success":false,"error":"invalid_password_encrypted"}`))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
plainPassBytes, err := h.crypto.Decrypt(decodedPass)
|
|||
|
|
if err != nil {
|
|||
|
|
log.Printf("[RegisterCert] ERROR: descifrado RSA fallido: %v", err)
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write([]byte(`{"success":false,"error":"decrypt_failed"}`))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
log.Printf("[RegisterCert] Contrasena descifrada correctamente")
|
|||
|
|
|
|||
|
|
plainPass := string(plainPassBytes)
|
|||
|
|
|
|||
|
|
log.Printf("[RegisterCert] Validando certificado P12...")
|
|||
|
|
validation := cert.ValidateP12Bytes(certBytes, plainPass)
|
|||
|
|
if !validation.Valid {
|
|||
|
|
log.Printf("[RegisterCert] ERROR validacion: %s", validation.Error)
|
|||
|
|
resp, _ := json.Marshal(map[string]interface{}{
|
|||
|
|
"success": false,
|
|||
|
|
"error": validation.Error,
|
|||
|
|
"cert": validation.CertInfo,
|
|||
|
|
})
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write(resp)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
log.Printf("[RegisterCert] Certificado valido. Dias hasta expiracion: %d, warnings: %v",
|
|||
|
|
validation.CertInfo.DaysUntilExpiry, validation.Warnings)
|
|||
|
|
|
|||
|
|
tempPath, err := h.cert.StoreFromBase64(certName, certFileB64)
|
|||
|
|
if err != nil {
|
|||
|
|
log.Printf("[RegisterCert] ERROR StoreFromBase64: %v", err)
|
|||
|
|
h.cert.DeleteTemp(tempPath)
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write([]byte(`{"success":false,"error":"temp_storage_failed"}`))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
log.Printf("[RegisterCert] Certificado guardado en temporal: %s", tempPath)
|
|||
|
|
|
|||
|
|
storedPath, err := h.cert.MoveToPerm(certName, tempPath)
|
|||
|
|
if err != nil {
|
|||
|
|
log.Printf("[RegisterCert] ERROR MoveToPerm: %v", err)
|
|||
|
|
h.cert.DeleteTemp(tempPath)
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write([]byte(`{"success":false,"error":"storage_failed"}`))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
log.Printf("[RegisterCert] Certificado almacenado definitivamente: %s", storedPath)
|
|||
|
|
|
|||
|
|
tokenData, err := h.cert.GenerateToken(certName, storedPath, plainPass)
|
|||
|
|
if err != nil {
|
|||
|
|
log.Printf("[RegisterCert] ERROR GenerateToken: %v", err)
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write([]byte(`{"success":false,"error":"token_generation_failed"}`))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
log.Printf("[RegisterCert] Token generado: %s", tokenData.Token)
|
|||
|
|
|
|||
|
|
if err := h.facturaSvc.ReloadCertificate(storedPath, plainPass); err != nil {
|
|||
|
|
log.Printf("[RegisterCert] WARNING: no se pudo recargar el certificado en el cliente VeriFactu: %v", err)
|
|||
|
|
} else {
|
|||
|
|
log.Printf("[RegisterCert] Certificado recargado en el cliente VeriFactu")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if len(validation.Warnings) > 0 {
|
|||
|
|
log.Printf("[RegisterCert] OK con warnings: %v", validation.Warnings)
|
|||
|
|
resp, _ := json.Marshal(map[string]interface{}{
|
|||
|
|
"success": true,
|
|||
|
|
"cert": validation.CertInfo,
|
|||
|
|
"token": tokenData.Token,
|
|||
|
|
"warnings": validation.Warnings,
|
|||
|
|
})
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write(resp)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
log.Printf("[RegisterCert] OK: certificado %q registrado", certName)
|
|||
|
|
resp, _ := json.Marshal(map[string]interface{}{
|
|||
|
|
"success": true,
|
|||
|
|
"cert": validation.CertInfo,
|
|||
|
|
"token": tokenData.Token,
|
|||
|
|
})
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write(resp)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (h *Handler) HandleFacturas(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
if r.Method != http.MethodPost {
|
|||
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body, err := io.ReadAll(r.Body)
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, "failed to read body", http.StatusBadRequest)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
log.Printf("facturas request: %s", string(body))
|
|||
|
|
|
|||
|
|
output, _ := h.facturaSvc.ProcessAlta(body)
|
|||
|
|
|
|||
|
|
log.Printf("facturas output:%+v", output)
|
|||
|
|
|
|||
|
|
resp, _ := json.Marshal(output)
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write(resp)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (h *Handler) HandleFacturasAnular(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
if r.Method != http.MethodPost {
|
|||
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
body, err := io.ReadAll(r.Body)
|
|||
|
|
if err != nil {
|
|||
|
|
http.Error(w, "failed to read body", http.StatusBadRequest)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var input internal.AnulacionInput
|
|||
|
|
if err := json.Unmarshal(body, &input); err != nil {
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write([]byte(`{"success":false,"error":"invalid_json"}`))
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
output, _ := h.facturaSvc.ProcessAnulacion(input)
|
|||
|
|
|
|||
|
|
resp, _ := json.Marshal(output)
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write(resp)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func (h *Handler) ListFormats(w http.ResponseWriter, r *http.Request) {
|
|||
|
|
if r.Method != http.MethodGet {
|
|||
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
available := formats.Available()
|
|||
|
|
resp, _ := json.Marshal(map[string]interface{}{
|
|||
|
|
"formats": available,
|
|||
|
|
})
|
|||
|
|
w.Header().Set("Content-Type", "application/json")
|
|||
|
|
w.Write(resp)
|
|||
|
|
}
|