Skip to content

Commit

Permalink
feat(spike): oapi codegen prove of concept
Browse files Browse the repository at this point in the history
  • Loading branch information
chris-4chain committed Jan 20, 2025
1 parent b06ac5a commit 4c0009e
Show file tree
Hide file tree
Showing 13 changed files with 435 additions and 2 deletions.
20 changes: 20 additions & 0 deletions actions/server_implementation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package actions

import (
"github.com/bitcoin-sv/spv-wallet/api"
)

// optional code omitted

// Server is the implementation of the server oapi-codegen's interface
type Server struct {
AdminServer
UserServer
}

var _ api.ServerInterface = &Server{}

// NewServer creates a new server
func NewServer() Server {
return Server{}
}
18 changes: 18 additions & 0 deletions actions/testapi_admin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package actions

import (
"github.com/bitcoin-sv/spv-wallet/api"
"github.com/gin-gonic/gin"
)

// AdminServer is the partial implementation of the server oapi-codegen's interface
// NOTE: This is showcase how to split Server implementation into multiple packages/structs/files
type AdminServer struct{}

// GetAdminTestapi is the implementation of the corresponding endpoint
func (AdminServer) GetAdminTestapi(c *gin.Context) {
c.JSON(200, api.ExResponse{
XpubID: "admin",
Something: "something",
})
}
52 changes: 52 additions & 0 deletions actions/testapi_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package actions_test

import (
"testing"

"github.com/bitcoin-sv/spv-wallet/actions/testabilities"
testengine "github.com/bitcoin-sv/spv-wallet/engine/testabilities"
"github.com/bitcoin-sv/spv-wallet/engine/tester/fixtures"
)

func TestUserCurrent(t *testing.T) {
// given:
given, then := testabilities.New(t)
cleanup := given.StartedSPVWalletWithConfiguration(
testengine.WithNewTransactionFlowEnabled(),
)
defer cleanup()

t.Run("For user", func(t *testing.T) {
// given:
client := given.HttpClient().ForUser()

// when:
res, _ := client.R().Get("/api/v2/testapi?something=1")

// then:
then.Response(res).
IsOK().
WithJSONMatching(`{
"xpubID": "{{ .id }}",
"something": "1"
}`, map[string]any{"id": fixtures.Sender.XPubID()})
})

t.Run("For admin", func(t *testing.T) {
// given:
client := given.HttpClient().ForAdmin()

// when:
res, _ := client.R().Get("/api/v2/admin/testapi")

// then:
then.Response(res).
IsOK().
WithJSONMatching(`{
"xpubID": "admin",
"something": "something"
}`, nil)

})

}
20 changes: 20 additions & 0 deletions actions/testapi_user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package actions

import (
"github.com/bitcoin-sv/spv-wallet/api"
"github.com/bitcoin-sv/spv-wallet/server/reqctx"
"github.com/gin-gonic/gin"
)

// UserServer is the implementation of the server oapi-codegen's interface
type UserServer struct{}

// GetTestapi is a handler for the GET /testapi endpoint.
func (UserServer) GetTestapi(c *gin.Context, request api.GetTestapiParams) {
user := reqctx.GetUserContext(c)

c.JSON(200, api.ExResponse{
XpubID: user.GetXPubID(),
Something: request.Something,
})
}
56 changes: 56 additions & 0 deletions api/api.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
openapi: "3.0.0"
info:
version: 1.0.0
title: Minimal ping API server
paths:
/testapi:
get:
security:
- SignatureAuth:
- "user"
parameters:
- name: something
in: query
required: true
schema:
type: string
responses:
'200':
description: example response
content:
application/json:
schema:
$ref: '#/components/schemas/ExResponse'
/admin/testapi:
get:
security:
- SignatureAuth:
- "admin"
responses:
'200':
description: example response
content:
application/json:
schema:
$ref: '#/components/schemas/ExResponse'
components:
schemas:
# base types
ExResponse:
type: object
required:
- xpubID
- something
properties:
xpubID:
type: string
something:
type: string
securitySchemes:
SignatureAuth:
type: http
scheme: headers
security:
- SignatureAuth:
- admin
- user
50 changes: 50 additions & 0 deletions api/scopes_middleware.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package api

import (
"github.com/bitcoin-sv/spv-wallet/engine/spverrors"
"github.com/bitcoin-sv/spv-wallet/server/middleware"
"github.com/bitcoin-sv/spv-wallet/server/reqctx"
"github.com/gin-gonic/gin"
)

var securedMiddlewares = []MiddlewareFunc{
(MiddlewareFunc)(middleware.AuthMiddleware()),
(MiddlewareFunc)(middleware.CheckSignatureMiddleware()),
}

// SignatureAuthWithScopes checks for scopes and runs auth&signature middlewares
func SignatureAuthWithScopes() MiddlewareFunc {
return func(c *gin.Context) {
if scope, ok := c.Get(SignatureAuthScopes); ok {
var userType string
if scopes := scope.([]string); len(scopes) == 0 {
spverrors.ErrorResponse(c, spverrors.ErrMissingAuthHeader, reqctx.Logger(c))
return
} else {
userType = scopes[0]
}

if userType != "admin" && userType != "user" {
spverrors.ErrorResponse(c, spverrors.ErrAuthorization, reqctx.Logger(c))
return
}

for _, mid := range securedMiddlewares {
mid(c)
if c.IsAborted() {
return
}
}
userCtx := reqctx.GetUserContext(c)
if userType == "admin" && userCtx.AuthType != reqctx.AuthTypeAdmin {
spverrors.ErrorResponse(c, spverrors.ErrNotAnAdminKey, reqctx.Logger(c))
return
} else if userType == "user" && userCtx.AuthType == reqctx.AuthTypeAdmin {
spverrors.ErrorResponse(c, spverrors.ErrAdminAuthOnUserEndpoint, reqctx.Logger(c))
return
}
}

c.Next()
}
}
127 changes: 127 additions & 0 deletions api/spec.gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions go.mod

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 4c0009e

Please sign in to comment.