chore(auth-server): enable typescript-eslint

Because:

* We aren't currently linting TS files in auth-server

This commit:

* Enables and fixes linting issues in auth-server

Closes FXA-6136
This commit is contained in:
Julian Poyourow 2022-11-01 13:17:53 -07:00
Родитель 6941fd4d64
Коммит 76ef507b26
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: EA0570ABC73D47D3
39 изменённых файлов: 406 добавлений и 333 удалений

Просмотреть файл

@ -121,6 +121,27 @@ jobs:
name: Reporting code coverage... name: Reporting code coverage...
command: bash <(curl -s https://codecov.io/bash) -F << parameters.package >> -X gcov command: bash <(curl -s https://codecov.io/bash) -F << parameters.package >> -X gcov
lint:
resource_class: medium+
docker:
- image: cimg/node:16.13
environment:
TRACING_SERVICE_NAME: ci-lint
TRACING_CONSOLE_EXPORTER_ENABLED: true
steps:
- base-install
- run:
name: Linting
command: |
PACKAGES=(\
'fxa-shared' \
'fxa-auth-server' \
)
for p in "${PACKAGES[@]}"; do
(cd packages/$p && yarn lint)
done
- jira/notify
test-many: test-many:
resource_class: medium+ resource_class: medium+
docker: docker:
@ -308,6 +329,12 @@ jobs:
workflows: workflows:
test_pull_request: test_pull_request:
jobs: jobs:
- lint:
filters:
branches:
ignore: main
tags:
ignore: /.*/
- test-many: - test-many:
filters: filters:
branches: branches:
@ -395,6 +422,12 @@ workflows:
ignore: /.*/ ignore: /.*/
test_and_deploy_tag: test_and_deploy_tag:
jobs: jobs:
- lint:
filters:
branches:
ignore: /.*/
tags:
only: /.*/
- test-many: - test-many:
filters: filters:
branches: branches:

Просмотреть файл

Просмотреть файл

@ -54,11 +54,14 @@
"lint-staged": { "lint-staged": {
"*.js": [ "*.js": [
"prettier --config _dev/.prettierrc --write", "prettier --config _dev/.prettierrc --write",
"eslint -c _dev/.eslintrc" "eslint"
], ],
"*.{ts,tsx}": [ "*.{ts,tsx}": [
"prettier --config _dev/.prettierrc --write" "prettier --config _dev/.prettierrc --write"
], ],
"packages/fxa-auth-server/**/*.{ts,tsx}": [
"eslint"
],
"*.css": [ "*.css": [
"prettier --config _dev/.prettierrc --write" "prettier --config _dev/.prettierrc --write"
], ],

Просмотреть файл

@ -1,13 +1,14 @@
{ {
"rules": { "rules": {
"require-atomic-updates": "off" "require-atomic-updates": "off",
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", {"vars": "all", "args": "none"}],
"no-redeclare": "off",
"@typescript-eslint/no-redeclare": "error"
}, },
"extends": ["plugin:fxa/server"], "extends": ["plugin:fxa/server"],
"plugins": ["fxa"], "plugins": ["@typescript-eslint", "fxa"],
"parserOptions": { "parser": "@typescript-eslint/parser",
"ecmaVersion": "2020",
"sourceType": "module"
},
"root": true, "root": true,
"ignorePatterns": [ "ignorePatterns": [
"dist", "dist",

Просмотреть файл

@ -86,6 +86,14 @@ Use the following as a template, and fill in your own values:
The sandbox PayPal business account API credentials above can be found in the PayPal developer dashboard under ["Sandbox" > "Accounts"](https://developer.paypal.com/developer/accounts/). You may need to create a business account if one doesn't exist. The sandbox PayPal business account API credentials above can be found in the PayPal developer dashboard under ["Sandbox" > "Accounts"](https://developer.paypal.com/developer/accounts/). You may need to create a business account if one doesn't exist.
## Linting
Run lint with:
yarn lint
Linting will also be run for staged files automatically via Husky when you attempt to commit.
## Testing ## Testing
Run tests with: Run tests with:

Просмотреть файл

@ -15,7 +15,7 @@ const FIVE_MINUTES = 1000 * 60 * 5;
convict.addFormats(require('convict-format-with-moment')); convict.addFormats(require('convict-format-with-moment'));
convict.addFormats(require('convict-format-with-validator')); convict.addFormats(require('convict-format-with-validator'));
const conf = convict({ const convictConf = convict({
env: { env: {
doc: 'The current node.js environment', doc: 'The current node.js environment',
default: 'prod', default: 'prod',
@ -1572,7 +1572,7 @@ const conf = convict({
format: RegExp, format: RegExp,
// eslint-disable-next-line no-useless-escape // eslint-disable-next-line no-useless-escape
default: default:
/^https:\/\/[a-zA-Z0-9._-]+(\.services\.mozilla\.com|autopush\.dev\.mozaws\.net|autopush\.stage\.mozaws\.net)(?:\:\d+)?(\/.*)?$/, /^https:\/\/[a-zA-Z0-9._-]+(\.services\.mozilla\.com|autopush\.dev\.mozaws\.net|autopush\.stage\.mozaws\.net)(?::\d+)?(\/.*)?$/,
}, },
}, },
pushbox: { pushbox: {
@ -1953,55 +1953,64 @@ const conf = convict({
// files to process, which will be overlayed in order, in the CONFIG_FILES // files to process, which will be overlayed in order, in the CONFIG_FILES
// environment variable. // environment variable.
let envConfig = path.join(__dirname, `${conf.get('env')}.json`); let envConfig = path.join(__dirname, `${convictConf.get('env')}.json`);
envConfig = `${envConfig},${process.env.CONFIG_FILES || ''}`; envConfig = `${envConfig},${process.env.CONFIG_FILES || ''}`;
const files = envConfig.split(',').filter(fs.existsSync); const files = envConfig.split(',').filter(fs.existsSync);
conf.loadFile(files); convictConf.loadFile(files);
conf.validate(); convictConf.validate();
// set the public url as the issuer domain for assertions // set the public url as the issuer domain for assertions
conf.set('domain', url.parse(conf.get('publicUrl')).host); convictConf.set('domain', url.parse(convictConf.get('publicUrl')).host);
// derive fxa-auth-mailer configuration from our content-server url // derive fxa-auth-mailer configuration from our content-server url
const baseUri = conf.get('contentServer.url'); const baseUri = convictConf.get('contentServer.url');
conf.set('smtp.accountSettingsUrl', `${baseUri}/settings`); convictConf.set('smtp.accountSettingsUrl', `${baseUri}/settings`);
conf.set( convictConf.set(
'smtp.accountRecoveryCodesUrl', 'smtp.accountRecoveryCodesUrl',
`${baseUri}/settings/two_step_authentication/replace_codes` `${baseUri}/settings/two_step_authentication/replace_codes`
); );
conf.set('smtp.verificationUrl', `${baseUri}/verify_email`); convictConf.set('smtp.verificationUrl', `${baseUri}/verify_email`);
conf.set('smtp.pushVerificationUrl', `${baseUri}/push/confirm_login`); convictConf.set('smtp.pushVerificationUrl', `${baseUri}/push/confirm_login`);
conf.set('smtp.passwordResetUrl', `${baseUri}/complete_reset_password`); convictConf.set('smtp.passwordResetUrl', `${baseUri}/complete_reset_password`);
conf.set('smtp.initiatePasswordResetUrl', `${baseUri}/reset_password`); convictConf.set('smtp.initiatePasswordResetUrl', `${baseUri}/reset_password`);
conf.set( convictConf.set(
'smtp.initiatePasswordChangeUrl', 'smtp.initiatePasswordChangeUrl',
`${baseUri}/settings/change_password` `${baseUri}/settings/change_password`
); );
conf.set('smtp.verifyLoginUrl', `${baseUri}/complete_signin`); convictConf.set('smtp.verifyLoginUrl', `${baseUri}/complete_signin`);
conf.set( convictConf.set(
'smtp.accountFinishSetupUrl', 'smtp.accountFinishSetupUrl',
`${baseUri}/post_verify/finish_account_setup/set_password` `${baseUri}/post_verify/finish_account_setup/set_password`
); );
conf.set('smtp.reportSignInUrl', `${baseUri}/report_signin`); convictConf.set('smtp.reportSignInUrl', `${baseUri}/report_signin`);
conf.set('smtp.revokeAccountRecoveryUrl', `${baseUri}/settings#recovery-key`); convictConf.set(
conf.set( 'smtp.revokeAccountRecoveryUrl',
`${baseUri}/settings#recovery-key`
);
convictConf.set(
'smtp.createAccountRecoveryUrl', 'smtp.createAccountRecoveryUrl',
`${baseUri}/settings/account_recovery` `${baseUri}/settings/account_recovery`
); );
conf.set('smtp.verifyPrimaryEmailUrl', `${baseUri}/verify_primary_email`); convictConf.set(
conf.set('smtp.verifySecondaryEmailUrl', `${baseUri}/verify_secondary_email`); 'smtp.verifyPrimaryEmailUrl',
conf.set('smtp.subscriptionSettingsUrl', `${baseUri}/subscriptions`); `${baseUri}/verify_primary_email`
conf.set('smtp.subscriptionSupportUrl', `${baseUri}/support`); );
conf.set('smtp.syncUrl', `${baseUri}/connect_another_device`); convictConf.set(
'smtp.verifySecondaryEmailUrl',
`${baseUri}/verify_secondary_email`
);
convictConf.set('smtp.subscriptionSettingsUrl', `${baseUri}/subscriptions`);
convictConf.set('smtp.subscriptionSupportUrl', `${baseUri}/support`);
convictConf.set('smtp.syncUrl', `${baseUri}/connect_another_device`);
conf.set('isProduction', conf.get('env') === 'prod'); convictConf.set('isProduction', convictConf.get('env') === 'prod');
//sns endpoint is not to be set in production //sns endpoint is not to be set in production
if (conf.has('snsTopicEndpoint') && conf.get('env') !== 'dev') { if (convictConf.has('snsTopicEndpoint') && convictConf.get('env') !== 'dev') {
throw new Error('snsTopicEndpoint is only allowed in dev env'); throw new Error('snsTopicEndpoint is only allowed in dev env');
} }
if (conf.get('env') === 'dev') { if (convictConf.get('env') === 'dev') {
if (!process.env.AWS_ACCESS_KEY_ID) { if (!process.env.AWS_ACCESS_KEY_ID) {
process.env.AWS_ACCESS_KEY_ID = 'DEV_KEY_ID'; process.env.AWS_ACCESS_KEY_ID = 'DEV_KEY_ID';
} }
@ -2010,65 +2019,71 @@ if (conf.get('env') === 'dev') {
} }
} }
if (conf.get('oauthServer.openid.keyFile')) { if (convictConf.get('oauthServer.openid.keyFile')) {
const keyFile = path.resolve( const keyFile = path.resolve(
__dirname, __dirname,
'..', '..',
conf.get('oauthServer.openid.keyFile') convictConf.get('oauthServer.openid.keyFile')
); );
conf.set('oauthServer.openid.keyFile', keyFile); convictConf.set('oauthServer.openid.keyFile', keyFile);
// If the file doesnt exist, or contains an empty object, then there's no active key. // If the file doesnt exist, or contains an empty object, then there's no active key.
conf.set('oauthServer.openid.key', null); convictConf.set('oauthServer.openid.key', null);
if (fs.existsSync(keyFile)) { if (fs.existsSync(keyFile)) {
const key = JSON.parse(fs.readFileSync(keyFile, 'utf-8')); const key = JSON.parse(fs.readFileSync(keyFile, 'utf-8'));
if (key && Object.keys(key).length > 0) { if (key && Object.keys(key).length > 0) {
conf.set('oauthServer.openid.key', key); convictConf.set('oauthServer.openid.key', key);
} }
} }
} else if (Object.keys(conf.get('oauthServer.openid.key')).length === 0) { } else if (
conf.set('oauthServer.openid.key', null); Object.keys(convictConf.get('oauthServer.openid.key')).length === 0
) {
convictConf.set('oauthServer.openid.key', null);
} }
if (conf.get('oauthServer.openid.newKeyFile')) { if (convictConf.get('oauthServer.openid.newKeyFile')) {
const newKeyFile = path.resolve( const newKeyFile = path.resolve(
__dirname, __dirname,
'..', '..',
conf.get('oauthServer.openid.newKeyFile') convictConf.get('oauthServer.openid.newKeyFile')
); );
conf.set('oauthServer.openid.newKeyFile', newKeyFile); convictConf.set('oauthServer.openid.newKeyFile', newKeyFile);
// If the file doesnt exist, or contains an empty object, then there's no new key. // If the file doesnt exist, or contains an empty object, then there's no new key.
conf.set('oauthServer.openid.newKey', null); convictConf.set('oauthServer.openid.newKey', null);
if (fs.existsSync(newKeyFile)) { if (fs.existsSync(newKeyFile)) {
const newKey = JSON.parse(fs.readFileSync(newKeyFile, 'utf-8')); const newKey = JSON.parse(fs.readFileSync(newKeyFile, 'utf-8'));
if (newKey && Object.keys(newKey).length > 0) { if (newKey && Object.keys(newKey).length > 0) {
conf.set('oauthServer.openid.newKey', newKey); convictConf.set('oauthServer.openid.newKey', newKey);
} }
} }
} else if (Object.keys(conf.get('oauthServer.openid.newKey')).length === 0) { } else if (
conf.set('oauthServer.openid.newKey', null); Object.keys(convictConf.get('oauthServer.openid.newKey')).length === 0
) {
convictConf.set('oauthServer.openid.newKey', null);
} }
if (conf.get('oauthServer.openid.oldKeyFile')) { if (convictConf.get('oauthServer.openid.oldKeyFile')) {
const oldKeyFile = path.resolve( const oldKeyFile = path.resolve(
__dirname, __dirname,
'..', '..',
conf.get('oauthServer.openid.oldKeyFile') convictConf.get('oauthServer.openid.oldKeyFile')
); );
conf.set('oauthServer.openid.oldKeyFile', oldKeyFile); convictConf.set('oauthServer.openid.oldKeyFile', oldKeyFile);
// If the file doesnt exist, or contains an empty object, then there's no old key. // If the file doesnt exist, or contains an empty object, then there's no old key.
conf.set('oauthServer.openid.oldKey', null); convictConf.set('oauthServer.openid.oldKey', null);
if (fs.existsSync(oldKeyFile)) { if (fs.existsSync(oldKeyFile)) {
const oldKey = JSON.parse(fs.readFileSync(oldKeyFile, 'utf-8')); const oldKey = JSON.parse(fs.readFileSync(oldKeyFile, 'utf-8'));
if (oldKey && Object.keys(oldKey).length > 0) { if (oldKey && Object.keys(oldKey).length > 0) {
conf.set('oauthServer.openid.oldKey', oldKey); convictConf.set('oauthServer.openid.oldKey', oldKey);
} }
} }
} else if (Object.keys(conf.get('oauthServer.openid.oldKey')).length === 0) { } else if (
conf.set('oauthServer.openid.oldKey', null); Object.keys(convictConf.get('oauthServer.openid.oldKey')).length === 0
) {
convictConf.set('oauthServer.openid.oldKey', null);
} }
// Ensure secrets are not set to their default values in production. // Ensure secrets are not set to their default values in production.
if (conf.get('isProduction')) { if (convictConf.get('isProduction')) {
const SECRET_SETTINGS = [ const SECRET_SETTINGS = [
'pushbox.key', 'pushbox.key',
'metrics.flow_id_key', 'metrics.flow_id_key',
@ -2078,13 +2093,13 @@ if (conf.get('isProduction')) {
'supportPanel.secretBearerToken', 'supportPanel.secretBearerToken',
]; ];
for (const key of SECRET_SETTINGS) { for (const key of SECRET_SETTINGS) {
if (conf.get(key) === conf.default(key)) { if (convictConf.get(key) === convictConf.default(key)) {
throw new Error(`Config '${key}' must be set in production`); throw new Error(`Config '${key}' must be set in production`);
} }
} }
} }
export type conf = typeof conf; export type conf = typeof convictConf;
export type ConfigType = ReturnType<conf['getProperties']>; export type ConfigType = ReturnType<conf['getProperties']>;
module.exports = conf; module.exports = convictConf;

Просмотреть файл

@ -25,8 +25,8 @@ class Localizer {
} }
protected async fetchMessages(currentLocales: string[]) { protected async fetchMessages(currentLocales: string[]) {
let fetchedPending: Record<string, Promise<string>> = {}; const fetchedPending: Record<string, Promise<string>> = {};
let fetched: Record<string, string> = {}; const fetched: Record<string, string> = {};
for (const locale of currentLocales) { for (const locale of currentLocales) {
fetchedPending[locale] = this.fetchTranslatedMessages(locale); fetchedPending[locale] = this.fetchTranslatedMessages(locale);
} }

Просмотреть файл

@ -9,10 +9,10 @@ const { AuthLogger } = require('../../types');
const { Container } = require('typedi'); const { Container } = require('typedi');
// These are used only in type declarations. // These are used only in type declarations.
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const AccessToken = require('./accessToken'); const AccessToken = require('./accessToken');
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
const RefreshTokenMetadata = require('./refreshTokenMetadata'); const RefreshTokenMetadata = require('./refreshTokenMetadata');
function resolveLogger() { function resolveLogger() {

Просмотреть файл

@ -12,8 +12,8 @@ export class CurrencyHelper {
this.payPalEnabled = config.subscriptions?.paypalNvpSigCredentials?.enabled; this.payPalEnabled = config.subscriptions?.paypalNvpSigCredentials?.enabled;
// Validate currencyMap // Validate currencyMap
const currencyMap = new Map(Object.entries(config.currenciesToCountries)); const currencyMap = new Map(Object.entries(config.currenciesToCountries));
let allListedCountries: string[] = []; const allListedCountries: string[] = [];
for (let [currency, countries] of currencyMap) { for (const [currency, countries] of currencyMap) {
// Is currency acceptable // Is currency acceptable
if (!CurrencyHelper.validCurrencyCodes.includes(currency)) { if (!CurrencyHelper.validCurrencyCodes.includes(currency)) {
throw new Error(`Currency code ${currency} is invalid.`); throw new Error(`Currency code ${currency} is invalid.`);
@ -25,7 +25,7 @@ export class CurrencyHelper {
throw new Error(`Currency code ${currency} is invalid.`); throw new Error(`Currency code ${currency} is invalid.`);
} }
// Are countries acceptable // Are countries acceptable
for (let country of countries) { for (const country of countries) {
if (!CurrencyHelper.validCountryCodes.includes(country)) { if (!CurrencyHelper.validCountryCodes.includes(country)) {
throw new Error(`Country code ${country} is invalid.`); throw new Error(`Country code ${country} is invalid.`);
} }

Просмотреть файл

@ -20,10 +20,10 @@ import { IapConfig } from './types';
export function getIapPurchaseType( export function getIapPurchaseType(
purchase: PlayStoreSubscriptionPurchase | AppStoreSubscriptionPurchase purchase: PlayStoreSubscriptionPurchase | AppStoreSubscriptionPurchase
): Omit<SubscriptionType, typeof MozillaSubscriptionTypes.WEB> { ): Omit<SubscriptionType, typeof MozillaSubscriptionTypes.WEB> {
if (purchase.hasOwnProperty('purchaseToken')) { if ('purchaseToken' in purchase) {
return MozillaSubscriptionTypes.IAP_GOOGLE; return MozillaSubscriptionTypes.IAP_GOOGLE;
} }
if (purchase.hasOwnProperty('originalTransactionId')) { if ('originalTransactionId' in purchase) {
return MozillaSubscriptionTypes.IAP_APPLE; return MozillaSubscriptionTypes.IAP_APPLE;
} }
throw new Error('Purchase is not recognized as either Google or Apple IAP.'); throw new Error('Purchase is not recognized as either Google or Apple IAP.');

Просмотреть файл

@ -40,7 +40,7 @@ export function appStoreSubscriptionPurchaseToAppStoreSubscriptionDTO(
// TODO: Should this always be present or just for TransactionType of // TODO: Should this always be present or just for TransactionType of
// "Auto-Renewable Subscription"? See https://developer.apple.com/forums/thread/705730 // "Auto-Renewable Subscription"? See https://developer.apple.com/forums/thread/705730
...(purchase.expiresDate && { expiry_time_millis: purchase.expiresDate }), ...(purchase.expiresDate && { expiry_time_millis: purchase.expiresDate }),
...(purchase.hasOwnProperty('isInBillingRetry') && { ...('isInBillingRetry' in purchase && {
is_in_billing_retry_period: purchase.isInBillingRetry, is_in_billing_retry_period: purchase.isInBillingRetry,
}), }),
price_id: purchase.price_id, price_id: purchase.price_id,

Просмотреть файл

@ -2,7 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { CollectionReference, Firestore } from '@google-cloud/firestore'; import { CollectionReference, Firestore } from '@google-cloud/firestore';
import { ACTIVE_SUBSCRIPTION_STATUSES } from 'fxa-shared/subscriptions/stripe';
import { StripeFirestore as StripeFirestoreBase } from 'fxa-shared/payments/stripe-firestore'; import { StripeFirestore as StripeFirestoreBase } from 'fxa-shared/payments/stripe-firestore';
import { Stripe } from 'stripe'; import { Stripe } from 'stripe';

Просмотреть файл

@ -71,7 +71,6 @@ import { AppStoreSubscriptionPurchase } from './iap/apple-app-store/subscription
import { PlayStoreSubscriptionPurchase } from './iap/google-play/subscription-purchase'; import { PlayStoreSubscriptionPurchase } from './iap/google-play/subscription-purchase';
import { getIapPurchaseType } from './iap/iap-config'; import { getIapPurchaseType } from './iap/iap-config';
import { FirestoreStripeError, StripeFirestore } from './stripe-firestore'; import { FirestoreStripeError, StripeFirestore } from './stripe-firestore';
import { stripeInvoiceToFirstInvoicePreviewDTO } from './stripe-formatter';
import { generateIdempotencyKey } from './utils'; import { generateIdempotencyKey } from './utils';
// Maintains backwards compatibility. Some type defs hoisted to fxa-shared/payments/stripe // Maintains backwards compatibility. Some type defs hoisted to fxa-shared/payments/stripe
@ -129,7 +128,7 @@ export type BillingAddressOptions = {
}; };
export type PaymentBillingDetails = Awaited< export type PaymentBillingDetails = Awaited<
ReturnType<StripeHelper['extractBillingDetails']> ReturnType<StripeHelper['extractBillingDetails']> // eslint-disable-line no-use-before-define
> & { > & {
paypal_payment_error?: PaypalPaymentError; paypal_payment_error?: PaypalPaymentError;
billing_agreement_id?: string; billing_agreement_id?: string;
@ -2633,7 +2632,7 @@ export class StripeHelper extends StripeHelperBase {
return []; return [];
} }
let formattedSubscriptions = []; const formattedSubscriptions = [];
for (const subscription of customer.subscriptions.data) { for (const subscription of customer.subscriptions.data) {
if (ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status)) { if (ACTIVE_SUBSCRIPTION_STATUSES.includes(subscription.status)) {
@ -3181,13 +3180,20 @@ export class StripeHelper extends StripeHelperBase {
* Process a webhook event from Stripe and if needed, save it to Firestore. * Process a webhook event from Stripe and if needed, save it to Firestore.
*/ */
async processWebhookEventToFirestore(event: Stripe.Event) { async processWebhookEventToFirestore(event: Stripe.Event) {
const { type, data } = event; const { type } = event;
// Stripe does not include the card_automatically_updated event
// despite this being a valid event for Stripe webhook registration
type StripeEnabledEvent =
| Stripe.WebhookEndpointUpdateParams.EnabledEvent
| 'payment_method.card_automatically_updated';
// Note that we must insert before any event handled by the general // Note that we must insert before any event handled by the general
// webhook code to ensure the object is up to date in Firestore before // webhook code to ensure the object is up to date in Firestore before
// our code handles the event. // our code handles the event.
let handled = true; let handled = true;
try { try {
switch (type as Stripe.WebhookEndpointUpdateParams.EnabledEvent) { switch (type as StripeEnabledEvent) {
case 'invoice.created': case 'invoice.created':
case 'invoice.finalized': case 'invoice.finalized':
case 'invoice.paid': case 'invoice.paid':
@ -3207,7 +3213,6 @@ export class StripeHelper extends StripeHelperBase {
await this.processSubscriptionEventToFirestore(event); await this.processSubscriptionEventToFirestore(event);
break; break;
case 'payment_method.attached': case 'payment_method.attached':
// @ts-ignore
case 'payment_method.card_automatically_updated': case 'payment_method.card_automatically_updated':
case 'payment_method.updated': case 'payment_method.updated':
await this.processPaymentMethodEventToFirestore(event); await this.processPaymentMethodEventToFirestore(event);

Просмотреть файл

@ -4,7 +4,7 @@
import { createHash } from 'crypto'; import { createHash } from 'crypto';
export function generateIdempotencyKey(params: string[]) { export function generateIdempotencyKey(params: string[]) {
let sha = createHash('sha256'); const sha = createHash('sha256');
sha.update(params.join('')); sha.update(params.join(''));
return sha.digest('base64url'); return sha.digest('base64url');
} }

Просмотреть файл

@ -463,7 +463,7 @@ export class AccountHandler {
this.setMetricsFlowCompleteSignal(request, service); this.setMetricsFlowCompleteSignal(request, service);
let { account, password } = await this.createAccount({ const { account, password } = await this.createAccount({
authPW, authPW,
authSalt, authSalt,
email, email,
@ -613,7 +613,6 @@ export class AccountHandler {
form.uid = payload.uid; form.uid = payload.uid;
const account = await this.db.account(uid); const account = await this.db.account(uid);
await this.setPasswordOnStubAccount(account, authPW); await this.setPasswordOnStubAccount(account, authPW);
const metricsContext = await request.gatherMetricsContext({});
await this.signupUtils.verifyAccount(request, account, {}); await this.signupUtils.verifyAccount(request, account, {});
const sessionToken = await this.createSessionToken({ const sessionToken = await this.createSessionToken({
account, account,
@ -635,7 +634,7 @@ export class AccountHandler {
// if it errored out after verifiying the account // if it errored out after verifiying the account
// remove the uid from the list of accounts to send reminders to. // remove the uid from the list of accounts to send reminders to.
if (!!uid) { if (uid) {
const account = await this.db.account(uid); const account = await this.db.account(uid);
if (account.verifierSetAt > 0) { if (account.verifierSetAt > 0) {
await this.subscriptionAccountReminders.delete(uid); await this.subscriptionAccountReminders.delete(uid);
@ -844,6 +843,87 @@ export class AccountHandler {
} }
}; };
const forceTokenVerification = (request: AuthRequest, account: any) => {
// If there was anything suspicious about the request,
// we should force token verification.
if (request.app.isSuspiciousRequest) {
return 'suspect';
}
if (this.config.signinConfirmation?.forceGlobally) {
return 'global';
}
// If it's an email address used for testing etc,
// we should force token verification.
if (
this.config.signinConfirmation?.forcedEmailAddresses?.test(
account.primaryEmail.email
)
) {
return 'email';
}
return false;
};
const skipTokenVerification = (request: AuthRequest, account: any) => {
// If they're logging in from an IP address on which they recently did
// another, successfully-verified login, then we can consider this one
// verified as well without going through the loop again.
const allowedRecency =
this.config.securityHistory.ipProfiling.allowedRecency || 0;
if (securityEventVerified && securityEventRecency < allowedRecency) {
this.log.info('Account.ipprofiling.seenAddress', {
uid: account.uid,
});
return true;
}
// If the account was recently created, don't make the user
// confirm sign-in for a configurable amount of time. This will reduce
// the friction of a user adding a second device.
const skipForNewAccounts =
this.config.signinConfirmation.skipForNewAccounts;
if (skipForNewAccounts?.enabled) {
const accountAge = requestNow - account.createdAt;
if (accountAge <= (skipForNewAccounts.maxAge as unknown as number)) {
this.log.info('account.signin.confirm.bypass.age', {
uid: account.uid,
});
return true;
}
}
// Certain accounts have the ability to *always* skip sign-in confirmation
// regardless of account age or device. This is for internal use where we need
// to guarantee the login experience.
const lowerCaseEmail = account.primaryEmail.normalizedEmail.toLowerCase();
const alwaysSkip =
this.skipConfirmationForEmailAddresses?.includes(lowerCaseEmail);
if (alwaysSkip) {
this.log.info('account.signin.confirm.bypass.always', {
uid: account.uid,
});
return true;
}
return false;
};
const forcePasswordChange = (account: any) => {
// If it's an email address used for testing etc,
// we should force password change.
if (
this.config.forcePasswordChange?.forcedEmailAddresses?.test(
account.primaryEmail.email
)
) {
return true;
}
// otw only force if account lockAt flag set
return accountRecord.lockedAt > 0;
};
const createSessionToken = async () => { const createSessionToken = async () => {
// All sessions are considered unverified by default. // All sessions are considered unverified by default.
let needsVerificationId = true; let needsVerificationId = true;
@ -922,87 +1002,6 @@ export class AccountHandler {
sessionToken = await this.db.createSessionToken(sessionTokenOptions); sessionToken = await this.db.createSessionToken(sessionTokenOptions);
}; };
const forcePasswordChange = (account: any) => {
// If it's an email address used for testing etc,
// we should force password change.
if (
this.config.forcePasswordChange?.forcedEmailAddresses?.test(
account.primaryEmail.email
)
) {
return true;
}
// otw only force if account lockAt flag set
return accountRecord.lockedAt > 0;
};
const forceTokenVerification = (request: AuthRequest, account: any) => {
// If there was anything suspicious about the request,
// we should force token verification.
if (request.app.isSuspiciousRequest) {
return 'suspect';
}
if (this.config.signinConfirmation?.forceGlobally) {
return 'global';
}
// If it's an email address used for testing etc,
// we should force token verification.
if (
this.config.signinConfirmation?.forcedEmailAddresses?.test(
account.primaryEmail.email
)
) {
return 'email';
}
return false;
};
const skipTokenVerification = (request: AuthRequest, account: any) => {
// If they're logging in from an IP address on which they recently did
// another, successfully-verified login, then we can consider this one
// verified as well without going through the loop again.
const allowedRecency =
this.config.securityHistory.ipProfiling.allowedRecency || 0;
if (securityEventVerified && securityEventRecency < allowedRecency) {
this.log.info('Account.ipprofiling.seenAddress', {
uid: account.uid,
});
return true;
}
// If the account was recently created, don't make the user
// confirm sign-in for a configurable amount of time. This will reduce
// the friction of a user adding a second device.
const skipForNewAccounts =
this.config.signinConfirmation.skipForNewAccounts;
if (skipForNewAccounts?.enabled) {
const accountAge = requestNow - account.createdAt;
if (accountAge <= (skipForNewAccounts.maxAge as unknown as number)) {
this.log.info('account.signin.confirm.bypass.age', {
uid: account.uid,
});
return true;
}
}
// Certain accounts have the ability to *always* skip sign-in confirmation
// regardless of account age or device. This is for internal use where we need
// to guarantee the login experience.
const lowerCaseEmail = account.primaryEmail.normalizedEmail.toLowerCase();
const alwaysSkip =
this.skipConfirmationForEmailAddresses?.includes(lowerCaseEmail);
if (alwaysSkip) {
this.log.info('account.signin.confirm.bypass.always', {
uid: account.uid,
});
return true;
}
return false;
};
const sendSigninNotifications = async () => { const sendSigninNotifications = async () => {
await this.signinUtils.sendSigninNotifications( await this.signinUtils.sendSigninNotifications(
request, request,

Просмотреть файл

@ -30,7 +30,7 @@ export class LinkedAccountHandler {
private db: any, private db: any,
private config: ConfigType, private config: ConfigType,
private mailer: any, private mailer: any,
private profile: ProfileClient, private profile: ProfileClient
) { ) {
if (config.googleAuthConfig && config.googleAuthConfig.clientId) { if (config.googleAuthConfig && config.googleAuthConfig.clientId) {
this.googleAuthClient = new OAuth2Client( this.googleAuthClient = new OAuth2Client(
@ -163,7 +163,10 @@ export class LinkedAccountHandler {
const name = idToken.name; const name = idToken.name;
let accountRecord; let accountRecord;
let linkedAccountRecord = await this.db.getLinkedAccount(userid, provider); const linkedAccountRecord = await this.db.getLinkedAccount(
userid,
provider
);
if (!linkedAccountRecord) { if (!linkedAccountRecord) {
try { try {
@ -303,7 +306,7 @@ export const linkedAccountRoutes = (
db: any, db: any,
config: ConfigType, config: ConfigType,
mailer: any, mailer: any,
profile: ProfileClient, profile: ProfileClient
) => { ) => {
const handler = new LinkedAccountHandler(log, db, config, mailer, profile); const handler = new LinkedAccountHandler(log, db, config, mailer, profile);

Просмотреть файл

@ -19,79 +19,6 @@ import { AuthLogger, AuthRequest } from '../../types';
import validators from '../validators'; import validators from '../validators';
import { handleAuth } from './utils'; import { handleAuth } from './utils';
export const mozillaSubscriptionRoutes = ({
log,
db,
customs,
stripeHelper,
playSubscriptions,
appStoreSubscriptions,
capabilityService,
}: {
log: AuthLogger;
db: any;
customs: any;
stripeHelper: StripeHelper;
playSubscriptions?: PlaySubscriptions;
appStoreSubscriptions?: AppStoreSubscriptions;
capabilityService?: CapabilityService;
}): ServerRoute[] => {
if (!playSubscriptions) {
playSubscriptions = Container.get(PlaySubscriptions);
}
if (!appStoreSubscriptions) {
appStoreSubscriptions = Container.get(AppStoreSubscriptions);
}
if (!capabilityService) {
capabilityService = Container.get(CapabilityService);
}
const mozillaSubscriptionHandler = new MozillaSubscriptionHandler(
log,
db,
customs,
stripeHelper,
playSubscriptions,
appStoreSubscriptions,
capabilityService
);
return [
{
method: 'GET',
path: '/oauth/mozilla-subscriptions/customer/billing-and-subscriptions',
options: {
...SUBSCRIPTIONS_DOCS.OAUTH_MOZILLA_SUBSCRIPTIONS_CUSTOMER_BILLING_AND_SUBSCRIPTIONS_GET,
auth: {
payload: false,
strategy: 'oauthToken',
},
response: {
schema: validators.subscriptionsMozillaSubscriptionsValidator as any,
},
},
handler: (request: AuthRequest) =>
mozillaSubscriptionHandler.getBillingDetailsAndSubscriptions(request),
},
{
method: 'GET',
path: '/oauth/mozilla-subscriptions/customer/plan-eligibility/{planId}',
options: {
...SUBSCRIPTIONS_DOCS.OAUTH_MOZILLA_SUBSCRIPTIONS_CUSTOMER_PLAN_ELIGIBILITY,
auth: {
payload: false,
strategy: 'oauthToken',
},
validate: {
params: {
planId: validators.subscriptionsPlanId.required(),
},
},
},
handler: (request: AuthRequest) =>
mozillaSubscriptionHandler.getPlanEligibility(request),
},
];
};
export class MozillaSubscriptionHandler { export class MozillaSubscriptionHandler {
constructor( constructor(
protected log: AuthLogger, protected log: AuthLogger,
@ -173,3 +100,76 @@ export class MozillaSubscriptionHandler {
}; };
} }
} }
export const mozillaSubscriptionRoutes = ({
log,
db,
customs,
stripeHelper,
playSubscriptions,
appStoreSubscriptions,
capabilityService,
}: {
log: AuthLogger;
db: any;
customs: any;
stripeHelper: StripeHelper;
playSubscriptions?: PlaySubscriptions;
appStoreSubscriptions?: AppStoreSubscriptions;
capabilityService?: CapabilityService;
}): ServerRoute[] => {
if (!playSubscriptions) {
playSubscriptions = Container.get(PlaySubscriptions);
}
if (!appStoreSubscriptions) {
appStoreSubscriptions = Container.get(AppStoreSubscriptions);
}
if (!capabilityService) {
capabilityService = Container.get(CapabilityService);
}
const mozillaSubscriptionHandler = new MozillaSubscriptionHandler(
log,
db,
customs,
stripeHelper,
playSubscriptions,
appStoreSubscriptions,
capabilityService
);
return [
{
method: 'GET',
path: '/oauth/mozilla-subscriptions/customer/billing-and-subscriptions',
options: {
...SUBSCRIPTIONS_DOCS.OAUTH_MOZILLA_SUBSCRIPTIONS_CUSTOMER_BILLING_AND_SUBSCRIPTIONS_GET,
auth: {
payload: false,
strategy: 'oauthToken',
},
response: {
schema: validators.subscriptionsMozillaSubscriptionsValidator as any,
},
},
handler: (request: AuthRequest) =>
mozillaSubscriptionHandler.getBillingDetailsAndSubscriptions(request),
},
{
method: 'GET',
path: '/oauth/mozilla-subscriptions/customer/plan-eligibility/{planId}',
options: {
...SUBSCRIPTIONS_DOCS.OAUTH_MOZILLA_SUBSCRIPTIONS_CUSTOMER_PLAN_ELIGIBILITY,
auth: {
payload: false,
strategy: 'oauthToken',
},
validate: {
params: {
planId: validators.subscriptionsPlanId.required(),
},
},
},
handler: (request: AuthRequest) =>
mozillaSubscriptionHandler.getPlanEligibility(request),
},
];
};

Просмотреть файл

@ -86,7 +86,7 @@ export class PayPalNotificationHandler extends PayPalHandler {
if (invoice.status == null || !['draft', 'open'].includes(invoice.status)) { if (invoice.status == null || !['draft', 'open'].includes(invoice.status)) {
if ( if (
invoice.status == 'uncollectible' && invoice.status === 'uncollectible' &&
['Completed', 'Processed'].includes(message.payment_status) ['Completed', 'Processed'].includes(message.payment_status)
) { ) {
// we need to refund the user since the invoice was cancelled // we need to refund the user since the invoice was cancelled
@ -108,7 +108,7 @@ export class PayPalNotificationHandler extends PayPalHandler {
case 'Failed': case 'Failed':
case 'Voided': case 'Voided':
case 'Expired': case 'Expired':
if (message.custom.length == 0) { if (message.custom.length === 0) {
this.log.error('handleMerchPayment', { this.log.error('handleMerchPayment', {
message: 'No idempotency key on PayPal transaction', message: 'No idempotency key on PayPal transaction',
ipnMessage: message, ipnMessage: message,

Просмотреть файл

@ -97,7 +97,7 @@ export class PayPalHandler extends StripeWebhookHandler {
try { try {
await this.customs.check(request, email, 'createSubscriptionWithPaypal'); await this.customs.check(request, email, 'createSubscriptionWithPaypal');
let customer = await this.stripeHelper.fetchCustomer(uid, [ const customer = await this.stripeHelper.fetchCustomer(uid, [
'subscriptions', 'subscriptions',
'tax', 'tax',
]); ]);
@ -135,7 +135,7 @@ export class PayPalHandler extends StripeWebhookHandler {
throw error.billingAgreementExists(customer.id); throw error.billingAgreementExists(customer.id);
} }
const { sourceCountry, subscription } = !!token const { sourceCountry, subscription } = token
? await this._createPaypalBillingAgreementAndSubscription({ ? await this._createPaypalBillingAgreementAndSubscription({
request, request,
uid, uid,
@ -370,7 +370,7 @@ export class PayPalHandler extends StripeWebhookHandler {
const { uid, email } = await handleAuth(this.db, request.auth, true); const { uid, email } = await handleAuth(this.db, request.auth, true);
await this.customs.check(request, email, 'updatePaypalBillingAgreement'); await this.customs.check(request, email, 'updatePaypalBillingAgreement');
let customer = await this.stripeHelper.fetchCustomer(uid, [ const customer = await this.stripeHelper.fetchCustomer(uid, [
'subscriptions', 'subscriptions',
]); ]);
@ -476,7 +476,7 @@ export class PayPalHandler extends StripeWebhookHandler {
// copy bill to address information to Customer // copy bill to address information to Customer
const accountCustomer = await getAccountCustomerByUid(uid); const accountCustomer = await getAccountCustomerByUid(uid);
if (accountCustomer.stripeCustomerId) { if (accountCustomer.stripeCustomerId) {
let locationDetails = {} as any; const locationDetails = {} as any;
if (agreementDetails.countryCode === options.location?.countryCode) { if (agreementDetails.countryCode === options.location?.countryCode) {
// Record the state (short name) if needed // Record the state (short name) if needed
const state = options.location?.state; const state = options.location?.state;

Просмотреть файл

@ -17,7 +17,6 @@ import {
reportValidationError, reportValidationError,
} from '../../../lib/sentry'; } from '../../../lib/sentry';
import error from '../../error'; import error from '../../error';
import { CapabilityService } from '../../payments/capability';
import { PayPalHelper, RefusedError } from '../../payments/paypal'; import { PayPalHelper, RefusedError } from '../../payments/paypal';
import { import {
CUSTOMER_RESOURCE, CUSTOMER_RESOURCE,
@ -792,7 +791,7 @@ export class StripeWebhookHandler extends StripeHandler {
(plan) => (plan.product as Stripe.Product).id !== product.id (plan) => (plan.product as Stripe.Product).id !== product.id
); );
const latestStripePlansForProduct = latestStripePlans.filter( const latestStripePlansForProduct = latestStripePlans.filter(
(plan) => (plan.product as Stripe.Product).id == product.id (plan) => (plan.product as Stripe.Product).id === product.id
); );
if (event.type !== 'product.deleted') { if (event.type !== 'product.deleted') {

Просмотреть файл

@ -283,7 +283,7 @@ export class StripeHandler {
const customer = await this.stripeHelper.fetchCustomer(uid); const customer = await this.stripeHelper.fetchCustomer(uid);
const planCurrency = (await this.stripeHelper.findAbbrevPlanById(planId)) const planCurrency = (await this.stripeHelper.findAbbrevPlanById(planId))
.currency; .currency;
if (customer && customer.currency != planCurrency) { if (customer && customer.currency !== planCurrency) {
throw error.currencyCurrencyMismatch(customer.currency, planCurrency); throw error.currencyCurrencyMismatch(customer.currency, planCurrency);
} }

Просмотреть файл

@ -18,6 +18,37 @@ import { StripeHelper } from '../payments/stripe';
import { AuthLogger, AuthRequest } from '../types'; import { AuthLogger, AuthRequest } from '../types';
import validators from './validators'; import validators from './validators';
export class SupportPanelHandler {
constructor(
protected log: AuthLogger,
protected stripeHelper: StripeHelper,
protected playSubscriptions: PlaySubscriptions,
protected appStoreSubscriptions: AppStoreSubscriptions
) {}
async getSubscriptions(request: AuthRequest) {
this.log.begin('supportPanelGetSubscriptions', request);
const { uid } = request.query as Record<string, string>;
const iapPlaySubscriptions = (
await this.playSubscriptions.getSubscriptions(uid)
).map(playStoreSubscriptionPurchaseToPlayStoreSubscriptionDTO);
const iapAppStoreSubscriptions = (
await this.appStoreSubscriptions.getSubscriptions(uid)
).map(appStoreSubscriptionPurchaseToAppStoreSubscriptionDTO);
const customer = await this.stripeHelper.fetchCustomer(uid);
const webSubscriptions = customer?.subscriptions;
const formattedWebSubscriptions = webSubscriptions
? await this.stripeHelper.formatSubscriptionsForSupport(webSubscriptions)
: [];
return {
[MozillaSubscriptionTypes.WEB]: formattedWebSubscriptions,
[MozillaSubscriptionTypes.IAP_GOOGLE]: iapPlaySubscriptions,
[MozillaSubscriptionTypes.IAP_APPLE]: iapAppStoreSubscriptions,
};
}
}
export const supportPanelRoutes = ({ export const supportPanelRoutes = ({
log, log,
config, config,
@ -74,36 +105,5 @@ export const supportPanelRoutes = ({
]; ];
}; };
export class SupportPanelHandler {
constructor(
protected log: AuthLogger,
protected stripeHelper: StripeHelper,
protected playSubscriptions: PlaySubscriptions,
protected appStoreSubscriptions: AppStoreSubscriptions
) {}
async getSubscriptions(request: AuthRequest) {
this.log.begin('supportPanelGetSubscriptions', request);
const { uid } = request.query as Record<string, string>;
const iapPlaySubscriptions = (
await this.playSubscriptions.getSubscriptions(uid)
).map(playStoreSubscriptionPurchaseToPlayStoreSubscriptionDTO);
const iapAppStoreSubscriptions = (
await this.appStoreSubscriptions.getSubscriptions(uid)
).map(appStoreSubscriptionPurchaseToAppStoreSubscriptionDTO);
const customer = await this.stripeHelper.fetchCustomer(uid);
const webSubscriptions = customer?.subscriptions;
const formattedWebSubscriptions = webSubscriptions
? await this.stripeHelper.formatSubscriptionsForSupport(webSubscriptions)
: [];
return {
[MozillaSubscriptionTypes.WEB]: formattedWebSubscriptions,
[MozillaSubscriptionTypes.IAP_GOOGLE]: iapPlaySubscriptions,
[MozillaSubscriptionTypes.IAP_APPLE]: iapAppStoreSubscriptions,
};
}
}
module.exports = supportPanelRoutes; module.exports = supportPanelRoutes;
module.exports.supportPanelRoutes = supportPanelRoutes; module.exports.supportPanelRoutes = supportPanelRoutes;

Просмотреть файл

@ -48,7 +48,7 @@ export function transformMjIncludeTags(mjml: string): string {
function extractMjIncludeTags(mjml: string): MjIncludeTag[] { function extractMjIncludeTags(mjml: string): MjIncludeTag[] {
let chomp = false; let chomp = false;
let include = ''; let include = '';
let includes = []; const includes = [];
mjml mjml
.replace(/<mj-include/g, ' <mj-include') .replace(/<mj-include/g, ' <mj-include')
.split(/\n|\s/g) .split(/\n|\s/g)
@ -102,7 +102,7 @@ function parseMjIncludeTag(include: string): MjIncludeTag {
} }
function toMjStyle(tag: MjIncludeTag) { function toMjStyle(tag: MjIncludeTag) {
let { inline, path, type } = tag; const { inline, path, type } = tag;
if (type !== 'css') return ''; if (type !== 'css') return '';
if (!path) return ''; if (!path) return '';

Просмотреть файл

@ -2,6 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// NOTE: This file handled with browser ESLint bindings
// instead of NodeJS for DOM typings support
/* eslint-env browser */
import { Story } from '@storybook/html'; import { Story } from '@storybook/html';
import Renderer from '../renderer'; import Renderer from '../renderer';
import { BrowserRendererBindings } from '../renderer/bindings-browser'; import { BrowserRendererBindings } from '../renderer/bindings-browser';

Просмотреть файл

@ -2,6 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// NOTE: This file handled with browser ESLint bindings
// instead of NodeJS for DOM typings support
/* eslint-env browser */
import { RendererBindings, RendererOpts, TemplateContext } from './bindings'; import { RendererBindings, RendererOpts, TemplateContext } from './bindings';
// When rendering templates in storybook, use the mjml-browser implementation // When rendering templates in storybook, use the mjml-browser implementation

Просмотреть файл

@ -2,6 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
// NOTE: This file handled with browser ESLint bindings
// instead of NodeJS for DOM typings support
/* eslint-env browser */
import { ILocalizerBindings } from '../../l10n/interfaces/ILocalizerBindings'; import { ILocalizerBindings } from '../../l10n/interfaces/ILocalizerBindings';
import { LocalizerOpts } from '../../l10n/models/LocalizerOpts'; import { LocalizerOpts } from '../../l10n/models/LocalizerOpts';

Просмотреть файл

@ -3,12 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
import { DOMLocalization, Localization } from '@fluent/dom'; import { DOMLocalization, Localization } from '@fluent/dom';
import { import { RendererBindings, TemplateContext, RendererContext } from './bindings';
RendererBindings,
TemplateContext,
RendererContext,
TemplateValues,
} from './bindings';
import Localizer, { FtlIdMsg } from '../../l10n'; import Localizer, { FtlIdMsg } from '../../l10n';
const RTL_LOCALES = [ const RTL_LOCALES = [
@ -135,9 +130,13 @@ class Renderer extends Localizer {
): Promise<GlobalTemplateValues> { ): Promise<GlobalTemplateValues> {
// We must use 'require' here, 'import' causes an 'unknown file extension .ts' // We must use 'require' here, 'import' causes an 'unknown file extension .ts'
// error. Might be a config option to make it work? // error. Might be a config option to make it work?
// eslint-disable-next-line no-useless-catch
try { try {
// make this a switch statement on 'template' if more cases arise? // make this a switch statement on 'template' if more cases arise?
if (context.template === 'lowRecoveryCodes' || context.template === 'postConsumeRecoveryCode') { if (
context.template === 'lowRecoveryCodes' ||
context.template === 'postConsumeRecoveryCode'
) {
return ( return (
await require(`../emails/templates/${context.template}/includes`) await require(`../emails/templates/${context.template}/includes`)
).getIncludes(context.numberRemaining); ).getIncludes(context.numberRemaining);
@ -159,7 +158,7 @@ class Renderer extends Localizer {
const ftlContext = flattenNestedObjects(context); const ftlContext = flattenNestedObjects(context);
const plainTextArr = text.split('\n'); const plainTextArr = text.split('\n');
for (let i in plainTextArr) { for (const i in plainTextArr) {
// match the lines that are of format key = "value" since we will be extracting the key // match the lines that are of format key = "value" since we will be extracting the key
// to pass down to fluent // to pass down to fluent
const { key, val } = splitPlainTextLine(plainTextArr[i]); const { key, val } = splitPlainTextLine(plainTextArr[i]);

Просмотреть файл

@ -15,18 +15,6 @@ import { ConfigType } from '../config';
* *
*/ */
export interface AuthLogger extends Logger {
(tags: string | string[], data?: string | object): void;
begin(location: string, request: AuthRequest): void;
notifyAttachedServices(
serviceName: string,
request: AuthRequest,
data: Record<string, any>
): Promise<void>;
}
export interface AuthApp extends RequestApplicationState { export interface AuthApp extends RequestApplicationState {
devices: Promise<any>; devices: Promise<any>;
locale: String; locale: String;
@ -56,6 +44,7 @@ export interface AuthApp extends RequestApplicationState {
} }
export interface AuthRequest extends Request { export interface AuthRequest extends Request {
// eslint-disable-next-line no-use-before-define
log: AuthLogger; log: AuthLogger;
app: AuthApp; app: AuthApp;
validateMetricsContext: any; validateMetricsContext: any;
@ -71,8 +60,22 @@ export interface ProfileClient {
updateDisplayName(uid: string, name: string): Promise<void>; updateDisplayName(uid: string, name: string): Promise<void>;
} }
export interface AuthLogger extends Logger {
(tags: string | string[], data?: string | object): void;
begin(location: string, request: AuthRequest): void;
notifyAttachedServices(
serviceName: string,
request: AuthRequest,
data: Record<string, any>
): Promise<void>;
}
// Container token types // Container token types
// eslint-disable-next-line @typescript-eslint/no-redeclare
export const AuthLogger = new Token<AuthLogger>('AUTH_LOGGER'); export const AuthLogger = new Token<AuthLogger>('AUTH_LOGGER');
export const AuthFirestore = new Token<Firestore>('AUTH_FIRESTORE'); export const AuthFirestore = new Token<Firestore>('AUTH_FIRESTORE');
export const AppConfig = new Token<ConfigType>('APP_CONFIG'); export const AppConfig = new Token<ConfigType>('APP_CONFIG');
// eslint-disable-next-line @typescript-eslint/no-redeclare
export const ProfileClient = new Token<ProfileClient>('PROFILE_CLIENT'); export const ProfileClient = new Token<ProfileClient>('PROFILE_CLIENT');

Просмотреть файл

@ -14,7 +14,7 @@
"create-mock-iap": "NODE_ENV=dev FIRESTORE_EMULATOR_HOST=localhost:9090 node -r esbuild-register ./scripts/create-mock-iap-subscriptions.ts", "create-mock-iap": "NODE_ENV=dev FIRESTORE_EMULATOR_HOST=localhost:9090 node -r esbuild-register ./scripts/create-mock-iap-subscriptions.ts",
"bump-template-versions": "node scripts/template-version-bump", "bump-template-versions": "node scripts/template-version-bump",
"audit": "npm audit --json | audit-filter --nsp-config=.nsprc --audit=-", "audit": "npm audit --json | audit-filter --nsp-config=.nsprc --audit=-",
"lint": "eslint . .storybook --ignore-pattern 'dist'", "lint": "eslint . .storybook --ignore-pattern 'dist' --ext .js,.ts",
"postinstall": "grunt merge-ftl && ../../_scripts/clone-l10n.sh fxa-auth-server", "postinstall": "grunt merge-ftl && ../../_scripts/clone-l10n.sh fxa-auth-server",
"start": "yarn emails-scss && scripts/start-local.sh", "start": "yarn emails-scss && scripts/start-local.sh",
"stop": "pm2 stop pm2.config.js", "stop": "pm2 stop pm2.config.js",

Просмотреть файл

@ -4,7 +4,7 @@
import program from 'commander'; import program from 'commander';
import { StatsD } from 'hot-shots'; import { StatsD } from 'hot-shots';
import Redis from 'ioredis'; import Redis from 'ioredis';
import Redlock, { Lock, RedlockAbortSignal } from 'redlock'; import Redlock, { RedlockAbortSignal } from 'redlock';
import Container from 'typedi'; import Container from 'typedi';
import { promisify } from 'util'; import { promisify } from 'util';
@ -90,6 +90,7 @@ export async function init() {
[program.lockName], [program.lockName],
lockDuration, lockDuration,
async (signal: RedlockAbortSignal) => { async (signal: RedlockAbortSignal) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for await (const _ of processor.processInvoices()) { for await (const _ of processor.processInvoices()) {
if (signal.aborted) { if (signal.aborted) {
throw signal.error; throw signal.error;
@ -101,6 +102,7 @@ export async function init() {
throw new Error(`Cannot acquire lock to run: ${err.message}`); throw new Error(`Cannot acquire lock to run: ${err.message}`);
} }
} else { } else {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
for await (const _ of processor.processInvoices()) { for await (const _ of processor.processInvoices()) {
// no need to do anything between invoices since we are not extending a lock // no need to do anything between invoices since we are not extending a lock
} }

Просмотреть файл

@ -1,18 +1,18 @@
import { PayPalClient } from 'fxa-auth-server/lib/payments/paypal/client'; //import { PayPalClient } from 'fxa-auth-server/lib/payments/paypal/client';
import { //import {
deleteAllPayPalBAs, //deleteAllPayPalBAs,
getAllPayPalBAByUid, //getAllPayPalBAByUid,
} from 'fxa-shared/db/models/auth'; //} from 'fxa-shared/db/models/auth';
import { Account } from 'fxa-shared/db/models/auth/account'; //import { Account } from 'fxa-shared/db/models/auth/account';
import Stripe from 'stripe'; //import Stripe from 'stripe';
import Container from 'typedi'; //import Container from 'typedi';
import { promisify } from 'util'; //import { promisify } from 'util';
import { StatsD } from 'hot-shots'; //import { StatsD } from 'hot-shots';
import { PayPalHelper } from '../lib/payments/paypal'; //import { PayPalHelper } from '../lib/payments/paypal';
import { setupProcessingTaskObjects } from '../lib/payments/processing-tasks-setup'; //import { setupProcessingTaskObjects } from '../lib/payments/processing-tasks-setup';
import { StripeHelper } from '../lib/payments/stripe'; //import { StripeHelper } from '../lib/payments/stripe';
import { reportSentryError } from '../lib/sentry'; //import { reportSentryError } from '../lib/sentry';
const config = require('../config').getProperties(); //const config = require('../config').getProperties();
// export async function retreiveUnverifiedAccounts( // export async function retreiveUnverifiedAccounts(
// database: any // database: any

Просмотреть файл

@ -106,7 +106,7 @@ const handleEnglishPlan = (plan: Partial<Stripe.Plan>) => {
return; return;
} }
let lang = findLocaleInTitle('en', plan.nickname!); const lang = findLocaleInTitle('en', plan.nickname!);
if (lang === 'en') { if (lang === 'en') {
// the plan's en strings are different than the product's, so we save // the plan's en strings are different than the product's, so we save

Просмотреть файл

@ -28,7 +28,6 @@ import {
} from './plan-language-tags-guesser'; } from './plan-language-tags-guesser';
import { promises as fsPromises, constants } from 'fs'; import { promises as fsPromises, constants } from 'fs';
import path from 'path'; import path from 'path';
import { string } from 'joi';
const DEFAULT_LOCALE = 'en'; const DEFAULT_LOCALE = 'en';
@ -135,7 +134,7 @@ export class StripeProductsAndPlansConverter {
key.toLowerCase().startsWith('capabilities') key.toLowerCase().startsWith('capabilities')
)) { )) {
// Parse the key to determine if it's an 'all RP' or single RP capability // Parse the key to determine if it's an 'all RP' or single RP capability
const [_, clientId] = oldKey.split(':'); const [, clientId] = oldKey.split(':');
const newKey = clientId ?? '*'; const newKey = clientId ?? '*';
capabilities[newKey] = commaSeparatedListToArray( capabilities[newKey] = commaSeparatedListToArray(
stripeObject.metadata![oldKey] stripeObject.metadata![oldKey]

Просмотреть файл

@ -13,7 +13,7 @@ const through = require('through');
let clientCount = 2; let clientCount = 2;
const pathStats = {}; const pathStats = {};
let requests = 0; let requests = 0;
let pass = 0; // eslint-disable-line no-unused-vars let pass = 0; // eslint-disable-line @typescript-eslint/no-unused-vars
let fail = 0; let fail = 0;
let start = null; let start = null;
@ -41,7 +41,7 @@ server.stderr
} }
requests++; requests++;
if (json.code === 200) { if (json.code === 200) {
pass++; pass++; // eslint-disable-line @typescript-eslint/no-unused-vars
} else { } else {
fail++; fail++;
} }

Просмотреть файл

@ -7,7 +7,6 @@ import { Localization } from '@fluent/dom';
import chai, { assert } from 'chai'; import chai, { assert } from 'chai';
import chaiAsPromised from 'chai-as-promised'; import chaiAsPromised from 'chai-as-promised';
import Localizer from '../../../lib/l10n'; import Localizer from '../../../lib/l10n';
import { parseAcceptLanguage } from 'fxa-shared/l10n/parseAcceptLanguage';
import { LocalizerBindings } from '../../../lib/l10n/bindings'; import { LocalizerBindings } from '../../../lib/l10n/bindings';
chai.use(chaiAsPromised); chai.use(chaiAsPromised);
@ -24,6 +23,7 @@ describe('Localizer', () => {
basePath: '/not/a/apth', basePath: '/not/a/apth',
}, },
}); });
// eslint-disable-next-line no-new
new Localizer(localizerBindings); new Localizer(localizerBindings);
}, 'Invalid ftl translations basePath'); }, 'Invalid ftl translations basePath');
}); });

Просмотреть файл

@ -11,7 +11,7 @@ describe('converts <mj-include> to <mj-style> tag', () => {
return mjml return mjml
.replace(/\n/g, '') .replace(/\n/g, '')
.replace(/\s+/g, ' ') .replace(/\s+/g, ' ')
.replace(/\>\s*</g, '/><') .replace(/>\s*</g, '/><')
.trim(); .trim();
} }

Просмотреть файл

@ -15,11 +15,12 @@ chai.use(chaiAsPromised);
describe('Renderer', () => { describe('Renderer', () => {
it('fails with a bad localizer ftl basePath', () => { it('fails with a bad localizer ftl basePath', () => {
assert.throws(() => { assert.throws(() => {
let LocalizerBindings = new NodeRendererBindings({ const LocalizerBindings = new NodeRendererBindings({
translations: { translations: {
basePath: '/not/a/apth', basePath: '/not/a/apth',
}, },
}); });
// eslint-disable-next-line no-new
new Renderer(LocalizerBindings); new Renderer(LocalizerBindings);
}, 'Invalid ftl translations basePath'); }, 'Invalid ftl translations basePath');
}); });
@ -53,7 +54,7 @@ describe('Renderer', () => {
it('handles escaped quote format', () => { it('handles escaped quote format', () => {
const { key, val } = splitPlainTextLine( const { key, val } = splitPlainTextLine(
`${pair.key}="${pair.val} \"baz\" "` `${pair.key}="${pair.val} "baz" "`
); );
assert.equal(key, pair.key); assert.equal(key, pair.key);
assert.equal(val, pair.val + ' "baz" '); assert.equal(val, pair.val + ' "baz" ');

Просмотреть файл

@ -126,12 +126,8 @@ describe('remote account create', function () {
it('create account with service identifier and resume', () => { it('create account with service identifier and resume', () => {
const email = server.uniqueEmail(); const email = server.uniqueEmail();
const password = 'allyourbasearebelongtous'; const password = 'allyourbasearebelongtous';
let client = null; // eslint-disable-line no-unused-vars
const options = { service: 'abcdef', resume: 'foo' }; const options = { service: 'abcdef', resume: 'foo' };
return Client.create(config.publicUrl, email, password, options) return Client.create(config.publicUrl, email, password, options)
.then((x) => {
client = x;
})
.then(() => { .then(() => {
return server.mailbox.waitForEmail(email); return server.mailbox.waitForEmail(email);
}) })

Просмотреть файл

@ -60,15 +60,11 @@ describe('remote recovery email verify', function () {
it('verification email link', () => { it('verification email link', () => {
const email = server.uniqueEmail(); const email = server.uniqueEmail();
const password = 'something'; const password = 'something';
let client = null; // eslint-disable-line no-unused-vars
const options = { const options = {
redirectTo: `https://sync.${config.smtp.redirectDomain}/`, redirectTo: `https://sync.${config.smtp.redirectDomain}/`,
service: 'sync', service: 'sync',
}; };
return Client.create(config.publicUrl, email, password, options) return Client.create(config.publicUrl, email, password, options)
.then((c) => {
client = c;
})
.then(() => { .then(() => {
return server.mailbox.waitForEmail(email); return server.mailbox.waitForEmail(email);
}) })