diff --git a/cmd/bounce.go b/cmd/bounce.go index f32ec5091..ef638f75d 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")) + return b, 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.SubscriberUUID != "" && !reUUID.MatchString(b.SubscriberUUID) { + return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidUUID")) } - if b.Email != "" && !subimporter.IsEmail(b.Email) { - return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidEmail")) + 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.SubscriberUUID != "" && !reUUID.MatchString(b.SubscriberUUID) { - return echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidUUID")) + if b.Type != models.BounceTypeHard && b.Type != models.BounceTypeSoft { + return b, echo.NewHTTPError(http.StatusBadRequest, app.i18n.T("globals.messages.invalidData")) } - return nil + return b, nil } diff --git a/cmd/campaigns.go b/cmd/campaigns.go index 41576c555..a281c59c4 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 13d064489..226da9137 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 22e57f873..d3f9b4698 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 1d28dd31f..d7b5c81fe 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 db161f248..6f743c53a 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 000000000..9ee31d8ef --- /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 2dc8a6af0..3237ca40e 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 720d92c90..afd5653aa 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 00995ad48..d935689fe 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 25ebdf55e..41f552861 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 fce095d24..878e56769 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 95d03b941..5646a1a74 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 f7f7a307e..6afa62e9c 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 3d2add76d..252e6b0f8 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 231095979..600275ed6 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 01aaa5a84..8411e3eb4 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 665788565..f0508a9af 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 315fc9337..b1300443e 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 6cc713413..a0ab14e70 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 6bcb49d40..3df6f2e24 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 c6c360621..d3eb74f4b 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 c7e6633cb..f3b0fb4eb 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 3d8880c88..c35d6bfaa 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 5865bc510..ce96387a5 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 4be646bc9..ead60de2d 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 a016da58e..76bff4f37 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 c3f6210ea..0029e060a 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"'),