@djangocfg/api 2.1.420 → 2.1.422
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-server.cjs +45 -27
- package/dist/auth-server.cjs.map +1 -1
- package/dist/auth-server.mjs +45 -27
- package/dist/auth-server.mjs.map +1 -1
- package/dist/auth.cjs +61 -33
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +2 -1
- package/dist/auth.d.ts +2 -1
- package/dist/auth.mjs +61 -33
- package/dist/auth.mjs.map +1 -1
- package/dist/clients.cjs +45 -27
- package/dist/clients.cjs.map +1 -1
- package/dist/clients.d.cts +1 -1
- package/dist/clients.d.ts +1 -1
- package/dist/clients.mjs +45 -27
- package/dist/clients.mjs.map +1 -1
- package/dist/hooks.cjs +48 -29
- package/dist/hooks.cjs.map +1 -1
- package/dist/hooks.mjs +48 -29
- package/dist/hooks.mjs.map +1 -1
- package/dist/index.cjs +45 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +45 -27
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsApiKeyRetrieve.ts +12 -3
- package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsOauthConnectionsList.ts +12 -3
- package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsOauthProvidersRetrieve.ts +12 -3
- package/src/_api/generated/_cfg_accounts/hooks/useCfgAccountsProfileRetrieve.ts +12 -3
- package/src/_api/generated/_cfg_accounts/openapi.json +9 -0
- package/src/_api/generated/_cfg_centrifugo/hooks/useCfgCentrifugoAuthTokenRetrieve.ts +12 -3
- package/src/_api/generated/_cfg_totp/hooks/useCfgTotpBackupCodesRetrieve.ts +12 -3
- package/src/_api/generated/_cfg_totp/hooks/useCfgTotpDevicesRetrieve.ts +12 -3
- package/src/_api/generated/client/client.gen.ts +1 -4
- package/src/_api/generated/client/types.gen.ts +3 -2
- package/src/_api/generated/client/utils.gen.ts +6 -8
- package/src/_api/generated/core/params.gen.ts +4 -4
- package/src/_api/generated/helpers/auth.ts +58 -9
- package/src/_api/generated/index.ts +6 -0
- package/src/_api/generated/openapi.json +9 -0
- package/src/_api/generated/sdk.gen.ts +18 -6
- package/src/auth/hooks/useAuthForm.ts +5 -4
- package/src/auth/hooks/useAutoAuth.ts +23 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@djangocfg/api",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.422",
|
|
4
4
|
"description": "Auto-generated TypeScript API client with React hooks, SWR integration, and Zod validation for Django REST Framework backends",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"django",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"devDependencies": {
|
|
85
85
|
"@types/node": "^25.2.3",
|
|
86
86
|
"@types/react": "^19.2.15",
|
|
87
|
-
"@djangocfg/typescript-config": "^2.1.
|
|
87
|
+
"@djangocfg/typescript-config": "^2.1.422",
|
|
88
88
|
"next": "^16.2.2",
|
|
89
89
|
"react": "^19.2.4",
|
|
90
90
|
"tsup": "^8.5.0",
|
|
@@ -12,11 +12,20 @@ import { APIKeySchema } from "../schemas/APIKey";
|
|
|
12
12
|
|
|
13
13
|
type Result = CfgAccountsApiKeyRetrieveResponses[keyof CfgAccountsApiKeyRetrieveResponses];
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Optional `enabled` flag — when `false`, the SWR key becomes `null` and
|
|
17
|
+
* no request is made. Use for gated endpoints (auth-required, feature
|
|
18
|
+
* flags, "fetch only when X is selected"). The flag is stripped before
|
|
19
|
+
* being forwarded to `useSWR` so the underlying SWR config stays pristine.
|
|
20
|
+
*/
|
|
21
|
+
type HookConfig = SWRConfiguration<Result> & { enabled?: boolean };
|
|
22
|
+
|
|
15
23
|
export function useCfgAccountsApiKeyRetrieve(
|
|
16
24
|
args?: Omit<CfgAccountsApiKeyRetrieveData, "url" | "body">,
|
|
17
|
-
config?:
|
|
25
|
+
config?: HookConfig,
|
|
18
26
|
) {
|
|
19
|
-
const
|
|
27
|
+
const { enabled = true, ...swrConfig } = config ?? {};
|
|
28
|
+
const key = enabled ? ["cfg_accounts_api_key_retrieve", args ?? {}] as const : null;
|
|
20
29
|
return useSWR<Result>(
|
|
21
30
|
key,
|
|
22
31
|
async () => {
|
|
@@ -60,6 +69,6 @@ export function useCfgAccountsApiKeyRetrieve(
|
|
|
60
69
|
}
|
|
61
70
|
return parsed.data as unknown as Result;
|
|
62
71
|
},
|
|
63
|
-
|
|
72
|
+
swrConfig,
|
|
64
73
|
);
|
|
65
74
|
}
|
|
@@ -12,11 +12,20 @@ import { cfg_accounts_oauth_connections_response_200_AutoRefSchema } from "../sc
|
|
|
12
12
|
|
|
13
13
|
type Result = CfgAccountsOauthConnectionsListResponses[keyof CfgAccountsOauthConnectionsListResponses];
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Optional `enabled` flag — when `false`, the SWR key becomes `null` and
|
|
17
|
+
* no request is made. Use for gated endpoints (auth-required, feature
|
|
18
|
+
* flags, "fetch only when X is selected"). The flag is stripped before
|
|
19
|
+
* being forwarded to `useSWR` so the underlying SWR config stays pristine.
|
|
20
|
+
*/
|
|
21
|
+
type HookConfig = SWRConfiguration<Result> & { enabled?: boolean };
|
|
22
|
+
|
|
15
23
|
export function useCfgAccountsOauthConnectionsList(
|
|
16
24
|
args?: Omit<CfgAccountsOauthConnectionsListData, "url" | "body">,
|
|
17
|
-
config?:
|
|
25
|
+
config?: HookConfig,
|
|
18
26
|
) {
|
|
19
|
-
const
|
|
27
|
+
const { enabled = true, ...swrConfig } = config ?? {};
|
|
28
|
+
const key = enabled ? ["cfg_accounts_oauth_connections_list", args ?? {}] as const : null;
|
|
20
29
|
return useSWR<Result>(
|
|
21
30
|
key,
|
|
22
31
|
async () => {
|
|
@@ -60,6 +69,6 @@ export function useCfgAccountsOauthConnectionsList(
|
|
|
60
69
|
}
|
|
61
70
|
return parsed.data as unknown as Result;
|
|
62
71
|
},
|
|
63
|
-
|
|
72
|
+
swrConfig,
|
|
64
73
|
);
|
|
65
74
|
}
|
|
@@ -12,11 +12,20 @@ import { OAuthProvidersResponseSchema } from "../schemas/OAuthProvidersResponse"
|
|
|
12
12
|
|
|
13
13
|
type Result = CfgAccountsOauthProvidersRetrieveResponses[keyof CfgAccountsOauthProvidersRetrieveResponses];
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Optional `enabled` flag — when `false`, the SWR key becomes `null` and
|
|
17
|
+
* no request is made. Use for gated endpoints (auth-required, feature
|
|
18
|
+
* flags, "fetch only when X is selected"). The flag is stripped before
|
|
19
|
+
* being forwarded to `useSWR` so the underlying SWR config stays pristine.
|
|
20
|
+
*/
|
|
21
|
+
type HookConfig = SWRConfiguration<Result> & { enabled?: boolean };
|
|
22
|
+
|
|
15
23
|
export function useCfgAccountsOauthProvidersRetrieve(
|
|
16
24
|
args?: Omit<CfgAccountsOauthProvidersRetrieveData, "url" | "body">,
|
|
17
|
-
config?:
|
|
25
|
+
config?: HookConfig,
|
|
18
26
|
) {
|
|
19
|
-
const
|
|
27
|
+
const { enabled = true, ...swrConfig } = config ?? {};
|
|
28
|
+
const key = enabled ? ["cfg_accounts_oauth_providers_retrieve", args ?? {}] as const : null;
|
|
20
29
|
return useSWR<Result>(
|
|
21
30
|
key,
|
|
22
31
|
async () => {
|
|
@@ -60,6 +69,6 @@ export function useCfgAccountsOauthProvidersRetrieve(
|
|
|
60
69
|
}
|
|
61
70
|
return parsed.data as unknown as Result;
|
|
62
71
|
},
|
|
63
|
-
|
|
72
|
+
swrConfig,
|
|
64
73
|
);
|
|
65
74
|
}
|
|
@@ -12,11 +12,20 @@ import { UserSchema } from "../schemas/User";
|
|
|
12
12
|
|
|
13
13
|
type Result = CfgAccountsProfileRetrieveResponses[keyof CfgAccountsProfileRetrieveResponses];
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Optional `enabled` flag — when `false`, the SWR key becomes `null` and
|
|
17
|
+
* no request is made. Use for gated endpoints (auth-required, feature
|
|
18
|
+
* flags, "fetch only when X is selected"). The flag is stripped before
|
|
19
|
+
* being forwarded to `useSWR` so the underlying SWR config stays pristine.
|
|
20
|
+
*/
|
|
21
|
+
type HookConfig = SWRConfiguration<Result> & { enabled?: boolean };
|
|
22
|
+
|
|
15
23
|
export function useCfgAccountsProfileRetrieve(
|
|
16
24
|
args?: Omit<CfgAccountsProfileRetrieveData, "url" | "body">,
|
|
17
|
-
config?:
|
|
25
|
+
config?: HookConfig,
|
|
18
26
|
) {
|
|
19
|
-
const
|
|
27
|
+
const { enabled = true, ...swrConfig } = config ?? {};
|
|
28
|
+
const key = enabled ? ["cfg_accounts_profile_retrieve", args ?? {}] as const : null;
|
|
20
29
|
return useSWR<Result>(
|
|
21
30
|
key,
|
|
22
31
|
async () => {
|
|
@@ -60,6 +69,6 @@ export function useCfgAccountsProfileRetrieve(
|
|
|
60
69
|
}
|
|
61
70
|
return parsed.data as unknown as Result;
|
|
62
71
|
},
|
|
63
|
-
|
|
72
|
+
swrConfig,
|
|
64
73
|
);
|
|
65
74
|
}
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"cookieAuth": []
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"apiKeyAuth": []
|
|
23
26
|
}
|
|
24
27
|
],
|
|
25
28
|
"responses": {
|
|
@@ -71,6 +74,9 @@
|
|
|
71
74
|
},
|
|
72
75
|
{
|
|
73
76
|
"cookieAuth": []
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"apiKeyAuth": []
|
|
74
80
|
}
|
|
75
81
|
],
|
|
76
82
|
"responses": {
|
|
@@ -131,6 +137,9 @@
|
|
|
131
137
|
},
|
|
132
138
|
{
|
|
133
139
|
"cookieAuth": []
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"apiKeyAuth": []
|
|
134
143
|
}
|
|
135
144
|
],
|
|
136
145
|
"responses": {
|
|
@@ -12,11 +12,20 @@ import { ConnectionTokenResponseSchema } from "../schemas/ConnectionTokenRespons
|
|
|
12
12
|
|
|
13
13
|
type Result = CfgCentrifugoAuthTokenRetrieveResponses[keyof CfgCentrifugoAuthTokenRetrieveResponses];
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Optional `enabled` flag — when `false`, the SWR key becomes `null` and
|
|
17
|
+
* no request is made. Use for gated endpoints (auth-required, feature
|
|
18
|
+
* flags, "fetch only when X is selected"). The flag is stripped before
|
|
19
|
+
* being forwarded to `useSWR` so the underlying SWR config stays pristine.
|
|
20
|
+
*/
|
|
21
|
+
type HookConfig = SWRConfiguration<Result> & { enabled?: boolean };
|
|
22
|
+
|
|
15
23
|
export function useCfgCentrifugoAuthTokenRetrieve(
|
|
16
24
|
args?: Omit<CfgCentrifugoAuthTokenRetrieveData, "url" | "body">,
|
|
17
|
-
config?:
|
|
25
|
+
config?: HookConfig,
|
|
18
26
|
) {
|
|
19
|
-
const
|
|
27
|
+
const { enabled = true, ...swrConfig } = config ?? {};
|
|
28
|
+
const key = enabled ? ["cfg_centrifugo_auth_token_retrieve", args ?? {}] as const : null;
|
|
20
29
|
return useSWR<Result>(
|
|
21
30
|
key,
|
|
22
31
|
async () => {
|
|
@@ -60,6 +69,6 @@ export function useCfgCentrifugoAuthTokenRetrieve(
|
|
|
60
69
|
}
|
|
61
70
|
return parsed.data as unknown as Result;
|
|
62
71
|
},
|
|
63
|
-
|
|
72
|
+
swrConfig,
|
|
64
73
|
);
|
|
65
74
|
}
|
|
@@ -12,11 +12,20 @@ import { BackupCodesStatusSchema } from "../schemas/BackupCodesStatus";
|
|
|
12
12
|
|
|
13
13
|
type Result = CfgTotpBackupCodesRetrieveResponses[keyof CfgTotpBackupCodesRetrieveResponses];
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Optional `enabled` flag — when `false`, the SWR key becomes `null` and
|
|
17
|
+
* no request is made. Use for gated endpoints (auth-required, feature
|
|
18
|
+
* flags, "fetch only when X is selected"). The flag is stripped before
|
|
19
|
+
* being forwarded to `useSWR` so the underlying SWR config stays pristine.
|
|
20
|
+
*/
|
|
21
|
+
type HookConfig = SWRConfiguration<Result> & { enabled?: boolean };
|
|
22
|
+
|
|
15
23
|
export function useCfgTotpBackupCodesRetrieve(
|
|
16
24
|
args?: Omit<CfgTotpBackupCodesRetrieveData, "url" | "body">,
|
|
17
|
-
config?:
|
|
25
|
+
config?: HookConfig,
|
|
18
26
|
) {
|
|
19
|
-
const
|
|
27
|
+
const { enabled = true, ...swrConfig } = config ?? {};
|
|
28
|
+
const key = enabled ? ["cfg_totp_backup_codes_retrieve", args ?? {}] as const : null;
|
|
20
29
|
return useSWR<Result>(
|
|
21
30
|
key,
|
|
22
31
|
async () => {
|
|
@@ -60,6 +69,6 @@ export function useCfgTotpBackupCodesRetrieve(
|
|
|
60
69
|
}
|
|
61
70
|
return parsed.data as unknown as Result;
|
|
62
71
|
},
|
|
63
|
-
|
|
72
|
+
swrConfig,
|
|
64
73
|
);
|
|
65
74
|
}
|
|
@@ -12,11 +12,20 @@ import { DeviceListResponseSchema } from "../schemas/DeviceListResponse";
|
|
|
12
12
|
|
|
13
13
|
type Result = CfgTotpDevicesRetrieveResponses[keyof CfgTotpDevicesRetrieveResponses];
|
|
14
14
|
|
|
15
|
+
/**
|
|
16
|
+
* Optional `enabled` flag — when `false`, the SWR key becomes `null` and
|
|
17
|
+
* no request is made. Use for gated endpoints (auth-required, feature
|
|
18
|
+
* flags, "fetch only when X is selected"). The flag is stripped before
|
|
19
|
+
* being forwarded to `useSWR` so the underlying SWR config stays pristine.
|
|
20
|
+
*/
|
|
21
|
+
type HookConfig = SWRConfiguration<Result> & { enabled?: boolean };
|
|
22
|
+
|
|
15
23
|
export function useCfgTotpDevicesRetrieve(
|
|
16
24
|
args?: Omit<CfgTotpDevicesRetrieveData, "url" | "body">,
|
|
17
|
-
config?:
|
|
25
|
+
config?: HookConfig,
|
|
18
26
|
) {
|
|
19
|
-
const
|
|
27
|
+
const { enabled = true, ...swrConfig } = config ?? {};
|
|
28
|
+
const key = enabled ? ["cfg_totp_devices_retrieve", args ?? {}] as const : null;
|
|
20
29
|
return useSWR<Result>(
|
|
21
30
|
key,
|
|
22
31
|
async () => {
|
|
@@ -60,6 +69,6 @@ export function useCfgTotpDevicesRetrieve(
|
|
|
60
69
|
}
|
|
61
70
|
return parsed.data as unknown as Result;
|
|
62
71
|
},
|
|
63
|
-
|
|
72
|
+
swrConfig,
|
|
64
73
|
);
|
|
65
74
|
}
|
|
@@ -151,12 +151,13 @@ type MethodFn = <
|
|
|
151
151
|
|
|
152
152
|
type SseFn = <
|
|
153
153
|
TData = unknown,
|
|
154
|
-
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
155
|
+
_TError = unknown,
|
|
155
156
|
ThrowOnError extends boolean = false,
|
|
156
157
|
TResponseStyle extends ResponseStyle = 'fields',
|
|
157
158
|
>(
|
|
158
159
|
options: Omit<RequestOptions<never, TResponseStyle, ThrowOnError>, 'method'>,
|
|
159
|
-
) => Promise<ServerSentEventsResult<TData
|
|
160
|
+
) => Promise<ServerSentEventsResult<TData>>;
|
|
160
161
|
|
|
161
162
|
type RequestFn = <
|
|
162
163
|
TData = unknown,
|
|
@@ -118,14 +118,12 @@ const checkForExistence = (
|
|
|
118
118
|
return false;
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
export
|
|
122
|
-
security
|
|
123
|
-
...options
|
|
124
|
-
}: Pick<Required<RequestOptions>, 'security'> &
|
|
125
|
-
Pick<RequestOptions, 'auth' | 'query'> & {
|
|
121
|
+
export async function setAuthParams(
|
|
122
|
+
options: Pick<RequestOptions, 'auth' | 'query' | 'security'> & {
|
|
126
123
|
headers: Headers;
|
|
127
|
-
}
|
|
128
|
-
|
|
124
|
+
},
|
|
125
|
+
): Promise<void> {
|
|
126
|
+
for (const auth of options.security ?? []) {
|
|
129
127
|
if (checkForExistence(options, auth.name)) {
|
|
130
128
|
continue;
|
|
131
129
|
}
|
|
@@ -154,7 +152,7 @@ export const setAuthParams = async ({
|
|
|
154
152
|
break;
|
|
155
153
|
}
|
|
156
154
|
}
|
|
157
|
-
}
|
|
155
|
+
}
|
|
158
156
|
|
|
159
157
|
export const buildUrl: Client['buildUrl'] = (options) =>
|
|
160
158
|
getUrl({
|
|
@@ -104,10 +104,10 @@ const stripEmptySlots = (params: Params) => {
|
|
|
104
104
|
|
|
105
105
|
export const buildClientParams = (args: ReadonlyArray<unknown>, fields: FieldsConfig) => {
|
|
106
106
|
const params: Params = {
|
|
107
|
-
body:
|
|
108
|
-
headers:
|
|
109
|
-
path:
|
|
110
|
-
query:
|
|
107
|
+
body: Object.create(null),
|
|
108
|
+
headers: Object.create(null),
|
|
109
|
+
path: Object.create(null),
|
|
110
|
+
query: Object.create(null),
|
|
111
111
|
};
|
|
112
112
|
|
|
113
113
|
const map = buildKeyMap(fields);
|
|
@@ -79,12 +79,30 @@ function detectLocale(): string | null {
|
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
/** Default baseUrl
|
|
82
|
+
/** Default baseUrl.
|
|
83
83
|
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
84
|
+
* Browser: uses NEXT_PUBLIC_API_PROXY_URL if set (empty string = same-origin
|
|
85
|
+
* Next.js proxy), otherwise falls back to NEXT_PUBLIC_API_URL directly.
|
|
86
|
+
* Set NEXT_PUBLIC_API_PROXY_URL='' in apps that proxy /apix/* via Next.js
|
|
87
|
+
* Route Handler; leave it unset in apps that hit Django directly.
|
|
88
|
+
*
|
|
89
|
+
* Server (SSR / RSC / Edge): use NEXT_PUBLIC_API_URL so server-side fetch
|
|
90
|
+
* reaches Django directly (proxy is a same-origin HTTP round-trip on server).
|
|
86
91
|
*/
|
|
87
92
|
function defaultBaseUrl(): string {
|
|
93
|
+
if (typeof window !== 'undefined') {
|
|
94
|
+
try {
|
|
95
|
+
if (typeof process !== 'undefined' && process.env) {
|
|
96
|
+
if (process.env.NEXT_PUBLIC_STATIC_BUILD === 'true') return '';
|
|
97
|
+
// NEXT_PUBLIC_API_PROXY_URL='' means same-origin proxy (e.g. /apix/* route handler).
|
|
98
|
+
// Next.js inlines defined vars as their value; undefined means the var was not set.
|
|
99
|
+
if (process.env.NEXT_PUBLIC_API_PROXY_URL !== undefined)
|
|
100
|
+
return process.env.NEXT_PUBLIC_API_PROXY_URL;
|
|
101
|
+
return process.env.NEXT_PUBLIC_API_URL || '';
|
|
102
|
+
}
|
|
103
|
+
} catch {}
|
|
104
|
+
return '';
|
|
105
|
+
}
|
|
88
106
|
try {
|
|
89
107
|
if (typeof process !== 'undefined' && process.env) {
|
|
90
108
|
if (process.env.NEXT_PUBLIC_STATIC_BUILD === 'true') return '';
|
|
@@ -96,13 +114,11 @@ function defaultBaseUrl(): string {
|
|
|
96
114
|
|
|
97
115
|
/** Default API key fallback from `NEXT_PUBLIC_API_KEY`.
|
|
98
116
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* that bypass the rewrite (e.g. server components calling Django directly).
|
|
117
|
+
* Both browser and server: if NEXT_PUBLIC_API_KEY is set it is used as a
|
|
118
|
+
* global fallback (e.g. a public demo key). When not set, returns null so
|
|
119
|
+
* per-request keys passed via options.headers take effect unobstructed.
|
|
103
120
|
*/
|
|
104
121
|
function defaultApiKey(): string | null {
|
|
105
|
-
if (isBrowser) return null;
|
|
106
122
|
try {
|
|
107
123
|
if (typeof process !== 'undefined' && process.env?.NEXT_PUBLIC_API_KEY) {
|
|
108
124
|
return process.env.NEXT_PUBLIC_API_KEY;
|
|
@@ -286,6 +302,37 @@ async function tryRefresh(): Promise<string | null> {
|
|
|
286
302
|
* non-browser environments, so headers populated by the interceptor
|
|
287
303
|
* are simply absent server-side (which is the correct behaviour
|
|
288
304
|
* unless the caller explicitly sets a server-side token).
|
|
305
|
+
*
|
|
306
|
+
* ── How API key auth works ────────────────────────────────────────────────
|
|
307
|
+
*
|
|
308
|
+
* There are three ways to supply X-API-Key, in priority order:
|
|
309
|
+
*
|
|
310
|
+
* 1. Per-request header (highest priority, overrides everything):
|
|
311
|
+
* SomeApi.someEndpoint({ headers: { 'X-API-Key': userKey } })
|
|
312
|
+
* Use this in playgrounds or any context where the key comes from
|
|
313
|
+
* user input rather than app config.
|
|
314
|
+
*
|
|
315
|
+
* 2. Global override via auth.setApiKey(key) — applies to every request
|
|
316
|
+
* from this client until cleared. Good for single-user sessions.
|
|
317
|
+
*
|
|
318
|
+
* 3. NEXT_PUBLIC_API_KEY env var — read by defaultApiKey() on every request.
|
|
319
|
+
* Use as a public fallback key (e.g. a shared demo key for unauthenticated
|
|
320
|
+
* visitors). When not set, returns null so per-request keys work freely.
|
|
321
|
+
*
|
|
322
|
+
* ── baseUrl gotcha with proxy packages ───────────────────────────────────
|
|
323
|
+
*
|
|
324
|
+
* If another package (e.g. @carapis/catalog-api/client) calls
|
|
325
|
+
* `auth.setBaseUrl('')` on import, ALL requests from this shared client
|
|
326
|
+
* will go to relative paths (Next.js proxy) instead of hitting Django
|
|
327
|
+
* directly. This is intentional for the public catalog — but breaks any
|
|
328
|
+
* feature that needs to reach Django with a dynamic API key from the
|
|
329
|
+
* browser (e.g. a playground).
|
|
330
|
+
*
|
|
331
|
+
* Fix: pass `baseUrl` explicitly per-call to override the global setting:
|
|
332
|
+
* SomeApi.someEndpoint({
|
|
333
|
+
* baseUrl: process.env.NEXT_PUBLIC_API_URL,
|
|
334
|
+
* headers: { 'X-API-Key': userKey },
|
|
335
|
+
* })
|
|
289
336
|
*/
|
|
290
337
|
export function installAuthOnClient(client: HeyClient): void {
|
|
291
338
|
if (_client) return; // idempotent
|
|
@@ -303,8 +350,10 @@ export function installAuthOnClient(client: HeyClient): void {
|
|
|
303
350
|
const locale = auth.getLocale();
|
|
304
351
|
if (locale) request.headers.set('Accept-Language', locale);
|
|
305
352
|
|
|
353
|
+
// Only set X-API-Key from global store if NOT already present on the
|
|
354
|
+
// request — per-request headers (option 1 above) take precedence.
|
|
306
355
|
const apiKey = auth.getApiKey();
|
|
307
|
-
if (apiKey) request.headers.set('X-API-Key', apiKey);
|
|
356
|
+
if (apiKey && !request.headers.has('X-API-Key')) request.headers.set('X-API-Key', apiKey);
|
|
308
357
|
|
|
309
358
|
try {
|
|
310
359
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
@@ -37,5 +37,11 @@ export { CfgAccountsApiKey, CfgAccountsOauth, CfgAccounts, CfgAccountsProfile, C
|
|
|
37
37
|
export { CfgCentrifugo } from './_cfg_centrifugo';
|
|
38
38
|
export { CfgTotpBackupCodes, CfgTotp, CfgTotpSetup, CfgTotpVerify } from './_cfg_totp';
|
|
39
39
|
|
|
40
|
+
// Generated DTO / schema types (Hey API). The per-group barrels each
|
|
41
|
+
// re-export these too; surfacing them at the top level lets consumers
|
|
42
|
+
// `import { FleetSummary } from '<api>'` without reaching into a
|
|
43
|
+
// group subpath.
|
|
44
|
+
export type * from './types.gen';
|
|
45
|
+
|
|
40
46
|
// Shared utilities (errors, storage adapters, logger).
|
|
41
47
|
export * from './helpers';
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
"cookieAuth": []
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"apiKeyAuth": []
|
|
23
26
|
}
|
|
24
27
|
],
|
|
25
28
|
"responses": {
|
|
@@ -71,6 +74,9 @@
|
|
|
71
74
|
},
|
|
72
75
|
{
|
|
73
76
|
"cookieAuth": []
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
"apiKeyAuth": []
|
|
74
80
|
}
|
|
75
81
|
],
|
|
76
82
|
"responses": {
|
|
@@ -131,6 +137,9 @@
|
|
|
131
137
|
},
|
|
132
138
|
{
|
|
133
139
|
"cookieAuth": []
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"apiKeyAuth": []
|
|
134
143
|
}
|
|
135
144
|
],
|
|
136
145
|
"responses": {
|
|
@@ -26,11 +26,15 @@ export class CfgAccountsApiKey {
|
|
|
26
26
|
*/
|
|
27
27
|
public static cfgAccountsApiKeyRetrieve<ThrowOnError extends boolean = false>(options?: Options<CfgAccountsApiKeyRetrieveData, ThrowOnError>) {
|
|
28
28
|
return (options?.client ?? client).get<CfgAccountsApiKeyRetrieveResponses, CfgAccountsApiKeyRetrieveErrors, ThrowOnError>({
|
|
29
|
-
security: [
|
|
29
|
+
security: [
|
|
30
|
+
{ scheme: 'bearer', type: 'http' },
|
|
31
|
+
{
|
|
30
32
|
in: 'cookie',
|
|
31
33
|
name: 'sessionid',
|
|
32
34
|
type: 'apiKey'
|
|
33
|
-
}
|
|
35
|
+
},
|
|
36
|
+
{ name: 'X-API-Key', type: 'apiKey' }
|
|
37
|
+
],
|
|
34
38
|
url: '/cfg/accounts/api-key/',
|
|
35
39
|
...options
|
|
36
40
|
});
|
|
@@ -43,11 +47,15 @@ export class CfgAccountsApiKey {
|
|
|
43
47
|
*/
|
|
44
48
|
public static cfgAccountsApiKeyRegenerateCreate<ThrowOnError extends boolean = false>(options: Options<CfgAccountsApiKeyRegenerateCreateData, ThrowOnError>) {
|
|
45
49
|
return (options.client ?? client).post<CfgAccountsApiKeyRegenerateCreateResponses, CfgAccountsApiKeyRegenerateCreateErrors, ThrowOnError>({
|
|
46
|
-
security: [
|
|
50
|
+
security: [
|
|
51
|
+
{ scheme: 'bearer', type: 'http' },
|
|
52
|
+
{
|
|
47
53
|
in: 'cookie',
|
|
48
54
|
name: 'sessionid',
|
|
49
55
|
type: 'apiKey'
|
|
50
|
-
}
|
|
56
|
+
},
|
|
57
|
+
{ name: 'X-API-Key', type: 'apiKey' }
|
|
58
|
+
],
|
|
51
59
|
url: '/cfg/accounts/api-key/regenerate/',
|
|
52
60
|
...options,
|
|
53
61
|
headers: {
|
|
@@ -64,11 +72,15 @@ export class CfgAccountsApiKey {
|
|
|
64
72
|
*/
|
|
65
73
|
public static cfgAccountsApiKeyTestCreate<ThrowOnError extends boolean = false>(options: Options<CfgAccountsApiKeyTestCreateData, ThrowOnError>) {
|
|
66
74
|
return (options.client ?? client).post<CfgAccountsApiKeyTestCreateResponses, unknown, ThrowOnError>({
|
|
67
|
-
security: [
|
|
75
|
+
security: [
|
|
76
|
+
{ scheme: 'bearer', type: 'http' },
|
|
77
|
+
{
|
|
68
78
|
in: 'cookie',
|
|
69
79
|
name: 'sessionid',
|
|
70
80
|
type: 'apiKey'
|
|
71
|
-
}
|
|
81
|
+
},
|
|
82
|
+
{ name: 'X-API-Key', type: 'apiKey' }
|
|
83
|
+
],
|
|
72
84
|
url: '/cfg/accounts/api-key/test/',
|
|
73
85
|
...options,
|
|
74
86
|
headers: {
|
|
@@ -311,17 +311,18 @@ export const useAuthForm = (options: UseAuthFormOptions): AuthFormReturn => {
|
|
|
311
311
|
|
|
312
312
|
useAutoAuth({
|
|
313
313
|
allowedPaths: [authPath],
|
|
314
|
-
onOTPDetected: (detectedOtp: string) => {
|
|
314
|
+
onOTPDetected: (detectedOtp: string, detectedEmail?: string) => {
|
|
315
315
|
if (isAutoSubmitFromUrlRef.current || isLoading) return;
|
|
316
316
|
isAutoSubmitFromUrlRef.current = true;
|
|
317
317
|
|
|
318
318
|
authLogger.info('OTP detected from URL, auto-submitting');
|
|
319
319
|
|
|
320
|
-
|
|
321
|
-
|
|
320
|
+
// Prefer the email from the magic-link URL (works on a fresh browser/device),
|
|
321
|
+
// fall back to the one saved from a previous request on this browser.
|
|
322
|
+
const autoIdentifier = detectedEmail || getSavedEmail() || '';
|
|
322
323
|
|
|
323
324
|
if (!autoIdentifier) {
|
|
324
|
-
authLogger.warn('No
|
|
325
|
+
authLogger.warn('No identifier found for auto-submit (no email in URL or storage)');
|
|
325
326
|
isAutoSubmitFromUrlRef.current = false;
|
|
326
327
|
return;
|
|
327
328
|
}
|
|
@@ -9,12 +9,25 @@ import { useCfgRouter } from './useCfgRouter';
|
|
|
9
9
|
import { useQueryParams } from './useQueryParams';
|
|
10
10
|
|
|
11
11
|
export interface UseAutoAuthOptions {
|
|
12
|
-
|
|
12
|
+
/** Called with the OTP from the URL, plus the optional `email` query param if present. */
|
|
13
|
+
onOTPDetected?: (otp: string, email?: string) => void;
|
|
13
14
|
cleanupUrl?: boolean;
|
|
14
15
|
/** Paths where auto-auth should be active. Default: ['/auth'] */
|
|
15
16
|
allowedPaths?: string[];
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
/**
|
|
20
|
+
* Strip a leading 2-letter locale segment and any trailing slash so paths
|
|
21
|
+
* compare consistently regardless of the next-intl locale prefix.
|
|
22
|
+
* e.g. '/ru/auth/' -> '/auth', '/auth' -> '/auth', '/' -> '/'
|
|
23
|
+
*/
|
|
24
|
+
const normalizePath = (path: string | null): string => {
|
|
25
|
+
if (!path) return '';
|
|
26
|
+
const withoutLocale = path.replace(/^\/[a-z]{2}(?=\/|$)/, '');
|
|
27
|
+
const trimmed = withoutLocale.replace(/\/+$/, '');
|
|
28
|
+
return trimmed || '/';
|
|
29
|
+
};
|
|
30
|
+
|
|
18
31
|
/**
|
|
19
32
|
* Hook for automatic authentication from URL query parameters
|
|
20
33
|
* Detects OTP from URL and triggers callback
|
|
@@ -25,8 +38,12 @@ export const useAutoAuth = (options: UseAutoAuthOptions = {}) => {
|
|
|
25
38
|
const pathname = usePathname();
|
|
26
39
|
const router = useCfgRouter();
|
|
27
40
|
|
|
28
|
-
// Check if current path is in allowed paths
|
|
29
|
-
|
|
41
|
+
// Check if current path is in allowed paths, ignoring the locale prefix
|
|
42
|
+
// (next-intl may keep it in the URL, e.g. /ru/auth) and trailing slashes.
|
|
43
|
+
const normalizedPath = normalizePath(pathname);
|
|
44
|
+
const isAllowedPath = allowedPaths.some(
|
|
45
|
+
path => normalizedPath === path || normalizedPath.startsWith(path + '/')
|
|
46
|
+
);
|
|
30
47
|
|
|
31
48
|
const hasOTP = !!(queryParams.get('otp'));
|
|
32
49
|
const isReady = !!pathname && hasOTP && isAllowedPath;
|
|
@@ -35,12 +52,13 @@ export const useAutoAuth = (options: UseAutoAuthOptions = {}) => {
|
|
|
35
52
|
if (!isReady) return;
|
|
36
53
|
|
|
37
54
|
const queryOtp = queryParams.get('otp') as string;
|
|
55
|
+
const queryEmail = queryParams.get('email') || undefined;
|
|
38
56
|
|
|
39
57
|
// Handle OTP detection - only on allowed paths to avoid conflicts with other pages
|
|
40
58
|
// (e.g., /dashboard/device uses ?otp= for device authorization, not user auth)
|
|
41
59
|
if (queryOtp && typeof queryOtp === 'string' && queryOtp.length === AUTH_CONSTANTS.EMAIL_OTP_LENGTH) {
|
|
42
60
|
authLogger.info('OTP detected in URL on auth page:', queryOtp);
|
|
43
|
-
onOTPDetected?.(queryOtp);
|
|
61
|
+
onOTPDetected?.(queryOtp, queryEmail);
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
// Clean up URL to remove sensitive params for security
|
|
@@ -51,6 +69,7 @@ export const useAutoAuth = (options: UseAutoAuthOptions = {}) => {
|
|
|
51
69
|
if (cleanupUrl && queryOtp) {
|
|
52
70
|
const cleanQuery = Object.fromEntries(queryParams.entries());
|
|
53
71
|
delete cleanQuery.otp;
|
|
72
|
+
delete cleanQuery.email;
|
|
54
73
|
const queryString = new URLSearchParams(cleanQuery).toString();
|
|
55
74
|
const realPathname = typeof window !== 'undefined' ? window.location.pathname : pathname;
|
|
56
75
|
router.replace(queryString ? `${realPathname}?${queryString}` : realPathname);
|