Bootstrap copied BFF and local accounts with public Sendico contracts (#34)
This commit is contained in:
1 parent
67dc5b5b1e
commit
7062672a87
556 files changed
+42291
-777
No files matched your search
@@ -0,0 +1,25 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/auth"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/db"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/domainprovider"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mservice"
|
||||
)
|
||||
|
||||
type API interface {
|
||||
LifecycleContext() context.Context
|
||||
Logger() mlogger.Logger
|
||||
DomainProvider() domainprovider.DomainProvider
|
||||
Config() *Config
|
||||
ProfileConfig() ConfigProvider
|
||||
DBFactory() db.Factory
|
||||
Permissions() auth.Provider
|
||||
RuntimeComponent(name string) any
|
||||
Register() Register
|
||||
}
|
||||
|
||||
type MicroServiceFactoryT = func(API) (mservice.MicroService, error)
|
||||
@@ -0,0 +1,41 @@
|
||||
package api
|
||||
|
||||
type AuthStorageConfig struct {
|
||||
AccountsCollection string `yaml:"accounts_collection"`
|
||||
RefreshTokensCollection string `yaml:"refresh_tokens_collection"`
|
||||
ClientsCollection string `yaml:"clients_collection"`
|
||||
VerificationTokensCollection string `yaml:"verification_tokens_collection"`
|
||||
LoginAttemptsCollection string `yaml:"login_attempts_collection"`
|
||||
VerificationRateLimitsCollection string `yaml:"verification_rate_limits_collection"`
|
||||
}
|
||||
|
||||
func (c *AuthStorageConfig) WithDefaults() AuthStorageConfig {
|
||||
if c == nil {
|
||||
c = &AuthStorageConfig{}
|
||||
}
|
||||
res := *c
|
||||
if res.AccountsCollection == "" {
|
||||
res.AccountsCollection = "accounts"
|
||||
}
|
||||
if res.RefreshTokensCollection == "" {
|
||||
res.RefreshTokensCollection = "refresh_tokens"
|
||||
}
|
||||
if res.ClientsCollection == "" {
|
||||
res.ClientsCollection = "clients"
|
||||
}
|
||||
if res.VerificationTokensCollection == "" {
|
||||
res.VerificationTokensCollection = "verification_tokens"
|
||||
}
|
||||
if res.LoginAttemptsCollection == "" {
|
||||
res.LoginAttemptsCollection = "login_attempts"
|
||||
}
|
||||
if res.VerificationRateLimitsCollection == "" {
|
||||
res.VerificationRateLimitsCollection = "verification_rate_limits"
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
type VerificationConfig struct {
|
||||
AllowedPurposes []string `yaml:"allowed_purposes"`
|
||||
RateLimits *VerificationRateLimitsConfig `yaml:"rate_limits"`
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
mwa "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware"
|
||||
storageconfig "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/storage"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Mw *mwa.Config `yaml:"middleware"`
|
||||
AuthStorage *AuthStorageConfig `yaml:"auth_storage"`
|
||||
Verification *VerificationConfig `yaml:"verification"`
|
||||
Storage *storageconfig.Config `yaml:"storage"`
|
||||
}
|
||||
|
||||
func (c *Config) SharedConfig() *Config {
|
||||
return c
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/sresponse"
|
||||
"git.sendico.io/tech/sxchnge/bff/edge/shared/interface/api/ws"
|
||||
api "git.sendico.io/tech/sxchnge/bff/pkg/api/http"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mservice"
|
||||
)
|
||||
|
||||
type Register interface {
|
||||
Handler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.HandlerFunc)
|
||||
AccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.AccountHandlerFunc)
|
||||
PendingAccountHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.PendingAccountHandlerFunc)
|
||||
WSHandler(messageType string, handler ws.HandlerFunc)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package api
|
||||
|
||||
import "git.sendico.io/tech/sxchnge/bff/pkg/mservice"
|
||||
|
||||
type ConfigProvider interface {
|
||||
SharedConfig() *Config
|
||||
}
|
||||
|
||||
type ConfigFactory func() ConfigProvider
|
||||
type RuntimeComponents map[string]any
|
||||
type RuntimeBootstrapFunc func(API) (RuntimeComponents, error)
|
||||
type RuntimeFinishFunc func(API) error
|
||||
|
||||
type RuntimeConfig struct {
|
||||
Name string
|
||||
Services []ServiceFactory
|
||||
EnableWebSocket bool
|
||||
ConfigFactory ConfigFactory
|
||||
Bootstrap RuntimeBootstrapFunc
|
||||
Finish RuntimeFinishFunc
|
||||
}
|
||||
|
||||
type ServiceFactory struct {
|
||||
Name mservice.Type
|
||||
Factory MicroServiceFactoryT
|
||||
}
|
||||
|
||||
func (cfg RuntimeConfig) RuntimeName() string {
|
||||
if cfg.Name == "" {
|
||||
return "edge"
|
||||
}
|
||||
return cfg.Name
|
||||
}
|
||||
|
||||
func (cfg RuntimeConfig) NewConfig() ConfigProvider {
|
||||
if cfg.ConfigFactory != nil {
|
||||
return cfg.ConfigFactory()
|
||||
}
|
||||
return &Config{}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package srequest
|
||||
|
||||
import (
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
type ChangePolicies struct {
|
||||
RoleRef bson.ObjectID `json:"roleRef"`
|
||||
Add *[]model.RolePolicy `json:"add,omitempty"`
|
||||
Remove *[]model.RolePolicy `json:"remove,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package srequest
|
||||
|
||||
import "go.mongodb.org/mongo-driver/v2/bson"
|
||||
|
||||
type ChangeRole struct {
|
||||
AccountRef bson.ObjectID `json:"accountRef"`
|
||||
NewRoleDescriptionRef bson.ObjectID `json:"newRoleDescriptionRef"`
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package srequest
|
||||
|
||||
import "git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
|
||||
type Login struct {
|
||||
model.SessionIdentifier `json:",inline"`
|
||||
model.LoginData `json:"login"`
|
||||
ClientSecret string `json:"clientSecret,omitempty"`
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
package srequest
|
||||
|
||||
import "git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
|
||||
type AccessTokenRefresh = model.ClientRefreshToken
|
||||
@@ -0,0 +1,5 @@
|
||||
package srequest
|
||||
|
||||
import "git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
|
||||
type TokenRefreshRotate = model.ClientRefreshToken
|
||||
@@ -0,0 +1,5 @@
|
||||
package srequest
|
||||
|
||||
type Validatable interface {
|
||||
Validate() error
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package srequest
|
||||
|
||||
import "git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
|
||||
type VerificationCodeRequest struct {
|
||||
Purpose string `json:"purpose"`
|
||||
Target string `json:"target,omitempty"`
|
||||
IdempotencyKey string `json:"idempotencyKey"`
|
||||
}
|
||||
|
||||
type CodeVerificationRequest struct {
|
||||
VerificationCodeRequest `json:",inline"`
|
||||
Code string `json:"code"`
|
||||
SessionIdentifier model.SessionIdentifier `json:"sessionIdentifier"`
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/http/response"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
"go.mongodb.org/mongo-driver/v2/bson"
|
||||
)
|
||||
|
||||
type AccountData struct {
|
||||
model.AccountPublic `json:",inline"`
|
||||
IsAnonymous bool `json:"isAnonymous"`
|
||||
}
|
||||
|
||||
type accountResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Account AccountData `json:"account"`
|
||||
}
|
||||
|
||||
type accountsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Accounts []AccountData `json:"accounts"`
|
||||
}
|
||||
|
||||
func NewAccountData(account *model.Account, isAnonymous bool) *AccountData {
|
||||
return &AccountData{
|
||||
AccountPublic: account.AccountPublic,
|
||||
IsAnonymous: isAnonymous,
|
||||
}
|
||||
}
|
||||
|
||||
func Account(logger mlogger.Logger, account *model.Account, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(
|
||||
logger,
|
||||
&accountResponse{
|
||||
Account: *NewAccountData(account, false),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
func Accounts(logger mlogger.Logger, accounts []model.Account, orgRef bson.ObjectID, accessToken *TokenData) http.HandlerFunc {
|
||||
publicAccounts := make([]AccountData, len(accounts))
|
||||
for i := range accounts {
|
||||
publicAccounts[i] = *NewAccountData(
|
||||
&accounts[i],
|
||||
model.AccountIsAnonymous(&accounts[i].UserDataBase, orgRef),
|
||||
)
|
||||
}
|
||||
|
||||
return response.Ok(
|
||||
logger,
|
||||
&accountsResponse{
|
||||
Accounts: publicAccounts,
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package sresponse
|
||||
|
||||
type AuthResponse struct {
|
||||
AccessToken TokenData `json:"accessToken"`
|
||||
}
|
||||
|
||||
type authResponse = AuthResponse
|
||||
@@ -0,0 +1,16 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/http/response"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
)
|
||||
|
||||
type fileUploaded struct {
|
||||
URL string `json:"url"`
|
||||
}
|
||||
|
||||
func FileUploaded(logger mlogger.Logger, url string) http.HandlerFunc {
|
||||
return response.Ok(logger, &fileUploaded{URL: url})
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/http/response"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
)
|
||||
|
||||
type loginResponse struct {
|
||||
accountResponse
|
||||
RefreshToken TokenData `json:"refreshToken"`
|
||||
}
|
||||
|
||||
func Login(logger mlogger.Logger, account *model.Account, accessToken, refreshToken *TokenData) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
responseRefreshToken := *refreshToken
|
||||
if browser.UsesCookie(r) {
|
||||
browser.SetRefreshCookie(w, r, refreshToken.Token, refreshToken.Expiration)
|
||||
responseRefreshToken.Token = ""
|
||||
}
|
||||
response.Ok(
|
||||
logger,
|
||||
&loginResponse{
|
||||
accountResponse: accountResponse{
|
||||
Account: *NewAccountData(account, false),
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
},
|
||||
RefreshToken: responseRefreshToken,
|
||||
},
|
||||
).ServeHTTP(w, r)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/http/response"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
)
|
||||
|
||||
type pendingLoginResponse struct {
|
||||
Account accountResponse `json:"account"`
|
||||
PendingToken TokenData `json:"pendingToken"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
|
||||
func LoginPending(logger mlogger.Logger, account *model.Account, pendingToken *TokenData, target string) http.HandlerFunc {
|
||||
return response.Accepted(
|
||||
logger,
|
||||
&pendingLoginResponse{
|
||||
Account: accountResponse{
|
||||
Account: *NewAccountData(account, false),
|
||||
authResponse: authResponse{},
|
||||
},
|
||||
PendingToken: *pendingToken,
|
||||
Target: target,
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/edge/shared/auth/browser"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func TestBrowserLoginMovesRefreshCredentialToHttpOnlyCookie(t *testing.T) {
|
||||
refresh := &TokenData{Token: "refresh-secret", Expiration: time.Now().Add(time.Hour)}
|
||||
access := &TokenData{Token: "access-secret", Expiration: time.Now().Add(time.Minute)}
|
||||
req := httptest.NewRequest(http.MethodPost, "http://app.sendico.io/api/v1/accounts/login", nil)
|
||||
req.Header.Set(browser.TransportHeader, browser.TransportCookie)
|
||||
req.Header.Set("X-Forwarded-Proto", "https")
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
Login(zap.NewNop(), &model.Account{}, access, refresh).ServeHTTP(recorder, req)
|
||||
|
||||
require.Equal(t, http.StatusOK, recorder.Code)
|
||||
require.NotContains(t, recorder.Body.String(), refresh.Token)
|
||||
require.Contains(t, recorder.Body.String(), access.Token)
|
||||
setCookie := recorder.Header().Get("Set-Cookie")
|
||||
require.True(t, strings.HasPrefix(setCookie, browser.RefreshCookie+"="))
|
||||
require.Contains(t, setCookie, "HttpOnly")
|
||||
require.Contains(t, setCookie, "Secure")
|
||||
require.Contains(t, setCookie, "SameSite=Strict")
|
||||
}
|
||||
|
||||
func TestNativeLoginKeepsRefreshCredentialInResponse(t *testing.T) {
|
||||
refresh := &TokenData{Token: "native-refresh", Expiration: time.Now().Add(time.Hour)}
|
||||
access := &TokenData{Token: "native-access", Expiration: time.Now().Add(time.Minute)}
|
||||
req := httptest.NewRequest(http.MethodPost, "http://native.test/api/v1/accounts/login", nil)
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
Login(zap.NewNop(), &model.Account{}, access, refresh).ServeHTTP(recorder, req)
|
||||
|
||||
require.Contains(t, recorder.Body.String(), refresh.Token)
|
||||
require.Empty(t, recorder.Header().Get("Set-Cookie"))
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/http/response"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
)
|
||||
|
||||
type organizationsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Organizations []model.Organization `json:"organizations"`
|
||||
}
|
||||
|
||||
func Organization(logger mlogger.Logger, organization *model.Organization, accessToken *TokenData) http.HandlerFunc {
|
||||
return Organizations(logger, []model.Organization{*organization}, accessToken)
|
||||
}
|
||||
|
||||
func Organizations(logger mlogger.Logger, organizations []model.Organization, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, organizationsResponse{
|
||||
Organizations: organizations,
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
type organizationPublicResponse struct {
|
||||
Organizations []model.OrganizationBase `json:"organizations"`
|
||||
}
|
||||
|
||||
func OrganizationPublic(logger mlogger.Logger, organization *model.OrganizationBase) http.HandlerFunc {
|
||||
return response.Ok(logger, organizationPublicResponse{
|
||||
Organizations: []model.OrganizationBase{*organization},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/http/response"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
)
|
||||
|
||||
type permissionsDescription struct {
|
||||
Roles []model.RoleDescription `json:"roles"`
|
||||
Policies []model.PolicyDescription `json:"policies"`
|
||||
}
|
||||
|
||||
type permissionsData struct {
|
||||
Roles []model.Role `json:"roles"`
|
||||
Policies []model.RolePolicy `json:"policies"`
|
||||
Permissions []model.Permission `json:"permissions"`
|
||||
}
|
||||
|
||||
type permissionsResponse struct {
|
||||
authResponse `json:",inline"`
|
||||
Descriptions permissionsDescription `json:"descriptions"`
|
||||
Permissions permissionsData `json:"permissions"`
|
||||
}
|
||||
|
||||
func Permissions(logger mlogger.Logger,
|
||||
rolesDescs []model.RoleDescription, policiesDescs []model.PolicyDescription,
|
||||
roles []model.Role, policies []model.RolePolicy, permissions []model.Permission,
|
||||
accessToken *TokenData,
|
||||
) http.HandlerFunc {
|
||||
return response.Ok(logger, permissionsResponse{
|
||||
Descriptions: permissionsDescription{
|
||||
Roles: rolesDescs,
|
||||
Policies: policiesDescs,
|
||||
},
|
||||
Permissions: permissionsData{
|
||||
Roles: roles,
|
||||
Policies: policies,
|
||||
Permissions: permissions,
|
||||
},
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
emodel "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/model"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/model"
|
||||
)
|
||||
|
||||
type (
|
||||
HandlerFunc = func(r *http.Request) http.HandlerFunc
|
||||
AccountHandlerFunc = func(r *http.Request, account *model.Account, accessToken *TokenData) http.HandlerFunc
|
||||
PendingAccountHandlerFunc = func(r *http.Request, account *model.Account, token *emodel.AccountToken) http.HandlerFunc
|
||||
)
|
||||
@@ -0,0 +1,27 @@
|
||||
package sresponse
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/api/http/response"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/mlogger"
|
||||
)
|
||||
|
||||
type resultAuth struct {
|
||||
authResponse `json:",inline"`
|
||||
response.Result `json:",inline"`
|
||||
}
|
||||
|
||||
func Success(logger mlogger.Logger, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Ok(logger, &resultAuth{
|
||||
Result: response.Result{Result: true},
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
|
||||
func Failed(logger mlogger.Logger, accessToken *TokenData) http.HandlerFunc {
|
||||
return response.Accepted(logger, &resultAuth{
|
||||
Result: response.Result{Result: false},
|
||||
authResponse: authResponse{AccessToken: *accessToken},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package sresponse
|
||||
|
||||
import "time"
|
||||
|
||||
type TokenData struct {
|
||||
Token string `json:"token"`
|
||||
Expiration time.Time `json:"expiration"`
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package sresponse
|
||||
|
||||
type VerificationResponse struct {
|
||||
IdempotencyKey string `json:"idempotencyKey"`
|
||||
TTLSeconds int `json:"ttl_seconds"`
|
||||
CooldownSeconds int `json:"cooldown_seconds"`
|
||||
Target string `json:"target"`
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sharedlimit "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/ratelimit"
|
||||
)
|
||||
|
||||
type VerificationRateLimitsConfig struct {
|
||||
PerDestination sharedlimit.WindowConfig `yaml:"per_destination"`
|
||||
PerIP sharedlimit.WindowConfig `yaml:"per_ip"`
|
||||
}
|
||||
|
||||
type VerificationRateLimits struct {
|
||||
PerDestination sharedlimit.Window
|
||||
PerIP sharedlimit.Window
|
||||
}
|
||||
|
||||
func DefaultVerificationRateLimits() VerificationRateLimits {
|
||||
return VerificationRateLimits{
|
||||
PerDestination: sharedlimit.Window{Limit: 5, Window: time.Hour},
|
||||
PerIP: sharedlimit.Window{Limit: 20, Window: time.Hour},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *VerificationConfig) ResolveRateLimits() (VerificationRateLimits, error) {
|
||||
if c == nil || c.RateLimits == nil {
|
||||
return DefaultVerificationRateLimits(), nil
|
||||
}
|
||||
destination, err := sharedlimit.ResolveWindow(c.RateLimits.PerDestination, "verification.rate_limits.per_destination")
|
||||
if err != nil {
|
||||
return VerificationRateLimits{}, err
|
||||
}
|
||||
ip, err := sharedlimit.ResolveWindow(c.RateLimits.PerIP, "verification.rate_limits.per_ip")
|
||||
if err != nil {
|
||||
return VerificationRateLimits{}, err
|
||||
}
|
||||
return VerificationRateLimits{PerDestination: destination, PerIP: ip}, nil
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
sharedlimit "git.sendico.io/tech/sxchnge/bff/edge/shared/interface/ratelimit"
|
||||
"git.sendico.io/tech/sxchnge/bff/pkg/merrors"
|
||||
)
|
||||
|
||||
func TestVerificationRateLimitsDefaults(t *testing.T) {
|
||||
got, err := (*VerificationConfig)(nil).ResolveRateLimits()
|
||||
if err != nil {
|
||||
t.Fatalf("resolve defaults: %v", err)
|
||||
}
|
||||
if got.PerDestination.Limit != 5 || got.PerDestination.Window != time.Hour ||
|
||||
got.PerIP.Limit != 20 || got.PerIP.Window != time.Hour {
|
||||
t.Fatalf("unexpected defaults: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerificationRateLimitsRejectInvalidPolicy(t *testing.T) {
|
||||
cfg := &VerificationConfig{RateLimits: &VerificationRateLimitsConfig{
|
||||
PerDestination: sharedlimit.WindowConfig{Limit: 5, Window: "1h"},
|
||||
PerIP: sharedlimit.WindowConfig{Limit: 0, Window: "1h"},
|
||||
}}
|
||||
_, err := cfg.ResolveRateLimits()
|
||||
if !errors.Is(err, merrors.ErrInvalidArg) {
|
||||
t.Fatalf("expected invalid argument, got %v", err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"git.sendico.io/tech/sxchnge/bff/edge/shared/interface/middleware"
|
||||
)
|
||||
|
||||
type (
|
||||
Config = middleware.WebSocketConfig
|
||||
)
|
||||
@@ -0,0 +1,12 @@
|
||||
package ws
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"golang.org/x/net/websocket"
|
||||
)
|
||||
|
||||
type (
|
||||
ResponseHandler func(messageType string, conn *websocket.Conn)
|
||||
HandlerFunc func(ctx context.Context, msg Message) ResponseHandler
|
||||
)
|
||||
@@ -0,0 +1,9 @@
|
||||
package ws
|
||||
|
||||
import "git.sendico.io/tech/sxchnge/bff/pkg/api/http/response"
|
||||
|
||||
type Message struct {
|
||||
response.BaseResponse
|
||||
ID string `json:"id"`
|
||||
MessageType string `json:"messageType"`
|
||||
}
|
||||
Reference in new issue
Block a user