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.
- package/.github/workflows/release.yaml +30 -0
- package/README.md +6 -3
- package/package.json +1 -1
- package/src/content-script.js +53 -11
|
@@ -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
|
-
|
|
22
|
-
|
|
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
package/src/content-script.js
CHANGED
|
@@ -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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
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
|
-
|
|
158
|
-
|
|
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
|
-
|
|
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);
|