test-web / test (push) Successful in 2m4s
test-go / test (push) Successful in 2m23s
test-go / integration (push) Successful in 5m28s
release / Build signed APK (releases and dev) (push) Successful in 6m29s
release / Build + push container image (push) Successful in 29s
release / Verify release artifacts (tag releases only) (push) Skipped
users.api_token held each user's apiKey in plaintext and was looked up by equality, so a leaked row or backup handed out working keys. Migration 0063 replaces it with api_token_hash (sha256, hex), computed in place from the existing keys so every Subsonic client keeps working. The key can no longer be read back: GET /api/me/api-token is gone, and POST returns the new key once. Settings shows it right after Regenerate with a copy button and a "won't be shown again" note. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
46 lines
1.3 KiB
Go
46 lines
1.3 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/apierror"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/audit"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/auth"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
|
)
|
|
|
|
type apiTokenResp struct {
|
|
APIToken string `json:"api_token"`
|
|
}
|
|
|
|
// handleRegenerateMyAPIToken implements POST /api/me/api-token.
|
|
//
|
|
// Only the key's hash is stored, so this response is the one time the key
|
|
// can be read. There is deliberately no GET: a key that has been shown and
|
|
// lost is replaced, not looked up.
|
|
func (h *handlers) handleRegenerateMyAPIToken(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := requireUser(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
newToken, err := auth.MintSessionToken()
|
|
if err != nil {
|
|
h.logger.Error("regenerate api token: mint failed", "err", err)
|
|
writeErr(w, apierror.Internal(err))
|
|
return
|
|
}
|
|
q := dbq.New(h.pool)
|
|
if err := q.RegenerateApiToken(r.Context(), dbq.RegenerateApiTokenParams{
|
|
ID: user.ID,
|
|
ApiTokenHash: auth.HashAPIToken(newToken),
|
|
}); err != nil {
|
|
h.logger.Error("regenerate api token: update failed", "err", err)
|
|
writeErr(w, apierror.Internal(err))
|
|
return
|
|
}
|
|
|
|
audit.WriteOrLog(r.Context(), h.pool, h.logger, user.ID, user.ID, audit.ActionTokenRegenerate, nil)
|
|
|
|
writeJSON(w, http.StatusOK, apiTokenResp{APIToken: newToken})
|
|
}
|