From 41a8f4c68d83100040ab0e43670befc80917d81f Mon Sep 17 00:00:00 2001 From: Vijay Budhram Date: Thu, 17 Oct 2024 11:18:55 -0400 Subject: [PATCH] feat(metrics): Add scopes to Glean access token check event --- .../lib/metrics/glean/index.ts | 9 ++ .../lib/metrics/glean/server_events.ts | 6 + .../lib/routes/oauth/verify.js | 1 + .../test/local/metrics/glean.ts | 31 ++++ .../test/oauth/routes/verify.js | 1 + .../server/lib/glean/server_events.js | 135 ++++++++++++++++++ .../metrics/glean/fxa-backend-metrics.yaml | 4 + 7 files changed, 187 insertions(+) diff --git a/packages/fxa-auth-server/lib/metrics/glean/index.ts b/packages/fxa-auth-server/lib/metrics/glean/index.ts index f952f6de56b..a3ee641f2c4 100644 --- a/packages/fxa-auth-server/lib/metrics/glean/index.ts +++ b/packages/fxa-auth-server/lib/metrics/glean/index.ts @@ -26,6 +26,7 @@ type MetricsData = { uid?: string; reason?: string; oauthClientId?: string; + scopes?: string; }; type AdditionalMetricsCallback = ( @@ -162,6 +163,7 @@ const createEventFn = utm_medium: metricsContext.utmMedium || '', utm_source: metricsContext.utmSource || '', utm_term: metricsContext.utmTerm || '', + scopes: metricsData?.scopes || '', }; // reason is sent in access_token_created, login_submit_backend_error, and reg_submit_error @@ -259,6 +261,13 @@ export function gleanMetrics(config: ConfigType) { }), tokenChecked: createEventFn('access_token_checked', { skipClientIp: true, + additionalMetrics: (metrics) => ({ + scopes: metrics.scopes + ? Array.isArray(metrics.scopes) + ? metrics.scopes.sort().join(',') + : metrics.scopes + : '', + }), }), }, diff --git a/packages/fxa-auth-server/lib/metrics/glean/server_events.ts b/packages/fxa-auth-server/lib/metrics/glean/server_events.ts index cabe46e0640..29f1ac54d15 100644 --- a/packages/fxa-auth-server/lib/metrics/glean/server_events.ts +++ b/packages/fxa-auth-server/lib/metrics/glean/server_events.ts @@ -320,6 +320,7 @@ class EventsServerEventLogger { * @param {string} utm_medium - The "medium" on which the user acted. For example, if the user clicked on a link in an email, then the value of this metric would be 'email'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. * @param {string} utm_source - The source from where the user started. For example, if the user clicked on a link on the Mozilla accounts web site, this value could be 'fx-website'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. * @param {string} utm_term - This metric is similar to the `utm.source`; it is used in the Firefox browser. For example, if the user started from about:welcome, then the value could be 'aboutwelcome-default-screen'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. + * @param {string} scopes - The scopes of the access token. */ recordAccessTokenChecked({ user_agent, @@ -338,6 +339,7 @@ class EventsServerEventLogger { utm_medium, utm_source, utm_term, + scopes, }: { user_agent: string; ip_address: string; @@ -355,10 +357,14 @@ class EventsServerEventLogger { utm_medium: string; utm_source: string; utm_term: string; + scopes: string; }) { const event = { category: 'access_token', name: 'checked', + extra: { + scopes: String(scopes), + }, }; this.#record({ user_agent, diff --git a/packages/fxa-auth-server/lib/routes/oauth/verify.js b/packages/fxa-auth-server/lib/routes/oauth/verify.js index dfebd4ec413..f1c0ad02545 100644 --- a/packages/fxa-auth-server/lib/routes/oauth/verify.js +++ b/packages/fxa-auth-server/lib/routes/oauth/verify.js @@ -46,6 +46,7 @@ module.exports = ({ log, glean }) => ({ glean.oauth.tokenChecked(req, { uid: info.user, oauthClientId: info.client_id, + scopes: info.scope, }); return info; }, diff --git a/packages/fxa-auth-server/test/local/metrics/glean.ts b/packages/fxa-auth-server/test/local/metrics/glean.ts index a162b3ad402..4528ec75a09 100644 --- a/packages/fxa-auth-server/test/local/metrics/glean.ts +++ b/packages/fxa-auth-server/test/local/metrics/glean.ts @@ -130,6 +130,7 @@ describe('Glean server side events', () => { recordRegAccVerifiedStub.reset(); recordRegCompleteStub.reset(); recordRegSubmitErrorStub.reset(); + recordAccessTokenCheckedStub.reset(); }); describe('enabled state', () => { @@ -559,6 +560,36 @@ describe('Glean server side events', () => { const metrics = recordStub.args[0][0]; assert.equal(metrics['ip_address'], ''); }); + + it('handles undefined scopes', async () => { + const glean = gleanMetrics(config); + await glean.oauth.tokenChecked(request, { + scopes: undefined, + }); + sinon.assert.calledOnce(recordAccessTokenCheckedStub); + const metrics = recordAccessTokenCheckedStub.args[0][0]; + assert.equal(metrics['scopes'], ''); + }); + + it('handles empty scopes', async () => { + const glean = gleanMetrics(config); + await glean.oauth.tokenChecked(request, { + scopes: '', + }); + sinon.assert.calledOnce(recordAccessTokenCheckedStub); + const metrics = recordAccessTokenCheckedStub.args[0][0]; + assert.equal(metrics['scopes'], ''); + }); + + it('includes sorted comma separated scopes', async () => { + const glean = gleanMetrics(config); + await glean.oauth.tokenChecked(request, { + scopes: ['profile', 'openid'], + }); + sinon.assert.calledOnce(recordAccessTokenCheckedStub); + const metrics = recordAccessTokenCheckedStub.args[0][0]; + assert.equal(metrics['scopes'], 'openid,profile'); + }); }); }); diff --git a/packages/fxa-auth-server/test/oauth/routes/verify.js b/packages/fxa-auth-server/test/oauth/routes/verify.js index acbe49bea15..27be43476c0 100644 --- a/packages/fxa-auth-server/test/oauth/routes/verify.js +++ b/packages/fxa-auth-server/test/oauth/routes/verify.js @@ -121,6 +121,7 @@ describe('/verify POST', () => { sinon.assert.calledOnceWithExactly(mocks.glean.oauth.tokenChecked, req, { uid: 'bar', oauthClientId: 'foo', + scopes: ['bar:foo', 'clients:write'], }); }); }); diff --git a/packages/fxa-content-server/server/lib/glean/server_events.js b/packages/fxa-content-server/server/lib/glean/server_events.js index bcaebf40fe2..f1ad164cbde 100644 --- a/packages/fxa-content-server/server/lib/glean/server_events.js +++ b/packages/fxa-content-server/server/lib/glean/server_events.js @@ -254,6 +254,7 @@ class EventsServerEventLogger { * @param {string} utm_medium - The "medium" on which the user acted. For example, if the user clicked on a link in an email, then the value of this metric would be 'email'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. * @param {string} utm_source - The source from where the user started. For example, if the user clicked on a link on the Mozilla accounts web site, this value could be 'fx-website'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. * @param {string} utm_term - This metric is similar to the `utm.source`; it is used in the Firefox browser. For example, if the user started from about:welcome, then the value could be 'aboutwelcome-default-screen'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. + * @param {string} scopes - The scopes of the access token. */ recordAccessTokenChecked({ user_agent, @@ -272,10 +273,14 @@ class EventsServerEventLogger { utm_medium, utm_source, utm_term, + scopes, }) { const event = { category: 'access_token', name: 'checked', + extra: { + scopes: String(scopes), + }, }; this.#record({ user_agent, @@ -1282,6 +1287,71 @@ class EventsServerEventLogger { event, }); } + /** + * Record and submit a password_reset_recovery_code_success event: + * User successfully verified their recovery code before password reset + * Event is logged using internal mozlog logger. + * + * @param {string} user_agent - The user agent. + * @param {string} ip_address - The IP address. Will be used to decode Geo + * information and scrubbed at ingestion. + * @param {string} account_user_id - The firefox/mozilla account id. + * @param {string} account_user_id_sha256 - A hex string of a sha256 hash of the account's uid. + * @param {string} relying_party_oauth_client_id - The client id of the relying party. + * @param {string} relying_party_service - The service name of the relying party. + * @param {string} session_device_type - one of 'mobile', 'tablet', or ''. + * @param {string} session_entrypoint - Entrypoint to the service. + * @param {string} session_entrypoint_experiment - Identifier for the experiment the user is part of at the entrypoint. + * @param {string} session_entrypoint_variation - Identifier for the experiment variation the user is part of at the entrypoint. + * @param {string} session_flow_id - an ID generated by FxA for its flow metrics. + * @param {string} utm_campaign - A marketing campaign. For example, if a user signs into FxA from selecting a Mozilla VPN plan on Mozilla VPN's product site, then the value of this metric could be 'vpn-product-page'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters. The special value of 'page+referral+-+not+part+of+a+campaign' is also allowed.. + * @param {string} utm_content - The content on which the user acted. For example, if the user clicked on the (previously available) "Get started here" link in "Looking for Firefox Sync? Get started here", then the value for this metric would be 'fx-sync-get-started'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. + * @param {string} utm_medium - The "medium" on which the user acted. For example, if the user clicked on a link in an email, then the value of this metric would be 'email'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. + * @param {string} utm_source - The source from where the user started. For example, if the user clicked on a link on the Mozilla accounts web site, this value could be 'fx-website'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. + * @param {string} utm_term - This metric is similar to the `utm.source`; it is used in the Firefox browser. For example, if the user started from about:welcome, then the value could be 'aboutwelcome-default-screen'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. + */ + recordPasswordResetRecoveryCodeSuccess({ + user_agent, + ip_address, + account_user_id, + account_user_id_sha256, + relying_party_oauth_client_id, + relying_party_service, + session_device_type, + session_entrypoint, + session_entrypoint_experiment, + session_entrypoint_variation, + session_flow_id, + utm_campaign, + utm_content, + utm_medium, + utm_source, + utm_term, + }) { + const event = { + category: 'password_reset', + name: 'recovery_code_success', + }; + this.#record({ + user_agent, + ip_address, + account_user_id, + account_user_id_sha256, + relying_party_oauth_client_id, + relying_party_service, + session_device_type, + session_entrypoint, + session_entrypoint_experiment, + session_entrypoint_variation, + session_flow_id, + utm_campaign, + utm_content, + utm_medium, + utm_source, + utm_term, + event, + }); + } /** * Record and submit a password_reset_recovery_key_create_success event: * Forgot Password w/ Recovery Key Create New Password Success (BE) User successfully submits the form to create a new password' @@ -1412,6 +1482,71 @@ class EventsServerEventLogger { event, }); } + /** + * Record and submit a password_reset_two_factor_success event: + * User successfully verified their two factor authentication code before password reset + * Event is logged using internal mozlog logger. + * + * @param {string} user_agent - The user agent. + * @param {string} ip_address - The IP address. Will be used to decode Geo + * information and scrubbed at ingestion. + * @param {string} account_user_id - The firefox/mozilla account id. + * @param {string} account_user_id_sha256 - A hex string of a sha256 hash of the account's uid. + * @param {string} relying_party_oauth_client_id - The client id of the relying party. + * @param {string} relying_party_service - The service name of the relying party. + * @param {string} session_device_type - one of 'mobile', 'tablet', or ''. + * @param {string} session_entrypoint - Entrypoint to the service. + * @param {string} session_entrypoint_experiment - Identifier for the experiment the user is part of at the entrypoint. + * @param {string} session_entrypoint_variation - Identifier for the experiment variation the user is part of at the entrypoint. + * @param {string} session_flow_id - an ID generated by FxA for its flow metrics. + * @param {string} utm_campaign - A marketing campaign. For example, if a user signs into FxA from selecting a Mozilla VPN plan on Mozilla VPN's product site, then the value of this metric could be 'vpn-product-page'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters. The special value of 'page+referral+-+not+part+of+a+campaign' is also allowed.. + * @param {string} utm_content - The content on which the user acted. For example, if the user clicked on the (previously available) "Get started here" link in "Looking for Firefox Sync? Get started here", then the value for this metric would be 'fx-sync-get-started'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. + * @param {string} utm_medium - The "medium" on which the user acted. For example, if the user clicked on a link in an email, then the value of this metric would be 'email'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. + * @param {string} utm_source - The source from where the user started. For example, if the user clicked on a link on the Mozilla accounts web site, this value could be 'fx-website'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. + * @param {string} utm_term - This metric is similar to the `utm.source`; it is used in the Firefox browser. For example, if the user started from about:welcome, then the value could be 'aboutwelcome-default-screen'. The value has a max length of 128 characters with the alphanumeric characters, _ (underscore), forward slash (/), . (period), % (percentage sign), and - (hyphen) in the allowed set of characters.. + */ + recordPasswordResetTwoFactorSuccess({ + user_agent, + ip_address, + account_user_id, + account_user_id_sha256, + relying_party_oauth_client_id, + relying_party_service, + session_device_type, + session_entrypoint, + session_entrypoint_experiment, + session_entrypoint_variation, + session_flow_id, + utm_campaign, + utm_content, + utm_medium, + utm_source, + utm_term, + }) { + const event = { + category: 'password_reset', + name: 'two_factor_success', + }; + this.#record({ + user_agent, + ip_address, + account_user_id, + account_user_id_sha256, + relying_party_oauth_client_id, + relying_party_service, + session_device_type, + session_entrypoint, + session_entrypoint_experiment, + session_entrypoint_variation, + session_flow_id, + utm_campaign, + utm_content, + utm_medium, + utm_source, + utm_term, + event, + }); + } /** * Record and submit a reg_acc_created event: * Registration Submission Success Event that indicates registration was successful. diff --git a/packages/fxa-shared/metrics/glean/fxa-backend-metrics.yaml b/packages/fxa-shared/metrics/glean/fxa-backend-metrics.yaml index bff31563cf9..6dbebfc2cd2 100644 --- a/packages/fxa-shared/metrics/glean/fxa-backend-metrics.yaml +++ b/packages/fxa-shared/metrics/glean/fxa-backend-metrics.yaml @@ -414,6 +414,10 @@ access_token: expires: never data_sensitivity: - interaction + extra_keys: + scopes: + description: The scopes of the access token + type: string created: type: event description: |