casdoor 1.5.1 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -26,3 +26,33 @@ jobs:
26
26
  run: yarn semantic-release
27
27
  env:
28
28
  GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
29
+ - name: Package
30
+ id: package
31
+ if: github.event_name == 'push' && github.repository == 'casdoor/casdoor-chrome-extension'
32
+ run: |
33
+ tag=$(git tag --points-at HEAD | grep '^v' | head -n 1)
34
+ if [ -z "$tag" ]; then
35
+ echo "No new release, nothing to package"
36
+ exit 0
37
+ fi
38
+ # the version only lives in the tag, it is written into the manifest here and not committed
39
+ node -e 'const fs = require("fs"); const m = JSON.parse(fs.readFileSync("manifest.json")); m.version = process.argv[1]; fs.writeFileSync("manifest.json", JSON.stringify(m, null, 2) + "\n")' "${tag#v}"
40
+ zip -r casdoor-extension.zip manifest.json src images
41
+ gh release upload "$tag" casdoor-extension.zip --clobber
42
+ echo "tag=$tag" >> "$GITHUB_OUTPUT"
43
+ env:
44
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45
+ - name: Publish to the Chrome Web Store
46
+ if: steps.package.outputs.tag != ''
47
+ run: |
48
+ if [ -z "$CLIENT_ID" ] || [ -z "$CLIENT_SECRET" ] || [ -z "$REFRESH_TOKEN" ] || [ -z "$PUBLISHER_ID" ]; then
49
+ echo "::warning::The Chrome Web Store secrets are not set, ${{ steps.package.outputs.tag }} is only attached to the GitHub release"
50
+ exit 0
51
+ fi
52
+ npx --yes chrome-webstore-upload-cli@4.0.2 --source casdoor-extension.zip
53
+ env:
54
+ EXTENSION_ID: bnjbegmkpjbcpneclidlbloadenondlm
55
+ PUBLISHER_ID: ${{ secrets.CHROME_PUBLISHER_ID }}
56
+ CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
57
+ CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
58
+ REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
package/README.md CHANGED
@@ -18,11 +18,14 @@ Casdoor only hands an account out for a page of the system's own sign-in URLs, r
18
18
 
19
19
  ### Install the extension
20
20
 
21
- 1. Go to `chrome://extensions` and check the box for **Developer mode** in the top right.
22
- 2. Click `Load unpacked` button, then choose this project folder.
21
+ Install it from the [Chrome Web Store](https://chromewebstore.google.com/detail/casdoor-browser-plugin/bnjbegmkpjbcpneclidlbloadenondlm). It also works in Edge and other Chromium browsers that allow extensions from the Chrome Web Store.
22
+
23
+ Where the store can't be reached, download `casdoor-extension.zip` from the [latest release](https://github.com/casdoor/casdoor-chrome-extension/releases/latest), unzip it, go to `chrome://extensions`, turn on **Developer mode** in the top right, click **Load unpacked** and choose the unzipped folder.
24
+
25
+ Each release is published to the Chrome Web Store and attached to the GitHub release by `.github/workflows/release.yaml`, with the version of the release written into `manifest.json`. The store can take a few days to review a new version.
23
26
 
24
27
  ### Sign in to Casdoor
25
28
 
26
29
  1. Open the extension, enter the Casdoor URL and the name of the application to sign in with.
27
- 2. Add the address shown at the bottom of the extension (`https://<extension id>.chromiumapp.org/`) to the redirect URLs of that application, and allow the `token` grant type in it.
30
+ 2. Add the address shown at the bottom of the extension (`https://<extension id>.chromiumapp.org/`) to the redirect URLs of that application, and allow the `token` grant type in it. The extension installed from the Chrome Web Store always has the address `https://bnjbegmkpjbcpneclidlbloadenondlm.chromiumapp.org/`, so it can be added once for everyone; an unpacked one gets its own.
28
31
  3. Click **Sign in to Casdoor**. The extension then lists the systems it fills for you.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "casdoor",
3
- "version": "1.5.1",
3
+ "version": "1.6.1",
4
4
  "description": "The Casdoor extension can help browser users automatically log into applications protected by Casdoor single-sign-on (SSO) system.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -33,6 +33,10 @@
33
33
  skip: "不用了",
34
34
  saved: "已保存到 Casdoor",
35
35
  close: "关闭",
36
+ missingSelector: "管理员配置的{field}选择器“{selector}”在这个页面上没有找到,已改用自动识别",
37
+ usernameField: "用户名输入框",
38
+ passwordField: "密码输入框",
39
+ submitField: "登录按钮",
36
40
  }
37
41
  : {
38
42
  filled: "Casdoor filled in the account {user} of {app}",
@@ -44,6 +48,10 @@
44
48
  skip: "No thanks",
45
49
  saved: "Saved to Casdoor",
46
50
  close: "Close",
51
+ missingSelector: "The {field} selector \"{selector}\" set by the admin matches nothing on this page, so it was found automatically",
52
+ usernameField: "username input",
53
+ passwordField: "password input",
54
+ submitField: "sign-in button",
47
55
  };
48
56
 
49
57
  function format(text, values) {
@@ -78,19 +86,36 @@
78
86
  return element.getClientRects().length > 0;
79
87
  }
80
88
 
89
+ // a class attribute pasted as it is, like "el-button login-btn el-button--primary", is taken as those classes
90
+ function selectorCandidates(selector) {
91
+ const value = selector.trim();
92
+ const candidates = [value];
93
+ if (/^[\w-]+(\s+[\w-]+)*$/.test(value)) {
94
+ candidates.push("." + value.split(/\s+/).join("."));
95
+ }
96
+ return candidates;
97
+ }
98
+
81
99
  function query(selector) {
82
- if (!selector) {
100
+ if (!selector || !selector.trim()) {
83
101
  return null;
84
102
  }
85
- try {
86
- return document.querySelector(selector);
87
- } catch (error) {
88
- return null;
103
+ for (const candidate of selectorCandidates(selector)) {
104
+ try {
105
+ const element = document.querySelector(candidate);
106
+ if (element) {
107
+ return element;
108
+ }
109
+ } catch (error) {
110
+ // not a valid selector, try the next way of reading it
111
+ }
89
112
  }
113
+ return null;
90
114
  }
91
115
 
92
116
  const usernameHints = /user|login|account|email|mail|phone|mobile|tel|name|uid|uname|loginid|工号|账号|帳號|用户|用戶|手机|邮箱/i;
93
- const submitHints = /^\s*(登\s*录|登\s*入|登\s*陆|log\s*in|sign\s*in|login|signin|submit|确\s*定|提\s*交|进入系统)\s*$/i;
117
+ // "登录", "登录系统", "立即登录", "Sign in", ...
118
+ const submitHints = /^\s*(?:(?:立即|马上|安全|用户|账号)?\s*(?:登\s*录|登\s*入|登\s*陆)\s*(?:系统|平台)?|进入系统|log\s*in|log\s*on|sign\s*in|login|logon|signin|submit|确\s*定|提\s*交)\s*$/i;
94
119
 
95
120
  function findPassword(site) {
96
121
  const configured = query(site.passwordSelector);
@@ -136,8 +161,14 @@
136
161
  return button;
137
162
  }
138
163
  }
139
- const clickable = [...document.querySelectorAll("button, input[type=button], input[type=submit], a, [role=button], div[onclick], span[onclick]")];
140
- return clickable.find((element) => isVisible(element) && submitHints.test(element.value || element.textContent || "")) || null;
164
+ // buttons first, so that a link or a tab with the same words loses to the real button
165
+ for (const selector of ["button, input[type=button], input[type=submit]", "[role=button]", "a, div[onclick], span[onclick]"]) {
166
+ const found = [...document.querySelectorAll(selector)].find((element) => isVisible(element) && submitHints.test(element.value || element.textContent || ""));
167
+ if (found) {
168
+ return found;
169
+ }
170
+ }
171
+ return null;
141
172
  }
142
173
 
143
174
  function setValue(input, value) {
@@ -154,8 +185,11 @@
154
185
  for (const type of ["keydown", "keypress", "keyup"]) {
155
186
  input.dispatchEvent(new KeyboardEvent(type, {key: "Enter", code: "Enter", keyCode: 13, which: 13, bubbles: true}));
156
187
  }
157
- if (input.form) {
158
- input.form.requestSubmit();
188
+ // a single-page app (Vue, React, ...) signs in from its own Enter handler, submitting its <form> would only reload
189
+ // the page; a server-side form says where it goes
190
+ const form = input.form;
191
+ if (form && (form.hasAttribute("action") || form.hasAttribute("method"))) {
192
+ form.requestSubmit();
159
193
  }
160
194
  }
161
195
 
@@ -187,6 +221,7 @@
187
221
  title.style.cssText = "font-weight:600;font-size:12px;color:#5c6ac4;margin-bottom:4px;";
188
222
  title.textContent = "Casdoor";
189
223
  const body = document.createElement("div");
224
+ body.style.whiteSpace = "pre-line";
190
225
  body.textContent = text;
191
226
  box.append(title, body);
192
227
  if (buttons.length > 0) {
@@ -239,7 +274,14 @@
239
274
  showNotice(strings.retry, buttons, 0);
240
275
  return;
241
276
  }
242
- showNotice(format(strings.filled, {app: site.displayName, user: account.username}), buttons);
277
+ // the admin's selectors that match nothing, so that a wrong one doesn't go unnoticed
278
+ const missing = [
279
+ [site.usernameSelector, strings.usernameField],
280
+ [site.passwordSelector, strings.passwordField],
281
+ [site.autoSubmit ? site.submitSelector : "", strings.submitField],
282
+ ].filter(([selector]) => selector && selector.trim() && !query(selector));
283
+ const warnings = missing.map(([selector, field]) => format(strings.missingSelector, {field, selector}));
284
+ showNotice([format(strings.filled, {app: site.displayName, user: account.username}), ...warnings].join("\n"), buttons, warnings.length > 0 ? 15000 : 8000);
243
285
 
244
286
  if (site.autoSubmit && !username) {
245
287
  await sleep(400);