Organize backend into product executable and shared Go modules

This commit is contained in:
Stephan D committed 2026-09-12 14:14:08 +02:00
1 parent 981e84a4b4
commit f0720ec66b
382 files changed
+1944 -6989

No files matched your search

@@ -0,0 +1,103 @@
package tokens
import (
"context"
customermodel "git.sendico.io/tech/sxchnge/product/customers/model"
sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model"
"net/http"
"testing"
"git.sendico.io/tech/sxchnge/product/sessions/store"
"go.mongodb.org/mongo-driver/v2/bson"
"go.uber.org/zap"
)
type recordingRefreshTokenDB struct {
refreshtokens.DB
token *sessionmodel.RefreshToken
}
func (db *recordingRefreshTokenDB) Create(_ context.Context, token *sessionmodel.RefreshToken) error {
db.token = token
return nil
}
func TestPrepareRefreshTokenStoresResolvedClientIP(t *testing.T) {
tests := []struct {
name string
remoteAddr string
realIP string
want string
}{
{
name: "proxied IPv4",
remoteAddr: "172.19.0.37:45706",
realIP: "203.0.113.10",
want: "203.0.113.10",
},
{
name: "direct IPv4",
remoteAddr: "203.0.113.11:45706",
want: "203.0.113.11",
},
{
name: "proxied IPv6",
remoteAddr: "172.19.0.37:45706",
realIP: "2001:0db8:0000:0000:0000:0000:0000:0012",
want: "2001:db8::12",
},
{
name: "direct IPv6",
remoteAddr: "[2001:db8::13]:45706",
want: "2001:db8::13",
},
{
name: "missing address",
want: "",
},
{
name: "invalid address",
remoteAddr: "not-an-address",
realIP: "also-not-an-address",
want: "",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
request := &http.Request{
RemoteAddr: tt.remoteAddr,
Header: make(http.Header),
}
if tt.realIP != "" {
request.Header.Set("X-Real-IP", tt.realIP)
}
account := &customermodel.Account{}
account.SetID(bson.NewObjectID())
db := &recordingRefreshTokenDB{}
_, err := PrepareRefreshToken(
context.Background(),
request,
db,
32,
24,
&sessionmodel.SessionIdentifier{ClientID: "client", DeviceID: "device"},
account,
zap.NewNop(),
)
if err != nil {
t.Fatalf("prepare refresh token: %v", err)
}
if db.token == nil {
t.Fatal("expected refresh token to be stored")
}
if db.token.IPAddress != tt.want {
t.Fatalf("unexpected stored IP address: got %q, want %q", db.token.IPAddress, tt.want)
}
if db.token.SessionVersion == "" {
t.Fatal("expected a server-generated session version")
}
})
}
}
+78
View File
@@ -0,0 +1,78 @@
package tokens
import (
"context"
"crypto/rand"
"encoding/base64"
customermodel "git.sendico.io/tech/sxchnge/product/customers/model"
sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model"
"io"
"net/http"
"time"
"git.sendico.io/tech/sxchnge/product/auth/ipguard"
"git.sendico.io/tech/sxchnge/product/sessions/store"
"git.sendico.io/tech/sxchnge/shared/errors"
"git.sendico.io/tech/sxchnge/shared/logging"
"git.sendico.io/tech/sxchnge/shared/logging/fields"
"go.uber.org/zap"
)
func generateRefreshTokenData(length int) (string, error) {
randomBytes := make([]byte, length)
if _, err := io.ReadFull(rand.Reader, randomBytes); err != nil {
return "", merrors.InternalWrap(err, "failed to generate secure random bytes")
}
return base64.URLEncoding.EncodeToString(randomBytes), nil
}
func PrepareRefreshToken(
ctx context.Context,
r *http.Request,
rtdb refreshtokens.DB,
length int,
refreshExpiration int,
session *sessionmodel.SessionIdentifier,
account *customermodel.Account,
logger mlogger.Logger,
) (*sessionmodel.RefreshToken, error) {
refreshToken, err := generateRefreshTokenData(length)
if err != nil {
logger.Warn("Failed to generate refresh token", zap.Error(err), mzap.StorableRef(account))
return nil, err
}
sessionVersion, err := generateRefreshTokenData(16)
if err != nil {
logger.Warn("Failed to generate session version", zap.Error(err), mzap.StorableRef(account))
return nil, err
}
versionedSession := *session
versionedSession.SessionVersion = sessionVersion
clientIP := ipguard.ClientIP(r)
ipAddress := ""
if clientIP != nil {
ipAddress = clientIP.String()
}
token := &sessionmodel.RefreshToken{
AccountRef: account.GetID(),
ClientRefreshToken: sessionmodel.ClientRefreshToken{
SessionIdentifier: versionedSession,
RefreshToken: refreshToken,
},
ExpiresAt: time.Now().Add(time.Duration(refreshExpiration) * time.Hour),
IsRevoked: false,
UserAgent: r.UserAgent(),
IPAddress: ipAddress,
}
if err = rtdb.Create(ctx, token); err != nil {
logger.Warn("Failed to store a refresh token", zap.Error(err), mzap.StorableRef(account),
zap.String("client_id", token.ClientID), zap.String("device_id", token.DeviceID))
return nil, err
}
return token, nil
}