зеркало из https://github.com/mozilla/fxa.git
feat(push): Add views for confirming a push notification login
This commit is contained in:
Родитель
334d907346
Коммит
5610838255
|
@ -34,6 +34,7 @@ secrets.json
|
||||||
storybooks-publish
|
storybooks-publish
|
||||||
tsconfig.tsbuildinfo
|
tsconfig.tsbuildinfo
|
||||||
tailwind.out.*
|
tailwind.out.*
|
||||||
|
.idea
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
**/node_modules
|
**/node_modules
|
||||||
|
|
|
@ -1238,4 +1238,8 @@ export default class AuthClient {
|
||||||
new Headers(headers)
|
new Headers(headers)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendPushLoginRequest(sessionToken: string) {
|
||||||
|
return this.sessionPost('/session/verify/send_push', sessionToken, {});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,8 +111,7 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
memcached: {
|
memcached: {
|
||||||
address: {
|
address: {
|
||||||
doc:
|
doc: 'Address:port of the memcached server (or `none` to disable memcached)',
|
||||||
'Address:port of the memcached server (or `none` to disable memcached)',
|
|
||||||
default: 'localhost:11211',
|
default: 'localhost:11211',
|
||||||
env: 'MEMCACHE_METRICS_CONTEXT_ADDRESS',
|
env: 'MEMCACHE_METRICS_CONTEXT_ADDRESS',
|
||||||
},
|
},
|
||||||
|
@ -180,8 +179,7 @@ const conf = convict({
|
||||||
connectionTimeout: {
|
connectionTimeout: {
|
||||||
default: '5 minutes',
|
default: '5 minutes',
|
||||||
env: 'DB_CONNECTION_TIMEOUT',
|
env: 'DB_CONNECTION_TIMEOUT',
|
||||||
doc:
|
doc: 'Timeout in milliseconds after which the mailer will stop trying to connect to the database',
|
||||||
'Timeout in milliseconds after which the mailer will stop trying to connect to the database',
|
|
||||||
format: 'duration',
|
format: 'duration',
|
||||||
},
|
},
|
||||||
poolee: {
|
poolee: {
|
||||||
|
@ -246,8 +244,7 @@ const conf = convict({
|
||||||
env: 'EMAIL_SERVICE_PORT',
|
env: 'EMAIL_SERVICE_PORT',
|
||||||
},
|
},
|
||||||
forcedEmailAddresses: {
|
forcedEmailAddresses: {
|
||||||
doc:
|
doc: 'force usage of fxa-email-service when sending emails to addresses that match this pattern',
|
||||||
'force usage of fxa-email-service when sending emails to addresses that match this pattern',
|
|
||||||
format: RegExp,
|
format: RegExp,
|
||||||
default: /emailservice\.[A-Za-z0-9._%+-]+@restmail\.net$/,
|
default: /emailservice\.[A-Za-z0-9._%+-]+@restmail\.net$/,
|
||||||
env: 'EMAIL_SERVICE_FORCE_EMAIL_REGEX',
|
env: 'EMAIL_SERVICE_FORCE_EMAIL_REGEX',
|
||||||
|
@ -560,8 +557,7 @@ const conf = convict({
|
||||||
default: '100 milliseconds',
|
default: '100 milliseconds',
|
||||||
env: 'REDIS_POOL_TIMEOUT',
|
env: 'REDIS_POOL_TIMEOUT',
|
||||||
format: 'duration',
|
format: 'duration',
|
||||||
doc:
|
doc: 'Initial backoff for Redis connection retries, increases exponentially with each attempt',
|
||||||
'Initial backoff for Redis connection retries, increases exponentially with each attempt',
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
subhubServerMessaging: {
|
subhubServerMessaging: {
|
||||||
|
@ -572,8 +568,7 @@ const conf = convict({
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
subhubUpdatesQueueUrl: {
|
subhubUpdatesQueueUrl: {
|
||||||
doc:
|
doc: 'The queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
|
||||||
'The queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
|
|
||||||
format: String,
|
format: String,
|
||||||
env: 'SUBHUB_QUEUE_URL',
|
env: 'SUBHUB_QUEUE_URL',
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -596,8 +591,7 @@ const conf = convict({
|
||||||
default: '15 minutes',
|
default: '15 minutes',
|
||||||
},
|
},
|
||||||
sessionTokenWithoutDevice: {
|
sessionTokenWithoutDevice: {
|
||||||
doc:
|
doc: 'Maximum age for session tokens without a device record, specify zero to disable',
|
||||||
'Maximum age for session tokens without a device record, specify zero to disable',
|
|
||||||
format: 'duration',
|
format: 'duration',
|
||||||
env: 'SESSION_TOKEN_WITHOUT_DEVICE_TTL',
|
env: 'SESSION_TOKEN_WITHOUT_DEVICE_TTL',
|
||||||
default: '4 weeks',
|
default: '4 weeks',
|
||||||
|
@ -624,8 +618,7 @@ const conf = convict({
|
||||||
default: 1,
|
default: 1,
|
||||||
},
|
},
|
||||||
snsTopicArn: {
|
snsTopicArn: {
|
||||||
doc:
|
doc: 'Amazon SNS topic on which to send account event notifications. Set to "disabled" to turn off the notifier',
|
||||||
'Amazon SNS topic on which to send account event notifications. Set to "disabled" to turn off the notifier',
|
|
||||||
format: String,
|
format: String,
|
||||||
env: 'SNS_TOPIC_ARN',
|
env: 'SNS_TOPIC_ARN',
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -638,36 +631,31 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
emailNotifications: {
|
emailNotifications: {
|
||||||
region: {
|
region: {
|
||||||
doc:
|
doc: 'The region where the queues live, most likely the same region we are sending email e.g. us-east-1, us-west-2',
|
||||||
'The region where the queues live, most likely the same region we are sending email e.g. us-east-1, us-west-2',
|
|
||||||
format: String,
|
format: String,
|
||||||
env: 'BOUNCE_REGION',
|
env: 'BOUNCE_REGION',
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
bounceQueueUrl: {
|
bounceQueueUrl: {
|
||||||
doc:
|
doc: 'The bounce queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
|
||||||
'The bounce queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
|
|
||||||
format: String,
|
format: String,
|
||||||
env: 'BOUNCE_QUEUE_URL',
|
env: 'BOUNCE_QUEUE_URL',
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
complaintQueueUrl: {
|
complaintQueueUrl: {
|
||||||
doc:
|
doc: 'The complaint queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
|
||||||
'The complaint queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
|
|
||||||
format: String,
|
format: String,
|
||||||
env: 'COMPLAINT_QUEUE_URL',
|
env: 'COMPLAINT_QUEUE_URL',
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
deliveryQueueUrl: {
|
deliveryQueueUrl: {
|
||||||
doc:
|
doc: 'The email delivery queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
|
||||||
'The email delivery queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
|
|
||||||
format: String,
|
format: String,
|
||||||
env: 'DELIVERY_QUEUE_URL',
|
env: 'DELIVERY_QUEUE_URL',
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
notificationQueueUrl: {
|
notificationQueueUrl: {
|
||||||
doc:
|
doc: 'Queue URL for notifications from fxa-email-service (eventually this will be the only email-related queue)',
|
||||||
'Queue URL for notifications from fxa-email-service (eventually this will be the only email-related queue)',
|
|
||||||
format: String,
|
format: String,
|
||||||
env: 'NOTIFICATION_QUEUE_URL',
|
env: 'NOTIFICATION_QUEUE_URL',
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -681,8 +669,7 @@ const conf = convict({
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
profileUpdatesQueueUrl: {
|
profileUpdatesQueueUrl: {
|
||||||
doc:
|
doc: 'The queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
|
||||||
'The queue URL to use (should include https://sqs.<region>.amazonaws.com/<account-id>/<queue-name>)',
|
|
||||||
format: String,
|
format: String,
|
||||||
env: 'PROFILE_UPDATES_QUEUE_URL',
|
env: 'PROFILE_UPDATES_QUEUE_URL',
|
||||||
default: '',
|
default: '',
|
||||||
|
@ -697,8 +684,7 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
secretBearerToken: {
|
secretBearerToken: {
|
||||||
default: 'YOU MUST CHANGE ME',
|
default: 'YOU MUST CHANGE ME',
|
||||||
doc:
|
doc: 'Secret for server-to-server bearer token auth for fxa-profile-server',
|
||||||
'Secret for server-to-server bearer token auth for fxa-profile-server',
|
|
||||||
env: 'PROFILE_SERVER_AUTH_SECRET_BEARER_TOKEN',
|
env: 'PROFILE_SERVER_AUTH_SECRET_BEARER_TOKEN',
|
||||||
format: 'String',
|
format: 'String',
|
||||||
},
|
},
|
||||||
|
@ -748,8 +734,7 @@ const conf = convict({
|
||||||
env: 'SUBHUB_ENABLED',
|
env: 'SUBHUB_ENABLED',
|
||||||
},
|
},
|
||||||
useStubs: {
|
useStubs: {
|
||||||
doc:
|
doc: 'Indicates whether to use stub methods for SubHub instead of talking to the server',
|
||||||
'Indicates whether to use stub methods for SubHub instead of talking to the server',
|
|
||||||
format: Boolean,
|
format: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
env: 'SUBHUB_USE_STUBS',
|
env: 'SUBHUB_USE_STUBS',
|
||||||
|
@ -835,8 +820,7 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
sharedSecret: {
|
sharedSecret: {
|
||||||
doc:
|
doc: 'Shared secret for authentication between backend subscription services',
|
||||||
'Shared secret for authentication between backend subscription services',
|
|
||||||
format: String,
|
format: String,
|
||||||
default: 'YOU MUST CHANGE ME',
|
default: 'YOU MUST CHANGE ME',
|
||||||
env: 'SUBSCRIPTIONS_SHARED_SECRET',
|
env: 'SUBSCRIPTIONS_SHARED_SECRET',
|
||||||
|
@ -856,8 +840,7 @@ const conf = convict({
|
||||||
transactionalEmails: {
|
transactionalEmails: {
|
||||||
// See also: https://jira.mozilla.com/browse/FXA-1148
|
// See also: https://jira.mozilla.com/browse/FXA-1148
|
||||||
enabled: {
|
enabled: {
|
||||||
doc:
|
doc: 'Indicates whether FxA sends transactional lifecycle emails for subscriptions (i.e. versus Marketing Cloud)',
|
||||||
'Indicates whether FxA sends transactional lifecycle emails for subscriptions (i.e. versus Marketing Cloud)',
|
|
||||||
format: Boolean,
|
format: Boolean,
|
||||||
env: 'SUBSCRIPTIONS_TRANSACTIONAL_EMAILS_ENABLED',
|
env: 'SUBSCRIPTIONS_TRANSACTIONAL_EMAILS_ENABLED',
|
||||||
default: false,
|
default: false,
|
||||||
|
@ -865,8 +848,7 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
currenciesToCountries: {
|
currenciesToCountries: {
|
||||||
doc:
|
doc: 'Mapping from ISO 4217 three-letter currency codes to list of ISO 3166-1 alpha-2 two-letter country codes: {"EUR": ["DE", "FR"], "USD": ["CA", "GB", "US" ]} Requirement for only one currency per country. Tested at runtime. Must be uppercased.',
|
||||||
'Mapping from ISO 4217 three-letter currency codes to list of ISO 3166-1 alpha-2 two-letter country codes: {"EUR": ["DE", "FR"], "USD": ["CA", "GB", "US" ]} Requirement for only one currency per country. Tested at runtime. Must be uppercased.',
|
|
||||||
format: Object,
|
format: Object,
|
||||||
default: {
|
default: {
|
||||||
USD: ['US', 'GB', 'NZ', 'MY', 'SG', 'CA', 'AS', 'GU', 'MP', 'PR', 'VI'],
|
USD: ['US', 'GB', 'NZ', 'MY', 'SG', 'CA', 'AS', 'GU', 'MP', 'PR', 'VI'],
|
||||||
|
@ -887,8 +869,7 @@ const conf = convict({
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
clientIds: {
|
clientIds: {
|
||||||
doc:
|
doc: 'Mappings from client id to service name: { "id1": "name-1", "id2": "name-2" }',
|
||||||
'Mappings from client id to service name: { "id1": "name-1", "id2": "name-2" }',
|
|
||||||
format: Object,
|
format: Object,
|
||||||
default: {},
|
default: {},
|
||||||
env: 'OAUTH_CLIENT_IDS',
|
env: 'OAUTH_CLIENT_IDS',
|
||||||
|
@ -902,8 +883,7 @@ const conf = convict({
|
||||||
// A safety switch for disabling new signins/signups from particular clients,
|
// A safety switch for disabling new signins/signups from particular clients,
|
||||||
// as a hedge against unexpected client behaviour.
|
// as a hedge against unexpected client behaviour.
|
||||||
disableNewConnectionsForClients: {
|
disableNewConnectionsForClients: {
|
||||||
doc:
|
doc: 'Comma-separated list of oauth client ids for which new connections should be temporarily refused',
|
||||||
'Comma-separated list of oauth client ids for which new connections should be temporarily refused',
|
|
||||||
env: 'OAUTH_DISABLE_NEW_CONNECTIONS_FOR_CLIENTS',
|
env: 'OAUTH_DISABLE_NEW_CONNECTIONS_FOR_CLIENTS',
|
||||||
format: Array,
|
format: Array,
|
||||||
default: [],
|
default: [],
|
||||||
|
@ -935,8 +915,7 @@ const conf = convict({
|
||||||
default: 'megaz0rd',
|
default: 'megaz0rd',
|
||||||
},
|
},
|
||||||
jwtSecretKeys: {
|
jwtSecretKeys: {
|
||||||
doc:
|
doc: 'Comma-separated list of secret keys for verifying oauth-to-auth server JWTs',
|
||||||
'Comma-separated list of secret keys for verifying oauth-to-auth server JWTs',
|
|
||||||
env: 'OAUTH_SERVER_SECRETS',
|
env: 'OAUTH_SERVER_SECRETS',
|
||||||
format: Array,
|
format: Array,
|
||||||
default: ['megaz0rd'],
|
default: ['megaz0rd'],
|
||||||
|
@ -1011,8 +990,7 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
authServerSecrets: {
|
authServerSecrets: {
|
||||||
doc:
|
doc: 'Comma-separated list of secret keys for verifying server-to-server JWTs',
|
||||||
'Comma-separated list of secret keys for verifying server-to-server JWTs',
|
|
||||||
env: 'AUTH_SERVER_SECRETS',
|
env: 'AUTH_SERVER_SECRETS',
|
||||||
format: 'Array',
|
format: 'Array',
|
||||||
default: [],
|
default: [],
|
||||||
|
@ -1043,8 +1021,7 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
clientManagement: {
|
clientManagement: {
|
||||||
enabled: {
|
enabled: {
|
||||||
doc:
|
doc: 'Enable client management in OAuth server routes. Do NOT set this to true in production.',
|
||||||
'Enable client management in OAuth server routes. Do NOT set this to true in production.',
|
|
||||||
default: false,
|
default: false,
|
||||||
format: 'Boolean',
|
format: 'Boolean',
|
||||||
env: 'CLIENT_MANAGEMENT_ENABLED',
|
env: 'CLIENT_MANAGEMENT_ENABLED',
|
||||||
|
@ -1054,15 +1031,13 @@ const conf = convict({
|
||||||
// This is used by oauth/db/index.js to identify pocket client ids so that it
|
// This is used by oauth/db/index.js to identify pocket client ids so that it
|
||||||
// can store them separately in mysql.
|
// can store them separately in mysql.
|
||||||
// It's also used for amplitude stats
|
// It's also used for amplitude stats
|
||||||
doc:
|
doc: 'Mappings from client id to service name: { "id1": "name-1", "id2": "name-2" }',
|
||||||
'Mappings from client id to service name: { "id1": "name-1", "id2": "name-2" }',
|
|
||||||
default: {},
|
default: {},
|
||||||
format: 'Object',
|
format: 'Object',
|
||||||
env: 'OAUTH_CLIENT_IDS',
|
env: 'OAUTH_CLIENT_IDS',
|
||||||
},
|
},
|
||||||
disabledClients: {
|
disabledClients: {
|
||||||
doc:
|
doc: 'Comma-separated list of client ids for which service should be temporarily refused',
|
||||||
'Comma-separated list of client ids for which service should be temporarily refused',
|
|
||||||
env: 'OAUTH_CLIENTS_DISABLED',
|
env: 'OAUTH_CLIENTS_DISABLED',
|
||||||
format: 'Array',
|
format: 'Array',
|
||||||
default: [],
|
default: [],
|
||||||
|
@ -1106,8 +1081,7 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
events: {
|
events: {
|
||||||
enabled: {
|
enabled: {
|
||||||
doc:
|
doc: 'Whether or not config.events has to be properly set in production',
|
||||||
'Whether or not config.events has to be properly set in production',
|
|
||||||
default: true,
|
default: true,
|
||||||
format: 'Boolean',
|
format: 'Boolean',
|
||||||
env: 'EVENTS_ENABLED',
|
env: 'EVENTS_ENABLED',
|
||||||
|
@ -1203,8 +1177,7 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
timezone: {
|
timezone: {
|
||||||
default: 'Z',
|
default: 'Z',
|
||||||
doc:
|
doc: 'The timezone configured on the MySQL server. This is used to type cast server date/time values to JavaScript `Date` object. Can be `local`, `Z`, or an offset in the form of or an offset in the form +HH:MM or -HH:MM.',
|
||||||
'The timezone configured on the MySQL server. This is used to type cast server date/time values to JavaScript `Date` object. Can be `local`, `Z`, or an offset in the form of or an offset in the form +HH:MM or -HH:MM.',
|
|
||||||
env: 'MYSQL_TIMEZONE',
|
env: 'MYSQL_TIMEZONE',
|
||||||
format: 'String',
|
format: 'String',
|
||||||
},
|
},
|
||||||
|
@ -1217,8 +1190,7 @@ const conf = convict({
|
||||||
env: 'FXA_OPENID_KEYFILE',
|
env: 'FXA_OPENID_KEYFILE',
|
||||||
},
|
},
|
||||||
newKeyFile: {
|
newKeyFile: {
|
||||||
doc:
|
doc: 'Path to private key JWK that will be used to sign JWTs in the future',
|
||||||
'Path to private key JWK that will be used to sign JWTs in the future',
|
|
||||||
default: '',
|
default: '',
|
||||||
format: 'String',
|
format: 'String',
|
||||||
env: 'FXA_OPENID_NEWKEYFILE',
|
env: 'FXA_OPENID_NEWKEYFILE',
|
||||||
|
@ -1245,8 +1217,7 @@ const conf = convict({
|
||||||
env: 'FXA_OPENID_OLDKEY',
|
env: 'FXA_OPENID_OLDKEY',
|
||||||
},
|
},
|
||||||
unsafelyAllowMissingActiveKey: {
|
unsafelyAllowMissingActiveKey: {
|
||||||
doc:
|
doc: 'Do not error out if there is no active key; should only be used when initializing keys',
|
||||||
'Do not error out if there is no active key; should only be used when initializing keys',
|
|
||||||
default: false,
|
default: false,
|
||||||
format: 'Boolean',
|
format: 'Boolean',
|
||||||
env: 'FXA_OPENID_UNSAFELY_ALLOW_MISSING_ACTIVE_KEY',
|
env: 'FXA_OPENID_UNSAFELY_ALLOW_MISSING_ACTIVE_KEY',
|
||||||
|
@ -1277,8 +1248,7 @@ const conf = convict({
|
||||||
env: 'PPID_CLIENT_IDS',
|
env: 'PPID_CLIENT_IDS',
|
||||||
},
|
},
|
||||||
rotatingClientIds: {
|
rotatingClientIds: {
|
||||||
doc:
|
doc: 'client_ids that receive automatically rotating PPIDs based on server time',
|
||||||
'client_ids that receive automatically rotating PPIDs based on server time',
|
|
||||||
default: [],
|
default: [],
|
||||||
format: 'Array',
|
format: 'Array',
|
||||||
env: 'PPID_ROTATING_CLIENT_IDS',
|
env: 'PPID_ROTATING_CLIENT_IDS',
|
||||||
|
@ -1332,8 +1302,7 @@ const conf = convict({
|
||||||
developerId: { doc: 'Bytes of generated developer ids', default: 16 },
|
developerId: { doc: 'Bytes of generated developer ids', default: 16 },
|
||||||
},
|
},
|
||||||
cacheControl: {
|
cacheControl: {
|
||||||
doc:
|
doc: 'Hapi: a string with the value of the "Cache-Control" header when caching is disabled',
|
||||||
'Hapi: a string with the value of the "Cache-Control" header when caching is disabled',
|
|
||||||
format: 'String',
|
format: 'String',
|
||||||
default: 'private, no-cache, no-store, must-revalidate',
|
default: 'private, no-cache, no-store, must-revalidate',
|
||||||
},
|
},
|
||||||
|
@ -1410,15 +1379,13 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
signinConfirmation: {
|
signinConfirmation: {
|
||||||
forcedEmailAddresses: {
|
forcedEmailAddresses: {
|
||||||
doc:
|
doc: 'Force sign-in confirmation for email addresses matching this regex.',
|
||||||
'Force sign-in confirmation for email addresses matching this regex.',
|
|
||||||
format: RegExp,
|
format: RegExp,
|
||||||
default: /.+@mozilla\.com$/,
|
default: /.+@mozilla\.com$/,
|
||||||
env: 'SIGNIN_CONFIRMATION_FORCE_EMAIL_REGEX',
|
env: 'SIGNIN_CONFIRMATION_FORCE_EMAIL_REGEX',
|
||||||
},
|
},
|
||||||
skipForEmailAddresses: {
|
skipForEmailAddresses: {
|
||||||
doc:
|
doc: 'Comma separated list of email addresses that will always skip any non TOTP sign-in confirmation',
|
||||||
'Comma separated list of email addresses that will always skip any non TOTP sign-in confirmation',
|
|
||||||
format: Array,
|
format: Array,
|
||||||
default: [],
|
default: [],
|
||||||
env: 'SIGNIN_CONFIRMATION_SKIP_FOR_EMAIL_ADDRESS',
|
env: 'SIGNIN_CONFIRMATION_SKIP_FOR_EMAIL_ADDRESS',
|
||||||
|
@ -1467,8 +1434,7 @@ const conf = convict({
|
||||||
securityHistory: {
|
securityHistory: {
|
||||||
ipProfiling: {
|
ipProfiling: {
|
||||||
allowedRecency: {
|
allowedRecency: {
|
||||||
doc:
|
doc: 'Length of time since previously verified event to allow skipping confirmation',
|
||||||
'Length of time since previously verified event to allow skipping confirmation',
|
|
||||||
default: '72 hours',
|
default: '72 hours',
|
||||||
format: 'duration',
|
format: 'duration',
|
||||||
env: 'IP_PROFILING_RECENCY',
|
env: 'IP_PROFILING_RECENCY',
|
||||||
|
@ -1483,15 +1449,13 @@ const conf = convict({
|
||||||
env: 'LASTACCESSTIME_UPDATES_ENABLED',
|
env: 'LASTACCESSTIME_UPDATES_ENABLED',
|
||||||
},
|
},
|
||||||
sampleRate: {
|
sampleRate: {
|
||||||
doc:
|
doc: 'sample rate for updates to the lastAccessTime session token property, in the range 0..1',
|
||||||
'sample rate for updates to the lastAccessTime session token property, in the range 0..1',
|
|
||||||
format: Number,
|
format: Number,
|
||||||
default: 0.3,
|
default: 0.3,
|
||||||
env: 'LASTACCESSTIME_UPDATES_SAMPLE_RATE',
|
env: 'LASTACCESSTIME_UPDATES_SAMPLE_RATE',
|
||||||
},
|
},
|
||||||
earliestSaneTimestamp: {
|
earliestSaneTimestamp: {
|
||||||
doc:
|
doc: 'timestamp used as the basis of the fallback value for lastAccessTimeFormatted, currently pinned to the deployment of 1.96.4 / a0940d7dc51e2ba20fa18aa3a830810e35c9a9d9',
|
||||||
'timestamp used as the basis of the fallback value for lastAccessTimeFormatted, currently pinned to the deployment of 1.96.4 / a0940d7dc51e2ba20fa18aa3a830810e35c9a9d9',
|
|
||||||
format: 'timestamp',
|
format: 'timestamp',
|
||||||
default: 1507081020000,
|
default: 1507081020000,
|
||||||
env: 'LASTACCESSTIME_EARLIEST_SANE_TIMESTAMP',
|
env: 'LASTACCESSTIME_EARLIEST_SANE_TIMESTAMP',
|
||||||
|
@ -1510,8 +1474,7 @@ const conf = convict({
|
||||||
env: 'SIGNIN_UNBLOCK_CODE_LIFETIME',
|
env: 'SIGNIN_UNBLOCK_CODE_LIFETIME',
|
||||||
},
|
},
|
||||||
forcedEmailAddresses: {
|
forcedEmailAddresses: {
|
||||||
doc:
|
doc: 'If feature enabled, force sign-in unblock for email addresses matching this regex.',
|
||||||
'If feature enabled, force sign-in unblock for email addresses matching this regex.',
|
|
||||||
format: RegExp,
|
format: RegExp,
|
||||||
default: /^$/, // default is no one
|
default: /^$/, // default is no one
|
||||||
env: 'SIGNIN_UNBLOCK_FORCED_EMAILS',
|
env: 'SIGNIN_UNBLOCK_FORCED_EMAILS',
|
||||||
|
@ -1522,7 +1485,8 @@ const conf = convict({
|
||||||
doc: 'RegExp that validates the URI format of the Push Server',
|
doc: 'RegExp that validates the URI format of the Push Server',
|
||||||
format: RegExp,
|
format: RegExp,
|
||||||
// eslint-disable-next-line no-useless-escape
|
// eslint-disable-next-line no-useless-escape
|
||||||
default: /^https:\/\/[a-zA-Z0-9._-]+(\.services\.mozilla\.com|autopush\.dev\.mozaws\.net|autopush\.stage\.mozaws\.net)(?:\:\d+)?(\/.*)?$/,
|
default:
|
||||||
|
/^https:\/\/[a-zA-Z0-9._-]+(\.services\.mozilla\.com|autopush\.dev\.mozaws\.net|autopush\.stage\.mozaws\.net)(?:\:\d+)?(\/.*)?$/,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
pushbox: {
|
pushbox: {
|
||||||
|
@ -1604,22 +1568,19 @@ const conf = convict({
|
||||||
env: 'SMS_INSTALL_FIREFOX_LINK',
|
env: 'SMS_INSTALL_FIREFOX_LINK',
|
||||||
},
|
},
|
||||||
installFirefoxWithSigninCodeBaseUri: {
|
installFirefoxWithSigninCodeBaseUri: {
|
||||||
doc:
|
doc: 'Base URI for the SMS template when the signinCodes feature is active',
|
||||||
'Base URI for the SMS template when the signinCodes feature is active',
|
|
||||||
default: 'https://accounts.firefox.com/m',
|
default: 'https://accounts.firefox.com/m',
|
||||||
format: 'url',
|
format: 'url',
|
||||||
env: 'SMS_SIGNIN_CODES_BASE_URI',
|
env: 'SMS_SIGNIN_CODES_BASE_URI',
|
||||||
},
|
},
|
||||||
enableBudgetChecks: {
|
enableBudgetChecks: {
|
||||||
doc:
|
doc: 'enable checks of the monthly SMS spend against the available budget',
|
||||||
'enable checks of the monthly SMS spend against the available budget',
|
|
||||||
default: true,
|
default: true,
|
||||||
format: Boolean,
|
format: Boolean,
|
||||||
env: 'SMS_ENABLE_BUDGET_CHECKS',
|
env: 'SMS_ENABLE_BUDGET_CHECKS',
|
||||||
},
|
},
|
||||||
minimumCreditThresholdUSD: {
|
minimumCreditThresholdUSD: {
|
||||||
doc:
|
doc: 'The minimum amount of available credit that is necessary to enable SMS, in US dollars',
|
||||||
'The minimum amount of available credit that is necessary to enable SMS, in US dollars',
|
|
||||||
default: 200,
|
default: 200,
|
||||||
format: 'nat',
|
format: 'nat',
|
||||||
env: 'SMS_MINIMUM_CREDIT_THRESHOLD',
|
env: 'SMS_MINIMUM_CREDIT_THRESHOLD',
|
||||||
|
@ -1631,8 +1592,7 @@ const conf = convict({
|
||||||
env: 'SMS_POLL_CURRENT_SPEND_INTERVAL',
|
env: 'SMS_POLL_CURRENT_SPEND_INTERVAL',
|
||||||
},
|
},
|
||||||
smsType: {
|
smsType: {
|
||||||
doc:
|
doc: 'AWS.SNS.SMS.SMSType argument value. "Promotional" or "Transactional".',
|
||||||
'AWS.SNS.SMS.SMSType argument value. "Promotional" or "Transactional".',
|
|
||||||
default: 'Promotional',
|
default: 'Promotional',
|
||||||
format: 'String',
|
format: 'String',
|
||||||
env: 'SMS_AWS_SNS_SMSTYPE',
|
env: 'SMS_AWS_SNS_SMSTYPE',
|
||||||
|
@ -1640,8 +1600,7 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
secondaryEmail: {
|
secondaryEmail: {
|
||||||
minUnverifiedAccountTime: {
|
minUnverifiedAccountTime: {
|
||||||
doc:
|
doc: 'The minimum amount of time an account can be unverified before another account can use it for secondary email',
|
||||||
'The minimum amount of time an account can be unverified before another account can use it for secondary email',
|
|
||||||
default: '1 day',
|
default: '1 day',
|
||||||
format: 'duration',
|
format: 'duration',
|
||||||
env: 'SECONDARY_EMAIL_MIN_UNVERIFIED_ACCOUNT_TIME',
|
env: 'SECONDARY_EMAIL_MIN_UNVERIFIED_ACCOUNT_TIME',
|
||||||
|
@ -1691,8 +1650,7 @@ const conf = convict({
|
||||||
env: 'RECOVERY_CODE_COUNT',
|
env: 'RECOVERY_CODE_COUNT',
|
||||||
},
|
},
|
||||||
notifyLowCount: {
|
notifyLowCount: {
|
||||||
doc:
|
doc: 'Notify the user when there are less than these many recovery codes',
|
||||||
'Notify the user when there are less than these many recovery codes',
|
|
||||||
default: 2,
|
default: 2,
|
||||||
env: 'RECOVERY_CODE_NOTIFY_LOW_COUNT',
|
env: 'RECOVERY_CODE_NOTIFY_LOW_COUNT',
|
||||||
},
|
},
|
||||||
|
@ -1712,8 +1670,7 @@ const conf = convict({
|
||||||
format: 'duration',
|
format: 'duration',
|
||||||
},
|
},
|
||||||
secondInterval: {
|
secondInterval: {
|
||||||
doc:
|
doc: 'Time since account creation after which the second reminder is sent',
|
||||||
'Time since account creation after which the second reminder is sent',
|
|
||||||
default: '5 days',
|
default: '5 days',
|
||||||
env: 'VERIFICATION_REMINDERS_SECOND_INTERVAL',
|
env: 'VERIFICATION_REMINDERS_SECOND_INTERVAL',
|
||||||
format: 'duration',
|
format: 'duration',
|
||||||
|
@ -1727,15 +1684,13 @@ const conf = convict({
|
||||||
},
|
},
|
||||||
maxConnections: {
|
maxConnections: {
|
||||||
default: 10,
|
default: 10,
|
||||||
doc:
|
doc: 'Maximum connection count for the verification reminders Redis pool',
|
||||||
'Maximum connection count for the verification reminders Redis pool',
|
|
||||||
env: 'VERIFICATION_REMINDERS_REDIS_MAX_CONNECTIONS',
|
env: 'VERIFICATION_REMINDERS_REDIS_MAX_CONNECTIONS',
|
||||||
format: 'nat',
|
format: 'nat',
|
||||||
},
|
},
|
||||||
minConnections: {
|
minConnections: {
|
||||||
default: 1,
|
default: 1,
|
||||||
doc:
|
doc: 'Minimum connection count for the verification reminders Redis pool',
|
||||||
'Minimum connection count for the verification reminders Redis pool',
|
|
||||||
env: 'VERIFICATION_REMINDERS_REDIS_MIN_CONNECTIONS',
|
env: 'VERIFICATION_REMINDERS_REDIS_MIN_CONNECTIONS',
|
||||||
format: 'nat',
|
format: 'nat',
|
||||||
},
|
},
|
||||||
|
@ -1813,15 +1768,13 @@ const conf = convict({
|
||||||
format: Number,
|
format: Number,
|
||||||
},
|
},
|
||||||
locationStateFieldId: {
|
locationStateFieldId: {
|
||||||
doc:
|
doc: 'Zendesk support ticket custom field for the state/region of the location',
|
||||||
'Zendesk support ticket custom field for the state/region of the location',
|
|
||||||
default: 360026463491,
|
default: 360026463491,
|
||||||
env: 'ZENDESK_LOCATION_STATE_FIELD_ID',
|
env: 'ZENDESK_LOCATION_STATE_FIELD_ID',
|
||||||
format: Number,
|
format: Number,
|
||||||
},
|
},
|
||||||
locationCountryFieldId: {
|
locationCountryFieldId: {
|
||||||
doc:
|
doc: 'Zendesk support ticket custom field for the country of the location',
|
||||||
'Zendesk support ticket custom field for the country of the location',
|
|
||||||
default: 360026463511,
|
default: 360026463511,
|
||||||
env: 'ZENDESK_LOCATION_COUNTRY_FIELD_ID',
|
env: 'ZENDESK_LOCATION_COUNTRY_FIELD_ID',
|
||||||
format: Number,
|
format: Number,
|
||||||
|
@ -1833,8 +1786,7 @@ const conf = convict({
|
||||||
format: Number,
|
format: Number,
|
||||||
},
|
},
|
||||||
appFieldId: {
|
appFieldId: {
|
||||||
doc:
|
doc: 'Zendesk support ticket custom field for product specific app or service',
|
||||||
'Zendesk support ticket custom field for product specific app or service',
|
|
||||||
default: 360030780972,
|
default: 360030780972,
|
||||||
env: 'ZENDESK_APP_FIELD_ID',
|
env: 'ZENDESK_APP_FIELD_ID',
|
||||||
format: Number,
|
format: Number,
|
||||||
|
@ -1863,8 +1815,7 @@ const conf = convict({
|
||||||
supportPanel: {
|
supportPanel: {
|
||||||
secretBearerToken: {
|
secretBearerToken: {
|
||||||
default: 'YOU MUST CHANGE ME',
|
default: 'YOU MUST CHANGE ME',
|
||||||
doc:
|
doc: 'Shared secret to access certain endpoints. Please only use for GET. No state mutation allowed!',
|
||||||
'Shared secret to access certain endpoints. Please only use for GET. No state mutation allowed!',
|
|
||||||
env: 'SUPPORT_PANEL_AUTH_SECRET_BEARER_TOKEN',
|
env: 'SUPPORT_PANEL_AUTH_SECRET_BEARER_TOKEN',
|
||||||
format: 'String',
|
format: 'String',
|
||||||
},
|
},
|
||||||
|
@ -1898,6 +1849,7 @@ conf.set(
|
||||||
`${baseUri}/settings/two_step_authentication/replace_codes`
|
`${baseUri}/settings/two_step_authentication/replace_codes`
|
||||||
);
|
);
|
||||||
conf.set('smtp.verificationUrl', `${baseUri}/verify_email`);
|
conf.set('smtp.verificationUrl', `${baseUri}/verify_email`);
|
||||||
|
conf.set('smtp.pushVerificationUrl', `${baseUri}/push/confirm_login`);
|
||||||
conf.set('smtp.passwordResetUrl', `${baseUri}/complete_reset_password`);
|
conf.set('smtp.passwordResetUrl', `${baseUri}/complete_reset_password`);
|
||||||
conf.set('smtp.initiatePasswordResetUrl', `${baseUri}/reset_password`);
|
conf.set('smtp.initiatePasswordResetUrl', `${baseUri}/reset_password`);
|
||||||
conf.set(
|
conf.set(
|
||||||
|
|
|
@ -447,25 +447,27 @@ module.exports = function (
|
||||||
const sessionToken = request.auth.credentials;
|
const sessionToken = request.auth.credentials;
|
||||||
const { uid, tokenVerificationId } = sessionToken;
|
const { uid, tokenVerificationId } = sessionToken;
|
||||||
|
|
||||||
const devices = await db.devices(uid);
|
const allDevices = await db.devices(uid);
|
||||||
const geoData = request.app.geo;
|
const location = request.app.geo.location || {};
|
||||||
|
|
||||||
const { ua } = request.app;
|
const { ua } = request.app;
|
||||||
const uaInfo = {
|
const uaInfo = {
|
||||||
uaBrowser: ua.browser,
|
uaBrowser: ua.browser,
|
||||||
uaBrowserVersion: ua.browserVersion,
|
|
||||||
uaOS: ua.os,
|
uaOS: ua.os,
|
||||||
uaOSVersion: ua.osVersion,
|
|
||||||
uaDeviceType: ua.deviceType,
|
uaDeviceType: ua.deviceType,
|
||||||
uaFormFactor: ua.formFactor,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Don't send notification to current device
|
||||||
|
const filteredDevices = allDevices.filter((d) => {
|
||||||
|
return d.sessionTokenId !== sessionToken.id;
|
||||||
|
});
|
||||||
|
|
||||||
const url = `${
|
const url = `${
|
||||||
config.smtp.verificationUrl
|
config.smtp.pushVerificationUrl
|
||||||
}?type=push_login_verification&code=${tokenVerificationId}&ua=${encodeURIComponent(
|
}?type=push_login_verification&code=${tokenVerificationId}&ua=${encodeURIComponent(
|
||||||
JSON.stringify(uaInfo)
|
JSON.stringify(uaInfo)
|
||||||
)}&location=${encodeURIComponent(
|
)}&location=${encodeURIComponent(
|
||||||
JSON.stringify(geoData.location)
|
JSON.stringify(location)
|
||||||
)}&ip=${encodeURIComponent(request.app.clientAddress)}`;
|
)}&ip=${encodeURIComponent(request.app.clientAddress)}`;
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
|
@ -475,7 +477,7 @@ module.exports = function (
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await push.notifyVerifyLoginRequest(uid, devices, options);
|
await push.notifyVerifyLoginRequest(uid, filteredDevices, options);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
log.error('Session.verify.send_push', {
|
log.error('Session.verify.send_push', {
|
||||||
uid: uid,
|
uid: uid,
|
||||||
|
|
|
@ -1416,6 +1416,14 @@ FxaClientWrapper.prototype = {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a push notification to compatible devices that can verify a login
|
||||||
|
* request
|
||||||
|
*
|
||||||
|
* @returns {Promise} resolves with response when complete.
|
||||||
|
*/
|
||||||
|
sendPushLoginRequest: createClientDelegate('sendPushLoginRequest'),
|
||||||
};
|
};
|
||||||
|
|
||||||
export default FxaClientWrapper;
|
export default FxaClientWrapper;
|
||||||
|
|
|
@ -184,6 +184,9 @@ const Router = Backbone.Router.extend({
|
||||||
type: VerificationReasons.SECONDARY_EMAIL_VERIFIED,
|
type: VerificationReasons.SECONDARY_EMAIL_VERIFIED,
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
'push/confirm_login(/)': createViewHandler('push/confirm_login'),
|
||||||
|
'push/send_login(/)': createViewHandler('push/send_login'),
|
||||||
|
'push/completed(/)': createViewHandler('push/completed'),
|
||||||
'primary_email_verified(/)': createViewHandler(ReadyView, {
|
'primary_email_verified(/)': createViewHandler(ReadyView, {
|
||||||
type: VerificationReasons.PRIMARY_EMAIL_VERIFIED,
|
type: VerificationReasons.PRIMARY_EMAIL_VERIFIED,
|
||||||
}),
|
}),
|
||||||
|
@ -208,11 +211,8 @@ const Router = Backbone.Router.extend({
|
||||||
'settings(/)': function () {
|
'settings(/)': function () {
|
||||||
// Because settings is a separate js app, we need to ensure navigating
|
// Because settings is a separate js app, we need to ensure navigating
|
||||||
// from the content-server app passes along flow parameters.
|
// from the content-server app passes along flow parameters.
|
||||||
const {
|
const { deviceId, flowBeginTime, flowId } =
|
||||||
deviceId,
|
this.metrics.getFlowEventMetadata();
|
||||||
flowBeginTime,
|
|
||||||
flowId,
|
|
||||||
} = this.metrics.getFlowEventMetadata();
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
broker,
|
broker,
|
||||||
|
@ -343,9 +343,8 @@ const Router = Backbone.Router.extend({
|
||||||
url = this.broker.transformLink(url);
|
url = this.broker.transformLink(url);
|
||||||
|
|
||||||
if (options.replace && this._viewModelStack.length) {
|
if (options.replace && this._viewModelStack.length) {
|
||||||
this._viewModelStack[this._viewModelStack.length - 1] = createViewModel(
|
this._viewModelStack[this._viewModelStack.length - 1] =
|
||||||
nextViewData
|
createViewModel(nextViewData);
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this._viewModelStack.push(createViewModel(nextViewData));
|
this._viewModelStack.push(createViewModel(nextViewData));
|
||||||
}
|
}
|
||||||
|
|
|
@ -1116,30 +1116,28 @@ const Account = Backbone.Model.extend(
|
||||||
*
|
*
|
||||||
* @method set
|
* @method set
|
||||||
*/
|
*/
|
||||||
set: _.wrap(Backbone.Model.prototype.set, function (
|
set: _.wrap(
|
||||||
func,
|
Backbone.Model.prototype.set,
|
||||||
attribute,
|
function (func, attribute, value, options) {
|
||||||
value,
|
let attributes;
|
||||||
options
|
// Handle both `"key", value` and `{key: value}` -style arguments.
|
||||||
) {
|
if (_.isObject(attribute)) {
|
||||||
let attributes;
|
attributes = attribute;
|
||||||
// Handle both `"key", value` and `{key: value}` -style arguments.
|
} else {
|
||||||
if (_.isObject(attribute)) {
|
attributes = {};
|
||||||
attributes = attribute;
|
attributes[attribute] = value;
|
||||||
} else {
|
|
||||||
attributes = {};
|
|
||||||
attributes[attribute] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-vars
|
|
||||||
for (const key in attributes) {
|
|
||||||
if (!_.contains(ALLOWED_KEYS, key)) {
|
|
||||||
throw new Error(key + ' cannot be set on an Account');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return func.call(this, attribute, value, options);
|
// eslint-disable-next-line no-unused-vars
|
||||||
}),
|
for (const key in attributes) {
|
||||||
|
if (!_.contains(ALLOWED_KEYS, key)) {
|
||||||
|
throw new Error(key + ' cannot be set on an Account');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return func.call(this, attribute, value, options);
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete a password reset
|
* Complete a password reset
|
||||||
|
@ -1820,6 +1818,15 @@ const Account = Backbone.Model.extend(
|
||||||
createCadReminder() {
|
createCadReminder() {
|
||||||
return this._fxaClient.createCadReminder(this.get('sessionToken'));
|
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,
|
ALLOWED_KEYS: ALLOWED_KEYS,
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
<div id="main-content" class="card pair-auth push-auth-complete">
|
||||||
|
<header>
|
||||||
|
<h1 id="push-auth-complete-header">{{#t}}Sign-in confirmed{{/t}}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="error"></div>
|
||||||
|
|
||||||
|
<div class="graphic graphic-checkbox" role="img" aria-label="{{#t}}Successfully connected{{/t}}"></div>
|
||||||
|
|
||||||
|
<p class="verification-message">{{#t}}Please close this page and continue on the other device.{{/t}}</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
<div id="main-content" class="card push-confirm-login">
|
||||||
|
<header>
|
||||||
|
<h1 id="fxa-push-confirm-login-header">{{#t}}New sign-in to Firefox{{/t}}</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="error"></div>
|
||||||
|
<div class="success"></div>
|
||||||
|
|
||||||
|
<form novalidate>
|
||||||
|
<p class="verification-message">{{#t}}For added security, please confirm this sign-in to begin syncing with this device:{{/t}}</p>
|
||||||
|
|
||||||
|
<div class="push-confirm-login-device">
|
||||||
|
{{{ unsafeDeviceBeingPairedHTML }}}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="button-row">
|
||||||
|
<button id="submit-btn" type="submit">{{#t}}Confirm sign-in{{/t}}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p>{{#unsafeTranslate}}If you suspect that someone is trying to gain access to your account, <a id="change-password" href="/settings/change_password">please change your password.</a>{{/unsafeTranslate}}</p>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
|
@ -0,0 +1,21 @@
|
||||||
|
<div id="main-content" class="card push-send-login">
|
||||||
|
<header>
|
||||||
|
<h1 id="fxa-push-send-login-header">{{#t}}Authorize this sign-in{{/t}}</span></h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<div class="error"></div>
|
||||||
|
<div class="success"></div>
|
||||||
|
|
||||||
|
<form novalidate>
|
||||||
|
<div class="graphic graphic-send-tab-complete" role="img" aria-label="{{#t}}Send login push notification{{/t}}"></div>
|
||||||
|
|
||||||
|
<p class="verification-message">{{#t}}Check your connected Firefox devices for the sign-in confirmation tab we've sent.{{/t}}</p>
|
||||||
|
|
||||||
|
<div class="links">
|
||||||
|
<a id="resend" href="#" data-flow-event="link.create-account">{{#t}}Resend tab{{/t}}</a>
|
||||||
|
<a id="send-email" class="delayed-fadein" href="#">{{#t}}Tab not arriving? Send an email instead{{/t}}</a>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
</div>
|
|
@ -0,0 +1,22 @@
|
||||||
|
/* 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;
|
|
@ -0,0 +1,62 @@
|
||||||
|
/* 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() {
|
||||||
|
// TODO: Unfortunately, we need to use `/recovery_email/verify_code`
|
||||||
|
// endpoint to verify our tokenVerificationCode because that code
|
||||||
|
// is linked directly to a specific session.
|
||||||
|
const account = this.getSignedInAccount();
|
||||||
|
return account
|
||||||
|
.verifySignUp(this.code)
|
||||||
|
.then(() => {
|
||||||
|
return this.navigate('/push/completed');
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
this.displayError(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ConfirmPushLoginView;
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* 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;
|
|
@ -16,6 +16,8 @@ import SessionVerificationPollMixin from './mixins/session-verification-poll-mix
|
||||||
|
|
||||||
const CODE_INPUT_SELECTOR = 'input.otp-code';
|
const CODE_INPUT_SELECTOR = 'input.otp-code';
|
||||||
|
|
||||||
|
const proto = FormView.prototype;
|
||||||
|
|
||||||
const View = FormView.extend({
|
const View = FormView.extend({
|
||||||
className: 'sign-in-token-code',
|
className: 'sign-in-token-code',
|
||||||
template: Template,
|
template: Template,
|
||||||
|
@ -37,9 +39,18 @@ const View = FormView.extend({
|
||||||
// is deleted. If the account no longer exists, redirects the user to
|
// is deleted. If the account no longer exists, redirects the user to
|
||||||
// sign up, if the account exists, then notifies them their account
|
// sign up, if the account exists, then notifies them their account
|
||||||
// has been blocked.
|
// has been blocked.
|
||||||
this.waitForSessionVerification(this.getAccount(), () => {
|
const account = this.getSignedInAccount();
|
||||||
// don't do anything on verification, that's taken care of in the submit handler.
|
return proto.afterVisible
|
||||||
});
|
.call(this)
|
||||||
|
.then(() => this.broker.persistVerificationData(account))
|
||||||
|
.then(() =>
|
||||||
|
this.invokeBrokerMethod('beforeSignUpConfirmationPoll', account)
|
||||||
|
)
|
||||||
|
.then(() => {
|
||||||
|
this.waitForSessionVerification(this.getAccount(), () => {
|
||||||
|
// don't do anything on verification, that's taken care of in the submit handler.
|
||||||
|
});
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
setInitialContext(context) {
|
setInitialContext(context) {
|
||||||
|
|
|
@ -239,3 +239,7 @@
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.push-confirm-login-device {
|
||||||
|
padding: 15px 0 45px 0;
|
||||||
|
}
|
|
@ -138,3 +138,8 @@
|
||||||
.graphic-pair-failure {
|
.graphic-pair-failure {
|
||||||
background-image: image-url('graphic_hearts_broken.svg');
|
background-image: image-url('graphic_hearts_broken.svg');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.graphic-send-tab-complete {
|
||||||
|
background-image: image-url('send_tab_complete.svg');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
/* 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('/'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,123 @@
|
||||||
|
/* 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'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,155 @@
|
||||||
|
/* 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -107,13 +107,13 @@ describe('views/sign_in_token_code', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('afterVisible', () => {
|
describe('afterVisible', () => {
|
||||||
it('starts polling in case the email bounces', () => {
|
it('starts polling in case the email bounces', async () => {
|
||||||
const account = { uid: 'uid' };
|
const account = { uid: 'uid' };
|
||||||
|
|
||||||
sinon.stub(view, 'waitForSessionVerification');
|
sinon.stub(view, 'waitForSessionVerification');
|
||||||
sinon.stub(view, 'getAccount').returns(account);
|
sinon.stub(view, 'getAccount').returns(account);
|
||||||
|
|
||||||
view.afterVisible();
|
await view.afterVisible();
|
||||||
|
|
||||||
assert.isTrue(view.waitForSessionVerification.calledOnceWith(account));
|
assert.isTrue(view.waitForSessionVerification.calledOnceWith(account));
|
||||||
});
|
});
|
||||||
|
|
|
@ -237,6 +237,9 @@ require('./spec/views/post_verify/password/force_password_change');
|
||||||
require('./spec/views/post_verify/secondary_email/add_secondary_email');
|
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/secondary_email/confirm_secondary_email');
|
||||||
require('./spec/views/progress_indicator');
|
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/ready');
|
||||||
require('./spec/views/report_sign_in');
|
require('./spec/views/report_sign_in');
|
||||||
require('./spec/views/reset_password');
|
require('./spec/views/reset_password');
|
||||||
|
|
|
@ -49,6 +49,9 @@ module.exports = function () {
|
||||||
'post_verify/secondary_email/confirm_secondary_email',
|
'post_verify/secondary_email/confirm_secondary_email',
|
||||||
'post_verify/secondary_email/verified_secondary_email',
|
'post_verify/secondary_email/verified_secondary_email',
|
||||||
'primary_email_verified',
|
'primary_email_verified',
|
||||||
|
'push/completed',
|
||||||
|
'push/confirm_login',
|
||||||
|
'push/send_login',
|
||||||
'report_signin',
|
'report_signin',
|
||||||
'reset_password',
|
'reset_password',
|
||||||
'reset_password_confirmed',
|
'reset_password_confirmed',
|
||||||
|
|
Загрузка…
Ссылка в новой задаче