Skip to content

Commit e3c3a99

Browse files
committed
refactor: eliminate token usage in URL query
1 parent 57111f2 commit e3c3a99

19 files changed

Lines changed: 295 additions & 198 deletions

File tree

internal/server/middleware/auth_jwt/new.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ func New(timeSource *timesource.Source, actionHandler *action.Handler, expiratio
9696
Timeout: expiration,
9797
MaxRefresh: expiration,
9898
TimeFunc: timeSource.Now,
99-
TokenLookup: "header: Authorization, query: token",
99+
TokenLookup: "header: Authorization",
100100
})
101101
if err != nil {
102102
return nil, err
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package auth_jwt
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"github.com/gin-gonic/gin"
8+
)
9+
10+
func NewWebsocketAuthAdapter() gin.HandlerFunc {
11+
return func(c *gin.Context) {
12+
upgrade := strings.ToLower(c.GetHeader("Upgrade"))
13+
connection := strings.ToLower(c.GetHeader("Connection"))
14+
15+
if upgrade != "websocket" || !strings.Contains(connection, "upgrade") {
16+
c.Next()
17+
return
18+
}
19+
20+
protocol := c.GetHeader("Sec-WebSocket-Protocol")
21+
if protocol == "" {
22+
c.Next()
23+
return
24+
}
25+
26+
if token := extractBearerToken(protocol); token != "" {
27+
c.Request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
28+
}
29+
30+
c.Next()
31+
}
32+
}
33+
34+
func extractBearerToken(protocols string) string {
35+
for _, p := range strings.Split(protocols, ",") {
36+
p = strings.TrimSpace(p)
37+
if strings.HasPrefix(p, "Bearer#") {
38+
return strings.TrimPrefix(p, "Bearer#")
39+
}
40+
}
41+
42+
return ""
43+
}

internal/server/router/socket/setup.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66

77
"github.com/anyshake/observer/internal/hardware"
88
"github.com/anyshake/observer/internal/hardware/explorer"
9+
"github.com/anyshake/observer/internal/server/middleware/auth_jwt"
910
"github.com/anyshake/observer/internal/server/response"
1011
"github.com/anyshake/observer/pkg/logger"
1112
"github.com/anyshake/observer/pkg/message"
@@ -25,7 +26,7 @@ func Setup(routerGroup *gin.RouterGroup, timeSource *timesource.Source, hardware
2526
s.storeHistory(t, di, cd)
2627
})
2728

28-
routerGroup.GET("/socket", jwtMiddleware, func(ctx *gin.Context) {
29+
routerGroup.GET("/socket", auth_jwt.NewWebsocketAuthAdapter(), jwtMiddleware, func(ctx *gin.Context) {
2930
upgrader := websocket.Upgrader{
3031
ReadBufferSize: 1024,
3132
WriteBufferSize: 1024,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package tiles
2+
3+
import (
4+
"encoding/binary"
5+
"fmt"
6+
)
7+
8+
func (h *mapTilesHandler) loadMapTile(z, x int) (*mapTileData, error) {
9+
cacheKey := fmt.Sprintf("z%d/x%d", z, x)
10+
if v, ok := h.cache.Get(cacheKey); ok {
11+
return v, nil
12+
}
13+
14+
data, err := h.tilesFs.ReadFile(fmt.Sprintf("%s/z%d/x%d.bin", h.baseDir, z, x))
15+
if err != nil {
16+
return nil, err
17+
}
18+
19+
if len(data) < 4 {
20+
return nil, fmt.Errorf("corrupt file")
21+
}
22+
count := binary.LittleEndian.Uint32(data[:4])
23+
if len(data) < int(4+count*12) {
24+
return nil, fmt.Errorf("corrupt header")
25+
}
26+
header := data[4 : 4+count*12]
27+
28+
objects := make([]mapTileDataObject, count)
29+
for i := uint32(0); i < count; i++ {
30+
base := i * 12
31+
objects[i] = mapTileDataObject{
32+
y: binary.LittleEndian.Uint32(header[base:]),
33+
offset: binary.LittleEndian.Uint32(header[base+4:]),
34+
size: binary.LittleEndian.Uint32(header[base+8:]),
35+
}
36+
}
37+
38+
for i := 1; i < len(objects); i++ {
39+
if objects[i].y <= objects[i-1].y {
40+
return nil, fmt.Errorf("bad index order: %d/%d", z, x)
41+
}
42+
}
43+
44+
tf := &mapTileData{
45+
data: data,
46+
objects: objects,
47+
base: int64(4 + len(objects)*12),
48+
}
49+
50+
h.cache.Add(cacheKey, tf)
51+
return tf, nil
52+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package tiles
2+
3+
import (
4+
"fmt"
5+
"sort"
6+
)
7+
8+
func (h *mapTilesHandler) readMapTile(z, x, y int) ([]byte, error) {
9+
tf, err := h.loadMapTile(z, x)
10+
if err != nil {
11+
return nil, err
12+
}
13+
14+
i := sort.Search(len(tf.objects), func(i int) bool {
15+
return tf.objects[i].y >= uint32(y)
16+
})
17+
18+
if i >= len(tf.objects) || tf.objects[i].y != uint32(y) {
19+
return nil, fmt.Errorf("tile not found")
20+
}
21+
22+
e := tf.objects[i]
23+
offset := tf.base + int64(e.offset)
24+
end := offset + int64(e.size)
25+
26+
if end > int64(len(tf.data)) {
27+
return nil, fmt.Errorf("data out of range")
28+
}
29+
30+
return tf.data[offset:end], nil
31+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package tiles
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"strconv"
7+
8+
"github.com/anyshake/observer/internal/server/response"
9+
"github.com/anyshake/observer/web"
10+
"github.com/gin-gonic/gin"
11+
lru "github.com/hashicorp/golang-lru/v2"
12+
)
13+
14+
func Setup(routerGroup *gin.RouterGroup, version string, jwtMiddleware gin.HandlerFunc) {
15+
lruCache, _ := lru.New[string, *mapTileData](256)
16+
fs, baseDir := web.NewMapTiles()
17+
h := &mapTilesHandler{
18+
tilesFs: fs,
19+
baseDir: baseDir,
20+
version: version,
21+
cache: lruCache,
22+
}
23+
24+
routerGroup.GET("/tiles", jwtMiddleware, func(ctx *gin.Context) {
25+
z, err := strconv.Atoi(ctx.Query("z"))
26+
if err != nil || z < 0 {
27+
response.Error(ctx, http.StatusBadRequest, "invalid Z index")
28+
return
29+
}
30+
x, err := strconv.Atoi(ctx.Query("x"))
31+
if err != nil || x < 0 {
32+
response.Error(ctx, http.StatusBadRequest, "invalid X index")
33+
return
34+
}
35+
y, err := strconv.Atoi(ctx.Query("y"))
36+
if err != nil || y < 0 {
37+
response.Error(ctx, http.StatusBadRequest, "invalid Y index")
38+
return
39+
}
40+
41+
data, err := h.readMapTile(z, x, y)
42+
if err != nil {
43+
response.Error(ctx, http.StatusNotFound, "tile not found")
44+
return
45+
}
46+
47+
if len(data) > 2 && data[0] == 0x1f && data[1] == 0x8b {
48+
ctx.Header("Content-Encoding", "gzip")
49+
}
50+
ctx.Header("Cache-Control", "public, max-age=31536000, immutable")
51+
ctx.Header("ETag", h.version)
52+
response.Blob(ctx, fmt.Sprintf("maptile-z%d-x%d-y%d", z, x, y), "application/vnd.mapbox-vector-tile", data)
53+
})
54+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package tiles
2+
3+
import (
4+
"embed"
5+
6+
lru "github.com/hashicorp/golang-lru/v2"
7+
)
8+
9+
type mapTileDataObject struct {
10+
y uint32
11+
offset uint32
12+
size uint32
13+
}
14+
15+
type mapTileData struct {
16+
objects []mapTileDataObject
17+
data []byte
18+
base int64
19+
}
20+
21+
type mapTilesHandler struct {
22+
tilesFs embed.FS
23+
baseDir string
24+
version string // for http cache invalidation
25+
cache *lru.Cache[string, *mapTileData]
26+
}

internal/server/setup.go

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"github.com/anyshake/observer/internal/server/router/files"
2222
graph_resolver "github.com/anyshake/observer/internal/server/router/graph"
2323
"github.com/anyshake/observer/internal/server/router/socket"
24+
"github.com/anyshake/observer/internal/server/router/tiles"
2425
"github.com/anyshake/observer/web"
2526
"github.com/gin-contrib/cors"
2627
gzipHandler "github.com/gin-contrib/gzip"
@@ -38,14 +39,14 @@ func (s *HttpServer) Setup(listen string) error {
3839
s.engine.Use(gzipHandler.Gzip(
3940
gzip.BestCompression,
4041
gzipHandler.WithExcludedPaths([]string{
41-
"/tiles", // Map tiles are already compressed
42+
"/api/tiles", // Map tiles are already compressed
4243
}),
4344
))
4445
s.engine.Use(secure.Secure(secure.Options{
4546
FrameDeny: true,
4647
BrowserXssFilter: true,
4748
ContentTypeNosniff: true,
48-
ContentSecurityPolicy: "default-src 'self'; connect-src 'self' https://anyshake.org; style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-inline' 'wasm-unsafe-eval'; font-src 'self' data:; img-src 'self' data: blob:;",
49+
ContentSecurityPolicy: "default-src 'self'; connect-src 'self' https://anyshake.org; style-src 'self' https://cdn.jsdelivr.net 'unsafe-inline'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-inline' 'wasm-unsafe-eval'; font-src 'self' data:; img-src 'self' data: blob:;worker-src 'self' blob:;",
4950
}))
5051
if s.cors {
5152
s.engine.Use(cors.New(cors.Config{
@@ -74,6 +75,7 @@ func (s *HttpServer) Setup(listen string) error {
7475
export.Setup(api, s.resolver.ActionHandler, s.resolver.HardwareDev, jwtMiddlewareFn)
7576
socket.Setup(api, s.resolver.TimeSource, s.resolver.HardwareDev, jwtMiddlewareFn)
7677
files.Setup(api, s.resolver.ServiceMap, jwtMiddlewareFn)
78+
tiles.Setup(api, s.resolver.CurrentVersion.String(), jwtMiddlewareFn)
7779

7880
graphql := handler.NewDefaultServer(graph_resolver.NewExecutableSchema(graph_resolver.Config{Resolvers: s.resolver}))
7981
graphql.SetRecoverFunc(func(ctx context.Context, err any) (userMessage error) {
@@ -96,12 +98,6 @@ func (s *HttpServer) Setup(listen string) error {
9698
})
9799
}
98100

99-
mapTileHandler, err := web.NewMapTilesHandler(128)
100-
if err != nil {
101-
return fmt.Errorf("failed to create map tile handler: %w", err)
102-
}
103-
s.engine.GET("/tiles", mapTileHandler)
104-
105101
webFs, webPath := web.NewWebDist()
106102
s.engine.Use(static.Serve("/", static.EmbedFolder(webFs, webPath)))
107103

web/dist.go renamed to web/new.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,10 @@ var dist embed.FS
1010
func NewWebDist() (embed.FS, string) {
1111
return dist, "dist"
1212
}
13+
14+
//go:embed tiles
15+
var tiles embed.FS
16+
17+
func NewMapTiles() (embed.FS, string) {
18+
return tiles, "tiles"
19+
}

web/src/.env.development

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ VITE_APP_BACKEND_BASE_HOST=http://127.0.0.1:8073
44
VITE_APP_RESTFUL_API_BASE_PATH=/api
55
VITE_APP_WEBSOCKET_API_ENDPOINT=/api/socket
66
VITE_APP_GRAPHQL_API_ENDPOINT=/api/graphql
7-
VITE_APP_MAPTILES_API_ENDPOINT=/tiles
7+
VITE_APP_MAPTILES_API_ENDPOINT=/api/tiles
88
VITE_APP_CTA_MESSAGE_DATA_BASE_URL=http://127.0.0.1:8000/cta-messages

0 commit comments

Comments
 (0)