diff --git a/.circleci/config.yml b/.circleci/config.yml index 8a631b03addf..4dcbbb823475 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,7 @@ jobs: image: cockroachdb/cockroach:v19.2.0 command: start --insecure - - image: oryd/hydra:v1.2.2 + image: oryd/hydra:v1.4.10 environment: - DSN=memory - URLS_SELF_ISSUER=http://127.0.0.1:4444/ @@ -72,6 +72,55 @@ jobs: - run: test -z "$CIRCLE_PR_NUMBER" && goveralls -service=circle-ci -coverprofile=coverage.txt -repotoken=$COVERALLS_REPO_TOKEN || echo "forks are not allowed to push to coveralls" + test-e2e: + parameters: + flavor: + type: string + docker: + - image: oryd/e2e-env:latest + environment: + - GO111MODULE=on + - TEST_DATABASE_MYSQL=mysql://root:test@(localhost:3306)/mysql?parseTime=true&multiStatements=true + - TEST_DATABASE_COCKROACHDB=cockroach://root@localhost:26257/defaultdb?sslmode=disable + - TEST_DATABASE_POSTGRESQL=postgres://test:test@localhost:5432/kratos?sslmode=disable + - image: postgres:9.6 + environment: + - POSTGRES_USER=test + - POSTGRES_PASSWORD=test + - POSTGRES_DB=kratos + - image: cockroachdb/cockroach:v2.1.6 + command: start --insecure + - image: mysql:5.7 + environment: + - MYSQL_ROOT_PASSWORD=test + - image: oryd/mailslurper:latest-smtps + working_directory: /go/src/github.com/ory/kratos + steps: + - checkout + # core node_modules cache + - restore_cache: + keys: + - v1-deps-{{ checksum "package-lock.json" }} + - v1-deps + - run: npm ci + - save_cache: + key: v1-deps-{{ checksum "package-lock.json" }} + # cache NPM modules and the folder with the Cypress binary + paths: + - ~/.npm + - ~/.cache + # go modules cache + - restore_cache: + keys: + - go-mod-v1-{{ checksum "go.sum" }} + - run: go mod download + - save_cache: + key: go-mod-v1-{{ checksum "go.sum" }} + paths: + - "/go/pkg/mod" + - run: make deps + - run: ./test/e2e/run.sh << parameters.flavor >> + workflows: tbr: jobs: @@ -81,6 +130,30 @@ workflows: filters: tags: only: /.*/ + - test-e2e: + name: test-e2e-sqlite + flavor: sqlite + filters: + tags: + only: /.*/ + - test-e2e: + name: test-e2e-postgres + flavor: postgres + filters: + tags: + only: /.*/ + - test-e2e: + name: test-e2e-mysql + flavor: mysql + filters: + tags: + only: /.*/ + - test-e2e: + name: test-e2e-cockroach + flavor: cockroach + filters: + tags: + only: /.*/ - test: filters: @@ -90,6 +163,10 @@ workflows: appname: Ory_Kratos requires: - test + - test-e2e-sqlite + - test-e2e-postgres + - test-e2e-mysql + - test-e2e-cockroach - golangci/lint filters: tags: @@ -99,6 +176,10 @@ workflows: - docs/build: requires: - test + - test-e2e-sqlite + - test-e2e-postgres + - test-e2e-mysql + - test-e2e-cockroach - golangci/lint filters: tags: @@ -108,6 +189,10 @@ workflows: - sdk/release: requires: - test + - test-e2e-sqlite + - test-e2e-postgres + - test-e2e-mysql + - test-e2e-cockroach - golangci/lint - sdk/generate - goreleaser/release @@ -119,6 +204,10 @@ workflows: - changelog/generate: requires: - test + - test-e2e-sqlite + - test-e2e-postgres + - test-e2e-mysql + - test-e2e-cockroach - golangci/lint filters: tags: @@ -135,6 +224,10 @@ workflows: requires: - goreleaser/test - test + - test-e2e-sqlite + - test-e2e-postgres + - test-e2e-mysql + - test-e2e-cockroach - golangci/lint filters: branches: diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000000..a543c21279ba --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.go] +indent_size = 4 +indent_style = tab diff --git a/.gitignore b/.gitignore index a53d8e0b8f5f..68fcd972d259 100644 --- a/.gitignore +++ b/.gitignore @@ -8,4 +8,6 @@ packrd/ *-packr.go dist/ node_modules -.bin/ +.bin/* +cypress/videos +cypress/screenshots diff --git a/.schema/config.schema.json b/.schema/config.schema.json index 1a9969e8fdd7..7e6fa5334a37 100644 --- a/.schema/config.schema.json +++ b/.schema/config.schema.json @@ -31,13 +31,13 @@ "type": "boolean" } }, - "additionalItems": false, + "additionalProperties": false, "required": [ "default_redirect_url" ] } }, - "additionalItems": false, + "additionalProperties": false, "required": [ "hook", "config" @@ -50,7 +50,7 @@ "const": "revoke_active_sessions" } }, - "additionalItems": false, + "additionalProperties": false, "required": [ "hook" ] @@ -62,7 +62,7 @@ "const": "verify" } }, - "additionalItems": false, + "additionalProperties": false, "required": [ "hook" ] @@ -74,7 +74,7 @@ "const": "session" } }, - "additionalItems": false, + "additionalProperties": false, "required": [ "hook" ] @@ -147,7 +147,7 @@ } } }, - "additionalItems": false, + "additionalProperties": false, "required": [ "id", "provider", @@ -272,7 +272,7 @@ }, "selfServiceBefore": { "type": "object", - "additionalItems": false, + "additionalProperties": false, "properties": { "hooks": { "type": "array", @@ -296,18 +296,18 @@ "properties": { "selfservice": { "type": "object", - "additionalItems": false, + "additionalProperties": false, "required": [ "logout" ], "properties": { "strategies": { "type": "object", - "additionalItems": false, + "additionalProperties": false, "properties": { "password": { "type": "object", - "additionalItems": false, + "additionalProperties": false, "properties": { "enabled": { "type": "boolean" @@ -316,14 +316,14 @@ }, "oidc": { "type": "object", - "additionalItems": false, + "additionalProperties": false, "properties": { "enabled": { "type": "boolean" }, "config": { "type": "object", - "additionalItems": false, + "additionalProperties": false, "properties": { "providers": { "title": "OpenID Connect and OAuth2 Providers", @@ -351,14 +351,14 @@ ] } }, - "additionalItems": false, + "additionalProperties": false, "required": [ "redirect_to" ] }, "settings": { "type": "object", - "additionalItems": false, + "additionalProperties": false, "properties": { "request_lifespan": { "type": "string", @@ -434,7 +434,7 @@ "$ref": "#/definitions/selfServiceAfterLogin" } }, - "additionalItems": false + "additionalProperties": false }, "registration": { "type": "object", diff --git a/Makefile b/Makefile index 5777207fc690..48e998d9d663 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ K := $(foreach exec,$(EXECUTABLES),\ $(if $(shell which $(exec)),some string,$(error "No $(exec) in PATH"))) export GO111MODULE := on -export PATH := .bin:${PATH} +export PATH := $(pwd)/.bin:${PATH} deps: ifneq ("v0", $(shell cat .bin/.lock)) @@ -18,7 +18,8 @@ ifneq ("v0", $(shell cat .bin/.lock)) go build -o .bin/goimports golang.org/x/tools/cmd/goimports go build -o .bin/swagutil github.com/ory/sdk/swagutil go build -o .bin/packr2 github.com/gobuffalo/packr/v2/packr2 - npm i + go build -o .bin/yq github.com/mikefarah/yq + npm ci echo "v0" > .bin/.lock endif @@ -60,7 +61,7 @@ test-resetdb: .PHONY: test test: test-resetdb - source scripts/test-envs.sh && go test -tags sqlite -count=1 ./... + source script/test-envs.sh && go test -tags sqlite -count=1 ./... # Generates the SDKs .PHONY: sdk @@ -90,8 +91,17 @@ quickstart-dev: .PHONY: format format: deps goreturns -w -local github.com/ory $$(listx .) + npm run format # Runs tests in short mode, without database adapters .PHONY: docker docker: docker build -f .docker/Dockerfile-build -t oryd/kratos:latest . + +.PHONY: test-e2e +test-e2e: test-resetdb + source script/test-envs.sh + test/e2e/run.sh sqlite + test/e2e/run.sh postgres + test/e2e/run.sh cockroach + test/e2e/run.sh mysql diff --git a/README.md b/README.md index 3440eed997ee..481458d7a373 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@

-ORY Kratos is the first and only cloud native Identity and User Management System in the world. Finally, it is no longer necessary to implement a User Login process for the umpteenth time! +ORY Kratos is the first and only cloud native Identity and User Management System in the world. Finally, it is no longer necessary to implement a User Login process for the umpteenth time! @@ -327,6 +327,12 @@ It is possible to develop ORY Kratos on Windows, but please be aware that all gu When cloning ORY Kratos, run `make tools`. It will download several required dependencies. If you haven't run the command in a while it's probably a good idea to run it again. +#### Install from source + +```shell script +make install +``` + #### Formatting Code You can format all code using `make format`. Our CI checks if your code is properly formatted. @@ -383,6 +389,21 @@ go test -tags sqlite ./... cd client; go test -tags sqlite . ``` +##### End-to-End Tests + +We use [Cypress](https://www.cypress.io) to run our e2e tests. You can run all tests using: + +```shell script +make test-e2e +``` + +If you intend developing e2e tests, run the following command for more details: + +```shell script +./test/e2e/run.sh +``` + + #### Build Docker You can build a development Docker Image using: diff --git a/cmd/daemon/serve.go b/cmd/daemon/serve.go index 8a9b4f6252bf..f39d41f36c00 100644 --- a/cmd/daemon/serve.go +++ b/cmd/daemon/serve.go @@ -172,10 +172,11 @@ func sqa(cmd *cobra.Command, d driver.Driver) *metricsx.Service { func bgTasks(d driver.Driver, wg *sync.WaitGroup, cmd *cobra.Command, args []string) { defer wg.Done() + d.Logger().Println("Courier worker started.") if err := graceful.Graceful(d.Registry().Courier().Work, d.Registry().Courier().Shutdown); err != nil { d.Logger().WithError(err).Fatalf("Failed to run courier worker.") } - d.Logger().Println("courier worker was shutdown gracefully") + d.Logger().Println("Courier worker was shutdown gracefully.") } func ServeAll(d driver.Driver) func(cmd *cobra.Command, args []string) { diff --git a/cmd/tools.go b/cmd/tools.go index 208282c9e90f..908fe3c480bc 100644 --- a/cmd/tools.go +++ b/cmd/tools.go @@ -16,6 +16,8 @@ import ( _ "github.com/gobuffalo/packr/v2" _ "github.com/jteeuwen/go-bindata" + _ "github.com/mikefarah/yq" + _ "github.com/ory/sdk/swagutil" _ "github.com/davidrjonas/semver-cli" ) diff --git a/continuity/container.go b/continuity/container.go index 413a72e73899..c71720f1fd74 100644 --- a/continuity/container.go +++ b/continuity/container.go @@ -53,11 +53,11 @@ func NewContainer(name string, o managerOptions) *Container { func (c *Container) Valid(identity uuid.UUID) error { if c.ExpiresAt.Before(time.Now()) { - return errors.WithStack(herodot.ErrBadRequest.WithReasonf("You mast restart the flow because the resumable session has expired.")) + return errors.WithStack(herodot.ErrBadRequest.WithReasonf("You must restart the flow because the resumable session has expired.")) } if identity != uuid.Nil && x.DerefUUID(c.IdentityID) != identity { - return errors.WithStack(herodot.ErrBadRequest.WithReasonf("You mast restart the flow because the resumable session was initiated by another person.")) + return errors.WithStack(herodot.ErrBadRequest.WithReasonf("You must restart the flow because the resumable session was initiated by another person.")) } return nil diff --git a/continuity/manager_cookie.go b/continuity/manager_cookie.go index 5bb34cfba321..6636dd5695c7 100644 --- a/continuity/manager_cookie.go +++ b/continuity/manager_cookie.go @@ -17,9 +17,9 @@ import ( ) var _ Manager = new(ManagerCookie) -var ErrNotResumable = *herodot.ErrBadRequest.WithReasonf("No resumable session could be found in the HTTP Header.") +var ErrNotResumable = *herodot.ErrBadRequest.WithError("session is not resumable").WithReasonf("No resumable session could be found in the HTTP Header.") -const sessionKeyID = "id" +const cookieName = "ory_kratos_continuity" type ( managerCookieDependencies interface { @@ -47,8 +47,8 @@ func (m *ManagerCookie) Pause(ctx context.Context, w http.ResponseWriter, r *htt } c := NewContainer(name, *o) - if err := x.SessionPersistValues(w, r, m.d.CookieManager(), name, map[string]interface{}{ - sessionKeyID: c.ID.String(), + if err := x.SessionPersistValues(w, r, m.d.CookieManager(), cookieName, map[string]interface{}{ + name: c.ID.String(), }); err != nil { return err } @@ -72,7 +72,7 @@ func (m *ManagerCookie) Continue(ctx context.Context, r *http.Request, name stri } if err := container.Valid(o.iid); err != nil { - return nil, errors.WithStack(ErrNotResumable.WithDebugf("%+v", err)) + return nil, err } if o.payloadRaw != nil && container.Payload != nil { @@ -90,7 +90,7 @@ func (m *ManagerCookie) Continue(ctx context.Context, r *http.Request, name stri func (m *ManagerCookie) sid(ctx context.Context, r *http.Request, name string) (uuid.UUID, error) { var sid uuid.UUID - if s, err := x.SessionGetString(r, m.d.CookieManager(), name, sessionKeyID); err != nil { + if s, err := x.SessionGetString(r, m.d.CookieManager(), cookieName, name); err != nil { return sid, errors.WithStack(ErrNotResumable.WithDebugf("%+v", err)) } else if sid = x.ParseUUID(s); sid == uuid.Nil { return sid, errors.WithStack(ErrNotResumable.WithDebug("sid is not a valid uuid")) @@ -123,8 +123,8 @@ func (m ManagerCookie) Abort(ctx context.Context, w http.ResponseWriter, r *http return err } - if err := x.SessionPersistValues(w, r, m.d.CookieManager(), name, map[string]interface{}{ - sessionKeyID: "", + if err := x.SessionPersistValues(w, r, m.d.CookieManager(), cookieName, map[string]interface{}{ + name: "", }); err != nil { return err } diff --git a/continuity/manager_test.go b/continuity/manager_test.go index d18427e0f159..124a37b86b9a 100644 --- a/continuity/manager_test.go +++ b/continuity/manager_test.go @@ -79,12 +79,12 @@ func TestManager(t *testing.T) { }) router.DELETE("/:name", func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { - c, err := p.Continue(r.Context(), r, ps.ByName("name"), tc.ro...) + err := p.Abort(r.Context(), w, r, ps.ByName("name")) if err != nil { writer.WriteError(w, r, err) return } - writer.Write(w, r, c) + w.WriteHeader(http.StatusNoContent) }) ts := httptest.NewServer(router) @@ -190,6 +190,27 @@ func TestManager(t *testing.T) { assert.JSONEq(t, b.String(), gjson.GetBytes(body, "payload").Raw, "%s", body) assert.Contains(t, href, gjson.GetBytes(body, "name").String(), "%s", body) }) + + t.Run("case=pause, abort, and continue session with failure", func(t *testing.T) { + href := genid() + res, err := cl.Do(x.NewTestHTTPRequest(t, "PUT", href, nil)) + require.NoError(t, err) + require.NoError(t, res.Body.Close()) + require.Equal(t, http.StatusNoContent, res.StatusCode) + + res, err = cl.Do(x.NewTestHTTPRequest(t, "DELETE", href, nil)) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) + require.Equal(t, http.StatusNoContent, res.StatusCode) + + res, err = cl.Do(x.NewTestHTTPRequest(t, "GET", href, nil)) + require.NoError(t, err) + t.Cleanup(func() { require.NoError(t, res.Body.Close()) }) + + require.Equal(t, http.StatusBadRequest, res.StatusCode) + body := x.MustReadAll(res.Body) + assert.Contains(t, gjson.GetBytes(body, "error.reason").String(), "resumable session") + }) }) } }) diff --git a/contrib/quickstart/kratos/email-password/sqlite/db.sqlite b/contrib/quickstart/kratos/email-password/sqlite/db.sqlite deleted file mode 100644 index 97d952585f32..000000000000 Binary files a/contrib/quickstart/kratos/email-password/sqlite/db.sqlite and /dev/null differ diff --git a/courier/courier.go b/courier/courier.go index b909501a424f..f93d0c4b2441 100644 --- a/courier/courier.go +++ b/courier/courier.go @@ -10,9 +10,8 @@ import ( "github.com/cenkalti/backoff" "github.com/gofrs/uuid" "github.com/pkg/errors" - "gopkg.in/gomail.v2" - "github.com/ory/x/errorsx" + gomail "github.com/ory/mail/v3" "github.com/ory/kratos/driver/configuration" "github.com/ory/kratos/x" @@ -24,7 +23,7 @@ type ( x.LoggingProvider } Courier struct { - dialer *gomail.Dialer + Dialer *gomail.Dialer d smtpDependencies c configuration.Provider // graceful shutdown handling @@ -38,23 +37,34 @@ type ( func NewSMTP(d smtpDependencies, c configuration.Provider) *Courier { uri := c.CourierSMTPURL() - sslSkipVerify, _ := strconv.ParseBool(uri.Query().Get("skip_ssl_verify")) password, _ := uri.User.Password() port, _ := strconv.ParseInt(uri.Port(), 10, 64) ctx, cancel := context.WithCancel(context.Background()) + + var ssl bool + var tlsConfig *tls.Config + if uri.Scheme == "smtps" { + ssl = true + sslSkipVerify, _ := strconv.ParseBool(uri.Query().Get("skip_ssl_verify")) + // #nosec G402 This is ok (and required!) because it is configurable and disabled by default. + tlsConfig = &tls.Config{InsecureSkipVerify: sslSkipVerify} + } + return &Courier{ d: d, c: c, ctx: ctx, shutdown: cancel, - dialer: &gomail.Dialer{ - Host: uri.Hostname(), - Port: int(port), - Username: uri.User.Username(), - Password: password, - SSL: uri.Scheme == "smtps", - /* #nosec we need to support SMTP servers wihout TLS */ - TLSConfig: &tls.Config{InsecureSkipVerify: sslSkipVerify}, + Dialer: &gomail.Dialer{ + /* #nosec we need to support SMTP servers without TLS */ + TLSConfig: tlsConfig, + Host: uri.Hostname(), + Port: int(port), + Username: uri.User.Username(), + Password: password, + SSL: ssl, + Timeout: time.Second * 10, + RetryFailure: true, }, } } @@ -96,7 +106,7 @@ func (m *Courier) Work() error { select { case <-m.ctx.Done(): - if m.ctx.Err() == context.Canceled { + if errors.Is(m.ctx.Err(), context.Canceled) { return nil } return m.ctx.Err() @@ -115,12 +125,11 @@ func (m *Courier) watchMessages(ctx context.Context, errChan chan error) { if err := backoff.Retry(func() error { messages, err := m.d.CourierPersister().NextMessages(ctx, 10) if err != nil { - if errorsx.Cause(err) == ErrQueueEmpty { + if errors.Is(err, ErrQueueEmpty) { return nil } return err } - for k := range messages { var msg = messages[k] @@ -134,11 +143,11 @@ func (m *Courier) watchMessages(ctx context.Context, errChan chan error) { gm.SetBody("text/plain", msg.Body) gm.AddAlternative("text/html", msg.Body) - if err := m.dialer.DialAndSend(gm); err != nil { + if err := m.Dialer.DialAndSend(ctx, gm); err != nil { m.d.Logger(). WithError(err). - WithField("smtp_server", fmt.Sprintf("%s:%d", m.dialer.Host, m.dialer.Port)). - WithField("smtp_ssl_enabled", m.dialer.SSL). + WithField("smtp_server", fmt.Sprintf("%s:%d", m.Dialer.Host, m.Dialer.Port)). + WithField("smtp_ssl_enabled", m.Dialer.SSL). // WithField("email_to", msg.Recipient). WithField("message_from", from). Error("Unable to send email using SMTP connection.") diff --git a/cypress.json b/cypress.json new file mode 100644 index 000000000000..c5691a9ac2b7 --- /dev/null +++ b/cypress.json @@ -0,0 +1,3 @@ +{ + "chromeWebSecurity": false +} diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 000000000000..da18d9352a17 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} \ No newline at end of file diff --git a/cypress/helpers/index.js b/cypress/helpers/index.js new file mode 100644 index 000000000000..97565386c19e --- /dev/null +++ b/cypress/helpers/index.js @@ -0,0 +1,53 @@ +const email = () => + Math.random().toString(36).substring(7) + + '@' + + Math.random().toString(36).substring(7) +const password = () => Math.random().toString(36) + +const assertAddress = ({ isVerified, email }) => ({ identity }) => { + expect(identity).to.have.property('addresses') + expect(identity.addresses).to.have.length(1) + + const address = identity.addresses[0] + expect(address.id).to.not.be.empty + expect(address.verified).to.equal(isVerified) + expect(address.value).to.equal(email) + + if (isVerified) { + expect(address.verified_at).to.not.be.null + } else { + expect(address.verified_at).to.be.null + } +} + +const parseHtml = (html) => new DOMParser().parseFromString(html, 'text/html') + +module.exports = { + APP_URL: (Cypress.env('app_url') || 'http://127.0.0.1:4455').replace( + /\/$/, + '' + ), + MAIL_API: (Cypress.env('mail_url') || 'http://127.0.0.1:4437').replace( + /\/$/, + '' + ), + website: 'https://www.ory.sh/', + parseHtml, + gen: { + email, + password, + identity: () => ({ email: email(), password: password() }), + }, + assertAddress, + + // Format is + // http://127.0.0.1:4455/.ory/kratos/public/self-service/browser/flows/verification/email/confirm/OdTRmdMKe0DfF6ScaOFYgWJwoAprTxnA + verifyHrefPattern: /^http:.*\/.ory\/kratos\/public\/self-service\/browser\/flows\/verification\/email\/confirm\/([a-zA-Z0-9]+)$/, + + // intervals define how long to wait for something, + pollInterval: 100, // how long to wait before retry + + // Adding 1+ second on top because MySQL doesn't do millisecs. + verifyLifespan: 5000 + 1050, + privilegedLifespan: 5000 + 1050, +} diff --git a/cypress/integration/profiles/email/login/error.spec.js b/cypress/integration/profiles/email/login/error.spec.js new file mode 100644 index 000000000000..3db05de39de3 --- /dev/null +++ b/cypress/integration/profiles/email/login/error.spec.js @@ -0,0 +1,53 @@ +import { APP_URL, gen } from '../../../../helpers' + +context('Login', () => { + beforeEach(() => { + cy.visit(APP_URL + '/auth/login') + }) + + it('fails when CSRF cookies are missing', () => { + cy.clearCookies() + + cy.get('input[name="identifier"]').type('i-do-not-exist') + cy.get('input[name="password"]').type('invalid-password') + + cy.get('button[type="submit"]').click() + + // FIXME https://github.com/ory/kratos/issues/91 + cy.get('html').should('contain.text', 'CSRF token is missing or invalid') + }) + + describe('shows validation errors when invalid signup data is used', () => { + it('should show an error when the identifier is missing', () => { + cy.get('button[type="submit"]').click() + cy.get('.form-errors .message').should( + 'contain.text', + 'missing properties: identifier' + ) + }) + + it('should show an error when the password is missing', () => { + const identity = gen.email() + cy.get('input[name="identifier"]') + .type(identity) + .should('have.value', identity) + + cy.get('button[type="submit"]').click() + cy.get('.form-errors .message').should( + 'contain.text', + 'missing properties: password' + ) + }) + + it('should show fail to sign in', () => { + cy.get('input[name="identifier"]').type('i-do-not-exist') + cy.get('input[name="password"]').type('invalid-password') + + cy.get('button[type="submit"]').click() + cy.get('.form-errors .message').should( + 'contain.text', + 'credentials are invalid' + ) + }) + }) +}) diff --git a/cypress/integration/profiles/email/login/success.spec.js b/cypress/integration/profiles/email/login/success.spec.js new file mode 100644 index 000000000000..186371cfedd1 --- /dev/null +++ b/cypress/integration/profiles/email/login/success.spec.js @@ -0,0 +1,32 @@ +import { APP_URL, gen, password, website } from '../../../../helpers' + +context('Login', () => { + const email = gen.email() + const password = gen.password() + + before(() => { + cy.register({ email, password, fields: { 'traits.website': website } }) + }) + + beforeEach(() => { + cy.clearCookies() + cy.visit(APP_URL + '/auth/login') + }) + + it('should sign up and be logged in', () => { + cy.get('input[name="identifier"]').type(email) + cy.get('input[name="password"]').type(password) + cy.get('button[type="submit"]').click() + + cy.session().should((session) => { + const { identity } = session + expect(identity.id).to.not.be.empty + expect(identity.traits_schema_id).to.equal('default') + expect(identity.traits_schema_url).to.equal( + `${APP_URL}/.ory/kratos/public/schemas/default` + ) + expect(identity.traits.website).to.equal(website) + expect(identity.traits.email).to.equal(email) + }) + }) +}) diff --git a/cypress/integration/profiles/email/login/ui.spec.js b/cypress/integration/profiles/email/login/ui.spec.js new file mode 100644 index 000000000000..5acaae14d1e2 --- /dev/null +++ b/cypress/integration/profiles/email/login/ui.spec.js @@ -0,0 +1,15 @@ +import { APP_URL } from '../../../../helpers' + +context('Login', () => { + beforeEach(() => { + cy.visit(APP_URL + '/auth/login') + }) + + describe('use ui elements', () => { + it('clicks the log in link', () => { + cy.get('a[href*="auth/registration"]').click() + cy.location('pathname').should('include', 'auth/registration') + cy.location('search').should('not.be.empty', 'request') + }) + }) +}) diff --git a/cypress/integration/profiles/email/logout/success.spec.js b/cypress/integration/profiles/email/logout/success.spec.js new file mode 100644 index 000000000000..ff6dab50863c --- /dev/null +++ b/cypress/integration/profiles/email/logout/success.spec.js @@ -0,0 +1,22 @@ +import { APP_URL, gen, password, website } from '../../../../helpers' + +context('Login', () => { + const email = gen.email() + const password = gen.password() + + before(() => { + cy.register({ email, password, fields: { 'traits.website': website } }) + }) + + beforeEach(() => { + cy.visit(APP_URL + '/') + }) + + it('should sign out and be able to sign in again', () => { + cy.get('a[href*="logout"]').click() + + cy.noSession() + + cy.url().should('include', '/auth/login') + }) +}) diff --git a/cypress/integration/profiles/email/registration/errors.spec.js b/cypress/integration/profiles/email/registration/errors.spec.js new file mode 100644 index 000000000000..89aeb44530d0 --- /dev/null +++ b/cypress/integration/profiles/email/registration/errors.spec.js @@ -0,0 +1,104 @@ +import { APP_URL, gen } from '../../../../helpers' + +context('Registration', () => { + beforeEach(() => { + cy.visit(APP_URL + '/auth/registration') + }) + + const identity = gen.email() + const password = gen.password() + + it('fails when CSRF cookies are missing', () => { + cy.clearCookies() + + cy.get('input[name="traits.website"]').type('https://www.ory.sh') + cy.get('input[name="traits.email"]') + .type(identity) + .should('have.value', identity) + cy.get('input[name="password"]') + .type('123456') + .should('have.value', '123456') + + cy.get('button[type="submit"]').click() + + // FIXME https://github.com/ory/kratos/issues/91 + cy.get('html').should('contain.text', 'CSRF token is missing or invalid') + }) + + describe('show errors when invalid signup data is used', () => { + it('should show an error when the password has leaked before', () => { + cy.get('input[name="traits.website"]').type('https://www.ory.sh') + cy.get('input[name="traits.email"]') + .type(identity) + .should('have.value', identity) + cy.get('input[name="password"]') + .type('123456') + .should('have.value', '123456') + + cy.get('button[type="submit"]').click() + cy.get('.form-errors .message').should('contain.text', 'data breaches') + }) + + it('should show an error when the password is to similar', () => { + cy.get('input[name="traits.website"]').type('https://www.ory.sh') + cy.get('input[name="traits.email"]').type(identity) + cy.get('input[name="password"]').type(identity) + + cy.get('button[type="submit"]').click() + cy.get('.form-errors .message').should('contain.text', 'to similar') + }) + + it('should show an error when the password is empty', () => { + cy.get('input[name="traits.website"]').type('https://www.ory.sh') + cy.get('input[name="traits.email"]').type(identity) + + cy.get('button[type="submit"]').click() + cy.get('.form-errors .message').should('contain.text', 'missing') + }) + + it('should show an error when the email is empty', () => { + cy.get('input[name="traits.website"]').type('https://www.ory.sh') + cy.get('input[name="password"]').type(password) + + cy.get('button[type="submit"]').click() + cy.get('.form-errors .message').should('contain.text', 'valid "email"') + }) + + it('should show an error when the email is not an email', () => { + cy.get('input[name="traits.website"]').type('https://www.ory.sh') + cy.get('input[name="password"]').type('not-an-email') + + cy.get('button[type="submit"]').click() + cy.get('.form-errors .message').should('contain.text', 'valid "email"') + }) + + it('should show a missing indicator if no fields are set', () => { + cy.get('button[type="submit"]').click() + cy.get('.form-errors .message').should( + 'contain.text', + 'missing properties' + ) + }) + + it('should show an error when the website is not a valid URI', () => { + cy.get('input[name="traits.website"]') + .type('1234') + .then(($input) => { + expect($input[0].validationMessage).to.contain('URL') + }) + }) + + it('should show an error when the website is too short', () => { + cy.get('input[name="traits.website"]').type('http://s') + + // fixme https://github.com/ory/kratos/issues/368 + cy.get('input[name="password"]').type(password) + + cy.get('button[type="submit"]').click() + cy.get('.form-errors .message').should( + 'contain.text', + 'length must be >= 10' + ) + }) + }) +}) diff --git a/cypress/integration/profiles/email/registration/success.spec.js b/cypress/integration/profiles/email/registration/success.spec.js new file mode 100644 index 000000000000..74430d7dd0e6 --- /dev/null +++ b/cypress/integration/profiles/email/registration/success.spec.js @@ -0,0 +1,32 @@ +import { APP_URL, gen } from '../../../../helpers' + +context('Registration', () => { + beforeEach(() => { + cy.visit(APP_URL + '/auth/registration') + }) + + it('should sign up and be logged in', () => { + const email = gen.email() + const password = gen.password() + const website = 'https://www.ory.sh/' + cy.get('input[name="traits.email"]').type(email) + cy.get('input[name="traits.website').type(website) + cy.get('input[name="password"]').type(password) + + cy.get('button[type="submit"]').click() + cy.get('pre').should('contain.text', email) + cy.get('.greeting').should('contain.text', 'Welcome back') + + cy.session().should((session) => { + const { identity } = session + expect(identity.id).to.not.be.empty + expect(identity.addresses).to.be.undefined + expect(identity.traits_schema_id).to.equal('default') + expect(identity.traits_schema_url).to.equal( + `${APP_URL}/.ory/kratos/public/schemas/default` + ) + expect(identity.traits.website).to.equal(website) + expect(identity.traits.email).to.equal(email) + }) + }) +}) diff --git a/cypress/integration/profiles/email/registration/ui.spec.js b/cypress/integration/profiles/email/registration/ui.spec.js new file mode 100644 index 000000000000..a7559dd295cf --- /dev/null +++ b/cypress/integration/profiles/email/registration/ui.spec.js @@ -0,0 +1,24 @@ +import { APP_URL } from '../../../../helpers' + +context('Registration', () => { + beforeEach(() => { + cy.visit(APP_URL + '/auth/registration') + }) + + describe('use ui elements', () => { + it('clicks the visibility toggle to show the password', () => { + cy.get('input[name="password"]').type('some password') + cy.get('input[name="password"]').should('have.prop', 'type', 'password') + cy.get('.password-visibility-toggle').click() + cy.get('input[name="password"]').should('have.prop', 'type', 'text') + cy.get('.password-visibility-toggle').click() + cy.get('input[name="password"]').should('have.prop', 'type', 'password') + }) + + it('clicks the log in link', () => { + cy.get('a[href*="auth/login"]').click() + cy.location('pathname').should('include', 'auth/login') + cy.location('search').should('not.be.empty', 'request') + }) + }) +}) diff --git a/cypress/integration/profiles/email/settings/errors.spec.js b/cypress/integration/profiles/email/settings/errors.spec.js new file mode 100644 index 000000000000..4afb8ee69ef4 --- /dev/null +++ b/cypress/integration/profiles/email/settings/errors.spec.js @@ -0,0 +1,198 @@ +// TODO: implement wrong credentials when reauthing +// TODO: implement other account when reauthing +import { + APP_URL, + gen, + password, + privilegedLifespan, + website, +} from '../../../../helpers' + +context('Settings', () => { + let email = gen.email() + let password = gen.password() + + const emailSecond = `second-${gen.email()}` + const passwordSecond = gen.password() + + const up = (value) => `not-${value}` + const down = (value) => value.replace(/not-/, '') + + before(() => { + cy.register({ + email: emailSecond, + password: passwordSecond, + fields: { 'traits.website': 'https://github.com/ory/kratos' }, + }) + cy.clearCookies() + cy.register({ email, password, fields: { 'traits.website': website } }) + }) + + beforeEach(() => { + cy.clearCookies() + cy.login({ email, password }) + cy.visit(APP_URL + '/settings') + }) + + it('fails when CSRF cookies are missing', () => { + cy.clearCookies() + + cy.get('#user-profile button[type="submit"]').click() + + // FIXME https://github.com/ory/kratos/issues/91 + cy.get('html').should('contain.text', 'CSRF token is missing or invalid') + }) + + describe('profile', () => { + it('fails with validation errors', () => { + cy.get('#user-profile input[name="traits.website"]') + .clear() + .type('http://s') + cy.get('#user-profile button[type="submit"]').click() + cy.get('#user-profile .form-errors .message').should( + 'contain.text', + 'length must be >= 10' + ) + }) + + it('fails because reauth is another person', () => { + cy.get('#user-profile input[name="traits.email"]').clear().type(up(email)) + cy.waitForPrivilegedSessionToExpire() // wait for the privileged session to time out + cy.get('#user-profile button[type="submit"]').click() + + cy.reauth({ + expect: { email }, + type: { email: emailSecond, password: passwordSecond }, + }) + + cy.get('pre code').should( + 'contain.text', + 'You must restart the flow because the resumable session was initiated by another person.' + ) + + // Try to log in with updated credentials -> should fail + cy.clearCookies() + cy.login({ email: up(email), password, expectSession: false }) + }) + + it('does not update data because resumable session was removed', () => { + cy.get('#user-profile input[name="traits.email"]').clear().type(up(email)) + cy.waitForPrivilegedSessionToExpire() // wait for the privileged session to time out + cy.get('#user-profile button[type="submit"]').click() + + cy.clearCookies() + cy.login({ email, password }) + + cy.session().should((session) => { + const { identity } = session + expect(identity.traits.email).to.equal(email) + }) + }) + + it('does not update without re-auth', () => { + cy.get('#user-profile input[name="traits.email"]').clear().type(up(email)) + cy.waitForPrivilegedSessionToExpire() // wait for the privileged session to time out + cy.get('#user-profile button[type="submit"]').click() + + cy.visit(APP_URL + '/') + + cy.session().should((session) => { + const { identity } = session + expect(identity.traits.email).to.equal(email) + }) + }) + + it('does not resume another failed request', () => { + // checks here that we're checking settingsRequest.id == cookie.stored.id + cy.get('#user-profile input[name="traits.email"]').clear().type(up(email)) + cy.waitForPrivilegedSessionToExpire() // wait for the privileged session to time out + cy.get('#user-profile button[type="submit"]').click() + + cy.visit(APP_URL + '/settings') + cy.get('#user-profile input[name="traits.website"]') + .clear() + .type('http://github.com/aeneasr') + cy.get('#user-profile button[type="submit"]').click() + + cy.session().should((session) => { + const { identity } = session + expect(identity.traits.email).to.equal(email) // this is NOT up(email) + expect(identity.traits.website).to.equal('http://github.com/aeneasr') // this is NOT up(email) + }) + }) + }) + + describe('password', () => { + it('fails if password policy is violated', () => { + cy.get('#user-password input[name="password"]').clear().type('123456') + cy.get('#user-password button[type="submit"]').click() + cy.get('#user-password .form-errors .message').should( + 'contain.text', + 'data breaches' + ) + }) + + it('fails because reauth is another person', () => { + cy.get('#user-password input[name="password"]').clear().type(up(password)) + cy.waitForPrivilegedSessionToExpire() // wait for the privileged session to time out + cy.get('#user-password button[type="submit"]').click() + + cy.reauth({ + expect: { email }, + type: { email: emailSecond, password: passwordSecond }, + }) + + cy.get('pre code').should( + 'contain.text', + 'You must restart the flow because the resumable session was initiated by another person.' + ) + + // Try to log in with updated credentials -> should fail + cy.clearCookies() + cy.login({ email, password: up(password), expectSession: false }) + }) + + it('does not update without re-auth', () => { + cy.get('#user-password input[name="password"]').clear().type(up(password)) + cy.waitForPrivilegedSessionToExpire() // wait for the privileged session to time out + cy.get('#user-password button[type="submit"]').click() + + cy.visit(APP_URL + '/') + cy.clearCookies() + cy.login({ email, password: up(password), expectSession: false }) + }) + + it('does not update data because resumable session was removed', () => { + cy.get('#user-password input[name="password"]').clear().type(up(password)) + cy.waitForPrivilegedSessionToExpire() // wait for the privileged session to time out + cy.get('#user-password button[type="submit"]').click() + + cy.clearCookies() + cy.login({ email, password }) + cy.clearCookies() + cy.login({ email, password: up(password), expectSession: false }) + }) + + it('does not resume another queued request', () => { + // checks here that we're checking settingsRequest.id == cookie.stored.id + cy.get('#user-password input[name="password"]') + .clear() + .type(up(up(password))) + cy.waitForPrivilegedSessionToExpire() // wait for the privileged session to time out + cy.get('#user-password button[type="submit"]').click() + + password = up(password) + cy.visit(APP_URL + '/settings') + cy.get('#user-password input[name="password"]').clear().type(password) + cy.get('#user-password button[type="submit"]').click() + + cy.reauth({ expect: { email }, type: { password: down(password) } }) + + cy.clearCookies() + cy.login({ email, password }) + + cy.clearCookies() + cy.login({ email, password: up(password), expectSession: false }) + }) + }) +}) diff --git a/cypress/integration/profiles/email/settings/success.spec.js b/cypress/integration/profiles/email/settings/success.spec.js new file mode 100644 index 000000000000..1652d274389a --- /dev/null +++ b/cypress/integration/profiles/email/settings/success.spec.js @@ -0,0 +1,134 @@ +import { + APP_URL, + gen, + password, + privilegedLifespan, + website, +} from '../../../../helpers' + +context('Settings', () => { + let email = gen.email() + let password = gen.password() + + const up = (value) => `not-${value}` + const down = (value) => value.replace(/not-/, '') + + before(() => { + cy.register({ email, password, fields: { 'traits.website': website } }) + }) + + beforeEach(() => { + cy.clearCookies() + cy.login({ email, password }) + cy.visit(APP_URL + '/settings') + }) + + it('shows all settings forms', () => { + cy.get('#user-profile h3').should('contain.text', 'Profile') + cy.get('#user-profile input[name="traits.email"]').should( + 'contain.value', + email + ) + cy.get('#user-profile input[name="traits.website"]').should( + 'contain.value', + website + ) + + cy.get('#user-password h3').should('contain.text', 'Password') + cy.get('#user-password input[name="password"]').should('be.empty') + }) + + describe('password', () => { + it('modifies the password with privileged session', () => { + password = up(password) + cy.get('#user-password input[name="password"]').clear().type(password) + cy.get('#user-password button[type="submit"]').click() + cy.get('.container').should( + 'contain.text', + 'Your changes have been saved!' + ) + cy.get('#user-password input[name="password"]').should('be.empty') + }) + + it('is unable to log in with the old password', () => { + cy.clearCookies() + cy.login({ + email: email, + password: down(password), + expectSession: false, + }) + }) + + it('modifies the password with an unprivileged session', () => { + password = up(password) + cy.get('#user-password input[name="password"]').clear().type(password) + cy.waitForPrivilegedSessionToExpire() // wait for the privileged session to time out + cy.get('#user-password button[type="submit"]').click() + + cy.reauth({ expect: { email }, type: { password: down(password) } }) + + cy.url().should('include', '/settings') + cy.get('.container').should( + 'contain.text', + 'Your changes have been saved!' + ) + cy.get('#user-password input[name="password"]').should('be.empty') + }) + }) + + describe('profile', () => { + it('modifies an unprotected trait', () => { + cy.get('#user-profile input[name="traits.website"]') + .clear() + .type('https://github.com/ory') + cy.get('#user-profile button[type="submit"]').click() + cy.get('.container').should( + 'contain.text', + 'Your changes have been saved!' + ) + cy.get('#user-profile input[name="traits.website"]').should( + 'contain.value', + 'https://github.com/ory' + ) + }) + + it('modifies a protected trait with privileged session', () => { + email = up(email) + cy.get('#user-profile input[name="traits.email"]').clear().type(email) + cy.get('#user-profile button[type="submit"]').click() + cy.get('.container').should( + 'contain.text', + 'Your changes have been saved!' + ) + cy.get('#user-profile input[name="traits.email"]').should( + 'contain.value', + email + ) + }) + + it('is unable to log in with the old email', () => { + cy.clearCookies() + cy.visit(APP_URL + '/auth/login') + cy.login({ email: down(email), password, expectSession: false }) + }) + + it('modifies a protected trait with unprivileged session', () => { + email = up(email) + cy.get('#user-profile input[name="traits.email"]').clear().type(email) + cy.waitForPrivilegedSessionToExpire() // wait for the privileged session to time out + cy.get('#user-profile button[type="submit"]').click() + + cy.reauth({ expect: { email: down(email) }, type: { password } }) + + cy.url().should('include', '/settings') + cy.get('.container').should( + 'contain.text', + 'Your changes have been saved!' + ) + cy.get('#user-profile input[name="traits.email"]').should( + 'contain.value', + email + ) + }) + }) +}) diff --git a/cypress/integration/profiles/email/settings/ui.spec.js b/cypress/integration/profiles/email/settings/ui.spec.js new file mode 100644 index 000000000000..efb6ee00b4cf --- /dev/null +++ b/cypress/integration/profiles/email/settings/ui.spec.js @@ -0,0 +1,13 @@ +context('Settings', () => { + beforeEach(() => { + cy.register({ fields: { 'traits.website': 'https://www.ory.sh/' } }) + }) + + describe('use ui elements', () => { + it('clicks the settings link', () => { + cy.get('a[href*="settings"]').click() + cy.location('pathname').should('include', 'settings') + cy.location('search').should('not.be.empty', 'request') + }) + }) +}) diff --git a/cypress/integration/profiles/verify/registration/errors.spec.js b/cypress/integration/profiles/verify/registration/errors.spec.js new file mode 100644 index 000000000000..7836cd74ef95 --- /dev/null +++ b/cypress/integration/profiles/verify/registration/errors.spec.js @@ -0,0 +1,47 @@ +import { + APP_URL, + assertAddress, + gen, + parseHtml, + verifyHrefPattern, +} from '../../../../helpers' + +context('Registration', () => { + describe('error flow', () => { + let identity + beforeEach(() => { + cy.visit(APP_URL + '/auth/registration') + cy.deleteMail() + + identity = gen.identity() + cy.register(identity) + cy.login(identity) + }) + + it('is unable to verify the email address if the code is no longer valid and resend the code', () => { + cy.verifyEmailButExpired({ expect: { email: identity.email } }) + + cy.get('input[name="to_verify"]').should('be.empty') + cy.get('input[name="to_verify"]').type(identity.email) + cy.get('button[type="submit"]').click() + + cy.location('pathname').should('eq', '/') + + cy.verifyEmail({ expect: { email: identity.email } }) + }) + + it('is unable to verify the email address if the code is incorrect', () => { + cy.getMail().then((mail) => { + const link = parseHtml(mail.body).querySelector('a') + + expect(verifyHrefPattern.test(link.href)).to.be.true + + cy.visit(link.href + '-not') // add random stuff to the confirm challenge + cy.log(link.href) + cy.session().then( + assertAddress({ isVerified: false, email: identity.email }) + ) + }) + }) + }) +}) diff --git a/cypress/integration/profiles/verify/registration/success.spec.js b/cypress/integration/profiles/verify/registration/success.spec.js new file mode 100644 index 000000000000..d037a49780db --- /dev/null +++ b/cypress/integration/profiles/verify/registration/success.spec.js @@ -0,0 +1,32 @@ +import { APP_URL, assertAddress, gen } from '../../../../helpers' + +context('Registration', () => { + describe('successful flow', () => { + beforeEach(() => { + cy.visit(APP_URL + '/auth/registration') + cy.deleteMail() + }) + + afterEach(() => { + cy.deleteMail() + }) + + const up = (value) => `up-${value}` + const { email, password } = gen.identity() + it('is able to verify the email address after sign up', () => { + cy.register({ email, password }) + cy.login({ email, password }) + cy.session().then(assertAddress({ isVerified: false, email })) + + cy.verifyEmail({ expect: { email } }) + }) + + xit('sends the warning email on double sign up', () => { + // FIXME https://github.com/ory/kratos/issues/133 + cy.clearCookies() + cy.register({ email, password: up(password) }) + + cy.verifyEmail({ expect: { email } }) + }) + }) +}) diff --git a/cypress/integration/profiles/verify/settings/error.spec.js b/cypress/integration/profiles/verify/settings/error.spec.js new file mode 100644 index 000000000000..dcfdb74a0523 --- /dev/null +++ b/cypress/integration/profiles/verify/settings/error.spec.js @@ -0,0 +1,52 @@ +import { + APP_URL, + assertAddress, + gen, + parseHtml, + verifyHrefPattern, +} from '../../../../helpers' + +context('Settings', () => { + describe('error flow', () => { + let identity + before(() => { + cy.deleteMail() + }) + + beforeEach(() => { + identity = gen.identity() + cy.register(identity) + cy.deleteMail({ atLeast: 1 }) // clean up registration email + + cy.login(identity) + cy.visit(APP_URL + '/settings') + }) + + it('is unable to verify the email address if the code is no longer valid', () => { + const email = `not-${identity.email}` + cy.get('#user-profile input[name="traits.email"]').clear().type(email) + cy.get('#user-profile button[type="submit"]').click() + cy.verifyEmailButExpired({ expect: { email } }) + }) + + it('is unable to verify the email address if the code is incorrect', () => { + const email = `not-${identity.email}` + cy.get('#user-profile input[name="traits.email"]').clear().type(email) + cy.get('#user-profile button[type="submit"]').click() + + cy.getMail().then((mail) => { + const link = parseHtml(mail.body).querySelector('a') + + expect(verifyHrefPattern.test(link.href)).to.be.true + + cy.visit(link.href + '-not') // add random stuff to the confirm challenge + cy.log(link.href) + cy.session().then(assertAddress({ isVerified: false, email })) + }) + }) + + xit('should not update the traits until the email has been verified and the old email has accepted the change', () => { + // FIXME https://github.com/ory/kratos/issues/292 + }) + }) +}) diff --git a/cypress/integration/profiles/verify/settings/success.spec.js b/cypress/integration/profiles/verify/settings/success.spec.js new file mode 100644 index 000000000000..d820dfdc21d6 --- /dev/null +++ b/cypress/integration/profiles/verify/settings/success.spec.js @@ -0,0 +1,43 @@ +import { APP_URL, assertAddress, gen } from '../../../../helpers' + +context('Settings', () => { + describe('successful flow', () => { + let identity + + before(() => { + cy.deleteMail() + }) + + beforeEach(() => { + identity = gen.identity() + cy.register(identity) + cy.deleteMail({ atLeast: 1 }) // clean up registration email + + cy.login(identity) + cy.visit(APP_URL + '/settings') + }) + + it('should update the verify address and request a verification email', () => { + const email = `not-${identity.email}` + cy.get('#user-profile input[name="traits.email"]').clear().type(email) + cy.get('#user-profile button[type="submit"]').click() + cy.get('.container').should( + 'contain.text', + 'Your changes have been saved!' + ) + cy.get('#user-profile input[name="traits.email"]').should( + 'contain.value', + email + ) + cy.session().then(assertAddress({ isVerified: false, email })) + + cy.verifyEmail({ expect: { email } }) + + cy.location('pathname').should('eq', '/') + }) + + xit('should should be able to allow or deny (and revert?) the address change', () => { + // FIXME https://github.com/ory/kratos/issues/292 + }) + }) +}) diff --git a/cypress/integration/profiles/verify/verify/errors.spec.js b/cypress/integration/profiles/verify/verify/errors.spec.js new file mode 100644 index 000000000000..e3b9b894a7dc --- /dev/null +++ b/cypress/integration/profiles/verify/verify/errors.spec.js @@ -0,0 +1,53 @@ +import { + APP_URL, + assertAddress, + gen, + parseHtml, + verifyHrefPattern, +} from '../../../../helpers' + +context('Verify', () => { + describe('error flow', () => { + let identity + before(() => { + cy.deleteMail() + }) + + beforeEach(() => { + identity = gen.identity() + cy.register(identity) + cy.deleteMail({ atLeast: 1 }) // clean up registration email + + cy.login(identity) + cy.visit(APP_URL + '/verify') + }) + + it('is unable to verify the email address if the code is expired', () => { + cy.get('input[name="to_verify"]').type(identity.email) + cy.get('button[type="submit"]').click() + + cy.location('pathname').should('eq', '/') + + cy.verifyEmailButExpired({ expect: { email: identity.email } }) + }) + + it('is unable to verify the email address if the code is incorrect', () => { + cy.get('input[name="to_verify"]').type(identity.email) + cy.get('button[type="submit"]').click() + + cy.location('pathname').should('eq', '/') + + cy.getMail().then((mail) => { + const link = parseHtml(mail.body).querySelector('a') + + expect(verifyHrefPattern.test(link.href)).to.be.true + + cy.visit(link.href + '-not') // add random stuff to the confirm challenge + cy.log(link.href) + cy.session().then( + assertAddress({ isVerified: false, email: identity.email }) + ) + }) + }) + }) +}) diff --git a/cypress/integration/profiles/verify/verify/success.spec.js b/cypress/integration/profiles/verify/verify/success.spec.js new file mode 100644 index 000000000000..f84869f6703a --- /dev/null +++ b/cypress/integration/profiles/verify/verify/success.spec.js @@ -0,0 +1,52 @@ +import { APP_URL, assertAddress, gen } from '../../../../helpers' + +context('Verify', () => { + describe('successful flow', () => { + let identity + + before(() => { + cy.deleteMail() + }) + + beforeEach(() => { + identity = gen.identity() + cy.register(identity) + cy.deleteMail({ atLeast: 1 }) // clean up registration email + + cy.login(identity) + cy.visit(APP_URL + '/verify') + }) + + it('should request verification and receive an email and verify it', () => { + cy.get('input[name="to_verify"]').type(identity.email) + cy.get('button[type="submit"]').click() + + cy.location('pathname').should('eq', '/') + + cy.verifyEmail({ expect: { email: identity.email } }) + + cy.location('pathname').should('eq', '/') + }) + + it('should request verification for an email that does not exist yet', () => { + const email = `not-${identity.email}` + cy.get('input[name="to_verify"]').type(email) + cy.get('button[type="submit"]').click() + + cy.getMail().should((message) => { + expect(message.subject.trim()).to.equal( + 'Someone tried to verify this email address' + ) + expect(message.fromAddress.trim()).to.equal('no-reply@ory.kratos.sh') + expect(message.toAddresses).to.have.length(1) + expect(message.toAddresses[0].trim()).to.equal(email) + }) + + cy.session().then( + assertAddress({ isVerified: false, email: identity.email }) + ) + + cy.location('pathname').should('eq', '/') + }) + }) +}) diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js new file mode 100644 index 000000000000..aa9918d21530 --- /dev/null +++ b/cypress/plugins/index.js @@ -0,0 +1,21 @@ +/// +// *********************************************************** +// This example plugins/index.js can be used to load plugins +// +// You can change the location of this file or turn off loading +// the plugins file with the 'pluginsFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/plugins-guide +// *********************************************************** + +// This function is called when a project is opened or re-opened (e.g. due to +// the project's config changing) + +/** + * @type {Cypress.PluginConfig} + */ +module.exports = (on, config) => { + // `on` is used to hook into various events Cypress emits + // `config` is the resolved Cypress config +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 000000000000..67458ff9c497 --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,224 @@ +// *********************************************** +// This example commands.js shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add("login", (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) +import { + APP_URL, + assertAddress, + gen, + MAIL_API, + parseHtml, + pollInterval, + privilegedLifespan, +} from '../helpers' + +Cypress.Commands.add( + 'register', + ({ email = gen.email(), password = gen.password(), fields = {} } = {}) => { + cy.visit(APP_URL + '/auth/registration') + cy.get('input[name="traits.email"]').type(email) + cy.get('input[name="password"]').type(password) + Object.keys(fields).forEach((key) => { + const value = fields[key] + cy.get(`input[name="${key}"]`).clear().type(value) + }) + cy.get('button[type="submit"]').click() + } +) + +Cypress.Commands.add('login', ({ email, password, expectSession = true }) => { + cy.visit(APP_URL + '/auth/login') + cy.get('input[name="identifier"]').clear().type(email) + cy.get('input[name="password"]').clear().type(password) + cy.get('button[type="submit"]').click() + if (expectSession) { + cy.session() + } else { + cy.noSession() + } +}) + +Cypress.Commands.add( + 'reauth', + ({ + expect: { email }, + type: { email: temail, password: tpassword } = {}, + }) => { + cy.url().should('include', '/auth/login') + cy.get('input[name="identifier"]').should('have.value', email) + if (temail) { + cy.get('input[name="identifier"]').clear().type(temail) + } + if (tpassword) { + cy.get('input[name="password"]').clear().type(tpassword) + } + cy.get('button[type="submit"]').click() + } +) + +Cypress.Commands.add('deleteMail', ({ atLeast = 0 } = {}) => { + let tries = 0 + let count = 0 + const req = () => + cy + .request('DELETE', `${MAIL_API}/mail`, { pruneCode: 'all' }) + .then(({ body }) => { + count += parseInt(body) + if (count < atLeast && tries < 100) { + cy.log( + `Expected at least ${atLeast} messages but deleteted only ${count} so far (body: ${body})` + ) + tries++ + cy.wait(pollInterval) + return req() + } + + return Promise.resolve() + }) + + return req() +}) + +Cypress.Commands.add('session', () => + cy + .request('GET', `${APP_URL}/.ory/kratos/public/sessions/whoami`) + .then((response) => { + expect(response.body.sid).to.not.be.empty + expect( + Cypress.moment().isBefore(Cypress.moment(response.body.expires_at)) + ).to.be.true + + // Add a grace second for MySQL which does not support millisecs. + expect( + Cypress.moment().isAfter( + Cypress.moment(response.body.issued_at).subtract(1, 's') + ) + ).to.be.true + expect( + Cypress.moment().isAfter( + Cypress.moment(response.body.authenticated_at).subtract(1, 's') + ) + ).to.be.true + expect(response.body.identity).to.exist + return response.body + }) +) + +Cypress.Commands.add('noSession', () => + cy + .request({ + method: 'GET', + url: `${APP_URL}/.ory/kratos/public/sessions/whoami`, + failOnStatusCode: false, + }) + .then((request) => { + expect(request.status).to.eq(401) + return request + }) +) + +Cypress.Commands.add('verifyEmail', ({ expect: { email } = {} } = {}) => + cy.getMail().then((message) => { + expect(message.subject.trim()).to.equal('Please verify your email address') + expect(message.fromAddress.trim()).to.equal('no-reply@ory.kratos.sh') + expect(message.toAddresses).to.have.length(1) + expect(message.toAddresses[0].trim()).to.equal(email) + + const link = parseHtml(message.body).querySelector('a') + expect(link).to.not.be.null + expect(link.href).to.contain(APP_URL) + + cy.visit(link.href) + cy.location('pathname').should('not.contain', 'verify') + cy.session().should(assertAddress({ isVerified: true, email })) + }) +) + +// Uses the verification email but waits so that it expires +Cypress.Commands.add( + 'verifyEmailButExpired', + ({ expect: { email } = {} } = {}) => + cy.getMail().then((message) => { + expect(message.subject.trim()).to.equal( + 'Please verify your email address' + ) + expect(message.fromAddress.trim()).to.equal('no-reply@ory.kratos.sh') + expect(message.toAddresses).to.have.length(1) + expect(message.toAddresses[0].trim()).to.equal(email) + + const link = parseHtml(message.body).querySelector('a') + cy.session().should((session) => { + assertAddress({ isVerified: false, email: email })(session) + cy.wait( + Cypress.moment(session.identity.addresses[0].expires_at).diff( + Cypress.moment() + ) + 100 + ) + }) + + cy.visit(link.href) + cy.location('pathname').should('include', 'verify') + cy.location('search').should('not.be.empty', 'request') + cy.get('.form-errors .message').should('contain.text', 'code has expired') + + cy.session().should(assertAddress({ isVerified: false, email: email })) + }) +) + +// Uses the verification email but waits so that it expires +Cypress.Commands.add('waitForPrivilegedSessionToExpire', () => { + cy.session().should((session) => { + expect(session.authenticated_at).to.not.be.empty + cy.wait( + Cypress.moment(session.authenticated_at) + .add(privilegedLifespan) + .diff(Cypress.moment()) + 100 + ) + }) +}) + +Cypress.Commands.add('getMail', ({ removeMail = true } = {}) => { + let tries = 0 + const req = () => + cy.request(`${MAIL_API}/mail`).then((response) => { + expect(response.body).to.have.property('mailItems') + const count = response.body.mailItems.length + if (count === 0 && tries < 100) { + tries++ + cy.wait(pollInterval) + return req() + } + + expect(count).to.equal(1) + if (removeMail) { + return cy + .deleteMail({ atLeast: count }) + .then(() => Promise.resolve(response.body.mailItems[0])) + } + + return Promise.resolve(response.body.mailItems[0]) + }) + + return req() +}) diff --git a/cypress/support/index.js b/cypress/support/index.js new file mode 100644 index 000000000000..d68db96df269 --- /dev/null +++ b/cypress/support/index.js @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/index.js is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json new file mode 100644 index 000000000000..b4871a1ab2cc --- /dev/null +++ b/cypress/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "strict": true, + "baseUrl": "../node_modules", + "target": "es5", + "lib": ["es5", "dom"], + "types": ["cypress"] + }, + "include": [ + "**/*.ts" + ] +} diff --git a/go.mod b/go.mod index 3038e01c1ee3..951789f79151 100644 --- a/go.mod +++ b/go.mod @@ -40,8 +40,10 @@ require ( github.com/leodido/go-urn v1.1.0 // indirect github.com/markbates/pkger v0.12.8 github.com/mattn/goveralls v0.0.5 + github.com/mikefarah/yq v1.15.0 github.com/mitchellh/go-homedir v1.1.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 + github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 // indirect github.com/ory/analytics-go/v4 v4.0.0 github.com/ory/dockertest v3.3.5+incompatible github.com/ory/go-acc v0.1.0 @@ -49,7 +51,8 @@ require ( github.com/ory/graceful v0.1.1 github.com/ory/herodot v0.8.2 github.com/ory/jsonschema/v3 v3.0.1 - github.com/ory/sdk/swagutil v0.0.0-20200430101046-db494fac5eb6 + github.com/ory/mail/v3 v3.0.0 + github.com/ory/sdk/swagutil v0.0.0-20200425113349-92ce176f501e github.com/ory/viper v1.7.4 github.com/ory/x v0.0.116 github.com/phayes/freeport v0.0.0-20180830031419-95f893ade6f2 @@ -67,5 +70,4 @@ require ( golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d golang.org/x/tools v0.0.0-20200331202046-9d5940d49312 gopkg.in/go-playground/validator.v9 v9.28.0 - gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df ) diff --git a/go.sum b/go.sum index 8dda51496493..b75d933ea0ed 100644 --- a/go.sum +++ b/go.sum @@ -827,6 +827,8 @@ github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5 github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s= github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mikefarah/yq v1.15.0 h1:ViMYNRG5UB7hzm8olxMFqPtkpMXXKO4g32/v9JUa62o= +github.com/mikefarah/yq v1.15.0/go.mod h1:Klb/IuhiBF3HOsJXH3YR+xURVe3KFmm0jxwiklmdjP4= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= @@ -863,6 +865,8 @@ github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa github.com/onsi/gomega v1.6.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7 h1:lDH9UUVJtmYCjyT0CI4q8xvlXPxeZ0gYCVvWbmPlp88= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI= @@ -897,8 +901,12 @@ github.com/ory/herodot v0.8.2 h1:Lq5DpT81tkcegzp1QFqVFlcDWNCcq9xIq5FQ191rI0E= github.com/ory/herodot v0.8.2/go.mod h1:kFWnruHnnokHH4e7tbkGyHOjHGj70sJTrdiz01Xcq4Y= github.com/ory/jsonschema/v3 v3.0.1 h1:xzV7w2rt/Qn+jvh71joIXNKKOCqqNyTlaIxdxU0IQJc= github.com/ory/jsonschema/v3 v3.0.1/go.mod h1:jgLHekkFk0uiGdEWGleC+tOm6JSSP8cbf17PnBuGXlw= -github.com/ory/sdk/swagutil v0.0.0-20200430101046-db494fac5eb6 h1:oKOQcB9DZy7govO5k12s9Ofwm4d4yZiMLmeMZE6QWZY= -github.com/ory/sdk/swagutil v0.0.0-20200430101046-db494fac5eb6/go.mod h1:Ufg1eAyz+Zt3+oweSZVThG13ewewWCKwBmoNmK8Z0co= +github.com/ory/mail v2.3.1+incompatible h1:vHntHDHtQXamt2T+iwTTlCoBkDvILUeujE9Ocwe9md4= +github.com/ory/mail v2.3.1+incompatible/go.mod h1:87D9/1gB6ewElQoN0lXJ0ayfqcj3cW3qCTXh+5E9mfU= +github.com/ory/mail/v3 v3.0.0 h1:8LFMRj473vGahFD/ntiotWEd4S80FKYFtiZTDfOQ+sM= +github.com/ory/mail/v3 v3.0.0/go.mod h1:JGAVeZF8YAlxbaFDUHqRZAKBCSeW2w1vuxf28hFbZAw= +github.com/ory/sdk/swagutil v0.0.0-20200425113349-92ce176f501e h1:X2MrG0850tnYPHyHNOCugvqcs0nfnqfmXHo3HrUFPdc= +github.com/ory/sdk/swagutil v0.0.0-20200425113349-92ce176f501e/go.mod h1:Ufg1eAyz+Zt3+oweSZVThG13ewewWCKwBmoNmK8Z0co= github.com/ory/viper v1.5.6/go.mod h1:TYmpFpKLxjQwvT4f0QPpkOn4sDXU1kDgAwJpgLYiQ28= github.com/ory/viper v1.7.4 h1:3RWBt7Pq9kSFNxLaRT0ljNdbtaWisCQG1cLPn2Yd4UY= github.com/ory/viper v1.7.4/go.mod h1:T6sodNZKNGPpashUOk7EtXz2isovz8oCd57GNVkkNmE= @@ -1431,6 +1439,7 @@ gopkg.in/ini.v1 v1.54.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.55.0 h1:E8yzL5unfpW3M6fz/eB7Cb5MQAYSZ7GKo4Qth+N2sgQ= gopkg.in/ini.v1 v1.55.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= +gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/square/go-jose.v2 v2.1.9/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= gopkg.in/square/go-jose.v2 v2.2.2 h1:orlkJ3myw8CN1nVQHBFfloD+L3egixIa4FvUP6RosSA= diff --git a/package-lock.json b/package-lock.json index 9f3356d6bf72..3bed804c263e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,6 +6,7 @@ "version": "7.8.3", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz", "integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==", + "dev": true, "requires": { "@babel/highlight": "^7.8.3" } @@ -13,12 +14,14 @@ "@babel/helper-validator-identifier": { "version": "7.9.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz", - "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==" + "integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==", + "dev": true }, "@babel/highlight": { "version": "7.9.0", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz", "integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==", + "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.9.0", "chalk": "^2.0.0", @@ -29,6 +32,7 @@ "version": "7.9.6", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.9.6.tgz", "integrity": "sha512-64AF1xY3OAkFHqOb9s4jpgk1Mm5vDZ4L3acHvAml+53nO1XbXLuDodsVpO4OIUsmemlUHMxNdYMNJmsvOwLrvQ==", + "dev": true, "requires": { "regenerator-runtime": "^0.13.4" } @@ -115,6 +119,55 @@ } } }, + "@hapi/address": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@hapi/address/-/address-4.0.1.tgz", + "integrity": "sha512-0oEP5UiyV4f3d6cBL8F3Z5S7iWSX39Knnl0lY8i+6gfmmIBj44JCBNtcMgwyS+5v7j3VYavNay0NFHDS+UGQcw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, + "@hapi/formula": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/formula/-/formula-2.0.0.tgz", + "integrity": "sha512-V87P8fv7PI0LH7LiVi8Lkf3x+KCO7pQozXRssAHNXXL9L1K+uyu4XypLXwxqVDKgyQai6qj3/KteNlrqDx4W5A==", + "dev": true + }, + "@hapi/hoek": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.0.4.tgz", + "integrity": "sha512-EwaJS7RjoXUZ2cXXKZZxZqieGtc7RbvQhUy8FwDoMQtxWVi14tFjeFCYPZAM1mBCpOpiBpyaZbb9NeHc7eGKgw==", + "dev": true + }, + "@hapi/joi": { + "version": "17.1.1", + "resolved": "https://registry.npmjs.org/@hapi/joi/-/joi-17.1.1.tgz", + "integrity": "sha512-p4DKeZAoeZW4g3u7ZeRo+vCDuSDgSvtsB/NpfjXEHTUjSeINAi/RrVOWiVQ1isaoLzMvFEhe8n5065mQq1AdQg==", + "dev": true, + "requires": { + "@hapi/address": "^4.0.1", + "@hapi/formula": "^2.0.0", + "@hapi/hoek": "^9.0.0", + "@hapi/pinpoint": "^2.0.0", + "@hapi/topo": "^5.0.0" + } + }, + "@hapi/pinpoint": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@hapi/pinpoint/-/pinpoint-2.0.0.tgz", + "integrity": "sha512-vzXR5MY7n4XeIvLpfl3HtE3coZYO4raKXW766R6DZw/6aLqR26iuZ109K7a0NtF2Db0jxqh7xz2AxkUwpUFybw==", + "dev": true + }, + "@hapi/topo": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz", + "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==", + "dev": true, + "requires": { + "@hapi/hoek": "^9.0.0" + } + }, "@samverschueren/stream-to-observable": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.0.tgz", @@ -139,7 +192,8 @@ "@types/caseless": { "version": "0.12.2", "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", - "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==", + "dev": true }, "@types/chai": { "version": "4.2.7", @@ -160,7 +214,8 @@ "@types/color-name": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", - "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==" + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true }, "@types/jquery": { "version": "3.3.31", @@ -192,17 +247,20 @@ "@types/node": { "version": "13.13.4", "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.4.tgz", - "integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==" + "integrity": "sha512-x26ur3dSXgv5AwKS0lNfbjpCakGIduWU1DU91Zz58ONRWrIKGunmZBNv4P7N+e27sJkiGDsw/3fT4AtsqQBrBA==", + "dev": true }, "@types/nunjucks": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/@types/nunjucks/-/nunjucks-3.1.3.tgz", - "integrity": "sha512-42IiIIBdoB7ZDwCVhCWYT4fMCj+4TeacuVgh7xyT2du5EhkpA+OFeeDdYTFCUt1MrHb8Aw7ZqFvr8s1bwP9l8w==" + "integrity": "sha512-42IiIIBdoB7ZDwCVhCWYT4fMCj+4TeacuVgh7xyT2du5EhkpA+OFeeDdYTFCUt1MrHb8Aw7ZqFvr8s1bwP9l8w==", + "dev": true }, "@types/request": { "version": "2.48.4", "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.4.tgz", "integrity": "sha512-W1t1MTKYR8PxICH+A4HgEIPuAC3sbljoEVfyZbeFJJDbr30guDspJri2XOaM2E+Un7ZjrihaDi7cf6fPa2tbgw==", + "dev": true, "requires": { "@types/caseless": "*", "@types/node": "*", @@ -214,6 +272,7 @@ "version": "2.5.1", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -247,17 +306,20 @@ "@types/tough-cookie": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", - "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==" + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==", + "dev": true }, "a-sync-waterfall": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", - "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==" + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "dev": true }, "ajv": { "version": "6.12.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.0.tgz", "integrity": "sha512-D6gFiFA0RRLyUbvijN74DWAjXSFxWKaWP7mldxkVhyhAV3+SWA9HEJPHQ2c9soIeTFJqcSdFDGFgdqs1iUU2Hw==", + "dev": true, "requires": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", @@ -293,6 +355,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, "optional": true, "requires": { "normalize-path": "^3.0.0", @@ -308,12 +371,14 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=", + "dev": true }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dev": true, "requires": { "safer-buffer": "~2.1.0" } @@ -321,7 +386,8 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "dev": true }, "async": { "version": "3.2.0", @@ -332,17 +398,20 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", + "dev": true }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "dev": true }, "aws4": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz", - "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==" + "integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==", + "dev": true }, "balanced-match": { "version": "1.0.0", @@ -354,6 +423,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dev": true, "requires": { "tweetnacl": "^0.14.3" } @@ -362,6 +432,7 @@ "version": "0.6.7", "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-0.6.7.tgz", "integrity": "sha512-PYgt/sCzR4aGpyNy5+ViSQ77ognMnWq7745zM+/flYO4/Yisdtp9wDQW2IKCyVYPUxQt3E/b5GBSwfhd1LPdlg==", + "dev": true, "requires": { "@babel/code-frame": "^7.0.0", "@babel/runtime": "^7.0.0", @@ -376,6 +447,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true, "optional": true }, "bluebird": { @@ -398,6 +470,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, "optional": true, "requires": { "fill-range": "^7.0.1" @@ -424,22 +497,26 @@ "call-me-maybe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=" + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "dev": true }, "camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", + "dev": true }, "chalk": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -450,6 +527,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -458,6 +536,7 @@ "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -474,6 +553,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.0.tgz", "integrity": "sha512-aXAaho2VJtisB/1fg1+3nlLJqGOuewTzQpd/Tz0yTg2R0e4IGtshYvtjowyEumcBv2z+y4+kc75Mz7j5xJskcQ==", + "dev": true, "optional": true, "requires": { "anymatch": "~3.1.1", @@ -548,6 +628,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "dev": true, "requires": { "string-width": "^4.2.0", "strip-ansi": "^6.0.0", @@ -557,12 +638,14 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, "ansi-styles": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, "requires": { "@types/color-name": "^1.1.1", "color-convert": "^2.0.1" @@ -572,6 +655,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "requires": { "color-name": "~1.1.4" } @@ -579,17 +663,20 @@ "color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -600,6 +687,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -608,6 +696,7 @@ "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, "requires": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", @@ -619,22 +708,26 @@ "co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true }, "code-error-fragment": { "version": "0.0.230", "resolved": "https://registry.npmjs.org/code-error-fragment/-/code-error-fragment-0.0.230.tgz", - "integrity": "sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==" + "integrity": "sha512-cadkfKp6932H8UkhzE/gcUqhRMNf8jHzkAN7+5Myabswaghu4xABTgPHDCjW+dBAJxj/SpkTYokpzDqY4pCzQw==", + "dev": true }, "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true }, "color-convert": { "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -642,12 +735,14 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true }, "colorful": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/colorful/-/colorful-2.1.0.tgz", - "integrity": "sha1-aovcvC2kKlDbO0iC+iUmOqofLY4=" + "integrity": "sha1-aovcvC2kKlDbO0iC+iUmOqofLY4=", + "dev": true }, "colors": { "version": "1.4.0", @@ -660,6 +755,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, "requires": { "delayed-stream": "~1.0.0" } @@ -697,17 +793,20 @@ "core-js": { "version": "3.6.5", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.5.tgz", - "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==" + "integrity": "sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==", + "dev": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "cross-spawn": { "version": "6.0.5", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "dev": true, "requires": { "nice-try": "^1.0.4", "path-key": "^2.0.1", @@ -774,6 +873,7 @@ "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -788,6 +888,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, "requires": { "ms": "^2.1.1" } @@ -795,17 +896,20 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "dev": true }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dev": true, "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -820,12 +924,14 @@ "emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true }, "end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "dev": true, "requires": { "once": "^1.4.0" } @@ -833,12 +939,14 @@ "es6-promise": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.3.1.tgz", - "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=" + "integrity": "sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=", + "dev": true }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true }, "eventemitter2": { "version": "4.1.2", @@ -850,6 +958,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "dev": true, "requires": { "cross-spawn": "^6.0.0", "get-stream": "^4.0.0", @@ -878,7 +987,8 @@ "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true }, "extract-zip": { "version": "1.7.0", @@ -912,22 +1022,26 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "dev": true }, "fast-deep-equal": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", - "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==", + "dev": true }, "fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true }, "fast-safe-stringify": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz", - "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==" + "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==", + "dev": true }, "fd-slicer": { "version": "1.1.0", @@ -952,6 +1066,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, "optional": true, "requires": { "to-regex-range": "^5.0.1" @@ -961,6 +1076,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, "requires": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -969,12 +1085,14 @@ "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "dev": true }, "form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dev": true, "requires": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", @@ -1002,17 +1120,20 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", + "dev": true, "optional": true }, "get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true }, "get-stream": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "dev": true, "requires": { "pump": "^3.0.0" } @@ -1030,6 +1151,7 @@ "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dev": true, "requires": { "assert-plus": "^1.0.0" } @@ -1052,6 +1174,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "dev": true, "optional": true, "requires": { "is-glob": "^4.0.1" @@ -1075,17 +1198,20 @@ "grapheme-splitter": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", - "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "dev": true }, "har-validator": { "version": "5.1.3", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "dev": true, "requires": { "ajv": "^6.5.5", "har-schema": "^2.0.0" @@ -1103,12 +1229,14 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -1118,7 +1246,8 @@ "http2-client": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.3.tgz", - "integrity": "sha512-nUxLymWQ9pzkzTmir24p2RtsgruLmhje7lH3hLX1IpwvyTg77fW+1brenPPP3USAR+rQ36p5sTA/x7sjCJVkAA==" + "integrity": "sha512-nUxLymWQ9pzkzTmir24p2RtsgruLmhje7lH3hLX1IpwvyTg77fW+1brenPPP3USAR+rQ36p5sTA/x7sjCJVkAA==", + "dev": true }, "indent-string": { "version": "3.2.0", @@ -1151,12 +1280,14 @@ "invert-kv": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz", - "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==" + "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==", + "dev": true }, "is-binary-path": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, "optional": true, "requires": { "binary-extensions": "^2.0.0" @@ -1175,17 +1306,20 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true }, "is-glob": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, "optional": true, "requires": { "is-extglob": "^2.1.1" @@ -1205,6 +1339,7 @@ "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, "optional": true }, "is-observable": { @@ -1234,12 +1369,14 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "dev": true }, "isarray": { "version": "1.0.0", @@ -1250,42 +1387,50 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "dev": true }, "js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", + "dev": true }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", + "dev": true }, "json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", + "dev": true }, "json-to-ast": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/json-to-ast/-/json-to-ast-2.1.0.tgz", "integrity": "sha512-W9Lq347r8tA1DfMvAGn9QNcgYm4Wm7Yc+k8e6vezpMnRT+NHbtlxgNBXRVjXe9YM6eTn6+p/MKOlV/aABJcSnQ==", + "dev": true, "requires": { "code-error-fragment": "0.0.230", "grapheme-splitter": "^1.0.4" @@ -1303,12 +1448,14 @@ "jsonpointer": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=" + "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", + "dev": true }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "dev": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -1326,6 +1473,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz", "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==", + "dev": true, "requires": { "invert-kv": "^2.0.0" } @@ -1333,7 +1481,8 @@ "leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==" + "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", + "dev": true }, "listr": { "version": "0.14.3", @@ -1459,6 +1608,7 @@ "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, "requires": { "p-locate": "^4.1.0" } @@ -1529,6 +1679,7 @@ "version": "0.1.3", "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "dev": true, "requires": { "p-defer": "^1.0.0" } @@ -1537,6 +1688,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz", "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==", + "dev": true, "requires": { "map-age-cleaner": "^0.1.1", "mimic-fn": "^2.0.0", @@ -1546,19 +1698,22 @@ "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true } } }, "mime-db": { "version": "1.43.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", - "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "dev": true }, "mime-types": { "version": "2.1.26", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "dev": true, "requires": { "mime-db": "1.43.0" } @@ -1602,17 +1757,20 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", + "dev": true }, "node-fetch-h2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/node-fetch-h2/-/node-fetch-h2-2.3.0.tgz", "integrity": "sha512-ofRW94Ab0T4AOh5Fk8t0h8OBWrmjb0SSB20xh1H8YnPV9EJ+f5AMoYSUQ2zgJ4Iq2HAK0I2l5/Nequ8YzFS3Hg==", + "dev": true, "requires": { "http2-client": "^1.2.5" } @@ -1621,6 +1779,7 @@ "version": "0.2.0", "resolved": "https://registry.npmjs.org/node-readfiles/-/node-readfiles-0.2.0.tgz", "integrity": "sha1-271K8SE04uY1wkXvk//Pb2BnOl0=", + "dev": true, "requires": { "es6-promise": "^3.2.1" } @@ -1629,12 +1788,14 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, "optional": true }, "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, "requires": { "path-key": "^2.0.0" } @@ -1642,12 +1803,14 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, "nunjucks": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.1.tgz", "integrity": "sha512-LYlVuC1ZNSalQQkLNNPvcgPt2M9FTY9bs39mTCuFXtqh7jWbYzhDlmz2M6onPiXEhdZo+b9anRhc+uBGuJZ2bQ==", + "dev": true, "requires": { "a-sync-waterfall": "^1.0.0", "asap": "^2.0.3", @@ -1658,7 +1821,8 @@ "commander": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" + "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==", + "dev": true } } }, @@ -1666,6 +1830,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", "integrity": "sha512-pJTS2+T0oGIwgjGpw7sIRU8RQMcUoKCDWFLdBqKB2BNmGpbBMH2sdqAaOXUg8OzonZHU0L7vfJu1mJFEiYDWOQ==", + "dev": true, "requires": { "fast-safe-stringify": "^2.0.7" } @@ -1674,6 +1839,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/oas-linter/-/oas-linter-3.1.2.tgz", "integrity": "sha512-mv3HBG9aQz8PLGvonewIN9Y2Ra8QL6jvotRvf7NCdZ20n5vg4dO4y61UZh6s+KRDfJaU1PO+9Oxrn3EUN4Xygw==", + "dev": true, "requires": { "should": "^13.2.1", "yaml": "^1.8.3" @@ -1683,6 +1849,7 @@ "version": "2.3.2", "resolved": "https://registry.npmjs.org/oas-resolver/-/oas-resolver-2.3.2.tgz", "integrity": "sha512-toGCUv8wyZZmUAAsw4jn+511xNpUFW2ZLp4sAZ7xpERIeosrbxBxtkVxot9kXvdUHtPjRafi5+bkJ56TwQeYSQ==", + "dev": true, "requires": { "node-fetch-h2": "^2.3.0", "oas-kit-common": "^1.0.8", @@ -1694,17 +1861,20 @@ "ansi-regex": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "dev": true }, "is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true }, "string-width": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", + "dev": true, "requires": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", @@ -1715,6 +1885,7 @@ "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "dev": true, "requires": { "ansi-regex": "^5.0.0" } @@ -1723,6 +1894,7 @@ "version": "15.3.1", "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.3.1.tgz", "integrity": "sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==", + "dev": true, "requires": { "cliui": "^6.0.0", "decamelize": "^1.2.0", @@ -1742,12 +1914,14 @@ "oas-schema-walker": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/oas-schema-walker/-/oas-schema-walker-1.1.4.tgz", - "integrity": "sha512-foVDDS0RJYMfhQEDh/WdBuCzydTcsCnGo9EeD8SpWq1uW10JXiz+8SfYVDA7LO87kjmlnTRZle/2gr5qxabaEA==" + "integrity": "sha512-foVDDS0RJYMfhQEDh/WdBuCzydTcsCnGo9EeD8SpWq1uW10JXiz+8SfYVDA7LO87kjmlnTRZle/2gr5qxabaEA==", + "dev": true }, "oas-validator": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/oas-validator/-/oas-validator-3.4.0.tgz", "integrity": "sha512-l/SxykuACi2U51osSsBXTxdsFc8Fw41xI7AsZkzgVgWJAzoEFaaNptt35WgY9C3757RUclsm6ye5GvSyYoozLQ==", + "dev": true, "requires": { "ajv": "^5.5.2", "better-ajv-errors": "^0.6.7", @@ -1765,6 +1939,7 @@ "version": "5.5.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, "requires": { "co": "^4.6.0", "fast-deep-equal": "^1.0.0", @@ -1775,19 +1950,22 @@ "fast-deep-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=" + "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", + "dev": true }, "json-schema-traverse": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=" + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true } } }, "oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "dev": true }, "object-assign": { "version": "4.1.1", @@ -1799,6 +1977,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -1813,6 +1992,7 @@ "version": "0.1.36", "resolved": "https://registry.npmjs.org/openapi-generator/-/openapi-generator-0.1.36.tgz", "integrity": "sha512-cIPjWeIzUa3bJK4XwzPp5wLS6trUo1WNTAvT4zGSNtkvaD1Vc9H65WZu2/W+NfQ+lhVvCsv4l9dIYtyqlNHiEg==", + "dev": true, "requires": { "@types/nunjucks": "^3.1.0", "@types/request": "^2.48.0", @@ -1829,19 +2009,22 @@ "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true } } }, "openapi3-ts": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/openapi3-ts/-/openapi3-ts-1.3.0.tgz", - "integrity": "sha512-Xk3hsB0PzB4dzr/r/FdmK+VfQbZH7lQQ2iipMS1/1eoz1wUvh5R7rmOakYvw0bQJJE6PYrOLx8UHsYmzgTr+YQ==" + "integrity": "sha512-Xk3hsB0PzB4dzr/r/FdmK+VfQbZH7lQQ2iipMS1/1eoz1wUvh5R7rmOakYvw0bQJJE6PYrOLx8UHsYmzgTr+YQ==", + "dev": true }, "os-locale": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz", "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==", + "dev": true, "requires": { "execa": "^1.0.0", "lcid": "^2.0.0", @@ -1857,22 +2040,26 @@ "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", - "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=" + "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=", + "dev": true }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, "p-is-promise": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz", - "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==" + "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==", + "dev": true }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, "requires": { "p-try": "^2.0.0" } @@ -1881,6 +2068,7 @@ "version": "4.1.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, "requires": { "p-limit": "^2.2.0" } @@ -1894,12 +2082,14 @@ "p-try": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true }, "path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true }, "path-is-absolute": { "version": "1.0.1", @@ -1916,7 +2106,8 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, "pend": { "version": "1.2.0", @@ -1927,12 +2118,14 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", + "dev": true }, "picomatch": { "version": "2.2.2", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, "optional": true }, "pify": { @@ -1941,6 +2134,12 @@ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true }, + "prettier": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.4.tgz", + "integrity": "sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==", + "dev": true + }, "pretty-bytes": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.3.0.tgz", @@ -1956,12 +2155,14 @@ "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "dev": true }, "pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "dev": true, "requires": { "end-of-stream": "^1.1.0", "once": "^1.3.1" @@ -1970,12 +2171,14 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "dev": true }, "querystring": { "version": "0.2.0", @@ -2016,6 +2219,7 @@ "version": "3.4.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz", "integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==", + "dev": true, "optional": true, "requires": { "picomatch": "^2.2.1" @@ -2024,17 +2228,20 @@ "reftools": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/reftools/-/reftools-1.1.1.tgz", - "integrity": "sha512-7ySkzK7YpUeJP16rzJqEXTZ7IrAq/AL/p+wWejD9wdKQOe+mYYVAOB3w5ZTs2eoHfmAidwr/6PcC+q+LzPF/DQ==" + "integrity": "sha512-7ySkzK7YpUeJP16rzJqEXTZ7IrAq/AL/p+wWejD9wdKQOe+mYYVAOB3w5ZTs2eoHfmAidwr/6PcC+q+LzPF/DQ==", + "dev": true }, "regenerator-runtime": { "version": "0.13.5", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", - "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==", + "dev": true }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "dev": true, "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -2067,15 +2274,37 @@ "throttleit": "^1.0.0" } }, + "request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "dev": true, + "requires": { + "lodash": "^4.17.15" + } + }, + "request-promise-native": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", + "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "dev": true, + "requires": { + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + } + }, "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true }, "restore-cursor": { "version": "1.0.1", @@ -2108,27 +2337,32 @@ "safe-buffer": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", - "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" + "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==", + "dev": true }, "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "dev": true }, "semver": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -2136,12 +2370,14 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, "should": { "version": "13.2.3", "resolved": "https://registry.npmjs.org/should/-/should-13.2.3.tgz", "integrity": "sha512-ggLesLtu2xp+ZxI+ysJTmNjh2U0TsC+rQ/pfED9bUZZ4DKefP27D+7YJVVTvKsmjLpIi9jAa7itwDGkDDmt1GQ==", + "dev": true, "requires": { "should-equal": "^2.0.0", "should-format": "^3.0.3", @@ -2154,6 +2390,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/should-equal/-/should-equal-2.0.0.tgz", "integrity": "sha512-ZP36TMrK9euEuWQYBig9W55WPC7uo37qzAEmbjHz4gfyuXrEUgF8cUvQVO+w+d3OMfPvSRQJ22lSm8MQJ43LTA==", + "dev": true, "requires": { "should-type": "^1.4.0" } @@ -2162,6 +2399,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/should-format/-/should-format-3.0.3.tgz", "integrity": "sha1-m/yPdPo5IFxT04w01xcwPidxJPE=", + "dev": true, "requires": { "should-type": "^1.3.0", "should-type-adaptors": "^1.0.1" @@ -2170,12 +2408,14 @@ "should-type": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/should-type/-/should-type-1.4.0.tgz", - "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=" + "integrity": "sha1-B1bYzoRt/QmEOmlHcZ36DUz/XPM=", + "dev": true }, "should-type-adaptors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/should-type-adaptors/-/should-type-adaptors-1.1.0.tgz", "integrity": "sha512-JA4hdoLnN+kebEp2Vs8eBe9g7uy0zbRo+RMcU0EsNy+R+k049Ki+N5tT5Jagst2g7EAja+euFuoXFCa8vIklfA==", + "dev": true, "requires": { "should-type": "^1.3.0", "should-util": "^1.0.0" @@ -2184,12 +2424,14 @@ "should-util": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/should-util/-/should-util-1.0.1.tgz", - "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==" + "integrity": "sha512-oXF8tfxx5cDk8r2kYqlkUJzZpDBqVY/II2WhvU0n9Y3XYvAYRmeaf1PvvIvTgPnv4KJ+ES5M0PyDq5Jp+Ygy2g==", + "dev": true }, "signal-exit": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true }, "slice-ansi": { "version": "0.0.4", @@ -2201,6 +2443,7 @@ "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dev": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -2213,10 +2456,17 @@ "tweetnacl": "~0.14.0" } }, + "stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "dev": true + }, "string-width": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -2225,12 +2475,14 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -2266,7 +2518,8 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true }, "supports-color": { "version": "7.1.0", @@ -2289,6 +2542,7 @@ "version": "5.4.0", "resolved": "https://registry.npmjs.org/swagger2openapi/-/swagger2openapi-5.4.0.tgz", "integrity": "sha512-f5QqfXawiVijhjMtYqWZ55ESHPZFqrPC8L9idhIiuSX8O2qsa1i4MVGtCM3TQF+Smzr/6WfT/7zBuzG3aTgPAA==", + "dev": true, "requires": { "better-ajv-errors": "^0.6.1", "call-me-maybe": "^1.0.1", @@ -2328,6 +2582,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "optional": true, "requires": { "is-number": "^7.0.0" @@ -2337,6 +2592,7 @@ "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dev": true, "requires": { "psl": "^1.1.28", "punycode": "^2.1.1" @@ -2345,12 +2601,14 @@ "tslib": { "version": "1.11.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.11.1.tgz", - "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==" + "integrity": "sha512-aZW88SY8kQbU7gpV19lN24LtXh/yD4ZZg6qieAJDDg+YBsJcSmLGK9QpnUjAKVG/xefmvJGd1WUmfpT/g6AJGA==", + "dev": true }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "requires": { "safe-buffer": "^5.0.1" } @@ -2358,7 +2616,8 @@ "tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", + "dev": true }, "typedarray": { "version": "0.0.6", @@ -2382,6 +2641,7 @@ "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, "requires": { "punycode": "^2.1.0" } @@ -2413,22 +2673,39 @@ "uuid": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true }, "verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, + "wait-on": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-4.0.2.tgz", + "integrity": "sha512-Qpmgm3Hw/sXm7xK68FBsYy5r+Uid94/QymwnEjn9GTpfiWTUVYm0bccivVwY/BXGYO2r+5Cd8S/DzrRZqHK/9w==", + "dev": true, + "requires": { + "@hapi/joi": "^17.1.1", + "lodash": "^4.17.15", + "minimist": "^1.2.5", + "request": "^2.88.2", + "request-promise-native": "^1.0.8", + "rxjs": "^6.5.5" + } + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -2436,7 +2713,8 @@ "which-module": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true }, "wrap-ansi": { "version": "3.0.1", @@ -2468,17 +2746,20 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "y18n": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==" + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true }, "yaml": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz", "integrity": "sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg==", + "dev": true, "requires": { "@babel/runtime": "^7.9.2" } @@ -2487,6 +2768,7 @@ "version": "12.0.5", "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", + "dev": true, "requires": { "cliui": "^4.0.0", "decamelize": "^1.2.0", @@ -2505,12 +2787,14 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, "cliui": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", + "dev": true, "requires": { "string-width": "^2.1.1", "strip-ansi": "^4.0.0", @@ -2521,6 +2805,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, "requires": { "locate-path": "^3.0.0" } @@ -2528,12 +2813,14 @@ "get-caller-file": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", - "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" + "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==", + "dev": true }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2542,6 +2829,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, "requires": { "p-locate": "^3.0.0", "path-exists": "^3.0.0" @@ -2551,6 +2839,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, "requires": { "p-limit": "^2.0.0" } @@ -2558,17 +2847,20 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -2577,6 +2869,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -2585,12 +2878,14 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "string-width": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -2601,6 +2896,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -2611,6 +2907,7 @@ "version": "11.1.1", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", + "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" @@ -2622,6 +2919,7 @@ "version": "18.1.3", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "dev": true, "requires": { "camelcase": "^5.0.0", "decamelize": "^1.2.0" diff --git a/package.json b/package.json index df904f875f42..a6ad8ea60bf4 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,15 @@ { "private": true, - "devDependencies": { - "cypress": "^4.3.0" + "scripts": { + "format": "prettier --no-semi --single-quote --trailing-comma es5 --write \"cypress/**/*.js\"", + "test": "cypress run", + "test:watch": "cypress open", + "wait-on": "wait-on" }, - "dependencies": { + "devDependencies": { + "cypress": "4.5.0", + "prettier": "2.0.4", + "wait-on": "4.0.2", "openapi-generator": "^0.1.36" } } diff --git a/scripts/test-envs.sh b/script/test-envs.sh similarity index 100% rename from scripts/test-envs.sh rename to script/test-envs.sh diff --git a/selfservice/flow/login/request.go b/selfservice/flow/login/request.go index 4817de010129..9b245c123d00 100644 --- a/selfservice/flow/login/request.go +++ b/selfservice/flow/login/request.go @@ -12,7 +12,6 @@ import ( "github.com/gofrs/uuid" "github.com/pkg/errors" - "github.com/ory/herodot" "github.com/ory/x/urlx" "github.com/ory/kratos/identity" @@ -129,9 +128,6 @@ func (r *Request) Valid() error { return errors.WithStack(newRequestExpiredError(time.Since(r.ExpiresAt))) } - if r.IssuedAt.After(time.Now()) { - return errors.WithStack(herodot.ErrBadRequest.WithReason("The login request was issued in the future.")) - } return nil } diff --git a/selfservice/flow/login/request_test.go b/selfservice/flow/login/request_test.go index ba7bd2495d68..b36c76218ff5 100644 --- a/selfservice/flow/login/request_test.go +++ b/selfservice/flow/login/request_test.go @@ -41,7 +41,6 @@ func TestRequest(t *testing.T) { r: &login.Request{ExpiresAt: time.Now().Add(time.Hour), IssuedAt: time.Now().Add(-time.Minute)}, valid: true, }, - {r: &login.Request{ExpiresAt: time.Now().Add(time.Hour), IssuedAt: time.Now().Add(time.Minute)}}, {r: &login.Request{ExpiresAt: time.Now().Add(-time.Hour), IssuedAt: time.Now().Add(-time.Minute)}}, } { if tc.valid { diff --git a/selfservice/flow/registration/request.go b/selfservice/flow/registration/request.go index 5b7ee928b2d5..00323ed2aad7 100644 --- a/selfservice/flow/registration/request.go +++ b/selfservice/flow/registration/request.go @@ -8,7 +8,6 @@ import ( "github.com/gofrs/uuid" "github.com/pkg/errors" - "github.com/ory/herodot" "github.com/ory/x/urlx" "github.com/ory/kratos/identity" @@ -130,8 +129,5 @@ func (r *Request) Valid() error { if r.ExpiresAt.Before(time.Now()) { return errors.WithStack(newRequestExpiredError(time.Since(r.ExpiresAt))) } - if r.IssuedAt.After(time.Now()) { - return errors.WithStack(herodot.ErrBadRequest.WithReason("The registration request was issued in the future.")) - } return nil } diff --git a/selfservice/flow/registration/request_test.go b/selfservice/flow/registration/request_test.go index 0abd3331b2fb..7053b4939db2 100644 --- a/selfservice/flow/registration/request_test.go +++ b/selfservice/flow/registration/request_test.go @@ -41,7 +41,6 @@ func TestRequest(t *testing.T) { r: ®istration.Request{ExpiresAt: time.Now().Add(time.Hour), IssuedAt: time.Now().Add(-time.Minute)}, valid: true, }, - {r: ®istration.Request{ExpiresAt: time.Now().Add(time.Hour), IssuedAt: time.Now().Add(time.Minute)}}, {r: ®istration.Request{ExpiresAt: time.Now().Add(-time.Hour), IssuedAt: time.Now().Add(-time.Minute)}}, } { if tc.valid { diff --git a/selfservice/flow/settings/strategy_profile.go b/selfservice/flow/settings/strategy_profile.go index da6d1b3b62d6..3bbff77b52f6 100644 --- a/selfservice/flow/settings/strategy_profile.go +++ b/selfservice/flow/settings/strategy_profile.go @@ -27,11 +27,15 @@ import ( ) const ( - StrategyProfile = "profile" - PublicSettingsProfilePath = "/self-service/browser/flows/settings/strategies/profile" - strategyProfileContinuityName = "settings_profile" + StrategyProfile = "profile" + PublicSettingsProfilePath = "/self-service/browser/flows/settings/strategies/profile" ) +func strategyProfileContinuityName(rid string) string { + // Use one individual container per request ID to prevent resuming other request IDs. + return "ory_kratos_settings_profile." + rid +} + var _ Strategy = new(StrategyTraits) type ( @@ -147,12 +151,24 @@ func (s *StrategyTraits) handleSubmit(w http.ResponseWriter, r *http.Request, ps } var p completeSelfServiceBrowserSettingsStrategyProfileFlowPayload - if _, err := s.d.ContinuityManager().Continue(r.Context(), r, - strategyProfileContinuityName, + rid := r.URL.Query().Get("request") + p = completeSelfServiceBrowserSettingsStrategyProfileFlowPayload{RequestID: rid} + if len(rid) == 0 { + s.handleSettingsError(w, r, nil, ss, json.RawMessage(ss.Identity.Traits), &p, errors.WithStack(herodot.ErrBadRequest.WithReasonf("The request query parameter is missing."))) + return + } + + if _, err = s.d.ContinuityManager().Continue(r.Context(), r, + strategyProfileContinuityName(rid), continuity.WithIdentity(ss.Identity), continuity.WithPayload(&p), ); err == nil { - s.continueFlow(w, r, ss, &p) + if p.RequestID == r.URL.Query().Get("request") { + s.continueFlow(w, r, ss, &p) + return + } + } else if !errors.Is(err, &continuity.ErrNotResumable) { + s.handleSettingsError(w, r, nil, ss, json.RawMessage(ss.Identity.Traits), &p, err) return } @@ -173,12 +189,6 @@ func (s *StrategyTraits) handleSubmit(w http.ResponseWriter, r *http.Request, ps return } - rid := r.URL.Query().Get("request") - if len(rid) == 0 { - s.handleSettingsError(w, r, nil, ss, json.RawMessage(ss.Identity.Traits), &p, errors.WithStack(herodot.ErrBadRequest.WithReasonf("The request query parameter is missing."))) - return - } - p.RequestID = rid s.continueFlow(w, r, ss, &p) } @@ -286,7 +296,7 @@ func (s *StrategyTraits) handleSettingsError(w http.ResponseWriter, r *http.Requ if errors.Is(err, ErrRequestNeedsReAuthentication) { if err := s.d.ContinuityManager().Pause( r.Context(), w, r, - strategyProfileContinuityName, + strategyProfileContinuityName(r.URL.Query().Get("request")), continuity.WithPayload(p), continuity.WithIdentity(ss.Identity), continuity.WithLifespan(time.Minute*15), diff --git a/selfservice/flow/settings/strategy_profile_test.go b/selfservice/flow/settings/strategy_profile_test.go index 1793748a608f..1e438be93713 100644 --- a/selfservice/flow/settings/strategy_profile_test.go +++ b/selfservice/flow/settings/strategy_profile_test.go @@ -42,6 +42,8 @@ func TestStrategyTraits(t *testing.T) { ui := testhelpers.NewSettingsUITestServer(t) viper.Set(configuration.ViperKeySelfServicePrivilegedAuthenticationAfter, "1ns") + _ = testhelpers.NewErrorTestServer(t, reg) + primaryIdentity := &identity.Identity{ ID: x.NewUUID(), Credentials: map[identity.CredentialsType]identity.Credentials{ diff --git a/selfservice/flow/verify/handler.go b/selfservice/flow/verify/handler.go index ccf49349eef4..f8a57bca1d91 100644 --- a/selfservice/flow/verify/handler.go +++ b/selfservice/flow/verify/handler.go @@ -336,7 +336,7 @@ func (h *Handler) verify(w http.ResponseWriter, r *http.Request, ps httprouter.P } if err := h.d.PrivilegedIdentityPool().VerifyAddress(r.Context(), ps.ByName("code")); err != nil { - if errorsx.Cause(err) == sqlcon.ErrNoRows { + if errors.Is(err, sqlcon.ErrNoRows) { a := NewRequest( h.c.SelfServiceSettingsRequestLifespan(), r, via, urlx.AppendPaths(h.c.SelfPublicURL(), strings.ReplaceAll(PublicVerificationCompletePath, ":via", string(via))), h.d.GenerateCSRFToken, @@ -352,6 +352,7 @@ func (h *Handler) verify(w http.ResponseWriter, r *http.Request, ps httprouter.P urlx.CopyWithQuery(h.c.VerificationURL(), url.Values{"request": {a.ID.String()}}).String(), http.StatusFound, ) + return } h.d.SelfServiceErrorManager().Forward(r.Context(), w, r, err) diff --git a/selfservice/strategy/password/settings.go b/selfservice/strategy/password/settings.go index 82d7d36a1289..3b7a9de23bf2 100644 --- a/selfservice/strategy/password/settings.go +++ b/selfservice/strategy/password/settings.go @@ -24,10 +24,14 @@ import ( ) const ( - SettingsPath = "/self-service/browser/flows/settings/strategies/password" - continuityKeySettings = "settings_password" + SettingsPath = "/self-service/browser/flows/settings/strategies/password" ) +func continuityKeySettings(rid string) string { + // Use one individual container per request ID to prevent resuming other request IDs. + return "settings_password." + rid +} + func (s *Strategy) RegisterSettingsRoutes(router *x.RouterPublic) { router.POST(SettingsPath, s.submitSettingsFlow) router.GET(SettingsPath, s.submitSettingsFlow) @@ -79,18 +83,24 @@ func (s *Strategy) submitSettingsFlow(w http.ResponseWriter, r *http.Request, ps return } + rid := r.URL.Query().Get("request") + if len(rid) == 0 { + s.handleSettingsError(w, r, nil, ss, nil, errors.WithStack(herodot.ErrBadRequest.WithReasonf("The request query parameter is missing."))) + return + } + var p completeSelfServiceBrowserSettingsPasswordFlowPayload if _, err := s.d.ContinuityManager().Continue(r.Context(), r, - continuityKeySettings, + continuityKeySettings(r.URL.Query().Get("request")), continuity.WithIdentity(ss.Identity), continuity.WithPayload(&p)); err == nil { - s.completeSettingsFlow(w, r, ss, &p) + if p.RequestID == r.URL.Query().Get("request") { + s.completeSettingsFlow(w, r, ss, &p) + return + } return - } - - rid := r.URL.Query().Get("request") - if len(rid) == 0 { - s.handleSettingsError(w, r, nil, ss, &p, errors.WithStack(herodot.ErrBadRequest.WithReasonf("The request query parameter is missing."))) + } else if !errors.Is(err, &continuity.ErrNotResumable) { + s.handleSettingsError(w, r, nil, ss, &p, err) return } @@ -105,7 +115,7 @@ func (s *Strategy) submitSettingsFlow(w http.ResponseWriter, r *http.Request, ps } if err := s.d.ContinuityManager().Pause( r.Context(), w, r, - continuityKeySettings, + continuityKeySettings(r.URL.Query().Get("request")), continuity.WithPayload(&p), continuity.WithIdentity(ss.Identity), continuity.WithLifespan(time.Minute*15), @@ -211,7 +221,7 @@ func (s *Strategy) handleSettingsError(w http.ResponseWriter, r *http.Request, r if errors.Is(err, settings.ErrRequestNeedsReAuthentication) { if err := s.d.ContinuityManager().Pause( r.Context(), w, r, - continuityKeySettings, + continuityKeySettings(r.URL.Query().Get("request")), continuity.WithPayload(p), continuity.WithIdentity(ss.Identity), continuity.WithLifespan(time.Minute*15), diff --git a/test/e2e/.gitignore b/test/e2e/.gitignore new file mode 100644 index 000000000000..ddb9705a2def --- /dev/null +++ b/test/e2e/.gitignore @@ -0,0 +1,2 @@ +*.log +kratos.generated.yml diff --git a/test/e2e/profiles/email/.kratos.yml b/test/e2e/profiles/email/.kratos.yml new file mode 100644 index 000000000000..3047e6e34f92 --- /dev/null +++ b/test/e2e/profiles/email/.kratos.yml @@ -0,0 +1,21 @@ +selfservice: + strategies: + password: + enabled: true + + settings: + privileged_session_max_age: 5000ms + + logout: + redirect_to: http://127.0.0.1:4455/auth/login + + registration: + after: + password: + hooks: + - + hook: session + +identity: + traits: + default_schema_url: file://test/e2e/profiles/email/identity.traits.schema.json diff --git a/test/e2e/profiles/email/identity.traits.schema.json b/test/e2e/profiles/email/identity.traits.schema.json new file mode 100644 index 000000000000..cc9f72a2bcbe --- /dev/null +++ b/test/e2e/profiles/email/identity.traits.schema.json @@ -0,0 +1,31 @@ +{ + "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Person", + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "title": "E-Mail", + "minLength": 3, + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + } + } + }, + "website": { + "type": "string", + "format": "uri", + "minLength": 10 + } + }, + "required": [ + "email", + "website" + ], + "additionalProperties": false +} diff --git a/test/e2e/profiles/kratos.base.yml b/test/e2e/profiles/kratos.base.yml new file mode 100644 index 000000000000..32b1c066105f --- /dev/null +++ b/test/e2e/profiles/kratos.base.yml @@ -0,0 +1,35 @@ +log: + level: debug + +secrets: + session: + - PLEASE-CHANGE-ME-I-AM-VERY-INSECURE + +urls: + login_ui: http://127.0.0.1:4455/auth/login + registration_ui: http://127.0.0.1:4455/auth/registration + error_ui: http://127.0.0.1:4455/error + settings_ui: http://127.0.0.1:4455/settings + verify_ui: http://127.0.0.1:4455/verify + + # These are undefined because not available in this demo + mfa_ui: http://127.0.0.1:4455/ + + self: + public: http://127.0.0.1:4455/.ory/kratos/public/ + admin: http://kratos:4434/ + default_return_to: http://127.0.0.1:4455/ + whitelisted_return_to_urls: + - http://127.0.0.1:4455 + +hashers: + argon2: + parallelism: 1 + memory: 16384 + iterations: 1 + salt_length: 16 + key_length: 16 + +courier: + smtp: + connection_uri: smtps://test:test@127.0.0.1:1025/?skip_ssl_verify=true diff --git a/test/e2e/profiles/verify/.kratos.yml b/test/e2e/profiles/verify/.kratos.yml new file mode 100644 index 000000000000..afa6b62fe9e4 --- /dev/null +++ b/test/e2e/profiles/verify/.kratos.yml @@ -0,0 +1,30 @@ +selfservice: + strategies: + password: + enabled: true + + settings: + privileged_session_max_age: 1m + after: + profile: + hooks: + - + hook: verify + + verify: + return_to: http://127.0.0.1:4455/ + link_lifespan: 5s + + logout: + redirect_to: http://127.0.0.1:4455/auth/login + + registration: + after: + password: + hooks: + - + hook: verify + +identity: + traits: + default_schema_url: file://test/e2e/profiles/verify/identity.traits.schema.json diff --git a/test/e2e/profiles/verify/identity.traits.schema.json b/test/e2e/profiles/verify/identity.traits.schema.json new file mode 100644 index 000000000000..424efd557d93 --- /dev/null +++ b/test/e2e/profiles/verify/identity.traits.schema.json @@ -0,0 +1,28 @@ +{ + "$id": "https://schemas.ory.sh/presets/kratos/quickstart/email-password/identity.schema.json", + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Person", + "type": "object", + "properties": { + "email": { + "type": "string", + "format": "email", + "title": "E-Mail", + "minLength": 3, + "ory.sh/kratos": { + "credentials": { + "password": { + "identifier": true + } + }, + "verification": { + "via": "email" + } + } + } + }, + "required": [ + "email" + ], + "additionalProperties": false +} diff --git a/test/e2e/run.sh b/test/e2e/run.sh new file mode 100755 index 000000000000..c188379a5379 --- /dev/null +++ b/test/e2e/run.sh @@ -0,0 +1,157 @@ +#!/bin/bash + +set -euxo pipefail + +cd "$( dirname "${BASH_SOURCE[0]}" )/../.." + +export PATH=.bin:$PATH +export KRATOS_PUBLIC_URL=http://127.0.0.1:4433/ +export KRATOS_ADMIN_URL=http://127.0.0.1:4434/ + +! nc -zv 127.0.0.1 4434 +! nc -zv 127.0.0.1 4433 +! nc -zv 127.0.0.1 4455 + +base=$(pwd) + +if [ -z ${KRATOS_APP_PATH+x} ]; then + dir="$(mktemp -d -t ci-XXXXXXXXXX)/kratos-selfservice-ui-node" + git clone git@github.com:ory/kratos-selfservice-ui-node.git "$dir" + (cd "$dir"; npm i && npm run build) +else + dir="${KRATOS_APP_PATH}" +fi + +kratos=./test/e2e/.bin/kratos +go build -tags sqlite -o $kratos . + +if [ -z ${CI+x} ]; then + docker rm mailslurper -f || true + docker run --name mailslurper -p 4436:4436 -p 4437:4437 -p 1025:1025 oryd/mailslurper:latest-smtps > "${base}/test/e2e/mailslurper.e2e.log" 2>&1 & +fi + +dev=no +for i in "$@" +do +case $i in + --dev) + dev=yes + shift # past argument=value + ;; +esac +done + +run() { + profile=$2 + killall kratos || true + killall node || true + + if [ -z ${KRATOS_APP_PATH+x} ]; then + (cd "$dir"; PORT=4455 SECURITY_MODE=cookie npm run serve \ + > "${base}/test/e2e/secureapp.e2e.log" 2>&1 &) + else + (cd "$dir"; PORT=4455 SECURITY_MODE=cookie npm run start \ + > "${base}/test/e2e/secureapp.e2e.log" 2>&1 &) + fi + + export DSN=${1} + $kratos migrate sql -e --yes + + yq merge test/e2e/profiles/kratos.base.yml "test/e2e/profiles/${profile}/.kratos.yml" > test/e2e/kratos.generated.yml + ($kratos serve --dev -c test/e2e/kratos.generated.yml > "${base}/test/e2e/kratos.${profile}.e2e.log" 2>&1 &) + + npm run wait-on -- -t 10000 http-get://127.0.0.1:4434/health/ready \ + http-get://127.0.0.1:4455/health \ + http-get://127.0.0.1:4437/mail + + if [[ $dev = "yes" ]]; then + npm run test:watch -- --config integrationFolder="cypress/integration/profiles/$profile" + else + npm run test -- --config integrationFolder="cypress/integration/profiles/$profile" + fi +} + +usage() { + echo $"This script runs the e2e tests. + +To run the tests just pick a database name: + + $0 + + Supported databases are 'sqlite', 'mysql', 'postgres', 'cockroach': + + $0 sqlite + $0 mysql + $0 postgres + $0 cockroach + ... + + If you are using a database other than SQLite, you need to set + an environment variable that points to it: + + export TEST_DATABASE_MYSQL=... + export TEST_DATABASE_POSTGRESQL=... + export TEST_DATABASE_COCKROACHDB=... + $0 + + The Makefile has a helper for that which uses Docker to start the + databases: + + make test-resetdb + source scripts/test-envs.sh + $0 + +To run e2e tests in dev mode (useful for writing them), run: + + $0 --dev + + Supported profiles are 'email', 'verify': + + $0 --dev email + $0 --dev verify + ... + +If you are making changes to the kratos-selfservice-ui-node +project as well, point the 'KRATOS_APP_PATH' environment variable to +the path where the kratos-selfservice-ui-node project is checked out: + + export KRATOS_APP_PATH=$HOME/workspace/kratos-selfservice-ui-node + $0 ..." +} + +if [[ $dev = "yes" ]]; then + if [ -z ${2+x} ]; then + usage + exit 1 + fi +fi + +export TEST_DATABASE_SQLITE="sqlite:///$(mktemp -d -t ci-XXXXXXXXXX)/db.sqlite?_fk=true" +case "$1" in + sqlite) + db="${TEST_DATABASE_SQLITE}" + ;; + + mysql) + db="${TEST_DATABASE_MYSQL}" + ;; + + postgres) + db="${TEST_DATABASE_POSTGRESQL}" + ;; + + cockroach) + db="${TEST_DATABASE_COCKROACHDB}" + ;; + + *) + usage + exit 1 +esac + +if [[ $dev = "yes" ]]; then + run "${db}" "$2" +else + run "${db}" email + run "${db}" verify +fi diff --git a/tests/fixtures/config.schema.test.failure/wrongTypes.main.yaml b/test/schema/fixtures/config.schema.test.failure/wrongTypes.main.yaml similarity index 100% rename from tests/fixtures/config.schema.test.failure/wrongTypes.main.yaml rename to test/schema/fixtures/config.schema.test.failure/wrongTypes.main.yaml diff --git a/tests/fixtures/config.schema.test.success/allKeys.main.yaml b/test/schema/fixtures/config.schema.test.success/allKeys.main.yaml similarity index 100% rename from tests/fixtures/config.schema.test.success/allKeys.main.yaml rename to test/schema/fixtures/config.schema.test.success/allKeys.main.yaml diff --git a/tests/fixtures/config.schema.test.success/allKeys.selfServiceAfterLogin.yaml b/test/schema/fixtures/config.schema.test.success/allKeys.selfServiceAfterLogin.yaml similarity index 100% rename from tests/fixtures/config.schema.test.success/allKeys.selfServiceAfterLogin.yaml rename to test/schema/fixtures/config.schema.test.success/allKeys.selfServiceAfterLogin.yaml diff --git a/tests/fixtures/config.schema.test.success/allKeys.selfServiceAfterLoginHooks.yaml b/test/schema/fixtures/config.schema.test.success/allKeys.selfServiceAfterLoginHooks.yaml similarity index 100% rename from tests/fixtures/config.schema.test.success/allKeys.selfServiceAfterLoginHooks.yaml rename to test/schema/fixtures/config.schema.test.success/allKeys.selfServiceAfterLoginHooks.yaml diff --git a/tests/fixtures/config.schema.test.success/allKeys.selfServiceAfterRegistrationHooks.yaml b/test/schema/fixtures/config.schema.test.success/allKeys.selfServiceAfterRegistrationHooks.yaml similarity index 100% rename from tests/fixtures/config.schema.test.success/allKeys.selfServiceAfterRegistrationHooks.yaml rename to test/schema/fixtures/config.schema.test.success/allKeys.selfServiceAfterRegistrationHooks.yaml diff --git a/tests/fixtures/config.schema.test.success/allKeys.selfServiceBefore.yaml b/test/schema/fixtures/config.schema.test.success/allKeys.selfServiceBefore.yaml similarity index 100% rename from tests/fixtures/config.schema.test.success/allKeys.selfServiceBefore.yaml rename to test/schema/fixtures/config.schema.test.success/allKeys.selfServiceBefore.yaml diff --git a/tests/fixtures/config.schema.test.success/allKeys.selfServiceOIDCProvider.yaml b/test/schema/fixtures/config.schema.test.success/allKeys.selfServiceOIDCProvider.yaml similarity index 100% rename from tests/fixtures/config.schema.test.success/allKeys.selfServiceOIDCProvider.yaml rename to test/schema/fixtures/config.schema.test.success/allKeys.selfServiceOIDCProvider.yaml diff --git a/tests/fixtures/config.schema.test.success/allKeys.selfServiceRedirectHook.yaml b/test/schema/fixtures/config.schema.test.success/allKeys.selfServiceRedirectHook.yaml similarity index 100% rename from tests/fixtures/config.schema.test.success/allKeys.selfServiceRedirectHook.yaml rename to test/schema/fixtures/config.schema.test.success/allKeys.selfServiceRedirectHook.yaml diff --git a/tests/fixtures/config.schema.test.success/allKeys.selfServiceSessionIssuerHook.yaml b/test/schema/fixtures/config.schema.test.success/allKeys.selfServiceSessionIssuerHook.yaml similarity index 100% rename from tests/fixtures/config.schema.test.success/allKeys.selfServiceSessionIssuerHook.yaml rename to test/schema/fixtures/config.schema.test.success/allKeys.selfServiceSessionIssuerHook.yaml diff --git a/tests/fixtures/config.schema.test.success/allKeys.selfServiceSessionRevokerHook.yaml b/test/schema/fixtures/config.schema.test.success/allKeys.selfServiceSessionRevokerHook.yaml similarity index 100% rename from tests/fixtures/config.schema.test.success/allKeys.selfServiceSessionRevokerHook.yaml rename to test/schema/fixtures/config.schema.test.success/allKeys.selfServiceSessionRevokerHook.yaml diff --git a/tests/schema_test.go b/test/schema/schema_test.go similarity index 99% rename from tests/schema_test.go rename to test/schema/schema_test.go index c5d0052d0407..31528e280d1c 100644 --- a/tests/schema_test.go +++ b/test/schema/schema_test.go @@ -1,4 +1,4 @@ -package tests +package schema import ( "bytes" diff --git a/x/cookie.go b/x/cookie.go index f653902043e6..94ed102e467c 100644 --- a/x/cookie.go +++ b/x/cookie.go @@ -31,7 +31,7 @@ func SessionGetString(r *http.Request, s sessions.Store, id string, key interfac } if v, ok := cookie.Values[key]; !ok { - return "", errors.Errorf("key %s does not exist in cookie", key) + return "", errors.Errorf("key %s does not exist in cookie: %+v", key, cookie.Values) } else if vv, ok := v.(string); !ok { return "", errors.Errorf("value of key %s is not of type string in cookie", key) } else {