Skip to content

Commit 7888c90

Browse files
committed
refactor(apps-proxy): split kai-preview into endpoints sub-package and helpers
Move the four endpoint handlers (handshake-token, bootstrap, exchange, refresh), the composite Handler, and routing symbols (PathPrefix, DevModeChecker, DevModeCheckerFunc) into a new internal/…/kaipreview/endpoints sub-package (package name "endpoints"). The parent kaipreview package retains the pure helpers: JWT minting/ verification, cookie helpers, CORS, IsIframeDocumentLoad, and the StorageTokenVerifier interface (moved to storage_token_verifier.go). External callers (apphandler.go) import both packages: kaipreview for helpers and kpendpoints alias for the handler/routing surface.
1 parent 9f4f8eb commit 7888c90

13 files changed

Lines changed: 70 additions & 52 deletions

File tree

internal/pkg/service/appsproxy/proxy/apphandler/apphandler.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/dataapps/api"
1616
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/dataapps/auth/provider"
1717
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview"
18+
kpendpoints "github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/endpoints"
1819
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/authproxy/selector"
1920
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/chain"
2021
"github.com/keboola/keboola-as-code/internal/pkg/service/common/ctxattr"
@@ -31,14 +32,14 @@ type appHandler struct {
3132
upstream chain.Handler
3233
allAuthHandlers chain.Handler
3334
authHandlerPerRule map[ruleIndex]chain.Handler
34-
kaiPreview *kaipreview.Handler
35+
kaiPreview *kpendpoints.Handler
3536
}
3637

3738
type ruleIndex int
3839

3940
func newAppHandler(manager *Manager, app api.AppConfig, appUpstream chain.Handler, authHandlers map[provider.ID]selector.Handler) (http.Handler, error) {
4041
// DevModeChecker is backed by the live K8s state watcher: re-evaluates on every request.
41-
devModeChecker := kaipreview.DevModeCheckerFunc(func(ctx context.Context, appID string) bool {
42+
devModeChecker := kpendpoints.DevModeCheckerFunc(func(ctx context.Context, appID string) bool {
4243
info, ok := manager.upstreamManager.AppInfo(ctx, api.AppID(appID))
4344
return ok && info.DevMode
4445
})
@@ -50,7 +51,7 @@ func newAppHandler(manager *Manager, app api.AppConfig, appUpstream chain.Handle
5051
attrs: app.Telemetry(),
5152
upstream: appUpstream,
5253
authHandlerPerRule: make(map[ruleIndex]chain.Handler),
53-
kaiPreview: kaipreview.NewHandler(kaipreview.HandlerDeps{
54+
kaiPreview: kpendpoints.NewHandler(kpendpoints.HandlerDeps{
5455
Clock: manager.clock,
5556
StorageTokenVerifier: manager.storageTokenVerifier,
5657
DevMode: devModeChecker,
@@ -171,7 +172,7 @@ func (h *appHandler) serveHTTPOrError(w http.ResponseWriter, req *http.Request)
171172
// (routing decision documented in spec § "apps-proxy: routing decision for dev-mode apps")
172173
if h.isDevMode(req.Context()) {
173174
// 1. /_proxy/kai-preview/* routes go to the kai-preview composite handler.
174-
if strings.HasPrefix(req.URL.Path, kaipreview.PathPrefix) {
175+
if strings.HasPrefix(req.URL.Path, kpendpoints.PathPrefix) {
175176
return h.kaiPreview.ServeHTTPOrError(w, req)
176177
}
177178
// 2. Valid session cookie → forward to upstream (skip AuthRules), with sliding refresh.
@@ -200,7 +201,7 @@ func (h *appHandler) serveHTTPOrError(w http.ResponseWriter, req *http.Request)
200201
// 3. Iframe document load on a dev-mode app with no session → serve bootstrap shim.
201202
if kaipreview.IsIframeDocumentLoad(req) {
202203
bootstrapReq := req.Clone(req.Context())
203-
bootstrapReq.URL.Path = kaipreview.PathPrefix + "/bootstrap"
204+
bootstrapReq.URL.Path = kpendpoints.PathPrefix + "/bootstrap"
204205
return h.kaiPreview.ServeHTTPOrError(w, bootstrapReq)
205206
}
206207
}

internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/bootstrap.go renamed to internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/endpoints/bootstrap.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package kaipreview
1+
package endpoints
22

33
import (
44
"embed"

internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/bootstrap_test.go renamed to internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/endpoints/bootstrap_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package kaipreview
1+
package endpoints
22

33
import (
44
"net/http"

internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/exchange.go renamed to internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/endpoints/exchange.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package kaipreview
1+
package endpoints
22

33
import (
44
"encoding/json"
@@ -7,6 +7,7 @@ import (
77

88
"github.com/jonboulle/clockwork"
99

10+
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview"
1011
"github.com/keboola/keboola-as-code/internal/pkg/utils/errors"
1112
)
1213

@@ -48,7 +49,7 @@ func (h *ExchangeHandler) ServeHTTPOrError(w http.ResponseWriter, r *http.Reques
4849
return nil
4950
}
5051

51-
claims, err := VerifyHandshakeJWT(h.deps.HandshakeKey, h.deps.Clock, body.Token)
52+
claims, err := kaipreview.VerifyHandshakeJWT(h.deps.HandshakeKey, h.deps.Clock, body.Token)
5253
if err != nil {
5354
http.Error(w, "invalid handshake token", http.StatusUnauthorized)
5455
return nil
@@ -58,12 +59,12 @@ func (h *ExchangeHandler) ServeHTTPOrError(w http.ResponseWriter, r *http.Reques
5859
return nil
5960
}
6061

61-
sessionJWT, err := MintSessionJWT(h.deps.SessionKey, h.deps.Clock, h.deps.AppID, h.deps.AppProjectID, h.deps.SessionTTL)
62+
sessionJWT, err := kaipreview.MintSessionJWT(h.deps.SessionKey, h.deps.Clock, h.deps.AppID, h.deps.AppProjectID, h.deps.SessionTTL)
6263
if err != nil {
6364
return errors.Errorf("kai-preview: mint session JWT: %w", err)
6465
}
6566

66-
SetSessionCookie(w, sessionJWT, h.deps.SessionTTL)
67+
kaipreview.SetSessionCookie(w, sessionJWT, h.deps.SessionTTL)
6768
w.Header().Set("Cache-Control", "no-store")
6869
w.WriteHeader(http.StatusOK)
6970
return nil

internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/exchange_test.go renamed to internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/endpoints/exchange_test.go

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package kaipreview
1+
package endpoints
22

33
import (
44
"bytes"
@@ -11,6 +11,8 @@ import (
1111
"github.com/jonboulle/clockwork"
1212
"github.com/stretchr/testify/assert"
1313
"github.com/stretchr/testify/require"
14+
15+
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview"
1416
)
1517

1618
func newTestExchangeHandler(devMode bool) *ExchangeHandler {
@@ -28,7 +30,7 @@ func newTestExchangeHandler(devMode bool) *ExchangeHandler {
2830
func mintForTest(t *testing.T, appID, projectID string) string {
2931
t.Helper()
3032
clock := clockwork.NewFakeClock()
31-
jwt, err := MintHandshakeJWT(testHandshakeKey, clock, appID, projectID)
33+
jwt, err := kaipreview.MintHandshakeJWT(testHandshakeKey, clock, appID, projectID)
3234
require.NoError(t, err)
3335
return jwt
3436
}
@@ -49,7 +51,7 @@ func TestExchangeHandler_Success(t *testing.T) {
4951

5052
cookies := w.Result().Cookies()
5153
require.Len(t, cookies, 1)
52-
assert.Equal(t, SessionCookieName, cookies[0].Name)
54+
assert.Equal(t, kaipreview.SessionCookieName, cookies[0].Name)
5355
assert.True(t, cookies[0].Partitioned)
5456
}
5557

internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/handler.go renamed to internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/endpoints/handler.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package kaipreview
1+
package endpoints
22

33
import (
44
"context"
@@ -9,6 +9,7 @@ import (
99
"github.com/jonboulle/clockwork"
1010

1111
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/config"
12+
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview"
1213
)
1314

1415
// PathPrefix is the URL prefix all kai-preview endpoints live under.
@@ -29,9 +30,9 @@ func (f DevModeCheckerFunc) IsDevMode(ctx context.Context, appID string) bool {
2930

3031
type HandlerDeps struct {
3132
Clock clockwork.Clock
32-
StorageTokenVerifier StorageTokenVerifier
33+
StorageTokenVerifier kaipreview.StorageTokenVerifier
3334
DevMode DevModeChecker
34-
CORS *CORS
35+
CORS *kaipreview.CORS
3536
HandshakeKey string
3637
SessionKey string
3738
SessionTTL time.Duration

internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/handler_test.go renamed to internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/endpoints/handler_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package kaipreview
1+
package endpoints
22

33
import (
44
"net/http"
@@ -9,14 +9,16 @@ import (
99
"github.com/jonboulle/clockwork"
1010
"github.com/stretchr/testify/assert"
1111
"github.com/stretchr/testify/require"
12+
13+
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview"
1214
)
1315

1416
func newTestCompositeHandler(devMode bool) *Handler {
1517
return NewHandler(HandlerDeps{
1618
Clock: clockwork.NewFakeClock(),
1719
StorageTokenVerifier: &stubStorageTokenVerifier{projectID: "proj-456"},
1820
DevMode: &stubDevModeChecker{devMode: devMode},
19-
CORS: NewCORS([]string{"https://connection.keboola.com"}),
21+
CORS: kaipreview.NewCORS([]string{"https://connection.keboola.com"}),
2022
HandshakeKey: testHandshakeKey,
2123
SessionKey: testSessionKey,
2224
SessionTTL: 4 * time.Hour,

internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/handshake_token.go renamed to internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/endpoints/handshake_token.go

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package kaipreview
1+
package endpoints
22

33
import (
44
"context"
@@ -7,14 +7,10 @@ import (
77

88
"github.com/jonboulle/clockwork"
99

10+
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview"
1011
"github.com/keboola/keboola-as-code/internal/pkg/utils/errors"
1112
)
1213

13-
// StorageTokenVerifier abstracts HTTPStorageTokenVerifier so tests can inject a stub without HTTP.
14-
type StorageTokenVerifier interface {
15-
Verify(ctx context.Context, token string) (*StorageTokenVerifyResult, error)
16-
}
17-
1814
// DevModeChecker tells the handler whether the current app is in dev mode.
1915
// Backed by the apps-proxy CRD watcher (AppInfo.DevMode).
2016
type DevModeChecker interface {
@@ -23,9 +19,9 @@ type DevModeChecker interface {
2319

2420
type HandshakeTokenDeps struct {
2521
Clock clockwork.Clock
26-
StorageTokenVerifier StorageTokenVerifier
22+
StorageTokenVerifier kaipreview.StorageTokenVerifier
2723
DevMode DevModeChecker
28-
CORS *CORS
24+
CORS *kaipreview.CORS
2925
HandshakeKey string
3026
AppID string
3127
AppProjectID string
@@ -81,7 +77,7 @@ func (h *HandshakeTokenHandler) ServeHTTPOrError(w http.ResponseWriter, r *http.
8177
return nil
8278
}
8379

84-
jwt, err := MintHandshakeJWT(h.deps.HandshakeKey, h.deps.Clock, h.deps.AppID, h.deps.AppProjectID)
80+
jwt, err := kaipreview.MintHandshakeJWT(h.deps.HandshakeKey, h.deps.Clock, h.deps.AppID, h.deps.AppProjectID)
8581
if err != nil {
8682
return errors.Errorf("kai-preview: mint handshake JWT: %w", err)
8783
}

internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/handshake_token_test.go renamed to internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/endpoints/handshake_token_test.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package kaipreview
1+
package endpoints
22

33
import (
44
"context"
@@ -11,18 +11,25 @@ import (
1111
"github.com/jonboulle/clockwork"
1212
"github.com/stretchr/testify/assert"
1313
"github.com/stretchr/testify/require"
14+
15+
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview"
16+
)
17+
18+
const (
19+
testHandshakeKey = "test-handshake-key-must-be-long-enough"
20+
testSessionKey = "test-session-key-also-long-enough"
1421
)
1522

1623
type stubStorageTokenVerifier struct {
1724
projectID string
1825
err error
1926
}
2027

21-
func (s *stubStorageTokenVerifier) Verify(_ context.Context, _ string) (*StorageTokenVerifyResult, error) {
28+
func (s *stubStorageTokenVerifier) Verify(_ context.Context, _ string) (*kaipreview.StorageTokenVerifyResult, error) {
2229
if s.err != nil {
2330
return nil, s.err
2431
}
25-
return &StorageTokenVerifyResult{ProjectID: s.projectID}, nil
32+
return &kaipreview.StorageTokenVerifyResult{ProjectID: s.projectID}, nil
2633
}
2734

2835
type stubDevModeChecker struct{ devMode bool }
@@ -36,7 +43,7 @@ type stubErr struct{ msg string }
3643
func (e *stubErr) Error() string { return e.msg }
3744

3845
func newTestHandshakeHandler(tokenValid bool, storageTokenProject string, devMode bool) *HandshakeTokenHandler {
39-
var verifier StorageTokenVerifier
46+
var verifier kaipreview.StorageTokenVerifier
4047
if tokenValid {
4148
verifier = &stubStorageTokenVerifier{projectID: storageTokenProject}
4249
} else {
@@ -46,7 +53,7 @@ func newTestHandshakeHandler(tokenValid bool, storageTokenProject string, devMod
4653
Clock: clockwork.NewFakeClock(),
4754
StorageTokenVerifier: verifier,
4855
DevMode: &stubDevModeChecker{devMode: devMode},
49-
CORS: NewCORS([]string{"https://connection.keboola.com"}),
56+
CORS: kaipreview.NewCORS([]string{"https://connection.keboola.com"}),
5057
HandshakeKey: testHandshakeKey,
5158
AppID: "app-123",
5259
AppProjectID: "proj-456",

internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/refresh.go renamed to internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview/endpoints/refresh.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
package kaipreview
1+
package endpoints
22

33
import (
44
"net/http"
55
"time"
66

77
"github.com/jonboulle/clockwork"
88

9+
"github.com/keboola/keboola-as-code/internal/pkg/service/appsproxy/proxy/apphandler/authproxy/kaipreview"
910
"github.com/keboola/keboola-as-code/internal/pkg/utils/errors"
1011
)
1112

@@ -14,7 +15,7 @@ type RefreshDeps struct {
1415
DevMode DevModeChecker
1516
SessionKey string
1617
SessionTTL time.Duration
17-
CORS *CORS
18+
CORS *kaipreview.CORS
1819
AppID string
1920
AppProjectID string
2021
}
@@ -50,13 +51,13 @@ func (h *RefreshHandler) ServeHTTPOrError(w http.ResponseWriter, r *http.Request
5051
return nil
5152
}
5253

53-
cookieValue := ReadSessionCookie(r)
54+
cookieValue := kaipreview.ReadSessionCookie(r)
5455
if cookieValue == "" {
5556
http.Error(w, "no session", http.StatusUnauthorized)
5657
return nil
5758
}
5859

59-
claims, err := VerifySessionJWT(h.deps.SessionKey, h.deps.Clock, cookieValue)
60+
claims, err := kaipreview.VerifySessionJWT(h.deps.SessionKey, h.deps.Clock, cookieValue)
6061
if err != nil {
6162
http.Error(w, "session expired", http.StatusUnauthorized)
6263
return nil
@@ -66,11 +67,11 @@ func (h *RefreshHandler) ServeHTTPOrError(w http.ResponseWriter, r *http.Request
6667
return nil
6768
}
6869

69-
newJWT, err := MintSessionJWT(h.deps.SessionKey, h.deps.Clock, h.deps.AppID, h.deps.AppProjectID, h.deps.SessionTTL)
70+
newJWT, err := kaipreview.MintSessionJWT(h.deps.SessionKey, h.deps.Clock, h.deps.AppID, h.deps.AppProjectID, h.deps.SessionTTL)
7071
if err != nil {
7172
return errors.Errorf("kai-preview: re-mint session JWT: %w", err)
7273
}
73-
SetSessionCookie(w, newJWT, h.deps.SessionTTL)
74+
kaipreview.SetSessionCookie(w, newJWT, h.deps.SessionTTL)
7475

7576
w.Header().Set("Cache-Control", "no-store")
7677
w.WriteHeader(http.StatusNoContent)

0 commit comments

Comments
 (0)