package logweb import ( "net/http" "strconv" "strings" "time" "xk-hy-transit-go/internal/fileauth" syncer "xk-hy-transit-go/internal/sync" ) // FileAuthGenerateResponse Web 上传凭证调试 API。 type FileAuthGenerateResponse struct { AccessKey string `json:"accessKey"` SecretSet bool `json:"secretSet"` Bucket string `json:"bucket"` Scope string `json:"scope"` B64Mode string `json:"b64Mode"` PolicyJSON string `json:"policyJson"` EncodedPutPolicy string `json:"encodedPutPolicy"` SignInput string `json:"signInput"` EncodedSign string `json:"encodedSign"` SignHex string `json:"signHex,omitempty"` UploadToken string `json:"uploadToken"` UploadTokenMasked string `json:"uploadTokenMasked"` DeadlineUnix int64 `json:"deadlineUnix"` ExpiresSec int64 `json:"expiresSec"` Algorithm string `json:"algorithm"` FormatOK bool `json:"formatOk"` FormatChecks []fileauth.FormatCheck `json:"formatChecks"` RunnerTokenMatch bool `json:"runnerTokenMatch"` RunnerTokenPreview string `json:"runnerTokenPreview"` } func (s *Server) handleAPIFileAuthGenerate(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet && r.Method != http.MethodPost { writeErr(w, 405, "GET or POST only") return } sr := asSyncRunner(s.deps.Runner) if sr == nil { writeErr(w, 503, "未配置 Runner(请使用 transit serve)") return } refresh := r.URL.Query().Get("refresh") == "1" || r.URL.Query().Get("refresh") == "true" if refresh { if err := sr.RegenerateUploadToken(); err != nil { writeErr(w, 502, "刷新 Runner uploadToken 失败: "+err.Error()) return } } snap := sr.ConfigSnapshot() accessKey := strings.TrimSpace(snap.EffectiveAppKey) secret := strings.TrimSpace(snap.EffectiveSecret) bucket := strings.TrimSpace(r.URL.Query().Get("bucket")) if bucket == "" { bucket = strings.TrimSpace(snap.EffectiveFileBucket) } if accessKey == "" || secret == "" { writeErr(w, 400, "生效 appKey/appSecret 未配置,请在 /config 重新拉取或配置 HY_APP_KEY/HY_APP_SECRET") return } if bucket == "" { writeErr(w, 400, "fileBucket(scope) 为空,请配置 HY_FILE_BUCKET") return } deadlineUnix := time.Now().Unix() + 3600 if ds := strings.TrimSpace(r.URL.Query().Get("deadline")); ds != "" { n, err := strconv.ParseInt(ds, 10, 64) if err != nil || n <= 0 { writeErr(w, 400, "invalid deadline (unix seconds)") return } deadlineUnix = n } parts, err := fileauth.BuildUploadTokenPartsForUpload(accessKey, secret, bucket, deadlineUnix) if err != nil { writeErr(w, 500, err.Error()) return } formatOK, checks := fileauth.ValidateUploadTokenFormat(parts.UploadToken, accessKey) runnerTok := strings.TrimSpace(sr.UploadToken()) runnerMatch := runnerTok != "" && runnerTok == parts.UploadToken writeJSON(w, FileAuthGenerateResponse{ AccessKey: parts.AccessKey, SecretSet: secret != "", Bucket: bucket, Scope: bucket, B64Mode: fileauth.B64Mode(), PolicyJSON: parts.PolicyJSON, EncodedPutPolicy: parts.EncodedPutPolicy, SignInput: parts.SignInput, EncodedSign: parts.EncodedSign, SignHex: fileauth.HMACSignHex(secret, parts.SignInput), UploadToken: parts.UploadToken, UploadTokenMasked: maskPreview(parts.UploadToken), DeadlineUnix: parts.DeadlineUnix, ExpiresSec: parts.ExpiresSec, Algorithm: parts.Algorithm, FormatOK: formatOK, FormatChecks: checks, RunnerTokenMatch: runnerMatch, RunnerTokenPreview: maskPreview(runnerTok), }) } // RunnerCredentialsForFileAuth 供测试或扩展:从快照读取凭证。 func RunnerCredentialsForFileAuth(snap syncer.ConfigSnapshot) (accessKey, secret, bucket string) { return strings.TrimSpace(snap.EffectiveAppKey), strings.TrimSpace(snap.EffectiveSecret), strings.TrimSpace(snap.EffectiveFileBucket) }