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:
123
main.go
123
main.go
@@ -69,6 +69,35 @@ type store struct {
|
||||
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)} }
|
||||
|
||||
// persistSnapshot schreibt eine bereits kopierte Liste auf Disk.
|
||||
@@ -333,6 +362,100 @@ func main() {
|
||||
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)
|
||||
mux.HandleFunc("/search", func(w http.ResponseWriter, r *http.Request) {
|
||||
q := r.URL.Query().Get("q")
|
||||
|
||||
Reference in New Issue
Block a user