зеркало из https://github.com/mozilla/fxa.git
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:
Родитель
6941fd4d64
Коммит
76ef507b26
|
@ -121,6 +121,27 @@ jobs:
|
|||
name: Reporting code coverage...
|
||||
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:
|
||||
resource_class: medium+
|
||||
docker:
|
||||
|
@ -308,6 +329,12 @@ jobs:
|
|||
workflows:
|
||||
test_pull_request:
|
||||
jobs:
|
||||
- lint:
|
||||
filters:
|
||||
branches:
|
||||
ignore: main
|
||||
tags:
|
||||
ignore: /.*/
|
||||
- test-many:
|
||||
filters:
|
||||
branches:
|
||||
|
@ -395,6 +422,12 @@ workflows:
|
|||
ignore: /.*/
|
||||
test_and_deploy_tag:
|
||||
jobs:
|
||||
- lint:
|
||||
filters:
|
||||
branches:
|
||||
ignore: /.*/
|
||||
tags:
|
||||
only: /.*/
|
||||
- test-many:
|
||||
filters:
|
||||
branches:
|
||||
|
|
|
@ -54,11 +54,14 @@
|
|||
"lint-staged": {
|
||||
"*.js": [
|
||||
"prettier --config _dev/.prettierrc --write",
|
||||
"eslint -c _dev/.eslintrc"
|
||||
"eslint"
|
||||
],
|
||||
"*.{ts,tsx}": [
|
||||
"prettier --config _dev/.prettierrc --write"
|
||||
],
|
||||
"packages/fxa-auth-server/**/*.{ts,tsx}": [
|
||||
"eslint"
|
||||
],
|
||||
"*.css": [
|
||||
"prettier --config _dev/.prettierrc --write"
|
||||
],
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
{
|
||||
"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"],
|
||||
"plugins": ["fxa"],
|
||||
"parserOptions": {
|
||||
"ecmaVersion": "2020",
|
||||
"sourceType": "module"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "fxa"],
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"root": true,
|
||||
"ignorePatterns": [
|
||||
"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.
|
||||
|
||||
## Linting
|
||||
|
||||
Run lint with:
|
||||
|
||||
yarn lint
|
||||
|
||||
Linting will also be run for staged files automatically via Husky when you attempt to commit.
|
||||
|
||||
## Testing
|
||||
|
||||
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-validator'));
|
||||
|
||||
const conf = convict({
|
||||
const convictConf = convict({
|
||||
env: {
|
||||
doc: 'The current node.js environment',
|
||||
default: 'prod',
|
||||
|
@ -1572,7 +1572,7 @@ const conf = convict({
|
|||
format: RegExp,
|
||||
// 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+)?(\/.*)?$/,
|
||||
/^https:\/\/[a-zA-Z0-9._-]+(\.services\.mozilla\.com|autopush\.dev\.mozaws\.net|autopush\.stage\.mozaws\.net)(?::\d+)?(\/.*)?$/,
|
||||
},
|
||||
},
|
||||
pushbox: {
|
||||
|
@ -1953,55 +1953,64 @@ const conf = convict({
|
|||
// files to process, which will be overlayed in order, in the CONFIG_FILES
|
||||
// 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 || ''}`;
|
||||
const files = envConfig.split(',').filter(fs.existsSync);
|
||||
conf.loadFile(files);
|
||||
conf.validate();
|
||||
convictConf.loadFile(files);
|
||||
convictConf.validate();
|
||||
|
||||
// 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
|
||||
const baseUri = conf.get('contentServer.url');
|
||||
conf.set('smtp.accountSettingsUrl', `${baseUri}/settings`);
|
||||
conf.set(
|
||||
const baseUri = convictConf.get('contentServer.url');
|
||||
convictConf.set('smtp.accountSettingsUrl', `${baseUri}/settings`);
|
||||
convictConf.set(
|
||||
'smtp.accountRecoveryCodesUrl',
|
||||
`${baseUri}/settings/two_step_authentication/replace_codes`
|
||||
);
|
||||
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.initiatePasswordResetUrl', `${baseUri}/reset_password`);
|
||||
conf.set(
|
||||
convictConf.set('smtp.verificationUrl', `${baseUri}/verify_email`);
|
||||
convictConf.set('smtp.pushVerificationUrl', `${baseUri}/push/confirm_login`);
|
||||
convictConf.set('smtp.passwordResetUrl', `${baseUri}/complete_reset_password`);
|
||||
convictConf.set('smtp.initiatePasswordResetUrl', `${baseUri}/reset_password`);
|
||||
convictConf.set(
|
||||
'smtp.initiatePasswordChangeUrl',
|
||||
`${baseUri}/settings/change_password`
|
||||
);
|
||||
conf.set('smtp.verifyLoginUrl', `${baseUri}/complete_signin`);
|
||||
conf.set(
|
||||
convictConf.set('smtp.verifyLoginUrl', `${baseUri}/complete_signin`);
|
||||
convictConf.set(
|
||||
'smtp.accountFinishSetupUrl',
|
||||
`${baseUri}/post_verify/finish_account_setup/set_password`
|
||||
);
|
||||
conf.set('smtp.reportSignInUrl', `${baseUri}/report_signin`);
|
||||
conf.set('smtp.revokeAccountRecoveryUrl', `${baseUri}/settings#recovery-key`);
|
||||
conf.set(
|
||||
convictConf.set('smtp.reportSignInUrl', `${baseUri}/report_signin`);
|
||||
convictConf.set(
|
||||
'smtp.revokeAccountRecoveryUrl',
|
||||
`${baseUri}/settings#recovery-key`
|
||||
);
|
||||
convictConf.set(
|
||||
'smtp.createAccountRecoveryUrl',
|
||||
`${baseUri}/settings/account_recovery`
|
||||
);
|
||||
conf.set('smtp.verifyPrimaryEmailUrl', `${baseUri}/verify_primary_email`);
|
||||
conf.set('smtp.verifySecondaryEmailUrl', `${baseUri}/verify_secondary_email`);
|
||||
conf.set('smtp.subscriptionSettingsUrl', `${baseUri}/subscriptions`);
|
||||
conf.set('smtp.subscriptionSupportUrl', `${baseUri}/support`);
|
||||
conf.set('smtp.syncUrl', `${baseUri}/connect_another_device`);
|
||||
convictConf.set(
|
||||
'smtp.verifyPrimaryEmailUrl',
|
||||
`${baseUri}/verify_primary_email`
|
||||
);
|
||||
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
|
||||
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');
|
||||
}
|
||||
|
||||
if (conf.get('env') === 'dev') {
|
||||
if (convictConf.get('env') === 'dev') {
|
||||
if (!process.env.AWS_ACCESS_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(
|
||||
__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.
|
||||
conf.set('oauthServer.openid.key', null);
|
||||
convictConf.set('oauthServer.openid.key', null);
|
||||
if (fs.existsSync(keyFile)) {
|
||||
const key = JSON.parse(fs.readFileSync(keyFile, 'utf-8'));
|
||||
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) {
|
||||
conf.set('oauthServer.openid.key', null);
|
||||
} else if (
|
||||
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(
|
||||
__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.
|
||||
conf.set('oauthServer.openid.newKey', null);
|
||||
convictConf.set('oauthServer.openid.newKey', null);
|
||||
if (fs.existsSync(newKeyFile)) {
|
||||
const newKey = JSON.parse(fs.readFileSync(newKeyFile, 'utf-8'));
|
||||
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) {
|
||||
conf.set('oauthServer.openid.newKey', null);
|
||||
} else if (
|
||||
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(
|
||||
__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.
|
||||
conf.set('oauthServer.openid.oldKey', null);
|
||||
convictConf.set('oauthServer.openid.oldKey', null);
|
||||
if (fs.existsSync(oldKeyFile)) {
|
||||
const oldKey = JSON.parse(fs.readFileSync(oldKeyFile, 'utf-8'));
|
||||
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) {
|
||||
conf.set('oauthServer.openid.oldKey', null);
|
||||
} else if (
|
||||
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.
|
||||
if (conf.get('isProduction')) {
|
||||
if (convictConf.get('isProduction')) {
|
||||
const SECRET_SETTINGS = [
|
||||
'pushbox.key',
|
||||
'metrics.flow_id_key',
|
||||
|
@ -2078,13 +2093,13 @@ if (conf.get('isProduction')) {
|
|||
'supportPanel.secretBearerToken',
|
||||
];
|
||||
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`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type conf = typeof conf;
|
||||
export type conf = typeof convictConf;
|
||||
export type ConfigType = ReturnType<conf['getProperties']>;
|
||||
|
||||
module.exports = conf;
|
||||
module.exports = convictConf;
|
||||
|
|
|
@ -25,8 +25,8 @@ class Localizer {
|
|||
}
|
||||
|
||||
protected async fetchMessages(currentLocales: string[]) {
|
||||
let fetchedPending: Record<string, Promise<string>> = {};
|
||||
let fetched: Record<string, string> = {};
|
||||
const fetchedPending: Record<string, Promise<string>> = {};
|
||||
const fetched: Record<string, string> = {};
|
||||
for (const locale of currentLocales) {
|
||||
fetchedPending[locale] = this.fetchTranslatedMessages(locale);
|
||||
}
|
||||
|
|
|
@ -9,10 +9,10 @@ const { AuthLogger } = require('../../types');
|
|||
const { Container } = require('typedi');
|
||||
|
||||
// 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');
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const RefreshTokenMetadata = require('./refreshTokenMetadata');
|
||||
|
||||
function resolveLogger() {
|
||||
|
|
|
@ -12,8 +12,8 @@ export class CurrencyHelper {
|
|||
this.payPalEnabled = config.subscriptions?.paypalNvpSigCredentials?.enabled;
|
||||
// Validate currencyMap
|
||||
const currencyMap = new Map(Object.entries(config.currenciesToCountries));
|
||||
let allListedCountries: string[] = [];
|
||||
for (let [currency, countries] of currencyMap) {
|
||||
const allListedCountries: string[] = [];
|
||||
for (const [currency, countries] of currencyMap) {
|
||||
// Is currency acceptable
|
||||
if (!CurrencyHelper.validCurrencyCodes.includes(currency)) {
|
||||
throw new Error(`Currency code ${currency} is invalid.`);
|
||||
|
@ -25,7 +25,7 @@ export class CurrencyHelper {
|
|||
throw new Error(`Currency code ${currency} is invalid.`);
|
||||
}
|
||||
// Are countries acceptable
|
||||
for (let country of countries) {
|
||||
for (const country of countries) {
|
||||
if (!CurrencyHelper.validCountryCodes.includes(country)) {
|
||||
throw new Error(`Country code ${country} is invalid.`);
|
||||
}
|
||||
|
|
|
@ -20,10 +20,10 @@ import { IapConfig } from './types';
|
|||
export function getIapPurchaseType(
|
||||
purchase: PlayStoreSubscriptionPurchase | AppStoreSubscriptionPurchase
|
||||
): Omit<SubscriptionType, typeof MozillaSubscriptionTypes.WEB> {
|
||||
if (purchase.hasOwnProperty('purchaseToken')) {
|
||||
if ('purchaseToken' in purchase) {
|
||||
return MozillaSubscriptionTypes.IAP_GOOGLE;
|
||||
}
|
||||
if (purchase.hasOwnProperty('originalTransactionId')) {
|
||||
if ('originalTransactionId' in purchase) {
|
||||
return MozillaSubscriptionTypes.IAP_APPLE;
|
||||
}
|
||||
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
|
||||
// "Auto-Renewable Subscription"? See https://developer.apple.com/forums/thread/705730
|
||||
...(purchase.expiresDate && { expiry_time_millis: purchase.expiresDate }),
|
||||
...(purchase.hasOwnProperty('isInBillingRetry') && {
|
||||
...('isInBillingRetry' in purchase && {
|
||||
is_in_billing_retry_period: purchase.isInBillingRetry,
|
||||
}),
|
||||
price_id: purchase.price_id,
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
* 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 { 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 { Stripe } from 'stripe';
|
||||
|
||||
|
|
|
@ -71,7 +71,6 @@ import { AppStoreSubscriptionPurchase } from './iap/apple-app-store/subscription
|
|||
import { PlayStoreSubscriptionPurchase } from './iap/google-play/subscription-purchase';
|
||||
import { getIapPurchaseType } from './iap/iap-config';
|
||||
import { FirestoreStripeError, StripeFirestore } from './stripe-firestore';
|
||||
import { stripeInvoiceToFirstInvoicePreviewDTO } from './stripe-formatter';
|
||||
import { generateIdempotencyKey } from './utils';
|
||||
|
||||
// Maintains backwards compatibility. Some type defs hoisted to fxa-shared/payments/stripe
|
||||
|
@ -129,7 +128,7 @@ export type BillingAddressOptions = {
|
|||
};
|
||||
|
||||
export type PaymentBillingDetails = Awaited<
|
||||
ReturnType<StripeHelper['extractBillingDetails']>
|
||||
ReturnType<StripeHelper['extractBillingDetails']> // eslint-disable-line no-use-before-define
|
||||
> & {
|
||||
paypal_payment_error?: PaypalPaymentError;
|
||||
billing_agreement_id?: string;
|
||||
|
@ -2633,7 +2632,7 @@ export class StripeHelper extends StripeHelperBase {
|
|||
return [];
|
||||
}
|
||||
|
||||
let formattedSubscriptions = [];
|
||||
const formattedSubscriptions = [];
|
||||
|
||||
for (const subscription of customer.subscriptions.data) {
|
||||
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.
|
||||
*/
|
||||
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
|
||||
// webhook code to ensure the object is up to date in Firestore before
|
||||
// our code handles the event.
|
||||
let handled = true;
|
||||
try {
|
||||
switch (type as Stripe.WebhookEndpointUpdateParams.EnabledEvent) {
|
||||
switch (type as StripeEnabledEvent) {
|
||||
case 'invoice.created':
|
||||
case 'invoice.finalized':
|
||||
case 'invoice.paid':
|
||||
|
@ -3207,7 +3213,6 @@ export class StripeHelper extends StripeHelperBase {
|
|||
await this.processSubscriptionEventToFirestore(event);
|
||||
break;
|
||||
case 'payment_method.attached':
|
||||
// @ts-ignore
|
||||
case 'payment_method.card_automatically_updated':
|
||||
case 'payment_method.updated':
|
||||
await this.processPaymentMethodEventToFirestore(event);
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import { createHash } from 'crypto';
|
||||
|
||||
export function generateIdempotencyKey(params: string[]) {
|
||||
let sha = createHash('sha256');
|
||||
const sha = createHash('sha256');
|
||||
sha.update(params.join(''));
|
||||
return sha.digest('base64url');
|
||||
}
|
||||
|
|
|
@ -463,7 +463,7 @@ export class AccountHandler {
|
|||
|
||||
this.setMetricsFlowCompleteSignal(request, service);
|
||||
|
||||
let { account, password } = await this.createAccount({
|
||||
const { account, password } = await this.createAccount({
|
||||
authPW,
|
||||
authSalt,
|
||||
email,
|
||||
|
@ -613,7 +613,6 @@ export class AccountHandler {
|
|||
form.uid = payload.uid;
|
||||
const account = await this.db.account(uid);
|
||||
await this.setPasswordOnStubAccount(account, authPW);
|
||||
const metricsContext = await request.gatherMetricsContext({});
|
||||
await this.signupUtils.verifyAccount(request, account, {});
|
||||
const sessionToken = await this.createSessionToken({
|
||||
account,
|
||||
|
@ -635,7 +634,7 @@ export class AccountHandler {
|
|||
|
||||
// if it errored out after verifiying the account
|
||||
// remove the uid from the list of accounts to send reminders to.
|
||||
if (!!uid) {
|
||||
if (uid) {
|
||||
const account = await this.db.account(uid);
|
||||
if (account.verifierSetAt > 0) {
|
||||
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 () => {
|
||||
// All sessions are considered unverified by default.
|
||||
let needsVerificationId = true;
|
||||
|
@ -922,87 +1002,6 @@ export class AccountHandler {
|
|||
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 () => {
|
||||
await this.signinUtils.sendSigninNotifications(
|
||||
request,
|
||||
|
|
|
@ -30,7 +30,7 @@ export class LinkedAccountHandler {
|
|||
private db: any,
|
||||
private config: ConfigType,
|
||||
private mailer: any,
|
||||
private profile: ProfileClient,
|
||||
private profile: ProfileClient
|
||||
) {
|
||||
if (config.googleAuthConfig && config.googleAuthConfig.clientId) {
|
||||
this.googleAuthClient = new OAuth2Client(
|
||||
|
@ -163,7 +163,10 @@ export class LinkedAccountHandler {
|
|||
const name = idToken.name;
|
||||
|
||||
let accountRecord;
|
||||
let linkedAccountRecord = await this.db.getLinkedAccount(userid, provider);
|
||||
const linkedAccountRecord = await this.db.getLinkedAccount(
|
||||
userid,
|
||||
provider
|
||||
);
|
||||
|
||||
if (!linkedAccountRecord) {
|
||||
try {
|
||||
|
@ -303,7 +306,7 @@ export const linkedAccountRoutes = (
|
|||
db: any,
|
||||
config: ConfigType,
|
||||
mailer: any,
|
||||
profile: ProfileClient,
|
||||
profile: ProfileClient
|
||||
) => {
|
||||
const handler = new LinkedAccountHandler(log, db, config, mailer, profile);
|
||||
|
||||
|
|
|
@ -19,79 +19,6 @@ import { AuthLogger, AuthRequest } from '../../types';
|
|||
import validators from '../validators';
|
||||
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 {
|
||||
constructor(
|
||||
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 == 'uncollectible' &&
|
||||
invoice.status === 'uncollectible' &&
|
||||
['Completed', 'Processed'].includes(message.payment_status)
|
||||
) {
|
||||
// we need to refund the user since the invoice was cancelled
|
||||
|
@ -108,7 +108,7 @@ export class PayPalNotificationHandler extends PayPalHandler {
|
|||
case 'Failed':
|
||||
case 'Voided':
|
||||
case 'Expired':
|
||||
if (message.custom.length == 0) {
|
||||
if (message.custom.length === 0) {
|
||||
this.log.error('handleMerchPayment', {
|
||||
message: 'No idempotency key on PayPal transaction',
|
||||
ipnMessage: message,
|
||||
|
|
|
@ -97,7 +97,7 @@ export class PayPalHandler extends StripeWebhookHandler {
|
|||
try {
|
||||
await this.customs.check(request, email, 'createSubscriptionWithPaypal');
|
||||
|
||||
let customer = await this.stripeHelper.fetchCustomer(uid, [
|
||||
const customer = await this.stripeHelper.fetchCustomer(uid, [
|
||||
'subscriptions',
|
||||
'tax',
|
||||
]);
|
||||
|
@ -135,7 +135,7 @@ export class PayPalHandler extends StripeWebhookHandler {
|
|||
throw error.billingAgreementExists(customer.id);
|
||||
}
|
||||
|
||||
const { sourceCountry, subscription } = !!token
|
||||
const { sourceCountry, subscription } = token
|
||||
? await this._createPaypalBillingAgreementAndSubscription({
|
||||
request,
|
||||
uid,
|
||||
|
@ -370,7 +370,7 @@ export class PayPalHandler extends StripeWebhookHandler {
|
|||
const { uid, email } = await handleAuth(this.db, request.auth, true);
|
||||
await this.customs.check(request, email, 'updatePaypalBillingAgreement');
|
||||
|
||||
let customer = await this.stripeHelper.fetchCustomer(uid, [
|
||||
const customer = await this.stripeHelper.fetchCustomer(uid, [
|
||||
'subscriptions',
|
||||
]);
|
||||
|
||||
|
@ -476,7 +476,7 @@ export class PayPalHandler extends StripeWebhookHandler {
|
|||
// copy bill to address information to Customer
|
||||
const accountCustomer = await getAccountCustomerByUid(uid);
|
||||
if (accountCustomer.stripeCustomerId) {
|
||||
let locationDetails = {} as any;
|
||||
const locationDetails = {} as any;
|
||||
if (agreementDetails.countryCode === options.location?.countryCode) {
|
||||
// Record the state (short name) if needed
|
||||
const state = options.location?.state;
|
||||
|
|
|
@ -17,7 +17,6 @@ import {
|
|||
reportValidationError,
|
||||
} from '../../../lib/sentry';
|
||||
import error from '../../error';
|
||||
import { CapabilityService } from '../../payments/capability';
|
||||
import { PayPalHelper, RefusedError } from '../../payments/paypal';
|
||||
import {
|
||||
CUSTOMER_RESOURCE,
|
||||
|
@ -792,7 +791,7 @@ export class StripeWebhookHandler extends StripeHandler {
|
|||
(plan) => (plan.product as Stripe.Product).id !== product.id
|
||||
);
|
||||
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') {
|
||||
|
|
|
@ -283,7 +283,7 @@ export class StripeHandler {
|
|||
const customer = await this.stripeHelper.fetchCustomer(uid);
|
||||
const planCurrency = (await this.stripeHelper.findAbbrevPlanById(planId))
|
||||
.currency;
|
||||
if (customer && customer.currency != planCurrency) {
|
||||
if (customer && customer.currency !== planCurrency) {
|
||||
throw error.currencyCurrencyMismatch(customer.currency, planCurrency);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,37 @@ import { StripeHelper } from '../payments/stripe';
|
|||
import { AuthLogger, AuthRequest } from '../types';
|
||||
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 = ({
|
||||
log,
|
||||
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 = supportPanelRoutes;
|
||||
|
|
|
@ -48,7 +48,7 @@ export function transformMjIncludeTags(mjml: string): string {
|
|||
function extractMjIncludeTags(mjml: string): MjIncludeTag[] {
|
||||
let chomp = false;
|
||||
let include = '';
|
||||
let includes = [];
|
||||
const includes = [];
|
||||
mjml
|
||||
.replace(/<mj-include/g, ' <mj-include')
|
||||
.split(/\n|\s/g)
|
||||
|
@ -102,7 +102,7 @@ function parseMjIncludeTag(include: string): MjIncludeTag {
|
|||
}
|
||||
|
||||
function toMjStyle(tag: MjIncludeTag) {
|
||||
let { inline, path, type } = tag;
|
||||
const { inline, path, type } = tag;
|
||||
|
||||
if (type !== 'css') return '';
|
||||
if (!path) return '';
|
||||
|
|
|
@ -2,6 +2,10 @@
|
|||
* 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/. */
|
||||
|
||||
// NOTE: This file handled with browser ESLint bindings
|
||||
// instead of NodeJS for DOM typings support
|
||||
/* eslint-env browser */
|
||||
|
||||
import { Story } from '@storybook/html';
|
||||
import Renderer from '../renderer';
|
||||
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
|
||||
* 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';
|
||||
|
||||
// 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
|
||||
* 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 { LocalizerOpts } from '../../l10n/models/LocalizerOpts';
|
||||
|
||||
|
|
|
@ -3,12 +3,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import { DOMLocalization, Localization } from '@fluent/dom';
|
||||
import {
|
||||
RendererBindings,
|
||||
TemplateContext,
|
||||
RendererContext,
|
||||
TemplateValues,
|
||||
} from './bindings';
|
||||
import { RendererBindings, TemplateContext, RendererContext } from './bindings';
|
||||
import Localizer, { FtlIdMsg } from '../../l10n';
|
||||
|
||||
const RTL_LOCALES = [
|
||||
|
@ -135,9 +130,13 @@ class Renderer extends Localizer {
|
|||
): Promise<GlobalTemplateValues> {
|
||||
// We must use 'require' here, 'import' causes an 'unknown file extension .ts'
|
||||
// error. Might be a config option to make it work?
|
||||
// eslint-disable-next-line no-useless-catch
|
||||
try {
|
||||
// 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 (
|
||||
await require(`../emails/templates/${context.template}/includes`)
|
||||
).getIncludes(context.numberRemaining);
|
||||
|
@ -159,7 +158,7 @@ class Renderer extends Localizer {
|
|||
const ftlContext = flattenNestedObjects(context);
|
||||
|
||||
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
|
||||
// to pass down to fluent
|
||||
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 {
|
||||
devices: Promise<any>;
|
||||
locale: String;
|
||||
|
@ -56,6 +44,7 @@ export interface AuthApp extends RequestApplicationState {
|
|||
}
|
||||
|
||||
export interface AuthRequest extends Request {
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
log: AuthLogger;
|
||||
app: AuthApp;
|
||||
validateMetricsContext: any;
|
||||
|
@ -71,8 +60,22 @@ export interface ProfileClient {
|
|||
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
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
export const AuthLogger = new Token<AuthLogger>('AUTH_LOGGER');
|
||||
export const AuthFirestore = new Token<Firestore>('AUTH_FIRESTORE');
|
||||
export const AppConfig = new Token<ConfigType>('APP_CONFIG');
|
||||
// eslint-disable-next-line @typescript-eslint/no-redeclare
|
||||
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",
|
||||
"bump-template-versions": "node scripts/template-version-bump",
|
||||
"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",
|
||||
"start": "yarn emails-scss && scripts/start-local.sh",
|
||||
"stop": "pm2 stop pm2.config.js",
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import program from 'commander';
|
||||
import { StatsD } from 'hot-shots';
|
||||
import Redis from 'ioredis';
|
||||
import Redlock, { Lock, RedlockAbortSignal } from 'redlock';
|
||||
import Redlock, { RedlockAbortSignal } from 'redlock';
|
||||
import Container from 'typedi';
|
||||
import { promisify } from 'util';
|
||||
|
||||
|
@ -90,6 +90,7 @@ export async function init() {
|
|||
[program.lockName],
|
||||
lockDuration,
|
||||
async (signal: RedlockAbortSignal) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for await (const _ of processor.processInvoices()) {
|
||||
if (signal.aborted) {
|
||||
throw signal.error;
|
||||
|
@ -101,6 +102,7 @@ export async function init() {
|
|||
throw new Error(`Cannot acquire lock to run: ${err.message}`);
|
||||
}
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
for await (const _ of processor.processInvoices()) {
|
||||
// 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 {
|
||||
deleteAllPayPalBAs,
|
||||
getAllPayPalBAByUid,
|
||||
} from 'fxa-shared/db/models/auth';
|
||||
import { Account } from 'fxa-shared/db/models/auth/account';
|
||||
import Stripe from 'stripe';
|
||||
import Container from 'typedi';
|
||||
import { promisify } from 'util';
|
||||
import { StatsD } from 'hot-shots';
|
||||
import { PayPalHelper } from '../lib/payments/paypal';
|
||||
import { setupProcessingTaskObjects } from '../lib/payments/processing-tasks-setup';
|
||||
import { StripeHelper } from '../lib/payments/stripe';
|
||||
import { reportSentryError } from '../lib/sentry';
|
||||
const config = require('../config').getProperties();
|
||||
//import { PayPalClient } from 'fxa-auth-server/lib/payments/paypal/client';
|
||||
//import {
|
||||
//deleteAllPayPalBAs,
|
||||
//getAllPayPalBAByUid,
|
||||
//} from 'fxa-shared/db/models/auth';
|
||||
//import { Account } from 'fxa-shared/db/models/auth/account';
|
||||
//import Stripe from 'stripe';
|
||||
//import Container from 'typedi';
|
||||
//import { promisify } from 'util';
|
||||
//import { StatsD } from 'hot-shots';
|
||||
//import { PayPalHelper } from '../lib/payments/paypal';
|
||||
//import { setupProcessingTaskObjects } from '../lib/payments/processing-tasks-setup';
|
||||
//import { StripeHelper } from '../lib/payments/stripe';
|
||||
//import { reportSentryError } from '../lib/sentry';
|
||||
//const config = require('../config').getProperties();
|
||||
|
||||
// export async function retreiveUnverifiedAccounts(
|
||||
// database: any
|
||||
|
|
|
@ -106,7 +106,7 @@ const handleEnglishPlan = (plan: Partial<Stripe.Plan>) => {
|
|||
return;
|
||||
}
|
||||
|
||||
let lang = findLocaleInTitle('en', plan.nickname!);
|
||||
const lang = findLocaleInTitle('en', plan.nickname!);
|
||||
|
||||
if (lang === 'en') {
|
||||
// the plan's en strings are different than the product's, so we save
|
||||
|
|
|
@ -28,7 +28,6 @@ import {
|
|||
} from './plan-language-tags-guesser';
|
||||
import { promises as fsPromises, constants } from 'fs';
|
||||
import path from 'path';
|
||||
import { string } from 'joi';
|
||||
|
||||
const DEFAULT_LOCALE = 'en';
|
||||
|
||||
|
@ -135,7 +134,7 @@ export class StripeProductsAndPlansConverter {
|
|||
key.toLowerCase().startsWith('capabilities')
|
||||
)) {
|
||||
// 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 ?? '*';
|
||||
capabilities[newKey] = commaSeparatedListToArray(
|
||||
stripeObject.metadata![oldKey]
|
||||
|
|
|
@ -13,7 +13,7 @@ const through = require('through');
|
|||
let clientCount = 2;
|
||||
const pathStats = {};
|
||||
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 start = null;
|
||||
|
||||
|
@ -41,7 +41,7 @@ server.stderr
|
|||
}
|
||||
requests++;
|
||||
if (json.code === 200) {
|
||||
pass++;
|
||||
pass++; // eslint-disable-line @typescript-eslint/no-unused-vars
|
||||
} else {
|
||||
fail++;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@ import { Localization } from '@fluent/dom';
|
|||
import chai, { assert } from 'chai';
|
||||
import chaiAsPromised from 'chai-as-promised';
|
||||
import Localizer from '../../../lib/l10n';
|
||||
import { parseAcceptLanguage } from 'fxa-shared/l10n/parseAcceptLanguage';
|
||||
import { LocalizerBindings } from '../../../lib/l10n/bindings';
|
||||
|
||||
chai.use(chaiAsPromised);
|
||||
|
@ -24,6 +23,7 @@ describe('Localizer', () => {
|
|||
basePath: '/not/a/apth',
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line no-new
|
||||
new Localizer(localizerBindings);
|
||||
}, 'Invalid ftl translations basePath');
|
||||
});
|
||||
|
|
|
@ -11,7 +11,7 @@ describe('converts <mj-include> to <mj-style> tag', () => {
|
|||
return mjml
|
||||
.replace(/\n/g, '')
|
||||
.replace(/\s+/g, ' ')
|
||||
.replace(/\>\s*</g, '/><')
|
||||
.replace(/>\s*</g, '/><')
|
||||
.trim();
|
||||
}
|
||||
|
||||
|
|
|
@ -15,11 +15,12 @@ chai.use(chaiAsPromised);
|
|||
describe('Renderer', () => {
|
||||
it('fails with a bad localizer ftl basePath', () => {
|
||||
assert.throws(() => {
|
||||
let LocalizerBindings = new NodeRendererBindings({
|
||||
const LocalizerBindings = new NodeRendererBindings({
|
||||
translations: {
|
||||
basePath: '/not/a/apth',
|
||||
},
|
||||
});
|
||||
// eslint-disable-next-line no-new
|
||||
new Renderer(LocalizerBindings);
|
||||
}, 'Invalid ftl translations basePath');
|
||||
});
|
||||
|
@ -53,7 +54,7 @@ describe('Renderer', () => {
|
|||
|
||||
it('handles escaped quote format', () => {
|
||||
const { key, val } = splitPlainTextLine(
|
||||
`${pair.key}="${pair.val} \"baz\" "`
|
||||
`${pair.key}="${pair.val} "baz" "`
|
||||
);
|
||||
assert.equal(key, pair.key);
|
||||
assert.equal(val, pair.val + ' "baz" ');
|
||||
|
|
|
@ -126,12 +126,8 @@ describe('remote account create', function () {
|
|||
it('create account with service identifier and resume', () => {
|
||||
const email = server.uniqueEmail();
|
||||
const password = 'allyourbasearebelongtous';
|
||||
let client = null; // eslint-disable-line no-unused-vars
|
||||
const options = { service: 'abcdef', resume: 'foo' };
|
||||
return Client.create(config.publicUrl, email, password, options)
|
||||
.then((x) => {
|
||||
client = x;
|
||||
})
|
||||
.then(() => {
|
||||
return server.mailbox.waitForEmail(email);
|
||||
})
|
||||
|
|
|
@ -60,15 +60,11 @@ describe('remote recovery email verify', function () {
|
|||
it('verification email link', () => {
|
||||
const email = server.uniqueEmail();
|
||||
const password = 'something';
|
||||
let client = null; // eslint-disable-line no-unused-vars
|
||||
const options = {
|
||||
redirectTo: `https://sync.${config.smtp.redirectDomain}/`,
|
||||
service: 'sync',
|
||||
};
|
||||
return Client.create(config.publicUrl, email, password, options)
|
||||
.then((c) => {
|
||||
client = c;
|
||||
})
|
||||
.then(() => {
|
||||
return server.mailbox.waitForEmail(email);
|
||||
})
|
||||
|
|
Загрузка…
Ссылка в новой задаче