diff --git a/internal/certificate/service.go b/internal/certificate/service.go index 406e183c..a207d1c3 100644 --- a/internal/certificate/service.go +++ b/internal/certificate/service.go @@ -5,7 +5,7 @@ import ( "bytes" "context" "encoding/json" - "errors" + "fmt" "strconv" "strings" "time" @@ -59,7 +59,7 @@ func (s *CertificateService) InitSchedule(ctx context.Context) error { return nil } -func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) ([]byte, error) { +func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) (*dtos.CertificateArchiveFileResp, error) { certificate, err := s.certRepo.GetById(ctx, req.CertificateId) if err != nil { return nil, err @@ -69,6 +69,10 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific zipWriter := zip.NewWriter(&buf) defer zipWriter.Close() + resp := &dtos.CertificateArchiveFileResp{ + FileFormat: "zip", + } + switch strings.ToUpper(req.Format) { case "", "PEM": { @@ -97,7 +101,8 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific return nil, err } - return buf.Bytes(), nil + resp.FileBytes = buf.Bytes() + return resp, nil } case "PFX": @@ -134,7 +139,8 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific return nil, err } - return buf.Bytes(), nil + resp.FileBytes = buf.Bytes() + return resp, nil } case "JKS": @@ -171,7 +177,8 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific return nil, err } - return buf.Bytes(), nil + resp.FileBytes = buf.Bytes() + return resp, nil } default: @@ -180,25 +187,30 @@ func (s *CertificateService) ArchiveFile(ctx context.Context, req *dtos.Certific } func (s *CertificateService) ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error) { - info, err := certs.ParseCertificateFromPEM(req.Certificate) + certX509, err := certs.ParseCertificateFromPEM(req.Certificate) + if err != nil { + return nil, err + } else if time.Now().After(certX509.NotAfter) { + return nil, fmt.Errorf("certificate has expired at %s", certX509.NotAfter.UTC().Format(time.RFC3339)) + } + + return &dtos.CertificateValidateCertificateResp{ + IsValid: true, + Domains: strings.Join(certX509.DNSNames, ";"), + }, nil +} + +func (s *CertificateService) ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) (*dtos.CertificateValidatePrivateKeyResp, error) { + _, err := certcrypto.ParsePEMPrivateKey([]byte(req.PrivateKey)) if err != nil { return nil, err } - if time.Now().After(info.NotAfter) { - return nil, errors.New("证书已过期") - } - - return &dtos.CertificateValidateCertificateResp{ - Domains: strings.Join(info.DNSNames, ";"), + return &dtos.CertificateValidatePrivateKeyResp{ + IsValid: true, }, nil } -func (s *CertificateService) ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) error { - _, err := certcrypto.ParsePEMPrivateKey([]byte(req.PrivateKey)) - return err -} - func buildExpireSoonNotification(certificates []*domain.Certificate) *struct { Subject string Message string diff --git a/internal/domain/dtos/certificate.go b/internal/domain/dtos/certificate.go index cf9eb785..a1853df0 100644 --- a/internal/domain/dtos/certificate.go +++ b/internal/domain/dtos/certificate.go @@ -5,22 +5,24 @@ type CertificateArchiveFileReq struct { Format string `json:"format"` } +type CertificateArchiveFileResp struct { + FileBytes []byte `json:"fileBytes"` + FileFormat string `json:"fileFormat"` +} + type CertificateValidateCertificateReq struct { Certificate string `json:"certificate"` } type CertificateValidateCertificateResp struct { - Domains string `json:"domains"` + IsValid bool `json:"isValid"` + Domains string `json:"domains,omitempty"` } type CertificateValidatePrivateKeyReq struct { PrivateKey string `json:"privateKey"` } -type CertificateUploadReq struct { - WorkflowId string `json:"workflowId"` - WorkflowNodeId string `json:"workflowNodeId"` - CertificateId string `json:"certificateId"` - Certificate string `json:"certificate"` - PrivateKey string `json:"privateKey"` +type CertificateValidatePrivateKeyResp struct { + IsValid bool `json:"isValid"` } diff --git a/internal/rest/handlers/certificate.go b/internal/rest/handlers/certificate.go index ded0db94..01b2b06d 100644 --- a/internal/rest/handlers/certificate.go +++ b/internal/rest/handlers/certificate.go @@ -11,9 +11,9 @@ import ( ) type certificateService interface { - ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) ([]byte, error) + ArchiveFile(ctx context.Context, req *dtos.CertificateArchiveFileReq) (*dtos.CertificateArchiveFileResp, error) ValidateCertificate(ctx context.Context, req *dtos.CertificateValidateCertificateReq) (*dtos.CertificateValidateCertificateResp, error) - ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) error + ValidatePrivateKey(ctx context.Context, req *dtos.CertificateValidatePrivateKeyReq) (*dtos.CertificateValidatePrivateKeyResp, error) } type CertificateHandler struct { @@ -38,10 +38,10 @@ func (handler *CertificateHandler) archiveFile(e *core.RequestEvent) error { return resp.Err(e, err) } - if bt, err := handler.service.ArchiveFile(e.Request.Context(), req); err != nil { + if res, err := handler.service.ArchiveFile(e.Request.Context(), req); err != nil { return resp.Err(e, err) } else { - return resp.Ok(e, bt) + return resp.Ok(e, res) } } @@ -51,10 +51,10 @@ func (handler *CertificateHandler) validateCertificate(e *core.RequestEvent) err return resp.Err(e, err) } - if rs, err := handler.service.ValidateCertificate(e.Request.Context(), req); err != nil { + if res, err := handler.service.ValidateCertificate(e.Request.Context(), req); err != nil { return resp.Err(e, err) } else { - return resp.Ok(e, rs) + return resp.Ok(e, res) } } @@ -64,9 +64,9 @@ func (handler *CertificateHandler) validatePrivateKey(e *core.RequestEvent) erro return resp.Err(e, err) } - if err := handler.service.ValidatePrivateKey(e.Request.Context(), req); err != nil { + if res, err := handler.service.ValidatePrivateKey(e.Request.Context(), req); err != nil { return resp.Err(e, err) } else { - return resp.Ok(e, nil) + return resp.Ok(e, res) } } diff --git a/ui/src/api/certificates.ts b/ui/src/api/certificates.ts index 3c00fdcf..6a8935bb 100644 --- a/ui/src/api/certificates.ts +++ b/ui/src/api/certificates.ts @@ -3,10 +3,14 @@ import { ClientResponseError } from "pocketbase"; import { type CertificateFormatType } from "@/domain/certificate"; import { getPocketBase } from "@/repository/_pocketbase"; +type ArchiveRespData = { + fileBytes: string; +}; + export const archive = async (certificateId: string, format?: CertificateFormatType) => { const pb = getPocketBase(); - const resp = await pb.send>(`/api/certificates/${encodeURIComponent(certificateId)}/archive`, { + const resp = await pb.send>(`/api/certificates/${encodeURIComponent(certificateId)}/archive`, { method: "POST", headers: { "Content-Type": "application/json", @@ -24,6 +28,7 @@ export const archive = async (certificateId: string, format?: CertificateFormatT }; type ValidateCertificateResp = { + isValid: boolean; domains: string; }; @@ -46,9 +51,13 @@ export const validateCertificate = async (certificate: string) => { return resp; }; +type ValidatePrivateKeyResp = { + isValid: boolean; +}; + export const validatePrivateKey = async (privateKey: string) => { const pb = getPocketBase(); - const resp = await pb.send(`/api/certificates/validate/private-key`, { + const resp = await pb.send>(`/api/certificates/validate/private-key`, { method: "POST", headers: { "Content-Type": "application/json", diff --git a/ui/src/components/certificate/CertificateDetail.tsx b/ui/src/components/certificate/CertificateDetail.tsx index d57a9609..6c842a36 100644 --- a/ui/src/components/certificate/CertificateDetail.tsx +++ b/ui/src/components/certificate/CertificateDetail.tsx @@ -22,7 +22,7 @@ const CertificateDetail = ({ data, ...props }: CertificateDetailProps) => { const handleDownloadClick = async (format: CertificateFormatType) => { try { const res = await archiveCertificate(data.id, format); - const bstr = atob(res.data); + const bstr = atob(res.data.fileBytes); const u8arr = Uint8Array.from(bstr, (ch) => ch.charCodeAt(0)); const blob = new Blob([u8arr], { type: "application/zip" }); saveAs(blob, `${data.id}-${data.subjectAltNames}.zip`);