From 7aee36eab1267bc4d707a8e67394897239cbfcb6 Mon Sep 17 00:00:00 2001 From: Kailash Nadh Date: Sat, 25 Sep 2021 12:57:55 +0530 Subject: [PATCH] Add support for blocklisting e-mail domains. E-mails in the domain blocklist are disallowed on the admin UI, public subscription forms, API, and in the bulk importer. - Add blocklist setting that takes a list of multi-line domains on the Settings -> Privacy UI. - Refactor e-mail validation in subimporter to add blocklist checking centrally. - Add Cypress testr testing domain blocklist behaviour on admin and non-admin views. Closes #336. --- cmd/bounce.go | 36 +- cmd/campaigns.go | 3 +- cmd/init.go | 5 +- cmd/public.go | 6 +- cmd/settings.go | 11 + cmd/subscribers.go | 15 +- .../fixtures/subs-domain-blocklist.csv | 5 + frontend/cypress/integration/subscribers.js | 114 +++- frontend/package.json | 2 +- frontend/src/views/Settings.vue | 6 + frontend/src/views/Subscribers.vue | 144 ++--- frontend/src/views/settings/privacy.vue | 7 + frontend/yarn.lock | 539 ++++++++---------- i18n/cs-cz.json | 3 + i18n/de.json | 3 + i18n/en.json | 3 + i18n/es.json | 3 + i18n/fr.json | 3 + i18n/it.json | 3 + i18n/ml.json | 3 + i18n/pl.json | 3 + i18n/pt-BR.json | 3 + i18n/pt.json | 3 + i18n/ru.json | 3 + i18n/tr.json | 3 + internal/migrations/v2.0.0.go | 1 + internal/subimporter/importer.go | 93 +-- schema.sql | 1 + 28 files changed, 572 insertions(+), 452 deletions(-) create mode 100644 frontend/cypress/fixtures/subs-domain-blocklist.csv diff --git a/cmd/bounce.go b/cmd/bounce.go index f32ec509..ef638f75 100644 --- a/cmd/bounce.go +++ b/cmd/bounce.go @@ -6,10 +6,8 @@ import ( "io/ioutil" "net/http" "strconv" - "strings" "time" - "github.com/knadh/listmonk/internal/subimporter" "github.com/knadh/listmonk/models" "github.com/labstack/echo" "github.com/lib/pq" @@ -164,12 +162,12 @@ func handleBounceWebhook(c echo.Context) error { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.Ts("globals.messages.invalidData")) } - if err := validateBounceFields(b, app); err != nil { + if bv, err := validateBounceFields(b, app); err != nil { return err + } else { + b = bv } - b.Email = strings.ToLower(b.Email) - if len(b.Meta) == 0 { b.Meta = json.RawMessage("{}") } @@ -234,22 +232,26 @@ func handleBounceWebhook(c echo.Context) error { return c.JSON(http.StatusOK, okResp{true}) } -func validateBounceFields(b models.Bounce, app *App) error { +func validateBounceFields(b models.Bounce, app *App) (models.Bounce, error) { if b.Email == "" && b.SubscriberUUID == "" { - return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData")) - } - - if b.Type != models.BounceTypeHard && b.Type != models.BounceTypeSoft { - return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData")) - } - - if b.Email != "" && !subimporter.IsEmail(b.Email) { - return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidEmail")) + return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData")) } if b.SubscriberUUID != "" && !reUUID.MatchString(b.SubscriberUUID) { - return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidUUID")) + return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidUUID")) } - return nil + if b.Email != "" { + em, err := app.importer.SanitizeEmail(b.Email) + if err != nil { + return b, echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } + b.Email = em + } + + if b.Type != models.BounceTypeHard && b.Type != models.BounceTypeSoft { + return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData")) + } + + return b, nil } diff --git a/cmd/campaigns.go b/cmd/campaigns.go index 41576c55..a281c59c 100644 --- a/cmd/campaigns.go +++ b/cmd/campaigns.go @@ -15,7 +15,6 @@ import ( "github.com/gofrs/uuid" "github.com/jmoiron/sqlx" - "github.com/knadh/listmonk/internal/subimporter" "github.com/knadh/listmonk/models" "github.com/labstack/echo" "github.com/lib/pq" @@ -687,7 +686,7 @@ func validateCampaignFields(c campaignReq, app *App) (campaignReq, error) { if c.FromEmail == "" { c.FromEmail = app.constants.FromEmail } else if !regexFromAddress.Match([]byte(c.FromEmail)) { - if !subimporter.IsEmail(c.FromEmail) { + if _, err := app.importer.SanitizeEmail(c.FromEmail); err != nil { return c, errors.New(app.i18n.T("campaigns.fieldInvalidFromEmail")) } } diff --git a/cmd/init.go b/cmd/init.go index 13d06448..226da913 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -61,6 +61,7 @@ type constants struct { AllowExport bool `koanf:"allow_export"` AllowWipe bool `koanf:"allow_wipe"` Exportable map[string]bool `koanf:"-"` + DomainBlocklist map[string]bool `koanf:"-"` } `koanf:"privacy"` AdminUsername []byte `koanf:"admin_username"` AdminPassword []byte `koanf:"admin_password"` @@ -291,6 +292,7 @@ func initConstants() *constants { c.Lang = ko.String("app.lang") c.Privacy.Exportable = maps.StringSliceToLookupMap(ko.Strings("privacy.exportable")) c.MediaProvider = ko.String("upload.provider") + c.Privacy.DomainBlocklist = maps.StringSliceToLookupMap(ko.Strings("privacy.domain_blocklist")) // Static URLS. // url.com/subscription/{campaign_uuid}/{subscriber_uuid} @@ -366,6 +368,7 @@ func initCampaignManager(q *Queries, cs *constants, app *App) *manager.Manager { func initImporter(q *Queries, db *sqlx.DB, app *App) *subimporter.Importer { return subimporter.New( subimporter.Options{ + DomainBlocklist: app.constants.Privacy.DomainBlocklist, UpsertStmt: q.UpsertSubscriber.Stmt, BlocklistStmt: q.UpsertBlocklistSubscriber.Stmt, UpdateListDateStmt: q.UpdateListsDate.Stmt, @@ -373,7 +376,7 @@ func initImporter(q *Queries, db *sqlx.DB, app *App) *subimporter.Importer { app.sendNotification(app.constants.NotifyEmails, subject, notifTplImport, data) return nil }, - }, db.DB) + }, db.DB, app.i18n) } // initSMTPMessenger initializes the SMTP messenger. diff --git a/cmd/public.go b/cmd/public.go index 22e57f87..d3f9b469 100644 --- a/cmd/public.go +++ b/cmd/public.go @@ -318,15 +318,17 @@ func handleSubscriptionForm(c echo.Context) error { } // If there's no name, use the name bit from the e-mail. - req.Email = strings.ToLower(req.Email) + req.Name = strings.TrimSpace(req.Name) if req.Name == "" { req.Name = strings.Split(req.Email, "@")[0] } // Validate fields. - if err := subimporter.ValidateFields(req.SubReq); err != nil { + if r, err := app.importer.ValidateFields(req.SubReq); err != nil { return c.Render(http.StatusInternalServerError, tplMessage, makeMsgTpl(app.i18n.T("public.errorTitle"), "", err.Error())) + } else { + req.SubReq = r } // Insert the subscriber into the DB. diff --git a/cmd/settings.go b/cmd/settings.go index 1d28dd31..d7b5c81f 100644 --- a/cmd/settings.go +++ b/cmd/settings.go @@ -39,6 +39,7 @@ type settings struct { PrivacyAllowExport bool `json:"privacy.allow_export"` PrivacyAllowWipe bool `json:"privacy.allow_wipe"` PrivacyExportable []string `json:"privacy.exportable"` + DomainBlocklist []string `json:"privacy.domain_blocklist"` UploadProvider string `json:"upload.provider"` UploadFilesystemUploadPath string `json:"upload.filesystem.upload_path"` @@ -246,6 +247,16 @@ func handleUpdateSettings(c echo.Context) error { set.SendgridKey = cur.SendgridKey } + // Domain blocklist. + doms := make([]string, 0) + for _, d := range set.DomainBlocklist { + d = strings.TrimSpace(strings.ToLower(d)) + if d != "" { + doms = append(doms, d) + } + } + set.DomainBlocklist = doms + // Marshal settings. b, err := json.Marshal(set) if err != nil { diff --git a/cmd/subscribers.go b/cmd/subscribers.go index db161f24..6f743c53 100644 --- a/cmd/subscribers.go +++ b/cmd/subscribers.go @@ -293,9 +293,12 @@ func handleCreateSubscriber(c echo.Context) error { if err := c.Bind(&req); err != nil { return err } - req.Email = strings.ToLower(strings.TrimSpace(req.Email)) - if err := subimporter.ValidateFields(req); err != nil { + + r, err := app.importer.ValidateFields(req) + if err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } else { + req = r } // Insert the subscriber into the DB. @@ -325,9 +328,13 @@ func handleUpdateSubscriber(c echo.Context) error { if id < 1 { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidID")) } - if req.Email != "" && !subimporter.IsEmail(req.Email) { - return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("subscribers.invalidEmail")) + + if em, err := app.importer.SanitizeEmail(req.Email); err != nil { + return echo.NewHTTPError(http.StatusBadRequest, err.Error()) + } else { + req.Email = em } + if req.Name != "" && !strHasLen(req.Name, 1, stdInputMaxLen) { return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("subscribers.invalidName")) } diff --git a/frontend/cypress/fixtures/subs-domain-blocklist.csv b/frontend/cypress/fixtures/subs-domain-blocklist.csv new file mode 100644 index 00000000..9ee31d8e --- /dev/null +++ b/frontend/cypress/fixtures/subs-domain-blocklist.csv @@ -0,0 +1,5 @@ +email,name,attributes +noban1-import@mail.com,First0 Last0,"{""age"": 29, ""city"": ""Bangalore"", ""clientId"": ""DAXX79""}" +ban1-import@BAN.net,First1 Last1,"{""age"": 43, ""city"": ""Bangalore"", ""clientId"": ""DAXX71""}" +noban2-import1@mail.com,First2 Last2,"{""age"": 47, ""city"": ""Bangalore"", ""clientId"": ""DAXX70""}" +ban2-import@ban.ORG,First1 Last1,"{""age"": 43, ""city"": ""Bangalore"", ""clientId"": ""DAXX71""}" diff --git a/frontend/cypress/integration/subscribers.js b/frontend/cypress/integration/subscribers.js index 2dc8a6af..3237ca40 100644 --- a/frontend/cypress/integration/subscribers.js +++ b/frontend/cypress/integration/subscribers.js @@ -1,3 +1,5 @@ +const apiUrl = Cypress.env('apiUrl'); + describe('Subscribers', () => { it('Opens subscribers page', () => { cy.resetDB(); @@ -43,6 +45,7 @@ describe('Subscribers', () => { }); cy.get('[data-cy=btn-query-reset]').click(); + cy.wait(1000); cy.get('tbody td[data-label=Status]').its('length').should('eq', 2); }); @@ -55,7 +58,7 @@ describe('Subscribers', () => { { radio: 'check-list-remove', lists: [0, 1], rows: { 1: [] } }, { radio: 'check-list-add', lists: [0, 1], rows: { 0: ['unsubscribed', 'unsubscribed'], 1: ['unconfirmed', 'unconfirmed'] } }, { radio: 'check-list-remove', lists: [0], rows: { 0: ['unsubscribed'] } }, - { radio: 'check-list-add', lists: [0], rows: { 0: ['unconfirmed', 'unsubscribed'] } }, + { radio: 'check-list-add', lists: [0], rows: { 0: ['unsubscribed', 'unconfirmed'] } }, ]; @@ -109,7 +112,7 @@ describe('Subscribers', () => { // Open the edit popup and edit the default lists. cy.get('[data-cy=btn-edit]').each(($el, n) => { - const email = `email-${n}@email.com`; + const email = `email-${n}@EMAIL.com`; const name = `name-${n}`; // Open the edit modal. @@ -136,7 +139,7 @@ describe('Subscribers', () => { cy.wait(250); cy.get('tbody tr').each(($el) => { cy.wrap($el).find('td[data-id]').invoke('attr', 'data-id').then((id) => { - cy.wrap($el).find('td[data-label=E-mail]').contains(rows[id].email); + cy.wrap($el).find('td[data-label=E-mail]').contains(rows[id].email.toLowerCase()); cy.wrap($el).find('td[data-label=Name]').contains(rows[id].name); cy.wrap($el).find('td[data-label=Status]').contains(rows[id].status, { matchCase: false }); @@ -171,7 +174,7 @@ describe('Subscribers', () => { // Cycle through each status and each list ID combination and create subscribers. const n = 0; for (let n = 0; n < 6; n++) { - const email = `email-${n}@email.com`; + const email = `email-${n}@EMAIL.com`; const name = `name-${n}`; const status = statuses[(n + 1) % statuses.length]; const list = lists[(n + 1) % lists.length]; @@ -192,7 +195,7 @@ describe('Subscribers', () => { // which is always the first row in the table. cy.wait(250); const tr = cy.get('tbody tr:nth-child(1)').then(($el) => { - cy.wrap($el).find('td[data-label=E-mail]').contains(email); + cy.wrap($el).find('td[data-label=E-mail]').contains(email.toLowerCase()); cy.wrap($el).find('td[data-label=Name]').contains(name); cy.wrap($el).find('td[data-label=Status]').contains(status, { matchCase: false }); cy.wrap($el).find(`.tags .${status === 'enabled' ? 'unconfirmed' : 'unsubscribed'}`) @@ -217,3 +220,104 @@ describe('Subscribers', () => { }); }); }); + + +describe('Domain blocklist', () => { + it('Opens settings page', () => { + cy.resetDB(); + }); + + it('Add domains to blocklist', () => { + cy.loginAndVisit('/settings'); + cy.get('.b-tabs nav a').eq(2).click(); + cy.get('textarea[name="privacy.domain_blocklist"]').clear().type('ban.net\n\nBaN.OrG\n\nban.com\n\n'); + cy.get('[data-cy=btn-save]').click(); + }); + + it('Try subscribing via public page', () => { + cy.visit(`${apiUrl}/subscription/form`); + cy.get('input[name=email]').clear().type('test@noban.net'); + cy.get('button[type=submit]').click(); + cy.get('h2').contains('Subscribe'); + + cy.visit(`${apiUrl}/subscription/form`); + cy.get('input[name=email]').clear().type('test@ban.net'); + cy.get('button[type=submit]').click(); + cy.get('h2').contains('Error'); + }); + + + // Post to the admin API. + it('Try via admin API', () => { + cy.wait(1000); + + // Add non-banned domain. + cy.request({ + method: 'POST', url: `${apiUrl}/api/subscribers`, failOnStatusCode: true, + body: { email: 'test1@noban.net', 'name': 'test', 'lists': [1], 'status': 'enabled' } + }).should((response) => { + expect(response.status).to.equal(200); + }); + + // Add banned domain. + cy.request({ + method: 'POST', url: `${apiUrl}/api/subscribers`, failOnStatusCode: false, + body: { email: 'test1@ban.com', 'name': 'test', 'lists': [1], 'status': 'enabled' } + }).should((response) => { + expect(response.status).to.equal(400); + }); + + // Modify an existinb subscriber to a banned domain. + cy.request({ + method: 'PUT', url: `${apiUrl}/api/subscribers/1`, failOnStatusCode: false, + body: { email: 'test3@ban.org', 'name': 'test', 'lists': [1], 'status': 'enabled' } + }).should((response) => { + expect(response.status).to.equal(400); + }); + }); + + it('Try via import', () => { + cy.loginAndVisit('/subscribers/import'); + cy.get('.list-selector input').click(); + cy.get('.list-selector .autocomplete a').first().click(); + + cy.fixture('subs-domain-blocklist.csv').then((data) => { + cy.get('input[type="file"]').attachFile({ + fileContent: data.toString(), + fileName: 'subs.csv', + mimeType: 'text/csv', + }); + }); + + cy.get('button.is-primary').click(); + cy.get('section.wrap .has-text-success'); + // cy.get('button.is-primary').click(); + cy.get('.log-view').should('contain', 'ban1-import@BAN.net').and('contain', 'ban2-import@ban.ORG'); + cy.wait(100); + }); + + it('Clear blocklist and try', () => { + cy.loginAndVisit('/settings'); + cy.get('.b-tabs nav a').eq(2).click(); + cy.get('textarea[name="privacy.domain_blocklist"]').clear(); + cy.get('[data-cy=btn-save]').click(); + cy.wait(1000); + + // Add banned domain. + cy.request({ + method: 'POST', url: `${apiUrl}/api/subscribers`, failOnStatusCode: true, + body: { email: 'test4@BAN.com', 'name': 'test', 'lists': [1], 'status': 'enabled' } + }).should((response) => { + expect(response.status).to.equal(200); + }); + + // Modify an existinb subscriber to a banned domain. + cy.request({ + method: 'PUT', url: `${apiUrl}/api/subscribers/1`, failOnStatusCode: true, + body: { email: 'test4@BAN.org', 'name': 'test', 'lists': [1], 'status': 'enabled' } + }).should((response) => { + expect(response.status).to.equal(200); + }); + }); + +}) diff --git a/frontend/package.json b/frontend/package.json index 720d92c9..afd5653a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -35,7 +35,7 @@ "@vue/cli-service": "~4.5.13", "@vue/eslint-config-airbnb": "^5.3.0", "babel-eslint": "^10.1.0", - "cypress": "^6.4.0", + "cypress": "8.4.1", "cypress-file-upload": "^5.0.2", "eslint": "^7.27.0", "eslint-plugin-import": "^2.23.3", diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index 00995ad4..d935689f 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -134,6 +134,9 @@ export default Vue.extend({ } } + // Domain blocklist array from multi-line strings. + form['privacy.domain_blocklist'] = form['privacy.domain_blocklist'].split('\n').map((v) => v.trim().toLowerCase()).filter((v) => v !== ''); + this.isLoading = true; this.$api.updateSettings(form).then((data) => { if (data.needsRestart) { @@ -192,6 +195,9 @@ export default Vue.extend({ } d['bounce.sendgrid_key'] = dummyPassword; + // Domain blocklist array to multi-line string. + d['privacy.domain_blocklist'] = d['privacy.domain_blocklist'].join('\n'); + this.key += 1; this.form = d; this.formCopy = JSON.stringify(d); diff --git a/frontend/src/views/Subscribers.vue b/frontend/src/views/Subscribers.vue index 25ebdf55..41f55286 100644 --- a/frontend/src/views/Subscribers.vue +++ b/frontend/src/views/Subscribers.vue @@ -18,84 +18,86 @@ -
-
-
-
- - - - +
+
+
+ +
+ + + + +

+ + + {{ $t('subscribers.advancedQuery') }} + +

+ +
+ + + + + + + {{ $t('subscribers.advancedQueryHelp') }}.{{ ' ' }} + + {{ $t('globals.buttons.learnMore') }}. + + + + +
+ {{ $t('subscribers.query') }} + + {{ $t('subscribers.reset') }} + +
+
+
+ +
+ +
+

- - - {{ $t('subscribers.advancedQuery') }} - + + {{ $t('subscribers.numSelected', { num: numSelectedSubscribers }) }} + + + — + + {{ $t('subscribers.selectAll', { num: subscribers.total }) }} + +

-
- - - - - - - {{ $t('subscribers.advancedQueryHelp') }}.{{ ' ' }} - - {{ $t('globals.buttons.learnMore') }}. - - - - -
- {{ $t('subscribers.query') }} - - {{ $t('subscribers.reset') }} - -
-
-
- -
- -
-
diff --git a/frontend/src/views/settings/privacy.vue b/frontend/src/views/settings/privacy.vue index fce095d2..878e5676 100644 --- a/frontend/src/views/settings/privacy.vue +++ b/frontend/src/views/settings/privacy.vue @@ -29,6 +29,13 @@ + + + +
diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 95d03b94..5646a1a7 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1131,20 +1131,10 @@ lodash "^4.17.13" to-fast-properties "^2.0.0" -"@cypress/listr-verbose-renderer@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" - integrity sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo= - dependencies: - chalk "^1.1.3" - cli-cursor "^1.0.2" - date-fns "^1.27.2" - figures "^1.7.0" - -"@cypress/request@^2.88.5": - version "2.88.5" - resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.5.tgz#8d7ecd17b53a849cfd5ab06d5abe7d84976375d7" - integrity sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA== +"@cypress/request@^2.88.6": + version "2.88.6" + resolved "https://registry.yarnpkg.com/@cypress/request/-/request-2.88.6.tgz#a970dd675befc6bdf8a8921576c01f51cc5798e9" + integrity sha512-z0UxBE/+qaESAHY9p9sM2h8Y4XqtsbDCt0/DPOrqA/RZgKi4PkxdpXyK4wCCnSk1xHqWHZZAE+gV6aDAR6+caQ== dependencies: aws-sign2 "~0.7.0" aws4 "^1.8.0" @@ -1159,13 +1149,12 @@ isstream "~0.1.2" json-stringify-safe "~5.0.1" mime-types "~2.1.19" - oauth-sign "~0.9.0" performance-now "^2.1.0" qs "~6.5.2" safe-buffer "^5.1.2" tough-cookie "~2.5.0" tunnel-agent "^0.6.0" - uuid "^3.3.2" + uuid "^8.3.2" "@cypress/xvfb@^1.2.4": version "1.2.4" @@ -1244,13 +1233,6 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b" integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw== -"@samverschueren/stream-to-observable@^0.3.0": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz#a21117b19ee9be70c379ec1877537ef2e1c63301" - integrity sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ== - dependencies: - any-observable "^0.3.0" - "@soda/friendly-errors-webpack-plugin@^1.7.1": version "1.7.1" resolved "https://registry.yarnpkg.com/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.7.1.tgz#706f64bcb4a8b9642b48ae3ace444c70334d615d" @@ -1367,10 +1349,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.11.tgz#61d4886e2424da73b7b25547f59fdcb534c165a3" integrity sha512-lCvvI24L21ZVeIiyIUHZ5Oflv1hhHQ5E1S25IRlKIXaRkVgmXpJMI3wUJkmym2bTbCe+WoIibQnMVAU3FguaOg== -"@types/node@12.12.50": - version "12.12.50" - resolved "https://registry.yarnpkg.com/@types/node/-/node-12.12.50.tgz#e9b2e85fafc15f2a8aa8fdd41091b983da5fd6ee" - integrity sha512-5ImO01Fb8YsEOYpV+aeyGYztcYcjGsBvN4D7G5r1ef2cuQOpymjWNQi5V0rKHE6PC2ru3HkoUr/Br2/8GUA84w== +"@types/node@^14.14.31": + version "14.17.18" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.18.tgz#0198489a751005f71217744aa966cd1f29447c81" + integrity sha512-haYyibw4pbteEhkSg0xdDLAI3679L75EJ799ymVrPxOA922bPx3ML59SoDsQ//rHlvqpu+e36kcbR3XRQtFblA== "@types/normalize-package-data@^2.4.0": version "2.4.0" @@ -1405,10 +1387,10 @@ "@types/mime" "^1" "@types/node" "*" -"@types/sinonjs__fake-timers@^6.0.1": - version "6.0.2" - resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae" - integrity sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg== +"@types/sinonjs__fake-timers@^6.0.2": + version "6.0.4" + resolved "https://registry.yarnpkg.com/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz#0ecc1b9259b76598ef01942f547904ce61a6a77d" + integrity sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A== "@types/sizzle@^2.3.2": version "2.3.2" @@ -1464,6 +1446,13 @@ anymatch "^3.0.0" source-map "^0.6.0" +"@types/yauzl@^2.9.1": + version "2.9.2" + resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.9.2.tgz#c48e5d56aff1444409e39fa164b0b4d4552a7b7a" + integrity sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA== + dependencies: + "@types/node" "*" + "@vue/babel-helper-vue-jsx-merge-props@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@vue/babel-helper-vue-jsx-merge-props/-/babel-helper-vue-jsx-merge-props-1.2.1.tgz#31624a7a505fb14da1d58023725a4c5f270e6a81" @@ -1943,6 +1932,14 @@ address@^1.1.2: resolved "https://registry.yarnpkg.com/address/-/address-1.1.2.tgz#bf1116c9c758c51b7a933d296b72c221ed9428b6" integrity sha512-aT6camzM4xEA54YVJYSqxz1kv4IHnQZRtThJJHhUMRExaU5spC7jX5ugSwTaTgJliIgs4VhZOk7htClvQ/LmRA== +aggregate-error@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + ajv-errors@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" @@ -2003,11 +2000,6 @@ ansi-colors@^4.1.1: resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== -ansi-escapes@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" - integrity sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ== - ansi-escapes@^4.2.1: version "4.3.1" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.1.tgz#a5c47cc43181f1f38ffd7076837700d395522a61" @@ -2015,6 +2007,13 @@ ansi-escapes@^4.2.1: dependencies: type-fest "^0.11.0" +ansi-escapes@^4.3.0: + version "4.3.2" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e" + integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ== + dependencies: + type-fest "^0.21.3" + ansi-html@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e" @@ -2060,11 +2059,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: "@types/color-name" "^1.1.1" color-convert "^2.0.1" -any-observable@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" - integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog== - any-promise@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" @@ -2104,7 +2098,7 @@ arch@^2.1.1: resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.2.tgz#0c52bbe7344bb4fa260c443d2cbad9c00ff2f0bf" integrity sha512-NTBIIbAfkJeIletyABbVtdPgeKfDafR+1mZV/AyyfC1UkVkp9iUjV+wwmqtUgphHYajbI86jejBJp5e+jkGTiQ== -arch@^2.1.2: +arch@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11" integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ== @@ -2415,7 +2409,7 @@ bindings@^1.5.0: dependencies: file-uri-to-path "1.0.0" -blob-util@2.0.2: +blob-util@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/blob-util/-/blob-util-2.0.2.tgz#3b4e3c281111bb7f11128518006cdc60b403a1eb" integrity sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ== @@ -2777,7 +2771,7 @@ caseless@~0.12.0: resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@^1.0.0, chalk@^1.1.3: +chalk@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= @@ -2894,10 +2888,10 @@ ci-info@^1.5.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== -ci-info@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" - integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== +ci-info@^3.1.1: + version "3.2.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.2.0.tgz#2876cb948a498797b5236f0095bc057d0dca38b6" + integrity sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" @@ -2924,14 +2918,12 @@ clean-css@4.2.x: dependencies: source-map "~0.6.0" -cli-cursor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" - integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= - dependencies: - restore-cursor "^1.0.1" +clean-stack@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b" + integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A== -cli-cursor@^2.0.0, cli-cursor@^2.1.0: +cli-cursor@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" integrity sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU= @@ -2972,13 +2964,13 @@ cli-table3@~0.6.0: optionalDependencies: colors "^1.1.2" -cli-truncate@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" - integrity sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ= +cli-truncate@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-2.1.0.tgz#c39e28bf05edcde5be3b98992a22deed5a2b93c7" + integrity sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg== dependencies: - slice-ansi "0.0.4" - string-width "^1.0.1" + slice-ansi "^3.0.0" + string-width "^4.2.0" cli-width@^2.0.0: version "2.2.1" @@ -3026,11 +3018,6 @@ coa@^2.0.2: chalk "^2.4.1" q "^1.1.2" -code-point-at@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" - integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= - codeflask@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/codeflask/-/codeflask-1.4.1.tgz#c5229854e3f648377922a75f1145f7316030d3db" @@ -3092,6 +3079,11 @@ colorette@^1.2.1, colorette@^1.2.2: resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94" integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w== +colorette@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.4.0.tgz#5190fbb87276259a86ad700bff2c6d6faa3fca40" + integrity sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g== + colors@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" @@ -3164,7 +3156,7 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-stream@^1.5.0, concat-stream@^1.6.2: +concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== @@ -3558,47 +3550,48 @@ cypress-file-upload@^5.0.2: dependencies: mime "^2.5.0" -cypress@^6.4.0: - version "6.8.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-6.8.0.tgz#8338f39212a8f71e91ff8c017a1b6e22d823d8c1" - integrity sha512-W2e9Oqi7DmF48QtOD0LfsOLVq6ef2hcXZvJXI/E3PgFNmZXEVwBefhAxVCW9yTPortjYA2XkM20KyC4HRkOm9w== +cypress@8.4.1: + version "8.4.1" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-8.4.1.tgz#8b5898bf49359cadc28f02ba05d51f63b8e3a717" + integrity sha512-itJXq0Vx3sXCUrDyBi2IUrkxVu/gTTp1VhjB5tzGgkeCR8Ae+/T8WV63rsZ7fS8Tpq7LPPXiyoM/sEdOX7cR6A== dependencies: - "@cypress/listr-verbose-renderer" "^0.4.1" - "@cypress/request" "^2.88.5" + "@cypress/request" "^2.88.6" "@cypress/xvfb" "^1.2.4" - "@types/node" "12.12.50" - "@types/sinonjs__fake-timers" "^6.0.1" + "@types/node" "^14.14.31" + "@types/sinonjs__fake-timers" "^6.0.2" "@types/sizzle" "^2.3.2" - arch "^2.1.2" - blob-util "2.0.2" + arch "^2.2.0" + blob-util "^2.0.2" bluebird "^3.7.2" cachedir "^2.3.0" chalk "^4.1.0" check-more-types "^2.24.0" + cli-cursor "^3.1.0" cli-table3 "~0.6.0" commander "^5.1.0" common-tags "^1.8.0" - dayjs "^1.9.3" - debug "4.3.2" - eventemitter2 "^6.4.2" - execa "^4.0.2" + dayjs "^1.10.4" + debug "^4.3.2" + enquirer "^2.3.6" + eventemitter2 "^6.4.3" + execa "4.1.0" executable "^4.1.1" - extract-zip "^1.7.0" - fs-extra "^9.0.1" + extract-zip "2.0.1" + figures "^3.2.0" + fs-extra "^9.1.0" getos "^3.2.1" - is-ci "^2.0.0" - is-installed-globally "^0.3.2" + is-ci "^3.0.0" + is-installed-globally "~0.4.0" lazy-ass "^1.6.0" - listr "^0.14.3" - lodash "^4.17.19" + listr2 "^3.8.3" + lodash "^4.17.21" log-symbols "^4.0.0" minimist "^1.2.5" - moment "^2.29.1" ospath "^1.2.2" - pretty-bytes "^5.4.1" + pretty-bytes "^5.6.0" ramda "~0.27.1" request-progress "^3.0.0" - supports-color "^7.2.0" + supports-color "^8.1.1" tmp "~0.2.1" untildify "^4.0.0" url "^0.11.0" @@ -3859,12 +3852,7 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -date-fns@^1.27.2: - version "1.30.1" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" - integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== - -dayjs@^1.10.4, dayjs@^1.9.3: +dayjs@^1.10.4: version "1.10.4" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2" integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw== @@ -3881,13 +3869,6 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9: dependencies: ms "2.0.0" -debug@4.3.2: - version "4.3.2" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" - integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== - dependencies: - ms "2.1.2" - debug@^3.1.0, debug@^3.2.7: version "3.2.7" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" @@ -3909,6 +3890,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" +debug@^4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + decamelize@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -4201,11 +4189,6 @@ electron-to-chromium@^1.3.723: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.752.tgz#0728587f1b9b970ec9ffad932496429aef750d09" integrity sha512-2Tg+7jSl3oPxgsBsWKh5H83QazTkmWG/cnNwJplmyZc7KcN61+I10oUgaXSVk/NwfvN3BdkKDR4FYuRBQQ2v0A== -elegant-spinner@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" - integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= - elliptic@^6.0.0, elliptic@^6.5.2: version "6.5.4" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" @@ -4269,7 +4252,7 @@ enhanced-resolve@^4.1.0: memory-fs "^0.5.0" tapable "^1.0.0" -enquirer@^2.3.5: +enquirer@^2.3.5, enquirer@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/enquirer/-/enquirer-2.3.6.tgz#2a7fe5dd634a1e4125a975ec994ff5456dc3734d" integrity sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg== @@ -4624,7 +4607,7 @@ event-pubsub@4.3.0: resolved "https://registry.yarnpkg.com/event-pubsub/-/event-pubsub-4.3.0.tgz#f68d816bc29f1ec02c539dc58c8dd40ce72cb36e" integrity sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ== -eventemitter2@^6.4.2: +eventemitter2@^6.4.3: version "6.4.4" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.4.tgz#aa96e8275c4dbeb017a5d0e03780c65612a1202b" integrity sha512-HLU3NDY6wARrLCEwyGKRBvuWYyvW6mHYv72SJJAH3iJN3a6eVUvkjFkcxah1bcTgGVBBrFdIopBJPhCQFMLyXw== @@ -4654,6 +4637,21 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: md5.js "^1.3.4" safe-buffer "^5.1.1" +execa@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" + integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== + dependencies: + cross-spawn "^7.0.0" + get-stream "^5.0.0" + human-signals "^1.1.1" + is-stream "^2.0.0" + merge-stream "^2.0.0" + npm-run-path "^4.0.0" + onetime "^5.1.0" + signal-exit "^3.0.2" + strip-final-newline "^2.0.0" + execa@^0.8.0: version "0.8.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da" @@ -4696,21 +4694,6 @@ execa@^3.3.0: signal-exit "^3.0.2" strip-final-newline "^2.0.0" -execa@^4.0.2: - version "4.1.0" - resolved "https://registry.yarnpkg.com/execa/-/execa-4.1.0.tgz#4e5491ad1572f2f17a77d388c6c857135b22847a" - integrity sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA== - dependencies: - cross-spawn "^7.0.0" - get-stream "^5.0.0" - human-signals "^1.1.1" - is-stream "^2.0.0" - merge-stream "^2.0.0" - npm-run-path "^4.0.0" - onetime "^5.1.0" - signal-exit "^3.0.2" - strip-final-newline "^2.0.0" - executable@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" @@ -4718,11 +4701,6 @@ executable@^4.1.1: dependencies: pify "^2.2.0" -exit-hook@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" - integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= - expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" @@ -4815,15 +4793,16 @@ extglob@^2.0.4: snapdragon "^0.8.1" to-regex "^3.0.1" -extract-zip@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.7.0.tgz#556cc3ae9df7f452c493a0cfb51cc30277940927" - integrity sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA== +extract-zip@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-2.0.1.tgz#663dca56fe46df890d5f131ef4a06d22bb8ba13a" + integrity sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg== dependencies: - concat-stream "^1.6.2" - debug "^2.6.9" - mkdirp "^0.5.4" + debug "^4.1.1" + get-stream "^5.1.0" yauzl "^2.10.0" + optionalDependencies: + "@types/yauzl" "^2.9.1" extsprintf@1.3.0: version "1.3.0" @@ -4893,22 +4872,7 @@ figgy-pudding@^3.5.1: resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.2.tgz#b4eee8148abb01dcf1d1ac34367d59e12fa61d6e" integrity sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw== -figures@^1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" - integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= - dependencies: - escape-string-regexp "^1.0.5" - object-assign "^4.1.0" - -figures@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" - integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI= - dependencies: - escape-string-regexp "^1.0.5" - -figures@^3.0.0: +figures@^3.0.0, figures@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/figures/-/figures-3.2.0.tgz#625c18bd293c604dc4a8ddb2febf0c88341746af" integrity sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg== @@ -5111,7 +5075,7 @@ fs-extra@^7.0.1: jsonfile "^4.0.0" universalify "^0.1.0" -fs-extra@^9.0.1: +fs-extra@^9.1.0: version "9.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-9.1.0.tgz#5954460c764a8da2094ba3554bf839e6b9a7c86d" integrity sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ== @@ -5202,6 +5166,13 @@ get-stream@^5.0.0: dependencies: pump "^3.0.0" +get-stream@^5.1.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.2.0.tgz#4966a1795ee5ace65e706c4b7beb71257d6e22d3" + integrity sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -5253,12 +5224,12 @@ glob@^7.0.3, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -global-dirs@^2.0.1: - version "2.1.0" - resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-2.1.0.tgz#e9046a49c806ff04d6c1825e196c8f0091e8df4d" - integrity sha512-MG6kdOUh/xBnyo9cJFeIKkLEc1AyFq42QTU4XiX51i2NEdxLxLWXIjEjmqKeSuKR7pAZjTqUVoT2b2huxVLgYQ== +global-dirs@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-3.0.0.tgz#70a76fe84ea315ab37b1f5576cbde7d48ef72686" + integrity sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA== dependencies: - ini "1.3.7" + ini "2.0.0" globals@^11.1.0: version "11.12.0" @@ -5742,10 +5713,10 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= -indent-string@^3.0.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" - integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== indent.js@^0.3.5: version "0.3.5" @@ -5785,10 +5756,10 @@ inherits@2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= -ini@1.3.7: - version "1.3.7" - resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.7.tgz#a09363e1911972ea16d7a8851005d84cf09a9a84" - integrity sha512-iKpRpXP+CrP2jyrxvg1kMUpXDyRUFDWurxbnVT1vQPx+Wz9uCYsMIqYuSBLV+PAaZG/d7kRLKRFc9oDMsH+mFQ== +ini@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ini/-/ini-2.0.0.tgz#e5fd556ecdd5726be978fa1001862eacb0a94bc5" + integrity sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA== inquirer@^7.1.0: version "7.1.0" @@ -5931,12 +5902,12 @@ is-ci@^1.0.10: dependencies: ci-info "^1.5.0" -is-ci@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c" - integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w== +is-ci@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-3.0.0.tgz#c7e7be3c9d8eef7d0fa144390bd1e4b88dc4c994" + integrity sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ== dependencies: - ci-info "^2.0.0" + ci-info "^3.1.1" is-color-stop@^1.0.0: version "1.1.0" @@ -6021,13 +5992,6 @@ is-extglob@^2.1.0, is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-fullwidth-code-point@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" - integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= - dependencies: - number-is-nan "^1.0.0" - is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" @@ -6052,13 +6016,13 @@ is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-installed-globally@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.3.2.tgz#fd3efa79ee670d1187233182d5b0a1dd00313141" - integrity sha512-wZ8x1js7Ia0kecP/CHM/3ABkAmujX7WPvQk6uu3Fly/Mk44pySulQpnHG46OMjHGXApINnV4QhY3SWnECO2z5g== +is-installed-globally@~0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.4.0.tgz#9a0fd407949c30f86eb6959ef1b7994ed0b7b520" + integrity sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ== dependencies: - global-dirs "^2.0.1" - is-path-inside "^3.0.1" + global-dirs "^3.0.0" + is-path-inside "^3.0.2" is-negative-zero@^2.0.1: version "2.0.1" @@ -6087,13 +6051,6 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-observable@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-observable/-/is-observable-1.1.0.tgz#b3e986c8f44de950867cab5403f5a3465005975e" - integrity sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA== - dependencies: - symbol-observable "^1.1.0" - is-path-cwd@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-2.2.0.tgz#67d43b82664a7b5191fd9119127eb300048a9fdb" @@ -6113,7 +6070,7 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" -is-path-inside@^3.0.1: +is-path-inside@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== @@ -6135,11 +6092,6 @@ is-plain-object@^2.0.3, is-plain-object@^2.0.4: dependencies: isobject "^3.0.1" -is-promise@^2.1.0: - version "2.2.2" - resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1" - integrity sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ== - is-regex@^1.0.4, is-regex@^1.0.5: version "1.1.0" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.0.tgz#ece38e389e490df0dc21caea2bd596f987f767ff" @@ -6454,49 +6406,18 @@ lines-and-columns@^1.1.6: resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA= -listr-silent-renderer@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" - integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4= - -listr-update-renderer@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz#4ea8368548a7b8aecb7e06d8c95cb45ae2ede6a2" - integrity sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA== +listr2@^3.8.3: + version "3.12.2" + resolved "https://registry.yarnpkg.com/listr2/-/listr2-3.12.2.tgz#2d55cc627111603ad4768a9e87c9c7bb9b49997e" + integrity sha512-64xC2CJ/As/xgVI3wbhlPWVPx0wfTqbUAkpb7bjDi0thSWMqrf07UFhrfsGoo8YSXmF049Rp9C0cjLC8rZxK9A== dependencies: - chalk "^1.1.3" - cli-truncate "^0.2.1" - elegant-spinner "^1.0.1" - figures "^1.7.0" - indent-string "^3.0.0" - log-symbols "^1.0.2" - log-update "^2.3.0" - strip-ansi "^3.0.1" - -listr-verbose-renderer@^0.5.0: - version "0.5.0" - resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz#f1132167535ea4c1261102b9f28dac7cba1e03db" - integrity sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw== - dependencies: - chalk "^2.4.1" - cli-cursor "^2.1.0" - date-fns "^1.27.2" - figures "^2.0.0" - -listr@^0.14.3: - version "0.14.3" - resolved "https://registry.yarnpkg.com/listr/-/listr-0.14.3.tgz#2fea909604e434be464c50bddba0d496928fa586" - integrity sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA== - dependencies: - "@samverschueren/stream-to-observable" "^0.3.0" - is-observable "^1.1.0" - is-promise "^2.1.0" - is-stream "^1.1.0" - listr-silent-renderer "^1.1.1" - listr-update-renderer "^0.5.0" - listr-verbose-renderer "^0.5.0" - p-map "^2.0.0" - rxjs "^6.3.3" + cli-truncate "^2.1.0" + colorette "^1.4.0" + log-update "^4.0.0" + p-map "^4.0.0" + rxjs "^6.6.7" + through "^2.3.8" + wrap-ansi "^7.0.0" load-json-file@^4.0.0: version "4.0.0" @@ -6627,18 +6548,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.21, lodash@^4.17.3: +lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.21, lodash@^4.17.3: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" - integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg= - dependencies: - chalk "^1.0.0" - log-symbols@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" @@ -6654,14 +6568,15 @@ log-symbols@^4.0.0: chalk "^4.1.0" is-unicode-supported "^0.1.0" -log-update@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/log-update/-/log-update-2.3.0.tgz#88328fd7d1ce7938b29283746f0b1bc126b24708" - integrity sha1-iDKP19HOeTiykoN0bwsbwSayRwg= +log-update@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-update/-/log-update-4.0.0.tgz#589ecd352471f2a1c0c570287543a64dfd20e0a1" + integrity sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg== dependencies: - ansi-escapes "^3.0.0" - cli-cursor "^2.0.0" - wrap-ansi "^3.0.1" + ansi-escapes "^4.3.0" + cli-cursor "^3.1.0" + slice-ansi "^4.0.0" + wrap-ansi "^6.2.0" loglevel@^1.6.8: version "1.6.8" @@ -6936,18 +6851,13 @@ mixin-deep@^1.2.0: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.1: +mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ== dependencies: minimist "^1.2.5" -moment@^2.29.1: - version "2.29.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" - integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== - move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" @@ -7177,11 +7087,6 @@ num2fraction@^1.2.2: resolved "https://registry.yarnpkg.com/num2fraction/-/num2fraction-1.2.2.tgz#6f682b6a027a4e9ddfa4564cd2589d1d4e669ede" integrity sha1-b2gragJ6Tp3fpFZM0lidHU5mnt4= -number-is-nan@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -7324,11 +7229,6 @@ once@^1.3.0, once@^1.3.1, once@^1.4.0: dependencies: wrappy "1" -onetime@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" - integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= - onetime@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" @@ -7458,6 +7358,13 @@ p-map@^2.0.0: resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175" integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw== +p-map@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b" + integrity sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ== + dependencies: + aggregate-error "^3.0.0" + p-retry@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-3.0.1.tgz#316b4c8893e2c8dc1cfa891f406c4b422bebf328" @@ -8083,7 +7990,7 @@ prettier@^1.18.2: resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb" integrity sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew== -pretty-bytes@^5.4.1: +pretty-bytes@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-5.6.0.tgz#356256f643804773c82f64723fe78c92c62beaeb" integrity sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg== @@ -8556,14 +8463,6 @@ resolve@^1.14.2, resolve@^1.20.0: is-core-module "^2.2.0" path-parse "^1.0.6" -restore-cursor@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" - integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= - dependencies: - exit-hook "^1.0.0" - onetime "^1.0.0" - restore-cursor@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" @@ -8639,13 +8538,6 @@ rw@1: resolved "https://registry.yarnpkg.com/rw/-/rw-1.3.3.tgz#3f862dfa91ab766b14885ef4d01124bfda074fb4" integrity sha1-P4Yt+pGrdmsUiF700BEkv9oHT7Q= -rxjs@^6.3.3: - version "6.6.6" - resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.6.tgz#14d8417aa5a07c5e633995b525e1e3c0dec03b70" - integrity sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg== - dependencies: - tslib "^1.9.0" - rxjs@^6.5.3: version "6.5.5" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.5.5.tgz#c5c884e3094c8cfee31bf27eb87e54ccfc87f9ec" @@ -8653,6 +8545,13 @@ rxjs@^6.5.3: dependencies: tslib "^1.9.0" +rxjs@^6.6.7: + version "6.6.7" + resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -8918,10 +8817,14 @@ slash@^2.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-2.0.0.tgz#de552851a1759df3a8f206535442f5ec4ddeab44" integrity sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A== -slice-ansi@0.0.4: - version "0.0.4" - resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" - integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= +slice-ansi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-3.0.0.tgz#31ddc10930a1b7e0b67b08c96c2f49b77a789787" + integrity sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ== + dependencies: + ansi-styles "^4.0.0" + astral-regex "^2.0.0" + is-fullwidth-code-point "^3.0.0" slice-ansi@^4.0.0: version "4.0.0" @@ -9184,16 +9087,7 @@ strict-uri-encode@^1.0.0: resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" integrity sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM= -string-width@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" - integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= - dependencies: - code-point-at "^1.0.0" - is-fullwidth-code-point "^1.0.0" - strip-ansi "^3.0.0" - -string-width@^2.0.0, string-width@^2.1.1: +string-width@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== @@ -9364,13 +9258,20 @@ supports-color@^6.1.0: dependencies: has-flag "^3.0.0" -supports-color@^7.1.0, supports-color@^7.2.0: +supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: has-flag "^4.0.0" +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + svg-tags@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/svg-tags/-/svg-tags-1.0.0.tgz#58f71cee3bd519b59d4b2a843b6c7de64ac04764" @@ -9395,11 +9296,6 @@ svgo@^1.0.0: unquote "~1.1.1" util.promisify "~1.0.0" -symbol-observable@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804" - integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ== - table@^6.0.9: version "6.7.1" resolved "https://registry.yarnpkg.com/table/-/table-6.7.1.tgz#ee05592b7143831a8c94f3cee6aae4c1ccef33e2" @@ -9507,7 +9403,7 @@ through2@^2.0.0: readable-stream "~2.3.6" xtend "~4.0.1" -through@^2.3.6: +through@^2.3.6, through@^2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU= @@ -9674,6 +9570,11 @@ type-fest@^0.20.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== +type-fest@^0.21.3: + version "0.21.3" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" + integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== + type-fest@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" @@ -9909,6 +9810,11 @@ uuid@^3.3.2, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^8.3.2: + version "8.3.2" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.2.tgz#80d5b5ced271bb9af6c445f21a1a04c606cefbe2" + integrity sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg== + v8-compile-cache@^2.0.3: version "2.1.1" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz#54bc3cdd43317bca91e35dcaf305b1a7237de745" @@ -10248,14 +10154,6 @@ worker-farm@^1.7.0: dependencies: errno "~0.1.7" -wrap-ansi@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-3.0.1.tgz#288a04d87eda5c286e060dfe8f135ce8d007f8ba" - integrity sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo= - dependencies: - string-width "^2.1.1" - strip-ansi "^4.0.0" - wrap-ansi@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" @@ -10274,6 +10172,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" diff --git a/i18n/cs-cz.json b/i18n/cs-cz.json index f7f7a307..6afa62e9 100644 --- a/i18n/cs-cz.json +++ b/i18n/cs-cz.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "Umožnit odběratelům exportovat shromážděná data?", "settings.privacy.allowWipe": "Umožnit vymazání", "settings.privacy.allowWipeHelp": "Umožnit odběratelům odstranit sebe včetně svých odběrů a všech ostatních dat z databáze. Pohledy na kampaně a klepnutí na odkazy se rovněž odeberou, zatímco pohledy a počty klepnutí se zachovají (aniž by měly přidruženého odběratele), takže statistiky a analýzy nebudou ovlivněny.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Sledování jednotlivých odběratelů", "settings.privacy.individualSubTrackingHelp": "Sledovat klepnutí a pohledy na kampaně na úrovni odběratelů. Je-li to zakázáno, sledování klepnutí a pohledů pokračuje, aniž by bylo propojeno s jednotlivými odběrateli.", "settings.privacy.listUnsubHeader": "Zahrnout záhlaví `List-Unsubscribe`", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Blokovat {num} odběratelů?", "subscribers.confirmDelete": "Odstranit {num} odběratelů?", "subscribers.confirmExport": "Exportovat {num} odběratelů?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Stáhnout data", "subscribers.email": "E-mail", "subscribers.emailExists": "E-mail již existuje.", diff --git a/i18n/de.json b/i18n/de.json index 3d2add76..252e6b0f 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "Erlaube Abonnenten alle ihre Daten zu exportieren?", "settings.privacy.allowWipe": "Löschen aktivieren", "settings.privacy.allowWipeHelp": "Erlaube Abonnenten alle Daten, welche über sie gespeichert sind zu löschen. Dies beinhaltet auch Klicks und Anzeigen, verändert allerdings nicht die Gesamtzahl. Statistiken bleiben auch unverändert.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Einzelabonnenten Tracking", "settings.privacy.individualSubTrackingHelp": "Abonnentenviews und Klicks werden einzeln getrackt. Wenn deaktiviert, werden die Daten ohne Zuordnung zu Abonnenten gespeichert.", "settings.privacy.listUnsubHeader": "Inkludiere `List-Unsubscribe` (von Liste abmelden) Header", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Blockiere {num} Abonnent(en)?", "subscribers.confirmDelete": "Lösche {num} Abonnent(en)?", "subscribers.confirmExport": "Exportiere {num} Abonnent(en)?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Daten herunterladen", "subscribers.email": "E-Mail", "subscribers.emailExists": "E-Mail existiert bereits.", diff --git a/i18n/en.json b/i18n/en.json index 23109597..600275ed 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "Allow subscribers to export data collected on them?", "settings.privacy.allowWipe": "Allow wiping", "settings.privacy.allowWipeHelp": "Allow subscribers to delete themselves including their subscriptions and all other data from the database. Campaign views and link clicks are also removed while views and click counts remain (with no subscriber associated to them) so that stats and analytics are not affected.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Individual subscriber tracking", "settings.privacy.individualSubTrackingHelp": "Track subscriber-level campaign views and clicks. When disabled, view and click tracking continue without being linked to individual subscribers.", "settings.privacy.listUnsubHeader": "Include `List-Unsubscribe` header", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Blocklist {num} subscriber(s)?", "subscribers.confirmDelete": "Delete {num} subscriber(s)?", "subscribers.confirmExport": "Export {num} subscriber(s)?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Download data", "subscribers.email": "E-mail", "subscribers.emailExists": "E-mail already exists.", diff --git a/i18n/es.json b/i18n/es.json index 01aaa5a8..8411e3eb 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "¿Permitir a los subscriptores exportar los datos recabados de ellos?", "settings.privacy.allowWipe": "Permitir limpieza de datos", "settings.privacy.allowWipeHelp": "Permitir a los subscriptores eliminarse incluyendo sus subscripciones y todos sus datos de la base de datos. Las vistas de las campañas y los vínculos cliqueados también son eliminados mientras que las vistas y el conteo de clics se mantienen. (sin subscriptores asociados a ellos) de manera que las estadísticas y el análisis no se vea afectado.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Seguimiento de subscriptor inválido.", "settings.privacy.individualSubTrackingHelp": "Seguir a nivel de subscriptor las vistas y clics en una campaña. Cuando está deshabilitado, el seguimiento de vistas y clics continua sin ser asociado con subscriptores individuales.", "settings.privacy.listUnsubHeader": "Incluir el encabezado `Des-subscribirse` de la lista", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Blocklist {num} subscriptor(es)?", "subscribers.confirmDelete": "Borrar {num} subscriptor(es)?", "subscribers.confirmExport": "Exportar {num} subscriptor(es)?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Descargar datos", "subscribers.email": "Correo electrónico", "subscribers.emailExists": "El correo electrónico ya existe.", diff --git a/i18n/fr.json b/i18n/fr.json index 66578856..f0508a9a 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "Autoriser les abonné·es à exporter les données collectées à leur sujet ?", "settings.privacy.allowWipe": "Autoriser la suppression des données par les abonné·es", "settings.privacy.allowWipeHelp": "Autoriser les abonné·es à supprimer leurs abonnements et toutes les autres données de la base de données. Les vues de campagne et les clics sur les liens sont également supprimés, tandis que le compteur de vues et de nombre de clics globaux restent inchangés (aucun·e abonné·e ne leur est associé) afin que les statistiques et les analyses ne soient pas affectées.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Suivi individuel des abonné·es (vérifiez si la légalislation l'autorise)", "settings.privacy.individualSubTrackingHelp": "Suivez les vues et les clics par abonné·e pour les campagnes (vérifiez si la légalislation en vigueur l'autorise). Si l'option est désactivée, le suivi des vues et des clics s'effectue de façon anonyme.", "settings.privacy.listUnsubHeader": "Inclure l'en-tête de désabonnement simplifié (via certaines messageries)", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Bloquer {num} abonné·e(s) ?", "subscribers.confirmDelete": "Supprimer {num} abonné·e(s) ?", "subscribers.confirmExport": "Exporter {num} abonné·e(s) ?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Télécharger les données", "subscribers.email": "Email", "subscribers.emailExists": "Cet email existe déjà.", diff --git a/i18n/it.json b/i18n/it.json index 315fc933..b1300443 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "Autorizzi gli iscritti a esportare i dati raccolti su di loro?", "settings.privacy.allowWipe": "Autorizza la cancellazione", "settings.privacy.allowWipeHelp": "Autorizza gli iscritti a cancellare le loro iscrizioni e tutti gli altri dati dal database. Le visualizzazioni della campagna e i clic sui link verranno anch'essi cancellati, mentre i contatori globali delle visualizzazioni e del numero di clic restano invariati (nessun iscritto vi è associato) in modo che le statistiche non siano compromesse.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Follow-up individuale degli abbonati", "settings.privacy.individualSubTrackingHelp": "Monitora le visualizzazioni e i clic della campagna per iscritto. Quando è disabilitato, il follow-up delle visualizzazioni e dei clic, si effettua senza essere legato agli iscritti individuali.", "settings.privacy.listUnsubHeader": "Includere l'intestazione `List-Unsubscribe`", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Lista di blocco {num} iscritto(i)?", "subscribers.confirmDelete": "Elimina {num} iscrittoi(i)?", "subscribers.confirmExport": "Esporta {num} iscritto(i)?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Scarica i dati", "subscribers.email": "Email", "subscribers.emailExists": "Email già esistente.", diff --git a/i18n/ml.json b/i18n/ml.json index 6cc71341..a0ab14e7 100644 --- a/i18n/ml.json +++ b/i18n/ml.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "ഉപഭോക്കാക്കളിൽ നിന്നും ശേഖരിച്ച വിവരങ്ങൾ എക്സ്പോർട്ട് ചെയ്യാൻ അനുവദിക്കണോ?", "settings.privacy.allowWipe": "വിവരങ്ങൾ എന്നന്നേയ്ക്കുമായി ഇല്ലാതാക്കുന്നത് അനുവദിക്കുക", "settings.privacy.allowWipeHelp": "ഉപഭോക്താക്കളെ അവരുടെ വരിക്കാരായിട്ടുള്ള ലിസ്റ്റുകളും മറ്റു വിവരങ്ങളും ഡാറ്റാബേസിൽ നിന്നും ഇല്ലാതാക്കാൻ അനുവദിക്കുക.ക്യാമ്പെയ്ൻ കാഴ്ചകളും കണ്ണികളിന്മേലുള്ള ക്ലിക്കുകളുടെ വിവരങ്ങളും ഇല്ലാതാക്കുമെങ്കിലും കാഴ്ചകളുടെയും കണ്ണിയിലുള്ള ക്ലിക്കുകളുടെ (ഉപഭോക്തൃ വിവരങ്ങളില്ലാതെ) എണ്ണവും നിലനിൽക്കും. അതിനാൽ സ്ഥിതിവിവരക്കണക്കുകളെയും വിശകലനങ്ങളെയും ബാധിക്കില്ല.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "വ്യക്തിഗത വരിക്കാരെ പിൻതുടരുക", "settings.privacy.individualSubTrackingHelp": "ഉപഭോക്തൃ തലത്തിലുള്ള ക്യാമ്പെയ്ൻ കാഴ്ചകളും കണ്ണിയിലെ ക്ലിക്കുകളും പിൻതുടരുക. അപ്രാപ്‌തമാക്കിയാൽ ക്യാമ്പെയ്ൻ കാഴ്ചകളും കണ്ണികളിന്മേലുള്ള ക്ലിക്കുകളുടെ വിവരങ്ങളും രേഖപ്പെടുത്തുമെങ്കുലും ഉപഭോക്താക്കളുടെ വിവരങ്ങളോട് ചേർക്കില്ല.", "settings.privacy.listUnsubHeader": "`List-Unsubscribe` തലക്കെട്ട് കൂട്ടിച്ചേർക്കുക", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "വരിക്കാരനെ തടയുന്ന പട്ടികയിൽ ചേർക്കട്ടേ? | {num} വരിക്കാരേ തടയുന്ന പട്ടികയിൽ ചേർക്കട്ടേ?", "subscribers.confirmDelete": "വരിക്കാരനെ ഇല്ലാതാക്കട്ടെ? | {num} വരിക്കാരേ ഇല്ലാതാക്കട്ടെ?", "subscribers.confirmExport": "വരിക്കാരനെ എക്സ്പോർട്ട് ചെയ്യട്ടേ? | {num} വരിക്കാരെ എക്സ്പോർട്ട് ചെയ്യട്ടേ?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "ഡാറ്റ ഡൗൺലോഡുചെയ്യുക", "subscribers.email": "ഇ-മെയിൽ", "subscribers.emailExists": "ഇ-മെയിൽ നേരത്തേതന്നെ ഉള്ളതാണ്", diff --git a/i18n/pl.json b/i18n/pl.json index 6bcb49d4..3df6f2e2 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "Czy zezwolić subskrybentom na eksportowanie danych zebranych o nich?", "settings.privacy.allowWipe": "Zezwól na czyszczenie danych", "settings.privacy.allowWipeHelp": "Czy zezwolić subskrybentom na usuwanie ich samych razem z wszystkimi ich danymi? Wyświetlenia i liczba kliknięć zostaną zachowane, ale zostaną z nich usunięte informacje kto wykonał tę akcję.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Śledzenie indywidualnych subskrybentów", "settings.privacy.individualSubTrackingHelp": "Śledź dane wyświetleń i kliknięć na poziomie pojedynczego subskrybenta. Jeśli wyłączone dane będą nadal zbierane, ale niepowiązane ze subskrybentami.", "settings.privacy.listUnsubHeader": "Dodawaj nagłówek `List-Unsubscribe`", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Czy zablokować {num} subskrybentów?", "subscribers.confirmDelete": "Usunąć {num} subskrybentów?", "subscribers.confirmExport": "Wyeksportować {num} subskrybentów?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Pobierz dane", "subscribers.email": "Email", "subscribers.emailExists": "Email już istnieje.", diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index c6c36062..d3eb74f4 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "Permitir que os assinantes exportem os dados coletados neles?", "settings.privacy.allowWipe": "Permitir limpeza", "settings.privacy.allowWipeHelp": "Permitir que os assinantes se excluam incluindo suas inscrições e todos os outros dados da base de dados. Visualizações da campanha e cliques de links também são removidos enquanto o total de visualizações e cliques permanecem (com nenhum inscrito associado a eles) para que as estatísticas e análises não sejam afetadas.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Rastreamento individual de inscrito", "settings.privacy.individualSubTrackingHelp": "Rastrear visualizações e cliques de cada inscrito. Quando desativado, o rastreio da visualizações e clique continuar sem estar associado a nenhuma inscrição.", "settings.privacy.listUnsubHeader": "Incluir cabeçalho `List-Unsubscribe`", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Bloquear {num} inscrito(s)?", "subscribers.confirmDelete": "Excluir {num} inscrito(s)?", "subscribers.confirmExport": "Exportar {num} inscrito(s)?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Baixar dados", "subscribers.email": "E-mail", "subscribers.emailExists": "E-mail já existe.", diff --git a/i18n/pt.json b/i18n/pt.json index c7e6633c..f3b0fb4e 100644 --- a/i18n/pt.json +++ b/i18n/pt.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "Permitir aos subscritores exportar os dados coletados neles mesmos?", "settings.privacy.allowWipe": "Permitir eliminação de dados", "settings.privacy.allowWipeHelp": "Permitir aos subscritores eliminar todos os seus dados, incluindo as suas subscrições, da base de dados. Visualizações de campanhas e cliques em links também são removidos enquanto visualizações e contagem de clicks permanecem (sem nenhum subscritor associado) para que as estatísticas não sejam afetadas.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Tracking individual de subscritores", "settings.privacy.individualSubTrackingHelp": "Track visualizações e clicked ao nível do subscritor. Quando desligado, visualizações e track de clicks continuam, mas sem estarem associadas a nenhum subscritor.", "settings.privacy.listUnsubHeader": "Incluir header `List-Unsubscribe`", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Adicionar {num} subscritor(es) à lista de bloqueio?", "subscribers.confirmDelete": "Eliminar {num} subscritor(es)?", "subscribers.confirmExport": "Exportar {num} subscritor(es)?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Descarregar dados", "subscribers.email": "E-mail", "subscribers.emailExists": "E-mail já existe.", diff --git a/i18n/ru.json b/i18n/ru.json index 3d8880c8..c35d6bfa 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "Разрешить подписчикам экспортировать собранные на них данные?", "settings.privacy.allowWipe": "Разрешить удаление", "settings.privacy.allowWipeHelp": "Разрешить подписчикам удалять себя (включая их подписки и иные данные) из базы данных. Просмотры кампании и клики по ссылкам также удаляются, в то время как просмотры и счетчики кликов остаются (без привязанного к ним подписчика), так что это не влияет на статистику и аналитику.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Отслеживание каждого подписчика", "settings.privacy.individualSubTrackingHelp": "Отслеживать просмотры и клики на уровне каждого подписчика. Если отключено, просмотры и клики отслеживаются без привязки к конкретным подписчикам.", "settings.privacy.listUnsubHeader": "Включать заголовок `List-Unsubscribe`", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Заблокировать {num} подписчика(ов)?", "subscribers.confirmDelete": "Удалить {num} подписчика(ов)?", "subscribers.confirmExport": "Экспортировать {num} подписчика(ов)?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Загрузить данные", "subscribers.email": "E-mail", "subscribers.emailExists": "E-mail существует.", diff --git a/i18n/tr.json b/i18n/tr.json index 5865bc51..ce96387a 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -409,6 +409,8 @@ "settings.privacy.allowExportHelp": "Abonelerin üzerlerinde toplanan verileri dışa aktarmalarına izin verin?", "settings.privacy.allowWipe": "Silmek için izin ver", "settings.privacy.allowWipeHelp": "Abonelerin, abonelikleri ve veritabanındaki diğer tüm veriler dahil olmak üzere kendilerini silmesine izin verin. Kampanya görüntülemeleri ve bağlantı tıklamaları da, görünümler ve tıklama sayıları kalır (bunlarla ilişkilendirilmiş abone olmadan), böylece istatistikler ve analizler etkilenmez.", + "settings.privacy.domainBlocklist": "Domain blocklist", + "settings.privacy.domainBlocklistHelp": "E-mail addresses with these domains are disallowed from subscribing. Enter one domain per line, eg: somesite.com", "settings.privacy.individualSubTracking": "Bireysel üye takibi", "settings.privacy.individualSubTrackingHelp": "Abone düzeyinde kampanya görüntülemelerini ve tıklamalarını izleyin. Devre dışı bırakıldığında, bireysel abonelere bağlanmadan görüntüleme ve tıklama izleme devam eder.", "settings.privacy.listUnsubHeader": " `List-Unsubscribe` Başlık bilgisini ekle", @@ -434,6 +436,7 @@ "subscribers.confirmBlocklist": "Erişime engelli {num} üye(leri)?", "subscribers.confirmDelete": "Sil {num} üye(leri)?", "subscribers.confirmExport": "Dışa aktar {num} üye(leri)?", + "subscribers.domainBlocklisted": "The e-mail domain is blocklisted.", "subscribers.downloadData": "Veriyi indir", "subscribers.email": "E-posta", "subscribers.emailExists": "E-posta zaten mevcut.", diff --git a/internal/migrations/v2.0.0.go b/internal/migrations/v2.0.0.go index 4be646bc..ead60de2 100644 --- a/internal/migrations/v2.0.0.go +++ b/internal/migrations/v2.0.0.go @@ -39,6 +39,7 @@ func V2_0_0(db *sqlx.DB, fs stuffbin.FileSystem, ko *koanf.Koanf) error { if _, err := db.Exec(` INSERT INTO settings (key, value) VALUES ('app.send_optin_confirmation', 'true'), + ('privacy.domain_blocklist', '[]'), ('bounce.enabled', 'false'), ('bounce.webhooks_enabled', 'false'), ('bounce.count', '2'), diff --git a/internal/subimporter/importer.go b/internal/subimporter/importer.go index a016da58..76bff4f3 100644 --- a/internal/subimporter/importer.go +++ b/internal/subimporter/importer.go @@ -24,6 +24,7 @@ import ( "sync" "github.com/gofrs/uuid" + "github.com/knadh/listmonk/internal/i18n" "github.com/knadh/listmonk/models" "github.com/lib/pq" ) @@ -50,8 +51,9 @@ const ( // Importer represents the bulk CSV subscriber import system. type Importer struct { - opt Options - db *sql.DB + opt Options + db *sql.DB + i18n *i18n.I18n stop chan bool status Status @@ -64,6 +66,9 @@ type Options struct { BlocklistStmt *sql.Stmt UpdateListDateStmt *sql.Stmt NotifCB models.AdminNotifCallback + + // Lookup table for blocklisted domains. + DomainBlocklist map[string]bool } // Session represents a single import session. @@ -123,12 +128,13 @@ var ( ) // New returns a new instance of Importer. -func New(opt Options, db *sql.DB) *Importer { +func New(opt Options, db *sql.DB, i *i18n.I18n) *Importer { im := Importer{ opt: opt, - stop: make(chan bool, 1), db: db, + i18n: i, status: Status{Status: StatusNone, logBuf: bytes.NewBuffer(nil)}, + stop: make(chan bool, 1), } return &im } @@ -518,11 +524,12 @@ func (s *Session) LoadCSV(srcPath string, delim rune) error { } sub := SubReq{} - // Lowercase to ensure uniqueness in the DB. - sub.Email = strings.ToLower(strings.TrimSpace(row["email"])) + sub.Email = row["email"] sub.Name = row["name"] - if err := ValidateFields(sub); err != nil { - s.log.Printf("skipping line %d: %v", i, err) + + sub, err = s.im.ValidateFields(sub) + if err != nil { + s.log.Printf("skipping line %d: %s: %v", i, sub.Email, err) continue } @@ -564,6 +571,51 @@ func (im *Importer) Stop() { } } +// ValidateFields validates incoming subscriber field values and returns sanitized fields. +func (im *Importer) ValidateFields(s SubReq) (SubReq, error) { + if len(s.Email) > 1000 { + return s, errors.New(im.i18n.T("subscribers.invalidEmail")) + } + + s.Name = strings.TrimSpace(s.Name) + if len(s.Name) == 0 || len(s.Name) > stdInputMaxLen { + return s, errors.New(im.i18n.T("subscribers.invalidName")) + } + + em, err := im.SanitizeEmail(s.Email) + if err != nil { + return s, err + } + s.Email = em + + return s, nil +} + +// SanitizeEmail validates and sanitizes an e-mail string and returns the lowercased, +// e-mail component of an e-mail string. +func (im *Importer) SanitizeEmail(email string) (string, error) { + email = strings.ToLower(strings.TrimSpace(email)) + + // Since `mail.ParseAddress` parses an email address which can also contain optional name component + // here we check if incoming email string is same as the parsed email.Address. So this eliminates + // any valid email address with name and also valid address with empty name like ``. + em, err := mail.ParseAddress(email) + if err != nil || em.Address != email { + return "", errors.New(im.i18n.T("subscribers.invalidEmail")) + } + + // Check if the e-mail's domain is blocklisted. + d := strings.Split(em.Address, "@") + if len(d) == 2 { + _, ok := im.opt.DomainBlocklist[d[1]] + if ok { + return "", errors.New(im.i18n.T("subscribers.domainBlocklisted")) + } + } + + return em.Address, nil +} + // mapCSVHeaders takes a list of headers obtained from a CSV file, a map of known headers, // and returns a new map with each of the headers in the known map mapped by the position (0-n) // in the given CSV list. @@ -584,20 +636,6 @@ func (s *Session) mapCSVHeaders(csvHdrs []string, knownHdrs map[string]bool) map return hdrKeys } -// ValidateFields validates incoming subscriber field values. -func ValidateFields(s SubReq) error { - if len(s.Email) > 1000 { - return errors.New(`e-mail too long`) - } - if !IsEmail(s.Email) { - return errors.New(`invalid e-mail "` + s.Email + `"`) - } - if len(s.Name) == 0 || len(s.Name) > stdInputMaxLen { - return errors.New(`invalid or empty name "` + s.Name + `"`) - } - return nil -} - // countLines counts the number of line breaks in a file. This does not // distinguish between "blank" and non "blank" lines. // Credit: https://stackoverflow.com/a/24563853 @@ -621,14 +659,3 @@ func countLines(r io.Reader) (int, error) { } } } - -// IsEmail checks whether the given string is a valid e-mail address. -func IsEmail(email string) bool { - // Since `mail.ParseAddress` parses an email address which can also contain optional name component - // here we check if incoming email string is same as the parsed email.Address. So this eliminates - // any valid email address with name and also valid address with empty name like ``. - if em, err := mail.ParseAddress(email); err != nil || em.Address != email { - return false - } - return true -} diff --git a/schema.sql b/schema.sql index c3f6210e..0029e060 100644 --- a/schema.sql +++ b/schema.sql @@ -193,6 +193,7 @@ INSERT INTO settings (key, value) VALUES ('privacy.allow_export', 'true'), ('privacy.allow_wipe', 'true'), ('privacy.exportable', '["profile", "subscriptions", "campaign_views", "link_clicks"]'), + ('privacy.domain_blocklist', '[]'), ('upload.provider', '"filesystem"'), ('upload.filesystem.upload_path', '"uploads"'), ('upload.filesystem.upload_uri', '"/uploads"'),