@auth-link/web 0.0.1 → 0.0.3
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/CHANGELOG.md +26 -4
- package/LICENSE +21 -0
- package/README.md +1386 -120
- package/dist/index.js +50 -11
- package/dist/index.js.map +2 -2
- package/dist/react.js.map +2 -2
- package/dist/types/core.d.ts +13 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/vue.js +12 -3
- package/dist/vue.js.map +2 -2
- package/docs/README.en.md +180 -25
- package/docs/README.ja.md +199 -44
- package/docs/README.zh-CN.md +251 -58
- package/docs/go-vue.en.md +254 -0
- package/docs/go-vue.ja.md +254 -0
- package/docs/go-vue.zh-CN.md +258 -0
- package/examples/vue-cms.vue +109 -0
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -1694,6 +1694,9 @@ function qrImage(ticket) {
|
|
|
1694
1694
|
qr.make();
|
|
1695
1695
|
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(qr.createSvgTag({ cellSize: 4, margin: 16, scalable: true }))}`;
|
|
1696
1696
|
}
|
|
1697
|
+
function backendProtocol(url) {
|
|
1698
|
+
return url.protocol === "https:" || url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
|
|
1699
|
+
}
|
|
1697
1700
|
async function limitedJson(response) {
|
|
1698
1701
|
const reader = response.body?.getReader();
|
|
1699
1702
|
if (!reader) fail("INVALID_RESPONSE");
|
|
@@ -1728,30 +1731,66 @@ async function limitedJson(response) {
|
|
|
1728
1731
|
function createAuthLinkClient(options) {
|
|
1729
1732
|
if ("apiKey" in options || "secret" in options) fail("SERVER_SECRET_FORBIDDEN");
|
|
1730
1733
|
const base = options.baseUrl ?? "/api/authlink";
|
|
1734
|
+
const allowedOrigins = new Set(options.allowedBackendOrigins ?? []);
|
|
1735
|
+
for (const origin of allowedOrigins) {
|
|
1736
|
+
let url;
|
|
1737
|
+
try {
|
|
1738
|
+
url = new URL(origin);
|
|
1739
|
+
} catch {
|
|
1740
|
+
return fail("INVALID_BACKEND_ORIGIN");
|
|
1741
|
+
}
|
|
1742
|
+
if (url.origin !== origin || url.hostname.includes("*") || !backendProtocol(url)) fail("INVALID_BACKEND_ORIGIN");
|
|
1743
|
+
}
|
|
1744
|
+
const credentials = options.credentials ?? (options.accessToken ? "omit" : "same-origin");
|
|
1745
|
+
if (!["omit", "same-origin", "include"].includes(credentials)) fail("INVALID_CREDENTIALS");
|
|
1746
|
+
const csrfHeaderName = options.csrfHeaderName ?? "X-CSRF-Token";
|
|
1747
|
+
const reserved = /* @__PURE__ */ new Set(["accept", "content-type", "authorization", "idempotency-key"]);
|
|
1748
|
+
try {
|
|
1749
|
+
new Headers([[csrfHeaderName, "test"]]);
|
|
1750
|
+
} catch {
|
|
1751
|
+
return fail("INVALID_CSRF_HEADER");
|
|
1752
|
+
}
|
|
1753
|
+
if (reserved.has(csrfHeaderName.toLowerCase())) fail("INVALID_CSRF_HEADER");
|
|
1754
|
+
reserved.add(csrfHeaderName.toLowerCase());
|
|
1731
1755
|
const call = async (path, body, signal, key) => {
|
|
1732
1756
|
if (typeof window === "undefined") fail("BROWSER_REQUIRED");
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1757
|
+
let url;
|
|
1758
|
+
try {
|
|
1759
|
+
url = new URL(base.replace(/\/+$/, "") + path, window.location.href);
|
|
1760
|
+
} catch {
|
|
1761
|
+
return fail("INVALID_BASE_URL");
|
|
1762
|
+
}
|
|
1763
|
+
if (url.username || url.password || url.search || url.hash || !backendProtocol(url)) fail("INVALID_BASE_URL");
|
|
1764
|
+
if (url.origin !== window.location.origin && !allowedOrigins.has(url.origin)) fail("BACKEND_ORIGIN_NOT_ALLOWED");
|
|
1737
1765
|
signal?.throwIfAborted();
|
|
1738
1766
|
const controller = new AbortController();
|
|
1739
1767
|
const cancel = () => controller.abort(signal?.reason);
|
|
1740
1768
|
signal?.addEventListener("abort", cancel, { once: true });
|
|
1741
1769
|
const timeout = setTimeout(() => controller.abort(), 1e4);
|
|
1742
1770
|
try {
|
|
1771
|
+
const headers = new Headers(typeof options.headers === "function" ? await abortable(Promise.resolve().then(options.headers), controller.signal) : options.headers);
|
|
1772
|
+
for (const name of reserved) if (headers.has(name)) fail("RESERVED_HEADER");
|
|
1773
|
+
headers.set("Accept", "application/json");
|
|
1774
|
+
headers.set("Content-Type", "application/json");
|
|
1775
|
+
if (key) headers.set("Idempotency-Key", key);
|
|
1776
|
+
if (options.csrfToken) {
|
|
1777
|
+
const csrf = await abortable(Promise.resolve().then(options.csrfToken), controller.signal);
|
|
1778
|
+
if (typeof csrf !== "string" || !csrf.trim() || csrf.length > 1024 || /[\r\n]/.test(csrf)) fail("CSRF_REQUIRED");
|
|
1779
|
+
headers.set(csrfHeaderName, csrf);
|
|
1780
|
+
} else if (!options.accessToken || credentials !== "omit") fail("CSRF_REQUIRED");
|
|
1781
|
+
if (options.accessToken) {
|
|
1782
|
+
const accessToken = await abortable(Promise.resolve().then(options.accessToken), controller.signal);
|
|
1783
|
+
if (typeof accessToken !== "string" || !accessToken || /\s/.test(accessToken)) fail("ACCESS_TOKEN_REQUIRED");
|
|
1784
|
+
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
1785
|
+
}
|
|
1786
|
+
controller.signal.throwIfAborted();
|
|
1743
1787
|
const response = await (options.fetch ?? globalThis.fetch)(url, {
|
|
1744
1788
|
method: "POST",
|
|
1745
|
-
credentials
|
|
1789
|
+
credentials,
|
|
1746
1790
|
redirect: "error",
|
|
1747
1791
|
cache: "no-store",
|
|
1748
1792
|
signal: controller.signal,
|
|
1749
|
-
headers
|
|
1750
|
-
Accept: "application/json",
|
|
1751
|
-
"Content-Type": "application/json",
|
|
1752
|
-
"X-CSRF-Token": csrf,
|
|
1753
|
-
...key ? { "Idempotency-Key": key } : {}
|
|
1754
|
-
},
|
|
1793
|
+
headers,
|
|
1755
1794
|
body: JSON.stringify(body)
|
|
1756
1795
|
});
|
|
1757
1796
|
if (!response.ok) fail(response.status === 401 || response.status === 403 ? "CMS_UNAUTHORIZED" : "CMS_REQUEST_FAILED");
|