Files
myeasycms-v2/apps/web/lib/i18n/i18n.server.ts
Giancarlo Buomprisco e193c94f06 Cookies validation and Security Guidelines (#242)
* Add OTP and security guidelines documentation and additional checks on client-provided values

- Introduced additional checks on client-provided values such as cookies
- Introduced a new OTP API documentation outlining the creation and verification of OTP tokens for sensitive operations.
- Added comprehensive security guidelines for writing secure code in Next.js, covering client and server components, environment variables, authentication, and error handling.

These additions enhance the project's security posture and provide clear instructions for developers on implementing secure practices.

* Add OTP API documentation and enhance security guidelines

- Introduced comprehensive documentation for the OTP API, detailing the creation and verification of OTP tokens for sensitive operations.
- Enhanced security guidelines for Next.js, emphasizing the importance of input validation, environment variable management, and error handling.
- Implemented additional checks for client-provided values to improve overall security posture.

These updates provide clear instructions for developers and strengthen the project's security framework.
2025-04-22 06:44:55 +08:00

99 lines
2.6 KiB
TypeScript

import 'server-only';
import { cache } from 'react';
import { cookies, headers } from 'next/headers';
import { z } from 'zod';
import {
initializeServerI18n,
parseAcceptLanguageHeader,
} from '@kit/i18n/server';
import featuresFlagConfig from '~/config/feature-flags.config';
import {
I18N_COOKIE_NAME,
getI18nSettings,
languages,
} from '~/lib/i18n/i18n.settings';
import { i18nResolver } from './i18n.resolver';
/**
* @name priority
* @description The language priority setting from the feature flag configuration.
*/
const priority = featuresFlagConfig.languagePriority;
/**
* @name createI18nServerInstance
* @description Creates an instance of the i18n server.
* It uses the language from the cookie if it exists, otherwise it uses the language from the accept-language header.
* If neither is available, it will default to the provided environment variable.
*
* Initialize the i18n instance for every RSC server request (eg. each page/layout)
*/
async function createInstance() {
const cookieStore = await cookies();
const langCookieValue = cookieStore.get(I18N_COOKIE_NAME)?.value;
let selectedLanguage: string | undefined = undefined;
// if the cookie is set, use the language from the cookie
if (langCookieValue) {
selectedLanguage = getLanguageOrFallback(langCookieValue);
}
// if not, check if the language priority is set to user and
// use the user's preferred language
if (!selectedLanguage && priority === 'user') {
const userPreferredLanguage = await getPreferredLanguageFromBrowser();
selectedLanguage = getLanguageOrFallback(userPreferredLanguage);
}
const settings = getI18nSettings(selectedLanguage);
return initializeServerI18n(settings, i18nResolver);
}
export const createI18nServerInstance = cache(createInstance);
/**
* @name getPreferredLanguageFromBrowser
* Get the user's preferred language from the accept-language header.
*/
async function getPreferredLanguageFromBrowser() {
const headersStore = await headers();
const acceptLanguage = headersStore.get('accept-language');
// no accept-language header, return
if (!acceptLanguage) {
return;
}
return parseAcceptLanguageHeader(acceptLanguage, languages)[0];
}
/**
* @name getLanguageOrFallback
* Get the language or fallback to the default language.
* @param selectedLanguage
*/
function getLanguageOrFallback(selectedLanguage: string | undefined) {
const language = z
.enum(languages as [string, ...string[]])
.safeParse(selectedLanguage);
if (language.success) {
return language.data;
}
console.warn(
`The language passed is invalid. Defaulted back to "${languages[0]}"`,
);
return languages[0];
}