@maestro-js/init 1.0.0-alpha.21 → 1.0.0-alpha.23
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/dist/bin.js +1 -1
- package/dist/{chunk-52NA54G4.js → chunk-FO3IYFDW.js} +74 -15
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -909,34 +909,93 @@ export function getRequestContext(context: Readonly<RouterContextProvider>) {
|
|
|
909
909
|
return request
|
|
910
910
|
}
|
|
911
911
|
`;
|
|
912
|
-
var csrfMiddlewareServer = `import
|
|
912
|
+
var csrfMiddlewareServer = `import { timingSafeEqual } from 'node:crypto'
|
|
913
|
+
import type { MiddlewareFunction } from 'react-router'
|
|
913
914
|
import { getRequestContext } from './request-context.server'
|
|
914
915
|
|
|
916
|
+
const MUTATING_METHODS = new Set(['POST', 'PUT', 'PATCH', 'DELETE'])
|
|
917
|
+
|
|
915
918
|
export const csrfMiddleware: MiddlewareFunction<Response> = async ({ request, context }, next) => {
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
919
|
+
if (!MUTATING_METHODS.has(request.method)) {
|
|
920
|
+
return next()
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
// Routes accepting JSON or other non-form bodies use an X-CSRF-Token header
|
|
924
|
+
// instead of a hidden form field. Either way, never throw 500 from this
|
|
925
|
+
// middleware on a malformed body \u2014 return a clean 403.
|
|
926
|
+
const contentType = request.headers.get('content-type') ?? ''
|
|
927
|
+
let clientCsrfToken: string | null = null
|
|
928
|
+
if (contentType.startsWith('application/x-www-form-urlencoded') || contentType.startsWith('multipart/form-data')) {
|
|
929
|
+
try {
|
|
930
|
+
const cloned = request.clone()
|
|
931
|
+
const formData = await cloned.formData()
|
|
932
|
+
const value = formData.get('__csrfToken')
|
|
933
|
+
clientCsrfToken = typeof value === 'string' ? value : null
|
|
934
|
+
} catch {
|
|
935
|
+
clientCsrfToken = null
|
|
925
936
|
}
|
|
937
|
+
} else {
|
|
938
|
+
clientCsrfToken = request.headers.get('x-csrf-token')
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
const requestContext = getRequestContext(context)
|
|
942
|
+
const sessionCsrfToken = requestContext.session.getCsrfToken()
|
|
943
|
+
|
|
944
|
+
if (!clientCsrfToken || !constantTimeEqual(clientCsrfToken, sessionCsrfToken)) {
|
|
945
|
+
throw new Response('Invalid CSRF token', { status: 403 })
|
|
926
946
|
}
|
|
927
947
|
return next()
|
|
928
948
|
}
|
|
949
|
+
|
|
950
|
+
function constantTimeEqual(a: string, b: string): boolean {
|
|
951
|
+
const aBuf = Buffer.from(a)
|
|
952
|
+
const bBuf = Buffer.from(b)
|
|
953
|
+
if (aBuf.length !== bBuf.length) return false
|
|
954
|
+
return timingSafeEqual(aBuf, bBuf)
|
|
955
|
+
}
|
|
929
956
|
`;
|
|
930
957
|
var securityHeadersMiddlewareServer = `import type { MiddlewareFunction } from 'react-router'
|
|
931
958
|
|
|
959
|
+
// Security-headers middleware must be the OUTERMOST middleware so that headers
|
|
960
|
+
// are applied to thrown Responses (e.g. 403s from csrfMiddleware) as well as
|
|
961
|
+
// returned ones \u2014 try/finally ensures both code paths get the headers.
|
|
932
962
|
export const securityHeadersMiddleware: MiddlewareFunction<Response> = async (_args, next) => {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
963
|
+
let response: Response
|
|
964
|
+
try {
|
|
965
|
+
response = await next()
|
|
966
|
+
} catch (thrown) {
|
|
967
|
+
if (thrown instanceof Response) {
|
|
968
|
+
applySecurityHeaders(thrown.headers)
|
|
969
|
+
}
|
|
970
|
+
throw thrown
|
|
971
|
+
}
|
|
972
|
+
applySecurityHeaders(response.headers)
|
|
938
973
|
return response
|
|
939
974
|
}
|
|
975
|
+
|
|
976
|
+
function applySecurityHeaders(headers: Headers) {
|
|
977
|
+
headers.set('Strict-Transport-Security', 'max-age=63072000; includeSubDomains; preload')
|
|
978
|
+
headers.set('X-Content-Type-Options', 'nosniff')
|
|
979
|
+
headers.set('X-Frame-Options', 'DENY')
|
|
980
|
+
headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
|
|
981
|
+
// Default-deny CSP. Tighten with route-level nonces for inline <script>/<style>.
|
|
982
|
+
// Apps that need third-party origins (analytics, fonts, CDNs) should extend
|
|
983
|
+
// this list \u2014 ship something restrictive by default rather than leaving it
|
|
984
|
+
// off entirely.
|
|
985
|
+
if (!headers.has('Content-Security-Policy')) {
|
|
986
|
+
headers.set(
|
|
987
|
+
'Content-Security-Policy',
|
|
988
|
+
"default-src 'self'; img-src 'self' data: https:; style-src 'self' 'unsafe-inline'; " +
|
|
989
|
+
"script-src 'self'; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
|
|
990
|
+
)
|
|
991
|
+
}
|
|
992
|
+
headers.set(
|
|
993
|
+
'Permissions-Policy',
|
|
994
|
+
'camera=(), microphone=(), geolocation=(), payment=(), usb=(), accelerometer=(), gyroscope=(), magnetometer=()'
|
|
995
|
+
)
|
|
996
|
+
headers.set('Cross-Origin-Opener-Policy', 'same-origin')
|
|
997
|
+
headers.set('Cross-Origin-Resource-Policy', 'same-origin')
|
|
998
|
+
}
|
|
940
999
|
`;
|
|
941
1000
|
var customErrorStatusCodeMiddlewareServer = `import * as Sentry from '@sentry/react-router'
|
|
942
1001
|
import { type MiddlewareFunction } from 'react-router'
|
package/dist/index.js
CHANGED