Skip to content

Commit

Permalink
feat: add checkorverify to optionally verify signature
Browse files Browse the repository at this point in the history
  • Loading branch information
jaspeen committed Jun 15, 2024
1 parent 0c5fe3c commit abfd0c6
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 10 deletions.
5 changes: 4 additions & 1 deletion api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,10 @@ func (a *Api) Routes(prefix string) *gin.Engine {

// check api key and validate body signature
// only for POST, PUT, PATCH
v1.POST("/verify", a.Verify)
v1.Match([]string{"POST", "PUT", "PATCH"}, "/verify", a.Verify)

// similar to verify, but it will considered as valid if no signature is present
v1.Match([]string{"POST", "PUT", "PATCH"}, "/checkorverify", a.CheckOrVerify)

manage := v1.Group("/apikeys")
// create new api key
Expand Down
46 changes: 43 additions & 3 deletions api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,8 @@ func TestCreateApiKey(t *testing.T) {

// check api key
var checkResp struct {
Sub string `json:"sub"`
Sub string `json:"sub"`
Verified *bool `json:"verified,omitempty"`
}
t.Run("check", func(t *testing.T) {
w = httptest.NewRecorder()
Expand All @@ -144,10 +145,11 @@ func TestCreateApiKey(t *testing.T) {
err = json.Unmarshal(w.Body.Bytes(), &checkResp)
require.Nil(t, err)
assert.Equal(t, "testsub", checkResp.Sub)
assert.Nil(t, checkResp.Verified)
})

// validate api key
t.Run("validate", func(t *testing.T) {
// verify api key
t.Run("verify", func(t *testing.T) {
w = httptest.NewRecorder()
req, _ = http.NewRequest("POST", "/verify", strings.NewReader("testdata"))
req.Header.Set(api.API_KEY_DEFAULT_HEADER, resp.ApiKey)
Expand All @@ -162,7 +164,45 @@ func TestCreateApiKey(t *testing.T) {

router.ServeHTTP(w, req)
require.Equal(t, 200, w.Code)
err = json.Unmarshal(w.Body.Bytes(), &checkResp)
require.Nil(t, err)
assert.Equal(t, "testsub", checkResp.Sub)
assert.True(t, *checkResp.Verified)
})

// checkverify
t.Run("checkverify", func(t *testing.T) {
w = httptest.NewRecorder()
req, _ = http.NewRequest("POST", "/checkorverify", strings.NewReader("testdata"))
req.Header.Set(api.API_KEY_DEFAULT_HEADER, resp.ApiKey)

privateKeyBytes, err := algo.Base64ToKey(resp.PrivateKey)
require.Nil(t, err)
timestampStr := fmt.Sprintf("%d", time.Now().Unix())
req.Header.Set(api.TIMESTAMP_DEFAULT_HEADER, timestampStr)
signatureBytes, err := algo.GetSignAlgorithm("ES256").Sign(privateKeyBytes, append([]byte("testdata"), []byte(timestampStr)...))
require.Nil(t, err)
req.Header.Set(api.SIGNATURE_DEFAULT_HEADER, base64.StdEncoding.EncodeToString(signatureBytes))

router.ServeHTTP(w, req)
require.Equal(t, 200, w.Code)
err = json.Unmarshal(w.Body.Bytes(), &checkResp)
require.Nil(t, err)
assert.Equal(t, "testsub", checkResp.Sub)
assert.True(t, *checkResp.Verified)
})

t.Run("checkverify nosig", func(t *testing.T) {
w = httptest.NewRecorder()
req, _ = http.NewRequest("POST", "/checkorverify", strings.NewReader("testdata"))
req.Header.Set(api.API_KEY_DEFAULT_HEADER, resp.ApiKey)

router.ServeHTTP(w, req)
require.Equal(t, 200, w.Code)
err = json.Unmarshal(w.Body.Bytes(), &checkResp)
require.Nil(t, err)
assert.Equal(t, "testsub", checkResp.Sub)
assert.False(t, *checkResp.Verified)
})

// get api key
Expand Down
26 changes: 20 additions & 6 deletions api/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ func (a *Api) checkAndGetApiKeyData(c *gin.Context) (*queries.GetApiKeyForVerify
}

type checkResponse struct {
Id string `json:"id"`
Sub string `json:"sub"`
Extra json.RawMessage `json:"extra,omitempty"`
Id string `json:"id"`
Sub string `json:"sub"`
Extra json.RawMessage `json:"extra,omitempty"`
Verified *bool `json:"verified,omitempty"`
}

func (a *Api) Check(c *gin.Context) {
Expand All @@ -74,7 +75,7 @@ func (a *Api) Check(c *gin.Context) {
}
}

func (a *Api) Verify(c *gin.Context) {
func (a *Api) verifyInternal(c *gin.Context, okIfNoSignature bool) {
apiKeyData, err := a.checkAndGetApiKeyData(c)
if err != nil {
slog.Debug(fmt.Sprintf("Failed to load api key: %s", err))
Expand All @@ -85,7 +86,12 @@ func (a *Api) Verify(c *gin.Context) {
signature := c.Request.Header.Get(SIGNATURE_DEFAULT_HEADER)
if signature == "" {
slog.Debug("Signature is empty")
respondUnauthorized(c)
if okIfNoSignature {
verified := false
c.JSON(200, checkResponse{Id: strconv.Itoa(int(apiKeyData.ID)), Sub: apiKeyData.Sub.String, Extra: apiKeyData.Extra.RawMessage, Verified: &verified})
} else {
respondUnauthorized(c)
}
return
}
timestampStr := c.Request.Header.Get(TIMESTAMP_DEFAULT_HEADER)
Expand Down Expand Up @@ -138,6 +144,14 @@ func (a *Api) Verify(c *gin.Context) {
respondUnauthorized(c)
return
}
verified := true
c.JSON(200, checkResponse{Id: strconv.Itoa(int(apiKeyData.ID)), Sub: apiKeyData.Sub.String, Extra: apiKeyData.Extra.RawMessage, Verified: &verified})
}

func (a *Api) Verify(c *gin.Context) {
a.verifyInternal(c, false)
}

c.JSON(200, checkResponse{Id: strconv.Itoa(int(apiKeyData.ID)), Sub: apiKeyData.Sub.String, Extra: apiKeyData.Extra.RawMessage})
func (a *Api) CheckOrVerify(c *gin.Context) {
a.verifyInternal(c, true)
}

0 comments on commit abfd0c6

Please sign in to comment.