@micha.bigler/ui-core-micha 2.2.12 → 2.2.14
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/dist/auth/apiClient.js +44 -12
- package/dist/auth/authApi.js +6 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/auth/apiClient.jsx +49 -12
- package/src/auth/authApi.jsx +6 -1
- package/src/index.js +6 -1
package/dist/auth/apiClient.js
CHANGED
|
@@ -9,25 +9,52 @@ let redirectingToLogin = false;
|
|
|
9
9
|
function isBrowser() {
|
|
10
10
|
return typeof window !== "undefined";
|
|
11
11
|
}
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
|
|
12
|
+
// Routen, die OHNE Login funktionieren müssen. Library-eigene Pfade sind eingefroren
|
|
13
|
+
// und können nicht entfernt werden — sonst würde z. B. `removePublicPath("/login")`
|
|
14
|
+
// auf der Login-Page selbst einen Redirect-Loop auslösen.
|
|
15
|
+
const BUILTIN_PUBLIC_PATHS = Object.freeze([
|
|
15
16
|
"/login",
|
|
16
17
|
"/signup",
|
|
17
|
-
"/reset-request-password",
|
|
18
|
-
"/invite", //
|
|
19
|
-
"/reset", //
|
|
20
|
-
"/welcome"
|
|
21
|
-
];
|
|
18
|
+
"/reset-request-password",
|
|
19
|
+
"/invite", // /invite/:uid/:token
|
|
20
|
+
"/reset", // /reset/:uid/:token
|
|
21
|
+
"/welcome",
|
|
22
|
+
]);
|
|
23
|
+
const CONSUMER_PUBLIC_PATHS = new Set();
|
|
24
|
+
/**
|
|
25
|
+
* Register an additional public path so a 401 on that route does not auto-redirect
|
|
26
|
+
* to `/login`. Typical use: a public landing on `/` in an otherwise authenticated app.
|
|
27
|
+
*
|
|
28
|
+
* MUST be called before the AuthProvider mounts (i.e. before `ReactDOM.render`).
|
|
29
|
+
* Calling it later won't help the bootstrap probe which fires on AuthProvider mount.
|
|
30
|
+
*/
|
|
31
|
+
export function addPublicPath(path) {
|
|
32
|
+
if (typeof path === "string" && path) {
|
|
33
|
+
CONSUMER_PUBLIC_PATHS.add(path);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/** Remove a consumer-added public path. Library-internal paths are protected. */
|
|
37
|
+
export function removePublicPath(path) {
|
|
38
|
+
CONSUMER_PUBLIC_PATHS.delete(path);
|
|
39
|
+
}
|
|
22
40
|
function isPublicSitePath(pathname) {
|
|
23
41
|
return pathname === "/sites" || pathname.startsWith("/sites/");
|
|
24
42
|
}
|
|
43
|
+
// Match rule: an entry of exactly "/" requires strict equality (avoids matching
|
|
44
|
+
// every path with startsWith). Other entries keep the looser prefix match so
|
|
45
|
+
// dynamic routes like /invite/:uid/:token still work.
|
|
46
|
+
function matchesPublicPath(pathname, entry) {
|
|
47
|
+
if (entry === "/")
|
|
48
|
+
return pathname === "/";
|
|
49
|
+
return pathname.startsWith(entry);
|
|
50
|
+
}
|
|
25
51
|
function redirectToLoginOnce() {
|
|
26
52
|
if (!isBrowser())
|
|
27
53
|
return;
|
|
28
54
|
const currentPath = window.location.pathname;
|
|
29
|
-
|
|
30
|
-
|
|
55
|
+
const isPublicPage = isPublicSitePath(currentPath) ||
|
|
56
|
+
BUILTIN_PUBLIC_PATHS.some((path) => matchesPublicPath(currentPath, path)) ||
|
|
57
|
+
Array.from(CONSUMER_PUBLIC_PATHS).some((path) => matchesPublicPath(currentPath, path));
|
|
31
58
|
// Wenn ja: NICHT weiterleiten. Der 401 Fehler wird an die Komponente durchgereicht.
|
|
32
59
|
if (isPublicPage)
|
|
33
60
|
return;
|
|
@@ -83,13 +110,18 @@ function extractAuthSignal(data) {
|
|
|
83
110
|
return { code: null, i18nKey: null };
|
|
84
111
|
}
|
|
85
112
|
apiClient.interceptors.response.use((response) => response, (error) => {
|
|
86
|
-
var _a, _b, _c, _d;
|
|
113
|
+
var _a, _b, _c, _d, _e;
|
|
87
114
|
const status = (_b = (_a = error === null || error === void 0 ? void 0 : error.response) === null || _a === void 0 ? void 0 : _a.status) !== null && _b !== void 0 ? _b : null;
|
|
88
115
|
const data = (_d = (_c = error === null || error === void 0 ? void 0 : error.response) === null || _c === void 0 ? void 0 : _c.data) !== null && _d !== void 0 ? _d : {};
|
|
89
116
|
const { code, i18nKey } = extractAuthSignal(data);
|
|
90
117
|
const isAuthStatus = status === 401 || status === 403;
|
|
91
118
|
const isNotAuthenticated = code === "not_authenticated" || i18nKey === "auth.not_authenticated";
|
|
92
|
-
|
|
119
|
+
// Per-request opt-out: bootstrap probes (e.g. fetchCurrentUser on app start)
|
|
120
|
+
// expect to handle 401 silently and must not trigger a redirect-on-mount.
|
|
121
|
+
// Carried as an axios config property, so it never travels to the backend
|
|
122
|
+
// (would otherwise trigger a CORS preflight on cross-origin requests).
|
|
123
|
+
const skipRedirect = ((_e = error === null || error === void 0 ? void 0 : error.config) === null || _e === void 0 ? void 0 : _e.skipAuthRedirect) === true;
|
|
124
|
+
if (isAuthStatus && isNotAuthenticated && !skipRedirect) {
|
|
93
125
|
redirectToLoginOnce();
|
|
94
126
|
}
|
|
95
127
|
return Promise.reject(error);
|
package/dist/auth/authApi.js
CHANGED
|
@@ -12,7 +12,12 @@ function getCsrfToken() {
|
|
|
12
12
|
// Session & User Core
|
|
13
13
|
// -----------------------------
|
|
14
14
|
export async function fetchCurrentUser() {
|
|
15
|
-
|
|
15
|
+
// Bootstrap-Probe: 401 darf nicht in einen Login-Redirect umschlagen,
|
|
16
|
+
// damit Public-Landings auf "/" sichtbar bleiben. `skipAuthRedirect` ist eine
|
|
17
|
+
// client-seitige axios-Config-Property und wird nicht ans Backend gesendet.
|
|
18
|
+
const res = await apiClient.get(`${USERS_BASE}/current/`, {
|
|
19
|
+
skipAuthRedirect: true,
|
|
20
|
+
});
|
|
16
21
|
return res.data;
|
|
17
22
|
}
|
|
18
23
|
export async function fetchAuthMethods() {
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// index.js (Entry Point deiner Library)
|
|
2
2
|
// --- 1. Auth Context (Essentiell für den Wrapper) ---
|
|
3
3
|
export { AuthContext, AuthProvider } from './auth/AuthContext';
|
|
4
|
-
export { default as apiClient, ensureCsrfToken } from "./auth/apiClient";
|
|
4
|
+
export { default as apiClient, ensureCsrfToken, addPublicPath, removePublicPath, } from "./auth/apiClient";
|
|
5
5
|
// --- 2. API & Services (Neue Struktur) ---
|
|
6
6
|
// Statt dem 'authApi'-Objekt exportieren wir die Funktionen direkt.
|
|
7
7
|
// Konsumenten können dann machen: import { loginWithPassword } from 'django-core-micha';
|
package/package.json
CHANGED
package/src/auth/apiClient.jsx
CHANGED
|
@@ -13,29 +13,60 @@ function isBrowser() {
|
|
|
13
13
|
return typeof window !== "undefined";
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
|
|
16
|
+
// Routen, die OHNE Login funktionieren müssen. Library-eigene Pfade sind eingefroren
|
|
17
|
+
// und können nicht entfernt werden — sonst würde z. B. `removePublicPath("/login")`
|
|
18
|
+
// auf der Login-Page selbst einen Redirect-Loop auslösen.
|
|
19
|
+
const BUILTIN_PUBLIC_PATHS = Object.freeze([
|
|
19
20
|
"/login",
|
|
20
21
|
"/signup",
|
|
21
|
-
"/reset-request-password",
|
|
22
|
-
"/invite", //
|
|
23
|
-
"/reset", //
|
|
24
|
-
"/welcome"
|
|
25
|
-
];
|
|
22
|
+
"/reset-request-password",
|
|
23
|
+
"/invite", // /invite/:uid/:token
|
|
24
|
+
"/reset", // /reset/:uid/:token
|
|
25
|
+
"/welcome",
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
const CONSUMER_PUBLIC_PATHS = new Set();
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Register an additional public path so a 401 on that route does not auto-redirect
|
|
32
|
+
* to `/login`. Typical use: a public landing on `/` in an otherwise authenticated app.
|
|
33
|
+
*
|
|
34
|
+
* MUST be called before the AuthProvider mounts (i.e. before `ReactDOM.render`).
|
|
35
|
+
* Calling it later won't help the bootstrap probe which fires on AuthProvider mount.
|
|
36
|
+
*/
|
|
37
|
+
export function addPublicPath(path) {
|
|
38
|
+
if (typeof path === "string" && path) {
|
|
39
|
+
CONSUMER_PUBLIC_PATHS.add(path);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/** Remove a consumer-added public path. Library-internal paths are protected. */
|
|
44
|
+
export function removePublicPath(path) {
|
|
45
|
+
CONSUMER_PUBLIC_PATHS.delete(path);
|
|
46
|
+
}
|
|
26
47
|
|
|
27
48
|
function isPublicSitePath(pathname) {
|
|
28
49
|
return pathname === "/sites" || pathname.startsWith("/sites/");
|
|
29
50
|
}
|
|
30
51
|
|
|
52
|
+
// Match rule: an entry of exactly "/" requires strict equality (avoids matching
|
|
53
|
+
// every path with startsWith). Other entries keep the looser prefix match so
|
|
54
|
+
// dynamic routes like /invite/:uid/:token still work.
|
|
55
|
+
function matchesPublicPath(pathname, entry) {
|
|
56
|
+
if (entry === "/") return pathname === "/";
|
|
57
|
+
return pathname.startsWith(entry);
|
|
58
|
+
}
|
|
59
|
+
|
|
31
60
|
function redirectToLoginOnce() {
|
|
32
61
|
if (!isBrowser()) return;
|
|
33
62
|
|
|
34
63
|
const currentPath = window.location.pathname;
|
|
35
64
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
65
|
+
const isPublicPage =
|
|
66
|
+
isPublicSitePath(currentPath) ||
|
|
67
|
+
BUILTIN_PUBLIC_PATHS.some((path) => matchesPublicPath(currentPath, path)) ||
|
|
68
|
+
Array.from(CONSUMER_PUBLIC_PATHS).some((path) => matchesPublicPath(currentPath, path));
|
|
69
|
+
|
|
39
70
|
// Wenn ja: NICHT weiterleiten. Der 401 Fehler wird an die Komponente durchgereicht.
|
|
40
71
|
if (isPublicPage) return;
|
|
41
72
|
|
|
@@ -108,7 +139,13 @@ apiClient.interceptors.response.use(
|
|
|
108
139
|
const isNotAuthenticated =
|
|
109
140
|
code === "not_authenticated" || i18nKey === "auth.not_authenticated";
|
|
110
141
|
|
|
111
|
-
|
|
142
|
+
// Per-request opt-out: bootstrap probes (e.g. fetchCurrentUser on app start)
|
|
143
|
+
// expect to handle 401 silently and must not trigger a redirect-on-mount.
|
|
144
|
+
// Carried as an axios config property, so it never travels to the backend
|
|
145
|
+
// (would otherwise trigger a CORS preflight on cross-origin requests).
|
|
146
|
+
const skipRedirect = error?.config?.skipAuthRedirect === true;
|
|
147
|
+
|
|
148
|
+
if (isAuthStatus && isNotAuthenticated && !skipRedirect) {
|
|
112
149
|
redirectToLoginOnce();
|
|
113
150
|
}
|
|
114
151
|
return Promise.reject(error);
|
package/src/auth/authApi.jsx
CHANGED
|
@@ -14,7 +14,12 @@ function getCsrfToken() {
|
|
|
14
14
|
// -----------------------------
|
|
15
15
|
|
|
16
16
|
export async function fetchCurrentUser() {
|
|
17
|
-
|
|
17
|
+
// Bootstrap-Probe: 401 darf nicht in einen Login-Redirect umschlagen,
|
|
18
|
+
// damit Public-Landings auf "/" sichtbar bleiben. `skipAuthRedirect` ist eine
|
|
19
|
+
// client-seitige axios-Config-Property und wird nicht ans Backend gesendet.
|
|
20
|
+
const res = await apiClient.get(`${USERS_BASE}/current/`, {
|
|
21
|
+
skipAuthRedirect: true,
|
|
22
|
+
});
|
|
18
23
|
return res.data;
|
|
19
24
|
}
|
|
20
25
|
|
package/src/index.js
CHANGED
|
@@ -3,7 +3,12 @@
|
|
|
3
3
|
// --- 1. Auth Context (Essentiell für den Wrapper) ---
|
|
4
4
|
export { AuthContext, AuthProvider } from './auth/AuthContext';
|
|
5
5
|
|
|
6
|
-
export {
|
|
6
|
+
export {
|
|
7
|
+
default as apiClient,
|
|
8
|
+
ensureCsrfToken,
|
|
9
|
+
addPublicPath,
|
|
10
|
+
removePublicPath,
|
|
11
|
+
} from "./auth/apiClient";
|
|
7
12
|
|
|
8
13
|
// --- 2. API & Services (Neue Struktur) ---
|
|
9
14
|
// Statt dem 'authApi'-Objekt exportieren wir die Funktionen direkt.
|