-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0786f42
commit 599cf80
Showing
8 changed files
with
240 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
dev: | ||
go run main.go -bind :9999 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,21 @@ | ||
# reflector | ||
Dogebox reflector service | ||
# Dogebox reflector service | ||
|
||
This service is a basic in-memory key-pair cache. It is used by the Dogebox (by default) to persist its local, internal IP address somewhere that the users client can find it once the Dogebox has switched networks. | ||
|
||
### API | ||
|
||
#### GET /:token | ||
|
||
Fetch the IP submitted via token. Is a one-shot fetch, will be removed after you have retrieved it. | ||
|
||
Return `200` `{ "ip": "1.2.3.4" }` if found. | ||
|
||
Returns `404` if not found. | ||
|
||
#### POST / | ||
|
||
Required body: `{ "token": "abc", "ip": "1.2.3.4" }` | ||
|
||
Returns `201` on create. | ||
|
||
Rate limited to 1 per minute via IP. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
module github.com/dogeorg/reflector | ||
|
||
go 1.23.1 | ||
|
||
require ( | ||
github.com/go-chi/chi/v5 v5.1.0 | ||
github.com/patrickmn/go-cache v2.1.0+incompatible | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
github.com/go-chi/chi/v5 v5.1.0 h1:acVI1TYaD+hhedDJ3r54HyA6sExp3HfXq7QWEEY/xMw= | ||
github.com/go-chi/chi/v5 v5.1.0/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= | ||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= | ||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package main | ||
|
||
import ( | ||
"flag" | ||
"log" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/dogeorg/reflector/pkg/api" | ||
"github.com/dogeorg/reflector/pkg/database" | ||
reflectormiddleware "github.com/dogeorg/reflector/pkg/middleware" | ||
"github.com/go-chi/chi/v5" | ||
"github.com/go-chi/chi/v5/middleware" | ||
) | ||
|
||
func main() { | ||
// Initialize database | ||
db, err := database.NewDatabase() | ||
if err != nil { | ||
log.Fatalf("Failed to initialize database: %v", err) | ||
} | ||
defer db.Close() | ||
|
||
// Create router | ||
r := chi.NewRouter() | ||
|
||
// Middleware | ||
r.Use(middleware.Logger) | ||
r.Use(middleware.Recoverer) | ||
|
||
// Routes | ||
r.Post("/", func(w http.ResponseWriter, r *http.Request) { | ||
reflectormiddleware.RateLimiter(time.Minute, 1)(api.CreateEntry(db)).ServeHTTP(w, r) | ||
}) | ||
|
||
r.Get("/{token}", api.GetIP(db)) | ||
|
||
// Parse command-line arguments | ||
bindAddr := flag.String("bind", ":8080", "Bind address and port for the server") | ||
flag.Parse() | ||
|
||
// Start server | ||
log.Printf("Server starting on %s\n", *bindAddr) | ||
log.Fatal(http.ListenAndServe(*bindAddr, r)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package api | ||
|
||
import ( | ||
"encoding/json" | ||
"net/http" | ||
"regexp" | ||
|
||
"github.com/dogeorg/reflector/pkg/database" | ||
"github.com/go-chi/chi/v5" | ||
) | ||
|
||
type Entry struct { | ||
Token string `json:"token"` | ||
IP string `json:"ip"` | ||
} | ||
|
||
func CreateEntry(db *database.Database) http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
var entry Entry | ||
if err := json.NewDecoder(r.Body).Decode(&entry); err != nil { | ||
http.Error(w, "Invalid JSON payload", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
if !isValidToken(entry.Token) || !isValidIP(entry.IP) { | ||
http.Error(w, "Invalid token or IP format", http.StatusBadRequest) | ||
return | ||
} | ||
|
||
if err := db.SaveEntry(entry.Token, entry.IP); err != nil { | ||
http.Error(w, "Failed to save entry", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
w.WriteHeader(http.StatusCreated) | ||
} | ||
} | ||
|
||
func GetIP(db *database.Database) http.HandlerFunc { | ||
return func(w http.ResponseWriter, r *http.Request) { | ||
token := chi.URLParam(r, "token") | ||
|
||
ip, err := db.GetIP(token) | ||
if err != nil { | ||
http.Error(w, "IP not found", http.StatusNotFound) | ||
return | ||
} | ||
|
||
if err := db.DeleteEntry(token); err != nil { | ||
http.Error(w, "Failed to remove entry", http.StatusInternalServerError) | ||
return | ||
} | ||
|
||
w.Header().Set("Content-Type", "application/json") | ||
json.NewEncoder(w).Encode(map[string]string{"ip": ip}) | ||
} | ||
} | ||
|
||
func isValidToken(token string) bool { | ||
return len(token) <= 20 | ||
} | ||
|
||
func isValidIP(ip string) bool { | ||
ipPattern := `^(\d{1,3}\.){3}\d{1,3}$` | ||
match, _ := regexp.MatchString(ipPattern, ip) | ||
return match && len(ip) <= 15 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package database | ||
|
||
import ( | ||
"fmt" | ||
"sync" | ||
"time" | ||
|
||
"github.com/patrickmn/go-cache" | ||
) | ||
|
||
type Database struct { | ||
cache *cache.Cache | ||
mu sync.Mutex | ||
} | ||
|
||
func NewDatabase() (*Database, error) { | ||
c := cache.New(2*time.Hour, 10*time.Minute) | ||
return &Database{cache: c}, nil | ||
} | ||
|
||
func (db *Database) Close() error { | ||
// No need to close in-memory cache | ||
return nil | ||
} | ||
|
||
func (db *Database) SaveEntry(token, ip string) error { | ||
db.mu.Lock() | ||
defer db.mu.Unlock() | ||
db.cache.Set(token, ip, cache.DefaultExpiration) | ||
return nil | ||
} | ||
|
||
func (db *Database) DeleteEntry(token string) error { | ||
db.mu.Lock() | ||
defer db.mu.Unlock() | ||
db.cache.Delete(token) | ||
return nil | ||
} | ||
|
||
func (db *Database) GetIP(token string) (string, error) { | ||
db.mu.Lock() | ||
defer db.mu.Unlock() | ||
ip, found := db.cache.Get(token) | ||
if !found { | ||
return "", fmt.Errorf("IP not found for token: %s", token) | ||
} | ||
return ip.(string), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package reflectormiddleware | ||
|
||
import ( | ||
"net/http" | ||
"sync" | ||
"time" | ||
) | ||
|
||
func RateLimiter(interval time.Duration, limit int) func(http.Handler) http.Handler { | ||
type client struct { | ||
count int | ||
lastSeen time.Time | ||
} | ||
|
||
var ( | ||
mu sync.Mutex | ||
clients = make(map[string]*client) | ||
) | ||
|
||
return func(next http.Handler) http.Handler { | ||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
ip := r.RemoteAddr | ||
|
||
mu.Lock() | ||
if _, found := clients[ip]; !found { | ||
clients[ip] = &client{} | ||
} | ||
c := clients[ip] | ||
now := time.Now() | ||
if now.Sub(c.lastSeen) > interval { | ||
c.count = 0 | ||
c.lastSeen = now | ||
} | ||
c.count++ | ||
if c.count > limit { | ||
mu.Unlock() | ||
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests) | ||
return | ||
} | ||
mu.Unlock() | ||
|
||
next.ServeHTTP(w, r) | ||
}) | ||
} | ||
} |