@innertia-solutions/innertia-nuxt 0.1.66 → 0.1.67

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@innertia-solutions/innertia-nuxt",
3
- "version": "0.1.66",
3
+ "version": "0.1.67",
4
4
  "description": "Innertia Solutions — Nuxt layer unificada: core, auth, multitenancy, theme y app contexts",
5
5
  "keywords": [
6
6
  "nuxt",
@@ -1,6 +1,7 @@
1
- import { defineEventHandler, getRouterParam, getQuery, sendRedirect, createError } from 'h3'
1
+ import { defineEventHandler, getRouterParam, getQuery, sendRedirect, createError, getRequestHeader } from 'h3'
2
2
  import { writeSession } from '../../../utils/session'
3
3
  import { normalizeLoginResponse } from '../../../utils/login'
4
+ import { forwardClientHeaders } from '../../../utils/bff'
4
5
 
5
6
  // Recibe el callback de OAuth, intercambia contra Laravel, sella la sesión y
6
7
  // redirige a la app. El token nunca llega al browser.
@@ -12,7 +13,7 @@ export default defineEventHandler(async (event) => {
12
13
  let data: any
13
14
  try {
14
15
  data = await $fetch(`${config.apiInternalUrl}/auth/${provider}/callback`, {
15
- method: 'GET', query, headers: { Accept: 'application/json' },
16
+ method: 'GET', query, headers: { Accept: 'application/json', ...forwardClientHeaders(n => getRequestHeader(event, n)) },
16
17
  })
17
18
  } catch (err: any) {
18
19
  const msg = err?.data?.message ?? 'oauth_failed'
@@ -1,5 +1,6 @@
1
- import { defineEventHandler, readBody, createError } from 'h3'
1
+ import { defineEventHandler, readBody, createError, getRequestHeader } from 'h3'
2
2
  import { writeSession } from '../../utils/session'
3
+ import { forwardClientHeaders } from '../../utils/bff'
3
4
  import { normalizeLoginResponse } from '../../utils/login'
4
5
 
5
6
  // Llama a Laravel /auth/login server-to-server; en éxito sella el JWT en la
@@ -13,7 +14,7 @@ export default defineEventHandler(async (event) => {
13
14
  data = await $fetch(`${config.apiInternalUrl}/auth/login`, {
14
15
  method: 'POST',
15
16
  body: { context, email, password, remember },
16
- headers: { Accept: 'application/json' },
17
+ headers: { Accept: 'application/json', ...forwardClientHeaders(n => getRequestHeader(event, n)) },
17
18
  })
18
19
  } catch (err: any) {
19
20
  const status = err?.response?.status ?? err?.statusCode ?? 500
@@ -1,5 +1,6 @@
1
- import { defineEventHandler, readBody, createError } from 'h3'
1
+ import { defineEventHandler, readBody, createError, getRequestHeader } from 'h3'
2
2
  import { writeSession } from '../../../utils/session'
3
+ import { forwardClientHeaders } from '../../../utils/bff'
3
4
  import { normalizeLoginResponse } from '../../../utils/login'
4
5
 
5
6
  // Login de plataforma (identidad separada, sin contexto de tenant). Llama a
@@ -15,7 +16,7 @@ export default defineEventHandler(async (event) => {
15
16
  data = await $fetch(`${config.apiInternalUrl}/platform/auth/login`, {
16
17
  method: 'POST',
17
18
  body: { email, password },
18
- headers: { Accept: 'application/json' },
19
+ headers: { Accept: 'application/json', ...forwardClientHeaders(n => getRequestHeader(event, n)) },
19
20
  })
20
21
  } catch (err: any) {
21
22
  const status = err?.response?.status ?? err?.statusCode ?? 500
@@ -20,3 +20,21 @@ describe('buildAuthHeaders', () => {
20
20
  expect(buildAuthHeaders(undefined)).toEqual({ cookie: '' })
21
21
  })
22
22
  })
23
+
24
+ describe('forwardClientHeaders', () => {
25
+ it('reenvía solo IP y user-agent del cliente, omitiendo los ausentes', async () => {
26
+ const { forwardClientHeaders } = await import('../bff')
27
+ const headers: Record<string, string> = {
28
+ 'x-forwarded-for': '200.1.2.3, 172.68.0.1',
29
+ 'cf-connecting-ip': '200.1.2.3',
30
+ 'user-agent': 'Mozilla/5.0',
31
+ cookie: 'innertia_session=secret',
32
+ authorization: 'Bearer x',
33
+ }
34
+ expect(forwardClientHeaders(n => headers[n])).toEqual({
35
+ 'x-forwarded-for': '200.1.2.3, 172.68.0.1',
36
+ 'cf-connecting-ip': '200.1.2.3',
37
+ 'user-agent': 'Mozilla/5.0',
38
+ })
39
+ })
40
+ })
@@ -18,3 +18,22 @@ export function resolveApiTarget(base: string, path: string, search: string): st
18
18
  export function buildAuthHeaders(token?: string): Record<string, string> {
19
19
  return token ? { authorization: `Bearer ${token}`, cookie: '' } : { cookie: '' }
20
20
  }
21
+
22
+ /** Headers del cliente que Laravel necesita para conocer su IP real y dispositivo. */
23
+ const CLIENT_HEADERS = ['x-forwarded-for', 'x-real-ip', 'cf-connecting-ip', 'user-agent'] as const
24
+
25
+ /**
26
+ * Headers del cliente original a reenviar en las llamadas server-to-server
27
+ * (login, login de plataforma, callback OAuth, logout). Sin esto Laravel ve a
28
+ * TODOS los usuarios con la IP del contenedor Nuxt: el throttle de login queda
29
+ * compartido entre todos (un atacante bloquea el login de todos) y las
30
+ * sesiones quedan sin IP ni dispositivo. Recibe un getter para no depender de h3.
31
+ */
32
+ export function forwardClientHeaders(get: (name: string) => string | undefined | null): Record<string, string> {
33
+ const out: Record<string, string> = {}
34
+ for (const name of CLIENT_HEADERS) {
35
+ const value = get(name)
36
+ if (value) out[name] = value
37
+ }
38
+ return out
39
+ }