Skip to content

Commit

Permalink
added account manager
Browse files Browse the repository at this point in the history
  • Loading branch information
jhoe123 committed Jun 2, 2022
1 parent 54f9ff6 commit 43a75a5
Show file tree
Hide file tree
Showing 14 changed files with 347 additions and 3 deletions.
9 changes: 9 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,15 @@
"mode": "debug",
"program": "${workspaceFolder}/sample/simple",
"cwd": "${workspaceFolder}/sample/simple/build"
},
{
"name": "Launch Account Manager",
"type": "go",
"request": "launch",
"mode": "debug",
"cwd": "${workspaceFolder}/internal/builds/linux/account_manager",
"program": "${workspaceFolder}/internal/cwd/account_manager",
"buildFlags": "-tags=IDE"
}
]
}
3 changes: 2 additions & 1 deletion foundation/app/rpc/responseUtils.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import (
const SUCCESS_CODE = 200
const SYSTEMERR_CODE = 400 // theres something wrong with the system
const INVALID_CODE = 401
const NOT_IMPLEMENTED = 300 // code was not implemented
const NOT_IMPLEMENTED = 300 // code was not implemented
const INVALID_PARAMETER_PROVIDED = 402 // parameters was invalid

// return json string for response
func CreateResponse(code int16, msg string) string {
Expand Down
4 changes: 2 additions & 2 deletions foundation/event/data/actionGroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ import (
)

type ActionGroup struct {
Activity *Action `json:"activity"`
Service *Action `json:"service"`
Activity *Action `json:"activity,omitempty"`
Service *Action `json:"service,omitempty"`
//Broadcasts []Action
}

Expand Down
19 changes: 19 additions & 0 deletions internal/builds/linux/account_manager/info.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "Account Manager",
"description": "Service that manage Elabox account and account related authentications.",
"packageId": "ela.account",
"build": 1,
"version": "0.1.0",
"program": "ela.account",
"programArgs": null,
"activityGroup": {
"customLink": "",
"customPort": 0,
"activities": null
},
"permissions": [],
"exportService": true,
"location": "system",
"nodejs": false,
"packagerVersion": "0.1.0"
}
8 changes: 8 additions & 0 deletions internal/builds/linux/account_manager/packager.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"cwd": "",
"config": "info.json",
"binDir": "",
"bin": "./bin/ela.account",
"packages": [],
"customInstaller": ""
}
1 change: 1 addition & 0 deletions internal/builds/linux/system/packager.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"../../../../../elabox-logs/build/ela.logs.box",
"../../../../../elabox-rewards/build/ela.rewards.box",
"../packageinstaller/ela.installer.box",
"../account_manager/ela.account.box",
"../companion/ela.companion.box",
"../carrier/ela.carrier.box",
"../../../../../elabox-dapp-store/build/ela.store/ela.store.box",
Expand Down
12 changes: 12 additions & 0 deletions internal/cwd/account_manager/account.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

type Account struct {
Address string `json:"address"`
}

func RetrievePrimaryAccountDetails() map[string]interface{} {
result := make(map[string]interface{})
wallet, _ := LoadWalletAddr()
result["wallet"] = wallet
return result
}
91 changes: 91 additions & 0 deletions internal/cwd/account_manager/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package main

import (
"crypto/sha256"
"errors"
"os"
"os/exec"
"strings"

"github.com/cansulting/elabox-system-tools/foundation/perm"
"github.com/cansulting/elabox-system-tools/foundation/system"
)

const SHADOW_FILE = "/etc/shadow"

// check authorization for specific DID, return true if DID is authorized
func AuthenticateDid(did string) bool {
deviceSerial := system.GetDeviceInfo().Serial
hash := sha256.Sum256([]byte(did + deviceSerial))
// step: load the currently saved did hash
savedHash, err := os.ReadFile(DID_HASH_PATH)
if err != nil {
return false
}
if string(hash[:]) != string(savedHash) {
return false
}
return true
}

func IsDidSetup() bool {
if _, err := os.Stat(DID_HASH_PATH); err != nil {
return false
}
return true
}

// use to authenticate specific password
func AuthenticateSystemAccount(username string, password string) (error, bool) {
contents, err := os.ReadFile(SHADOW_FILE)
if err != nil {
return err, false
}
hashContent := Grep(username, string(contents))
// unable to find specific user account
if hashContent == "" {
return nil, false
}
creds := strings.Split(hashContent, "$")
salt := creds[2]
savedHash := strings.Split(hashContent, ":")[1]
encryptType := creds[1]
// generate hash
cmd := exec.Command(
"/usr/bin/openssl",
"passwd", "-"+encryptType,
"-salt", salt,
password,
)
hash, err := cmd.CombinedOutput()
if err != nil {
return err, false
}
// password is correct
strHash := string(hash)
strHash = strings.TrimRight(strHash, "\n")
if savedHash == strHash {
return nil, true
}
return nil, false
}

// set the current device did
func SetDeviceDid(presentation map[string]interface{}) error {
// step: validate presentation
if presentation["holder"] == nil {
return errors.New("no holder provider in presentation")
}
// step: create hash
did := presentation["holder"].(string)
deviceSerial := system.GetDeviceInfo().Serial
hash := sha256.Sum256([]byte(did + deviceSerial))
// step: save to file
if err := os.MkdirAll(DID_DATA_DIR, perm.PUBLIC_WRITE); err != nil {
return err
}
if err := os.WriteFile(DID_HASH_PATH, hash[:], perm.PUBLIC_VIEW); err != nil {
return err
}
return nil
}
16 changes: 16 additions & 0 deletions internal/cwd/account_manager/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package main

import "github.com/cansulting/elabox-system-tools/foundation/app"

// actions
const AC_AUTH_DID = "account.actions.AUTH_DID" // use to authenticaticate specific did
const AC_SETUP_CHECK = "account.actions.DID_SETUP_CHECK" // use to check if theres an existing did setup
const AC_SETUP_DID = "account.actions.DID_SETUP" // use to setup did

const PACKAGE_ID = "ela.account"
const HOME_DIR = "/home/elabox"
const DID_DATA_DIR = HOME_DIR + "/data/" + PACKAGE_ID
const DID_HASH_PATH = DID_DATA_DIR + "/did.dat"
const KEYSTORE_PATH = "/home/elabox/documents/ela.mainchain/keystore.dat"

var Controller *app.Controller
12 changes: 12 additions & 0 deletions internal/cwd/account_manager/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package main

import "github.com/cansulting/elabox-system-tools/foundation/app"

func main() {
con, err := app.NewController(nil, &MyService{})
if err != nil {
panic(err)
}
Controller = con
app.RunApp(con)
}
31 changes: 31 additions & 0 deletions internal/cwd/account_manager/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package main

import "testing"

const SAMPLE_DID = "HELLO_DID_USER"
const SAMPLE_USERNAME = "elabox"
const SAMPLE_PASSWORD = "elabox"

// use to test device did
func Test_SetDeviceDid(t *testing.T) {
presentation := make(map[string]interface{})
presentation["holder"] = SAMPLE_DID
if err := SetDeviceDid(presentation); err != nil {
t.Error(err)
}
}

func Test_AuthenticationDid(t *testing.T) {
if !AuthenticateDid(SAMPLE_DID) {
t.Error("failed to authenticate did")
}
}

func Test_AuthenticateSystemAccount(t *testing.T) {
err, success := AuthenticateSystemAccount(SAMPLE_USERNAME, SAMPLE_PASSWORD)
if err != nil {
t.Error(err)
return
}
t.Log("Authenticate", success)
}
87 changes: 87 additions & 0 deletions internal/cwd/account_manager/myservice.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package main

import (
"github.com/cansulting/elabox-system-tools/foundation/app/rpc"
"github.com/cansulting/elabox-system-tools/foundation/event/data"
"github.com/cansulting/elabox-system-tools/foundation/event/protocol"
)

type MyService struct {
}

func (instance *MyService) IsRunning() bool {
return true
}

func (instance *MyService) OnStart() error {
Controller.RPC.OnRecieved(AC_AUTH_DID, instance.onAuthDidAction)
Controller.RPC.OnRecieved(AC_SETUP_CHECK, instance.onCheckSetup)
Controller.RPC.OnRecieved(AC_SETUP_DID, instance.onSetupDid)
return nil
}

func (instance *MyService) OnEnd() error {
return nil
}

// authorize user given the did presentation, upon success returns JWT token
func (instance *MyService) onAuthDidAction(client protocol.ClientInterface, action data.Action) string {
presentation, err := action.DataToMap()
// step: validate presentation
if err != nil {
return rpc.CreateResponse(rpc.INVALID_CODE, "invalid did presentation provided, "+err.Error())
}
if presentation["holder"] == nil {
return rpc.CreateResponse(rpc.INVALID_CODE, "no holder was provided")
}
// step: compare with the existing hash
did := presentation["holder"].(string)
if AuthenticateDid(did) {
acc := RetrievePrimaryAccountDetails()
return rpc.CreateJsonResponse(rpc.SUCCESS_CODE, acc)
} else {
return rpc.CreateResponse(rpc.INVALID_CODE, "incorrect did credentials")
}
}

// use to check whether the elabox was setup already. return "setup" if already setup
func (instance *MyService) onCheckSetup(client protocol.ClientInterface, action data.Action) string {
if IsDidSetup() {
return rpc.CreateSuccessResponse("setup")
} else {
return rpc.CreateSuccessResponse("not_setup")
}
}

// use to setup did, requires did presentation, username and password
func (instance *MyService) onSetupDid(client protocol.ClientInterface, action data.Action) string {
acData, err := action.DataToMap()
// step: validate inputs
if err != nil {
return rpc.CreateResponse(rpc.INVALID_PARAMETER_PROVIDED, "invalid parameters provided, "+err.Error())
}
if acData["presentation"] == nil {
return rpc.CreateResponse(rpc.INVALID_PARAMETER_PROVIDED, "no presentation provided")
}

// step: authenticate for existing did setup
if IsDidSetup() {
if acData["username"] == nil || acData["password"] == nil {
return rpc.CreateResponse(rpc.INVALID_PARAMETER_PROVIDED, "username and password is required")
}
pass := acData["password"].(string)
username := acData["username"].(string)
err, success := AuthenticateSystemAccount(username, pass)
if err != nil {
return rpc.CreateResponse(rpc.SYSTEMERR_CODE, err.Error())
}
if !success {
return rpc.CreateResponse(rpc.INVALID_PARAMETER_PROVIDED, "password or username is incorrect")
}
}
presentation := acData["presentation"].(map[string]interface{})
if err := SetDeviceDid(presentation); err != nil {
return rpc.CreateResponse(rpc.SYSTEMERR_CODE, "failed to setup did, "+err.Error())
}
return rpc.CreateSuccessResponse("success")
}
49 changes: 49 additions & 0 deletions internal/cwd/account_manager/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package main

import (
"encoding/json"
"os"
"strings"
)

type Address struct {
Address string `json:"Address"`
ProgramHash string `json:"ProgramHash"`
}

type KeyStore struct {
Account []Address `json:"Account"`
}

func Grep(keyword string, src string) string {
splits := strings.Split(src, "\n")
keywordbt := []byte(keyword)
for _, line := range splits {
if len(line) >= len(keywordbt) {
found := true
for i, keywordC := range keywordbt {
if keywordC != line[i] {
found = false
break
}
}
if found {
return line
}
}
}
// nothing was found
return ""
}

// use to retrieve wallet address from keystore path
func LoadWalletAddr() (string, error) {
dat, err := os.ReadFile(KEYSTORE_PATH)
if err != nil {
return "", err
}

var keyStore KeyStore
_ = json.Unmarshal(dat, &keyStore)
return keyStore.Account[0].Address, nil
}
Loading

0 comments on commit 43a75a5

Please sign in to comment.