Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ FLAGS = -ldflags "\
"

test:
go test -cover ./...
go test ./...

run:
STATICS=statics/www/ go run $(FLAGS) ./cmd/inceptiondb/...
Expand Down
20 changes: 14 additions & 6 deletions api/0_build.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ import (
"github.com/fulldump/inceptiondb/statics"
)

func Build(s service.Servicer, staticsDir, version string) *box.B { // TODO: remove datadir
func Build(s service.Servicer, staticsDir, version, apiKey, apiSecret string, hideUI bool) *box.B { // TODO: remove datadir

b := box.NewBox()

v1 := b.Resource("/v1")
v1.WithInterceptors(box.SetResponseHeader("Content-Type", "application/json"))

if apiKey != "" && apiSecret != "" {
v1.WithInterceptors(
Authenticate(apiKey, apiSecret),
)
}

apicollectionv1.BuildV1Collection(v1, s).
WithInterceptors(
injectServicer(s),
Expand Down Expand Up @@ -58,11 +64,13 @@ func Build(s service.Servicer, staticsDir, version string) *box.B { // TODO: rem
return spec
})

// Mount statics
b.Resource("/*").
WithActions(
box.Get(statics.ServeStatics(staticsDir)).WithName("serveStatics"),
)
if !hideUI {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why hiding UI? Maybe we should allow to introduce input api-key/api-secret from the UI

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is done as well.

// Mount statics
b.Resource("/*").
WithActions(
box.Get(statics.ServeStatics(staticsDir)).WithName("serveStatics"),
)
}

return b
}
Expand Down
16 changes: 16 additions & 0 deletions api/0_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/fulldump/box"
Expand Down Expand Up @@ -50,6 +51,10 @@ func (p PrettyError) MarshalJSON() ([]byte, error) {
})
}

func (p PrettyError) MarshalTo(w io.Writer) error {
Copy link
Owner

@fulldump fulldump Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not needed

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree

return json.NewEncoder(w).Encode(p)
}

func InterceptorUnavailable(db *database.Database) box.I {
return func(next box.H) box.H {
return func(ctx context.Context) {
Expand Down Expand Up @@ -79,6 +84,17 @@ func PrettyErrorInterceptor(next box.H) box.H {
}
w := box.GetResponse(ctx)

if err == ErrUnauthorized {
w.WriteHeader(http.StatusUnauthorized)
json.NewEncoder(w).Encode(map[string]interface{}{
"error": map[string]interface{}{
"message": err.Error(),
"description": fmt.Sprintf("user is not authenticated"),
},
})
return
}

if err == box.ErrResourceNotFound {
w.WriteHeader(http.StatusNotFound)
json.NewEncoder(w).Encode(map[string]interface{}{
Expand Down
25 changes: 25 additions & 0 deletions api/0_interceptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
"context"
"errors"
"fmt"
"log"
"net/http"
Expand All @@ -12,6 +13,30 @@ import (
"github.com/fulldump/box"
)

var ErrUnauthorized = errors.New("unauthorized")

func Authenticate(apiKey, apiSecret string) box.I {
return func(next box.H) box.H {
return func(ctx context.Context) {

if apiKey == "" && apiSecret == "" {
Copy link
Owner

@fulldump fulldump Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why letting the user pass if no credentials?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If InceptionDB has been started without api-key then we let the user continue. Some other system will take care of this, or it is a none credentials needed scenario (testing).

It is not mandatory, as it is not in the current version.

next(ctx)
return
}

r := box.GetRequest(ctx)
key := r.Header.Get("X-Api-Key")
secret := r.Header.Get("X-Api-Secret")

if key != apiKey || secret != apiSecret {
box.SetError(ctx, ErrUnauthorized)
return
}
next(ctx)
}
}
}

func RecoverFromPanic(next box.H) box.H {
return func(ctx context.Context) {
defer func() {
Expand Down
2 changes: 1 addition & 1 deletion api/acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func TestAcceptance(t *testing.T) {

s := service.NewService(db)

b := Build(s, "", "test")
b := Build(s, "", "test", "", "", false)
b.WithInterceptors(
InterceptorUnavailable(db),
RecoverFromPanic,
Expand Down
70 changes: 70 additions & 0 deletions api/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package api

import (
"net/http"
"testing"

"github.com/fulldump/apitest"
"github.com/fulldump/biff"

"github.com/fulldump/inceptiondb/database"
"github.com/fulldump/inceptiondb/service"
)

func TestAuthentication(t *testing.T) {

biff.Alternative("Authentication", func(a *biff.A) {

db := database.NewDatabase(&database.Config{
Dir: t.TempDir(),
})

s := service.NewService(db)

apiKey := "my-key"
apiSecret := "my-secret"

b := Build(s, "", "test", apiKey, apiSecret, false)
b.WithInterceptors(
PrettyErrorInterceptor,
)

api := apitest.NewWithHandler(b)

a.Alternative("Missing headers", func(a *biff.A) {
resp := api.Request("GET", "/v1/collections").Do()
biff.AssertEqual(resp.StatusCode, http.StatusUnauthorized)
biff.AssertEqualJson(resp.BodyJson(), map[string]any{
"error": map[string]any{
"message": "unauthorized",
"description": "user is not authenticated",
},
})
})

a.Alternative("Wrong Key", func(a *biff.A) {
resp := api.Request("GET", "/v1/collections").
WithHeader("X-Api-Key", "wrong-key").
WithHeader("X-Api-Secret", apiSecret).
Do()
biff.AssertEqual(resp.StatusCode, http.StatusUnauthorized)
})

a.Alternative("Wrong Secret", func(a *biff.A) {
resp := api.Request("GET", "/v1/collections").
WithHeader("X-Api-Key", apiKey).
WithHeader("X-Api-Secret", "wrong-secret").
Do()
biff.AssertEqual(resp.StatusCode, http.StatusUnauthorized)
})

a.Alternative("Correct credentials", func(a *biff.A) {
resp := api.Request("GET", "/v1/collections").
WithHeader("X-Api-Key", apiKey).
WithHeader("X-Api-Secret", apiSecret).
Do()
biff.AssertEqual(resp.StatusCode, http.StatusOK)
})

})
}
3 changes: 2 additions & 1 deletion bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ func Bootstrap(c *configuration.Configuration) (start, stop func()) {
Dir: c.Dir,
})

b := api.Build(service.NewService(db), c.Statics, VERSION)
// b := api.Build(service.NewService(db), c.Statics, VERSION)
b := api.Build(service.NewService(db), c.Statics, VERSION, c.ApiKey, c.ApiSecret, c.HideUI)
if c.EnableCompression {
b.WithInterceptors(api.Compression)
}
Expand Down
5 changes: 5 additions & 0 deletions cmd/inceptiondb/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"fmt"
"log"
"os"

"github.com/fulldump/goconfig"
Expand Down Expand Up @@ -42,6 +43,10 @@ func main() {
e.Encode(c)
}

if c.ApiKey == "" || c.ApiSecret == "" {
Copy link
Owner

@fulldump fulldump Dec 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldnt put a warning, just a log saying Auth: disabled or enabled

log.Println("ApiKey and ApiSecret are not set, authentication will be disabled")
}

start, _ := bootstrap.Bootstrap(c)
start()
}
3 changes: 3 additions & 0 deletions configuration/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ type Configuration struct {
ShowBanner bool `usage:"show big banner"`
ShowConfig bool `usage:"print config"`
EnableCompression bool `usage:"enable http compression (gzip)"`
ApiKey string `usage:"API Key for v2 authentication"`
ApiSecret string `usage:"API Secret for v2 authentication"`
HideUI bool `usage:"do not serve UI"`
}
Loading