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.
This commit is contained in:
committed by
GitHub
parent
1327a8efb7
commit
e193c94f06
@@ -1,11 +1,31 @@
|
||||
import { getLogger } from '@kit/shared/logger';
|
||||
|
||||
/**
|
||||
* Resolves the translation file for a given language and namespace.
|
||||
*
|
||||
* @name i18nResolver
|
||||
* @description Resolve the translation file for the given language and namespace in the current application.
|
||||
* @param language
|
||||
* @param namespace
|
||||
*/
|
||||
export async function i18nResolver(language: string, namespace: string) {
|
||||
const data = await import(
|
||||
`../../public/locales/${language}/${namespace}.json`
|
||||
);
|
||||
const logger = await getLogger();
|
||||
|
||||
return data as Record<string, string>;
|
||||
try {
|
||||
const data = await import(
|
||||
`../../public/locales/${language}/${namespace}.json`
|
||||
);
|
||||
|
||||
return data as Record<string, string>;
|
||||
} catch (error) {
|
||||
console.group(
|
||||
`Error while loading translation file: ${language}/${namespace}`,
|
||||
);
|
||||
logger.error(error instanceof Error ? error.message : error);
|
||||
logger.warn(
|
||||
`Please create a translation file for this language at "public/locales/${language}/${namespace}.json"`,
|
||||
);
|
||||
console.groupEnd();
|
||||
|
||||
// return an empty object if the file could not be loaded to avoid loops
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
import 'server-only';
|
||||
|
||||
import { cache } from 'react';
|
||||
|
||||
import { cookies, headers } from 'next/headers';
|
||||
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
initializeServerI18n,
|
||||
parseAcceptLanguageHeader,
|
||||
@@ -32,13 +36,13 @@ const priority = featuresFlagConfig.languagePriority;
|
||||
*/
|
||||
async function createInstance() {
|
||||
const cookieStore = await cookies();
|
||||
const cookie = cookieStore.get(I18N_COOKIE_NAME)?.value;
|
||||
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 (cookie) {
|
||||
selectedLanguage = getLanguageOrFallback(cookie);
|
||||
if (langCookieValue) {
|
||||
selectedLanguage = getLanguageOrFallback(langCookieValue);
|
||||
}
|
||||
|
||||
// if not, check if the language priority is set to user and
|
||||
@@ -56,10 +60,15 @@ async function createInstance() {
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -67,16 +76,23 @@ async function getPreferredLanguageFromBrowser() {
|
||||
return parseAcceptLanguageHeader(acceptLanguage, languages)[0];
|
||||
}
|
||||
|
||||
function getLanguageOrFallback(language: string | undefined) {
|
||||
let selectedLanguage = language;
|
||||
/**
|
||||
* @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 (!languages.includes(language ?? '')) {
|
||||
console.warn(
|
||||
`Language "${language}" is not supported. Falling back to "${languages[0]}"`,
|
||||
);
|
||||
|
||||
selectedLanguage = languages[0];
|
||||
if (language.success) {
|
||||
return language.data;
|
||||
}
|
||||
|
||||
return selectedLanguage;
|
||||
console.warn(
|
||||
`The language passed is invalid. Defaulted back to "${languages[0]}"`,
|
||||
);
|
||||
|
||||
return languages[0];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
type Theme = 'light' | 'dark' | 'system';
|
||||
import { z } from 'zod';
|
||||
|
||||
/**
|
||||
* @name Theme
|
||||
* @description The theme mode enum.
|
||||
*/
|
||||
const Theme = z.enum(['light', 'dark', 'system'], {
|
||||
description: 'The theme mode',
|
||||
});
|
||||
|
||||
/**
|
||||
* @name appDefaultThemeMode
|
||||
* @description The default theme mode set by the application.
|
||||
*/
|
||||
const appDefaultThemeMode = Theme.safeParse(
|
||||
process.env.NEXT_PUBLIC_DEFAULT_THEME_MODE,
|
||||
);
|
||||
|
||||
/**
|
||||
* @name fallbackThemeMode
|
||||
* @description The fallback theme mode if none of the other options are available.
|
||||
*/
|
||||
const fallbackThemeMode = `light`;
|
||||
|
||||
/**
|
||||
* @name getRootTheme
|
||||
@@ -9,8 +31,19 @@ type Theme = 'light' | 'dark' | 'system';
|
||||
*/
|
||||
export async function getRootTheme() {
|
||||
const cookiesStore = await cookies();
|
||||
const themeCookieValue = cookiesStore.get('theme')?.value;
|
||||
const theme = Theme.safeParse(themeCookieValue);
|
||||
|
||||
const themeCookie = cookiesStore.get('theme')?.value as Theme;
|
||||
// pass the theme from the cookie if it exists
|
||||
if (theme.success) {
|
||||
return theme.data;
|
||||
}
|
||||
|
||||
return themeCookie ?? process.env.NEXT_PUBLIC_DEFAULT_THEME_MODE ?? 'light';
|
||||
// pass the default theme from the environment variable if it exists
|
||||
if (appDefaultThemeMode.success) {
|
||||
return appDefaultThemeMode.data;
|
||||
}
|
||||
|
||||
// in all other cases, fallback to the default theme
|
||||
return fallbackThemeMode;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user