Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ziga/og fix frontend issues from workshop #1525

Merged
merged 10 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions tools/walletextension/api/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,9 @@ func ethRequestHandler(walletExt *walletextension.WalletExtension, conn userconn
return
}

// Get userID
// Get userID and check if user exists (if not - use default user)
hexUserID, err := getQueryParameter(conn.ReadRequestParams(), common.UserQueryParameter)
if err != nil {
if err != nil || !walletExt.UserExists(hexUserID) {
walletExt.Logger().Error(fmt.Errorf("user not found in the query params: %w. Using the default user", err).Error())
hexUserID = hex.EncodeToString([]byte(common.DefaultUser)) // todo (@ziga) - this can be removed once old WE endpoints are removed
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 14 additions & 2 deletions tools/walletextension/api/staticOG/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
<head>
<title>Obscuro Gateway </title>
<link rel="icon" type="favicon-32x32" sizes="32x32" href="favicon-32x32.png">
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/web3@latest/dist/web3.min.js"></script>
<script src="https://cdn.ethers.io/lib/ethers-5.2.umd.min.js"
type="application/javascript"></script>
<script type="module" type="application/javascript" src="javascript.js"></script>
<link rel="stylesheet" href="style.css">
</head>
Expand All @@ -18,8 +19,19 @@ <h2>◠. Obscuro Gateway Demo! ◠.</h2>
<button class="btn btn-primary btn-lg btn-block mb-3" id="addAllAccounts" style="display: none">Add all accounts from Metamask</button>
<button class="btn btn-primary btn-lg btn-block mb-3" id="revokeUserID">Revoke UserID</button>

<p id="userID"></p>
<p id="status"></p>

<table id="accountsTable" border="1">
<thead>
<tr>
<th>Accounts</th>
<th>Added to Obscuro Gateway</th>
</tr>
</thead>
<tbody id="tableBody">
<!-- Rows will be added here -->
</tbody>
</table>

</body>
</html>
156 changes: 121 additions & 35 deletions tools/walletextension/api/staticOG/javascript.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const idAddAccount = "addAccount";
const idAddAllAccounts = "addAllAccounts";
const idRevokeUserID = "revokeUserID";
const idStatus = "status";
const idUserID = "userID";
const obscuroGatewayVersion = "v1"
const pathJoin = obscuroGatewayVersion + "/join/";
const pathAuthenticate = obscuroGatewayVersion + "/authenticate/";
Expand All @@ -18,7 +17,7 @@ const jsonHeaders = {
"Accept": "application/json",
"Content-Type": "application/json"
};
const metamaskRequestAccounts = "eth_requestAccounts";

const metamaskPersonalSign = "personal_sign";

function isValidUserIDFormat(value) {
Expand All @@ -27,6 +26,8 @@ function isValidUserIDFormat(value) {

let obscuroGatewayAddress = window.location.protocol + "//" + window.location.host;

let provider = null;


async function addNetworkToMetaMask(ethereum, userID, chainIDDecimal) {
// add network to MetaMask
Expand All @@ -45,7 +46,7 @@ async function addNetworkToMetaMask(ethereum, userID, chainIDDecimal) {
decimals: 18
},
rpcUrls: [obscuroGatewayAddress+"/"+obscuroGatewayVersion+'/?u='+userID],
blockExplorerUrls: null,
blockExplorerUrls: null
},
],
});
Expand Down Expand Up @@ -108,39 +109,114 @@ async function revokeUserID(userID) {
return revokeResponse.ok
}

const initialize = () => {
function getRandomIntAsString(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
const randomInt = Math.floor(Math.random() * (max - min + 1)) + min;
return randomInt.toString();
}


async function getUserID() {
try {
return await provider.send('eth_getStorageAt', ["getUserID", getRandomIntAsString(0, 1000), null])
}catch (e) {
console.log(e)
return null;
}
}

async function connectAccount() {
try {
return await window.ethereum.request({ method: 'eth_requestAccounts' });
} catch (error) {
// TODO: Display warning to user to allow it and refresh page...
console.error('User denied account access:', error);
return null;
}
}

// Check if Metamask is available on mobile or as a plugin in browser
// (https://docs.metamask.io/wallet/how-to/integrate-with-mobile/)
function checkIfMetamaskIsLoaded() {
if (window.ethereum) {
handleEthereum();
} else {
// TODO: Refactor and change the way we hide and display items on our webpage
document.getElementById(idJoin).style.display = "none";
document.getElementById(idAddAccount).style.display = "none";
document.getElementById(idAddAllAccounts).style.display = "none";
document.getElementById(idRevokeUserID).style.display = "none";
const statusArea = document.getElementById(idStatus);
statusArea.innerText = 'Connecting to Metamask...';
window.addEventListener('ethereum#initialized', handleEthereum, {
once: true,
});

// If the event is not dispatched by the end of the timeout,
// the user probably doesn't have MetaMask installed.
setTimeout(handleEthereum, 3000); // 3 seconds
}
}

function handleEthereum() {
const { ethereum } = window;
if (ethereum && ethereum.isMetaMask) {
provider = new ethers.providers.Web3Provider(window.ethereum);
initialize()
} else {
const statusArea = document.getElementById(idStatus);
statusArea.innerText = 'Please install MetaMask to use Obscuro Gateway.';
}
}

async function populateAccountsTable(document, tableBody, userID) {
tableBody.innerHTML = '';
const accounts = await provider.listAccounts();
for (const account of accounts) {
const row = document.createElement('tr');

const accountCell = document.createElement('td');
accountCell.textContent = account;
row.appendChild(accountCell);

const statusCell = document.createElement('td');

statusCell.textContent = await accountIsAuthenticated(account, userID); // Status is empty for now
row.appendChild(statusCell);

tableBody.appendChild(row);
}
}

const initialize = async () => {
const joinButton = document.getElementById(idJoin);
const addAccountButton = document.getElementById(idAddAccount);
const addAllAccountsButton = document.getElementById(idAddAllAccounts);
const revokeUserIDButton = document.getElementById(idRevokeUserID);
const statusArea = document.getElementById(idStatus);
const userIDArea = document.getElementById(idUserID);

// get ObscuroGatewayUserID from local storage
let userID = localStorage.getItem("ObscuroGatewayUserID")
const accountsTable = document.getElementById('accountsTable')
const tableBody = document.getElementById('tableBody');
// getUserID from the gateway with getStorageAt method
let userID = await getUserID()

// check if userID exists and has correct type and length (is valid) and display either
// option to join or to add new account to existing user
// check if userID exists and has a correct type and length (is valid) and display either
// option to join or to add a new account to existing user
if (isValidUserIDFormat(userID)) {
userIDArea.innerText = "Your userID is: " + userID
joinButton.style.display = "none"
addAccountButton.style.display = "block"
addAllAccountsButton.style.display = "block"
revokeUserIDButton.style.display = "block"
accountsTable.style.display = "block"
await populateAccountsTable(document, tableBody, userID)
} else {
joinButton.style.display = "block"
addAccountButton.style.display = "none"
revokeUserIDButton.style.display = "none"
accountsTable.style.display = "none"
}

let ethereum = window.ethereum;
if (!ethereum) {
joinButton.style.display = "none"
addAccountButton.style.display = "none"
statusArea.innerText = "Please install MetaMask to use Obscuro Gateway"
}


joinButton.addEventListener(eventClick, async () => {
// join Obscuro Gateway
const joinResp = await fetch(
Expand All @@ -156,7 +232,6 @@ const initialize = () => {

// save userID to the localStorage and hide button that enables users to join
userID = await joinResp.text();
localStorage.setItem("ObscuroGatewayUserID", userID);
joinButton.style.display = "none"

// add Obscuro network to Metamask
Expand All @@ -170,62 +245,73 @@ const initialize = () => {
addAccountButton.style.display = "block"
addAllAccountsButton.style.display = "block"
revokeUserIDButton.style.display = "block"
accountsTable.style.display = "block"
await populateAccountsTable(document, tableBody, userID)
})

addAccountButton.addEventListener(eventClick, async () => {
// check if we have userID and it is correct length
if (!isValidUserIDFormat(userID)){
// check if we have userID and it is the correct length
if (!isValidUserIDFormat(userID)) {
statusArea.innerText = "\n Please join Obscuro network first"
joinButton.style.display = "block"
addAccountButton.style.display = "none"
}

// Get account and prompt user to sign joining with selected account
const accounts = await ethereum.request({method: metamaskRequestAccounts});
if (accounts.length === 0) {
await connectAccount()

// Get an account and prompt user to sign joining with a selected account
const account = await provider.getSigner().getAddress();
if (account.length === 0) {
statusArea.innerText = "No MetaMask accounts found."
return
}
let authenticateAccountStatus = await authenticateAccountWithObscuroGateway(ethereum, accounts[0], userID)
statusArea.innerText = "\n Authentication status: " + authenticateAccountStatus
let authenticateAccountStatus = await authenticateAccountWithObscuroGateway(ethereum, account, userID)
//statusArea.innerText = "\n Authentication status: " + authenticateAccountStatus
accountsTable.style.display = "block"
await populateAccountsTable(document, tableBody, userID)
})

addAllAccountsButton.addEventListener(eventClick, async () => {
// check if we have userID and it is correct length
if (!isValidUserIDFormat(userID)){
// check if we have userID and it is the correct length
if (!isValidUserIDFormat(userID)) {
statusArea.innerText = "\n Please join Obscuro network first"
joinButton.style.display = "block"
addAccountButton.style.display = "none"
}

// Get account and prompt user to sign joining with selected account
const accounts = await ethereum.request({method: metamaskRequestAccounts});
await connectAccount()

// Get an account and prompt user to sign joining with selected account
const accounts = await provider.listAccounts();
if (accounts.length === 0) {
statusArea.innerText = "No MetaMask accounts found."
return
}

for (const account of accounts) {
let authenticateAccountStatus = await authenticateAccountWithObscuroGateway(ethereum, account, userID)
statusArea.innerText += "\n Authentication status: " + authenticateAccountStatus + " for account: " + account;
accountsTable.style.display = "block"
await populateAccountsTable(document, tableBody, userID)
}
})

revokeUserIDButton.addEventListener(eventClick, async () => {
let result = await revokeUserID(userID);

await populateAccountsTable(document, tableBody, userID)

if (result) {
localStorage.removeItem("ObscuroGatewayUserID")
joinButton.style.display = "block";
revokeUserIDButton.style.display = "none";
addAllAccountsButton.style.display = "none";
userIDArea.innerText = "";
statusArea.innerText = "Revoking UserID successful. Please remove current network from Metamask."
addAccountButton.style.display = "none";
}else{
accountsTable.style.display = "none"
} else {
statusArea.innerText = "Revoking UserID failed";
}
})

}

window.addEventListener(eventDomLoaded, initialize);
window.addEventListener(eventDomLoaded, checkIfMetamaskIsLoaded);
19 changes: 18 additions & 1 deletion tools/walletextension/api/staticOG/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,21 @@ button {
/*Formats the block-decoder form.*/
form { display: table; }
label { display: table-cell; }
input { display: table-cell; }
input { display: table-cell; }

/* basic style for the accounts table*/
table {
border-collapse: collapse;
width: fit-content;
margin: auto; /* Center the table */
background-color: #333; /* Dark Background */
color: white; /* Light Text */
}
th, td {
border: 1px solid #666; /* Darker Border */
padding: 15px;
text-align: center;
}
th {
background-color: #444; /* Slightly Darker Header */
}
4 changes: 2 additions & 2 deletions tools/walletextension/test/wallet_extension_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,8 @@ func TestGetStorageAtForReturningUserID(t *testing.T) {
invalidUserID := "abc123"
respBody2 := makeHTTPEthJSONReqWithUserID(walletHTTPPort, rpc.GetStorageAt, []interface{}{"getUserID", "0", nil}, invalidUserID)

if !strings.Contains(string(respBody2), "UserAccountManager doesn't exist for user: "+invalidUserID) {
t.Fatalf("expected response containing invalid userID '%s', got '%s'", invalidUserID, string(respBody2))
if !strings.Contains(string(respBody2), "method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found") {
t.Fatalf("expected method eth_getStorageAt cannot be called with an unauthorised client - no signed viewing keys found, got '%s'", string(respBody2))
}

// make a request to GetStorageAt with userID that is in the database, but wrong parameters
Expand Down
22 changes: 19 additions & 3 deletions tools/walletextension/wallet_extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ func (w *WalletExtension) UserHasAccount(hexUserID string, address string) (bool
return false, err
}

// check if any of the accounts matches given account
// check if any of the account matches given account
found := false
for _, account := range accounts {
if bytes.Equal(account.AccountAddress, addressBytes) {
Expand All @@ -284,7 +284,7 @@ func (w *WalletExtension) UserHasAccount(hexUserID string, address string) (bool
return found, nil
}

// DeleteUser deletes user and accounts associated with user from database for given userID
// DeleteUser deletes user and accounts associated with user from the database for given userID
func (w *WalletExtension) DeleteUser(hexUserID string) error {
userIDBytes, err := common.GetUserIDbyte(hexUserID)
if err != nil {
Expand All @@ -307,7 +307,23 @@ func (w *WalletExtension) DeleteUser(hexUserID string) error {
return nil
}

// verifySignature checks if message was signed by the correct address and if signature is valid
func (w *WalletExtension) UserExists(hexUserID string) bool {
userIDBytes, err := common.GetUserIDbyte(hexUserID)
if err != nil {
w.Logger().Error(fmt.Errorf("error decoding string (%s), %w", hexUserID, err).Error())
return false
}

key, err := w.storage.GetUserPrivateKey(userIDBytes)
if err != nil {
w.Logger().Error(fmt.Errorf("error getting user's private key (%s), %w", hexUserID, err).Error())
return false
}

return len(key) > 0
}

// verifySignature checks if a message was signed by the correct address and if signature is valid
func verifySignature(message string, signature []byte, address gethcommon.Address) (bool, error) {
// prefix the message like in the personal_sign method
prefixedMessage := fmt.Sprintf(common.PersonalSignMessagePrefix, len(message), message)
Expand Down