Check for valid callback source address
This commit is contained in:
@@ -40,6 +40,7 @@ Cache.TTL = 24h
|
|||||||
YooKassa.BaseUrl = https://api.yookassa.ru/v3
|
YooKassa.BaseUrl = https://api.yookassa.ru/v3
|
||||||
YooKassa.Timeout = 30s
|
YooKassa.Timeout = 30s
|
||||||
YooKassa.Test = false
|
YooKassa.Test = false
|
||||||
|
YooKassa.AllowedCallbackSubnets = 185.71.76.0/27,185.71.77.0/27,77.75.153.0/25,77.75.156.11/32,77.75.156.35/32,77.75.154.128/25,2a02:5180::/32
|
||||||
# Base API key/secret
|
# Base API key/secret
|
||||||
YooKassa.ApiBaseKey =
|
YooKassa.ApiBaseKey =
|
||||||
YooKassa.ApiBaseSecret =
|
YooKassa.ApiBaseSecret =
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
"payouts/internal/service/database"
|
"payouts/internal/service/database"
|
||||||
"payouts/internal/service/database/orm"
|
"payouts/internal/service/database/orm"
|
||||||
"payouts/internal/service/yookassa"
|
"payouts/internal/service/yookassa"
|
||||||
|
yookassaConf "payouts/internal/service/yookassa/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -30,6 +32,7 @@ type payoutHandler struct {
|
|||||||
dbService database.Service
|
dbService database.Service
|
||||||
cacheService cache.Service
|
cacheService cache.Service
|
||||||
yooKassa yookassa.Service
|
yooKassa yookassa.Service
|
||||||
|
yookassaConf yookassaConf.YooKassa
|
||||||
}
|
}
|
||||||
|
|
||||||
// Params represents the module input params
|
// Params represents the module input params
|
||||||
@@ -47,6 +50,7 @@ func NewPayoutHandler(p Params) (Handler, error) {
|
|||||||
dbService: p.DbService,
|
dbService: p.DbService,
|
||||||
cacheService: p.CacheService,
|
cacheService: p.CacheService,
|
||||||
yooKassa: p.YooKassa,
|
yooKassa: p.YooKassa,
|
||||||
|
yookassaConf: p.YooKassa.GetConfig(),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +74,27 @@ func (p *payoutHandler) getSession(r *http.Request) (*orm.User, error) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *payoutHandler) checkAllowedIpCallback(ipStr string) bool {
|
||||||
|
ipWithoutPort, _, _ := net.SplitHostPort(ipStr)
|
||||||
|
|
||||||
|
ip := net.ParseIP(ipWithoutPort)
|
||||||
|
if ip == nil {
|
||||||
|
slog.Error(fmt.Sprintf("Invalid IP: %s", ipStr))
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, subnetStr := range p.yookassaConf.AllowedCallbackSubnets {
|
||||||
|
_, ipNet, err := net.ParseCIDR(subnetStr)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error(fmt.Sprintf("Invalid subnet CIDR: %v", err))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if ipNet.Contains(ip) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
// GetSbpBanks implements [Handler].
|
// GetSbpBanks implements [Handler].
|
||||||
func (p *payoutHandler) GetSbpBanks(w http.ResponseWriter, r *http.Request) {
|
func (p *payoutHandler) GetSbpBanks(w http.ResponseWriter, r *http.Request) {
|
||||||
panic("unimplemented")
|
panic("unimplemented")
|
||||||
@@ -83,7 +108,7 @@ func (p *payoutHandler) PayoutCreate(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
_, err := p.getSession(r)
|
_, err := p.getSession(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errResponse("unautiorized", err, http.StatusUnauthorized)
|
errResponse("unauthorized", err, http.StatusUnauthorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
panic("unimplemented")
|
panic("unimplemented")
|
||||||
@@ -95,5 +120,12 @@ func (p *payoutHandler) PayoutCallback(w http.ResponseWriter, r *http.Request) {
|
|||||||
decoder := json.NewDecoder(r.Body)
|
decoder := json.NewDecoder(r.Body)
|
||||||
decoder.Decode(&inData)
|
decoder.Decode(&inData)
|
||||||
|
|
||||||
|
// todo: check also the X-real-ip and/or X-Forwarded-For
|
||||||
|
if !p.checkAllowedIpCallback(r.RemoteAddr) {
|
||||||
|
slog.Error(fmt.Sprintf("Callback came from unallowed ip: %s", r.RemoteAddr))
|
||||||
|
http.Error(w, "unallowed", http.StatusForbidden)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
slog.Info(fmt.Sprintf("Received callback from %s with object %v with headers %v", r.RemoteAddr, inData, r.Header))
|
slog.Info(fmt.Sprintf("Received callback from %s with object %v with headers %v", r.RemoteAddr, inData, r.Header))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ type YooKassa struct {
|
|||||||
Timeout time.Duration
|
Timeout time.Duration
|
||||||
Test bool
|
Test bool
|
||||||
|
|
||||||
|
AllowedCallbackSubnets []string
|
||||||
|
|
||||||
ApiBaseKey string
|
ApiBaseKey string
|
||||||
ApiBaseSecret string
|
ApiBaseSecret string
|
||||||
ApiPaymentKey string
|
ApiPaymentKey string
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"payouts/internal/config"
|
"payouts/internal/config"
|
||||||
"payouts/internal/models"
|
"payouts/internal/models"
|
||||||
"payouts/internal/service/database/orm"
|
"payouts/internal/service/database/orm"
|
||||||
|
yookassaConf "payouts/internal/service/yookassa/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Module = fx.Options(
|
var Module = fx.Options(
|
||||||
@@ -27,6 +28,7 @@ func WithContext(ctx context.Context) Optional {
|
|||||||
|
|
||||||
type Service interface {
|
type Service interface {
|
||||||
CreatePayout(models.PayoutReq, *orm.User, ...Optional)
|
CreatePayout(models.PayoutReq, *orm.User, ...Optional)
|
||||||
|
GetConfig() yookassaConf.YooKassa
|
||||||
}
|
}
|
||||||
|
|
||||||
type Param struct {
|
type Param struct {
|
||||||
|
|||||||
@@ -65,3 +65,8 @@ func (y *yookassaService) CreatePayout(req models.PayoutReq, userSession *orm.Us
|
|||||||
|
|
||||||
y.payClient.PayoutsPost(params.ctx, &gen.PayoutRequest{}, gen.PayoutsPostParams{})
|
y.payClient.PayoutsPost(params.ctx, &gen.PayoutRequest{}, gen.PayoutsPostParams{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetConfig implements [Service].
|
||||||
|
func (y *yookassaService) GetConfig() config.YooKassa {
|
||||||
|
return y.conf
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user