@strapi/admin 5.33.1 → 5.33.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.
@@ -1,11 +1,111 @@
1
1
  import pipe from 'lodash/fp/pipe';
2
2
  import qs__default from 'qs';
3
- import { getCookieValue } from './cookies.mjs';
3
+ import { setCookie, getCookieValue } from './cookies.mjs';
4
4
 
5
5
  const STORAGE_KEYS = {
6
6
  TOKEN: 'jwtToken',
7
7
  USER: 'userInfo'
8
8
  };
9
+ /**
10
+ * Module-level promise to ensure only one token refresh happens at a time
11
+ */ let refreshPromise = null;
12
+ /**
13
+ * Callback to notify the app when the token is updated (e.g., to update Redux state)
14
+ */ let onTokenUpdate = null;
15
+ /**
16
+ * Set the callback that will be called when the token is refreshed.
17
+ * This allows the React layer to update Redux state when a token refresh occurs.
18
+ *
19
+ * @param callback - Function to call with the new token, or null to clear
20
+ * @example
21
+ * // In a React component
22
+ * useEffect(() => {
23
+ * setOnTokenUpdate((token) => dispatch(setToken(token)));
24
+ * return () => setOnTokenUpdate(null);
25
+ * }, [dispatch]);
26
+ */ const setOnTokenUpdate = (callback)=>{
27
+ onTokenUpdate = callback;
28
+ };
29
+ /**
30
+ * Check if the URL is an auth path that should not trigger token refresh.
31
+ * Note: No ^ anchor since the URL may include the baseURL prefix (e.g., "http://localhost:1337/admin/login").
32
+ * This differs from baseQuery.ts which uses ^/admin since it receives normalized paths.
33
+ */ const isAuthPath = (url)=>/\/admin\/(login|logout|access-token)\b/.test(url);
34
+ /**
35
+ * Store the new token in the appropriate storage (localStorage or cookie)
36
+ * and notify the app to update its state.
37
+ *
38
+ * Uses localStorage if the user selected "remember me" during login,
39
+ * otherwise uses cookies for session-based storage.
40
+ *
41
+ * @param token - The JWT token to store
42
+ * @internal Exported for testing purposes
43
+ */ const storeToken = (token)=>{
44
+ // Check if the original token was stored in localStorage (persist mode)
45
+ const wasPersistedToLocalStorage = Boolean(localStorage.getItem(STORAGE_KEYS.TOKEN));
46
+ if (wasPersistedToLocalStorage) {
47
+ localStorage.setItem(STORAGE_KEYS.TOKEN, JSON.stringify(token));
48
+ } else {
49
+ setCookie(STORAGE_KEYS.TOKEN, token);
50
+ }
51
+ // Notify the app to update its state (e.g., Redux)
52
+ if (onTokenUpdate) {
53
+ onTokenUpdate(token);
54
+ }
55
+ };
56
+ /**
57
+ * Refresh the access token by calling the /admin/access-token endpoint.
58
+ * This uses a low-level fetch to avoid recursion through the interceptor.
59
+ * Returns the new token on success, or null on failure.
60
+ */ const refreshAccessToken = async ()=>{
61
+ const backendURL = window.strapi.backendURL;
62
+ try {
63
+ const response = await fetch(`${backendURL}/admin/access-token`, {
64
+ method: 'POST',
65
+ credentials: 'include',
66
+ headers: {
67
+ Accept: 'application/json',
68
+ 'Content-Type': 'application/json'
69
+ }
70
+ });
71
+ if (!response.ok) {
72
+ console.warn('[Auth] Token refresh failed with status:', response.status);
73
+ return null;
74
+ }
75
+ const result = await response.json();
76
+ const token = result?.data?.token;
77
+ if (!token) {
78
+ console.warn('[Auth] Token refresh response missing token');
79
+ return null;
80
+ }
81
+ storeToken(token);
82
+ return token;
83
+ } catch (error) {
84
+ console.error('[Auth] Token refresh error:', error);
85
+ return null;
86
+ }
87
+ };
88
+ /**
89
+ * Attempt to refresh the token if not already refreshing.
90
+ * Uses a module-level promise to prevent concurrent refresh requests.
91
+ *
92
+ * @returns The new authentication token
93
+ * @throws {Error} If the token refresh fails (e.g., refresh token expired)
94
+ * @internal Exported for testing purposes
95
+ */ const attemptTokenRefresh = async ()=>{
96
+ if (!refreshPromise) {
97
+ refreshPromise = refreshAccessToken().finally(()=>{
98
+ refreshPromise = null;
99
+ });
100
+ }
101
+ const newToken = await refreshPromise;
102
+ if (!newToken) {
103
+ const error = new Error('Session expired. Please log in again.');
104
+ error.name = 'TokenRefreshError';
105
+ throw error;
106
+ }
107
+ return newToken;
108
+ };
9
109
  class FetchError extends Error {
10
110
  constructor(message, response){
11
111
  super(message);
@@ -52,11 +152,14 @@ const getToken = ()=>{
52
152
  * ```
53
153
  */ const getFetchClient = (defaultOptions = {})=>{
54
154
  const backendURL = window.strapi.backendURL;
55
- const defaultHeader = {
56
- Accept: 'application/json',
57
- 'Content-Type': 'application/json',
58
- Authorization: `Bearer ${getToken()}`
59
- };
155
+ /**
156
+ * Create default headers with the current token.
157
+ * This is a function so we can get a fresh token after refresh.
158
+ */ const getDefaultHeaders = ()=>({
159
+ Accept: 'application/json',
160
+ 'Content-Type': 'application/json',
161
+ Authorization: `Bearer ${getToken()}`
162
+ });
60
163
  const isFormDataRequest = (body)=>body instanceof FormData;
61
164
  const addPrependingSlash = (url)=>url.charAt(0) !== '/' ? `/${url}` : url;
62
165
  // This regular expression matches a string that starts with either "http://" or "https://" or any other protocol name in lower case letters, followed by "://" and ends with anything else
@@ -72,12 +175,16 @@ const getToken = ()=>{
72
175
  * In native Fetch API, a response is considered "not ok"
73
176
  * when the status code falls in the 200 to 299 (inclusive) range
74
177
  */ if (!response.ok && result.error && !validateStatus?.(response.status)) {
75
- throw new FetchError(result.error.message, {
178
+ const fetchError = new FetchError(result.error.message, {
76
179
  data: result
77
180
  });
181
+ fetchError.status = response.status;
182
+ throw fetchError;
78
183
  }
79
184
  if (!response.ok && !validateStatus?.(response.status)) {
80
- throw new FetchError('Unknown Server Error');
185
+ const fetchError = new FetchError('Unknown Server Error');
186
+ fetchError.status = response.status;
187
+ throw fetchError;
81
188
  }
82
189
  return {
83
190
  data: result
@@ -94,6 +201,28 @@ const getToken = ()=>{
94
201
  }
95
202
  }
96
203
  };
204
+ /**
205
+ * Execute a fetch request with automatic token refresh on 401 errors.
206
+ * @param url - The request URL (used to check if it's an auth path)
207
+ * @param executeRequest - Function that performs the fetch (called again on retry with fresh headers)
208
+ */ const withTokenRefresh = async (url, executeRequest)=>{
209
+ try {
210
+ return await executeRequest();
211
+ } catch (error) {
212
+ // Only attempt refresh for 401 errors on non-auth paths
213
+ if (isFetchError(error) && error.status === 401 && !isAuthPath(url)) {
214
+ try {
215
+ await attemptTokenRefresh();
216
+ // Retry - executeRequest will call getDefaultHeaders() again, picking up the new token
217
+ return await executeRequest();
218
+ } catch {
219
+ // If refresh fails, throw the original error
220
+ throw error;
221
+ }
222
+ }
223
+ throw error;
224
+ }
225
+ };
97
226
  const paramsSerializer = (params)=>(url)=>{
98
227
  if (params) {
99
228
  if (typeof params === 'string') {
@@ -119,79 +248,86 @@ const getToken = ()=>{
119
248
  */ const makeCreateRequestUrl = (options)=>pipe(normalizeUrl, addBaseUrl, paramsSerializer(options?.params));
120
249
  const fetchClient = {
121
250
  get: async (url, options)=>{
122
- const headers = new Headers({
123
- ...defaultHeader,
124
- ...options?.headers
125
- });
126
- /**
127
- * this applies all our transformations to the URL
128
- * - normalizing (making sure it has the correct slash)
129
- * - appending our BaseURL which comes from the window.strapi object
130
- * - serializing our params with QS
131
- */ const createRequestUrl = makeCreateRequestUrl(options);
132
- const response = await fetch(createRequestUrl(url), {
133
- signal: options?.signal ?? defaultOptions.signal,
134
- method: 'GET',
135
- headers
136
- });
137
- return responseInterceptor(response, options?.validateStatus);
251
+ const createRequestUrl = makeCreateRequestUrl(options);
252
+ const executeRequest = async ()=>{
253
+ const headers = new Headers({
254
+ ...getDefaultHeaders(),
255
+ ...options?.headers
256
+ });
257
+ const response = await fetch(createRequestUrl(url), {
258
+ signal: options?.signal ?? defaultOptions.signal,
259
+ method: 'GET',
260
+ headers
261
+ });
262
+ return responseInterceptor(response, options?.validateStatus);
263
+ };
264
+ return withTokenRefresh(url, executeRequest);
138
265
  },
139
266
  post: async (url, data, options)=>{
140
- const headers = new Headers({
141
- ...defaultHeader,
142
- ...options?.headers
143
- });
144
267
  const createRequestUrl = makeCreateRequestUrl(options);
145
- /**
146
- * we have to remove the Content-Type value if it was a formData request
147
- * the browser will automatically set the header value
148
- */ if (isFormDataRequest(data)) {
149
- headers.delete('Content-Type');
150
- }
151
- const response = await fetch(createRequestUrl(url), {
152
- signal: options?.signal ?? defaultOptions.signal,
153
- method: 'POST',
154
- headers,
155
- body: isFormDataRequest(data) ? data : JSON.stringify(data)
156
- });
157
- return responseInterceptor(response, options?.validateStatus);
268
+ const executeRequest = async ()=>{
269
+ const headers = new Headers({
270
+ ...getDefaultHeaders(),
271
+ ...options?.headers
272
+ });
273
+ /**
274
+ * we have to remove the Content-Type value if it was a formData request
275
+ * the browser will automatically set the header value
276
+ */ if (isFormDataRequest(data)) {
277
+ headers.delete('Content-Type');
278
+ }
279
+ const response = await fetch(createRequestUrl(url), {
280
+ signal: options?.signal ?? defaultOptions.signal,
281
+ method: 'POST',
282
+ headers,
283
+ body: isFormDataRequest(data) ? data : JSON.stringify(data)
284
+ });
285
+ return responseInterceptor(response, options?.validateStatus);
286
+ };
287
+ return withTokenRefresh(url, executeRequest);
158
288
  },
159
289
  put: async (url, data, options)=>{
160
- const headers = new Headers({
161
- ...defaultHeader,
162
- ...options?.headers
163
- });
164
290
  const createRequestUrl = makeCreateRequestUrl(options);
165
- /**
166
- * we have to remove the Content-Type value if it was a formData request
167
- * the browser will automatically set the header value
168
- */ if (isFormDataRequest(data)) {
169
- headers.delete('Content-Type');
170
- }
171
- const response = await fetch(createRequestUrl(url), {
172
- signal: options?.signal ?? defaultOptions.signal,
173
- method: 'PUT',
174
- headers,
175
- body: isFormDataRequest(data) ? data : JSON.stringify(data)
176
- });
177
- return responseInterceptor(response, options?.validateStatus);
291
+ const executeRequest = async ()=>{
292
+ const headers = new Headers({
293
+ ...getDefaultHeaders(),
294
+ ...options?.headers
295
+ });
296
+ /**
297
+ * we have to remove the Content-Type value if it was a formData request
298
+ * the browser will automatically set the header value
299
+ */ if (isFormDataRequest(data)) {
300
+ headers.delete('Content-Type');
301
+ }
302
+ const response = await fetch(createRequestUrl(url), {
303
+ signal: options?.signal ?? defaultOptions.signal,
304
+ method: 'PUT',
305
+ headers,
306
+ body: isFormDataRequest(data) ? data : JSON.stringify(data)
307
+ });
308
+ return responseInterceptor(response, options?.validateStatus);
309
+ };
310
+ return withTokenRefresh(url, executeRequest);
178
311
  },
179
312
  del: async (url, options)=>{
180
- const headers = new Headers({
181
- ...defaultHeader,
182
- ...options?.headers
183
- });
184
313
  const createRequestUrl = makeCreateRequestUrl(options);
185
- const response = await fetch(createRequestUrl(url), {
186
- signal: options?.signal ?? defaultOptions.signal,
187
- method: 'DELETE',
188
- headers
189
- });
190
- return responseInterceptor(response, options?.validateStatus);
314
+ const executeRequest = async ()=>{
315
+ const headers = new Headers({
316
+ ...getDefaultHeaders(),
317
+ ...options?.headers
318
+ });
319
+ const response = await fetch(createRequestUrl(url), {
320
+ signal: options?.signal ?? defaultOptions.signal,
321
+ method: 'DELETE',
322
+ headers
323
+ });
324
+ return responseInterceptor(response, options?.validateStatus);
325
+ };
326
+ return withTokenRefresh(url, executeRequest);
191
327
  }
192
328
  };
193
329
  return fetchClient;
194
330
  };
195
331
 
196
- export { FetchError, getFetchClient, isFetchError };
332
+ export { FetchError, attemptTokenRefresh, getFetchClient, isFetchError, setOnTokenUpdate, storeToken };
197
333
  //# sourceMappingURL=getFetchClient.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"getFetchClient.mjs","sources":["../../../../../admin/src/utils/getFetchClient.ts"],"sourcesContent":["import pipe from 'lodash/fp/pipe';\nimport qs from 'qs';\n\nimport { getCookieValue } from './cookies';\n\nimport type { errors } from '@strapi/utils';\n\nexport type ApiError =\n | errors.ApplicationError\n | errors.ForbiddenError\n | errors.NotFoundError\n | errors.NotImplementedError\n | errors.PaginationError\n | errors.PayloadTooLargeError\n | errors.PolicyError\n | errors.RateLimitError\n | errors.UnauthorizedError\n | errors.ValidationError\n | errors.YupValidationError;\n\nconst STORAGE_KEYS = {\n TOKEN: 'jwtToken',\n USER: 'userInfo',\n};\n\ntype FetchResponse<TData = any> = {\n data: TData;\n status?: number;\n};\n\ntype FetchOptions = {\n params?: any;\n signal?: AbortSignal;\n headers?: Record<string, string>;\n validateStatus?: ((status: number) => boolean) | null;\n};\n\ntype FetchConfig = {\n signal?: AbortSignal;\n};\n\ninterface ErrorResponse {\n data: {\n data?: any;\n error: ApiError & { status?: number };\n };\n}\n\nclass FetchError extends Error {\n public name: string;\n public message: string;\n public response?: ErrorResponse;\n public code?: number;\n public status?: number;\n\n constructor(message: string, response?: ErrorResponse) {\n super(message);\n this.name = 'FetchError';\n this.message = message;\n this.response = response;\n this.code = response?.data?.error?.status;\n this.status = response?.data?.error?.status;\n\n // Ensure correct stack trace in error object\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, FetchError);\n }\n }\n}\n\nconst isFetchError = (error: unknown): error is FetchError => {\n return error instanceof FetchError;\n};\n\nconst getToken = (): string | null => {\n const fromLocalStorage = localStorage.getItem(STORAGE_KEYS.TOKEN);\n if (fromLocalStorage) {\n return JSON.parse(fromLocalStorage);\n }\n\n const fromCookie = getCookieValue(STORAGE_KEYS.TOKEN);\n return fromCookie ?? null;\n};\n\ntype FetchClient = {\n get: <TData = any>(url: string, config?: FetchOptions) => Promise<FetchResponse<TData>>;\n put: <TData = any, TSend = any>(\n url: string,\n data?: TSend,\n config?: FetchOptions\n ) => Promise<FetchResponse<TData>>;\n post: <TData = any, TSend = any>(\n url: string,\n data?: TSend,\n config?: FetchOptions\n ) => Promise<FetchResponse<TData>>;\n del: <TData = any>(url: string, config?: FetchOptions) => Promise<FetchResponse<TData>>;\n};\n\n/**\n * @public\n * @param {FetchConfig} [defaultOptions={}] - Fetch Configs.\n * @returns {FetchClient} A fetch client object with methods for making HTTP requests.\n * @description This is an abstraction around the native fetch exposed by a function. It provides a simple interface to handle API calls\n * to the Strapi backend.\n * @example\n * ```tsx\n * import { getFetchClient } from '@strapi/admin/admin';\n *\n * const myFunct = () => {\n * const { get } = getFetchClient();\n * const requestURL = \"/some-endpoint\";\n *\n * const { data } = await get(requestURL);\n *\n * return data;\n * };\n * ```\n */\nconst getFetchClient = (defaultOptions: FetchConfig = {}): FetchClient => {\n const backendURL = window.strapi.backendURL;\n const defaultHeader = {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${getToken()}`,\n };\n\n const isFormDataRequest = (body: unknown) => body instanceof FormData;\n const addPrependingSlash = (url: string) => (url.charAt(0) !== '/' ? `/${url}` : url);\n\n // This regular expression matches a string that starts with either \"http://\" or \"https://\" or any other protocol name in lower case letters, followed by \"://\" and ends with anything else\n const hasProtocol = (url: string) => new RegExp('^(?:[a-z+]+:)?//', 'i').test(url);\n\n // Check if the url has a prepending slash, if not add a slash\n const normalizeUrl = (url: string) => (hasProtocol(url) ? url : addPrependingSlash(url));\n\n // Add a response interceptor to return the response\n const responseInterceptor = async <TData = any>(\n response: Response,\n validateStatus?: FetchOptions['validateStatus']\n ): Promise<FetchResponse<TData>> => {\n try {\n const result = await response.json();\n\n /**\n * validateStatus allows us to customize when a response should throw an error\n * In native Fetch API, a response is considered \"not ok\"\n * when the status code falls in the 200 to 299 (inclusive) range\n */\n if (!response.ok && result.error && !validateStatus?.(response.status)) {\n throw new FetchError(result.error.message, { data: result });\n }\n\n if (!response.ok && !validateStatus?.(response.status)) {\n throw new FetchError('Unknown Server Error');\n }\n\n return { data: result };\n } catch (error) {\n if (error instanceof SyntaxError && response.ok) {\n // Making sure that a SyntaxError doesn't throw if it's successful\n return { data: [], status: response.status } as FetchResponse<any>;\n } else {\n throw error;\n }\n }\n };\n\n const paramsSerializer =\n <Param = unknown>(params?: Param) =>\n (url: string) => {\n if (params) {\n if (typeof params === 'string') {\n return `${url}?${params}`;\n }\n\n /**\n * TODO V6: Encoding should be enabled in this step\n * So the rest of the app doesn't have to worry about it,\n * It's considered a breaking change because it impacts any API request, including the user's custom code\n */\n const serializedParams = qs.stringify(params, { encode: false });\n return `${url}?${serializedParams}`;\n }\n return url;\n };\n\n const addBaseUrl = (url: Parameters<typeof fetch>[0]) => {\n return `${backendURL}${url}`;\n };\n\n /**\n * We use the factory method because the options\n * are unique to the individual request\n */\n const makeCreateRequestUrl = (options?: FetchOptions) =>\n pipe(normalizeUrl, addBaseUrl, paramsSerializer(options?.params));\n\n const fetchClient: FetchClient = {\n get: async <TData>(url: string, options?: FetchOptions): Promise<FetchResponse<TData>> => {\n const headers = new Headers({\n ...defaultHeader,\n ...options?.headers,\n });\n /**\n * this applies all our transformations to the URL\n * - normalizing (making sure it has the correct slash)\n * - appending our BaseURL which comes from the window.strapi object\n * - serializing our params with QS\n */\n const createRequestUrl = makeCreateRequestUrl(options);\n const response = await fetch(createRequestUrl(url), {\n signal: options?.signal ?? defaultOptions.signal,\n method: 'GET',\n headers,\n });\n\n return responseInterceptor<TData>(response, options?.validateStatus);\n },\n post: async <TData, TSend = any>(\n url: string,\n data?: TSend,\n options?: FetchOptions\n ): Promise<FetchResponse<TData>> => {\n const headers = new Headers({\n ...defaultHeader,\n ...options?.headers,\n });\n\n const createRequestUrl = makeCreateRequestUrl(options);\n\n /**\n * we have to remove the Content-Type value if it was a formData request\n * the browser will automatically set the header value\n */\n if (isFormDataRequest(data)) {\n headers.delete('Content-Type');\n }\n\n const response = await fetch(createRequestUrl(url), {\n signal: options?.signal ?? defaultOptions.signal,\n method: 'POST',\n headers,\n body: isFormDataRequest(data) ? (data as FormData) : JSON.stringify(data),\n });\n return responseInterceptor<TData>(response, options?.validateStatus);\n },\n put: async <TData, TSend = any>(\n url: string,\n data?: TSend,\n options?: FetchOptions\n ): Promise<FetchResponse<TData>> => {\n const headers = new Headers({\n ...defaultHeader,\n ...options?.headers,\n });\n\n const createRequestUrl = makeCreateRequestUrl(options);\n\n /**\n * we have to remove the Content-Type value if it was a formData request\n * the browser will automatically set the header value\n */\n if (isFormDataRequest(data)) {\n headers.delete('Content-Type');\n }\n\n const response = await fetch(createRequestUrl(url), {\n signal: options?.signal ?? defaultOptions.signal,\n method: 'PUT',\n headers,\n body: isFormDataRequest(data) ? (data as FormData) : JSON.stringify(data),\n });\n\n return responseInterceptor<TData>(response, options?.validateStatus);\n },\n del: async <TData>(url: string, options?: FetchOptions): Promise<FetchResponse<TData>> => {\n const headers = new Headers({\n ...defaultHeader,\n ...options?.headers,\n });\n\n const createRequestUrl = makeCreateRequestUrl(options);\n const response = await fetch(createRequestUrl(url), {\n signal: options?.signal ?? defaultOptions.signal,\n method: 'DELETE',\n headers,\n });\n return responseInterceptor<TData>(response, options?.validateStatus);\n },\n };\n\n return fetchClient;\n};\n\nexport { getFetchClient, isFetchError, FetchError };\nexport type { FetchOptions, FetchResponse, FetchConfig, FetchClient, ErrorResponse };\n"],"names":["STORAGE_KEYS","TOKEN","USER","FetchError","Error","message","response","name","code","data","error","status","captureStackTrace","isFetchError","getToken","fromLocalStorage","localStorage","getItem","JSON","parse","fromCookie","getCookieValue","getFetchClient","defaultOptions","backendURL","window","strapi","defaultHeader","Accept","Authorization","isFormDataRequest","body","FormData","addPrependingSlash","url","charAt","hasProtocol","RegExp","test","normalizeUrl","responseInterceptor","validateStatus","result","json","ok","SyntaxError","paramsSerializer","params","serializedParams","qs","stringify","encode","addBaseUrl","makeCreateRequestUrl","options","pipe","fetchClient","get","headers","Headers","createRequestUrl","fetch","signal","method","post","delete","put","del"],"mappings":"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"}
1
+ {"version":3,"file":"getFetchClient.mjs","sources":["../../../../../admin/src/utils/getFetchClient.ts"],"sourcesContent":["import pipe from 'lodash/fp/pipe';\n// eslint-disable-next-line import/default\nimport qs from 'qs';\n\nimport { getCookieValue, setCookie } from './cookies';\n\nimport type { errors } from '@strapi/utils';\n\nexport type ApiError =\n | errors.ApplicationError\n | errors.ForbiddenError\n | errors.NotFoundError\n | errors.NotImplementedError\n | errors.PaginationError\n | errors.PayloadTooLargeError\n | errors.PolicyError\n | errors.RateLimitError\n | errors.UnauthorizedError\n | errors.ValidationError\n | errors.YupValidationError;\n\nconst STORAGE_KEYS = {\n TOKEN: 'jwtToken',\n USER: 'userInfo',\n};\n\n/**\n * Module-level promise to ensure only one token refresh happens at a time\n */\nlet refreshPromise: Promise<string | null> | null = null;\n\n/**\n * Callback to notify the app when the token is updated (e.g., to update Redux state)\n */\nlet onTokenUpdate: ((token: string) => void) | null = null;\n\n/**\n * Set the callback that will be called when the token is refreshed.\n * This allows the React layer to update Redux state when a token refresh occurs.\n *\n * @param callback - Function to call with the new token, or null to clear\n * @example\n * // In a React component\n * useEffect(() => {\n * setOnTokenUpdate((token) => dispatch(setToken(token)));\n * return () => setOnTokenUpdate(null);\n * }, [dispatch]);\n */\nconst setOnTokenUpdate = (callback: ((token: string) => void) | null): void => {\n onTokenUpdate = callback;\n};\n\n/**\n * Check if the URL is an auth path that should not trigger token refresh.\n * Note: No ^ anchor since the URL may include the baseURL prefix (e.g., \"http://localhost:1337/admin/login\").\n * This differs from baseQuery.ts which uses ^/admin since it receives normalized paths.\n */\nconst isAuthPath = (url: string) => /\\/admin\\/(login|logout|access-token)\\b/.test(url);\n\n/**\n * Store the new token in the appropriate storage (localStorage or cookie)\n * and notify the app to update its state.\n *\n * Uses localStorage if the user selected \"remember me\" during login,\n * otherwise uses cookies for session-based storage.\n *\n * @param token - The JWT token to store\n * @internal Exported for testing purposes\n */\nconst storeToken = (token: string): void => {\n // Check if the original token was stored in localStorage (persist mode)\n const wasPersistedToLocalStorage = Boolean(localStorage.getItem(STORAGE_KEYS.TOKEN));\n\n if (wasPersistedToLocalStorage) {\n localStorage.setItem(STORAGE_KEYS.TOKEN, JSON.stringify(token));\n } else {\n setCookie(STORAGE_KEYS.TOKEN, token);\n }\n\n // Notify the app to update its state (e.g., Redux)\n if (onTokenUpdate) {\n onTokenUpdate(token);\n }\n};\n\n/**\n * Refresh the access token by calling the /admin/access-token endpoint.\n * This uses a low-level fetch to avoid recursion through the interceptor.\n * Returns the new token on success, or null on failure.\n */\nconst refreshAccessToken = async (): Promise<string | null> => {\n const backendURL = window.strapi.backendURL;\n\n try {\n const response = await fetch(`${backendURL}/admin/access-token`, {\n method: 'POST',\n credentials: 'include', // Include cookies for the refresh token\n headers: {\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n },\n });\n\n if (!response.ok) {\n console.warn('[Auth] Token refresh failed with status:', response.status);\n return null;\n }\n\n const result = await response.json();\n const token = result?.data?.token as string | undefined;\n\n if (!token) {\n console.warn('[Auth] Token refresh response missing token');\n return null;\n }\n\n storeToken(token);\n return token;\n } catch (error) {\n console.error('[Auth] Token refresh error:', error);\n return null;\n }\n};\n\n/**\n * Attempt to refresh the token if not already refreshing.\n * Uses a module-level promise to prevent concurrent refresh requests.\n *\n * @returns The new authentication token\n * @throws {Error} If the token refresh fails (e.g., refresh token expired)\n * @internal Exported for testing purposes\n */\nconst attemptTokenRefresh = async (): Promise<string> => {\n if (!refreshPromise) {\n refreshPromise = refreshAccessToken().finally(() => {\n refreshPromise = null;\n });\n }\n\n const newToken = await refreshPromise;\n if (!newToken) {\n const error = new Error('Session expired. Please log in again.');\n error.name = 'TokenRefreshError';\n throw error;\n }\n\n return newToken;\n};\n\ntype FetchResponse<TData = any> = {\n data: TData;\n status?: number;\n};\n\ntype FetchOptions = {\n params?: any;\n signal?: AbortSignal;\n headers?: Record<string, string>;\n validateStatus?: ((status: number) => boolean) | null;\n};\n\ntype FetchConfig = {\n signal?: AbortSignal;\n};\n\ninterface ErrorResponse {\n data: {\n data?: any;\n error: ApiError & { status?: number };\n };\n}\n\nclass FetchError extends Error {\n public name: string;\n public message: string;\n public response?: ErrorResponse;\n public code?: number;\n public status?: number;\n\n constructor(message: string, response?: ErrorResponse) {\n super(message);\n this.name = 'FetchError';\n this.message = message;\n this.response = response;\n this.code = response?.data?.error?.status;\n this.status = response?.data?.error?.status;\n\n // Ensure correct stack trace in error object\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, FetchError);\n }\n }\n}\n\nconst isFetchError = (error: unknown): error is FetchError => {\n return error instanceof FetchError;\n};\n\nconst getToken = (): string | null => {\n const fromLocalStorage = localStorage.getItem(STORAGE_KEYS.TOKEN);\n if (fromLocalStorage) {\n return JSON.parse(fromLocalStorage);\n }\n\n const fromCookie = getCookieValue(STORAGE_KEYS.TOKEN);\n return fromCookie ?? null;\n};\n\ntype FetchClient = {\n get: <TData = any>(url: string, config?: FetchOptions) => Promise<FetchResponse<TData>>;\n put: <TData = any, TSend = any>(\n url: string,\n data?: TSend,\n config?: FetchOptions\n ) => Promise<FetchResponse<TData>>;\n post: <TData = any, TSend = any>(\n url: string,\n data?: TSend,\n config?: FetchOptions\n ) => Promise<FetchResponse<TData>>;\n del: <TData = any>(url: string, config?: FetchOptions) => Promise<FetchResponse<TData>>;\n};\n\n/**\n * @public\n * @param {FetchConfig} [defaultOptions={}] - Fetch Configs.\n * @returns {FetchClient} A fetch client object with methods for making HTTP requests.\n * @description This is an abstraction around the native fetch exposed by a function. It provides a simple interface to handle API calls\n * to the Strapi backend.\n * @example\n * ```tsx\n * import { getFetchClient } from '@strapi/admin/admin';\n *\n * const myFunct = () => {\n * const { get } = getFetchClient();\n * const requestURL = \"/some-endpoint\";\n *\n * const { data } = await get(requestURL);\n *\n * return data;\n * };\n * ```\n */\nconst getFetchClient = (defaultOptions: FetchConfig = {}): FetchClient => {\n const backendURL = window.strapi.backendURL;\n\n /**\n * Create default headers with the current token.\n * This is a function so we can get a fresh token after refresh.\n */\n const getDefaultHeaders = () => ({\n Accept: 'application/json',\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${getToken()}`,\n });\n\n const isFormDataRequest = (body: unknown) => body instanceof FormData;\n const addPrependingSlash = (url: string) => (url.charAt(0) !== '/' ? `/${url}` : url);\n\n // This regular expression matches a string that starts with either \"http://\" or \"https://\" or any other protocol name in lower case letters, followed by \"://\" and ends with anything else\n const hasProtocol = (url: string) => new RegExp('^(?:[a-z+]+:)?//', 'i').test(url);\n\n // Check if the url has a prepending slash, if not add a slash\n const normalizeUrl = (url: string) => (hasProtocol(url) ? url : addPrependingSlash(url));\n\n // Add a response interceptor to return the response\n const responseInterceptor = async <TData = any>(\n response: Response,\n validateStatus?: FetchOptions['validateStatus']\n ): Promise<FetchResponse<TData>> => {\n try {\n const result = await response.json();\n\n /**\n * validateStatus allows us to customize when a response should throw an error\n * In native Fetch API, a response is considered \"not ok\"\n * when the status code falls in the 200 to 299 (inclusive) range\n */\n if (!response.ok && result.error && !validateStatus?.(response.status)) {\n const fetchError = new FetchError(result.error.message, { data: result });\n fetchError.status = response.status;\n throw fetchError;\n }\n\n if (!response.ok && !validateStatus?.(response.status)) {\n const fetchError = new FetchError('Unknown Server Error');\n fetchError.status = response.status;\n throw fetchError;\n }\n\n return { data: result };\n } catch (error) {\n if (error instanceof SyntaxError && response.ok) {\n // Making sure that a SyntaxError doesn't throw if it's successful\n return { data: [], status: response.status } as FetchResponse<any>;\n } else {\n throw error;\n }\n }\n };\n\n /**\n * Execute a fetch request with automatic token refresh on 401 errors.\n * @param url - The request URL (used to check if it's an auth path)\n * @param executeRequest - Function that performs the fetch (called again on retry with fresh headers)\n */\n const withTokenRefresh = async <TData>(\n url: string,\n executeRequest: () => Promise<FetchResponse<TData>>\n ): Promise<FetchResponse<TData>> => {\n try {\n return await executeRequest();\n } catch (error) {\n // Only attempt refresh for 401 errors on non-auth paths\n if (isFetchError(error) && error.status === 401 && !isAuthPath(url)) {\n try {\n await attemptTokenRefresh();\n // Retry - executeRequest will call getDefaultHeaders() again, picking up the new token\n return await executeRequest();\n } catch {\n // If refresh fails, throw the original error\n throw error;\n }\n }\n throw error;\n }\n };\n\n const paramsSerializer =\n <Param = unknown>(params?: Param) =>\n (url: string) => {\n if (params) {\n if (typeof params === 'string') {\n return `${url}?${params}`;\n }\n\n /**\n * TODO V6: Encoding should be enabled in this step\n * So the rest of the app doesn't have to worry about it,\n * It's considered a breaking change because it impacts any API request, including the user's custom code\n */\n const serializedParams = qs.stringify(params, { encode: false });\n return `${url}?${serializedParams}`;\n }\n return url;\n };\n\n const addBaseUrl = (url: Parameters<typeof fetch>[0]) => {\n return `${backendURL}${url}`;\n };\n\n /**\n * We use the factory method because the options\n * are unique to the individual request\n */\n const makeCreateRequestUrl = (options?: FetchOptions) =>\n pipe(normalizeUrl, addBaseUrl, paramsSerializer(options?.params));\n\n const fetchClient: FetchClient = {\n get: async <TData>(url: string, options?: FetchOptions): Promise<FetchResponse<TData>> => {\n const createRequestUrl = makeCreateRequestUrl(options);\n\n const executeRequest = async () => {\n const headers = new Headers({\n ...getDefaultHeaders(),\n ...options?.headers,\n });\n\n const response = await fetch(createRequestUrl(url), {\n signal: options?.signal ?? defaultOptions.signal,\n method: 'GET',\n headers,\n });\n\n return responseInterceptor<TData>(response, options?.validateStatus);\n };\n\n return withTokenRefresh(url, executeRequest);\n },\n post: async <TData, TSend = any>(\n url: string,\n data?: TSend,\n options?: FetchOptions\n ): Promise<FetchResponse<TData>> => {\n const createRequestUrl = makeCreateRequestUrl(options);\n\n const executeRequest = async () => {\n const headers = new Headers({\n ...getDefaultHeaders(),\n ...options?.headers,\n });\n\n /**\n * we have to remove the Content-Type value if it was a formData request\n * the browser will automatically set the header value\n */\n if (isFormDataRequest(data)) {\n headers.delete('Content-Type');\n }\n\n const response = await fetch(createRequestUrl(url), {\n signal: options?.signal ?? defaultOptions.signal,\n method: 'POST',\n headers,\n body: isFormDataRequest(data) ? (data as FormData) : JSON.stringify(data),\n });\n return responseInterceptor<TData>(response, options?.validateStatus);\n };\n\n return withTokenRefresh(url, executeRequest);\n },\n put: async <TData, TSend = any>(\n url: string,\n data?: TSend,\n options?: FetchOptions\n ): Promise<FetchResponse<TData>> => {\n const createRequestUrl = makeCreateRequestUrl(options);\n\n const executeRequest = async () => {\n const headers = new Headers({\n ...getDefaultHeaders(),\n ...options?.headers,\n });\n\n /**\n * we have to remove the Content-Type value if it was a formData request\n * the browser will automatically set the header value\n */\n if (isFormDataRequest(data)) {\n headers.delete('Content-Type');\n }\n\n const response = await fetch(createRequestUrl(url), {\n signal: options?.signal ?? defaultOptions.signal,\n method: 'PUT',\n headers,\n body: isFormDataRequest(data) ? (data as FormData) : JSON.stringify(data),\n });\n\n return responseInterceptor<TData>(response, options?.validateStatus);\n };\n\n return withTokenRefresh(url, executeRequest);\n },\n del: async <TData>(url: string, options?: FetchOptions): Promise<FetchResponse<TData>> => {\n const createRequestUrl = makeCreateRequestUrl(options);\n\n const executeRequest = async () => {\n const headers = new Headers({\n ...getDefaultHeaders(),\n ...options?.headers,\n });\n\n const response = await fetch(createRequestUrl(url), {\n signal: options?.signal ?? defaultOptions.signal,\n method: 'DELETE',\n headers,\n });\n return responseInterceptor<TData>(response, options?.validateStatus);\n };\n\n return withTokenRefresh(url, executeRequest);\n },\n };\n\n return fetchClient;\n};\n\nexport {\n getFetchClient,\n isFetchError,\n FetchError,\n attemptTokenRefresh,\n storeToken,\n setOnTokenUpdate,\n};\nexport type { FetchOptions, FetchResponse, FetchConfig, FetchClient, ErrorResponse };\n"],"names":["STORAGE_KEYS","TOKEN","USER","refreshPromise","onTokenUpdate","setOnTokenUpdate","callback","isAuthPath","url","test","storeToken","token","wasPersistedToLocalStorage","Boolean","localStorage","getItem","setItem","JSON","stringify","setCookie","refreshAccessToken","backendURL","window","strapi","response","fetch","method","credentials","headers","Accept","ok","console","warn","status","result","json","data","error","attemptTokenRefresh","finally","newToken","Error","name","FetchError","message","code","captureStackTrace","isFetchError","getToken","fromLocalStorage","parse","fromCookie","getCookieValue","getFetchClient","defaultOptions","getDefaultHeaders","Authorization","isFormDataRequest","body","FormData","addPrependingSlash","charAt","hasProtocol","RegExp","normalizeUrl","responseInterceptor","validateStatus","fetchError","SyntaxError","withTokenRefresh","executeRequest","paramsSerializer","params","serializedParams","qs","encode","addBaseUrl","makeCreateRequestUrl","options","pipe","fetchClient","get","createRequestUrl","Headers","signal","post","delete","put","del"],"mappings":"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"}
@@ -97,8 +97,11 @@ exports.useAdminUsers = users.useAdminUsers;
97
97
  exports.useGetCountDocumentsQuery = homepage.useGetCountDocumentsQuery;
98
98
  exports.translatedErrors = translatedErrors.translatedErrors;
99
99
  exports.FetchError = getFetchClient.FetchError;
100
+ exports.attemptTokenRefresh = getFetchClient.attemptTokenRefresh;
100
101
  exports.getFetchClient = getFetchClient.getFetchClient;
101
102
  exports.isFetchError = getFetchClient.isFetchError;
103
+ exports.setOnTokenUpdate = getFetchClient.setOnTokenUpdate;
104
+ exports.storeToken = getFetchClient.storeToken;
102
105
  exports.fetchBaseQuery = baseQuery.fetchBaseQuery;
103
106
  exports.isBaseQueryError = baseQuery.isBaseQueryError;
104
107
  exports.ConditionSchema = rulesEngine.ConditionSchema;
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -37,7 +37,7 @@ export { useDeviceType } from './admin/src/hooks/useDeviceType.mjs';
37
37
  export { useAdminUsers } from './admin/src/services/users.mjs';
38
38
  export { useGetCountDocumentsQuery } from './admin/src/services/homepage.mjs';
39
39
  export { translatedErrors } from './admin/src/utils/translatedErrors.mjs';
40
- export { FetchError, getFetchClient, isFetchError } from './admin/src/utils/getFetchClient.mjs';
40
+ export { FetchError, attemptTokenRefresh, getFetchClient, isFetchError, setOnTokenUpdate, storeToken } from './admin/src/utils/getFetchClient.mjs';
41
41
  export { fetchBaseQuery, isBaseQueryError } from './admin/src/utils/baseQuery.mjs';
42
42
  export { ConditionSchema, createRulesEngine } from './admin/src/utils/rulesEngine.mjs';
43
43
  export { adminApi } from './admin/src/services/api.mjs';
@@ -10,7 +10,7 @@ declare const useGetPermissionsQuery: import("@reduxjs/toolkit/dist/query/react/
10
10
  apiName: string;
11
11
  type: string;
12
12
  };
13
- method: "GET" | "POST" | "PUT" | "DELETE";
13
+ method: "POST" | "GET" | "PUT" | "DELETE";
14
14
  path: string;
15
15
  }[];
16
16
  }, "adminApi">>;
@@ -1,5 +1,38 @@
1
1
  import type { errors } from '@strapi/utils';
2
2
  export type ApiError = errors.ApplicationError | errors.ForbiddenError | errors.NotFoundError | errors.NotImplementedError | errors.PaginationError | errors.PayloadTooLargeError | errors.PolicyError | errors.RateLimitError | errors.UnauthorizedError | errors.ValidationError | errors.YupValidationError;
3
+ /**
4
+ * Set the callback that will be called when the token is refreshed.
5
+ * This allows the React layer to update Redux state when a token refresh occurs.
6
+ *
7
+ * @param callback - Function to call with the new token, or null to clear
8
+ * @example
9
+ * // In a React component
10
+ * useEffect(() => {
11
+ * setOnTokenUpdate((token) => dispatch(setToken(token)));
12
+ * return () => setOnTokenUpdate(null);
13
+ * }, [dispatch]);
14
+ */
15
+ declare const setOnTokenUpdate: (callback: ((token: string) => void) | null) => void;
16
+ /**
17
+ * Store the new token in the appropriate storage (localStorage or cookie)
18
+ * and notify the app to update its state.
19
+ *
20
+ * Uses localStorage if the user selected "remember me" during login,
21
+ * otherwise uses cookies for session-based storage.
22
+ *
23
+ * @param token - The JWT token to store
24
+ * @internal Exported for testing purposes
25
+ */
26
+ declare const storeToken: (token: string) => void;
27
+ /**
28
+ * Attempt to refresh the token if not already refreshing.
29
+ * Uses a module-level promise to prevent concurrent refresh requests.
30
+ *
31
+ * @returns The new authentication token
32
+ * @throws {Error} If the token refresh fails (e.g., refresh token expired)
33
+ * @internal Exported for testing purposes
34
+ */
35
+ declare const attemptTokenRefresh: () => Promise<string>;
3
36
  type FetchResponse<TData = any> = {
4
37
  data: TData;
5
38
  status?: number;
@@ -57,5 +90,5 @@ type FetchClient = {
57
90
  * ```
58
91
  */
59
92
  declare const getFetchClient: (defaultOptions?: FetchConfig) => FetchClient;
60
- export { getFetchClient, isFetchError, FetchError };
93
+ export { getFetchClient, isFetchError, FetchError, attemptTokenRefresh, storeToken, setOnTokenUpdate, };
61
94
  export type { FetchOptions, FetchResponse, FetchConfig, FetchClient, ErrorResponse };
@@ -34,7 +34,7 @@ declare const _default: {
34
34
  shouldStopCreate: boolean;
35
35
  licenseLimitStatus: string | null;
36
36
  isHostedOnStrapiCloud: boolean;
37
- aiLicenseKey: unknown;
37
+ aiLicenseKey: string | undefined;
38
38
  type: string | null | undefined;
39
39
  isTrial: boolean;
40
40
  features: {
@@ -52,7 +52,7 @@ declare const _default: {
52
52
  shouldStopCreate: boolean;
53
53
  licenseLimitStatus: string | null;
54
54
  isHostedOnStrapiCloud: boolean;
55
- aiLicenseKey: unknown;
55
+ aiLicenseKey: string | undefined;
56
56
  type: string | null | undefined;
57
57
  isTrial: boolean;
58
58
  features: {
@@ -63,7 +63,7 @@ declare const getAdminEE: () => {
63
63
  shouldStopCreate: boolean;
64
64
  licenseLimitStatus: string | null;
65
65
  isHostedOnStrapiCloud: boolean;
66
- aiLicenseKey: unknown;
66
+ aiLicenseKey: string | undefined;
67
67
  type: string | null | undefined;
68
68
  isTrial: boolean;
69
69
  features: {
@@ -2,6 +2,7 @@
2
2
 
3
3
  var index = require('../utils/index.js');
4
4
  var user = require('../validation/user.js');
5
+ var sessionAuth = require('../../../shared/utils/session-auth.js');
5
6
 
6
7
  var authenticatedUser = {
7
8
  async getMe (ctx) {
@@ -25,6 +26,11 @@ var authenticatedUser = {
25
26
  ]
26
27
  });
27
28
  }
29
+ // Invalidate all sessions when password changes for security
30
+ const sessionManager = sessionAuth.getSessionManager();
31
+ if (sessionManager && sessionManager.hasOrigin('admin')) {
32
+ await sessionManager('admin').invalidateRefreshToken(String(ctx.state.user.id));
33
+ }
28
34
  }
29
35
  const updatedUser = await userService.updateById(ctx.state.user.id, userInfo);
30
36
  ctx.body = {
@@ -1 +1 @@
1
- {"version":3,"file":"authenticated-user.js","sources":["../../../../../server/src/controllers/authenticated-user.ts"],"sourcesContent":["import type { Context } from 'koa';\nimport type { AdminUser } from '../../../shared/contracts/shared';\n\nimport { getService } from '../utils';\nimport { validateProfileUpdateInput } from '../validation/user';\nimport { GetMe, GetOwnPermissions, UpdateMe } from '../../../shared/contracts/users';\n\nexport default {\n async getMe(ctx: Context) {\n const userInfo = getService('user').sanitizeUser(ctx.state.user as AdminUser);\n\n ctx.body = {\n data: userInfo,\n } satisfies GetMe.Response;\n },\n\n async updateMe(ctx: Context) {\n const input = ctx.request.body as UpdateMe.Request['body'];\n\n await validateProfileUpdateInput(input);\n\n const userService = getService('user');\n const authServer = getService('auth');\n\n const { currentPassword, ...userInfo } = input;\n\n if (currentPassword && userInfo.password) {\n const isValid = await authServer.validatePassword(currentPassword, ctx.state.user.password);\n\n if (!isValid) {\n return ctx.badRequest('ValidationError', {\n currentPassword: ['Invalid credentials'],\n });\n }\n }\n\n const updatedUser = await userService.updateById(ctx.state.user.id, userInfo);\n\n ctx.body = {\n data: userService.sanitizeUser(updatedUser),\n } satisfies UpdateMe.Response;\n },\n\n async getOwnPermissions(ctx: Context) {\n const { findUserPermissions, sanitizePermission } = getService('permission');\n const { user } = ctx.state;\n\n const userPermissions = await findUserPermissions(user as AdminUser);\n\n ctx.body = {\n // @ts-expect-error - transform response type to sanitized permission\n data: userPermissions.map(sanitizePermission),\n } satisfies GetOwnPermissions.Response;\n },\n};\n"],"names":["getMe","ctx","userInfo","getService","sanitizeUser","state","user","body","data","updateMe","input","request","validateProfileUpdateInput","userService","authServer","currentPassword","password","isValid","validatePassword","badRequest","updatedUser","updateById","id","getOwnPermissions","findUserPermissions","sanitizePermission","userPermissions","map"],"mappings":";;;;;AAOA,wBAAe;AACb,IAAA,MAAMA,OAAMC,GAAY,EAAA;QACtB,MAAMC,QAAAA,GAAWC,iBAAW,MAAQC,CAAAA,CAAAA,YAAY,CAACH,GAAII,CAAAA,KAAK,CAACC,IAAI,CAAA;AAE/DL,QAAAA,GAAAA,CAAIM,IAAI,GAAG;YACTC,IAAMN,EAAAA;AACR,SAAA;AACF,KAAA;AAEA,IAAA,MAAMO,UAASR,GAAY,EAAA;AACzB,QAAA,MAAMS,KAAQT,GAAAA,GAAAA,CAAIU,OAAO,CAACJ,IAAI;AAE9B,QAAA,MAAMK,+BAA2BF,CAAAA,KAAAA,CAAAA;AAEjC,QAAA,MAAMG,cAAcV,gBAAW,CAAA,MAAA,CAAA;AAC/B,QAAA,MAAMW,aAAaX,gBAAW,CAAA,MAAA,CAAA;AAE9B,QAAA,MAAM,EAAEY,eAAe,EAAE,GAAGb,UAAU,GAAGQ,KAAAA;QAEzC,IAAIK,eAAAA,IAAmBb,QAASc,CAAAA,QAAQ,EAAE;YACxC,MAAMC,OAAAA,GAAU,MAAMH,UAAAA,CAAWI,gBAAgB,CAACH,eAAiBd,EAAAA,GAAAA,CAAII,KAAK,CAACC,IAAI,CAACU,QAAQ,CAAA;AAE1F,YAAA,IAAI,CAACC,OAAS,EAAA;gBACZ,OAAOhB,GAAAA,CAAIkB,UAAU,CAAC,iBAAmB,EAAA;oBACvCJ,eAAiB,EAAA;AAAC,wBAAA;AAAsB;AAC1C,iBAAA,CAAA;AACF;AACF;QAEA,MAAMK,WAAAA,GAAc,MAAMP,WAAAA,CAAYQ,UAAU,CAACpB,GAAII,CAAAA,KAAK,CAACC,IAAI,CAACgB,EAAE,EAAEpB,QAAAA,CAAAA;AAEpED,QAAAA,GAAAA,CAAIM,IAAI,GAAG;YACTC,IAAMK,EAAAA,WAAAA,CAAYT,YAAY,CAACgB,WAAAA;AACjC,SAAA;AACF,KAAA;AAEA,IAAA,MAAMG,mBAAkBtB,GAAY,EAAA;AAClC,QAAA,MAAM,EAAEuB,mBAAmB,EAAEC,kBAAkB,EAAE,GAAGtB,gBAAW,CAAA,YAAA,CAAA;AAC/D,QAAA,MAAM,EAAEG,IAAI,EAAE,GAAGL,IAAII,KAAK;QAE1B,MAAMqB,eAAAA,GAAkB,MAAMF,mBAAoBlB,CAAAA,IAAAA,CAAAA;AAElDL,QAAAA,GAAAA,CAAIM,IAAI,GAAG;;YAETC,IAAMkB,EAAAA,eAAAA,CAAgBC,GAAG,CAACF,kBAAAA;AAC5B,SAAA;AACF;AACF,CAAE;;;;"}
1
+ {"version":3,"file":"authenticated-user.js","sources":["../../../../../server/src/controllers/authenticated-user.ts"],"sourcesContent":["import type { Context } from 'koa';\nimport type { AdminUser } from '../../../shared/contracts/shared';\n\nimport { getService } from '../utils';\nimport { validateProfileUpdateInput } from '../validation/user';\nimport { GetMe, GetOwnPermissions, UpdateMe } from '../../../shared/contracts/users';\nimport { getSessionManager } from '../../../shared/utils/session-auth';\n\nexport default {\n async getMe(ctx: Context) {\n const userInfo = getService('user').sanitizeUser(ctx.state.user as AdminUser);\n\n ctx.body = {\n data: userInfo,\n } satisfies GetMe.Response;\n },\n\n async updateMe(ctx: Context) {\n const input = ctx.request.body as UpdateMe.Request['body'];\n\n await validateProfileUpdateInput(input);\n\n const userService = getService('user');\n const authServer = getService('auth');\n\n const { currentPassword, ...userInfo } = input;\n\n if (currentPassword && userInfo.password) {\n const isValid = await authServer.validatePassword(currentPassword, ctx.state.user.password);\n\n if (!isValid) {\n return ctx.badRequest('ValidationError', {\n currentPassword: ['Invalid credentials'],\n });\n }\n\n // Invalidate all sessions when password changes for security\n const sessionManager = getSessionManager();\n if (sessionManager && sessionManager.hasOrigin('admin')) {\n await sessionManager('admin').invalidateRefreshToken(String(ctx.state.user.id));\n }\n }\n\n const updatedUser = await userService.updateById(ctx.state.user.id, userInfo);\n\n ctx.body = {\n data: userService.sanitizeUser(updatedUser),\n } satisfies UpdateMe.Response;\n },\n\n async getOwnPermissions(ctx: Context) {\n const { findUserPermissions, sanitizePermission } = getService('permission');\n const { user } = ctx.state;\n\n const userPermissions = await findUserPermissions(user as AdminUser);\n\n ctx.body = {\n // @ts-expect-error - transform response type to sanitized permission\n data: userPermissions.map(sanitizePermission),\n } satisfies GetOwnPermissions.Response;\n },\n};\n"],"names":["getMe","ctx","userInfo","getService","sanitizeUser","state","user","body","data","updateMe","input","request","validateProfileUpdateInput","userService","authServer","currentPassword","password","isValid","validatePassword","badRequest","sessionManager","getSessionManager","hasOrigin","invalidateRefreshToken","String","id","updatedUser","updateById","getOwnPermissions","findUserPermissions","sanitizePermission","userPermissions","map"],"mappings":";;;;;;AAQA,wBAAe;AACb,IAAA,MAAMA,OAAMC,GAAY,EAAA;QACtB,MAAMC,QAAAA,GAAWC,iBAAW,MAAQC,CAAAA,CAAAA,YAAY,CAACH,GAAII,CAAAA,KAAK,CAACC,IAAI,CAAA;AAE/DL,QAAAA,GAAAA,CAAIM,IAAI,GAAG;YACTC,IAAMN,EAAAA;AACR,SAAA;AACF,KAAA;AAEA,IAAA,MAAMO,UAASR,GAAY,EAAA;AACzB,QAAA,MAAMS,KAAQT,GAAAA,GAAAA,CAAIU,OAAO,CAACJ,IAAI;AAE9B,QAAA,MAAMK,+BAA2BF,CAAAA,KAAAA,CAAAA;AAEjC,QAAA,MAAMG,cAAcV,gBAAW,CAAA,MAAA,CAAA;AAC/B,QAAA,MAAMW,aAAaX,gBAAW,CAAA,MAAA,CAAA;AAE9B,QAAA,MAAM,EAAEY,eAAe,EAAE,GAAGb,UAAU,GAAGQ,KAAAA;QAEzC,IAAIK,eAAAA,IAAmBb,QAASc,CAAAA,QAAQ,EAAE;YACxC,MAAMC,OAAAA,GAAU,MAAMH,UAAAA,CAAWI,gBAAgB,CAACH,eAAiBd,EAAAA,GAAAA,CAAII,KAAK,CAACC,IAAI,CAACU,QAAQ,CAAA;AAE1F,YAAA,IAAI,CAACC,OAAS,EAAA;gBACZ,OAAOhB,GAAAA,CAAIkB,UAAU,CAAC,iBAAmB,EAAA;oBACvCJ,eAAiB,EAAA;AAAC,wBAAA;AAAsB;AAC1C,iBAAA,CAAA;AACF;;AAGA,YAAA,MAAMK,cAAiBC,GAAAA,6BAAAA,EAAAA;AACvB,YAAA,IAAID,cAAkBA,IAAAA,cAAAA,CAAeE,SAAS,CAAC,OAAU,CAAA,EAAA;gBACvD,MAAMF,cAAAA,CAAe,OAASG,CAAAA,CAAAA,sBAAsB,CAACC,MAAAA,CAAOvB,IAAII,KAAK,CAACC,IAAI,CAACmB,EAAE,CAAA,CAAA;AAC/E;AACF;QAEA,MAAMC,WAAAA,GAAc,MAAMb,WAAAA,CAAYc,UAAU,CAAC1B,GAAII,CAAAA,KAAK,CAACC,IAAI,CAACmB,EAAE,EAAEvB,QAAAA,CAAAA;AAEpED,QAAAA,GAAAA,CAAIM,IAAI,GAAG;YACTC,IAAMK,EAAAA,WAAAA,CAAYT,YAAY,CAACsB,WAAAA;AACjC,SAAA;AACF,KAAA;AAEA,IAAA,MAAME,mBAAkB3B,GAAY,EAAA;AAClC,QAAA,MAAM,EAAE4B,mBAAmB,EAAEC,kBAAkB,EAAE,GAAG3B,gBAAW,CAAA,YAAA,CAAA;AAC/D,QAAA,MAAM,EAAEG,IAAI,EAAE,GAAGL,IAAII,KAAK;QAE1B,MAAM0B,eAAAA,GAAkB,MAAMF,mBAAoBvB,CAAAA,IAAAA,CAAAA;AAElDL,QAAAA,GAAAA,CAAIM,IAAI,GAAG;;YAETC,IAAMuB,EAAAA,eAAAA,CAAgBC,GAAG,CAACF,kBAAAA;AAC5B,SAAA;AACF;AACF,CAAE;;;;"}
@@ -1,5 +1,6 @@
1
1
  import { getService } from '../utils/index.mjs';
2
2
  import { validateProfileUpdateInput } from '../validation/user.mjs';
3
+ import { getSessionManager } from '../../../shared/utils/session-auth.mjs';
3
4
 
4
5
  var authenticatedUser = {
5
6
  async getMe (ctx) {
@@ -23,6 +24,11 @@ var authenticatedUser = {
23
24
  ]
24
25
  });
25
26
  }
27
+ // Invalidate all sessions when password changes for security
28
+ const sessionManager = getSessionManager();
29
+ if (sessionManager && sessionManager.hasOrigin('admin')) {
30
+ await sessionManager('admin').invalidateRefreshToken(String(ctx.state.user.id));
31
+ }
26
32
  }
27
33
  const updatedUser = await userService.updateById(ctx.state.user.id, userInfo);
28
34
  ctx.body = {
@@ -1 +1 @@
1
- {"version":3,"file":"authenticated-user.mjs","sources":["../../../../../server/src/controllers/authenticated-user.ts"],"sourcesContent":["import type { Context } from 'koa';\nimport type { AdminUser } from '../../../shared/contracts/shared';\n\nimport { getService } from '../utils';\nimport { validateProfileUpdateInput } from '../validation/user';\nimport { GetMe, GetOwnPermissions, UpdateMe } from '../../../shared/contracts/users';\n\nexport default {\n async getMe(ctx: Context) {\n const userInfo = getService('user').sanitizeUser(ctx.state.user as AdminUser);\n\n ctx.body = {\n data: userInfo,\n } satisfies GetMe.Response;\n },\n\n async updateMe(ctx: Context) {\n const input = ctx.request.body as UpdateMe.Request['body'];\n\n await validateProfileUpdateInput(input);\n\n const userService = getService('user');\n const authServer = getService('auth');\n\n const { currentPassword, ...userInfo } = input;\n\n if (currentPassword && userInfo.password) {\n const isValid = await authServer.validatePassword(currentPassword, ctx.state.user.password);\n\n if (!isValid) {\n return ctx.badRequest('ValidationError', {\n currentPassword: ['Invalid credentials'],\n });\n }\n }\n\n const updatedUser = await userService.updateById(ctx.state.user.id, userInfo);\n\n ctx.body = {\n data: userService.sanitizeUser(updatedUser),\n } satisfies UpdateMe.Response;\n },\n\n async getOwnPermissions(ctx: Context) {\n const { findUserPermissions, sanitizePermission } = getService('permission');\n const { user } = ctx.state;\n\n const userPermissions = await findUserPermissions(user as AdminUser);\n\n ctx.body = {\n // @ts-expect-error - transform response type to sanitized permission\n data: userPermissions.map(sanitizePermission),\n } satisfies GetOwnPermissions.Response;\n },\n};\n"],"names":["getMe","ctx","userInfo","getService","sanitizeUser","state","user","body","data","updateMe","input","request","validateProfileUpdateInput","userService","authServer","currentPassword","password","isValid","validatePassword","badRequest","updatedUser","updateById","id","getOwnPermissions","findUserPermissions","sanitizePermission","userPermissions","map"],"mappings":";;;AAOA,wBAAe;AACb,IAAA,MAAMA,OAAMC,GAAY,EAAA;QACtB,MAAMC,QAAAA,GAAWC,WAAW,MAAQC,CAAAA,CAAAA,YAAY,CAACH,GAAII,CAAAA,KAAK,CAACC,IAAI,CAAA;AAE/DL,QAAAA,GAAAA,CAAIM,IAAI,GAAG;YACTC,IAAMN,EAAAA;AACR,SAAA;AACF,KAAA;AAEA,IAAA,MAAMO,UAASR,GAAY,EAAA;AACzB,QAAA,MAAMS,KAAQT,GAAAA,GAAAA,CAAIU,OAAO,CAACJ,IAAI;AAE9B,QAAA,MAAMK,0BAA2BF,CAAAA,KAAAA,CAAAA;AAEjC,QAAA,MAAMG,cAAcV,UAAW,CAAA,MAAA,CAAA;AAC/B,QAAA,MAAMW,aAAaX,UAAW,CAAA,MAAA,CAAA;AAE9B,QAAA,MAAM,EAAEY,eAAe,EAAE,GAAGb,UAAU,GAAGQ,KAAAA;QAEzC,IAAIK,eAAAA,IAAmBb,QAASc,CAAAA,QAAQ,EAAE;YACxC,MAAMC,OAAAA,GAAU,MAAMH,UAAAA,CAAWI,gBAAgB,CAACH,eAAiBd,EAAAA,GAAAA,CAAII,KAAK,CAACC,IAAI,CAACU,QAAQ,CAAA;AAE1F,YAAA,IAAI,CAACC,OAAS,EAAA;gBACZ,OAAOhB,GAAAA,CAAIkB,UAAU,CAAC,iBAAmB,EAAA;oBACvCJ,eAAiB,EAAA;AAAC,wBAAA;AAAsB;AAC1C,iBAAA,CAAA;AACF;AACF;QAEA,MAAMK,WAAAA,GAAc,MAAMP,WAAAA,CAAYQ,UAAU,CAACpB,GAAII,CAAAA,KAAK,CAACC,IAAI,CAACgB,EAAE,EAAEpB,QAAAA,CAAAA;AAEpED,QAAAA,GAAAA,CAAIM,IAAI,GAAG;YACTC,IAAMK,EAAAA,WAAAA,CAAYT,YAAY,CAACgB,WAAAA;AACjC,SAAA;AACF,KAAA;AAEA,IAAA,MAAMG,mBAAkBtB,GAAY,EAAA;AAClC,QAAA,MAAM,EAAEuB,mBAAmB,EAAEC,kBAAkB,EAAE,GAAGtB,UAAW,CAAA,YAAA,CAAA;AAC/D,QAAA,MAAM,EAAEG,IAAI,EAAE,GAAGL,IAAII,KAAK;QAE1B,MAAMqB,eAAAA,GAAkB,MAAMF,mBAAoBlB,CAAAA,IAAAA,CAAAA;AAElDL,QAAAA,GAAAA,CAAIM,IAAI,GAAG;;YAETC,IAAMkB,EAAAA,eAAAA,CAAgBC,GAAG,CAACF,kBAAAA;AAC5B,SAAA;AACF;AACF,CAAE;;;;"}
1
+ {"version":3,"file":"authenticated-user.mjs","sources":["../../../../../server/src/controllers/authenticated-user.ts"],"sourcesContent":["import type { Context } from 'koa';\nimport type { AdminUser } from '../../../shared/contracts/shared';\n\nimport { getService } from '../utils';\nimport { validateProfileUpdateInput } from '../validation/user';\nimport { GetMe, GetOwnPermissions, UpdateMe } from '../../../shared/contracts/users';\nimport { getSessionManager } from '../../../shared/utils/session-auth';\n\nexport default {\n async getMe(ctx: Context) {\n const userInfo = getService('user').sanitizeUser(ctx.state.user as AdminUser);\n\n ctx.body = {\n data: userInfo,\n } satisfies GetMe.Response;\n },\n\n async updateMe(ctx: Context) {\n const input = ctx.request.body as UpdateMe.Request['body'];\n\n await validateProfileUpdateInput(input);\n\n const userService = getService('user');\n const authServer = getService('auth');\n\n const { currentPassword, ...userInfo } = input;\n\n if (currentPassword && userInfo.password) {\n const isValid = await authServer.validatePassword(currentPassword, ctx.state.user.password);\n\n if (!isValid) {\n return ctx.badRequest('ValidationError', {\n currentPassword: ['Invalid credentials'],\n });\n }\n\n // Invalidate all sessions when password changes for security\n const sessionManager = getSessionManager();\n if (sessionManager && sessionManager.hasOrigin('admin')) {\n await sessionManager('admin').invalidateRefreshToken(String(ctx.state.user.id));\n }\n }\n\n const updatedUser = await userService.updateById(ctx.state.user.id, userInfo);\n\n ctx.body = {\n data: userService.sanitizeUser(updatedUser),\n } satisfies UpdateMe.Response;\n },\n\n async getOwnPermissions(ctx: Context) {\n const { findUserPermissions, sanitizePermission } = getService('permission');\n const { user } = ctx.state;\n\n const userPermissions = await findUserPermissions(user as AdminUser);\n\n ctx.body = {\n // @ts-expect-error - transform response type to sanitized permission\n data: userPermissions.map(sanitizePermission),\n } satisfies GetOwnPermissions.Response;\n },\n};\n"],"names":["getMe","ctx","userInfo","getService","sanitizeUser","state","user","body","data","updateMe","input","request","validateProfileUpdateInput","userService","authServer","currentPassword","password","isValid","validatePassword","badRequest","sessionManager","getSessionManager","hasOrigin","invalidateRefreshToken","String","id","updatedUser","updateById","getOwnPermissions","findUserPermissions","sanitizePermission","userPermissions","map"],"mappings":";;;;AAQA,wBAAe;AACb,IAAA,MAAMA,OAAMC,GAAY,EAAA;QACtB,MAAMC,QAAAA,GAAWC,WAAW,MAAQC,CAAAA,CAAAA,YAAY,CAACH,GAAII,CAAAA,KAAK,CAACC,IAAI,CAAA;AAE/DL,QAAAA,GAAAA,CAAIM,IAAI,GAAG;YACTC,IAAMN,EAAAA;AACR,SAAA;AACF,KAAA;AAEA,IAAA,MAAMO,UAASR,GAAY,EAAA;AACzB,QAAA,MAAMS,KAAQT,GAAAA,GAAAA,CAAIU,OAAO,CAACJ,IAAI;AAE9B,QAAA,MAAMK,0BAA2BF,CAAAA,KAAAA,CAAAA;AAEjC,QAAA,MAAMG,cAAcV,UAAW,CAAA,MAAA,CAAA;AAC/B,QAAA,MAAMW,aAAaX,UAAW,CAAA,MAAA,CAAA;AAE9B,QAAA,MAAM,EAAEY,eAAe,EAAE,GAAGb,UAAU,GAAGQ,KAAAA;QAEzC,IAAIK,eAAAA,IAAmBb,QAASc,CAAAA,QAAQ,EAAE;YACxC,MAAMC,OAAAA,GAAU,MAAMH,UAAAA,CAAWI,gBAAgB,CAACH,eAAiBd,EAAAA,GAAAA,CAAII,KAAK,CAACC,IAAI,CAACU,QAAQ,CAAA;AAE1F,YAAA,IAAI,CAACC,OAAS,EAAA;gBACZ,OAAOhB,GAAAA,CAAIkB,UAAU,CAAC,iBAAmB,EAAA;oBACvCJ,eAAiB,EAAA;AAAC,wBAAA;AAAsB;AAC1C,iBAAA,CAAA;AACF;;AAGA,YAAA,MAAMK,cAAiBC,GAAAA,iBAAAA,EAAAA;AACvB,YAAA,IAAID,cAAkBA,IAAAA,cAAAA,CAAeE,SAAS,CAAC,OAAU,CAAA,EAAA;gBACvD,MAAMF,cAAAA,CAAe,OAASG,CAAAA,CAAAA,sBAAsB,CAACC,MAAAA,CAAOvB,IAAII,KAAK,CAACC,IAAI,CAACmB,EAAE,CAAA,CAAA;AAC/E;AACF;QAEA,MAAMC,WAAAA,GAAc,MAAMb,WAAAA,CAAYc,UAAU,CAAC1B,GAAII,CAAAA,KAAK,CAACC,IAAI,CAACmB,EAAE,EAAEvB,QAAAA,CAAAA;AAEpED,QAAAA,GAAAA,CAAIM,IAAI,GAAG;YACTC,IAAMK,EAAAA,WAAAA,CAAYT,YAAY,CAACsB,WAAAA;AACjC,SAAA;AACF,KAAA;AAEA,IAAA,MAAME,mBAAkB3B,GAAY,EAAA;AAClC,QAAA,MAAM,EAAE4B,mBAAmB,EAAEC,kBAAkB,EAAE,GAAG3B,UAAW,CAAA,YAAA,CAAA;AAC/D,QAAA,MAAM,EAAEG,IAAI,EAAE,GAAGL,IAAII,KAAK;QAE1B,MAAM0B,eAAAA,GAAkB,MAAMF,mBAAoBvB,CAAAA,IAAAA,CAAAA;AAElDL,QAAAA,GAAAA,CAAIM,IAAI,GAAG;;YAETC,IAAMuB,EAAAA,eAAAA,CAAgBC,GAAG,CAACF,kBAAAA;AAC5B,SAAA;AACF;AACF,CAAE;;;;"}