зеркало из https://github.com/mozilla/fxa.git
Merge pull request #17550 from mozilla/fxa-9998-add-sentry-payments
feat(next): add sentry to payments-next
This commit is contained in:
Коммит
73bd174c9d
|
@ -177,3 +177,6 @@ tmp
|
||||||
# shared-cms
|
# shared-cms
|
||||||
libs/shared/cms/src/__generated__/graphql.d.ts
|
libs/shared/cms/src/__generated__/graphql.d.ts
|
||||||
libs/shared/cms/src/__generated__/graphql.js
|
libs/shared/cms/src/__generated__/graphql.js
|
||||||
|
|
||||||
|
# Sentry Config File
|
||||||
|
.env.sentry-build-plugin
|
||||||
|
|
|
@ -79,5 +79,18 @@ STATS_D_CONFIG__PREFIX=
|
||||||
CSP__ACCOUNTS_STATIC_CDN=https://accounts-static.cdn.mozilla.net
|
CSP__ACCOUNTS_STATIC_CDN=https://accounts-static.cdn.mozilla.net
|
||||||
CSP__PAYPAL_API='https://www.sandbox.paypal.com'
|
CSP__PAYPAL_API='https://www.sandbox.paypal.com'
|
||||||
|
|
||||||
|
# Sentry Config
|
||||||
|
SENTRY__SERVER_NAME=fxa-payments-next-server
|
||||||
|
SENTRY__AUTH_TOKEN=
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
CONTENT_SERVER_URL=http://localhost:3030
|
CONTENT_SERVER_URL=http://localhost:3030
|
||||||
|
|
||||||
|
# Nextjs Public Environment Variables
|
||||||
|
|
||||||
|
# Sentry Config
|
||||||
|
NEXT_PUBLIC_SENTRY_DSN=
|
||||||
|
NEXT_PUBLIC_SENTRY_ENV=local
|
||||||
|
NEXT_PUBLIC_SENTRY_CLIENT_NAME=fxa-payments-next-client
|
||||||
|
NEXT_PUBLIC_SENTRY_SAMPLE_RATE=1
|
||||||
|
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=1
|
||||||
|
|
|
@ -75,5 +75,18 @@ STATS_D_CONFIG__PREFIX=
|
||||||
CSP__ACCOUNTS_STATIC_CDN=https://accounts-static.cdn.mozilla.net
|
CSP__ACCOUNTS_STATIC_CDN=https://accounts-static.cdn.mozilla.net
|
||||||
CSP__PAYPAL_API='https://www.paypal.com'
|
CSP__PAYPAL_API='https://www.paypal.com'
|
||||||
|
|
||||||
|
# Sentry Config
|
||||||
|
SENTRY__SERVER_NAME=fxa-payments-next-server
|
||||||
|
SENTRY__AUTH_TOKEN=
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
CONTENT_SERVER_URL=https://accounts.firefox.com
|
CONTENT_SERVER_URL=https://accounts.firefox.com
|
||||||
|
|
||||||
|
# Nextjs Public Environment Variables
|
||||||
|
|
||||||
|
# Sentry Config
|
||||||
|
NEXT_PUBLIC_SENTRY_DSN=
|
||||||
|
NEXT_PUBLIC_SENTRY_ENV=prod
|
||||||
|
NEXT_PUBLIC_SENTRY_CLIENT_NAME=fxa-payments-next-client
|
||||||
|
NEXT_PUBLIC_SENTRY_SAMPLE_RATE=1
|
||||||
|
NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE=1
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
import NextError from 'next/error';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
|
||||||
|
export default function GlobalError({
|
||||||
|
error,
|
||||||
|
}: {
|
||||||
|
error: Error & { digest?: string };
|
||||||
|
}) {
|
||||||
|
useEffect(() => {
|
||||||
|
Sentry.captureException(error);
|
||||||
|
}, [error]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
{/* `NextError` is the default Next.js error page component. Its type
|
||||||
|
definition requires a `statusCode` prop. However, since the App Router
|
||||||
|
does not expose status codes for errors, we simply pass 0 to render a
|
||||||
|
generic error message. */}
|
||||||
|
<NextError statusCode={0} />
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
);
|
||||||
|
}
|
|
@ -1,7 +1,18 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import 'reflect-metadata';
|
import 'reflect-metadata';
|
||||||
import 'server-only';
|
import 'server-only';
|
||||||
import { Type } from 'class-transformer';
|
import { Type } from 'class-transformer';
|
||||||
import { IsString, ValidateNested, IsDefined, IsUrl } from 'class-validator';
|
import {
|
||||||
|
IsString,
|
||||||
|
ValidateNested,
|
||||||
|
IsDefined,
|
||||||
|
IsUrl,
|
||||||
|
IsNumber,
|
||||||
|
IsOptional,
|
||||||
|
} from 'class-validator';
|
||||||
import {
|
import {
|
||||||
RootConfig as NestAppRootConfig,
|
RootConfig as NestAppRootConfig,
|
||||||
validate,
|
validate,
|
||||||
|
@ -20,6 +31,14 @@ class PaypalConfig {
|
||||||
clientId!: string;
|
clientId!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class SentryServerConfig {
|
||||||
|
@IsString()
|
||||||
|
serverName!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
authToken!: string;
|
||||||
|
}
|
||||||
|
|
||||||
class AuthJSConfig {
|
class AuthJSConfig {
|
||||||
@IsUrl({ require_tld: false })
|
@IsUrl({ require_tld: false })
|
||||||
issuerUrl!: string;
|
issuerUrl!: string;
|
||||||
|
@ -47,6 +66,11 @@ export class PaymentsNextConfig extends NestAppRootConfig {
|
||||||
@IsDefined()
|
@IsDefined()
|
||||||
csp!: CspConfig;
|
csp!: CspConfig;
|
||||||
|
|
||||||
|
@Type(() => SentryServerConfig)
|
||||||
|
@ValidateNested()
|
||||||
|
@IsDefined()
|
||||||
|
sentry!: SentryServerConfig;
|
||||||
|
|
||||||
@IsString()
|
@IsString()
|
||||||
authSecret!: string;
|
authSecret!: string;
|
||||||
|
|
||||||
|
@ -55,6 +79,29 @@ export class PaymentsNextConfig extends NestAppRootConfig {
|
||||||
|
|
||||||
@IsUrl({ require_tld: false })
|
@IsUrl({ require_tld: false })
|
||||||
contentServerUrl!: string;
|
contentServerUrl!: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Nextjs Public Environment Variables
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sentry Config
|
||||||
|
*/
|
||||||
|
@IsOptional()
|
||||||
|
@IsString()
|
||||||
|
nextPublicSentryDsn?: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
nextPublicSentryEnv!: string;
|
||||||
|
|
||||||
|
@IsString()
|
||||||
|
nextPublicSentryClientName!: string;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
nextPublicSentrySampleRate!: number;
|
||||||
|
|
||||||
|
@IsNumber()
|
||||||
|
nextPublicSentryTracesSampleRate!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const config = validate(process.env, PaymentsNextConfig);
|
export const config = validate(process.env, PaymentsNextConfig);
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
export async function register() {
|
export async function register() {
|
||||||
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
if (process.env.NEXT_RUNTIME === 'nodejs') {
|
||||||
|
await import('./sentry.server.config');
|
||||||
const { getApp } = await import('@fxa/payments/ui/server');
|
const { getApp } = await import('@fxa/payments/ui/server');
|
||||||
|
|
||||||
await getApp().initialize();
|
await getApp().initialize();
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const { composePlugins, withNx } = require('@nx/next');
|
const { composePlugins, withNx } = require('@nx/next');
|
||||||
|
const { withSentryConfig } = require('@sentry/nextjs');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
|
* @type {import('@nx/next/plugins/with-nx').WithNxOptions}
|
||||||
|
@ -22,6 +23,8 @@ const nextConfig = {
|
||||||
'@nestjs/core',
|
'@nestjs/core',
|
||||||
'@nestjs/common',
|
'@nestjs/common',
|
||||||
'@nestjs/websockets',
|
'@nestjs/websockets',
|
||||||
|
'@nestjs/graphql',
|
||||||
|
'@nestjs/mapped-types',
|
||||||
'class-transformer',
|
'class-transformer',
|
||||||
'class-validator',
|
'class-validator',
|
||||||
'hot-shots',
|
'hot-shots',
|
||||||
|
@ -50,9 +53,54 @@ const nextConfig = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {import('@sentry/nextjs').SentryBuildOptions}
|
||||||
|
**/
|
||||||
|
const sentryOptions = {
|
||||||
|
// For all available options, see:
|
||||||
|
// https://github.com/getsentry/sentry-webpack-plugin#options
|
||||||
|
|
||||||
|
org: "mozilla",
|
||||||
|
project: "fxa-payments-next",
|
||||||
|
|
||||||
|
// Enable source maps
|
||||||
|
authToken: process.env.SENTRY_AUTH_TOKEN,
|
||||||
|
|
||||||
|
// Only print logs for uploading source maps in CI
|
||||||
|
silent: !process.env.CI,
|
||||||
|
|
||||||
|
// For all available options, see:
|
||||||
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/
|
||||||
|
|
||||||
|
// Upload a larger set of source maps for prettier stack traces (increases build time)
|
||||||
|
widenClientFileUpload: true,
|
||||||
|
|
||||||
|
// Automatically annotate React components to show their full name in breadcrumbs and session replay
|
||||||
|
reactComponentAnnotation: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
// Route browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers.
|
||||||
|
// This can increase your server load as well as your hosting bill.
|
||||||
|
// Note: Check that the configured route will not match with your Next.js middleware, otherwise reporting of client-
|
||||||
|
// side errors will fail.
|
||||||
|
tunnelRoute: "/monitoring",
|
||||||
|
|
||||||
|
// Hides source maps from generated client bundles
|
||||||
|
hideSourceMaps: true,
|
||||||
|
|
||||||
|
// Automatically tree-shake Sentry logger statements to reduce bundle size
|
||||||
|
disableLogger: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use withSentryConfig to wrap the next config
|
||||||
|
const sentryEnhancedConfig = (passedConfig) =>
|
||||||
|
withSentryConfig(passedConfig, sentryOptions);
|
||||||
|
|
||||||
const plugins = [
|
const plugins = [
|
||||||
// Add more Next.js plugins to this list if needed.
|
// Add more Next.js plugins to this list if needed.
|
||||||
withNx,
|
withNx,
|
||||||
|
sentryEnhancedConfig,
|
||||||
];
|
];
|
||||||
|
|
||||||
module.exports = composePlugins(...plugins)(nextConfig);
|
module.exports = composePlugins(...plugins)(nextConfig);
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "payments-next",
|
"name": "payments-next",
|
||||||
"version": "0.0.1",
|
"version": "0.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "next start"
|
"start": "next start"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
// This file configures the initialization of Sentry on the client.
|
||||||
|
// The config you add here will be used whenever a users loads a page in their browser.
|
||||||
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||||
|
import { initSentryForNextjsClient } from '@fxa/shared/sentry/client';
|
||||||
|
import { version } from './package.json';
|
||||||
|
|
||||||
|
const DEFAULT_SAMPLE_RATE = '1';
|
||||||
|
const DEFAULT_TRACES_SAMPLE_RATE = '1';
|
||||||
|
|
||||||
|
const sentryConfig = {
|
||||||
|
dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
|
||||||
|
env: process.env.NEXT_PUBLIC_SENTRY_ENV,
|
||||||
|
clientName: process.env.NEXT_PUBLIC_SENTRY_CLIENT_NAME,
|
||||||
|
sampleRate: parseInt(
|
||||||
|
process.env.NEXT_PUBLIC_SENTRY_SAMPLE_RATE || DEFAULT_SAMPLE_RATE
|
||||||
|
),
|
||||||
|
tracesSampleRate: parseInt(
|
||||||
|
process.env.NEXT_PUBLIC_SENTRY_TRACES_SAMPLE_RATE ||
|
||||||
|
DEFAULT_TRACES_SAMPLE_RATE
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
initSentryForNextjsClient({
|
||||||
|
release: version,
|
||||||
|
sentry: sentryConfig,
|
||||||
|
});
|
|
@ -0,0 +1,26 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
// This file configures the initialization of Sentry on the server.
|
||||||
|
// The config you add here will be used whenever the server handles a request.
|
||||||
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||||
|
import { initSentryForNextjsServer } from '@fxa/shared/sentry';
|
||||||
|
import { config } from './config';
|
||||||
|
import { version } from './package.json';
|
||||||
|
|
||||||
|
const sentryConfig = {
|
||||||
|
dsn: config.nextPublicSentryDsn,
|
||||||
|
env: config.nextPublicSentryEnv,
|
||||||
|
serverName: config.sentry.serverName,
|
||||||
|
sampleRate: config.nextPublicSentrySampleRate,
|
||||||
|
tracesSampleRate: config.nextPublicSentryTracesSampleRate,
|
||||||
|
};
|
||||||
|
|
||||||
|
initSentryForNextjsServer(
|
||||||
|
{
|
||||||
|
release: version,
|
||||||
|
sentry: sentryConfig,
|
||||||
|
},
|
||||||
|
console
|
||||||
|
);
|
|
@ -1,3 +1,7 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
import set from 'set-value';
|
import set from 'set-value';
|
||||||
import { plainToInstance, ClassConstructor } from 'class-transformer';
|
import { plainToInstance, ClassConstructor } from 'class-transformer';
|
||||||
import { validateSync } from 'class-validator';
|
import { validateSync } from 'class-validator';
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
export * from './lib/next/client';
|
|
@ -8,3 +8,4 @@ export * from './lib/nest/sentry.constants';
|
||||||
export * from './lib/reporting';
|
export * from './lib/reporting';
|
||||||
export * from './lib/node';
|
export * from './lib/node';
|
||||||
export * from './lib/browser';
|
export * from './lib/browser';
|
||||||
|
export * from './lib/next/server';
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
// This file configures the initialization of Sentry on the client.
|
||||||
|
// The config you add here will be used whenever a users loads a page in their browser.
|
||||||
|
// https://docs.sentry.io/platforms/javascript/guides/nextjs/
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
import { SentryConfigOpts } from '../models/SentryConfigOpts';
|
||||||
|
import { buildSentryConfig } from '../config-builder';
|
||||||
|
import { Logger } from '../sentry.types';
|
||||||
|
import { beforeSend } from '../utils/beforeSend.client';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @@todo - To be worked on in FXA-10398
|
||||||
|
*/
|
||||||
|
const sentryEnabled = true;
|
||||||
|
|
||||||
|
export function initSentryForNextjsClient(
|
||||||
|
config: SentryConfigOpts,
|
||||||
|
log?: Logger
|
||||||
|
) {
|
||||||
|
if (!log) {
|
||||||
|
log = console;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!config?.sentry?.dsn) {
|
||||||
|
log.error('No Sentry dsn provided');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We want sentry to be disabled by default... This is because we only emit data
|
||||||
|
// for users that 'have opted in'. A subsequent call to 'enable' is needed to ensure
|
||||||
|
// that sentry events only flow under the proper circumstances.
|
||||||
|
//disable();
|
||||||
|
|
||||||
|
const opts = buildSentryConfig(config, log);
|
||||||
|
try {
|
||||||
|
Sentry.init({
|
||||||
|
...opts,
|
||||||
|
integrations: [
|
||||||
|
Sentry.browserTracingIntegration({
|
||||||
|
enableInp: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
beforeSend: function (event: Sentry.ErrorEvent) {
|
||||||
|
return beforeSend(sentryEnabled, opts, event);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log.error(e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
import { ErrorEvent } from '@sentry/types';
|
||||||
|
import { SentryConfigOpts } from '../models/SentryConfigOpts';
|
||||||
|
import { buildSentryConfig } from '../config-builder';
|
||||||
|
import { Logger } from '../sentry.types';
|
||||||
|
import { tagFxaName } from '../utils/tagFxaName';
|
||||||
|
|
||||||
|
type ExtraOpts = {
|
||||||
|
integrations?: any[];
|
||||||
|
eventFilters?: Array<(event: ErrorEvent, hint: any) => ErrorEvent>;
|
||||||
|
};
|
||||||
|
|
||||||
|
type InitSentryOpts = SentryConfigOpts & ExtraOpts;
|
||||||
|
|
||||||
|
export function initSentryForNextjsServer(config: InitSentryOpts, log: Logger) {
|
||||||
|
if (!config?.sentry?.dsn) {
|
||||||
|
log.error('No Sentry dsn provided. Cannot start sentry');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const opts = buildSentryConfig(config, log);
|
||||||
|
/**
|
||||||
|
* @@todo - Move to lib/utils/beforeSend.server.ts - FXA-10402
|
||||||
|
*/
|
||||||
|
const beforeSend = function (event: ErrorEvent, hint: any) {
|
||||||
|
// Default
|
||||||
|
event = tagFxaName(event, config.sentry?.serverName || 'unknown');
|
||||||
|
|
||||||
|
// Custom filters
|
||||||
|
config.eventFilters?.forEach((filter) => {
|
||||||
|
event = filter(event, hint);
|
||||||
|
});
|
||||||
|
return event;
|
||||||
|
};
|
||||||
|
|
||||||
|
const integrations = [
|
||||||
|
// Default
|
||||||
|
Sentry.extraErrorDataIntegration({ depth: 5 }),
|
||||||
|
|
||||||
|
// Custom Integrations
|
||||||
|
...(config.integrations || []),
|
||||||
|
];
|
||||||
|
|
||||||
|
try {
|
||||||
|
Sentry.init({
|
||||||
|
// Defaults Options
|
||||||
|
normalizeDepth: 6,
|
||||||
|
maxValueLength: 500,
|
||||||
|
|
||||||
|
// Custom Options
|
||||||
|
integrations,
|
||||||
|
beforeSend,
|
||||||
|
...opts,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
log.debug('init-sentry', { msg: 'Issue initializing sentry!' });
|
||||||
|
log.error('init-sentry', e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,6 +24,9 @@ export function initSentry(config: InitSentryOpts, log: Logger) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const opts = buildSentryConfig(config, log);
|
const opts = buildSentryConfig(config, log);
|
||||||
|
/**
|
||||||
|
* @@todo - Move to lib/utils/beforeSend.server.ts - FXA-10402
|
||||||
|
*/
|
||||||
const beforeSend = function (event: ErrorEvent, hint: any) {
|
const beforeSend = function (event: ErrorEvent, hint: any) {
|
||||||
// Default
|
// Default
|
||||||
event = tagFxaName(event, config.sentry?.serverName || 'unknown');
|
event = tagFxaName(event, config.sentry?.serverName || 'unknown');
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import { SentryConfigOpts } from '../models/SentryConfigOpts';
|
||||||
|
import { beforeSend } from './beforeSend.client';
|
||||||
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
|
||||||
|
const config: SentryConfigOpts = {
|
||||||
|
release: 'v0.0.0',
|
||||||
|
sentry: {
|
||||||
|
dsn: 'https://public:private@host:8080/1',
|
||||||
|
env: 'test',
|
||||||
|
clientName: 'fxa-shared-testing',
|
||||||
|
sampleRate: 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const sentryEnabled = true;
|
||||||
|
|
||||||
|
describe('beforeSend', () => {
|
||||||
|
it('works without request url', () => {
|
||||||
|
const data = {
|
||||||
|
key: 'value',
|
||||||
|
} as unknown as Sentry.ErrorEvent;
|
||||||
|
|
||||||
|
const resultData = beforeSend(sentryEnabled, config, data);
|
||||||
|
|
||||||
|
expect(data).toEqual(resultData);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('fingerprints errno', () => {
|
||||||
|
const data = {
|
||||||
|
request: {
|
||||||
|
url: 'https://example.com',
|
||||||
|
},
|
||||||
|
tags: {
|
||||||
|
errno: '100',
|
||||||
|
},
|
||||||
|
type: undefined,
|
||||||
|
} as Sentry.ErrorEvent;
|
||||||
|
|
||||||
|
const resultData = beforeSend(sentryEnabled, config, data);
|
||||||
|
expect(resultData?.fingerprint?.[0]).toEqual('errno100');
|
||||||
|
expect(resultData?.level).toEqual('info');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('properly erases sensitive information from url', () => {
|
||||||
|
const url = 'https://accounts.firefox.com/complete_reset_password';
|
||||||
|
const badQuery =
|
||||||
|
'?token=foo&code=bar&email=some%40restmail.net&service=sync';
|
||||||
|
const goodQuery = '?token=VALUE&code=VALUE&email=VALUE&service=sync';
|
||||||
|
const badData = {
|
||||||
|
request: {
|
||||||
|
url: url + badQuery,
|
||||||
|
},
|
||||||
|
} as Sentry.ErrorEvent;
|
||||||
|
|
||||||
|
const goodData = {
|
||||||
|
request: {
|
||||||
|
url: url + goodQuery,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const resultData = beforeSend(sentryEnabled, config, badData);
|
||||||
|
expect(resultData?.request?.url).toEqual(goodData.request.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('properly erases sensitive information from referrer', () => {
|
||||||
|
const url = 'https://accounts.firefox.com/complete_reset_password';
|
||||||
|
const badQuery =
|
||||||
|
'?token=foo&code=bar&email=some%40restmail.net&service=sync';
|
||||||
|
const goodQuery = '?token=VALUE&code=VALUE&email=VALUE&service=sync';
|
||||||
|
const badData = {
|
||||||
|
request: {
|
||||||
|
headers: {
|
||||||
|
Referer: url + badQuery,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
type: undefined,
|
||||||
|
} as Sentry.ErrorEvent;
|
||||||
|
|
||||||
|
const goodData = {
|
||||||
|
request: {
|
||||||
|
headers: {
|
||||||
|
Referer: url + goodQuery,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const resultData = beforeSend(sentryEnabled, config, badData);
|
||||||
|
expect(resultData?.request?.headers?.Referer).toEqual(
|
||||||
|
goodData.request.headers.Referer
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('properly erases sensitive information from abs_path', () => {
|
||||||
|
const url = 'https://accounts.firefox.com/complete_reset_password';
|
||||||
|
const badCulprit = 'https://accounts.firefox.com/scripts/57f6d4e4.main.js';
|
||||||
|
const badAbsPath =
|
||||||
|
'https://accounts.firefox.com/complete_reset_password?token=foo&code=bar&email=a@a.com&service=sync&resume=barbar';
|
||||||
|
const goodAbsPath =
|
||||||
|
'https://accounts.firefox.com/complete_reset_password?token=VALUE&code=VALUE&email=VALUE&service=sync&resume=VALUE';
|
||||||
|
const data = {
|
||||||
|
culprit: badCulprit,
|
||||||
|
exception: {
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
stacktrace: {
|
||||||
|
frames: [
|
||||||
|
{
|
||||||
|
abs_path: badAbsPath, // eslint-disable-line camelcase
|
||||||
|
},
|
||||||
|
{
|
||||||
|
abs_path: badAbsPath, // eslint-disable-line camelcase
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
request: {
|
||||||
|
url,
|
||||||
|
},
|
||||||
|
type: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const resultData = beforeSend(sentryEnabled, config, data);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
resultData?.exception?.values?.[0].stacktrace?.frames?.[0].abs_path
|
||||||
|
).toEqual(goodAbsPath);
|
||||||
|
expect(
|
||||||
|
resultData?.exception?.values?.[0].stacktrace?.frames?.[1].abs_path
|
||||||
|
).toEqual(goodAbsPath);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,65 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
// Change to @sentry/browser after upgrade to Sentry 8
|
||||||
|
import * as Sentry from '@sentry/nextjs';
|
||||||
|
import { cleanUpQueryParam } from './cleanUpQueryParam';
|
||||||
|
import { SentryConfigOpts } from '../models/SentryConfigOpts';
|
||||||
|
import { tagFxaName } from './tagFxaName';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* function that gets called before data gets sent to error metrics
|
||||||
|
*
|
||||||
|
* @param {Object} event
|
||||||
|
* Error object data
|
||||||
|
* @returns {Object} data
|
||||||
|
* Modified error object data
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
export function beforeSend(
|
||||||
|
sentryEnabled: boolean,
|
||||||
|
opts: SentryConfigOpts,
|
||||||
|
event: Sentry.ErrorEvent
|
||||||
|
) {
|
||||||
|
if (sentryEnabled === false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.request) {
|
||||||
|
if (event.request.url) {
|
||||||
|
event.request.url = cleanUpQueryParam(event.request.url);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.tags) {
|
||||||
|
// if this is a known errno, then use grouping with fingerprints
|
||||||
|
// Docs: https://docs.sentry.io/hosted/learn/rollups/#fallback-grouping
|
||||||
|
if (event.tags.errno) {
|
||||||
|
event.fingerprint = ['errno' + (event.tags.errno as number)];
|
||||||
|
// if it is a known error change the error level to info.
|
||||||
|
event.level = 'info';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.exception?.values) {
|
||||||
|
event.exception.values.forEach((value: Sentry.Exception) => {
|
||||||
|
if (value.stacktrace && value.stacktrace.frames) {
|
||||||
|
value.stacktrace.frames.forEach((frame: { abs_path?: string }) => {
|
||||||
|
if (frame.abs_path) {
|
||||||
|
frame.abs_path = cleanUpQueryParam(frame.abs_path); // eslint-disable-line camelcase
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.request.headers?.Referer) {
|
||||||
|
event.request.headers.Referer = cleanUpQueryParam(
|
||||||
|
event.request.headers.Referer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
event = tagFxaName(event, opts.sentry?.clientName || opts.sentry?.serverName);
|
||||||
|
return event;
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
import { cleanUpQueryParam } from './cleanUpQueryParam';
|
||||||
|
|
||||||
|
describe('cleanUpQueryParam', () => {
|
||||||
|
it('properly erases sensitive information', () => {
|
||||||
|
const fixtureUrl1 =
|
||||||
|
'https://accounts.firefox.com/complete_reset_password?token=foo&code=bar&email=some%40restmail.net';
|
||||||
|
const expectedUrl1 =
|
||||||
|
'https://accounts.firefox.com/complete_reset_password?token=VALUE&code=VALUE&email=VALUE';
|
||||||
|
const resultUrl1 = cleanUpQueryParam(fixtureUrl1);
|
||||||
|
|
||||||
|
expect(resultUrl1).toEqual(expectedUrl1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('properly erases sensitive information, keeps allowed fields', () => {
|
||||||
|
const fixtureUrl2 =
|
||||||
|
'https://accounts.firefox.com/signup?client_id=foo&service=sync';
|
||||||
|
const expectedUrl2 =
|
||||||
|
'https://accounts.firefox.com/signup?client_id=foo&service=sync';
|
||||||
|
const resultUrl2 = cleanUpQueryParam(fixtureUrl2);
|
||||||
|
|
||||||
|
expect(resultUrl2).toEqual(expectedUrl2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('properly returns the url when there is no query', () => {
|
||||||
|
const expectedUrl = 'https://accounts.firefox.com/signup';
|
||||||
|
const resultUrl = cleanUpQueryParam(expectedUrl);
|
||||||
|
|
||||||
|
expect(resultUrl).toEqual(expectedUrl);
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,44 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query parameters we allow to propagate to sentry
|
||||||
|
*/
|
||||||
|
const ALLOWED_QUERY_PARAMETERS = [
|
||||||
|
'automatedBrowser',
|
||||||
|
'client_id',
|
||||||
|
'context',
|
||||||
|
'entrypoint',
|
||||||
|
'keys',
|
||||||
|
'migration',
|
||||||
|
'redirect_uri',
|
||||||
|
'scope',
|
||||||
|
'service',
|
||||||
|
'setting',
|
||||||
|
'style',
|
||||||
|
];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overwrites sensitive query parameters with a dummy value.
|
||||||
|
*
|
||||||
|
* @param url
|
||||||
|
* @returns url
|
||||||
|
*/
|
||||||
|
export function cleanUpQueryParam(url = '') {
|
||||||
|
const urlObj = new URL(url);
|
||||||
|
|
||||||
|
if (!urlObj.search.length) {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate the search parameters.
|
||||||
|
urlObj.searchParams.forEach((_, key) => {
|
||||||
|
if (!ALLOWED_QUERY_PARAMETERS.includes(key)) {
|
||||||
|
// if the param is a PII (not allowed) then reset the value.
|
||||||
|
urlObj.searchParams.set(key, 'VALUE');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return urlObj.href;
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||||
|
|
||||||
|
/** Adds fxa.name to data.tags */
|
||||||
|
export function tagFxaName(data: any, name?: string) {
|
||||||
|
data.tags = data.tags || {};
|
||||||
|
data.tags['fxa.name'] = name || 'unknown';
|
||||||
|
return data;
|
||||||
|
}
|
|
@ -78,6 +78,7 @@
|
||||||
"@radix-ui/react-tooltip": "^1.1.2",
|
"@radix-ui/react-tooltip": "^1.1.2",
|
||||||
"@sentry/browser": "^7.113.0",
|
"@sentry/browser": "^7.113.0",
|
||||||
"@sentry/integrations": "^7.113.0",
|
"@sentry/integrations": "^7.113.0",
|
||||||
|
"@sentry/nextjs": "^8",
|
||||||
"@sentry/node": "^7.113.0",
|
"@sentry/node": "^7.113.0",
|
||||||
"@sentry/opentelemetry-node": "^7.113.0",
|
"@sentry/opentelemetry-node": "^7.113.0",
|
||||||
"@swc/helpers": "0.5.11",
|
"@swc/helpers": "0.5.11",
|
||||||
|
@ -278,7 +279,8 @@
|
||||||
"tap/typescript": "^4.5.2",
|
"tap/typescript": "^4.5.2",
|
||||||
"terser:>4.0.0 <5": ">=4.8.1",
|
"terser:>4.0.0 <5": ">=4.8.1",
|
||||||
"terser:>5 <6": ">=5.14.2",
|
"terser:>5 <6": ">=5.14.2",
|
||||||
"underscore": ">=1.13.2"
|
"underscore": ">=1.13.2",
|
||||||
|
"@sentry/types": "^7.113.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@3.3.0",
|
"packageManager": "yarn@3.3.0",
|
||||||
"_moduleAliases": {
|
"_moduleAliases": {
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
"@fxa/shared/pem-jwk": ["libs/shared/pem-jwk/src/index.ts"],
|
"@fxa/shared/pem-jwk": ["libs/shared/pem-jwk/src/index.ts"],
|
||||||
"@fxa/shared/react": ["libs/shared/react/src/index.ts"],
|
"@fxa/shared/react": ["libs/shared/react/src/index.ts"],
|
||||||
"@fxa/shared/sentry": ["libs/shared/sentry/src/index.ts"],
|
"@fxa/shared/sentry": ["libs/shared/sentry/src/index.ts"],
|
||||||
|
"@fxa/shared/sentry/client": ["libs/shared/sentry/src/client.ts"],
|
||||||
"@fxa/vendored/common-password-list": [
|
"@fxa/vendored/common-password-list": [
|
||||||
"libs/vendored/common-password-list/src/index.ts"
|
"libs/vendored/common-password-list/src/index.ts"
|
||||||
],
|
],
|
||||||
|
|
838
yarn.lock
838
yarn.lock
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
Загрузка…
Ссылка в новой задаче