5cebbdee03
When an application wants to interact with resources on behalf of an end-user, it needs to be granted access. In OAuth, this is done when a user submits the consent screen. Change-Id: Id838772f76999f63f5c9dbdda0995697b41c123a
216 lines
6.3 KiB
Go
216 lines
6.3 KiB
Go
//go:build js && wasm
|
|
// +build js,wasm
|
|
|
|
// Copyright (C) 2020 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package main
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"syscall/js"
|
|
|
|
"github.com/zeebo/errs"
|
|
|
|
"storj.io/common/base58"
|
|
console "storj.io/storj/satellite/console/consolewasm"
|
|
)
|
|
|
|
func main() {
|
|
js.Global().Set("deriveAndEncryptRootKey", deriveAndEncryptRootKey())
|
|
js.Global().Set("generateAccessGrant", generateAccessGrant())
|
|
js.Global().Set("setAPIKeyPermission", setAPIKeyPermission())
|
|
js.Global().Set("newPermission", newPermission())
|
|
js.Global().Set("restrictGrant", restrictGrant())
|
|
<-make(chan bool)
|
|
}
|
|
|
|
// deriveAndEncryptRootKey derives the root key portion of the access grant and then encrypts it using the provided aes
|
|
// key. To ensure the key used in encryption is a proper length, we take a sha256 of the provided key and use it instead
|
|
// of using the key directly. Then we base58 encode the result and return it to the caller.
|
|
func deriveAndEncryptRootKey() js.Func {
|
|
return js.FuncOf(responseHandler(func(this js.Value, args []js.Value) (interface{}, error) {
|
|
if len(args) < 3 {
|
|
return nil, errs.New("not enough arguments. Need 3, but only %d supplied. The order of arguments are: encryption passphrase, project ID, and AES Key.", len(args))
|
|
}
|
|
|
|
encryptionPassphrase := args[0].String()
|
|
projectSalt := args[1].String()
|
|
aesKey := args[2].String()
|
|
|
|
rootKey, err := console.DeriveRootKey(encryptionPassphrase, projectSalt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
key := sha256.Sum256([]byte(aesKey))
|
|
cipher, err := aes.NewCipher(key[:])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
value := *(rootKey.Raw())
|
|
out := make([]byte, len(value))
|
|
cipher.Encrypt(out, value[:])
|
|
|
|
return base58.Encode(out), nil
|
|
}))
|
|
}
|
|
|
|
// generateAccessGrant creates a new access grant with the provided api key and encryption passphrase.
|
|
func generateAccessGrant() js.Func {
|
|
return js.FuncOf(responseHandler(func(this js.Value, args []js.Value) (interface{}, error) {
|
|
if len(args) < 4 {
|
|
return nil, errs.New("not enough arguments. Need 4, but only %d supplied. The order of arguments are: satellite Node URL, API key, encryption passphrase, and project ID.", len(args))
|
|
}
|
|
satelliteNodeURL := args[0].String()
|
|
apiKey := args[1].String()
|
|
encryptionPassphrase := args[2].String()
|
|
projectSalt := args[3].String()
|
|
|
|
access, err := console.GenAccessGrant(satelliteNodeURL,
|
|
apiKey,
|
|
encryptionPassphrase,
|
|
projectSalt,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return access, nil
|
|
}))
|
|
}
|
|
|
|
// setAPIKeyPermission creates a new api key with specific permissions.
|
|
func setAPIKeyPermission() js.Func {
|
|
return js.FuncOf(responseHandler(func(this js.Value, args []js.Value) (interface{}, error) {
|
|
if len(args) < 3 {
|
|
return nil, errs.New("not enough arguments. Need 3, but only %d supplied. The order of arguments are: API key, bucket names, and permission object.", len(args))
|
|
}
|
|
apiKey := args[0].String()
|
|
|
|
// convert array of bucket names to go []string type
|
|
buckets := args[1]
|
|
if ok := buckets.InstanceOf(js.Global().Get("Array")); !ok {
|
|
return nil, errs.New("invalid data type. Expect Array, Got %s", buckets.Type().String())
|
|
}
|
|
bucketNames, err := parseArrayOfStrings(buckets)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// convert js permission to go permission type
|
|
permissionJS := args[2]
|
|
if permissionJS.Type() != js.TypeObject {
|
|
return nil, errs.New("invalid argument type. Expect %s, Got %s", js.TypeObject.String(), permissionJS.Type().String())
|
|
}
|
|
permission, err := parsePermission(permissionJS)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
restrictedKey, err := console.SetPermission(apiKey, bucketNames, permission)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return restrictedKey.Serialize(), nil
|
|
}))
|
|
}
|
|
|
|
// restrictGrant restricts an access grant with the permissions and paths and returns a new access grant.
|
|
func restrictGrant() js.Func {
|
|
return js.FuncOf(responseHandler(func(this js.Value, args []js.Value) (interface{}, error) {
|
|
if len(args) < 3 {
|
|
return nil, errs.New("not enough arguments. Need 3, but only %d supplied. The order of arguments are: access grant, paths, and permission object.", len(args))
|
|
}
|
|
accessGrant := args[0].String()
|
|
|
|
// convert array of paths to go []string type
|
|
pathsObj := args[1]
|
|
if ok := pathsObj.InstanceOf(js.Global().Get("Array")); !ok {
|
|
return nil, errs.New("invalid data type. Expect Array, Got %s", pathsObj.Type().String())
|
|
}
|
|
paths, err := parseArrayOfStrings(pathsObj)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// convert js permission to go permission type
|
|
permissionJS := args[2]
|
|
if permissionJS.Type() != js.TypeObject {
|
|
return nil, errs.New("invalid argument type. Expect %s, Got %s", js.TypeObject.String(), permissionJS.Type().String())
|
|
}
|
|
permission, err := parsePermission(permissionJS)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return console.RestrictGrant(accessGrant, paths, permission)
|
|
}))
|
|
}
|
|
|
|
// newPermission creates a new permission object.
|
|
func newPermission() js.Func {
|
|
return js.FuncOf(responseHandler(func(this js.Value, args []js.Value) (interface{}, error) {
|
|
p, err := json.Marshal(console.Permission{})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var jsObj map[string]interface{}
|
|
if err = json.Unmarshal(p, &jsObj); err != nil {
|
|
return nil, err
|
|
}
|
|
return jsObj, nil
|
|
}))
|
|
}
|
|
|
|
func parsePermission(arg js.Value) (console.Permission, error) {
|
|
var permission console.Permission
|
|
|
|
// convert javascript object to a json string
|
|
jsJSON := js.Global().Get("JSON")
|
|
p := jsJSON.Call("stringify", arg)
|
|
|
|
if err := json.Unmarshal([]byte(p.String()), &permission); err != nil {
|
|
return permission, err
|
|
}
|
|
|
|
return permission, nil
|
|
}
|
|
|
|
func parseArrayOfStrings(arg js.Value) ([]string, error) {
|
|
data := make([]string, arg.Length())
|
|
for i := 0; i < arg.Length(); i++ {
|
|
data[i] = arg.Index(i).String()
|
|
}
|
|
|
|
return data, nil
|
|
}
|
|
|
|
type result struct {
|
|
value interface{}
|
|
err error
|
|
}
|
|
|
|
func (r result) ToJS() map[string]interface{} {
|
|
var errMsg string
|
|
if r.err != nil {
|
|
errMsg = r.err.Error()
|
|
}
|
|
return map[string]interface{}{
|
|
"value": js.ValueOf(r.value),
|
|
"error": errMsg,
|
|
}
|
|
}
|
|
|
|
func responseHandler(fn func(this js.Value, args []js.Value) (value interface{}, err error)) func(js.Value, []js.Value) interface{} {
|
|
return func(this js.Value, args []js.Value) interface{} {
|
|
value, err := fn(this, args)
|
|
return result{value, err}.ToJS()
|
|
}
|
|
}
|