added autoreport from Generator-App
All checks were successful
release-tag / release-image (push) Successful in 2m29s
All checks were successful
release-tag / release-image (push) Successful in 2m29s
This commit is contained in:
@@ -22,4 +22,5 @@ WORKDIR /app
|
|||||||
COPY --from=build /out/pgpdashboard /app/pgpdashboard
|
COPY --from=build /out/pgpdashboard /app/pgpdashboard
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
USER nonroot:nonroot
|
USER nonroot:nonroot
|
||||||
|
#KEYSERVER_API_TOKEN='supersecret'
|
||||||
ENTRYPOINT ["/app/pgpdashboard"]
|
ENTRYPOINT ["/app/pgpdashboard"]
|
||||||
|
|||||||
123
main.go
123
main.go
@@ -69,6 +69,35 @@ type store struct {
|
|||||||
byID map[string]KeyRecord
|
byID map[string]KeyRecord
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type apiUploadReq struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Fingerprint string `json:"fingerprint"` // optional, wird sonst aus Key geparsed
|
||||||
|
PublicArmored string `json:"public_armored"` // ASCII armored public key
|
||||||
|
Filename string `json:"filename"` // optional
|
||||||
|
}
|
||||||
|
|
||||||
|
type apiUploadResp struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Email string `json:"email"`
|
||||||
|
Fingerprint string `json:"fingerprint"`
|
||||||
|
WKDHash string `json:"wkd_hash"`
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Local string `json:"local"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func bearerOK(r *http.Request, token string) bool {
|
||||||
|
if token == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
h := r.Header.Get("Authorization")
|
||||||
|
const p = "Bearer "
|
||||||
|
if !strings.HasPrefix(h, p) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return strings.TrimSpace(strings.TrimPrefix(h, p)) == token
|
||||||
|
}
|
||||||
|
|
||||||
func newStore() *store { return &store{byID: make(map[string]KeyRecord)} }
|
func newStore() *store { return &store{byID: make(map[string]KeyRecord)} }
|
||||||
|
|
||||||
// persistSnapshot schreibt eine bereits kopierte Liste auf Disk.
|
// persistSnapshot schreibt eine bereits kopierte Liste auf Disk.
|
||||||
@@ -333,6 +362,100 @@ func main() {
|
|||||||
http.Error(w, err.Error(), 500)
|
http.Error(w, err.Error(), 500)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
apiToken := getenv("KEYSERVER_API_TOKEN", "")
|
||||||
|
mux.HandleFunc("/api/v1/keys", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !bearerOK(r, apiToken) {
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limit body size
|
||||||
|
r.Body = http.MaxBytesReader(w, r.Body, maxUploadSize)
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
var req apiUploadReq
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
|
http.Error(w, "invalid json", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Name = strings.TrimSpace(req.Name)
|
||||||
|
req.Email = strings.TrimSpace(req.Email)
|
||||||
|
req.Fingerprint = strings.TrimSpace(req.Fingerprint)
|
||||||
|
req.PublicArmored = strings.TrimSpace(req.PublicArmored)
|
||||||
|
|
||||||
|
if req.Email == "" || !strings.Contains(req.Email, "@") {
|
||||||
|
http.Error(w, "missing/invalid email", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.PublicArmored == "" || !strings.Contains(req.PublicArmored, "-----BEGIN PGP PUBLIC KEY BLOCK-----") {
|
||||||
|
http.Error(w, "public_armored must be ASCII-armored PGP public key", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := []byte(req.PublicArmored)
|
||||||
|
|
||||||
|
// Parse fingerprint (authoritative)
|
||||||
|
autoFPR, err := parseFingerprintFromASCII(b)
|
||||||
|
if err != nil || autoFPR == "" {
|
||||||
|
http.Error(w, "could not parse fingerprint from key", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fpr := strings.ToUpper(strings.ReplaceAll(autoFPR, " ", ""))
|
||||||
|
|
||||||
|
// Optional: wenn req.Fingerprint gesetzt ist, validieren wir nur (kein override)
|
||||||
|
if req.Fingerprint != "" && normalizeFPR(req.Fingerprint) != normalizeFPR(fpr) {
|
||||||
|
http.Error(w, "fingerprint mismatch", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// filename
|
||||||
|
fn := strings.TrimSpace(req.Filename)
|
||||||
|
if fn == "" {
|
||||||
|
fn = sanitizeFilename(req.Email)
|
||||||
|
} else {
|
||||||
|
fn = sanitizeFilename(fn)
|
||||||
|
}
|
||||||
|
path := filepath.Join(keysDir, fn)
|
||||||
|
|
||||||
|
if err := os.WriteFile(path, b, 0o644); err != nil {
|
||||||
|
http.Error(w, "save error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := KeyRecord{
|
||||||
|
ID: genID(req.Email, fpr),
|
||||||
|
Name: req.Name,
|
||||||
|
Email: req.Email,
|
||||||
|
Fingerprint: fpr,
|
||||||
|
Filename: fn,
|
||||||
|
CreatedAt: time.Now(),
|
||||||
|
}
|
||||||
|
if err := st.upsert(rec); err != nil {
|
||||||
|
http.Error(w, "index error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// WKD meta (wenn du die bereits korrigierte wkdHash(local-part) nutzt)
|
||||||
|
h, d, l := wkdHash(rec.Email) // falls deine Signatur noch (hash,domain) ist
|
||||||
|
// Wenn du die neue wkdHash(email) -> (hash,domain,local) nutzt, dann: h,d,_ := wkdHash(rec.Email)
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
_ = json.NewEncoder(w).Encode(apiUploadResp{
|
||||||
|
ID: rec.ID,
|
||||||
|
Email: rec.Email,
|
||||||
|
Fingerprint: rec.Fingerprint,
|
||||||
|
WKDHash: h,
|
||||||
|
Domain: d,
|
||||||
|
Local: l,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
// Live search (HTMX)
|
// Live search (HTMX)
|
||||||
mux.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
|
mux.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
|
||||||
q := r.URL.Query().Get("q")
|
q := r.URL.Query().Get("q")
|
||||||
|
|||||||
Reference in New Issue
Block a user