diff --git a/packages/fxa-auth-client/lib/client.ts b/packages/fxa-auth-client/lib/client.ts index a56b0a55f12..b1b65289dba 100644 --- a/packages/fxa-auth-client/lib/client.ts +++ b/packages/fxa-auth-client/lib/client.ts @@ -2625,35 +2625,6 @@ export default class AuthClient { ); } - async sendLoginPushRequest( - sessionToken: hexstring, - headers?: Headers - ): Promise { - return this.sessionPost( - '/session/verify/send_push', - sessionToken, - {}, - headers - ); - } - - async verifyLoginPushRequest( - sessionToken: hexstring, - tokenVerificationId: string, - code: string, - headers?: Headers - ): Promise { - return this.sessionPost( - '/session/verify/verify_push', - sessionToken, - { - tokenVerificationId, - code, - }, - headers - ); - } - async verifyTotpCode( sessionToken: hexstring, code: string, @@ -3144,15 +3115,6 @@ export default class AuthClient { ); } - async sendPushLoginRequest(sessionToken: string, headers?: Headers) { - return this.sessionPost( - '/session/verify/send_push', - sessionToken, - {}, - headers - ); - } - /** * Tries to register a recovery phone number. Important, this must be used for inline_recovery flow! * diff --git a/packages/fxa-auth-server/config/index.ts b/packages/fxa-auth-server/config/index.ts index 3aa361d2fdd..c23fd3ad965 100644 --- a/packages/fxa-auth-server/config/index.ts +++ b/packages/fxa-auth-server/config/index.ts @@ -2928,7 +2928,6 @@ convictConf.set( 'https://support.mozilla.org/kb/secure-mozilla-account-two-step-authentication' ); convictConf.set('smtp.verificationUrl', `${baseUri}/verify_email`); -convictConf.set('smtp.pushVerificationUrl', `${baseUri}/push/confirm_login`); convictConf.set('smtp.passwordResetUrl', `${baseUri}/complete_reset_password`); convictConf.set('smtp.initiatePasswordResetUrl', `${baseUri}/reset_password`); convictConf.set( diff --git a/packages/fxa-auth-server/docs/swagger/session-api.ts b/packages/fxa-auth-server/docs/swagger/session-api.ts index 60a2cfdc841..3f452b110a8 100644 --- a/packages/fxa-auth-server/docs/swagger/session-api.ts +++ b/packages/fxa-auth-server/docs/swagger/session-api.ts @@ -130,30 +130,6 @@ const SESSION_RESEND_CODE_POST = { notes: ['🔒 Authenticated with session token'], }; -const SESSION_SEND_PUSH_POST = { - ...TAGS_SESSION, - description: '/session/verify/send_push', - notes: [ - dedent` - 🔒 Authenticated with session token - - Sends a push notification to all push enabled devices to verify current session. - `, - ], -}; - -const SESSION_VERIFY_PUSH_POST = { - ...TAGS_SESSION, - description: '/session/verify/verify_push', - notes: [ - dedent` - 🔒 Authenticated with session token - - Endpoint that accepts a code and tokenVerificationId to verify a session. - `, - ], -}; - const API_DOCS = { SESSION_DESTROY_POST, SESSION_DUPLICATE_POST, @@ -161,8 +137,6 @@ const API_DOCS = { SESSION_STATUS_GET, SESSION_RESEND_CODE_POST, SESSION_VERIFY_CODE_POST, - SESSION_SEND_PUSH_POST, - SESSION_VERIFY_PUSH_POST, }; export default API_DOCS; diff --git a/packages/fxa-auth-server/lib/l10n/server.ftl b/packages/fxa-auth-server/lib/l10n/server.ftl index f76a39ee262..f095973c6aa 100644 --- a/packages/fxa-auth-server/lib/l10n/server.ftl +++ b/packages/fxa-auth-server/lib/l10n/server.ftl @@ -1,8 +1,5 @@ ## Non-email strings -session-verify-send-push-title-2 = Logging in to your { -product-mozilla-account }? -session-verify-send-push-body-2 = Click here to confirm it’s you - # Message sent by SMS with limited character length, please test translation with the messaging segment calculator # https://twiliodeved.github.io/message-segment-calculator/ # Messages should be limited to one segment diff --git a/packages/fxa-auth-server/lib/push.js b/packages/fxa-auth-server/lib/push.js index daa9650041e..46723626ce7 100644 --- a/packages/fxa-auth-server/lib/push.js +++ b/packages/fxa-auth-server/lib/push.js @@ -17,7 +17,6 @@ const PUSH_COMMANDS = { PASSWORD_RESET: 'fxaccounts:password_reset', ACCOUNT_DESTROYED: 'fxaccounts:account_destroyed', COMMAND_RECEIVED: 'fxaccounts:command_received', - LOGIN_REQUEST: 'fxaccounts:verify_login', }; const PUSH_REASONS = new Set([ @@ -31,7 +30,6 @@ const PUSH_REASONS = new Set([ 'devicesNotify', 'accountDestroyed', 'commandReceived', - 'verifyLogin', ]); const PUSH_ERRORS = new Set([ @@ -347,25 +345,6 @@ module.exports = function (log, db, config, statsd) { }); }, - /** - * Notify a device to verify a login request. The push notification - * contains location, UA and verification code in the url. - * - * @param {String} uid - * @param {Device[]} devices - * @param {Object} data - * @promise - */ - notifyVerifyLoginRequest(uid, devices, data) { - return this.sendPush(uid, devices, 'verifyLogin', { - data: { - version: PUSH_PAYLOAD_SCHEMA_VERSION, - command: PUSH_COMMANDS.LOGIN_REQUEST, - data, - }, - }); - }, - /** * Send a push notification with or without data to a list of devices * diff --git a/packages/fxa-auth-server/lib/routes/session.js b/packages/fxa-auth-server/lib/routes/session.js index 55e12d83edb..7fbb2eaef13 100644 --- a/packages/fxa-auth-server/lib/routes/session.js +++ b/packages/fxa-auth-server/lib/routes/session.js @@ -9,9 +9,6 @@ const isA = require('joi'); const requestHelper = require('../routes/utils/request_helper'); const METRICS_CONTEXT_SCHEMA = require('../metrics/context').schema; const validators = require('./validators'); -const Localizer = require('../l10n').default; -const NodeRendererBindings = - require('../senders/renderer/bindings-node').default; const SESSION_DOCS = require('../../docs/swagger/session-api').default; const DESCRIPTION = require('../../docs/swagger/shared/descriptions').default; const HEX_STRING = validators.HEX_STRING; @@ -703,244 +700,6 @@ module.exports = function ( } } - return {}; - }, - }, - { - method: 'POST', - path: '/session/verify/send_push', - options: { - ...SESSION_DOCS.SESSION_SEND_PUSH_POST, - auth: { - strategy: 'sessionToken', - }, - }, - handler: async function (request) { - log.begin('Session.send_push', request); - - const sessionToken = request.auth.credentials; - const { uid, email, tokenVerificationId } = sessionToken; - - // Check to see if this account has a verified TOTP token. If so, then it should - // not be allowed to bypass TOTP requirement by sending a sign-in push notification. - try { - const result = await db.totpToken(sessionToken.uid); - - if (result && result.verified && result.enabled) { - return {}; - } - } catch (err) { - if (err.errno !== error.ERRNO.TOTP_TOKEN_NOT_FOUND) { - throw err; - } - } - - const allDevices = await db.devices(uid); - - const account = await db.account(sessionToken.uid); - const secret = account.primaryEmail.emailCode; - - const code = otpUtils.generateOtpCode(secret, otpOptions); - - // Filter devices that can accept the push notification. - const filteredDevices = allDevices.filter((d) => { - // Don't push to the current device - if (d.sessionTokenId === sessionToken.id) { - return false; - } - // Exclude expired devices - if (d.pushEndpointExpired === true) { - return false; - } - // Currently, we only support sending push notifications to Firefox Desktop - return d.type === 'desktop' && d.uaBrowser === 'Firefox'; - }); - - const confirmUrl = `${config.contentServer.url}/signin_push_code_confirm`; - - const localizer = new Localizer(new NodeRendererBindings()); - - // If/when we use .localizeStrings in other files, probably move where strings are - // maintained to separate file? - const titleFtlId = 'session-verify-send-push-title-2'; - const bodyFtlId = 'session-verify-send-push-body-2'; - - const ftlIdMsgs = [ - { - id: titleFtlId, - message: 'Logging in to your Mozilla account?', - }, - { - id: bodyFtlId, - message: 'Click here to confirm it’s you', - }, - ]; - const localizedStrings = await localizer.localizeStrings( - request.app.locale, - ftlIdMsgs - ); - - const options = { - title: localizedStrings[titleFtlId], - body: localizedStrings[bodyFtlId], - }; - - const { region, city, country } = request.app.geo; - const remoteMetaData = { - deviceName: sessionToken.deviceName, - deviceFamily: sessionToken.uaBrowser, - deviceOS: sessionToken.uaOS, - ipAddress: request.app.clientAddress, - city, - region, - country, - }; - const params = new URLSearchParams({ - tokenVerificationId, - code, - uid, - email, - remoteMetaData: encodeURIComponent(JSON.stringify(remoteMetaData)), - }); - const url = `${confirmUrl}?${params.toString()}`; - try { - await push.notifyVerifyLoginRequest(uid, filteredDevices, { - ...options, - url, - }); - } catch (err) { - log.error('Session.send_push', { - uid: uid, - error: err, - }); - } - - return {}; - }, - }, - { - method: 'POST', - path: '/session/verify/verify_push', - options: { - ...SESSION_DOCS.SESSION_VERIFY_CODE_POST, - auth: { - strategy: 'sessionToken', - }, - validate: { - payload: isA.object({ - code: validators.DIGITS, - tokenVerificationId: validators.hexString.length(32), - }), - }, - }, - handler: async function (request) { - log.begin('Session.verify_push', request); - const options = request.payload; - const sessionToken = request.auth.credentials; - const { uid, email } = sessionToken; - const { code, tokenVerificationId } = options; - - await customs.checkAuthenticated( - request, - uid, - email, - 'verifySessionCode' - ); - request.emitMetricsEvent('session.verify_push'); - - const device = await db.deviceFromTokenVerificationId( - uid, - tokenVerificationId - ); - - // If device is not found, this means the device has already been verified. - // Since the user can not take any additional action, it is safe to return - // a successful response. - if (!device) { - return {}; - } - - // Check to see if the otp code passed matches the expected value from - // using the account's' `emailCode` as the secret in the otp code generation. - const account = await db.account(uid); - const secret = account.primaryEmail.emailCode; - - const { valid: isValidCode } = otpUtils.verifyOtpCode( - code, - secret, - otpOptions, - 'session.verify_push' - ); - - if (!isValidCode) { - if (customs.v2Enabled()) { - await customs.checkAuthenticated( - request, - uid, - email, - 'verifySessionCodeFailed' - ); - } - throw error.invalidOrExpiredOtpCode(); - } - - await db.verifyTokens(tokenVerificationId, account); - - // We have a matching code! Let's verify session and send the - // corresponding email and emit metrics. - request.emitMetricsEvent('account.confirmed', { uid }); - glean.login.verifyCodeConfirmed(request, { uid }); - await signinUtils.cleanupReminders({ verified: true }, account); - const devices = await db.devices(uid); - push - .notifyAccountUpdated(uid, devices, 'accountConfirm') - .catch((err) => log.error('push.accountConfirm.error', { uid, err })); - - // Send new device login notification email after successful verification - if (account.primaryEmail.isVerified) { - const geoData = request.app.geo; - const service = request.query.service; - const emailOptions = { - acceptLanguage: request.app.acceptLanguage, - ip: request.app.clientAddress, - location: geoData.location, - service, - timeZone: geoData.timeZone, - uaBrowser: sessionToken.uaBrowser, - uaBrowserVersion: sessionToken.uaBrowserVersion, - uaOS: sessionToken.uaOS, - uaOSVersion: sessionToken.uaOSVersion, - uaDeviceType: sessionToken.uaDeviceType, - uid, - }; - - try { - if (fxaMailer.canSend('newDeviceLogin')) { - const clientInfo = await oauthClientInfoService.fetch(service); - await fxaMailer.sendNewDeviceLoginEmail({ - ...FxaMailerFormat.account(account), - ...FxaMailerFormat.device(request), - ...FxaMailerFormat.localTime(request), - ...FxaMailerFormat.location(request), - ...(await FxaMailerFormat.metricsContext(request)), - ...FxaMailerFormat.sync(service), - clientName: clientInfo.name, - showBannerWarning: false, - }); - } else { - await mailer.sendNewDeviceLoginEmail( - account.emails, - account, - emailOptions - ); - } - } catch (err) { - log.trace('Session.verify_push.sendNewDeviceLoginEmail.error', { - error: err, - }); - } - } - return {}; }, }, diff --git a/packages/fxa-auth-server/lib/routes/session.spec.ts b/packages/fxa-auth-server/lib/routes/session.spec.ts index 99940acde5d..50d795db343 100644 --- a/packages/fxa-auth-server/lib/routes/session.spec.ts +++ b/packages/fxa-auth-server/lib/routes/session.spec.ts @@ -20,36 +20,6 @@ const signupCodeAccount = { tokenVerificationId: 'sometoken', }; -const MOCK_DEVICES = [ - // Current device - { - sessionTokenId: 'sessionTokenId', - name: 'foo', - type: 'desktop', - pushEndpointExpired: false, - pushPublicKey: 'foo', - uaBrowser: 'Firefox', - }, - // Only pushable device - { - sessionTokenId: 'sessionTokenId2', - name: 'foo2', - type: 'desktop', - pushEndpointExpired: false, - pushPublicKey: 'foo', - uaBrowser: 'Firefox', - }, - // Unsupported mobile device - { - sessionTokenId: 'sessionTokenId3', - name: 'foo3', - type: 'mobile', - pushEndpointExpired: false, - pushPublicKey: 'foo', - uaBrowser: 'Firefox', - }, -]; - function makeRoutes(options: any = {}) { const config = options.config || {}; config.oauth = config.oauth || {}; @@ -1236,206 +1206,3 @@ describe('/session/resend_code', () => { expect(args[0].code).toBe(expectedCode); }); }); - -describe('/session/verify/send_push', () => { - let route: any, request: any, log: any, db: any, mailer: any, push: any; - - beforeEach(() => { - db = mocks.mockDB({ ...signupCodeAccount, devices: MOCK_DEVICES }); - db.totpToken = jest.fn(() => Promise.resolve({ enabled: false })); - log = mocks.mockLog(); - mailer = mocks.mockMailer(); - push = mocks.mockPush(); - const config = { - contentServer: { url: 'http://localhost:3030' }, - }; - const routes = makeRoutes({ log, config, db, mailer, push }); - route = getRoute(routes, '/session/verify/send_push'); - - request = mocks.mockRequest({ - credentials: { - ...signupCodeAccount, - uaBrowser: 'Firefox', - id: 'sessionTokenId', - }, - log, - uaBrowser: 'Firefox', - }); - }); - - it('should send a push notification with verification code', async () => { - const response = await runTest(route, request); - expect(response).toEqual({}); - expect(db.devices).toHaveBeenCalledTimes(1); - expect(db.totpToken).toHaveBeenCalledTimes(1); - expect(db.account).toHaveBeenCalledTimes(1); - - const args = push.notifyVerifyLoginRequest.mock.calls[0]; - expect(args[0]).toBe('foo'); - expect(args[1]).toEqual([ - { - sessionTokenId: 'sessionTokenId2', - name: 'foo2', - type: 'desktop', - pushEndpointExpired: false, - pushPublicKey: 'foo', - uaBrowser: 'Firefox', - }, - ]); - expect(args[2].title).toBe('Logging in to your Mozilla account?'); - expect(args[2].body).toBe('Click here to confirm it\u2019s you'); - const url = args[2].url; - expect(url).toContain('http://localhost:3030/signin_push_code_confirm?'); - expect(url).toContain('tokenVerificationId=sometoken'); - expect(url).toMatch(/code=\d{6}/); - expect(url).toContain('uid=foo'); - expect(url).toContain('email=foo%40example.org'); - expect(url).toContain( - 'remoteMetaData=%257B%2522deviceFamily%2522%253A%2522Firefox%2522%252C%2522ipAddress%2522%253A%252263.245.221.32%2522%257D' - ); - }); - - it('should not send a push notification if TOTP token is verified and enabled', async () => { - db.totpToken = jest.fn(() => - Promise.resolve({ verified: true, enabled: true }) - ); - const response = await runTest(route, request); - expect(response).toEqual({}); - expect(db.totpToken).toHaveBeenCalledTimes(1); - expect(push.notifyVerifyLoginRequest).not.toHaveBeenCalled(); - }); -}); - -describe('/session/verify/verify_push', () => { - let route: any, - request: any, - log: any, - db: any, - mailer: any, - push: any, - customs: any; - - beforeEach(() => { - db = mocks.mockDB({ ...signupCodeAccount, devices: MOCK_DEVICES }); - db.deviceFromTokenVerificationId = jest.fn(() => - Promise.resolve(MOCK_DEVICES[1]) - ); - log = mocks.mockLog(); - mailer = mocks.mockMailer(); - push = mocks.mockPush(); - customs = mocks.mockCustoms(); - mocks.mockOAuthClientInfo(); - const config = {}; - const routes = makeRoutes({ log, config, db, mailer, push, customs }); - route = getRoute(routes, '/session/verify/verify_push'); - }); - - it('should verify push notification login request', async () => { - const expectedCode = getExpectedOtpCode({}, signupCodeAccount.emailCode); - request = mocks.mockRequest({ - log, - credentials: { - ...signupCodeAccount, - uaBrowser: 'Firefox', - id: 'sessionTokenId', - }, - payload: { - code: expectedCode, - uid: 'foo', - email: 'a@aa.com', - tokenVerificationId: 'sometoken', - }, - }); - const response = await runTest(route, request); - expect(response).toEqual({}); - - expect(customs.checkAuthenticated).toHaveBeenCalledTimes(1); - expect(customs.checkAuthenticated).toHaveBeenCalledWith( - request, - 'foo', - signupCodeAccount.email, - 'verifySessionCode' - ); - expect(db.devices).toHaveBeenCalledTimes(1); - expect(db.devices).toHaveBeenCalledWith('foo'); - expect(db.deviceFromTokenVerificationId).toHaveBeenCalledTimes(1); - expect(db.deviceFromTokenVerificationId).toHaveBeenCalledWith( - 'foo', - 'sometoken' - ); - expect(db.account).toHaveBeenCalledTimes(1); - expect(db.account).toHaveBeenNthCalledWith(1, 'foo'); - expect(db.verifyTokens).toHaveBeenCalledTimes(1); - expect(db.verifyTokens).toHaveBeenNthCalledWith( - 1, - 'sometoken', - expect.anything() - ); - - expect(push.notifyAccountUpdated).toHaveBeenCalledTimes(1); - expect(push.notifyAccountUpdated).toHaveBeenCalledWith( - 'foo', - MOCK_DEVICES, - 'accountConfirm' - ); - }); - - it('should return if session is already verified', async () => { - db.deviceFromTokenVerificationId = jest.fn(() => - Promise.resolve(undefined) - ); - request = mocks.mockRequest({ - log, - credentials: { - ...signupCodeAccount, - uaBrowser: 'Firefox', - id: 'sessionTokenId', - }, - payload: { - code: '123123', - uid: 'foo', - email: 'foo@example.org', - tokenVerificationId: 'sometoken', - }, - }); - const response = await runTest(route, request); - expect(response).toEqual({}); - expect(db.verifyTokens).not.toHaveBeenCalled(); - }); - - it('should fail if invalid code', async () => { - request = mocks.mockRequest({ - log, - credentials: { - ...signupCodeAccount, - uaBrowser: 'Firefox', - id: 'sessionTokenId', - }, - payload: { - code: '123123', - uid: 'foo', - email: 'foo@example.org', - tokenVerificationId: 'sometoken', - }, - }); - await expect(runTest(route, request)).rejects.toMatchObject({ - errno: 183, - message: 'Invalid or expired confirmation code', - }); - - expect(customs.checkAuthenticated).toHaveBeenCalledTimes(2); - expect(customs.checkAuthenticated).toHaveBeenCalledWith( - request, - 'foo', - 'foo@example.org', - 'verifySessionCode' - ); - - expect(customs.checkAuthenticated).toHaveBeenCalledWith( - request, - 'foo', - 'foo@example.org', - 'verifySessionCodeFailed' - ); - }); -}); diff --git a/packages/fxa-auth-server/test/mocks.js b/packages/fxa-auth-server/test/mocks.js index bca6e9bf994..e0675d12cde 100644 --- a/packages/fxa-auth-server/test/mocks.js +++ b/packages/fxa-auth-server/test/mocks.js @@ -220,7 +220,6 @@ const PUSH_METHOD_NAMES = [ 'notifyAccountDestroyed', 'notifyCommandReceived', 'notifyProfileUpdated', - 'notifyVerifyLoginRequest', 'sendPush', ]; diff --git a/packages/fxa-content-server/app/scripts/lib/experiment.js b/packages/fxa-content-server/app/scripts/lib/experiment.js index 9348020526d..3f993f2b1d3 100644 --- a/packages/fxa-content-server/app/scripts/lib/experiment.js +++ b/packages/fxa-content-server/app/scripts/lib/experiment.js @@ -25,7 +25,6 @@ const STARTUP_EXPERIMENTS = { const MANUAL_EXPERIMENTS = { emailMxValidation: BaseExperiment, qrCodeCad: BaseExperiment, - pushLogin: BaseExperiment, pocketMigration: BaseExperiment, }; diff --git a/packages/fxa-content-server/app/scripts/lib/experiments/grouping-rules/push.js b/packages/fxa-content-server/app/scripts/lib/experiments/grouping-rules/push.js deleted file mode 100644 index 1adda1c33df..00000000000 --- a/packages/fxa-content-server/app/scripts/lib/experiments/grouping-rules/push.js +++ /dev/null @@ -1,61 +0,0 @@ -/* 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 defines experiment groups the 2FA via push feature. Please reference - * https://docs.google.com/document/d/16fdARc6MC9XO7FBG9YJ4bpeqhHcv1G3ua0uzeS4EckY/edit# - * - */ -'use strict'; - -const BaseGroupingRule = require('./base'); -const GROUPS = [ - 'control', - // Treatment branches - 'treatment', -]; - -// This experiment is disabled by default. If you would like to go through -// the flow, load email-first screen and append query params -// `?forceExperiment=pushLogin&forceExperimentGroup=treatment` -const ROLLOUT_RATE = 0.0; - -module.exports = class PushLogin extends BaseGroupingRule { - constructor() { - super(); - this.name = 'pushLogin'; - - // Easier to set class properties for testability - this.groups = GROUPS; - this.rolloutRate = ROLLOUT_RATE; - } - - /** - * For this experiment, we are doing a staged rollout.` - * - * @param {Object} subject data used to decide - * @param {Boolean} isSync is this a sync signup? - * @returns {Any} - */ - choose(subject = {}) { - let choice = false; - const { isSync } = subject; - - // Only enroll for sync users - if (!isSync) { - return false; - } - - // TODO: Find out how Softvision plans to test and verify this in - // if(this.isTestEmail(subject.account.get('email'))) { - // return 'push'; - // } - - if (this.bernoulliTrial(this.rolloutRate, subject.uniqueUserId)) { - choice = this.uniformChoice(GROUPS, subject.uniqueUserId); - } - - return choice; - } -}; diff --git a/packages/fxa-content-server/app/scripts/lib/fxa-client.js b/packages/fxa-content-server/app/scripts/lib/fxa-client.js index cfe9aba52b4..de52753df85 100644 --- a/packages/fxa-content-server/app/scripts/lib/fxa-client.js +++ b/packages/fxa-content-server/app/scripts/lib/fxa-client.js @@ -1362,14 +1362,6 @@ FxaClientWrapper.prototype = { */ createCadReminder: createClientDelegate('createCadReminder'), - /** - * Sends a push notification to compatible devices that can verify a login - * request - * - * @returns {Promise} resolves with response when complete. - */ - sendPushLoginRequest: createClientDelegate('sendPushLoginRequest'), - finishSetup: withClient((client, relier, token, email, password) => { return client.finishSetup(token, email, password).then((accountData) => { return getUpdatedSessionData(email, relier, accountData); diff --git a/packages/fxa-content-server/app/scripts/lib/router.js b/packages/fxa-content-server/app/scripts/lib/router.js index 4c66ea0cd40..11d16e2a5b0 100644 --- a/packages/fxa-content-server/app/scripts/lib/router.js +++ b/packages/fxa-content-server/app/scripts/lib/router.js @@ -396,9 +396,6 @@ Router = Router.extend({ ); }, - 'push/confirm_login(/)': createViewHandler('push/confirm_login'), - 'push/send_login(/)': createViewHandler('push/send_login'), - 'push/completed(/)': createViewHandler('push/completed'), 'primary_email_verified(/)': function () { this.createReactOrBackboneViewHandler( 'primary_email_verified', @@ -580,20 +577,6 @@ Router = Router.extend({ } ); }, - 'signin_push_code(/)': function () { - this.createReactViewHandler('signin_push_code', { - ...Url.searchParams(this.window.location.search), - // for subplat redirect only - ...(this.relier.get('redirectTo') && { - redirect_to: this.relier.get('redirectTo'), - }), - }); - }, - 'signin_push_code_confirm(/)': function () { - this.createReactViewHandler('signin_push_code_confirm', { - ...Url.searchParams(this.window.location.search), - }); - }, 'signin_unblock(/)': function () { this.createReactOrBackboneViewHandler( 'signin_unblock', diff --git a/packages/fxa-content-server/app/scripts/models/account.js b/packages/fxa-content-server/app/scripts/models/account.js index 507b8d3a682..1b3a63a9b58 100644 --- a/packages/fxa-content-server/app/scripts/models/account.js +++ b/packages/fxa-content-server/app/scripts/models/account.js @@ -1704,14 +1704,6 @@ const Account = Backbone.Model.extend( return this._fxaClient.createCadReminder(this.get('sessionToken')); }, - /** - * Sends a push notification to verify a login request. - * - * @returns {Promise} resolves with response when complete. - */ - sendPushLoginRequest() { - return this._fxaClient.sendPushLoginRequest(this.get('sessionToken')); - }, }, { ALLOWED_KEYS: ALLOWED_KEYS, diff --git a/packages/fxa-content-server/app/scripts/templates/push/completed.mustache b/packages/fxa-content-server/app/scripts/templates/push/completed.mustache deleted file mode 100644 index a0f657568e8..00000000000 --- a/packages/fxa-content-server/app/scripts/templates/push/completed.mustache +++ /dev/null @@ -1,13 +0,0 @@ -
-
-

{{#t}}Sign-in confirmed{{/t}}

-
- -
-
- - - -

{{#t}}Please close this page and continue on the other device.{{/t}}

-
-
diff --git a/packages/fxa-content-server/app/scripts/templates/push/confirm_login.mustache b/packages/fxa-content-server/app/scripts/templates/push/confirm_login.mustache deleted file mode 100644 index 122b2a69023..00000000000 --- a/packages/fxa-content-server/app/scripts/templates/push/confirm_login.mustache +++ /dev/null @@ -1,25 +0,0 @@ -
-
-

{{#t}}New sign-in to Firefox{{/t}}

-
- -
-
-
- -
-

{{#t}}For added security, please confirm this sign-in to begin syncing with this device:{{/t}}

- - - -
- -
- -

{{#unsafeTranslate}}If you suspect that someone is trying to gain access to your account, please change your password.{{/unsafeTranslate}}

- -
-
-
diff --git a/packages/fxa-content-server/app/scripts/templates/push/send_login.mustache b/packages/fxa-content-server/app/scripts/templates/push/send_login.mustache deleted file mode 100644 index e5a03cbd922..00000000000 --- a/packages/fxa-content-server/app/scripts/templates/push/send_login.mustache +++ /dev/null @@ -1,21 +0,0 @@ - diff --git a/packages/fxa-content-server/app/scripts/views/mixins/push-login-experiment-mixin.js b/packages/fxa-content-server/app/scripts/views/mixins/push-login-experiment-mixin.js deleted file mode 100644 index ce8bada49cc..00000000000 --- a/packages/fxa-content-server/app/scripts/views/mixins/push-login-experiment-mixin.js +++ /dev/null @@ -1,18 +0,0 @@ -/* 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 ExperimentMixin from './experiment-mixin'; -const EXPERIMENT_NAME = 'pushLogin'; - -export default { - dependsOn: [ExperimentMixin], - - isInPushLoginExperiment() { - const experimentGroup = this.getAndReportExperimentGroup(EXPERIMENT_NAME, { - isSync: this.relier.isSync(), - }); - - return experimentGroup === 'treatment'; - }, -}; diff --git a/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js b/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js index 10c765efb12..0abc76fc9ee 100644 --- a/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js +++ b/packages/fxa-content-server/app/scripts/views/mixins/signin-mixin.js @@ -11,10 +11,9 @@ import NavigateBehavior from '../behaviors/navigate'; import ResumeTokenMixin from './resume-token-mixin'; import VerificationMethods from '../../lib/verification-methods'; import VerificationReasons from '../../lib/verification-reasons'; -import PushLoginExperiment from './push-login-experiment-mixin'; export default { - dependsOn: [ResumeTokenMixin, PushLoginExperiment], + dependsOn: [ResumeTokenMixin], /** * Sign in a user @@ -188,10 +187,6 @@ export default { (verificationReason === VerificationReasons.CHANGE_PASSWORD && verificationMethod === VerificationMethods.EMAIL_OTP) ) { - if (this.isInPushLoginExperiment()) { - return this.navigate('/push/send_login'); - } - return this.navigate('signin_token_code', { account }); } diff --git a/packages/fxa-content-server/app/scripts/views/push/completed.js b/packages/fxa-content-server/app/scripts/views/push/completed.js deleted file mode 100644 index 3a949c0a72c..00000000000 --- a/packages/fxa-content-server/app/scripts/views/push/completed.js +++ /dev/null @@ -1,22 +0,0 @@ -/* 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 FormView from '../form'; -import Template from '../../templates/push/completed.mustache'; - -class CompletedPushLoginView extends FormView { - template = Template; - - beforeRender() { - const account = this.getSignedInAccount(); - // If no user is logged in redirect to the login page and set the `redirectTo` property - // to current url. After a user has logged in, they will be redirected back to this page. - if (account && account.isDefault()) { - this.relier.set('redirectTo', this.window.location.href); - return this.navigate('/'); - } - } -} - -export default CompletedPushLoginView; diff --git a/packages/fxa-content-server/app/scripts/views/push/confirm_login.js b/packages/fxa-content-server/app/scripts/views/push/confirm_login.js deleted file mode 100644 index 93eee1bda12..00000000000 --- a/packages/fxa-content-server/app/scripts/views/push/confirm_login.js +++ /dev/null @@ -1,59 +0,0 @@ -/* 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 { assign } from 'underscore'; -import FormView from '../form'; -import preventDefaultThen from '../decorators/prevent_default_then'; -import Template from '../../templates/push/confirm_login.mustache'; -import DeviceBeingPairedTemplate from '../../templates/partial/device-being-paired.mustache'; -import Url from '../../lib/url'; - -class ConfirmPushLoginView extends FormView { - template = Template; - - events = assign(this.events, { - 'click #change-password': preventDefaultThen('changePassword'), - }); - - initialize(options = {}) { - const params = Url.searchParams(this.window.location.search); - const ua = options.ua || JSON.parse(params.ua); - - const location = params.location ? JSON.parse(params.location) : {}; - const ip = options.ip || params.ip; - this.code = options.code || params.code; - - this.deviceContext = { - family: ua.uaBrowser, - OS: ua.uaOS, - ipAddress: ip, - ...location, - }; - } - - setInitialContext(context) { - context.set({ - unsafeDeviceBeingPairedHTML: this.renderTemplate( - DeviceBeingPairedTemplate, - this.deviceContext - ), - }); - } - - changePassword() {} - - submit() { - const account = this.getSignedInAccount(); - return account - .verifySignUp(this.code) - .then(() => { - return this.navigate('/push/completed'); - }) - .catch((err) => { - this.displayError(err); - }); - } -} - -export default ConfirmPushLoginView; diff --git a/packages/fxa-content-server/app/scripts/views/push/send_login.js b/packages/fxa-content-server/app/scripts/views/push/send_login.js deleted file mode 100644 index 01e4568b7bc..00000000000 --- a/packages/fxa-content-server/app/scripts/views/push/send_login.js +++ /dev/null @@ -1,77 +0,0 @@ -/* 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 { assign } from 'underscore'; -import FormView from '../form'; -import preventDefaultThen from '../decorators/prevent_default_then'; -import Template from '../../templates/push/send_login.mustache'; -import SessionVerificationPollMixin from '../mixins/session-verification-poll-mixin'; -import Cocktail from '../../lib/cocktail'; -import FlowEventsMixin from '../mixins/flow-events-mixin'; - -const proto = FormView.prototype; - -class SendPushLoginView extends FormView { - template = Template; - - events = assign(this.events, { - 'click #resend': preventDefaultThen('resend'), - 'click #send-email': preventDefaultThen('useEmailCode'), - }); - - beforeRender() { - const account = this.getSignedInAccount(); - return account - .sendPushLoginRequest() - .then(() => this.invokeBrokerMethod('beforeSignIn', account)); - } - - afterVisible() { - const account = this.getSignedInAccount(); - return proto.afterVisible - .call(this) - .then(() => this.broker.persistVerificationData(account)) - .then(() => - this.invokeBrokerMethod('beforeSignUpConfirmationPoll', account) - ) - .then(() => { - return this.waitForSessionVerification(account, () => { - this.logViewEvent('verification.success'); - this.notifier.trigger('verification.success'); - - return this.invokeBrokerMethod( - 'afterCompleteSignInWithCode', - account - ); - }); - }); - } - - resend() { - const account = this.getSignedInAccount(); - return account - .sendPushLoginRequest() - .then(() => { - this.displaySuccess('Notification sent'); - }) - .catch(() => { - this.displayError('Something went wrong'); - }); - } - - useEmailCode() { - const account = this.getSignedInAccount(); - return account.verifySessionResendCode().then(() => { - return this.navigate('/signin_token_code'); - }); - } -} - -Cocktail.mixin( - SendPushLoginView, - FlowEventsMixin, - SessionVerificationPollMixin -); - -export default SendPushLoginView; diff --git a/packages/fxa-content-server/app/tests/spec/lib/experiment.js b/packages/fxa-content-server/app/tests/spec/lib/experiment.js index 7c0eed09b70..3a9526eac47 100644 --- a/packages/fxa-content-server/app/tests/spec/lib/experiment.js +++ b/packages/fxa-content-server/app/tests/spec/lib/experiment.js @@ -89,9 +89,15 @@ describe('lib/experiment', () => { describe('createExperiment', () => { it('creates an experiment, only once, notifies of flow.initialize', () => { - const firstExperiment = expInt.createExperiment('pushLogin', 'treatment'); + const firstExperiment = expInt.createExperiment( + 'emailMxValidation', + 'treatment' + ); assert.ok(firstExperiment); - const secondExperiment = expInt.createExperiment('pushLogin', 'treatment'); + const secondExperiment = expInt.createExperiment( + 'emailMxValidation', + 'treatment' + ); // It's the same object, not updated assert.strictEqual(firstExperiment, secondExperiment); assert.isTrue(notifier.trigger.calledOnceWith('flow.initialize')); diff --git a/packages/fxa-content-server/app/tests/spec/lib/experiments/grouping-rules/push.js b/packages/fxa-content-server/app/tests/spec/lib/experiments/grouping-rules/push.js deleted file mode 100644 index 171e42a434e..00000000000 --- a/packages/fxa-content-server/app/tests/spec/lib/experiments/grouping-rules/push.js +++ /dev/null @@ -1,50 +0,0 @@ -/* 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 { assert } from 'chai'; -import Experiment from 'lib/experiments/grouping-rules/push'; - -describe('lib/experiments/grouping-rules/push', () => { - let experiment; - - beforeEach(() => { - experiment = new Experiment(); - }); - - describe('choose', () => { - it('returns false if not Sync', () => { - assert.isFalse( - experiment.choose({ - experimentGroupingRules: { choose: () => experiment.name }, - isSync: false, - uniqueUserId: 'user-id', - }) - ); - }); - - it('returns false if rollout 0%', () => { - experiment.rolloutRate = 0; - assert.isFalse( - experiment.choose({ - experimentGroupingRules: { choose: () => experiment.name }, - isSync: true, - uniqueUserId: 'user-id', - }) - ); - }); - - it('returns treatment if rollout 100%', () => { - experiment.rolloutRate = 1; - assert.isTrue( - experiment.groups.includes( - experiment.choose({ - experimentGroupingRules: { choose: () => experiment.name }, - isSync: true, - uniqueUserId: 'user-id', - }) - ) - ); - }); - }); -}); diff --git a/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js b/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js index a9922f72057..6d5b8eb5851 100644 --- a/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js +++ b/packages/fxa-content-server/app/tests/spec/views/mixins/signin-mixin.js @@ -88,7 +88,6 @@ describe('views/mixins/signin-mixin', function () { signIn: SignInMixin.signIn, unsafeDisplayError: sinon.spy(), user: user, - isInPushLoginExperiment: sinon.spy(), isInReactExperiment: () => false, }; diff --git a/packages/fxa-content-server/app/tests/spec/views/push/completed.js b/packages/fxa-content-server/app/tests/spec/views/push/completed.js deleted file mode 100644 index f23dface5c6..00000000000 --- a/packages/fxa-content-server/app/tests/spec/views/push/completed.js +++ /dev/null @@ -1,76 +0,0 @@ -/* 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 { assert } from 'chai'; -import $ from 'jquery'; -import Account from 'models/account'; -import Backbone from 'backbone'; -import Metrics from 'lib/metrics'; -import Relier from 'models/reliers/relier'; -import View from 'views/push/completed'; -import _ from 'underscore'; -import sinon from 'sinon'; - -describe('views/push/completed', () => { - let account; - let model; - let relier; - let view; - let notifier; - let metrics; - - beforeEach(() => { - account = new Account({ - email: 'a@a.com', - uid: 'uid', - }); - - relier = new Relier({}); - model = new Backbone.Model({ - account, - }); - - notifier = _.extend({}, Backbone.Events); - metrics = new Metrics({ notifier }); - - view = new View({ - model, - relier, - notifier, - metrics, - }); - - sinon.stub(view, 'getSignedInAccount').callsFake(() => account); - - return view.render().then(() => $('#container').html(view.$el)); - }); - - afterEach(() => { - view.remove(); - view.destroy(); - view = null; - }); - - describe('render', () => { - it('renders the view', () => { - assert.lengthOf(view.$('#push-auth-complete-header'), 1); - assert.include( - view.$('.verification-message').text(), - 'Please close this page' - ); - }); - - describe('without an account', () => { - beforeEach(() => { - account = new Account({}); - sinon.spy(view, 'navigate'); - return view.render(); - }); - - it('redirects to the email first page', () => { - assert.isTrue(view.navigate.calledWith('/')); - }); - }); - }); -}); diff --git a/packages/fxa-content-server/app/tests/spec/views/push/confirm_login.js b/packages/fxa-content-server/app/tests/spec/views/push/confirm_login.js deleted file mode 100644 index 00191c2c17b..00000000000 --- a/packages/fxa-content-server/app/tests/spec/views/push/confirm_login.js +++ /dev/null @@ -1,123 +0,0 @@ -/* 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 _ from 'underscore'; -import { assert } from 'chai'; -import Account from 'models/account'; -import Backbone from 'backbone'; -import BaseBroker from 'models/auth_brokers/base'; -import Metrics from 'lib/metrics'; -import Relier from 'models/reliers/relier'; -import sinon from 'sinon'; -import User from 'models/user'; -import View from 'views/push/confirm_login'; -import WindowMock from '../../../mocks/window'; -import $ from 'jquery'; - -describe('views/push/confirm_login', () => { - let account; - let broker; - let metrics; - let model; - let notifier; - let relier; - let user; - let view; - let windowMock; - const ua = { - uaBrowser: 'Firefox', - uaOS: 'OSX', - }; - - beforeEach(() => { - windowMock = new WindowMock(); - - relier = new Relier({ - window: windowMock, - }); - - broker = new BaseBroker({ - relier: relier, - window: windowMock, - }); - - account = new Account({ - email: 'a@a.com', - uid: 'uid', - }); - - model = new Backbone.Model({ - account, - }); - - notifier = _.extend({}, Backbone.Events); - metrics = new Metrics({ notifier }); - - user = new User(); - - view = new View({ - broker, - metrics, - model, - notifier, - relier, - user, - window: windowMock, - ua, - ip: '123.123.123.123', - code: 'validCode', - }); - - sinon.stub(view, 'getSignedInAccount').callsFake(() => account); - - return view.render().then(() => $('#container').html(view.$el)); - }); - - afterEach(() => { - metrics.destroy(); - view.remove(); - view.destroy(); - view = metrics = null; - }); - - describe('render', () => { - it('renders the view', () => { - assert.lengthOf(view.$('#fxa-push-confirm-login-header'), 1); - assert.include( - view.$('.verification-message').text(), - 'please confirm this sign-in' - ); - assert.lengthOf(view.$('#submit-btn'), 1); - assert.lengthOf(view.$('#change-password'), 1); - assert.include(view.$('.push-confirm-login-device').text(), 'Firefox'); - assert.include( - view.$('.push-confirm-login-device').text(), - '123.123.123.123' - ); - assert.include( - view.$('.push-confirm-login-device').text(), - 'Location unknown' - ); - }); - }); - - describe('submit', () => { - describe('success', () => { - beforeEach(() => { - sinon.stub(account, 'verifySignUp').callsFake(() => Promise.resolve()); - sinon.spy(view, 'navigate'); - - return view.submit(); - }); - - it('calls correct methods', () => { - assert.isTrue( - account.verifySignUp.calledWith('validCode'), - 'verify with correct code' - ); - assert.isTrue(view.navigate.calledOnceWith('/push/completed')); - }); - }); - }); -}); diff --git a/packages/fxa-content-server/app/tests/spec/views/push/send_login.js b/packages/fxa-content-server/app/tests/spec/views/push/send_login.js deleted file mode 100644 index 2ea3b80738f..00000000000 --- a/packages/fxa-content-server/app/tests/spec/views/push/send_login.js +++ /dev/null @@ -1,155 +0,0 @@ -/* 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 _ from 'underscore'; -import { assert } from 'chai'; -import Account from 'models/account'; -import Backbone from 'backbone'; -import BaseBroker from 'models/auth_brokers/base'; -import Metrics from 'lib/metrics'; -import Relier from 'models/reliers/relier'; -import sinon from 'sinon'; -import User from 'models/user'; -import View from 'views/push/send_login'; -import WindowMock from '../../../mocks/window'; -import SentryMetrics from 'lib/sentry'; -import SessionVerificationPoll from 'models/polls/session-verification'; - -describe('views/push/send_login', () => { - let account; - let broker; - let metrics; - let model; - let notifier; - let relier; - let user; - let view; - let windowMock; - let sentryMetrics; - let sessionVerificationPoll; - - beforeEach(async () => { - windowMock = new WindowMock(); - - relier = new Relier({ - window: windowMock, - }); - - broker = new BaseBroker({ - relier: relier, - window: windowMock, - }); - - account = new Account({ - email: 'a@a.com', - uid: 'uid', - }); - - model = new Backbone.Model({ - account: account, - lastPage: 'signin', - password: 'password', - }); - - notifier = _.extend({}, Backbone.Events); - sentryMetrics = new SentryMetrics(); - metrics = new Metrics({ notifier, sentryMetrics }); - - user = new User(); - - sessionVerificationPoll = new SessionVerificationPoll( - {}, - { - account, - pollIntervalInMS: 2, - window: windowMock, - } - ); - - view = new View({ - broker, - canGoBack: true, - metrics, - model, - notifier, - relier, - user, - viewName: 'send-login', - window: windowMock, - sessionVerificationPoll, - }); - - sinon.stub(view, 'getSignedInAccount').callsFake(() => account); - sinon - .stub(account, 'sendPushLoginRequest') - .callsFake(() => Promise.resolve()); - sinon - .stub(view, '_handleSessionVerificationPollErrors') - .callsFake(() => {}); - sinon.stub(sessionVerificationPoll, 'start').callsFake(() => {}); - - return view.render(); - }); - - afterEach(function () { - metrics.destroy(); - view.remove(); - view.destroy(); - view = metrics = null; - }); - - describe('render', () => { - it('renders the view', () => { - assert.lengthOf(view.$('#fxa-push-send-login-header'), 1); - assert.include( - view.$('.verification-message').text(), - 'Check your connected Firefox devices' - ); - assert.lengthOf(view.$('#resend'), 1); - assert.lengthOf(view.$('#send-email'), 1); - }); - - it('sends push notification to account', () => { - assert.isTrue(account.sendPushLoginRequest.calledOnce); - }); - }); - - describe('afterVisible', () => { - beforeEach(async () => { - sinon.spy(broker, 'persistVerificationData'); - sinon.spy(view, 'waitForSessionVerification'); - sinon.spy(view, 'invokeBrokerMethod'); - return view.afterVisible().then(() => { - // simulate account being verified - return sessionVerificationPoll.trigger('verified'); - }); - }); - - it('starts polling for session to be verified', () => { - assert.isTrue(broker.persistVerificationData.calledOnceWith(account)); - assert.isTrue(view.waitForSessionVerification.calledOnce); - assert.isTrue(view.invokeBrokerMethod.calledTwice); - const args = view.invokeBrokerMethod.args; - assert.equal(args[0][0], 'beforeSignUpConfirmationPoll'); - assert.equal(args[1][0], 'afterCompleteSignInWithCode'); - }); - }); - - describe('resend', () => { - describe('success', () => { - beforeEach(() => { - sinon.spy(view, 'displaySuccess'); - return view.render().then(() => { - account.sendPushLoginRequest.resetHistory(); - return view.resend(); - }); - }); - - it('calls correct methods', () => { - assert.isTrue(account.sendPushLoginRequest.calledOnce); - assert.isTrue(view.displaySuccess.calledOnce); - }); - }); - }); -}); diff --git a/packages/fxa-content-server/app/tests/test_start.js b/packages/fxa-content-server/app/tests/test_start.js index 4f62163bbe6..80592952f31 100644 --- a/packages/fxa-content-server/app/tests/test_start.js +++ b/packages/fxa-content-server/app/tests/test_start.js @@ -58,7 +58,6 @@ require('./spec/lib/experiments/grouping-rules/base'); require('./spec/lib/experiments/grouping-rules/communication-prefs'); require('./spec/lib/experiments/grouping-rules/index'); require('./spec/lib/experiments/grouping-rules/is-sampled-user'); -require('./spec/lib/experiments/grouping-rules/push'); require('./spec/lib/experiments/grouping-rules/sentry'); require('./spec/lib/fxa-client'); // lib/glean spec skipped in the in-browser mocha bundle. Under @mozilla/glean @@ -238,9 +237,6 @@ require('./spec/views/post_verify/secondary_email/add_secondary_email'); require('./spec/views/post_verify/secondary_email/confirm_secondary_email'); require('./spec/views/post_verify/third_party_auth/set_password'); require('./spec/views/progress_indicator'); -require('./spec/views/push/confirm_login'); -require('./spec/views/push/send_login'); -require('./spec/views/push/completed'); require('./spec/views/ready'); require('./spec/views/report_sign_in'); require('./spec/views/reset_password'); diff --git a/packages/fxa-content-server/server/lib/routes/react-app/content-server-routes.js b/packages/fxa-content-server/server/lib/routes/react-app/content-server-routes.js index 4ffcda8255d..19fd796751c 100644 --- a/packages/fxa-content-server/server/lib/routes/react-app/content-server-routes.js +++ b/packages/fxa-content-server/server/lib/routes/react-app/content-server-routes.js @@ -52,9 +52,6 @@ const FRONTEND_ROUTES = [ 'post_verify/third_party_auth/set_password', 'primary_email_verified', 'signup_confirmed_sync', // React app only - 'push/completed', - 'push/confirm_login', - 'push/send_login', 'report_signin', 'reset_password', 'reset_password_confirmed', @@ -68,8 +65,6 @@ const FRONTEND_ROUTES = [ 'signin', 'signin_bounced', 'signin_token_code', - 'signin_push_code', - 'signin_push_code_confirm', 'signin_totp_code', 'signin_recovery_code', 'signin_recovery_choice', diff --git a/packages/fxa-content-server/server/lib/routes/react-app/index.js b/packages/fxa-content-server/server/lib/routes/react-app/index.js index d4e268019ac..abd69a1bf70 100644 --- a/packages/fxa-content-server/server/lib/routes/react-app/index.js +++ b/packages/fxa-content-server/server/lib/routes/react-app/index.js @@ -95,8 +95,6 @@ const getReactRouteGroups = (showReactApp, reactRoute) => { 'inline_totp_setup', 'inline_recovery_setup', 'inline_recovery_key_setup', - 'signin_push_code', - 'signin_push_code_confirm', 'signin_passwordless_code', 'oauth/signin_passwordless_code', ]), diff --git a/packages/fxa-settings/src/components/App/index.tsx b/packages/fxa-settings/src/components/App/index.tsx index 8b1c842141f..36a132686a2 100644 --- a/packages/fxa-settings/src/components/App/index.tsx +++ b/packages/fxa-settings/src/components/App/index.tsx @@ -127,12 +127,6 @@ const SigninTokenCodeContainer = lazy( const SigninTotpCodeContainer = lazy( () => import('../../pages/Signin/SigninTotpCode/container') ); -const SigninPushCodeContainer = lazy( - () => import('../../pages/Signin/SigninPushCode/container') -); -const SigninPushCodeConfirmContainer = lazy( - () => import('../../pages/Signin/SigninPushCodeConfirm/container') -); const SigninUnblockContainer = lazy( () => import('../../pages/Signin/SigninUnblock/container') ); @@ -720,14 +714,6 @@ const AuthAndAccountSetupRoutes = ({ path="/signin_totp_code/*" {...{ integration, serviceName, setCurrentSplitLayout }} /> - - \ No newline at end of file diff --git a/packages/fxa-settings/src/components/images/index.tsx b/packages/fxa-settings/src/components/images/index.tsx index 51f53310a1e..b7c5f66b37b 100644 --- a/packages/fxa-settings/src/components/images/index.tsx +++ b/packages/fxa-settings/src/components/images/index.tsx @@ -12,7 +12,6 @@ import { ReactComponent as HeartsBroken } from './graphic_hearts_broken.svg'; import { ReactComponent as HeartsVerified } from './graphic_hearts_verified.svg'; import { ReactComponent as BackupCodes } from './graphic_backup_codes.min.svg'; import { ReactComponent as TwoFactorAuth } from './graphic_two_factor_auth.svg'; -import { ReactComponent as PushFactorAuth } from './graphic_push_factor_auth.svg'; import { ReactComponent as Mail } from './graphic_mail.svg'; import { ReactComponent as Key } from './graphic_recovery_key.min.svg'; import { ReactComponent as Password } from './graphic_password.min.svg'; @@ -115,15 +114,6 @@ export const TwoFactorAuthImage = ({ className, ariaHidden }: ImageProps) => ( /> ); -export const PushAuthImage = ({ className, ariaHidden }: ImageProps) => ( - -); - export const MailImage = ({ className, ariaHidden }: ImageProps) => ( { - return { - ...jest.requireActual('../../../models'), - useAuthClient: () => { - return { - checkTotpTokenExists: jest - .fn() - .mockResolvedValue({ verified: mockHasTotpAuthClient }), - sessionStatus: jest.fn().mockResolvedValue({ - state: mockSessionStatus, - }), - sendLoginPushRequest: mockSendLoginPushRequest, - }; - }, - useSensitiveDataClient: jest.fn(), - }; -}); - -// Set this when testing location state -let mockLocationState = {}; -const mockLocation = () => { - return { - pathname: '/signin_push_code', - state: mockLocationState, - }; -}; -const mockNavigate = jest.fn(); -jest.mock('@reach/router', () => { - return { - __esModule: true, - ...jest.requireActual('@reach/router'), - useNavigate: () => mockNavigate, - useLocation: () => mockLocation(), - }; -}); - -let currentSigninPushCodeProps: SigninPushCodeProps | undefined; -let moduleMock: jest.SpyInstance; -function mockSigninPushCodeModule() { - currentSigninPushCodeProps = undefined; - moduleMock = jest - .spyOn(SigninPushCodeModule, 'default') - .mockImplementation((props: SigninPushCodeProps) => { - currentSigninPushCodeProps = props; - return
signin push code mock
; - }); -} - -function mockReactUtilsModule() { - jest.spyOn(ReactUtils, 'hardNavigate').mockImplementation(() => {}); -} - -// Set this when testing local storage -function mockCurrentAccount(storedAccount = { uid: '123' }) { - jest.spyOn(CacheModule, 'currentAccount').mockReturnValue(storedAccount); -} - -function resetMockSensitiveDataClient() { - (useSensitiveDataClient as jest.Mock).mockImplementation( - () => mockSensitiveDataClient - ); - mockSensitiveDataClient.getDataType = jest.fn().mockReturnValue({ - keyFetchToken: MOCK_KEY_FETCH_TOKEN, - unwrapBKey: MOCK_UNWRAP_BKEY, - }); -} - -async function render() { - renderWithLocalizationProvider( - - - - ); -} - -describe('SigninPushCode container', () => { - beforeEach(() => { - applyDefaultMocks(); - }); - - describe('initial states', () => { - describe('email', () => { - it('can be set from router state', async () => { - mockLocationState = createMockSigninLocationState(); - render(); - await waitFor(() => { - expect(CacheModule.currentAccount).not.toHaveBeenCalled(); - }); - expect(currentSigninPushCodeProps?.signinState.email).toBe(MOCK_EMAIL); - expect(SigninPushCodeModule.default).toHaveBeenCalled(); - }); - it('router state takes precedence over local storage', async () => { - mockLocationState = createMockSigninLocationState(); - render(); - expect(CacheModule.currentAccount).not.toHaveBeenCalled(); - await waitFor(() => { - expect(currentSigninPushCodeProps?.signinState.email).toBe( - MOCK_EMAIL - ); - }); - expect(SigninPushCodeModule.default).toHaveBeenCalled(); - }); - it('is read from localStorage if email is not provided via router state', async () => { - mockLocationState = {}; - mockCurrentAccount(MOCK_STORED_ACCOUNT); - render(); - expect(CacheModule.currentAccount).toHaveBeenCalled(); - await waitFor(() => { - expect(currentSigninPushCodeProps?.signinState.email).toBe( - MOCK_STORED_ACCOUNT.email - ); - }); - expect(SigninPushCodeModule.default).toHaveBeenCalled(); - }); - it('is handled if not provided in location state or local storage', async () => { - mockLocationState = {}; - render(); - expect(CacheModule.currentAccount).toHaveBeenCalled(); - expect(mockNavigate).toHaveBeenCalledWith('/'); - expect(SigninPushCodeModule.default).not.toHaveBeenCalled(); - }); - }); - - describe('totp status', () => { - beforeEach(() => { - mockLocationState = createMockSigninLocationState(); - }); - - it('redirects to totp screen if user has totp enabled', async () => { - mockHasTotpAuthClient = true; - render(); - - await waitFor(() => { - expect(mockNavigate).toHaveBeenCalledWith('/signin_totp_code', { - state: mockLocationState, - }); - }); - }); - - it('does not redirect with totp false', async () => { - mockHasTotpAuthClient = false; - render(); - - await waitFor(() => { - expect(mockNavigate).not.toHaveBeenCalled(); - }); - }); - }); - }); - - describe('render', () => { - beforeEach(() => { - moduleMock.mockRestore(); - }); - - it('sends push notification', async () => { - mockSessionStatus = 'false'; - mockLocationState = createMockSigninLocationState(); - render(); - - await waitFor(() => expect(mockSendLoginPushRequest).toHaveBeenCalled()); - }); - - it('navigates when session verified', async () => { - mockSessionStatus = 'verified'; - mockLocationState = createMockSigninLocationState(); - render(); - - await waitFor(() => - expect(ReactUtils.hardNavigate).toHaveBeenCalledWith( - '/pair?showSuccessMessage=true', - undefined, - undefined, - false - ) - ); - }); - }); -}); diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCode/container.tsx b/packages/fxa-settings/src/pages/Signin/SigninPushCode/container.tsx deleted file mode 100644 index 0771e3f5d2a..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCode/container.tsx +++ /dev/null @@ -1,142 +0,0 @@ -/* 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 { RouteComponentProps, useLocation } from '@reach/router'; -import SigninPushCode from '.'; -import { MozServices } from '../../../lib/types'; -import { getSigninState, handleNavigation } from '../utils'; -import { SigninLocationState } from '../interfaces'; -import { - Integration, - isWebIntegration, - useAuthClient, - useSensitiveDataClient, -} from '../../../models'; -import { useFinishOAuthFlowHandler } from '../../../lib/oauth/hooks'; -import AppLayout from '../../../components/AppLayout'; -import OAuthDataError from '../../../components/OAuthDataError'; -import { useWebRedirect } from '../../../lib/hooks/useWebRedirect'; -import { useEffect, useState } from 'react'; -import { useNavigateWithQuery } from '../../../lib/hooks/useNavigateWithQuery'; -import { SensitiveData } from '../../../lib/sensitive-data-client'; - -export type SigninPushCodeContainerProps = { - integration: Integration; - serviceName: MozServices; -}; - -export const SigninPushCodeContainer = ({ - integration, - serviceName, -}: SigninPushCodeContainerProps & RouteComponentProps) => { - const authClient = useAuthClient(); - const navigateWithQuery = useNavigateWithQuery(); - const { finishOAuthFlowHandler, oAuthDataError } = useFinishOAuthFlowHandler( - authClient, - integration - ); - // TODO: FXA-9177, consider using localStorage instead of location state - const location = useLocation() as ReturnType & { - state: SigninLocationState; - }; - const signinState = getSigninState(location.state); - const sensitiveDataClient = useSensitiveDataClient(); - const { unwrapBKey } = - sensitiveDataClient.getDataType(SensitiveData.Key.Auth) || {}; - - const webRedirectCheck = useWebRedirect(integration.data.redirectTo); - - const redirectTo = - isWebIntegration(integration) && webRedirectCheck?.isValid - ? integration.data.redirectTo - : ''; - - const [totpVerified, setTotpVerified] = useState(false); - useEffect(() => { - if (!signinState || !signinState.sessionToken) { - // case handled after the useEffect - return; - } - const getTotpStatus = async () => { - const { verified } = await authClient.checkTotpTokenExists( - signinState.sessionToken - ); - setTotpVerified(verified); - }; - getTotpStatus(); - }, [authClient, signinState]); - - if (oAuthDataError) { - return ; - } - - if (!signinState) { - navigateWithQuery('/'); - return ; - } - - // redirect if there is 2FA is set up for the account, - // but the session is not TOTP verified - if (totpVerified) { - navigateWithQuery('/signin_totp_code', { - state: signinState, - }); - return ; - } - - const onCodeVerified = async () => { - const navigationOptions = { - email: signinState.email, - signinData: { - ...signinState, - emailVerified: true, - sessionVerified: true, - }, - unwrapBKey, - integration, - finishOAuthFlowHandler, - queryParams: location.search, - redirectTo, - }; - await handleNavigation(navigationOptions); - }; - - const sendLoginPushNotification = async () => { - try { - const response = await authClient.sessionStatus(signinState.sessionToken); - if (response.state !== 'verified') { - await authClient.sendLoginPushRequest(signinState.sessionToken); - } - if (response.state === 'verified') { - await onCodeVerified(); - } - } catch (error) { - console.error('Error sending push notification:', error); - } - }; - - const pollSessionStatus = async () => { - try { - const response = await authClient.sessionStatus(signinState.sessionToken); - if (response.state === 'verified') { - await onCodeVerified(); - } - } catch (error) { - console.error('Error fetching session status:', error); - } - }; - - return ( - - ); -}; - -export default SigninPushCodeContainer; diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCode/en.ftl b/packages/fxa-settings/src/pages/Signin/SigninPushCode/en.ftl deleted file mode 100644 index 2cb7eeec03c..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCode/en.ftl +++ /dev/null @@ -1,8 +0,0 @@ -## SigninPushCode page -## This page is used to send a push notification to the user's device for two-factor authentication (2FA). - -signin-push-code-heading-w-default-service = Verify this login to continue to account settings -signin-push-code-heading-w-custom-service = Verify this login to continue to { $serviceName } -signin-push-code-instruction = Please check your other devices and approve this login from your { -brand-firefox } browser. -signin-push-code-did-not-recieve = Didn’t receive the notification? -signin-push-code-send-email-link = Email code diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCode/index.stories.tsx b/packages/fxa-settings/src/pages/Signin/SigninPushCode/index.stories.tsx deleted file mode 100644 index a7b1209aab9..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCode/index.stories.tsx +++ /dev/null @@ -1,17 +0,0 @@ -/* 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 React from 'react'; -import SigninPushCode from '.'; -import { Meta } from '@storybook/react'; -import { withLocalization } from 'fxa-react/lib/storybooks'; -import { Subject } from './mocks'; - -export default { - title: 'Pages/Signin/SigninPushCode', - component: SigninPushCode, - decorators: [withLocalization], -} as Meta; - -export const Default = () => ; diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCode/index.test.tsx b/packages/fxa-settings/src/pages/Signin/SigninPushCode/index.test.tsx deleted file mode 100644 index 5ad8f5e78b4..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCode/index.test.tsx +++ /dev/null @@ -1,66 +0,0 @@ -/* 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 React from 'react'; -import { screen } from '@testing-library/react'; -import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider'; // import { getFtlBundle, testAllL10n } from 'fxa-react/lib/test-utils'; -// import { FluentBundle } from '@fluent/bundle'; -import { MozServices } from '../../../lib/types'; -import { Subject } from './mocks'; - -const mockLocation = () => { - return { - pathname: '/signin_push_cpde', - }; -}; - -jest.mock('@reach/router', () => ({ - ...jest.requireActual('@reach/router'), - navigate: jest.fn(), - useLocation: () => mockLocation(), -})); - -describe('Sign in with push notification code page', () => { - // TODO: enable l10n tests when they've been updated to handle embedded tags in ftl strings - // TODO: in FXA-6461 - // let bundle: FluentBundle; - // beforeAll(async () => { - // bundle = await getFtlBundle('settings'); - // }); - - beforeEach(() => { - jest.clearAllMocks(); - jest.resetAllMocks(); - }); - - it('renders as expected', () => { - renderWithLocalizationProvider(); - // testAllL10n(screen, bundle); - - const headingEl = screen.getByRole('heading', { level: 1 }); - expect(headingEl).toHaveTextContent( - 'Verify this login to continue to account settings' - ); - screen.getByText( - 'Please check your other devices and approve this login from your Firefox browser.' - ); - - screen.getByText('Didn’t receive the notification?'); - screen.getByRole('link', { name: 'Email code' }); - }); - - it('shows the relying party in the header when a service name is provided', () => { - renderWithLocalizationProvider( - - ); - const headingEl = screen.getByRole('heading', { level: 1 }); - expect(headingEl).toHaveTextContent( - 'Verify this login to continue to Mozilla VPN' - ); - }); -}); diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCode/index.tsx b/packages/fxa-settings/src/pages/Signin/SigninPushCode/index.tsx deleted file mode 100644 index dce4a92c45b..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCode/index.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* 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 React, { useEffect } from 'react'; -import { Link, RouteComponentProps, useLocation } from '@reach/router'; -import { FtlMsg } from 'fxa-react/lib/utils'; -import { PushAuthImage } from '../../../components/images'; -import CardHeader from '../../../components/CardHeader'; -import AppLayout from '../../../components/AppLayout'; - -import { SigninPushCodeProps } from './interfaces'; - -export const viewName = 'signin-push-code'; - -const POLL_INTERVAL = 2000; // Poll every 2 seconds -const MAX_POLL_TIME = 300000; // 5 minutes - -export const SigninPushCode = ({ - signinState, - serviceName, - sendLoginPushNotification, - pollSessionStatus, -}: SigninPushCodeProps & RouteComponentProps) => { - const location = useLocation(); - - useEffect(() => { - sendLoginPushNotification(); - - const intervalId = setInterval(pollSessionStatus, POLL_INTERVAL); - const timeoutId = setTimeout( - () => clearInterval(intervalId), - MAX_POLL_TIME - ); - - return () => { - clearInterval(intervalId); - clearTimeout(timeoutId); - }; - }); - - return ( - - - -
- -
- - -

- Please check your other devices and approve this login from your - Firefox browser. -

-
- -
- -

Didn’t receive the notification?

-
- - - Email code - - -
-
- ); -}; - -export default SigninPushCode; diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCode/interfaces.ts b/packages/fxa-settings/src/pages/Signin/SigninPushCode/interfaces.ts deleted file mode 100644 index ed28cf7580b..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCode/interfaces.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* 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 { SigninLocationState } from '../interfaces'; -import { MozServices } from '../../../lib/types'; - -export type SigninPushCodeProps = { - signinState: SigninLocationState; - serviceName?: MozServices; - sendLoginPushNotification: () => void; - pollSessionStatus: () => void; -}; diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCode/mocks.tsx b/packages/fxa-settings/src/pages/Signin/SigninPushCode/mocks.tsx deleted file mode 100644 index a5fd5e8c531..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCode/mocks.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* 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 React from 'react'; -import { LocationProvider } from '@reach/router'; -import { IntegrationType } from '../../../models'; -import { SigninPushCode } from '.'; -import { SigninPushCodeProps } from './interfaces'; -import { - MOCK_EMAIL, - MOCK_KEY_FETCH_TOKEN, - MOCK_SESSION_TOKEN, - MOCK_UID, - MOCK_UNWRAP_BKEY, -} from '../../mocks'; -import { MozServices } from '../../../lib/types'; -import VerificationMethods from '../../../constants/verification-methods'; -import VerificationReasons from '../../../constants/verification-reasons'; - -export const MOCK_LOCATION_STATE = { - email: MOCK_EMAIL, - uid: MOCK_UID, - sessionToken: MOCK_SESSION_TOKEN, - emailVerified: false, - sessionVerified: false, - verificationMethod: VerificationMethods.EMAIL_OTP, -}; - -export const createMockSigninLocationState = ( - wantsKeys = false, - verificationReason?: VerificationReasons -) => { - return { - email: MOCK_EMAIL, - uid: MOCK_UID, - sessionToken: MOCK_SESSION_TOKEN, - emailVerified: false, - sessionVerified: false, - verificationReason, - ...(wantsKeys && { - keyFetchToken: MOCK_KEY_FETCH_TOKEN, - unwrapBKey: MOCK_UNWRAP_BKEY, - }), - }; -}; - -export function createMockSyncIntegration() { - return { - type: IntegrationType.SyncDesktopV3, - getService: () => MozServices.FirefoxSync, - isSync: () => true, - requiresKeys: () => true, - wantsKeys: () => true, - getCmsInfo: () => undefined, - data: {}, - }; -} - -export const Subject = ({ - serviceName = MozServices.Default, - signinState = MOCK_LOCATION_STATE, -}: Partial) => { - return ( - - Promise.resolve(), - pollSessionStatus: () => Promise.resolve(), - }} - /> - - ); -}; diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/container.test.tsx b/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/container.test.tsx deleted file mode 100644 index 51cf43238e2..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/container.test.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* 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 ReactUtils from 'fxa-react/lib/utils'; -import { SigninPushCodeConfirmContainer } from './container'; - -import { renderWithLocalizationProvider } from 'fxa-react/lib/test-utils/localizationProvider'; -import { LocationProvider } from '@reach/router'; - -import * as UseValidateModule from '../../../lib/hooks/useValidate'; -import { MOCK_HEXSTRING_32, MOCK_REMOTE_METADATA } from '../../mocks'; -import { ModelDataProvider } from '../../../lib/model-data'; -import { fireEvent, screen, waitFor } from '@testing-library/react'; - -function applyDefaultMocks() { - jest.resetAllMocks(); - jest.restoreAllMocks(); - - mockUseValidateModule(); - mockReactUtilsModule(); -} - -let mockVerifyLoginPushRequest = jest.fn().mockResolvedValue({}); -jest.mock('../../../models', () => { - return { - ...jest.requireActual('../../../models'), - useAuthClient: () => { - return { - verifyLoginPushRequest: mockVerifyLoginPushRequest, - }; - }, - }; -}); - -const mockNavigate = jest.fn(); -jest.mock('@reach/router', () => { - return { - __esModule: true, - ...jest.requireActual('@reach/router'), - useNavigate: () => mockNavigate, - }; -}); - -function mockUseValidateModule() { - jest.spyOn(UseValidateModule, 'useValidatedQueryParams').mockReturnValue({ - queryParamModel: { - code: '123456', - tokenVerificationId: MOCK_HEXSTRING_32, - remoteMetaData: MOCK_REMOTE_METADATA, - } as unknown as ModelDataProvider, - validationError: undefined, - }); -} - -function mockReactUtilsModule() { - jest.spyOn(ReactUtils, 'hardNavigate').mockImplementation(() => {}); -} - -async function render(options = {}) { - renderWithLocalizationProvider( - - - - ); -} - -describe('SigninPushCodeConfirm container', () => { - beforeEach(() => { - applyDefaultMocks(); - }); - - it('can verify push notification', async () => { - render(); - fireEvent.click(screen.getByText('Confirm login')); - await waitFor(() => { - expect(mockVerifyLoginPushRequest).toHaveBeenCalledWith( - null, - MOCK_HEXSTRING_32, - '123456' - ); - }); - screen.getByText('Your login has been approved. Please close this window.'); - }); -}); diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/container.tsx b/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/container.tsx deleted file mode 100644 index 9ba7aa06589..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/container.tsx +++ /dev/null @@ -1,64 +0,0 @@ -/* 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 React, { useState } from 'react'; -import { RouteComponentProps } from '@reach/router'; -import { useAuthClient } from '../../../models'; -import SigninPushCodeConfirm from './index'; -import { useValidatedQueryParams } from '../../../lib/hooks/useValidate'; -import { PushSigninQueryParams } from '../../../models/pages/signin/push-signin-query-params'; -import { FtlMsg } from '../../../../../fxa-react/lib/utils'; -import { sessionToken } from '../../../lib/cache'; - -export const SigninPushCodeConfirmContainer = (props: RouteComponentProps) => { - const authClient = useAuthClient(); - const { queryParamModel, validationError } = useValidatedQueryParams( - PushSigninQueryParams - ); - const { tokenVerificationId, code, remoteMetaData } = queryParamModel; - const remoteMetaDataParsed = JSON.parse(decodeURIComponent(remoteMetaData)); - const [sessionVerified, setSessionVerified] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); - - if (validationError) { - return ( -
- -

Link is damaged. Please try again.

-
-
- ); - } - - const handleSubmit = async () => { - setIsLoading(true); - try { - await authClient.verifyLoginPushRequest( - sessionToken()!, - tokenVerificationId, - code - ); - setSessionVerified(true); - } catch (error) { - setErrorMessage('Error verifying login push request'); - } finally { - setIsLoading(false); - } - }; - - return ( - - ); -}; - -export default SigninPushCodeConfirmContainer; diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/en.ftl b/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/en.ftl deleted file mode 100644 index 2fc92c3e956..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/en.ftl +++ /dev/null @@ -1,9 +0,0 @@ -## SigninPushCodeConfirmPage - -signin-push-code-confirm-instruction = Confirm your login -signin-push-code-confirm-description = We detected a login attempt from the following device. If this was you, please approve the login -signin-push-code-confirm-verifying = Verifying -signin-push-code-confirm-login = Confirm login -signin-push-code-confirm-wasnt-me = This wasn’t me, change password. -signin-push-code-confirm-login-approved = Your login has been approved. Please close this window. -signin-push-code-confirm-link-error = Link is damaged. Please try again. \ No newline at end of file diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/greencheck.svg b/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/greencheck.svg deleted file mode 100644 index 96d95e9f224..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/greencheck.svg +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/index.stories.tsx b/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/index.stories.tsx deleted file mode 100644 index a8498c1d8b9..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/index.stories.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* 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 React from 'react'; -import SigninPushCode from '.'; -import { Meta } from '@storybook/react'; -import { withLocalization } from 'fxa-react/lib/storybooks'; -import { Subject } from './mocks'; - -export default { - title: 'Pages/Signin/SigninPushCodeConfrim', - component: SigninPushCode, - decorators: [withLocalization], -} as Meta; - -export const Default = () => ; - -export const Verifying = () => ; - -export const SessionVerified = () => ; diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/index.tsx b/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/index.tsx deleted file mode 100644 index 0ec49402b91..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/index.tsx +++ /dev/null @@ -1,168 +0,0 @@ -/* 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 React from 'react'; -import { Link, RouteComponentProps } from '@reach/router'; -import { RemoteMetadata } from '../../../lib/types'; - -import AppLayout from '../../../components/AppLayout'; -import DeviceInfoBlock from '../../../components/DeviceInfoBlock'; -import { FtlMsg } from '../../../../../fxa-react/lib/utils'; - -// Reuse these images temporarily -import monitorIcon from '../../../components/Settings/BentoMenu/monitor.svg'; -import relayIcon from '../../../components/Settings/BentoMenu/relay.svg'; -import vpnIcon from '../../../components/Settings/BentoMenu/vpn-logo.svg'; -import checkmarkIcon from './greencheck.svg'; -import { LinkExternal } from 'fxa-react/components/LinkExternal'; -import Banner from '../../../components/Banner'; - -export type SigninPushCodeConfirmProps = { - authDeviceInfo: RemoteMetadata; - handleSubmit: () => void; - sessionVerified: boolean; - isLoading: boolean; - errorMessage?: string; -}; - -const Products = () => { - const products = [ - { - icon: monitorIcon, - title: 'Mozilla Monitor', - description: - 'Get notified if your information is involved in a data breach.', - link: 'https://monitor.firefox.com/', - }, - { - icon: relayIcon, - title: 'Firefox Relay', - description: - 'Keep your email address safe from spam and unwanted emails.', - link: 'https://relay.firefox.com/', - }, - { - icon: vpnIcon, - title: 'Mozilla VPN', - description: - 'Protect your internet connection and browse privately with Mozilla VPN.', - link: 'https://vpn.mozilla.org/', - }, - ]; - - // Note this isn't localized yet since the UX will most likely change - return ( -
-

- Explore products from Mozilla that protect your privacy -

- {products.map((product, index) => ( -
- {`${product.title} -
-

{product.title}

-

- {product.description} - - {' '} - Learn more - -

-
-
- ))} -
- ); -}; - -const LoginApprovedMessage = () => { - return ( -
- Checkmark Icon - -

- Your login has been approved. Please close this window. -

-
- -
- ); -}; - -const SigninPushCodeConfirm = ({ - authDeviceInfo, - handleSubmit, - sessionVerified, - isLoading, - errorMessage, -}: SigninPushCodeConfirmProps & RouteComponentProps) => { - return ( - - {sessionVerified ? ( - - ) : ( - <> - -

Confirm your login

-
- - {errorMessage && ( - - )} - - -

- We detected a login attempt from the following device. If this was - you, please approve the login. -

-
- - -
- - - - - This wasn’t me, change password. - - -
- - )} -
- ); -}; - -export default SigninPushCodeConfirm; diff --git a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/mocks.tsx b/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/mocks.tsx deleted file mode 100644 index 09a0bc5632c..00000000000 --- a/packages/fxa-settings/src/pages/Signin/SigninPushCodeConfirm/mocks.tsx +++ /dev/null @@ -1,31 +0,0 @@ -/* 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 React from 'react'; -import SigninPushCodeConfirm, { SigninPushCodeConfirmProps } from '.'; - -export const Subject = ({ - authDeviceInfo, - sessionVerified, - isLoading, -}: Partial) => { - return ( - {}} - authDeviceInfo={ - authDeviceInfo || { - deviceName: 'MacBook Pro', - deviceFamily: 'Device Family', - deviceOS: 'Device OS', - ipAddress: '123.123.123.123', - city: 'City', - region: 'Region', - country: 'Country', - } - } - /> - ); -};