Make product API versions independently installable
This commit is contained in:
1 parent
ce6583f454
commit
d04c8b12ab
69 files changed
+755
-404
No files matched your search
+2
-2
@@ -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
|
||||
+1
-1
@@ -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"
|
||||
+2
-2
@@ -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))
|
||||
File renamed without changes.
+9
-11
@@ -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
|
||||
+1
-1
@@ -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"
|
||||
)
|
||||
+2
-2
@@ -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) {
|
||||
+1
-1
@@ -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"
|
||||
)
|
||||
|
||||
+2
-2
@@ -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"
|
||||
+1
-1
@@ -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"
|
||||
+1
-1
@@ -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"
|
||||
+2
-2
@@ -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},
|
||||
File renamed without changes.
+1
-1
@@ -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"
|
||||
File renamed without changes.
+1
-1
@@ -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"
|
||||
+2
-2
@@ -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"
|
||||
+1
-1
@@ -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},
|
||||
+1
-1
@@ -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"
|
||||
+1
-1
@@ -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"
|
||||
+3
-3
@@ -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,
|
||||
+2
-2
@@ -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"
|
||||
+1
-1
@@ -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"
|
||||
+1
-1
@@ -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"
|
||||
)
|
||||
Reference in new issue
Block a user