gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 04/04: drastically reduce permissions for Web integr


From: gnunet
Subject: [taler-wallet-core] 04/04: drastically reduce permissions for Web integration
Date: Fri, 01 May 2020 10:47:49 +0200

This is an automated email from the git hooks/post-receive script.

dold pushed a commit to branch master
in repository wallet-core.

commit 609397d95a73bdae55de41c47b19932e810d0320
Author: Florian Dold <address@hidden>
AuthorDate: Fri May 1 14:16:56 2020 +0530

    drastically reduce permissions for Web integration
    
    The old web integration with more permissions is still available on an
    opt-in basis.
---
 src/types/walletTypes.ts             |   5 +
 src/webex/messages.ts                |   8 ++
 src/webex/pageEntryPoint.ts          |   4 +
 src/webex/pages/pay.tsx              |   2 +-
 src/webex/pages/popup.tsx            | 111 ++++++++++++++++++-
 src/webex/pages/welcome.tsx          |  57 ++++++++--
 src/webex/pages/withdraw.tsx         |   7 ++
 src/webex/renderHtml.tsx             |   4 +-
 src/webex/wxApi.ts                   |  15 +++
 src/webex/wxBackend.ts               | 203 ++++++++++++++++++++++-------------
 webextension/manifest.json           |  14 +--
 webextension/static/style/wallet.css |  35 +++---
 webextension/static/welcome.html     |   9 +-
 webextension/static/withdraw.html    |   6 +-
 14 files changed, 365 insertions(+), 115 deletions(-)

diff --git a/src/types/walletTypes.ts b/src/types/walletTypes.ts
index 113a137c..ed334bc4 100644
--- a/src/types/walletTypes.ts
+++ b/src/types/walletTypes.ts
@@ -475,3 +475,8 @@ export interface DepositInfo {
   denomPub: string;
   denomSig: string;
 }
+
+
+export interface ExtendedPermissionsResponse {
+  newValue: boolean;
+}
\ No newline at end of file
diff --git a/src/webex/messages.ts b/src/webex/messages.ts
index 745e309c..179eec88 100644
--- a/src/webex/messages.ts
+++ b/src/webex/messages.ts
@@ -164,6 +164,14 @@ export interface MessageMap {
     request: {};
     response: walletTypes.WalletDiagnostics;
   };
+  "set-extended-permissions": {
+    request: { value: boolean };
+    response: walletTypes.ExtendedPermissionsResponse;
+  };
+  "get-extended-permissions": {
+    request: { };
+    response: walletTypes.ExtendedPermissionsResponse;
+  };
 }
 
 /**
diff --git a/src/webex/pageEntryPoint.ts b/src/webex/pageEntryPoint.ts
index dd9c1303..b9bdba06 100644
--- a/src/webex/pageEntryPoint.ts
+++ b/src/webex/pageEntryPoint.ts
@@ -24,6 +24,7 @@ import ReactDOM from "react-dom";
 import { createPopup } from "./pages/popup";
 import { createWithdrawPage } from "./pages/withdraw";
 import { createWelcomePage } from "./pages/welcome";
+import { createPayPage } from "./pages/pay";
 
 function main(): void {
   try {
@@ -43,6 +44,9 @@ function main(): void {
       case "welcome.html":
         mainElement = createWelcomePage();
         break;
+      case "pay.html":
+        mainElement = createPayPage();
+        break;
       default:
         throw Error(`page '${page}' not implemented`);
     }
diff --git a/src/webex/pages/pay.tsx b/src/webex/pages/pay.tsx
index 61f28770..a69b6b76 100644
--- a/src/webex/pages/pay.tsx
+++ b/src/webex/pages/pay.tsx
@@ -178,7 +178,7 @@ function TalerPayDialog({ talerPayUri }: { talerPayUri: 
string }): JSX.Element {
   );
 }
 
-export function makePayPage(): JSX.Element {
+export function createPayPage(): JSX.Element {
   const url = new URL(document.location.href);
   const talerPayUri = url.searchParams.get("talerPayUri");
   if (!talerPayUri) {
diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx
index 0fd2477f..f6d95e2f 100644
--- a/src/webex/pages/popup.tsx
+++ b/src/webex/pages/popup.tsx
@@ -34,11 +34,12 @@ import { WalletBalance, WalletBalanceEntry } from 
"../../types/walletTypes";
 import { abbrev, renderAmount, PageLink } from "../renderHtml";
 import * as wxApi from "../wxApi";
 
-import React, { Fragment } from "react";
+import React, { Fragment, useState, useEffect } from "react";
 import { HistoryEvent } from "../../types/history";
 
 import moment from "moment";
 import { Timestamp } from "../../util/time";
+import { classifyTalerUri, TalerUriType } from "../../util/taleruri";
 
 // FIXME: move to newer react functions
 /* eslint-disable react/no-deprecated */
@@ -761,7 +762,113 @@ function openTab(page: string) {
   };
 }
 
+function makeExtensionUrlWithParams(
+  url: string,
+  params?: { [name: string]: string | undefined },
+): string {
+  const innerUrl = new URL(chrome.extension.getURL("/" + url));
+  if (params) {
+    for (const key in params) {
+      const p = params[key];
+      if (p) {
+        innerUrl.searchParams.set(key, p);
+      }
+    }
+  }
+  return innerUrl.href;
+}
+
+function actionForTalerUri(talerUri: string): string | undefined {
+  const uriType = classifyTalerUri(talerUri);
+  switch (uriType) {
+    case TalerUriType.TalerWithdraw:
+      return makeExtensionUrlWithParams("withdraw.html", {
+        talerWithdrawUri: talerUri,
+      });
+    case TalerUriType.TalerPay:
+      return makeExtensionUrlWithParams("pay.html", {
+        talerPayUri: talerUri,
+      });
+    case TalerUriType.TalerTip:
+      return makeExtensionUrlWithParams("tip.html", {
+        talerTipUri: talerUri,
+      });
+    case TalerUriType.TalerRefund:
+      return makeExtensionUrlWithParams("refund.html", {
+        talerRefundUri: talerUri,
+      });
+    case TalerUriType.TalerNotifyReserve:
+      // FIXME: implement
+      break;
+    default:
+      console.warn(
+        "Response with HTTP 402 has Taler header, but header value is not a 
taler:// URI.",
+      );
+      break;
+  }
+  return undefined;
+}
+
+async function findTalerUriInActiveTab(): Promise<string | undefined> {
+  return new Promise((resolve, reject) => {
+    chrome.tabs.executeScript(
+      {
+        code: `
+        (() => {
+          let x = document.querySelector("a[href^='taler://'");
+          return x ? x.href.toString() : null;
+        })();
+      `,
+        allFrames: false,
+      },
+      (result) => {
+        if (chrome.runtime.lastError) {
+          console.error(chrome.runtime.lastError);
+          resolve(undefined);
+          return;
+        }
+        console.log("got result", result);
+        resolve(result[0]);
+      },
+    );
+  });
+}
+
 function WalletPopup(): JSX.Element {
+  const [talerActionUrl, setTalerActionUrl] = useState<string | undefined>(
+    undefined,
+  );
+  const [dismissed, setDismissed] = useState(false);
+  useEffect(() => {
+    async function check(): Promise<void> {
+      const talerUri = await findTalerUriInActiveTab();
+      if (talerUri) {
+        const actionUrl = actionForTalerUri(talerUri);
+        setTalerActionUrl(actionUrl);
+      }
+    }
+    check();
+  });
+  if (talerActionUrl && !dismissed) {
+    return (
+      <div style={{ padding: "1em" }}>
+        <h1>Taler Action</h1>
+        <p>This page has a Taler action. </p>
+        <p>
+          <button
+            onClick={() => {
+              window.open(talerActionUrl, "_blank");
+            }}
+          >
+            Open
+          </button>
+        </p>
+        <p>
+          <button onClick={() => setDismissed(true)}>Dismiss</button>
+        </p>
+      </div>
+    );
+  }
   return (
     <div>
       <WalletNavBar />
@@ -777,6 +884,6 @@ function WalletPopup(): JSX.Element {
 }
 
 export function createPopup(): JSX.Element {
-  chrome.runtime.connect({ name: "popup" });
+  //chrome.runtime.connect({ name: "popup" });
   return <WalletPopup />;
 }
diff --git a/src/webex/pages/welcome.tsx b/src/webex/pages/welcome.tsx
index eecbe2be..5092d2dd 100644
--- a/src/webex/pages/welcome.tsx
+++ b/src/webex/pages/welcome.tsx
@@ -24,8 +24,9 @@ import React, { useState, useEffect } from "react";
 import { getDiagnostics } from "../wxApi";
 import { PageLink } from "../renderHtml";
 import { WalletDiagnostics } from "../../types/walletTypes";
+import * as wxApi from "../wxApi";
 
-function Diagnostics(): JSX.Element {
+function Diagnostics(): JSX.Element | null {
   const [timedOut, setTimedOut] = useState(false);
   const [diagnostics, setDiagnostics] = useState<WalletDiagnostics | 
undefined>(
     undefined,
@@ -55,7 +56,7 @@ function Diagnostics(): JSX.Element {
 
   if (diagnostics) {
     if (diagnostics.errors.length === 0) {
-      return <p>Running diagnostics ... everything looks fine.</p>;
+      return null;
     } else {
       return (
         <div
@@ -96,16 +97,56 @@ function Diagnostics(): JSX.Element {
 }
 
 function Welcome(): JSX.Element {
+  const [extendedPermissions, setExtendedPermissions] = useState(false);
+  async function handleExtendedPerm(newVal: boolean): Promise<void> {
+    const res = await wxApi.setExtendedPermissions(newVal);
+    setExtendedPermissions(res.newValue);
+  }
+  useEffect(() => {
+    async function getExtendedPermValue(): Promise<void> {
+      const res = await wxApi.getExtendedPermissions()
+      setExtendedPermissions(res.newValue);
+    }
+    getExtendedPermValue();
+  });
   return (
     <>
       <p>Thank you for installing the wallet.</p>
-      <h2>First Steps</h2>
-      <p>
-        Check out <a href="https://demo.taler.net/";>demo.taler.net</a> for a
-        demo.
-      </p>
-      <h2>Troubleshooting</h2>
       <Diagnostics />
+      <h2>Permissions</h2>
+      <div>
+        <input
+          checked={extendedPermissions}
+          onChange={(x) => handleExtendedPerm(x.target.checked)}
+          type="checkbox"
+          id="checkbox-perm"
+          style={{ width: "1.5em", height: "1.5em", verticalAlign: "middle" }}
+        />
+        <label
+          htmlFor="checkbox-perm"
+          style={{ marginLeft: "0.5em", fontWeight: "bold" }}
+        >
+          Automatically open wallet based on page content
+        </label>
+        <span
+          style={{
+            color: "#383838",
+            fontSize: "smaller",
+            display: "block",
+            marginLeft: "2em",
+          }}
+        >
+          (Enabling this option below will make using the wallet faster, but
+          requires more permissions from your browser.)
+        </span>
+      </div>
+      <h2>Next Steps</h2>
+      <a href="https://demo.taler.net/"; style={{ display: "block" }}>
+        Try the demo »
+      </a>
+      <a href="https://demo.taler.net/"; style={{ display: "block" }}>
+        Learn how to top up your wallet balance »
+      </a>
     </>
   );
 }
diff --git a/src/webex/pages/withdraw.tsx b/src/webex/pages/withdraw.tsx
index efd0adc8..1647a706 100644
--- a/src/webex/pages/withdraw.tsx
+++ b/src/webex/pages/withdraw.tsx
@@ -160,11 +160,18 @@ function NewExchangeSelection(props: {
 
   return (
     <div>
+      <h1>Digital Cash Withdrawal</h1>
       <i18n.Translate wrap="p">
         You are about to withdraw{" "}
         <strong>{renderAmount(details.bankWithdrawDetails.amount)}</strong> 
from
         your bank account into your wallet.
       </i18n.Translate>
+      { selectedExchange ?
+        <p>
+          The exchange <strong>{selectedExchange}</strong> will be used as the 
Taler payment service provider.
+        </p> : null
+      }
+
       <div>
         <button
           className="pure-button button-success"
diff --git a/src/webex/renderHtml.tsx b/src/webex/renderHtml.tsx
index b1363abf..a56af37f 100644
--- a/src/webex/renderHtml.tsx
+++ b/src/webex/renderHtml.tsx
@@ -109,6 +109,7 @@ export class Collapsible extends React.Component<
       return (
         <h2>
           <a className="opener opener-collapsed" href="#" onClick={doOpen}>
+            {" "}
             {this.props.title}
           </a>
         </h2>
@@ -118,6 +119,7 @@ export class Collapsible extends React.Component<
       <div>
         <h2>
           <a className="opener opener-open" href="#" onClick={doClose}>
+            {" "}
             {this.props.title}
           </a>
         </h2>
@@ -143,7 +145,6 @@ function WireFee(props: {
           <th>Closing Fee</th>
         </tr>
       </thead>
-      ,
       <tbody>
         {props.rci.wireFees.feesForType[props.s].map((f) => (
           <tr key={f.sig}>
@@ -153,7 +154,6 @@ function WireFee(props: {
           </tr>
         ))}
       </tbody>
-      ,
     </>
   );
 }
diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts
index 07b223c8..128041e5 100644
--- a/src/webex/wxApi.ts
+++ b/src/webex/wxApi.ts
@@ -41,6 +41,7 @@ import {
   WithdrawDetails,
   PreparePayResult,
   AcceptWithdrawalResponse,
+  ExtendedPermissionsResponse,
 } from "../types/walletTypes";
 
 import { MessageMap, MessageType } from "./messages";
@@ -324,3 +325,17 @@ export function acceptWithdrawal(
 export function getDiagnostics(): Promise<WalletDiagnostics> {
   return callBackend("get-diagnostics", {});
 }
+
+/**
+ * Get diagnostics information
+ */
+export function setExtendedPermissions(value: boolean): 
Promise<ExtendedPermissionsResponse> {
+  return callBackend("set-extended-permissions", { value });
+}
+
+/**
+ * Get diagnostics information
+ */
+export function getExtendedPermissions(): Promise<ExtendedPermissionsResponse> 
{
+  return callBackend("get-extended-permissions", {});
+}
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
index 6bd87b45..17e5215f 100644
--- a/src/webex/wxBackend.ts
+++ b/src/webex/wxBackend.ts
@@ -63,6 +63,11 @@ let outdatedDbVersion: number | undefined;
 
 const walletInit: OpenedPromise<void> = openPromise<void>();
 
+const extendedPermissions = {
+  permissions: ["webRequest", "webRequestBlocking", "tabs"],
+  origins: ["http://*/*";, "https://*/*";],
+};
+
 async function handleMessage(
   sender: MessageSender,
   type: MessageType,
@@ -282,6 +287,43 @@ async function handleMessage(
     }
     case "prepare-pay":
       return needsWallet().preparePayForUri(detail.talerPayUri);
+    case "set-extended-permissions": {
+      const newVal = detail.value;
+      if (newVal) {
+        const res = await new Promise((resolve, reject) => {
+          chrome.permissions.request(
+            extendedPermissions,
+            (granted: boolean) => {
+              console.log("permissions granted:", granted);
+              if (chrome.runtime.lastError) {
+                console.error(chrome.runtime.lastError);
+              }
+              resolve(granted);
+            },
+          );
+        });
+        if (res) {
+          setupHeaderListener();
+        }
+        return { newValue: res };
+      } else {
+        await new Promise((resolve, reject) => {
+          chrome.permissions.remove(extendedPermissions, (rem) => {
+            console.log("permissions removed:", rem);
+            resolve();
+          });
+        });
+        return { newVal: false };
+      }
+    }
+    case "get-extended-permissions": {
+      const res = await new Promise((resolve, reject) => {
+        chrome.permissions.contains(extendedPermissions, (result: boolean) => {
+          resolve(result);
+        });
+      });
+      return { newValue: res };
+    }
     default:
       // Exhaustiveness check.
       // See https://www.typescriptlang.org/docs/handbook/advanced-types.html
@@ -453,6 +495,91 @@ try {
   console.error(e);
 }
 
+function headerListener(
+  details: chrome.webRequest.WebResponseHeadersDetails,
+): chrome.webRequest.BlockingResponse | undefined {
+  if (chrome.runtime.lastError) {
+    console.error(chrome.runtime.lastError);
+    return;
+  }
+  const wallet = currentWallet;
+  if (!wallet) {
+    console.warn("wallet not available while handling header");
+    return;
+  }
+  if (details.statusCode === 402 || details.statusCode === 202) {
+    console.log(`got 402/202 from ${details.url}`);
+    for (const header of details.responseHeaders || []) {
+      if (header.name.toLowerCase() === "taler") {
+        const talerUri = header.value || "";
+        const uriType = classifyTalerUri(talerUri);
+        switch (uriType) {
+          case TalerUriType.TalerWithdraw:
+            return makeSyncWalletRedirect(
+              "withdraw.html",
+              details.tabId,
+              details.url,
+              {
+                talerWithdrawUri: talerUri,
+              },
+            );
+          case TalerUriType.TalerPay:
+            return makeSyncWalletRedirect(
+              "pay.html",
+              details.tabId,
+              details.url,
+              {
+                talerPayUri: talerUri,
+              },
+            );
+          case TalerUriType.TalerTip:
+            return makeSyncWalletRedirect(
+              "tip.html",
+              details.tabId,
+              details.url,
+              {
+                talerTipUri: talerUri,
+              },
+            );
+          case TalerUriType.TalerRefund:
+            return makeSyncWalletRedirect(
+              "refund.html",
+              details.tabId,
+              details.url,
+              {
+                talerRefundUri: talerUri,
+              },
+            );
+          case TalerUriType.TalerNotifyReserve:
+            Promise.resolve().then(() => {
+              const w = currentWallet;
+              if (!w) {
+                return;
+              }
+              w.handleNotifyReserve();
+            });
+            break;
+          default:
+            console.warn(
+              "Response with HTTP 402 has Taler header, but header value is 
not a taler:// URI.",
+            );
+            break;
+        }
+      }
+    }
+  }
+  return;
+}
+
+function setupHeaderListener(): void {
+  // Handlers for catching HTTP requests
+  chrome.webRequest.onHeadersReceived.addListener(
+    headerListener,
+    { urls: ["https://*/*";, "http://*/*";] },
+    ["responseHeaders", "blocking"],
+  );
+}
+
 /**
  * Main function to run for the WebExtension backend.
  *
@@ -474,79 +601,5 @@ export async function wxMain(): Promise<void> {
     return true;
   });
 
-  // Handlers for catching HTTP requests
-  chrome.webRequest.onHeadersReceived.addListener(
-    (details) => {
-      const wallet = currentWallet;
-      if (!wallet) {
-        console.warn("wallet not available while handling header");
-        return;
-      }
-      if (details.statusCode === 402 || details.statusCode === 202) {
-        console.log(`got 402/202 from ${details.url}`);
-        for (const header of details.responseHeaders || []) {
-          if (header.name.toLowerCase() === "taler") {
-            const talerUri = header.value || "";
-            const uriType = classifyTalerUri(talerUri);
-            switch (uriType) {
-              case TalerUriType.TalerWithdraw:
-                return makeSyncWalletRedirect(
-                  "withdraw.html",
-                  details.tabId,
-                  details.url,
-                  {
-                    talerWithdrawUri: talerUri,
-                  },
-                );
-              case TalerUriType.TalerPay:
-                return makeSyncWalletRedirect(
-                  "pay.html",
-                  details.tabId,
-                  details.url,
-                  {
-                    talerPayUri: talerUri,
-                  },
-                );
-              case TalerUriType.TalerTip:
-                return makeSyncWalletRedirect(
-                  "tip.html",
-                  details.tabId,
-                  details.url,
-                  {
-                    talerTipUri: talerUri,
-                  },
-                );
-              case TalerUriType.TalerRefund:
-                return makeSyncWalletRedirect(
-                  "refund.html",
-                  details.tabId,
-                  details.url,
-                  {
-                    talerRefundUri: talerUri,
-                  },
-                );
-              case TalerUriType.TalerNotifyReserve:
-                Promise.resolve().then(() => {
-                  const w = currentWallet;
-                  if (!w) {
-                    return;
-                  }
-                  w.handleNotifyReserve();
-                });
-                break;
-
-              default:
-                console.warn(
-                  "Response with HTTP 402 has Taler header, but header value 
is not a taler:// URI.",
-                );
-                break;
-            }
-          }
-        }
-      }
-      return;
-    },
-    { urls: ["https://*/*";, "http://*/*";] },
-    ["responseHeaders", "blocking"],
-  );
+  setupHeaderListener();
 }
diff --git a/webextension/manifest.json b/webextension/manifest.json
index 5bcb8c06..7592b350 100644
--- a/webextension/manifest.json
+++ b/webextension/manifest.json
@@ -24,6 +24,10 @@
 
   "permissions": [
     "storage",
+    "activeTab"
+  ],
+
+  "optional_permissions": [
     "tabs",
     "webRequest",
     "webRequestBlocking",
@@ -39,16 +43,6 @@
     "default_popup": "popup.html"
   },
 
-  "content_scripts": [
-    {
-      "matches": ["*://*/*"],
-      "js": [
-        "contentScript.js"
-      ],
-      "run_at": "document_start"
-    }
-  ],
-
   "background": {
     "page": "background.html",
     "persistent": true
diff --git a/webextension/static/style/wallet.css 
b/webextension/static/style/wallet.css
index 16a414b3..7c06f238 100644
--- a/webextension/static/style/wallet.css
+++ b/webextension/static/style/wallet.css
@@ -1,14 +1,15 @@
 body {
   font-size: 100%;
   overflow-y: scroll;
+  margin-top: 2em;
 }
 
 #main {
-  border: solid 1px black;
+  border: solid 5px black;
   border-radius: 10px;
   margin-left: auto;
   margin-right: auto;
-  margin-top: 2em;
+  padding-top: 2em;
   max-width: 50%;
   padding: 2em;
 }
@@ -18,16 +19,6 @@ header {
   height: 100px;
   margin: 0;
   padding: 0;
-  border-bottom: 1px solid black;
-}
-
-header h1 {
-  font-size: 200%;
-  margin: 0;
-  padding: 0 0 0 120px;
-  position: relative;
-  top: 50%;
-  transform: translateY(-50%);
 }
 
 header #logo {
@@ -37,7 +28,6 @@ header #logo {
   padding: 0;
   margin: 0;
   text-align: center;
-  border-right: 1px solid black;
   background-image: url(/img/logo.png);
   background-size: 100px;
 }
@@ -50,7 +40,6 @@ aside {
 section#main {
   margin: auto;
   padding: 20px;
-  border-left: 1px solid black;
   height: 100%;
   max-width: 50%;
 }
@@ -61,19 +50,23 @@ section#main h1:first-child {
 
 h1 {
   font-size: 160%;
+  font-family: "monospace";
 }
 
 h2 {
   font-size: 140%;
+  font-family: "monospace";
 }
 
 h3 {
   font-size: 120%;
+  font-family: "monospace";
 }
 
 h4,
 h5,
 h6 {
+  font-family: "monospace";
   font-size: 100%;
 }
 
@@ -281,3 +274,17 @@ a.opener {
 object.svg-icon.svg-baseline {
   transform: translate(0, 0.125em);
 }
+
+.switch {
+  position: relative;
+  display: inline-block;
+  width: 60px;
+  height: 34px;
+}
+
+/* Hide default HTML checkbox */
+.switch input {
+  opacity: 0;
+  width: 0;
+  height: 0;
+}
\ No newline at end of file
diff --git a/webextension/static/welcome.html b/webextension/static/welcome.html
index dc893211..07ecac70 100644
--- a/webextension/static/welcome.html
+++ b/webextension/static/welcome.html
@@ -2,7 +2,7 @@
 <html>
   <head>
     <meta charset="UTF-8" />
-    <title>Taler Wallet: Withdraw</title>
+    <title>Taler Wallet Installed</title>
 
     <link rel="icon" href="/img/icon.png" />
     <link rel="stylesheet" type="text/css" href="/style/pure.css" />
@@ -12,7 +12,12 @@
 
   <body>
     <section id="main">
-      <h1>GNU Taler Wallet Installed!</h1>
+      <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
+        <h1 style="font-family: monospace; font-size: 250%;">
+          <span style="color: #aa3939;">❰</span>Taler Wallet<span 
style="color: #aa3939;">❱</span>
+        </h1>
+      </div>
+      <h1>Browser Extension Installed!</h1>
       <div id="container">Loading...</div>
     </section>
   </body>
diff --git a/webextension/static/withdraw.html 
b/webextension/static/withdraw.html
index d2aab1b6..5137204b 100644
--- a/webextension/static/withdraw.html
+++ b/webextension/static/withdraw.html
@@ -11,7 +11,11 @@
 
   <body>
     <section id="main">
-      <h1>GNU Taler Wallet</h1>
+      <div style="border-bottom: 3px dashed #aa3939; margin-bottom: 2em;">
+        <h1 style="font-family: monospace; font-size: 250%;">
+          <span style="color: #aa3939;">❰</span>Taler Wallet<span 
style="color: #aa3939;">❱</span>
+        </h1>
+      </div>
       <div class="fade" id="container"></div>
     </section>
   </body>

-- 
To stop receiving notification emails like this one, please contact
address@hidden.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]