258 lines
6.3 KiB
Go
258 lines
6.3 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
|
|
|
"github.com/go-oauth2/oauth2/v4"
|
|
"github.com/go-oauth2/oauth2/v4/errors"
|
|
"github.com/go-oauth2/oauth2/v4/manage"
|
|
"github.com/go-oauth2/oauth2/v4/models"
|
|
"github.com/go-oauth2/oauth2/v4/server"
|
|
"github.com/go-oauth2/oauth2/v4/store"
|
|
|
|
"log"
|
|
"log/slog"
|
|
"net/http"
|
|
)
|
|
|
|
type IBlockchainStorage interface {
|
|
GetByID(ctx context.Context, id string) (oauth2.ClientInfo, error) // read
|
|
Set(clt oauth2.ClientInfo) error // create and update
|
|
Delete(id string) error // delete
|
|
CheckPassword(id string, secret string) (bool, error) // CheckUser
|
|
}
|
|
|
|
type BlockchainStorage struct {
|
|
contract *Contract
|
|
}
|
|
|
|
type StorageClientInfo struct {
|
|
id string
|
|
secret string
|
|
}
|
|
|
|
func NewBlockchainStorage(actor Actor, hash util.Uint160) *BlockchainStorage {
|
|
return &BlockchainStorage{contract: New(actor, hash)}
|
|
}
|
|
|
|
func (storage BlockchainStorage) GetByID(ctx context.Context, id string) (oauth2.ClientInfo, error) {
|
|
password, err := storage.contract.GetUser(id)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &StorageClientInfo{id: id, secret: password.StringLE()}, nil
|
|
}
|
|
|
|
func (storage BlockchainStorage) Set(clt oauth2.ClientInfo) error {
|
|
password, decodeErr := util.Uint256DecodeStringLE(clt.GetSecret())
|
|
if decodeErr != nil {
|
|
return decodeErr
|
|
}
|
|
|
|
_, _, err := storage.contract.UpdateUser(clt.GetID(), password)
|
|
if err != nil {
|
|
_, _, err = storage.contract.CreateUser(clt.GetID(), password)
|
|
|
|
if err != nil {
|
|
return errors.New("Something went wrong")
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (storage BlockchainStorage) Delete(id string) error {
|
|
// should we use hash and ValidUntilBlock?
|
|
_, _, res := storage.contract.DeleteUser(id)
|
|
return res
|
|
}
|
|
|
|
func (storage BlockchainStorage) CheckPassword(id string, secret string) (bool, error) {
|
|
password, decodeErr := util.Uint256DecodeStringLE(secret)
|
|
if decodeErr != nil {
|
|
return false, decodeErr
|
|
}
|
|
|
|
_, err := storage.contract.CheckUser(id, password)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
func (model StorageClientInfo) GetID() string {
|
|
return model.id
|
|
}
|
|
|
|
func (model StorageClientInfo) GetSecret() string {
|
|
return model.secret
|
|
}
|
|
|
|
func (model StorageClientInfo) GetDomain() string {
|
|
return model.GetDomain()
|
|
}
|
|
|
|
func (model StorageClientInfo) IsPublic() bool {
|
|
return model.IsPublic()
|
|
}
|
|
|
|
func (model StorageClientInfo) GetUserID() string {
|
|
return model.GetUserID()
|
|
}
|
|
|
|
type InMemoryClient struct {
|
|
Id string
|
|
Domain string
|
|
IsPublic bool
|
|
}
|
|
|
|
var clients map[string]InMemoryClient
|
|
|
|
func addInMemoryClient(id string, domain string, isPublic bool) error {
|
|
result := InMemoryClient{
|
|
Id: id,
|
|
Domain: domain,
|
|
IsPublic: isPublic,
|
|
}
|
|
_, contains := clients[id]
|
|
if contains {
|
|
return fmt.Errorf("client with id %s already exist", id)
|
|
} else {
|
|
clients[id] = result
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func getInMemoryClient(id string) (*InMemoryClient, error) {
|
|
result, contains := clients[id]
|
|
if contains {
|
|
return &result, nil
|
|
} else {
|
|
return nil, fmt.Errorf("client with id %s not found", id)
|
|
}
|
|
}
|
|
|
|
func updateInMemoryClientById(id string, client InMemoryClient) error {
|
|
_, contains := clients[id]
|
|
if !contains || id != client.Id {
|
|
return fmt.Errorf("client with id %s not found", id)
|
|
} else {
|
|
clients[id] = client
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func deleteInMemoryClient(id string) {
|
|
delete(clients, id)
|
|
}
|
|
|
|
func main() {
|
|
manager := manage.NewDefaultManager()
|
|
manager.SetAuthorizeCodeTokenCfg(manage.DefaultAuthorizeCodeTokenCfg)
|
|
|
|
// contract integration
|
|
fileWallet, _ := wallet.NewWalletFromFile("somewhere")
|
|
defer fileWallet.Close()
|
|
|
|
rpcClient, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
|
|
rpcActor, _ := actor.NewSimple(rpcClient, fileWallet.Accounts[0])
|
|
|
|
// token memory store
|
|
// todo: Implement blockchain store
|
|
manager.MustTokenStorage(store.NewMemoryTokenStore())
|
|
|
|
// client memory store
|
|
// todo: Implement blockchain store
|
|
clientStore := store.NewClientStore()
|
|
|
|
manager.MapClientStorage(clientStore)
|
|
|
|
srv := server.NewDefaultServer(manager)
|
|
srv.SetAllowGetAccessRequest(true)
|
|
srv.SetClientInfoHandler(server.ClientFormHandler)
|
|
manager.SetRefreshTokenCfg(manage.DefaultRefreshTokenCfg)
|
|
|
|
srv.SetInternalErrorHandler(func(err error) (re *errors.Response) {
|
|
log.Println("Internal Error:", err.Error())
|
|
return
|
|
})
|
|
|
|
srv.SetResponseErrorHandler(func(re *errors.Response) {
|
|
log.Println("Response Error:", re.Error.Error())
|
|
})
|
|
|
|
http.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
|
|
srv.HandleTokenRequest(w, r)
|
|
|
|
id := r.Header.Get("client_id")
|
|
_, err := getInMemoryClient(id)
|
|
if err != nil {
|
|
slog.Warn("Client with id " + id + "not found")
|
|
w.WriteHeader(http.StatusBadRequest)
|
|
}
|
|
})
|
|
|
|
http.HandleFunc("/register", func(writer http.ResponseWriter, request *http.Request) {
|
|
id := request.Header.Get("client_id")
|
|
secret := request.Header.Get("client_secret")
|
|
|
|
// check whether client exists
|
|
_, err := clientStore.GetByID(context.Background(), id)
|
|
if err == nil {
|
|
slog.Warn("Client with id " + id + "already exists.")
|
|
writer.WriteHeader(http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// add client's credentials to blockchain
|
|
err = clientStore.Set(id, &models.Client{
|
|
ID: id,
|
|
Secret: secret,
|
|
})
|
|
|
|
if err != nil {
|
|
slog.Error("Failed to register user with id "+id+".", err)
|
|
writer.WriteHeader(http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
client, _ := clientStore.GetByID(context.Background(), id)
|
|
err = addInMemoryClient(
|
|
client.GetID(),
|
|
client.GetDomain(),
|
|
client.IsPublic())
|
|
if err != nil {
|
|
slog.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
writer.WriteHeader(http.StatusOK)
|
|
})
|
|
|
|
// for tests
|
|
http.HandleFunc("/protected", validateToken(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Write([]byte("Hello, I'm protected"))
|
|
}, srv))
|
|
|
|
log.Fatal(http.ListenAndServe(":9096", nil))
|
|
}
|
|
|
|
func validateToken(f http.HandlerFunc, srv *server.Server) http.HandlerFunc {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, err := srv.ValidationBearerToken(r)
|
|
if err != nil {
|
|
http.Error(w, err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
f.ServeHTTP(w, r)
|
|
})
|
|
}
|