12

Is it troublesome to switch "accounts" repeatedly? Write a Google plug-in to help you quickly switch user login status

image.png

background

As the business continues to grow and the types of users continue to increase, we need to switch between different accounts for "debugging" & "testing".

I want to make a plug-in to switch the login state of all websites that rely on cookies for login state records. I have learned a lot during the process of developing the plug-in, so this time I will share the process of developing this plug-in.

1. The process of switching accounts for the current project

first step:

Click to log out (it will load for more than 3 seconds), because it not only clears the local cookies but also clears the login status of the server.

image.png

The second step:

Switch to the correct login method, mobile login or email login, enter the account password, of course the browser will help you remember the password (it will load for more than 3s)

image.png

The third step: need to find the target page (above 2s)

Because a successful login will jump back to the home page by default, so we have to jump back to the target page.

Summarize

Although the above steps do not take long, you can feel that there is room for optimization. For example, you need to manually select users and passwords, and these account passwords do not have a semantic naming. You need to remember which account corresponds to the person under the mailbox. What are the permissions.

2. Technical analysis and technical goals

principle:

The principle of a large number of "user login" processes on the market is to store the user's "token" in the "cookie" and set it to the "httpOnly" mode (to prevent the front-end code from changing the cookie), and then every user request will bring this "cookie" ", "server" performs identity recognition by verifying this "cookie", thus knowing which user sent the request.

Of course, it can also be that after the back end returns to the front end, the front end is stored in "localStorage", and this "token" is brought in the header every time a request is made, and the follow-up is the same as the above process.

And this "token" almost always has an expiration time. When the "token" fails, the "server" will generally return a specific status code. For example, the return of 401 means that the login status is invalid, and the front end recognizes that the status code is 401 and jumps to login. Process.

analyze:

The so-called "login state" can actually be abstractly understood as the "token" stored in the front-end, and our simulated login state is to obtain this "token". In fact, the link is already clear. Copying all the current "cookies" of the user is the record For a user, when the user logs in to another account and wants to switch back to the previous account, we assign the previous "cookie" to the current domain name to complete the user switch.

The more difficult problem to solve is how to get the data in the "httpOnly" state and assign the data back correctly.

Target
  1. You can switch between multiple accounts that have been logged in, and you can name the logged-in account.
  2. Accounts can be grouped according to domain names, and accounts can be added, deleted, modified, and checked.
  3. You can "share" your login information with others.
  4. It can be extended to all scenarios where cookies are used to store login information.

Third, let's launch the Google plug-in

Recommend a few articles I wrote before:
Google Plugin Getting Started Article Recommendation (
Google Plugin Getting Started Article Recommendation (Part 2)

First open the permissions of the Google plugin, because it may be necessary to call all the cookies under the domain, so it is impossible to open the permissions (permissions) to the maximum:

manifest.json

{
  "manifest_version": 2,
  "version": "0.1",
  "name": "切换用户",
  "description": "通过记录用户登录信息, 快速切换用户",
  "permissions": ["cookies", "<all_urls>", "tabs", "storage"],
  "browser_action": {
    "default_popup": "popup/index.html",
    "default_icon": "images/logo.png"
  }
}

image.png

Create a new popup folder, containing index.html file

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <link rel="stylesheet" href="./bundle.css" />
  </head>
  <body>
    <script src="./index.js"></script>
  </body>
</html>

image.png

Four, svelte development page

Recommend my two previous articles
svelte entry on
svelte entry

Create a svelte project named dev_popup, and configure rollup.config.js as follows

  output: {
    sourcemap: false,
    format: "iife",
    name: "app",
    file: "../popup/index.js",
  },

Write a structure casually in the App.svelte file, and give a basic style:

<main class="wrap">
  <div>你好</div>
</main>
<style>
ul,
li {
  list-style: none;
  margin: 0;
  padding: 0;
}
.wrap {
  overflow: auto;
  color: rgb(71, 68, 68);
  width: 600px;
  height: 400px;
  padding: 20px;
  padding-bottom: 30px;
}
</style>

Seeing the effect in the figure below represents the successful introduction:

nnnn.png

This pop-up pop-up box will re-execute the internal code every time it "pops up", and then we will try to get the information of the page where it is.

Five, get the url, domain of the current page

Since every time we click on the area outside the "pop-up box", the pop-up box will be hidden, so in fact, every time we open the pop-up box, we need to check what the current page is, and the so-called "current page" is currently active" lastFocusedWindow"&"active", the tab that finally gets the focus and is activated, this logic can be written in the first line:

  chrome.tabs.query({ active: true, lastFocusedWindow: true }, (tabs) => {
      console.log(tabs);
  });

image.png

The data obtained above does not give the corresponding domain, so we manually use the regular /^(https?:\/\/([^\/]+))?/g; analyze it.

  let url = "";
  let domain = "";

  chrome.tabs.query({ active: true, lastFocusedWindow: true }, (tabs) => {
    getDomain(tabs[0].url);
  });

  function getDomain(weburl) {
    const urlReg = /^(https?:\/\/([^\/]+))?/g;
    const res = urlReg.exec(weburl);
    domain = res[2];
    url = res[0];
  }

The above regular means that, starting with http:// or https://, matches the part where "/" does not appear in the content. This part is the domain, because the design uses the domain of the website as the key to store users Login information.

6. Get the "cookie" under the current domain name, and overcome httpOnly

Here I found that you can use chrome.cookies.getAll(options, callback) to get all the cookies of the user, but there are some pitfalls to pay attention to when passing parameters in this method.

Its options can pass a list of parameters:

image.png

At that time, I chose to pass in the domain to obtain cookies, but the problem is that the data obtained by domain is too much. The company's internal website can obtain 150+ pieces of data, because we want to achieve persistent storage of user information. , And the local storage capacity of the Google plug-in is "5M". When we set the cookie, a large part of the cookie will report the setting failure error, so we must get the cookie more accurately.

    chrome.cookies.getAll({ url }, function (cookies) {
           console.log(cookies)
    });

image.png

In the figure, we can see that this method can get the value of "httpOnly" as "true", and in the data we can find that the domain looks a bit strange, why it is divided into two types: "www.baidu. What is the difference between com" & ".baidu.com", if you see it, you have to study it.

Seven, domain starts with "dot"

For example, the current page on Baidu Tieba https://tieba.baidu.com , which contains cookies, is as shown below

image.png

Read the cookie in the console:

image.png

Send a request, although the domain is www.baidu.com can be displayed in the application, but it cannot be read and the request will not carry:

image.png

And it is impossible to set the domain to www.baidu.com. Through searching information, it is found that if the subdomain wants to get the cookie of the upper domain, it needs the upper domain to start with "dot" before it can be passed to the lower domain.

8. Store user information

We can get the user's cookie information, so we need to store this information, so that when the user uses our plugin next time, he can still see the previous user information. The Google plugin provides an localStorage similar to 061c821152c68f:

Save: (Serialize it)

        chrome.storage.local.set({
          users: JSON.stringify(users),
        });

Get: (deserialization required)

    chrome.storage.local.get("users", (local) => {
      const users = JSON.parse(local.users || null) || {};
      console.log()
    });

We can abstract the acquisition of user information into a function:

  function getUserData(cb) {
    chrome.storage.local.get("users", (local) => {
      const users = JSON.parse(local.users || null) || {};
      cb(users);
    });
  }

Nine, create a user

The so-called creation of a user is essentially to record the cookie information of the user on the current page and chrome.storage.local information in 061c821152c6ee. The knowledge point is nothing more than simply designing a data structure:

  function showCreateUser() {
    const userName = prompt("填写用户名", "");
    if (userName) {
      createUser(userName);
    }
  }
    function createUser(userName) {
    chrome.cookies.getAll({ url }, function (cookies) {
      getUserData((users) => {
        const obj = { userName, cookies, createTime: getNowTime() };
        users[domain] ? users[domain].push(obj) : (users[domain] = [obj]);
        chrome.storage.local.set({
          users: JSON.stringify(users),
        });
        data = users;
      });
    });
  }

prompt this native api forgotten? Yes, it is you!

This api is a native pop-up input box, the callback result is the content entered by the user, we let the user name the new "account":

image.png

And group by domain, store all cookie information of the user, the data structure is as follows:

image.png

10. Switch users

The essence here is to assign all cookies to the current website, which is chrome.cookies.set . Of course, this can directly call the api. Every time the cookies are passed in, we put them in a loop, because Google does not provide a batch-put api:

  function activeUser(cookies, clearCookies) {
    cookies.forEach((item) => {
      chrome.cookies.set({
        url,
        name: item.name,
        value: clearCookies ? "" : item.value,
        domain: item.domain,
        path: item.path,
        httpOnly: item.httpOnly,
        secure: item.secure,
        storeId: item.storeId,
        expirationDate: item.expirationDate,
      });
    });
    alert("切换成功");
  }

Here I hope to completely restore the user’s previous cookie structure, so I filled in a lot of content. It is worth noting that the URL is required. If you do not fill it in, an error will be reported. This must write the current URL to prevent cookie injection errors under other domain names. :

image.png

11. Shareable login state (import and export)

When we log in to an account by entering the account password and record it, in fact, other students don't need to go through this process again. We can directly share the user cookie information we have stored and share it with others.

The principle is that when the "Export" button is clicked, the information in our plug-in is copied to the user's clipboard, and when the user clicks the import button, it is ok to paste the information in:

  function shareUser(key, user) {
    const obj = {
      key,
      userName: user.userName,
      cookies: user.cookies,
      createTime: user.createTime,
    };
    copy(JSON.stringify(obj));
    alert("复制成功! 导入即可使用");
  }

  function handleExportData() {
    let exportData = prompt("输入导入信息", "");
    if (exportData) {
      exportData = JSON.parse(exportData);
      if (!data[exportData.key]) data[exportData.key] = [];
      data[exportData.key].push({
        cookies: exportData.cookies,
        userName: exportData.userName,
        createTime: exportData.createTime,
      });
      chrome.storage.local.set({
        users: JSON.stringify(data),
      });
      data = data;
    }
  }

image.png

At this time, the cookie information is already in our clipboard, just paste it directly:

image.png

12. Delete user

Of course there must be a delete function, and probably because the plug-in memory is only limited by 5M, the delete function is more important.


  function deleteUser(domain, index) {
    getUserData((users) => {
      users[domain].splice(index, 1);
      chrome.storage.local.set({
        users: JSON.stringify(users),
      });
      data = users;
    });
  }

Here, delete the nth element according to the domain, because I didn't generate an id for them, so just delete it by location.

13. Isolated display between websites

Here is a simple style optimization. The unmatched domain is displayed in a dilute display, and the switch button is hidden, and the URL that matches the current address is placed in front of the user for convenience.

image.png

14. Login status clear

The user needs to switch accounts to record different accounts, but if you directly use the logout on the page, theoretically the server will clear the user's login authentication information at this time, that is, the cookie information we recorded is invalidated, so we need to help the user Manually clear the cookie, so that as long as the page is refreshed, it will automatically jump to the login page, and the validity of the user's login state is also retained:

  function handleClearCookie() {
    chrome.cookies.getAll({ url }, function (cookies) {
      activeUser(cookies, true);
      alert("cookie 清理完毕请刷新");
    });
  }

end

This is the case this time, I hope to make progress with you.


lulu_up
5.7k 声望6.9k 粉丝

自信自律, 终身学习, 创业者, 心理咨询师