-
-
Notifications
You must be signed in to change notification settings - Fork 4
Inception using Api Keys by config #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
66fef0a
3bc6c7d
eae05cd
3f32cca
ac8216c
5f8c036
f157305
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ import ( | |
| "context" | ||
| "encoding/json" | ||
| "fmt" | ||
| "io" | ||
| "net/http" | ||
|
|
||
| "github.com/fulldump/box" | ||
|
|
@@ -50,6 +51,10 @@ func (p PrettyError) MarshalJSON() ([]byte, error) { | |
| }) | ||
| } | ||
|
|
||
| func (p PrettyError) MarshalTo(w io.Writer) error { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not needed
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) { | ||
|
|
@@ -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{}{ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ package api | |
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "fmt" | ||
| "log" | ||
| "net/http" | ||
|
|
@@ -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 == "" { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why letting the user pass if no credentials?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() { | ||
|
|
||
| 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) | ||
| }) | ||
|
|
||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,6 +3,7 @@ package main | |
| import ( | ||
| "encoding/json" | ||
| "fmt" | ||
| "log" | ||
| "os" | ||
|
|
||
| "github.com/fulldump/goconfig" | ||
|
|
@@ -42,6 +43,10 @@ func main() { | |
| e.Encode(c) | ||
| } | ||
|
|
||
| if c.ApiKey == "" || c.ApiSecret == "" { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
| } | ||
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.