diff --git a/.docker/Dockerfile b/.docker/Dockerfile index aac17fe9ed2..be995fd17ef 100644 --- a/.docker/Dockerfile +++ b/.docker/Dockerfile @@ -8,7 +8,7 @@ RUN apk add -U --no-cache ca-certificates COPY kratos /usr/bin/kratos -# Exposing the ory home directory to simplify passing in Kratos configuration (e.g. if the file $HOME/.kratos.yaml +# Exposing the ory home directory to simplify passing in Kratos configuration (e.g. if the file $HOME/.kratos.yaml # exists, it will be automatically used as the configuration file). VOLUME /home/ory diff --git a/.docker/Dockerfile-build b/.docker/Dockerfile-build index 73b1fcad116..a093c4271be 100644 --- a/.docker/Dockerfile-build +++ b/.docker/Dockerfile-build @@ -7,14 +7,16 @@ WORKDIR /go/src/github.com/ory/kratos ADD go.mod go.mod ADD go.sum go.sum +ENV GO111MODULE on +ENV CGO_ENABLED 1 + RUN go mod download -RUN GO111MODULE=on go install github.com/gobuffalo/packr/v2/packr2 github.com/markbates/pkger/cmd/pkger +RUN go install github.com/gobuffalo/packr/v2/packr2 ADD . . RUN packr2 -RUN pkger -RUN CGO_ENABLED=1 go build -tags sqlite -a -o /usr/bin/kratos +RUN go build -tags sqlite -a -o /usr/bin/kratos FROM alpine:3.11 @@ -30,7 +32,7 @@ RUN mkdir -p /var/lib/sqlite RUN chown ory:ory /var/lib/sqlite VOLUME /var/lib/sqlite -# Exposing the ory home directory to simplify passing in Kratos configuration (e.g. if the file $HOME/.kratos.yaml +# Exposing the ory home directory to simplify passing in Kratos configuration (e.g. if the file $HOME/.kratos.yaml # exists, it will be automatically used as the configuration file). VOLUME /home/ory diff --git a/.goreleaser.yml b/.goreleaser.yml index e6a8162f017..8749502a212 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -8,9 +8,8 @@ env: before: hooks: - go mod download - - go install github.com/gobuffalo/packr/v2/packr2 github.com/markbates/pkger/cmd/pkger + - go install github.com/gobuffalo/packr/v2/packr2 - packr2 - - pkger builds: - diff --git a/Makefile b/Makefile index 49232dd25e4..3d1b509c7ca 100644 --- a/Makefile +++ b/Makefile @@ -14,7 +14,6 @@ GO_DEPENDENCIES = github.com/ory/go-acc \ github.com/go-swagger/go-swagger/cmd/swagger \ golang.org/x/tools/cmd/goimports \ github.com/mikefarah/yq \ - github.com/markbates/pkger/cmd/pkger \ github.com/gobuffalo/packr/v2/packr2 define make-go-dependency diff --git a/docs/docs/self-service.mdx b/docs/docs/self-service.mdx index 1f1e5fbae9b..6bbbde6ef49 100644 --- a/docs/docs/self-service.mdx +++ b/docs/docs/self-service.mdx @@ -4,6 +4,9 @@ title: Self-Service Flows sidebar_label: Concepts and Overview --- +import Tabs from '@theme/Tabs' +import TabItem from '@theme/TabItem' + ORY Kratos implements flows that users perform themselves as opposed to administrative intervention. Facebook and Google both provide self-service registration and profile management features as you are able to make changes to @@ -40,15 +43,15 @@ Research, Troy Hunt, ...) and implements the following flows: - [User-Facing Error](self-service/flows/user-facing-errors) - [2FA / MFA](self-service/flows/2fa-mfa-multi-factor-authentication) -Some flows break down into strategies which implement some of the flow's +Some flows break down into "flow methods" which implement some of the flow's business logic: - The `password` strategy implement the [login and registration with "email or/and username and password" method](self-service/flows/user-login-user-registration/username-email-password), - account recovery flow ("reset your password"), and + and ["change your password" user settings method](self-service/flows/user-settings/change-password). - The `oidc` (OpenID Connect, OAuth2, Social Sign In) strategy implements - ["Sign in with ..." login and registration method](self-service/flows/user-login-user-registration/openid-connect-social-sign-in-oauth2), + ["Sign in with ..." login and registration method](self-service/flows/user-login-user-registration/openid-connect-social-sign-in-oauth2) and ["un/link another social account" user settings method](self-service/flows/user-settings/link-unlink-openid-connect-oauth2). - The `profile` strategy implements the @@ -75,44 +78,170 @@ All Self-Service Flows [Profile Management](self-service/flows/user-settings), [Account Recovery](self-service/flows/account-recovery), [Email or Phone verification](self-service/flows/verify-email-account-activation)) -support these two flow types and work more or less the same. +support these two flow types and use the same data models but do use different API endpoints. ### Browser Flows -1. The Browser opens (e.g. with `` or `window.location.href`) the flow's - initialization endpoint (e.g.`/self-service/login/browser` or - `/self-service/registration/browser`). -2. The initialization endpoint creates a flow object and stores it in the - database. The flow object has an ID and contains additional information about - the flow such as the login methods (e.g. "username/password" and "Sign in - with Google") and their form data. Once stored, the Browser is HTTP 302 - redirected to the flow's configured UI URL (e.g. - `selfservice.flows.login.ui_url`), appending the flow ID as the `flow` URL - Query Parameter; -3. The endpoint responsible for the UI URL uses the `flow` URL Query Parameter - (e.g. `http://my-app/auth/login?flow=abcde`) to call the flow information - endpoint (e.g. `/self-service/login/flows?id=abcde`) and fetch the flow - data - so for example the login form and any validation errors. This endpoint - is available at both ORY Kratos's Public and Admin Endpoints. -4. The UI endpoint renders the fetched data in any way it sees it fit. The flow - is typically completed by the browser making another request to one of ORY - Kratos' endpoints, which is usually described in the fetched request data in - form of an e.g. form action URL. +First, the Browser opens the flow's initialization endpoint (e.g.`/self-service/login/browser`, +`/self-service/registration/browser`, ...): + + + + +This CURL request emulates a browser request: + +``` +$ curl -x GET + -H "Accept: text/html" http://127.0.0.1:4433/self-service/login/browser +``` + + + + +```html +Sign in +``` + + + + +```html + + +Sign in +``` + + + + +```js +express.get('/login', function (req, res) { + res.redirect(302, 'http://127.0.0.1:4433/') +}) + +``` + + + + +The initialization endpoint creates a flow object and stores it in the +database. The flow object has an ID and contains additional information about +the flow such as the login methods (e.g. "username/password" and "Sign in +with Google") and their form data. + +Once stored, the Browser is HTTP 302 redirected to the flow's configured UI URL (e.g. +`selfservice.flows.login.ui_url`), appending the flow ID as the `flow` URL +Query Parameter. The initialization endpoint (e.g. `http://127.0.0.1:4433/self-service/login/browser`) +responsds with: + +``` +GET http(s)://public.my-kratos/self-service/login/browser + +HTTP 302 Found +Location: +``` + +The Browser opens the URL which renders the HTML form that, for example, shows the "username and password" field, +the "Update your email address" field, or other flow forms. This HTML form is rendered be the [Self-Service UI](concepts/ui-user-interface#self-service-user-interface-ssui) +which you fully control. + +The endpoint responsible for the UI URL uses the `flow` URL Query Parameter +(e.g. `http://my-app/auth/login?flow=abcde`) to call the flow information +endpoint (e.g. `/self-service/login/flows?id=abcde`) and fetch the flow +data - so for example the login form and any validation errors. This endpoint +is available at both ORY Kratos's Public and Admin Endpoints. For example, +the Self-Service UI responsible for rendering the Login HTML Form would +make a request along the lines of: + +``` +# The endpoint uses ORY Kratos' REST API to fetch information about the request +$ curl -x GET \ + -H "Accept: application/json" \ + http://127.0.0.1:4433/self-service/login/browser?request=... +``` + +The result includes login methods, their fields, and additional information about the flow: + +``` + +``` + +For more details, check out the individual flow documentation. + +The user completes the flow by submitting the form. The form action always points to ORY Kratos directly. +The business logic for the flow method will then validate the form payload and return to the UI URL on +validation errors + +and include the validation errors. + +If a system error (e.g. broken configuration file) occurs, the browser is redirected to the +[Error UI](self-service/flows/user-facing-errors). + +If the form payload is valid, the flow completes with a success. The result here depends on the flow itself - +the login flow for example redirects the user to a specified redirect URL and sets a session cookie. The +registration flow also redirects to a specified redirect URL but only creates the user in the database and might +issue a session cookie if configured to do so. ### API Flows -1. The client makes a `Accept: application/json` GET request to the flow's - initialization endpoint (e.g.`/self-service/login/api` or - `/self-service/registration/api`). +The client (e.g. mobile application) makes a request to the flow's initialization endpoint + + +First, the Browser opens the flow's initialization endpoint (e.g.`/self-service/login/browser`, +`/self-service/registration/browser`, ...): + + + +``` +$ curl -x GET \ + -H "Accept: application/json" \ + http://127.0.0.1:4433/self-service/login/api +``` + + + + +```js +const fetch = require('node-fetch') + +fetch.get('http://127.0.0.1:4433/self-service/login/api', { + headers: { Accept: 'text/html' } +}) + .then(res=>res.json()) + .then((flow) => console.log(flow)) +``` + + + + 2. The initialization endpoint creates a flow object and stores it in the database. The flow object has an ID and contains additional information about the flow such as the login methods (e.g. "username/password" and "Sign in with Google") and their form data. Once stored, the flow data is directly returned as `application/json` without redirects. + 3. The client uses the flow data to render the UI - so for example the login form and any validation errors. + 4. The client completes the flow by e.g. making another `application/json` request. + 5. If needed, the client can re-request the flow information (e.g. because the form completed with validation errors) by calling the flow information endpoint (e.g. `/self-service/login/flows?id=abcde`). This endpoint is diff --git a/go.mod b/go.mod index 48155eaa55c..344f92ec483 100644 --- a/go.mod +++ b/go.mod @@ -46,7 +46,6 @@ require ( github.com/julienschmidt/httprouter v1.2.0 github.com/justinas/nosurf v1.1.0 github.com/leodido/go-urn v1.1.0 // indirect - github.com/markbates/pkger v0.17.0 github.com/mattn/goveralls v0.0.5 github.com/mikefarah/yq v1.15.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 diff --git a/go_mod_indirect_pins.go b/go_mod_indirect_pins.go index a3b503638ba..f1eb82ae61c 100644 --- a/go_mod_indirect_pins.go +++ b/go_mod_indirect_pins.go @@ -24,7 +24,5 @@ import ( _ "github.com/hashicorp/consul/api" - _ "github.com/markbates/pkger/cmd/pkger" - _ "github.com/ory/cli" ) diff --git a/main.go b/main.go index 49ddd83f4f8..79012991e92 100644 --- a/main.go +++ b/main.go @@ -16,8 +16,6 @@ package main import ( - "github.com/markbates/pkger" - "github.com/ory/x/profilex" "github.com/ory/kratos/cmd" @@ -26,7 +24,5 @@ import ( func main() { defer profilex.Profile().Stop() - _ = pkger.Include("/courier/template/templates") - _ = pkger.Include("/.schema/config.schema.json") cmd.Execute() } diff --git a/selfservice/strategy/password/schema.go b/selfservice/strategy/password/schema.go index 27842a6c1ac..4acf429b6dc 100644 --- a/selfservice/strategy/password/schema.go +++ b/selfservice/strategy/password/schema.go @@ -1,10 +1,21 @@ package password import ( - "github.com/markbates/pkger" - - "github.com/ory/kratos/x/pkgerx" + "github.com/gobuffalo/packr/v2" ) -var loginSchema = pkgerx.MustRead(pkger.Open("/selfservice/strategy/password/.schema/login.schema.json")) -var registrationSchema = pkgerx.MustRead(pkger.Open("/selfservice/strategy/password/.schema/registration.schema.json")) +var schemas = packr.New(".schema", ".schema") +var loginSchema, registrationSchema []byte + +func init() { + var err error + loginSchema, err = schemas.Find("login.schema.json") + if err != nil { + panic(err) + } + + registrationSchema, err = schemas.Find("registration.schema.json") + if err != nil { + panic(err) + } +} diff --git a/x/config.go b/x/config.go index 3703619a0e4..56bb7b1d8d3 100644 --- a/x/config.go +++ b/x/config.go @@ -1,23 +1,18 @@ package x import ( - "io/ioutil" - - "github.com/markbates/pkger" - + "github.com/gobuffalo/packr/v2" "github.com/ory/x/logrusx" "github.com/ory/x/viperx" ) +var schemas = packr.New("schemas", "../.schema") + func WatchAndValidateViper(log *logrusx.Logger) { - file, err := pkger.Open("/.schema/config.schema.json") + schema, err := schemas.Find("config.schema.json") if err != nil { log.WithError(err).Fatal("Unable to open configuration JSON Schema.") } - defer file.Close() - schema, err := ioutil.ReadAll(file) - if err != nil { - log.WithError(err).Fatal("Unable to read configuration JSON Schema.") - } + viperx.WatchAndValidateViper(log, schema, "ORY Kratos", []string{"serve", "profiling", "log"}, "") } diff --git a/x/pkgerx/pkgerx.go b/x/pkgerx/pkgerx.go deleted file mode 100644 index 2ef25aa1e98..00000000000 --- a/x/pkgerx/pkgerx.go +++ /dev/null @@ -1,17 +0,0 @@ -package pkgerx - -import ( - "io" - "io/ioutil" -) - -func MustRead(closer io.ReadCloser, err error) []byte { - out, err := ioutil.ReadAll(closer) - if err != nil { - _ = closer.Close() - panic(err) - } - - _ = closer.Close() - return out -}