Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add httpOnly flag to cookie options in getI18nCookie function #3060

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ env:
on:
push:
branches:
- main
- v8
pull_request:
branches:
- main
- v8

jobs:
lint:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ jobs:
- name: Commit changelog
uses: stefanzweifel/git-auto-commit-action@v5
with:
branch: main
branch: v8
file_pattern: 'CHANGELOG.md'
commit_message: 'chore: generate changelog'

Expand Down
22 changes: 22 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
# v8.3.3 (2024-07-28T09:51:21Z)

This changelog is generated by [GitHub Releases](https://github.com/nuxt-modules/i18n/releases/tag/v8.3.3)

###    πŸž Bug Fixes

- Encode `switchLocalePath` during SSR replacement &nbsp;-&nbsp; by @BobbieGoede in https://github.com/nuxt-modules/i18n/issues/3043 [<samp>(28d22)</samp>](https://github.com/nuxt-modules/i18n/commit/28d22aa6)

##### &nbsp;&nbsp;&nbsp;&nbsp;[View changes on GitHub](https://github.com/nuxt-modules/i18n/compare/v8.3.2...v8.3.3)

# v8.3.2 (2024-07-27T04:42:45Z)

This changelog is generated by [GitHub Releases](https://github.com/nuxt-modules/i18n/releases/tag/v8.3.2)

### &nbsp;&nbsp;&nbsp;🐞 Bug Fixes

- Update `@nuxt/module-builder` &nbsp;-&nbsp; by @BobbieGoede in https://github.com/nuxt-modules/i18n/issues/2960 [<samp>(fe300)</samp>](https://github.com/nuxt-modules/i18n/commit/fe300294)
- Locale prefixes are not added to route aliases &nbsp;-&nbsp; by @BobbieGoede in https://github.com/nuxt-modules/i18n/issues/2962 [<samp>(62236)</samp>](https://github.com/nuxt-modules/i18n/commit/62236964)
- Unable to configure server integration using inline options &nbsp;-&nbsp; by @BobbieGoede in https://github.com/nuxt-modules/i18n/issues/3020 [<samp>(856ba)</samp>](https://github.com/nuxt-modules/i18n/commit/856ba4fc)

##### &nbsp;&nbsp;&nbsp;&nbsp;[View changes on GitHub](https://github.com/nuxt-modules/i18n/compare/v8.3.1...v8.3.2)

# v8.3.1 (2024-04-24T10:05:08Z)

This changelog is generated by [GitHub Releases](https://github.com/nuxt-modules/i18n/releases/tag/v8.3.1)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@nuxtjs/i18n",
"description": "i18n for Nuxt",
"version": "8.3.1",
"version": "8.3.3",
"homepage": "https://i18n.nuxtjs.org",
"bugs": {
"url": "https://github.com/nuxt-community/i18n-module/issues"
Expand Down
3 changes: 3 additions & 0 deletions playground/pages/index.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { computed } from 'vue'
import { type Locale } from 'vue-i18n'

const route = useRoute()
const {
Expand All @@ -18,6 +19,8 @@ const localePath = useLocalePath()
const switchLocalePath = useSwitchLocalePath()
const getRouteBaseName = useRouteBaseName()

switchLocalePath(locale.value)
setLocale(locale.value)
// route.meta.pageTransition.onBeforeEnter = async () => {
// await finalizePendingLocaleChange()
// }
Expand Down
10 changes: 10 additions & 0 deletions specs/experimental/switch_locale_path_link_ssr.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,4 +55,14 @@ describe('experimental.switchLocalePathLinkSSR', async () => {
expect(product2dom.querySelector('#i18n-alt-en').href).toEqual('/products/red-mug')
expect(product2dom.querySelector('#switch-locale-path-link-en').href).toEqual('/products/red-mug')
})

test('encode localized path to prevent XSS', async () => {
const url = `/experimental//"><script>console.log('xss')</script><`

const html = await $fetch(url)
const dom = getDom(html)

// the localized should be the same as encoded
expect(dom.querySelector('#slp-xss a').href).toEqual(encodeURI('/nl' + url))
})
})
2 changes: 1 addition & 1 deletion specs/fixtures/basic_usage/app.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
import { useI18n } from '#i18n'
import { useRuntimeConfig } from 'nuxt/app'
import { useRuntimeConfig } from '#imports'

const { finalizePendingLocaleChange } = useI18n()

Expand Down
8 changes: 8 additions & 0 deletions specs/fixtures/basic_usage/pages/experimental/[...slug].vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<script setup lang="ts"></script>

<template>
<h1>No XSS</h1>
<section id="slp-xss">
<SwitchLocalePathLink locale="nl">Switch to NL</SwitchLocalePathLink>
</section>
</template>
23 changes: 23 additions & 0 deletions specs/fixtures/inline_options/locale-detector.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Detect based on query, cookie, header
export default defineI18nLocaleDetector((event, config) => {
// try to get locale from query
const query = tryQueryLocale(event, { lang: '' }) // disable locale default value with `lang` option
if (query) {
return query.toString()
}

// try to get locale from cookie
const cookie = tryCookieLocale(event, { lang: '', name: 'i18n_locale' }) // disable locale default value with `lang` option
if (cookie) {
return cookie.toString()
}

// try to get locale from header (`accept-header`)
const header = tryHeaderLocale(event, { lang: '' }) // disable locale default value with `lang` option
if (header) {
return header.toString()
}

// If the locale cannot be resolved up to this point, it is resolved with the value `defaultLocale` of the locale config passed to the function
return config.defaultLocale
})
5 changes: 4 additions & 1 deletion specs/fixtures/inline_options/nuxt.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ export default defineNuxtConfig({
file: 'locale-file-en.json',
name: 'English'
}
]
],
experimental: {
localeDetector: './locale-detector.ts'
}
}
]
],
Expand Down
7 changes: 7 additions & 0 deletions specs/fixtures/inline_options/server/api/translate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { useTranslation } from '@intlify/h3'

export default defineEventHandler(async event => {
const t = await useTranslation(event)

return t('home')
})
7 changes: 6 additions & 1 deletion specs/inline_options.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { test, expect, describe } from 'vitest'
import { fileURLToPath } from 'node:url'
import { setup } from './utils'
import { setup, url, $fetch } from './utils'
import { renderPage } from './helper'

await setup({
Expand Down Expand Up @@ -34,4 +34,9 @@ describe('inline options are handled correctly', async () => {
)
).toBe(false)
})

test('(#2721) server integration from inline configuration', async () => {
const res = await $fetch(url('/api/translate'))
expect(res).toEqual('Homepage')
})
})
3 changes: 2 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ export const DEFAULT_OPTIONS = {
cookieSecure: false,
fallbackLocale: '',
redirectOn: 'root',
useCookie: true
useCookie: true,
httpOnly: false,
},
differentDomains: false,
baseUrl: '',
Expand Down
1 change: 1 addition & 0 deletions src/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,7 @@ export default defineNuxtModule<NuxtI18nOptions>({

// for core plugin
addPlugin(resolve(runtimeDir, 'plugins/i18n'))
addPlugin(resolve(runtimeDir, 'plugins/switch-locale-path-ssr'))

// for composables
nuxt.options.alias['#i18n'] = resolve(distDir, 'runtime/composables/index.mjs')
Expand Down
13 changes: 8 additions & 5 deletions src/nitro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { addServerPlugin, createResolver, resolvePath, useLogger } from '@nuxt/k
import yamlPlugin from '@rollup/plugin-yaml'
import json5Plugin from '@miyaneee/rollup-plugin-json5'
import { getFeatureFlags } from './bundler'
import { isExists } from './utils'
import { getLayerI18n, isExists } from './utils'
import {
H3_PKG,
UTILS_H3_PKG,
Expand Down Expand Up @@ -131,13 +131,16 @@ export { localeDetector }
}

async function resolveLocaleDetectorPath(nuxt: Nuxt) {
const serverI18nLayer = nuxt.options._layers.find(
l => l.config.i18n?.experimental?.localeDetector != null && l.config.i18n?.experimental?.localeDetector !== ''
)
const serverI18nLayer = nuxt.options._layers.find(l => {
const layerI18n = getLayerI18n(l)
return layerI18n?.experimental?.localeDetector != null && layerI18n?.experimental?.localeDetector !== ''
})

let enableServerIntegration = serverI18nLayer != null

if (serverI18nLayer != null) {
const pathTo = resolve(serverI18nLayer.config.rootDir, serverI18nLayer.config.i18n!.experimental!.localeDetector!)
const serverI18nLayerConfig = getLayerI18n(serverI18nLayer)
const pathTo = resolve(serverI18nLayer.config.rootDir, serverI18nLayerConfig!.experimental!.localeDetector!)
const localeDetectorPath = await resolvePath(pathTo, {
cwd: nuxt.options.rootDir,
extensions: EXECUTABLE_EXTENSIONS
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/components/SwitchLocalePathLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default defineComponent({

return () => [
h(Comment, `${SWITCH_LOCALE_PATH_LINK_IDENTIFIER}-[${props.locale}]`),
h(NuxtLink, { ...attrs, to: switchLocalePath(props.locale) }, slots.default),
h(NuxtLink, { ...attrs, to: encodeURI(switchLocalePath(props.locale)) }, slots.default),
h(Comment, `/${SWITCH_LOCALE_PATH_LINK_IDENTIFIER}`)
]
}
Expand Down
3 changes: 2 additions & 1 deletion src/runtime/internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,8 @@ export function getI18nCookie() {
expires: new Date(date.setDate(date.getDate() + 365)),
path: '/',
sameSite: detect && detect.cookieCrossOrigin ? 'none' : 'lax',
secure: (detect && detect.cookieCrossOrigin) || (detect && detect.cookieSecure)
secure: (detect && detect.cookieCrossOrigin) || (detect && detect.cookieSecure),
httpOnly: detect && detect.httpOnly ? true : false
}

if (detect && detect.cookieDomain) {
Expand Down
27 changes: 1 addition & 26 deletions src/runtime/plugins/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import {
isSSG,
localeLoaders,
parallelPlugin,
normalizedLocales,
SWITCH_LOCALE_PATH_LINK_IDENTIFIER
normalizedLocales
} from '#build/i18n.options.mjs'
import { loadVueI18nOptions, loadInitialMessages, loadLocale } from '../messages'
import { useSwitchLocalePath } from '../composables'
import {
loadAndSetLocale,
detectLocale,
Expand Down Expand Up @@ -401,29 +399,6 @@ export default defineNuxtPlugin({
// inject for nuxt helpers
injectNuxtHelpers(nuxtContext, i18n)

// Replace `SwitchLocalePathLink` href in rendered html for SSR support
if (runtimeI18n.experimental.switchLocalePathLinkSSR === true) {
const switchLocalePath = useSwitchLocalePath()

const switchLocalePathLinkWrapperExpr = new RegExp(
[
`<!--${SWITCH_LOCALE_PATH_LINK_IDENTIFIER}-\\[(\\w+)\\]-->`,
`.+?`,
`<!--/${SWITCH_LOCALE_PATH_LINK_IDENTIFIER}-->`
].join(''),
'g'
)

nuxt.hook('app:rendered', ctx => {
if (ctx.renderResult?.html == null) return

ctx.renderResult.html = ctx.renderResult.html.replaceAll(
switchLocalePathLinkWrapperExpr,
(match: string, p1: string) => match.replace(/href="([^"]+)"/, `href="${switchLocalePath(p1 ?? '')}"`)
)
})
}

let routeChangeCount = 0

addRouteMiddleware(
Expand Down
33 changes: 33 additions & 0 deletions src/runtime/plugins/switch-locale-path-ssr.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { defineNuxtPlugin } from '#imports'
import { useSwitchLocalePath } from '#i18n'
import { SWITCH_LOCALE_PATH_LINK_IDENTIFIER } from '#build/i18n.options.mjs'

// Replace `SwitchLocalePathLink` href in rendered html for SSR support
export default defineNuxtPlugin({
name: 'i18n:plugin:switch-locale-path-ssr',
dependsOn: ['i18n:plugin'],
setup(nuxt) {
if (nuxt.$config.public.i18n.experimental.switchLocalePathLinkSSR !== true) return

const switchLocalePath = useSwitchLocalePath()

const switchLocalePathLinkWrapperExpr = new RegExp(
[
`<!--${SWITCH_LOCALE_PATH_LINK_IDENTIFIER}-\\[(\\w+)\\]-->`,
`.+?`,
`<!--/${SWITCH_LOCALE_PATH_LINK_IDENTIFIER}-->`
].join(''),
'g'
)

nuxt.hook('app:rendered', ctx => {
if (ctx.renderResult?.html == null) return

ctx.renderResult.html = ctx.renderResult.html.replaceAll(
switchLocalePathLinkWrapperExpr,
(match: string, p1: string) =>
match.replace(/href="([^"]+)"/, `href="${encodeURI(switchLocalePath(p1 ?? ''))}"`)
)
})
}
})
2 changes: 1 addition & 1 deletion src/runtime/routing/extends/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getLocalesRegex } from '../utils'
import { localeCodes } from '#build/i18n.options.mjs'

import type { RouteLocationNormalized, RouteLocationNormalizedLoaded } from 'vue-router'
import { useRuntimeConfig } from 'nuxt/app'
import { useRuntimeConfig } from '#imports'

export function createLocaleFromRouteGetter() {
const { routesNameSeparator, defaultLocaleRouteNameSuffix } = useRuntimeConfig().public.i18n
Expand Down
3 changes: 2 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export interface DetectBrowserLanguageOptions {
cookieSecure?: boolean
fallbackLocale?: Locale | null
redirectOn?: RedirectOnOptions
useCookie?: boolean
useCookie?: boolean,
httpOnly?: boolean
}

export type LocaleType = 'static' | 'dynamic' | 'unknown'
Expand Down