mirror of https://github.com/ory/kratos
125 lines
3.2 KiB
Go
125 lines
3.2 KiB
Go
// Copyright © 2023 Ory Corp
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package x
|
|
|
|
import (
|
|
"cmp"
|
|
"context"
|
|
"net/http"
|
|
"net/url"
|
|
|
|
"github.com/golang/gddo/httputil"
|
|
"github.com/hashicorp/go-retryablehttp"
|
|
|
|
"github.com/ory/herodot"
|
|
"github.com/ory/x/httpx"
|
|
)
|
|
|
|
type ctxKey struct{}
|
|
|
|
var baseURLKey ctxKey
|
|
|
|
func WithBaseURL(ctx context.Context, baseURL *url.URL) context.Context {
|
|
if baseURL == nil {
|
|
return ctx
|
|
}
|
|
baseURL.Scheme = "https" // Force https
|
|
return context.WithValue(ctx, baseURLKey, baseURL)
|
|
}
|
|
|
|
func BaseURLFromContext(ctx context.Context) *url.URL {
|
|
if ctx == nil {
|
|
return nil
|
|
}
|
|
if v := ctx.Value(baseURLKey); v != nil {
|
|
if u, ok := v.(*url.URL); ok {
|
|
return u
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// FlowBaseURL returns the base URL to be used for a self-service flow. It will
|
|
// either take the request URL, or an explicit base URL set in the context.
|
|
func FlowBaseURL(ctx context.Context, flow interface{ GetRequestURL() string }) (*url.URL, error) {
|
|
if u := BaseURLFromContext(ctx); u != nil {
|
|
return u, nil
|
|
}
|
|
u, err := url.Parse(flow.GetRequestURL())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
u.Path = "/"
|
|
|
|
return u, nil
|
|
}
|
|
|
|
func RequestURL(r *http.Request) *url.URL {
|
|
source := *r.URL
|
|
source.Host = cmp.Or(source.Host, r.Header.Get("X-Forwarded-Host"), r.Host)
|
|
|
|
if proto := r.Header.Get("X-Forwarded-Proto"); len(proto) > 0 {
|
|
source.Scheme = proto
|
|
}
|
|
|
|
if source.Scheme == "" {
|
|
source.Scheme = "https"
|
|
if r.TLS == nil {
|
|
source.Scheme = "http"
|
|
}
|
|
}
|
|
|
|
return &source
|
|
}
|
|
|
|
// SendFlowCompletedAsRedirectOrJSON should be used when a login, registration, ... flow has been completed successfully.
|
|
// It will redirect the user to the provided URL if the request accepts HTML, or return a JSON response if the request is
|
|
// an SPA request
|
|
func SendFlowCompletedAsRedirectOrJSON(
|
|
w http.ResponseWriter, r *http.Request, writer herodot.Writer, out interface{}, redirectTo string,
|
|
) {
|
|
sendFlowAsRedirectOrJSON(w, r, writer, out, redirectTo, http.StatusOK)
|
|
}
|
|
|
|
// SendFlowErrorAsRedirectOrJSON should be used when a login, registration, ... flow has errors (e.g. validation errors
|
|
// or missing data) and should be redirected to the provided URL if the request accepts HTML, or return a JSON response
|
|
// if the request is an SPA request.
|
|
func SendFlowErrorAsRedirectOrJSON(
|
|
w http.ResponseWriter, r *http.Request, writer herodot.Writer, out interface{}, redirectTo string,
|
|
) {
|
|
sendFlowAsRedirectOrJSON(w, r, writer, out, redirectTo, http.StatusBadRequest)
|
|
}
|
|
|
|
func sendFlowAsRedirectOrJSON(
|
|
w http.ResponseWriter, r *http.Request, writer herodot.Writer, out interface{}, redirectTo string, jsonResponseCode int,
|
|
) {
|
|
switch httputil.NegotiateContentType(r, []string{
|
|
"text/html",
|
|
"application/json",
|
|
}, "text/html") {
|
|
case "application/json":
|
|
if err, ok := out.(error); ok {
|
|
writer.WriteError(w, r, err)
|
|
return
|
|
}
|
|
|
|
writer.WriteCode(w, r, jsonResponseCode, out)
|
|
case "text/html":
|
|
fallthrough
|
|
default:
|
|
http.Redirect(w, r, redirectTo, http.StatusSeeOther)
|
|
}
|
|
}
|
|
|
|
func AcceptsJSON(r *http.Request) bool {
|
|
return httputil.NegotiateContentType(r, []string{
|
|
"text/html",
|
|
"application/json",
|
|
}, "text/html") == "application/json"
|
|
}
|
|
|
|
type HTTPClientProvider interface {
|
|
HTTPClient(context.Context, ...httpx.ResilientOptions) *retryablehttp.Client
|
|
}
|