authera 2.0.6 → 2.2.0
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/helper/axios.d.ts +10 -0
- package/dist/helper/axios.js +105 -0
- package/dist/index.d.ts +5 -1
- package/dist/index.js +4 -1
- package/dist/web/login.d.ts +3 -1
- package/dist/web/login.js +5 -4
- package/package.json +1 -1
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type AxiosInstance } from "axios";
|
|
2
|
+
import type { customeFunc } from "./storage";
|
|
3
|
+
import type { AuthHookSettings } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* Create a preconfigured Axios instance that:
|
|
6
|
+
* - Attaches Authorization header from storage on each request
|
|
7
|
+
* - On 401 responses, attempts token refresh and retries the original request
|
|
8
|
+
* - If refresh fails, redirects to fallback_401_url
|
|
9
|
+
*/
|
|
10
|
+
export default function createAxios<T extends string>(storage: customeFunc, settings: AuthHookSettings<T>): AxiosInstance;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import axios from "axios";
|
|
3
|
+
/**
|
|
4
|
+
* Create a preconfigured Axios instance that:
|
|
5
|
+
* - Attaches Authorization header from storage on each request
|
|
6
|
+
* - On 401 responses, attempts token refresh and retries the original request
|
|
7
|
+
* - If refresh fails, redirects to fallback_401_url
|
|
8
|
+
*/
|
|
9
|
+
export default function createAxios(storage, settings) {
|
|
10
|
+
const instance = axios.create({
|
|
11
|
+
baseURL: settings.backendUrl,
|
|
12
|
+
});
|
|
13
|
+
// Single-flight refresh across concurrent 401s
|
|
14
|
+
let refreshPromise = null;
|
|
15
|
+
const readAccessToken = () => {
|
|
16
|
+
try {
|
|
17
|
+
const token = storage.get("access_token");
|
|
18
|
+
return token || null;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const readRefreshToken = () => {
|
|
25
|
+
try {
|
|
26
|
+
const token = storage.get("refresh_token");
|
|
27
|
+
return token || null;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const writeTokens = (access, refresh) => {
|
|
34
|
+
if (access)
|
|
35
|
+
storage.set("access_token", access);
|
|
36
|
+
if (refresh)
|
|
37
|
+
storage.set("refresh_token", refresh);
|
|
38
|
+
};
|
|
39
|
+
const resolveAccessFromResponse = (data) => {
|
|
40
|
+
return data?.access_token ?? data?.accessToken ?? data?.token ?? null;
|
|
41
|
+
};
|
|
42
|
+
const resolveRefreshFromResponse = (data) => {
|
|
43
|
+
return data?.refresh_token ?? data?.refreshToken ?? null;
|
|
44
|
+
};
|
|
45
|
+
const doRefresh = async () => {
|
|
46
|
+
const token = readRefreshToken();
|
|
47
|
+
if (!token)
|
|
48
|
+
return null;
|
|
49
|
+
try {
|
|
50
|
+
// Use a bare client to avoid interceptor recursion
|
|
51
|
+
const client = axios.create({ baseURL: settings.backendUrl });
|
|
52
|
+
const resp = await client.post("auth/refresh/", { refresh_token: token });
|
|
53
|
+
const newAccess = resolveAccessFromResponse(resp.data);
|
|
54
|
+
const newRefresh = resolveRefreshFromResponse(resp.data);
|
|
55
|
+
writeTokens(newAccess, newRefresh);
|
|
56
|
+
return newAccess;
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const getOrStartRefresh = () => {
|
|
63
|
+
if (!refreshPromise) {
|
|
64
|
+
refreshPromise = doRefresh().finally(() => {
|
|
65
|
+
// allow subsequent refreshes
|
|
66
|
+
refreshPromise = null;
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return refreshPromise;
|
|
70
|
+
};
|
|
71
|
+
// Attach Authorization header on each request
|
|
72
|
+
instance.interceptors.request.use((config) => {
|
|
73
|
+
const access = readAccessToken();
|
|
74
|
+
if (access) {
|
|
75
|
+
config.headers = {
|
|
76
|
+
...config.headers,
|
|
77
|
+
Authorization: `Bearer ${access}`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return config;
|
|
81
|
+
});
|
|
82
|
+
// Handle 401 responses with token refresh
|
|
83
|
+
instance.interceptors.response.use((response) => response, async (error) => {
|
|
84
|
+
const responseStatus = error.response?.status;
|
|
85
|
+
const originalConfig = (error.config || {});
|
|
86
|
+
if (responseStatus === 401 && !originalConfig._retry) {
|
|
87
|
+
originalConfig._retry = true;
|
|
88
|
+
const newAccess = await getOrStartRefresh();
|
|
89
|
+
if (newAccess) {
|
|
90
|
+
originalConfig.headers = {
|
|
91
|
+
...(originalConfig.headers || {}),
|
|
92
|
+
Authorization: `Bearer ${newAccess}`,
|
|
93
|
+
};
|
|
94
|
+
// retry original request with new token
|
|
95
|
+
return instance.request(originalConfig);
|
|
96
|
+
}
|
|
97
|
+
// refresh failed: redirect to login/fallback
|
|
98
|
+
if (typeof window !== "undefined" && settings.fallback_401_url) {
|
|
99
|
+
window.location.href = settings.fallback_401_url;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return Promise.reject(error);
|
|
103
|
+
});
|
|
104
|
+
return instance;
|
|
105
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,6 +26,10 @@ export default function AuthHook<T extends string>(props: AuthHookSettings<T>):
|
|
|
26
26
|
setRefreshToken: (token: string) => void;
|
|
27
27
|
logout: () => void;
|
|
28
28
|
};
|
|
29
|
-
|
|
29
|
+
axios: import("axios").AxiosInstance;
|
|
30
|
+
LoginScenario: (prop: {
|
|
31
|
+
submitButtonClassName?: string;
|
|
32
|
+
submitButtonText?: string;
|
|
33
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
30
34
|
};
|
|
31
35
|
export { default as AuthGuard } from "./web/guard";
|
package/dist/index.js
CHANGED
|
@@ -4,16 +4,19 @@ import { name2storage } from "./helper/storage";
|
|
|
4
4
|
import AuthProvider from "./web";
|
|
5
5
|
import { useAuth } from "./hooks/useAuth";
|
|
6
6
|
import LoginForm from "./web/login";
|
|
7
|
+
import createAxios from "./helper/axios";
|
|
7
8
|
export default function AuthHook(props) {
|
|
8
9
|
// set storage functions
|
|
9
10
|
let storage = props.storage;
|
|
10
11
|
if (typeof storage === "string")
|
|
11
12
|
storage = name2storage(storage);
|
|
12
13
|
// create backend data
|
|
14
|
+
const axios = createAxios(storage, props);
|
|
13
15
|
return {
|
|
14
16
|
createAuthProvider: (children) => (_jsx(AuthProvider, { storage: storage, authera_props: props, children: children })),
|
|
15
17
|
useAuth: () => useAuth(),
|
|
16
|
-
|
|
18
|
+
axios,
|
|
19
|
+
LoginScenario: (prop) => (_jsx(LoginForm, { on_after_login: props.on_after_login, on_after_step: props.on_after_step, backendUrl: props.backendUrl, submitButtonClassName: prop.submitButtonClassName, submitButtonText: prop.submitButtonText })),
|
|
17
20
|
};
|
|
18
21
|
}
|
|
19
22
|
export { default as AuthGuard } from "./web/guard";
|
package/dist/web/login.d.ts
CHANGED
|
@@ -2,6 +2,8 @@ interface LoginFormProps {
|
|
|
2
2
|
on_after_login?: (response_data: any) => void;
|
|
3
3
|
on_after_step?: (step_key: string) => void;
|
|
4
4
|
backendUrl: string;
|
|
5
|
+
submitButtonClassName?: string;
|
|
6
|
+
submitButtonText?: string;
|
|
5
7
|
}
|
|
6
|
-
export default function LoginForm({ on_after_login, on_after_step, backendUrl, }: LoginFormProps): import("react/jsx-runtime").JSX.Element;
|
|
8
|
+
export default function LoginForm({ on_after_login, on_after_step, backendUrl, submitButtonClassName, submitButtonText, }: LoginFormProps): import("react/jsx-runtime").JSX.Element;
|
|
7
9
|
export {};
|
package/dist/web/login.js
CHANGED
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import axios from "axios";
|
|
4
4
|
import { convertFromSchema } from "minimal-form";
|
|
5
|
-
import EasyForm from "minimal-form";
|
|
6
5
|
import { useEffect, useState } from "react";
|
|
7
6
|
import { useForm } from "react-hook-form";
|
|
8
7
|
import { useAuth } from "../hooks/useAuth";
|
|
9
|
-
|
|
8
|
+
import EasyFormModule from "minimal-form";
|
|
9
|
+
const EasyForm = EasyFormModule.default || EasyFormModule;
|
|
10
|
+
export default function LoginForm({ on_after_login, on_after_step, backendUrl, submitButtonClassName, submitButtonText, }) {
|
|
10
11
|
const [steps, stepsHnadler] = useState();
|
|
11
12
|
const [activeStep, activeStepHandler] = useState();
|
|
12
13
|
const [loading, loadingHandler] = useState(true);
|
|
@@ -15,7 +16,7 @@ export default function LoginForm({ on_after_login, on_after_step, backendUrl, }
|
|
|
15
16
|
const { handleSubmit, control } = useForm();
|
|
16
17
|
const { setUserData, setPermits } = useAuth();
|
|
17
18
|
const request = axios.create({
|
|
18
|
-
baseURL: backendUrl,
|
|
19
|
+
baseURL: backendUrl + "/auth",
|
|
19
20
|
});
|
|
20
21
|
// fetch data from steps
|
|
21
22
|
useEffect(() => {
|
|
@@ -63,5 +64,5 @@ export default function LoginForm({ on_after_login, on_after_step, backendUrl, }
|
|
|
63
64
|
if (loading)
|
|
64
65
|
return _jsx("p", { children: "loading ..." });
|
|
65
66
|
// show forms from steps
|
|
66
|
-
return (_jsxs("form", { onSubmit: handleSubmit(onSubmit), children: [_jsx(EasyForm, { control: control, structure: activeStep
|
|
67
|
+
return (_jsxs("form", { onSubmit: handleSubmit(onSubmit), children: [_jsx(EasyForm, { control: control, structure: activeStep?.structure || {} }), _jsx("button", { type: "submit", className: submitButtonClassName, children: submitButtonText })] }));
|
|
67
68
|
}
|