Make product API versions independently installable

This commit is contained in:
Stephan D committed 2026-09-13 10:26:06 +02:00
1 parent ce6583f454
commit d04c8b12ab
69 files changed
+755 -404

No files matched your search

@@ -9,8 +9,8 @@ import (
"testing"
"git.sendico.io/tech/sxchnge/product/auth/config"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
emodel "git.sendico.io/tech/sxchnge/product/auth/model"
"git.sendico.io/tech/sxchnge/product/auth/response"
"git.sendico.io/tech/sxchnge/product/customers/store"
"git.sendico.io/tech/sxchnge/shared/errors"
api "git.sendico.io/tech/sxchnge/shared/http"
@@ -48,7 +48,7 @@ func TestCustomerHandlerResolvesIdentityWithoutRoles(t *testing.T) {
accounts.customer.Status = status
routes := chi.NewRouter()
signature := &middleware.Signature{Algorithm: "HS256", PrivateKey: []byte("test-only-signing-key-at-least-32-bytes")}
router := NewRouter(zap.NewNop(), "/api/v1", routes, accounts, &sessionDB{token: &sessionmodel.RefreshToken{}}, &middleware.TokenConfig{Expiration: middleware.TokenExpiration{Account: 1}}, signature)
router := Install(zap.NewNop(), "/api/v1", routes, accounts, &sessionDB{token: &sessionmodel.RefreshToken{}}, &middleware.TokenConfig{Expiration: middleware.TokenExpiration{Account: 1}}, signature)
called := false
router.AccountHandler(mservice.Accounts, "/me", api.Get, func(_ *http.Request, c *customermodel.Account, _ *sresponse.TokenData) http.HandlerFunc {
called = true
@@ -6,9 +6,9 @@ import (
"net/http"
"strings"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
"git.sendico.io/tech/sxchnge/product/auth/ipguard"
emodel "git.sendico.io/tech/sxchnge/product/auth/model"
"git.sendico.io/tech/sxchnge/product/auth/response"
"git.sendico.io/tech/sxchnge/product/sessions/metrics"
"git.sendico.io/tech/sxchnge/shared/errors"
api "git.sendico.io/tech/sxchnge/shared/http"
@@ -4,7 +4,7 @@ import (
"net/http"
"git.sendico.io/tech/sxchnge/product/auth/config"
re "git.sendico.io/tech/sxchnge/product/auth/http/endpoint"
re "git.sendico.io/tech/sxchnge/product/auth/http/v1/endpoint"
emodel "git.sendico.io/tech/sxchnge/product/auth/model"
"git.sendico.io/tech/sxchnge/product/customers/store"
"git.sendico.io/tech/sxchnge/product/sessions/metrics"
@@ -27,7 +27,7 @@ type AuthorizedRouter struct {
service mservice.Type
}
func NewRouter(logger mlogger.Logger, apiEndpoint string, router chi.Router, db account.DB, rtdb refreshtokens.DB, config *middleware.TokenConfig, signature *middleware.Signature) *AuthorizedRouter {
func Install(logger mlogger.Logger, apiEndpoint string, router chi.Router, db account.DB, rtdb refreshtokens.DB, config *middleware.TokenConfig, signature *middleware.Signature) *AuthorizedRouter {
ja := jwtauth.New(signature.Algorithm, signature.PrivateKey, signature.PublicKey)
router.Use(jwtauth.Verifier(ja))
router.Use(jwtauth.Authenticator(ja))
@@ -1,12 +1,10 @@
package router
import (
"os"
"git.sendico.io/tech/sxchnge/product/auth/config"
rauthorized "git.sendico.io/tech/sxchnge/product/auth/http/authorized"
rpublic "git.sendico.io/tech/sxchnge/product/auth/http/public"
"git.sendico.io/tech/sxchnge/product/auth/response"
rauthorized "git.sendico.io/tech/sxchnge/product/auth/http/v1/authorized"
rpublic "git.sendico.io/tech/sxchnge/product/auth/http/v1/public"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
"git.sendico.io/tech/sxchnge/product/customers/store"
"git.sendico.io/tech/sxchnge/product/sessions/store"
api "git.sendico.io/tech/sxchnge/shared/http"
@@ -34,13 +32,15 @@ func (d *Dispatcher) PendingAccountHandler(service mservice.Type, endpoint strin
d.protected.PendingAccountHandler(service, endpoint, method, handler)
}
func NewDispatcher(
func Install(
logger mlogger.Logger,
router chi.Router,
endpoint string,
db account.DB,
rtdb refreshtokens.DB,
attempts ratelimits.Store,
config *middleware.Config,
token *middleware.TokenConfig,
signature middleware.Signature,
loginRateLimits middleware.LoginRateLimits,
flows ...rpublic.Flows,
) *Dispatcher {
@@ -49,13 +49,11 @@ func NewDispatcher(
}
d.logger.Debug("Installing endpoints middleware...")
endpoint := os.Getenv(config.EndPointEnv)
signature := middleware.SignatureConf(config)
router.Group(func(r chi.Router) {
d.public = rpublic.NewRouter(d.logger, endpoint, db, rtdb, attempts, r, &config.Token, &signature, loginRateLimits, flows...)
d.public = rpublic.Install(d.logger, endpoint, db, rtdb, attempts, r, token, &signature, loginRateLimits, flows...)
})
router.Group(func(r chi.Router) {
d.protected = rauthorized.NewRouter(d.logger, endpoint, r, db, rtdb, &config.Token, &signature)
d.protected = rauthorized.Install(d.logger, endpoint, r, db, rtdb, token, &signature)
})
return d
@@ -7,7 +7,7 @@ import (
"net/http"
"git.sendico.io/tech/sxchnge/product/auth/config"
"git.sendico.io/tech/sxchnge/product/auth/response"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
"git.sendico.io/tech/sxchnge/shared/logging"
"github.com/go-chi/chi/v5"
)
@@ -4,7 +4,7 @@ import (
"net/http"
"path"
"git.sendico.io/tech/sxchnge/product/auth/response"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
api "git.sendico.io/tech/sxchnge/shared/http"
"git.sendico.io/tech/sxchnge/shared/service"
"github.com/go-chi/chi/v5"
@@ -33,7 +33,7 @@ func (er *HttpEndpointRouter) chooseMethod(method api.HTTPMethod) RegistratorT {
}
func (er *HttpEndpointRouter) endpoint(service mservice.Type, handler string) string {
return path.Join(er.apiEndpoint, service, handler)
return path.Join("/", er.apiEndpoint, service, handler)
}
func (er *HttpEndpointRouter) InstallHandler(service mservice.Type, endpoint string, method api.HTTPMethod, handler sresponse.HandlerFunc) {
@@ -5,8 +5,8 @@ import (
sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model"
"time"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
emodel "git.sendico.io/tech/sxchnge/product/auth/model"
"git.sendico.io/tech/sxchnge/product/auth/response"
"github.com/go-chi/jwtauth/v5"
)
@@ -9,9 +9,9 @@ import (
"strings"
clientauth "git.sendico.io/tech/sxchnge/product/auth/client"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/request"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
"git.sendico.io/tech/sxchnge/product/auth/ipguard"
"git.sendico.io/tech/sxchnge/product/auth/request"
"git.sendico.io/tech/sxchnge/product/auth/response"
customermodel "git.sendico.io/tech/sxchnge/product/customers/model"
sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model"
"git.sendico.io/tech/sxchnge/shared/errors"
@@ -9,8 +9,8 @@ import (
"time"
"git.sendico.io/tech/sxchnge/product/auth/config"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/request"
"git.sendico.io/tech/sxchnge/product/auth/ipguard"
"git.sendico.io/tech/sxchnge/product/auth/request"
"git.sendico.io/tech/sxchnge/shared/errors"
"git.sendico.io/tech/sxchnge/shared/http/response"
"git.sendico.io/tech/sxchnge/shared/ratelimit/store"
@@ -13,7 +13,7 @@ import (
"time"
"git.sendico.io/tech/sxchnge/product/auth/config"
"git.sendico.io/tech/sxchnge/product/auth/request"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/request"
"git.sendico.io/tech/sxchnge/shared/errors"
"git.sendico.io/tech/sxchnge/shared/service"
"go.uber.org/zap"
@@ -11,7 +11,7 @@ import (
"testing"
"git.sendico.io/tech/sxchnge/product/auth/config"
"git.sendico.io/tech/sxchnge/product/auth/request"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/request"
"git.sendico.io/tech/sxchnge/shared/errors"
"git.sendico.io/tech/sxchnge/shared/service"
"github.com/go-chi/chi/v5"
@@ -183,7 +183,7 @@ func TestAPILoginAllowsLoginAgainAfterRevokeSameSession(t *testing.T) {
PrivateKey: []byte("test-secret"),
}
router := NewRouter(
router := Install(
zap.NewNop(),
"/",
&testAccountDB{result: account},
@@ -4,7 +4,7 @@ import (
"encoding/json"
"net/http"
"git.sendico.io/tech/sxchnge/product/auth/request"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/request"
"git.sendico.io/tech/sxchnge/product/sessions/browser"
"git.sendico.io/tech/sxchnge/product/sessions/metrics"
"git.sendico.io/tech/sxchnge/shared/http/response"
@@ -4,7 +4,7 @@ import (
"net/http"
"time"
sresponse "git.sendico.io/tech/sxchnge/product/auth/response"
sresponse "git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
"github.com/go-chi/chi/v5/middleware"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
@@ -6,8 +6,8 @@ import (
"net/http"
"time"
"git.sendico.io/tech/sxchnge/product/auth/request"
"git.sendico.io/tech/sxchnge/product/auth/response"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/request"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
"git.sendico.io/tech/sxchnge/product/sessions/browser"
"git.sendico.io/tech/sxchnge/shared/http/response"
"git.sendico.io/tech/sxchnge/shared/service"
@@ -47,7 +47,7 @@ func newBrowserRefreshRouter(t *testing.T, expiresAt time.Time) (*PublicRouter,
Algorithm: "HS256",
PrivateKey: []byte("browser-refresh-test-secret"),
}
return NewRouter(
return Install(
zap.NewNop(),
"/api/v1",
&testAccountDB{result: account},
@@ -6,7 +6,7 @@ import (
sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model"
"net/http"
"git.sendico.io/tech/sxchnge/product/auth/response"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
rtokens "git.sendico.io/tech/sxchnge/product/sessions/tokens"
"git.sendico.io/tech/sxchnge/shared/http/response"
"git.sendico.io/tech/sxchnge/shared/logging/fields"
@@ -5,7 +5,7 @@ import (
"errors"
"net/http"
"git.sendico.io/tech/sxchnge/product/auth/request"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/request"
"git.sendico.io/tech/sxchnge/shared/http/response"
"git.sendico.io/tech/sxchnge/shared/service"
"go.uber.org/zap"
@@ -5,8 +5,8 @@ import (
"git.sendico.io/tech/sxchnge/product/auth/config"
"git.sendico.io/tech/sxchnge/product/auth/flow"
re "git.sendico.io/tech/sxchnge/product/auth/http/endpoint"
"git.sendico.io/tech/sxchnge/product/auth/response"
re "git.sendico.io/tech/sxchnge/product/auth/http/v1/endpoint"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
"git.sendico.io/tech/sxchnge/product/customers/store"
"git.sendico.io/tech/sxchnge/product/sessions/store"
"git.sendico.io/tech/sxchnge/product/twofactor"
@@ -41,7 +41,7 @@ func (pr *PublicRouter) InstallHandler(service mservice.Type, endpoint string, m
pr.imp.InstallHandler(service, endpoint, method, handler)
}
func NewRouter(
func Install(
logger mlogger.Logger,
apiEndpoint string,
db account.DB,
@@ -8,8 +8,8 @@ import (
"net/http"
"time"
"git.sendico.io/tech/sxchnge/product/auth/request"
"git.sendico.io/tech/sxchnge/product/auth/response"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/request"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
"git.sendico.io/tech/sxchnge/product/sessions/browser"
"git.sendico.io/tech/sxchnge/shared/errors"
"git.sendico.io/tech/sxchnge/shared/logging/fields"
@@ -11,9 +11,9 @@ import (
"net/http"
"time"
sresponse "git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
"git.sendico.io/tech/sxchnge/product/auth/ipguard"
authmodel "git.sendico.io/tech/sxchnge/product/auth/model"
sresponse "git.sendico.io/tech/sxchnge/product/auth/response"
customer "git.sendico.io/tech/sxchnge/product/customers/model"
session "git.sendico.io/tech/sxchnge/product/sessions/model"
factor "git.sendico.io/tech/sxchnge/product/twofactor/model"
@@ -71,7 +71,7 @@ func TestSignupActivationAndLoginEmailFlow(t *testing.T) {
factors, err := twofactor.New(stores.Accounts, stores.Verification, records, factory.TransactionFactory(), service.Secret, zap.NewNop(), delivery.Email{Sender: mail})
require.NoError(t, err)
routes := chi.NewRouter()
NewRouter(zap.NewNop(), "/api/v1", stores.Accounts, sessions, nil, routes, &middleware.TokenConfig{Length: 32, Expiration: middleware.TokenExpiration{Account: 1, Refresh: 24}}, &middleware.Signature{Algorithm: "HS256", PrivateKey: service.Secret}, middleware.LoginRateLimits{}, Flows{Onboarding: service, Factors: factors})
Install(zap.NewNop(), "/api/v1", stores.Accounts, sessions, nil, routes, &middleware.TokenConfig{Length: 32, Expiration: middleware.TokenExpiration{Account: 1, Refresh: 24}}, &middleware.Signature{Algorithm: "HS256", PrivateKey: service.Secret}, middleware.LoginRateLimits{}, Flows{Onboarding: service, Factors: factors})
post := func(path string, body any) *httptest.ResponseRecorder {
raw, err := json.Marshal(body)
require.NoError(t, err)
@@ -0,0 +1,11 @@
package srequest
import sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model"
import customermodel "git.sendico.io/tech/sxchnge/product/customers/model"
type Login struct {
sessionmodel.SessionIdentifier `json:",inline"`
customermodel.LoginData `json:"login"`
ClientSecret string `json:"clientSecret,omitempty"`
}
@@ -0,0 +1,5 @@
package srequest
import sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model"
type AccessTokenRefresh = sessionmodel.ClientRefreshToken
@@ -0,0 +1,5 @@
package srequest
import sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/model"
type TokenRefreshRotate = sessionmodel.ClientRefreshToken
@@ -0,0 +1,5 @@
package srequest
type Validatable interface {
Validate() error
}
@@ -0,0 +1,15 @@
package srequest
import sessionmodel "git.sendico.io/tech/sxchnge/product/sessions/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 sessionmodel.SessionIdentifier `json:"sessionIdentifier"`
}
@@ -0,0 +1,40 @@
package sresponse
import (
customermodel "git.sendico.io/tech/sxchnge/product/customers/model"
"net/http"
"git.sendico.io/tech/sxchnge/shared/http/response"
"git.sendico.io/tech/sxchnge/shared/logging"
)
type AccountData struct {
ID string `json:"id"`
Name string `json:"name"`
LastName string `json:"lastName"`
Login string `json:"login"`
Locale string `json:"locale"`
AvatarURL *string `json:"avatarUrl,omitempty"`
}
type accountResponse struct {
authResponse `json:",inline"`
Account AccountData `json:"account"`
}
func NewAccountData(account *customermodel.Account) *AccountData {
return &AccountData{
ID: account.ID.Hex(), Name: account.Name, LastName: account.LastName,
Login: account.Login, Locale: account.Locale, AvatarURL: account.AvatarURL,
}
}
func Account(logger mlogger.Logger, account *customermodel.Account, accessToken *TokenData) http.HandlerFunc {
return response.Ok(
logger,
&accountResponse{
Account: *NewAccountData(account),
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/shared/http/response"
"git.sendico.io/tech/sxchnge/shared/logging"
)
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 (
customermodel "git.sendico.io/tech/sxchnge/product/customers/model"
"net/http"
"git.sendico.io/tech/sxchnge/product/sessions/browser"
"git.sendico.io/tech/sxchnge/shared/http/response"
"git.sendico.io/tech/sxchnge/shared/logging"
)
type loginResponse struct {
accountResponse
RefreshToken TokenData `json:"refreshToken"`
}
func Login(logger mlogger.Logger, account *customermodel.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),
authResponse: authResponse{AccessToken: *accessToken},
},
RefreshToken: responseRefreshToken,
},
).ServeHTTP(w, r)
}
}
@@ -0,0 +1,29 @@
package sresponse
import (
customermodel "git.sendico.io/tech/sxchnge/product/customers/model"
"net/http"
"git.sendico.io/tech/sxchnge/shared/http/response"
"git.sendico.io/tech/sxchnge/shared/logging"
)
type pendingLoginResponse struct {
Account accountResponse `json:"account"`
PendingToken TokenData `json:"pendingToken"`
Target string `json:"target"`
}
func LoginPending(logger mlogger.Logger, account *customermodel.Account, pendingToken *TokenData, target string) http.HandlerFunc {
return response.Accepted(
logger,
&pendingLoginResponse{
Account: accountResponse{
Account: *NewAccountData(account),
authResponse: authResponse{},
},
PendingToken: *pendingToken,
Target: target,
},
)
}
@@ -0,0 +1,58 @@
package sresponse
import (
customermodel "git.sendico.io/tech/sxchnge/product/customers/model"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"git.sendico.io/tech/sxchnge/product/sessions/browser"
"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(), &customermodel.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(), &customermodel.Account{}, access, refresh).ServeHTTP(recorder, req)
require.Contains(t, recorder.Body.String(), refresh.Token)
require.Empty(t, recorder.Header().Get("Set-Cookie"))
}
func TestCustomerResponseExcludesStorageAndPlatformFields(t *testing.T) {
account := &customermodel.Account{Password: "private-password-hash", Status: customermodel.AccountActive}
account.Login = "customer@example.test"
account.Name = "Customer"
recorder := httptest.NewRecorder()
Account(zap.NewNop(), account, &TokenData{}).ServeHTTP(recorder, httptest.NewRequest(http.MethodGet, "/accounts/me", nil))
require.Contains(t, recorder.Body.String(), "customer@example.test")
for _, field := range []string{"private-password-hash", "password", "status\":\"active", "organization", "role", "policy", "isAnonymous", "createdAt", "isArchived"} {
require.NotContains(t, recorder.Body.String(), field)
}
}
@@ -0,0 +1,14 @@
package sresponse
import (
customermodel "git.sendico.io/tech/sxchnge/product/customers/model"
"net/http"
emodel "git.sendico.io/tech/sxchnge/product/auth/model"
)
type (
HandlerFunc = func(r *http.Request) http.HandlerFunc
AccountHandlerFunc = func(r *http.Request, account *customermodel.Account, accessToken *TokenData) http.HandlerFunc
PendingAccountHandlerFunc = func(r *http.Request, account *customermodel.Account, token *emodel.AccountToken) http.HandlerFunc
)
@@ -0,0 +1,27 @@
package sresponse
import (
"net/http"
"git.sendico.io/tech/sxchnge/shared/http/response"
"git.sendico.io/tech/sxchnge/shared/logging"
)
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"`
}
@@ -1,7 +1,7 @@
package router
import (
"git.sendico.io/tech/sxchnge/product/auth/response"
"git.sendico.io/tech/sxchnge/product/auth/http/v1/response"
api "git.sendico.io/tech/sxchnge/shared/http"
"git.sendico.io/tech/sxchnge/shared/service"
)