I am trying to implement strict-dynamic CSP rules for my nextjs application. I want a new nonce value at every app load, but it is not happening. Currently a value is set for every build. Since I need the nonce value to set headers for the web pages, I need it in next.config.js.
Please let me know what I'm doing wrong.
next.config.js
const { v4 } = require('uuid');
const { createSecureHeaders } = require("next-secure-headers");
const crypto = require('crypto');
const { PHASE_DEVELOPMENT_SERVER } = require("next/constants");
const generatedNonce = function nonceGenerator() {
const hash = crypto.createHash('sha256');
hash.update(v4());
return hash.digest('base64');
}();
module.exports = function nextConfig(phase, { defaultConfig }) {
return {
publicRuntimeConfig: {
generatedNonce
},
headers: async () => {
return [
{
source: "/(.*)?",
headers: createSecureHeaders({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'none"],
scriptSrc: [
...(phase === PHASE_DEVELOPMENT_SERVER ? ["'unsafe-eval'"] : []),
`'nonce-${generatedNonce}'`,
"'strict-dynamic'",
],
},
},
nosniff: "nosniff",
referrerPolicy: "no-referrer",
frameGuard: "sameorigin",
})
}
]
}
};
}
Related
I am trying to follow this tutorial:
I am stuck at step 3, which is where the server is defined as follows:
import { createServer } from "#graphql-yoga/node";
import { join } from "path";
import { readFileSync } from "fs";
const typeDefs = readFileSync(join(process.cwd(), "schema.graphql"), {
encoding: "utf-8",
});
const resolvers = {
Query: {
cart: (_, { id }) => {
return {
id,
totalItems: 0,
};
},
},
};
const server = createServer({
cors: false,
endpoint: "/api",
logging: {
prettyLog: false,
},
schema: {
typeDefs,
resolvers,
},
});
export default server;
When I try to use that definition and start the local host, I get an error that says:
TypeError: (0 ,
graphql_yoga_node__WEBPACK_IMPORTED_MODULE_0_.createServer) is not a function at eval
Can anyone see if this tutorial is now out of date. I can see that I am using next v 13.1.1 and the tutorial uses v12. I've been having an awful time trying to find an explanation of how to use these packages, in their current formats. Is this one now out of date?
Can anyone see how to define a server for next v13?
There are some change between graph-yoga v2 and v3, you can look this tutorial to solve it.
For others that might be stuck, this might be a way to define the schema using graphql-yoga (now instead of graphql-yoga/node)
import { createSchema, createYoga } from 'graphql-yoga'
import { createServer } from 'node:http'
import { join } from "path";
import { readFileSync } from "fs";
const typeDefs = readFileSync(join(process.cwd(), "schema.graphql"), {
encoding: "utf-8",
});
const resolvers = {
Query: {
cart: (_, { id }) => {
return {
id,
totalItems: 0,
};
},
},
};
const yoga = createYoga({
cors: false,
endpoint: "/api",
logging: {
prettyLog: false,
},
schema: createSchema({
typeDefs,
resolvers,
}),
});
const server = createServer(yoga);
server.listen(3000, () => {
console.info('Server is running on http://localhost:3000/graphql')
});
export default server;
I'm using Prismic and NextJS for the first time.
What I'm trying to is make it so when the user goes to the base url for the page localhost:3000/ in dev something will load. /About and /Pricing are working fine the base url doesn't work.
import { GetStaticPaths, GetStaticProps } from 'next'
import { SliceZone } from '#prismicio/react'
import * as prismicH from "#prismicio/helpers";
import { createClient, linkResolver } from '../../prismicio'
import { components } from '../../slices'
interface HomePageProps {
page: any
}
const HomePage = (props:HomePageProps) => {
return <SliceZone slices={props.page.data.slices} components={components} />
}
export default HomePage
interface HomePageStaticProps {
params: any
previewData:any
}
export async function getStaticProps(props:HomePageStaticProps) {
console.log("DOES NOT FIRE FOR localhost:3000")
const client = createClient({ previewData:props.previewData })
const params = props.params;
const uid = params?.pagePath?.[params.pagePath.length - 1] || "home";
const page = await client.getByUID("page", uid);
return {
props: {
page,
},
}
}
export const getStaticPaths: GetStaticPaths = async () => {
const client = createClient();
const pages = await client.getAllByType("page");
const paths = pages.map((page) => prismicH.asLink(page, linkResolver)) as string[];
console.log(paths) // [ '/pricing', '/about', '/' ]
return {
paths,
fallback: false,
};
}
or to simplify it further
[[...pagePath]].tsx fails when going to localhost:3000/ but does not fail on localhost:3000/about or localhost:3000/pricing.
import { GetStaticPaths, GetStaticProps } from 'next'
interface HomePageProps {
page: string
}
const HomePage = (props:HomePageProps) => {
return <>{props.page}</>
}
export default HomePage
interface HomePageStaticProps {
params: any
previewData:any
}
export async function getStaticProps(props:HomePageStaticProps) {
const params = props.params;
const uid = params?.pagePath?.[params.pagePath.length - 1] || "home";
//const page = await client.getByUID("page", uid);
return {
props: {
page:uid,
},
}
}
export const getStaticPaths: GetStaticPaths = async () => {
const paths = [ '/pricing', '/about', '/' ];
return {
paths,
fallback: false,
};
}
As far as I can see your'e not fetching the right way. In order to to have a clean project I would recommend to use a const var where you can determine between dev and production enviorenment. To do so you can simply create a file for example: constants.js containing the following:
export const baseurl = process.env.NODE_ENV === "production"
? process.env.NEXT_PUBLIC_DOMAIN // <-- your domain on live
: "http://localhost:3000"; // localhost on dev
Now with this you automatically have localhost on your dev. Notice that you need http:// which your'e missing at the moment. Now the next example shows you how to fetch something on your root / by entering the following code:
import { baseurl } from "../utils/constants"; // <-- importing the constant
// This function gets called at build time on server-side.
// It won't be called on client-side, so you can even do
// direct database queries.
export async function getStaticProps() {
// Call an external API endpoint to get posts.
// You can use any data fetching library
const res = await fetch(`${baseurl}/api/posts`)
const posts = await res.json()
// By returning { props: { posts } }, the Blog component
// will receive `posts` as a prop at build time
return {
props: {
posts,
},
}
}
If you are using Create-T3-App
https://github.com/t3-oss/create-t3-app
then
your next.config.mjs will default to this as of Nov 7, 2022
const config = {
reactStrictMode: true,
swcMinify: true,
i18n: {
locales: ["en"],
defaultLocale: "en",
},
};
export default config;
remove
const config = {
reactStrictMode: true,
swcMinify: true,
//i18n: {
// locales: ["en"],
// defaultLocale: "en",
//},
};
export default config;
This will make default "/" pathing work, if you require i18n, I'm sorry I can't help.
I want to send Etag.
export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
const targetIndex =
req.rawHeaders.findIndex((val) => val === "If-None-Match") + 1;
const EtagVal = req.rawHeaders[targetIndex];
res.setHeader("Cache-Control", "public, max-age=0, must-revalidate");
res.setHeader("ETag", "aaaa");
return {
props: {},
};
};
This Works locally. build & next start..
It does not work after vercel deployment.
I tried configure next.config.js like this
const nextConfig = {
reactStrictMode: true,
async headers() {
return [
{
source: '/:path*',
"headers" : [
{
"key" : "ETag",
},
{
"key" : "If-None-Match",
}
]
}
]
}
}
Unlike the document that any value is allowed if value is not defined,
An error occurs during the build phase.
A noticed that onUploadProgress from axios are not being called at all on my Nuxt.js Project. After some debugging I found out it is related to the "#nuxtjs/pwa" and "#nuxtjs/firebase" modules. I use firebase auth, and the PWA module uses a service worker to take care of SSR auth and inject the auth token on outgoing requests.
This modules are interfering somehow on the axios onUploadProgress. I use axios to upload files to other Apis.
Once I remove the "#nuxtjs/pwa" module the onUploadProgress from axios gets called normally.
Does anyone have an idea how to fix that?
The versions of the modules:
"#nuxtjs/axios": "^5.13.6",
"#nuxtjs/firebase": "^7.6.1",
"#nuxtjs/pwa": "^3.3.5",
nuxt.config.js
firebase: {
....
services: {
auth: {
ssr: true,
persistence: 'local',
initialize: {
onAuthStateChangedAction: 'auth/onAuthStateChangedAction',
subscribeManually: false,
},
},
firestore: true,
functions: true,
},
}
pwa: {
meta: false,
icon: false,
workbox: {
importScripts: ['/firebase-auth-sw.js'],
dev: process.env.NODE_ENV === 'development',
},
},
Axios upload
const asset = await $axios.post(uploadUrl, formData, {
headers: { 'Content-Type': 'multipart/form-data' },
onUploadProgress: (progressEvent) => {
console.log('onUploadProgress');
const prog = parseInt(
Math.round((progressEvent.loaded / progressEvent.total) * 100)
);
progress(prog);
},
});
the console.log isn't called at all.
firebase-auth-sw
const ignorePaths = ["\u002F__webpack_hmr","\u002F_loading","\u002F_nuxt\u002F"]
importScripts(
'https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js'
)
importScripts(
'https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js'
)
firebase.initializeApp({"apiKey":"AIzaSyDUjfwaCRNG72CaPznknOfbNLySkFQvfrs","authDomain":"j-a-developer-web-site.firebaseapp.com","projectId":"j-a-developer-web-site","storageBucket":"j-a-developer-web-site.appspot.com","messagingSenderId":"393360816421","appId":"1:393360816421:web:75c43cac27032d924502cc"})
// Initialize authService
const authService = firebase.auth()
/**
* Returns a promise that resolves with an ID token if available.
* #return {!Promise<?string>} The promise that resolves with an ID token if
* available. Otherwise, the promise resolves with null.
*/
const getIdToken = () => {
return new Promise((resolve) => {
const unsubscribe = authService.onAuthStateChanged((user) => {
unsubscribe()
if (user) {
// force token refresh as it might be used to sign in server side
user.getIdToken(true).then((idToken) => {
resolve(idToken)
}, () => {
resolve(null)
})
} else {
resolve(null)
}
})
})
}
const fetchWithAuthorization = async (original, idToken) => {
// Clone headers as request headers are immutable.
const headers = new Headers()
for (let entry of original.headers.entries()) {
headers.append(entry[0], entry[1])
}
// Add ID token to header.
headers.append('Authorization', 'Bearer ' + idToken)
// Create authorized request
const { url, ...props } = original.clone()
const authorized = new Request(url, {
...props,
mode: 'same-origin',
redirect: 'manual',
headers
})
return fetch(authorized)
}
self.addEventListener('fetch', (event) => {
const url = new URL(event.request.url)
const expectsHTML = event.request.headers.get('accept').includes('text/html')
const isSameOrigin = self.location.origin === url.origin
const isHttps = (self.location.protocol === 'https:' || self.location.hostname === 'localhost' || self.location.hostname === '127.0.0.1')
const isIgnored = ignorePaths.some(path => {
if (typeof path === 'string') {
return url.pathname.startsWith(path)
}
return path.test(url.pathname.slice(1))
})
// https://github.com/nuxt-community/firebase-module/issues/465
if (!expectsHTML || !isSameOrigin || !isHttps || isIgnored) {
event.respondWith(fetch(event.request))
return
}
// Fetch the resource after checking for the ID token.
// This can also be integrated with existing logic to serve cached files
// in offline mode.
event.respondWith(
getIdToken().then(
idToken => idToken
// if the token was retrieved we attempt an authorized fetch
// if anything goes wrong we fall back to the original request
? fetchWithAuthorization(event.request, idToken).catch(() => fetch(event.request))
// otherwise we return a fetch of the original request directly
: fetch(event.request)
)
)
})
// In service worker script.
self.addEventListener('activate', event => {
event.waitUntil(clients.claim())
})
sw
const options = {"workboxURL":"https://cdn.jsdelivr.net/npm/workbox-cdn#5.1.4/workbox/workbox-sw.js","importScripts":["/firebase-auth-sw.js"],"config":{"debug":true},"cacheOptions":{"cacheId":"J.A-Developer-Web-Site-dev","directoryIndex":"/","revision":"qIA7lTEhJ6Mk"},"clientsClaim":true,"skipWaiting":true,"cleanupOutdatedCaches":true,"offlineAnalytics":false,"preCaching":[{"revision":"qIA7lTEhJ6Mk","url":"/?standalone=true"}],"runtimeCaching":[{"urlPattern":"/_nuxt/","handler":"NetworkFirst","method":"GET","strategyPlugins":[]},{"urlPattern":"/","handler":"NetworkFirst","method":"GET","strategyPlugins":[]}],"offlinePage":null,"pagesURLPattern":"/","offlineStrategy":"NetworkFirst"}
importScripts(...[options.workboxURL, ...options.importScripts])
initWorkbox(workbox, options)
workboxExtensions(workbox, options)
precacheAssets(workbox, options)
cachingExtensions(workbox, options)
runtimeCaching(workbox, options)
offlinePage(workbox, options)
routingExtensions(workbox, options)
function getProp(obj, prop) {
return prop.split('.').reduce((p, c) => p[c], obj)
}
function initWorkbox(workbox, options) {
if (options.config) {
// Set workbox config
workbox.setConfig(options.config)
}
if (options.cacheNames) {
// Set workbox cache names
workbox.core.setCacheNameDetails(options.cacheNames)
}
if (options.clientsClaim) {
// Start controlling any existing clients as soon as it activates
workbox.core.clientsClaim()
}
if (options.skipWaiting) {
workbox.core.skipWaiting()
}
if (options.cleanupOutdatedCaches) {
workbox.precaching.cleanupOutdatedCaches()
}
if (options.offlineAnalytics) {
// Enable offline Google Analytics tracking
workbox.googleAnalytics.initialize()
}
}
function precacheAssets(workbox, options) {
if (options.preCaching.length) {
workbox.precaching.precacheAndRoute(options.preCaching, options.cacheOptions)
}
}
function runtimeCaching(workbox, options) {
const requestInterceptor = {
requestWillFetch({ request }) {
if (request.cache === 'only-if-cached' && request.mode === 'no-cors') {
return new Request(request.url, { ...request, cache: 'default', mode: 'no-cors' })
}
return request
},
fetchDidFail(ctx) {
ctx.error.message =
'[workbox] Network request for ' + ctx.request.url + ' threw an error: ' + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
},
handlerDidError(ctx) {
ctx.error.message =
`[workbox] Network handler threw an error: ` + ctx.error.message
console.error(ctx.error, 'Details:', ctx)
return null
}
}
for (const entry of options.runtimeCaching) {
const urlPattern = new RegExp(entry.urlPattern)
const method = entry.method || 'GET'
const plugins = (entry.strategyPlugins || [])
.map(p => new (getProp(workbox, p.use))(...p.config))
plugins.unshift(requestInterceptor)
const strategyOptions = { ...entry.strategyOptions, plugins }
const strategy = new workbox.strategies[entry.handler](strategyOptions)
workbox.routing.registerRoute(urlPattern, strategy, method)
}
}
function offlinePage(workbox, options) {
if (options.offlinePage) {
// Register router handler for offlinePage
workbox.routing.registerRoute(new RegExp(options.pagesURLPattern), ({ request, event }) => {
const strategy = new workbox.strategies[options.offlineStrategy]
return strategy
.handle({ request, event })
.catch(() => caches.match(options.offlinePage))
})
}
}
function workboxExtensions(workbox, options) {
}
function cachingExtensions(workbox, options) {
}
function routingExtensions(workbox, options) {
}
store's index.js
export const state = () => ({});
export const actions = {
async nuxtServerInit({ dispatch, commit }, { res }) {
// initialize the store with user if already authenticated
if (res && res.locals && res.locals.user) {
const {
allClaims: claims,
idToken: token,
...authUser
} = res.locals.user;
await dispatch('auth/onAuthStateChangedAction', {
authUser,
claims,
token,
});
}
},
};
We're using Next.js and want to route all paths (not just root) to locale-based paths based on the browser Accept-Language header. However, if the user SETS their region, we will set a cookie that would need to be checked first to respect user preferences.
So we need to check for the cookie, and if it's not there, try redirect based on browser language header instead. We're using ISG so limited to next.config.js redirects serverside.
According to the docs, this should work, but since we're using ISG, we need to do this in next.config.js redirects function.
We've tried this solution and it does not work (we get infinite redirects as both cookie AND header match):
const { i18n } = require('./next-i18next.config');
const withTM = require('next-transpile-modules')(['fitty', 'react-svg']); // pass the modules you would like to see transpiled
const handleLocaleRedirects = (path) => {
const result = [];
i18n.locales.forEach((locale) => {
i18n.locales.forEach((loc) => {
if (loc !== locale) {
result.push({
source: `/${locale}${path}`,
has: [
{
type: 'header',
key: 'accept-language',
value: `^${loc}(.*)`,
},
],
permanent: false,
locale: false,
destination: `/${loc}${path}`,
});
result.push({
source: `/${locale}${path}`,
has: [
{
type: 'cookie',
key: 'NEXT_LOCALE',
value: loc,
},
],
permanent: true,
locale: false,
destination: `/${loc}${path}`,
});
}
});
});
return result;
};
module.exports = withTM({
i18n,
reactStrictMode: true,
images: {
domains: [
'dxjnh2froe2ec.cloudfront.net',
'starsona-stb-usea1.s3.amazonaws.com',
],
},
eslint: {
// Warning: Dangerously allow production builds to successfully complete even if
// your project has ESLint errors.
ignoreDuringBuilds: true,
},
async redirects() {
return [...handleLocaleRedirects('/:celebrityId')];
},
});
I've managed to achieve this using _app.js
Add getInitialProps inside _app.js
It checks cookie inside request, gets current locale using ctx.locale,
My default locale is en-IN so if targetLocale matches default locale it sets an empty string to targetLocale, then redirects using header.
Other than that we don't have to use localeDetection because we are handling on our own.
MyApp.getInitialProps = async ({ ctx }) => {
if (ctx.req) {
const rawCookies = ctx.req.headers.cookie
let locale = ctx.locale
const path = ctx.asPath
if (rawCookies != undefined) {
const cookies = cookie.parse(rawCookies)
let targetLocale = cookies['NEXT_LOCALE']
if (targetLocale != locale) {
if (targetLocale == 'en-IN') {
targetLocale = ''
} else {
targetLocale = '/' + targetLocale
}
ctx.res.writeHead(302, {
Location: `${targetLocale}${path}`
})
ctx.res.end()
}
}
}
return {}
}
Other than this, I'm showing modal when there is no cookie named NEXT_LOCALE to handle first time users.