Implement error pages, improve request logging

This commit is contained in:
Jan Dittberner 2021-01-02 18:06:03 +01:00
parent 0cf51b8ff1
commit e9c34a2337
19 changed files with 462 additions and 167 deletions

View file

@ -22,6 +22,10 @@ other = "Bitte gib ein Passwort ein."
hash = "sha1-31632fcec9d22a8463757f459e51c7c0eccd1f28" hash = "sha1-31632fcec9d22a8463757f459e51c7c0eccd1f28"
other = "Dieses Feld wird benötigt." other = "Dieses Feld wird benötigt."
[ErrorTitle]
hash = "sha1-736aec25a98f5ec5b71400bb0163f891f509b566"
other = "Es ist ein Fehler aufgetreten"
[ErrorUnknown] [ErrorUnknown]
hash = "sha1-e5fd9aa24c9417e7332e6f25936ae2a6ec8f1524" hash = "sha1-e5fd9aa24c9417e7332e6f25936ae2a6ec8f1524"
other = "Unbekannter Fehler" other = "Unbekannter Fehler"

View file

@ -4,6 +4,7 @@ ErrorEmail = "Please enter a valid email address."
ErrorEmailRequired = "Please enter an email address." ErrorEmailRequired = "Please enter an email address."
ErrorPasswordRequired = "Please enter a password." ErrorPasswordRequired = "Please enter a password."
ErrorRequired = "Please enter a value" ErrorRequired = "Please enter a value"
ErrorTitle = "An error has occurred"
ErrorUnknown = "Unknown error" ErrorUnknown = "Unknown error"
IndexGreeting = "Hello {{ .User }}" IndexGreeting = "Hello {{ .User }}"
IndexIntroductionText = "This is an authorization protected resource" IndexIntroductionText = "This is an authorization protected resource"

View file

@ -10,6 +10,7 @@ import (
"golang.org/x/oauth2" "golang.org/x/oauth2"
"git.cacert.org/oidc_login/app/services" "git.cacert.org/oidc_login/app/services"
"git.cacert.org/oidc_login/common/handlers"
commonServices "git.cacert.org/oidc_login/common/services" commonServices "git.cacert.org/oidc_login/common/services"
) )
@ -28,7 +29,7 @@ type oidcCallbackHandler struct {
func (c *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (c *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet { if r.Method != http.MethodGet {
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) w.WriteHeader(http.StatusMethodNotAllowed)
return return
} }
if r.URL.Path != "/callback" { if r.URL.Path != "/callback" {
@ -39,7 +40,13 @@ func (c *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
errorText := r.URL.Query().Get("error") errorText := r.URL.Query().Get("error")
errorDescription := r.URL.Query().Get("error_description") errorDescription := r.URL.Query().Get("error_description")
if errorText != "" { if errorText != "" {
c.RenderErrorTemplate(w, errorText, errorDescription, http.StatusForbidden) errorDetails := &handlers.ErrorDetails{
ErrorMessage: errorText,
}
if errorDescription != "" {
errorDetails.ErrorDetails = []string{errorDescription}
}
handlers.GetErrorBucket(r).AddError(errorDetails)
return return
} }
@ -106,14 +113,6 @@ Not valid after: %s
w.WriteHeader(http.StatusFound) w.WriteHeader(http.StatusFound)
} }
func (c *oidcCallbackHandler) RenderErrorTemplate(w http.ResponseWriter, errorText string, errorDescription string, status int) {
if errorDescription != "" {
http.Error(w, errorDescription, status)
} else {
http.Error(w, errorText, status)
}
}
func NewCallbackHandler(ctx context.Context, logger *log.Logger) *oidcCallbackHandler { func NewCallbackHandler(ctx context.Context, logger *log.Logger) *oidcCallbackHandler {
return &oidcCallbackHandler{ return &oidcCallbackHandler{
keySet: commonServices.GetJwkSet(ctx), keySet: commonServices.GetJwkSet(ctx),

View file

@ -6,20 +6,11 @@ import (
"encoding/base64" "encoding/base64"
"fmt" "fmt"
"net/http" "net/http"
"os"
"os/signal"
"strings"
"sync/atomic"
"time" "time"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/toml" "github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/confmap" "github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
"git.cacert.org/oidc_login/app/handlers" "git.cacert.org/oidc_login/app/handlers"
"git.cacert.org/oidc_login/app/services" "git.cacert.org/oidc_login/app/services"
@ -28,48 +19,21 @@ import (
) )
func main() { func main() {
f := flag.NewFlagSet("config", flag.ContinueOnError)
f.Usage = func() {
fmt.Println(f.FlagUsages())
os.Exit(0)
}
f.StringSlice("conf", []string{"resource_app.toml"}, "path to one or more .toml files")
logger := log.New() logger := log.New()
var err error config, err := commonServices.ConfigureApplication(
logger,
if err = f.Parse(os.Args[1:]); err != nil { "RESOURCE_APP",
logger.Fatal(err) map[string]interface{}{
} "server.port": 4000,
"server.name": "app.cacert.localhost",
config := koanf.New(".") "server.key": "certs/app.cacert.localhost.key",
"server.certificate": "certs/app.cacert.localhost.crt.pem",
_ = config.Load(confmap.Provider(map[string]interface{}{ "oidc.server": "https://auth.cacert.localhost:4444/",
"server.port": 4000, "session.path": "sessions/app",
"server.name": "app.cacert.localhost", "i18n.languages": []string{"en", "de"},
"server.key": "certs/app.cacert.localhost.key", })
"server.certificate": "certs/app.cacert.localhost.crt.pem", if err != nil {
"oidc.server": "https://auth.cacert.localhost:4444/", log.Fatalf("error loading configuration: %v", err)
"session.path": "sessions/app",
"i18n.languages": []string{"en", "de"},
}, "."), nil)
cFiles, _ := f.GetStringSlice("conf")
for _, c := range cFiles {
if err := config.Load(file.Provider(c), toml.Parser()); err != nil {
logger.Fatalf("error loading config file: %s", err)
}
}
if err := config.Load(posflag.Provider(f, ".", config), nil); err != nil {
logger.Fatalf("error loading configuration: %s", err)
}
if err := config.Load(file.Provider("resource_app.toml"), toml.Parser()); err != nil && !os.IsNotExist(err) {
log.Fatalf("error loading config: %v", err)
}
const prefix = "RESOURCE_APP_"
if err := config.Load(env.Provider(prefix, ".", func(s string) string {
return strings.Replace(strings.ToLower(
strings.TrimPrefix(s, prefix)), "_", ".", -1)
}), nil); err != nil {
log.Fatalf("error loading config: %v", err)
} }
oidcServer := config.MustString("oidc.server") oidcServer := config.MustString("oidc.server")
@ -151,6 +115,14 @@ func main() {
tracing := commonHandlers.Tracing(nextRequestId) tracing := commonHandlers.Tracing(nextRequestId)
logging := commonHandlers.Logging(logger) logging := commonHandlers.Logging(logger)
hsts := commonHandlers.EnableHSTS() hsts := commonHandlers.EnableHSTS()
errorMiddleware, err := commonHandlers.ErrorHandling(
ctx,
logger,
"templates/app",
)
if err != nil {
logger.Fatalf("could not initialize request error handling: %v", err)
}
tlsConfig := &tls.Config{ tlsConfig := &tls.Config{
ServerName: config.String("server.name"), ServerName: config.String("server.name"),
@ -158,40 +130,12 @@ func main() {
} }
server := &http.Server{ server := &http.Server{
Addr: serverAddr, Addr: serverAddr,
Handler: tracing(logging(hsts(router))), Handler: tracing(logging(hsts(errorMiddleware(router)))),
ReadTimeout: 5 * time.Second, ReadTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second, WriteTimeout: 10 * time.Second,
IdleTimeout: 15 * time.Second, IdleTimeout: 15 * time.Second,
TLSConfig: tlsConfig, TLSConfig: tlsConfig,
} }
done := make(chan bool) commonHandlers.StartApplication(logger, ctx, server, config)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
go func() {
<-quit
logger.Infoln("Server is shutting down...")
atomic.StoreInt32(&commonHandlers.Healthy, 0)
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)
}
close(done)
}()
logger.Infof("Server is ready to handle requests at https://%s/", server.Addr)
atomic.StoreInt32(&commonHandlers.Healthy, 1)
if err := server.ListenAndServeTLS(
config.String("server.certificate"), config.String("server.key"),
); err != nil && err != http.ErrServerClosed {
logger.Fatalf("Could not listen on %s: %v\n", server.Addr, err)
}
<-done
logger.Infoln("Server stopped")
} }

View file

@ -11,21 +11,13 @@ import (
"net/url" "net/url"
"os" "os"
"os/signal" "os/signal"
"strings"
"sync/atomic" "sync/atomic"
"time" "time"
"github.com/go-openapi/runtime/client" "github.com/go-openapi/runtime/client"
"github.com/gorilla/csrf" "github.com/gorilla/csrf"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
hydra "github.com/ory/hydra-client-go/client" hydra "github.com/ory/hydra-client-go/client"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
flag "github.com/spf13/pflag"
commonHandlers "git.cacert.org/oidc_login/common/handlers" commonHandlers "git.cacert.org/oidc_login/common/handlers"
commonServices "git.cacert.org/oidc_login/common/services" commonServices "git.cacert.org/oidc_login/common/services"
@ -34,45 +26,21 @@ import (
) )
func main() { func main() {
f := flag.NewFlagSet("config", flag.ContinueOnError)
f.Usage = func() {
fmt.Println(f.FlagUsages())
os.Exit(0)
}
f.StringSlice("conf", []string{"idp.toml"}, "path to one or more .toml files")
logger := log.New() logger := log.New()
var err error config, err := commonServices.ConfigureApplication(
logger,
if err = f.Parse(os.Args[1:]); err != nil { "IDP",
logger.Fatal(err) map[string]interface{}{
} "server.port": 3000,
"server.name": "login.cacert.localhost",
config := koanf.New(".") "server.key": "certs/idp.cacert.localhost.key",
"server.certificate": "certs/idp.cacert.localhost.crt.pem",
_ = config.Load(confmap.Provider(map[string]interface{}{ "security.client.ca-file": "certs/client_ca.pem",
"server.port": 3000, "admin.url": "https://hydra.cacert.localhost:4445/",
"server.name": "login.cacert.localhost", "i18n.languages": []string{"en", "de"},
"server.key": "certs/idp.cacert.localhost.key", })
"server.certificate": "certs/idp.cacert.localhost.crt.pem", if err != nil {
"security.client.ca-file": "certs/client_ca.pem", log.Fatalf("error loading configuration: %v", err)
"admin.url": "https://hydra.cacert.localhost:4445/",
"i18n.languages": []string{"en", "de"},
}, "."), nil)
cFiles, _ := f.GetStringSlice("conf")
for _, c := range cFiles {
if err := config.Load(file.Provider(c), toml.Parser()); err != nil {
logger.Fatalf("error loading config file: %s", err)
}
}
if err := config.Load(posflag.Provider(f, ".", config), nil); err != nil {
logger.Fatalf("error loading configuration: %s", err)
}
const prefix = "IDP_"
if err := config.Load(env.Provider(prefix, ".", func(s string) string {
return strings.Replace(strings.ToLower(
strings.TrimPrefix(s, prefix)), "_", ".", -1)
}), nil); err != nil {
log.Fatalf("error loading config: %v", err)
} }
logger.Infoln("Server is starting") logger.Infoln("Server is starting")
@ -139,6 +107,14 @@ func main() {
csrf.Secure(true), csrf.Secure(true),
csrf.SameSite(csrf.SameSiteStrictMode), csrf.SameSite(csrf.SameSiteStrictMode),
csrf.MaxAge(600)) csrf.MaxAge(600))
errorMiddleware, err := commonHandlers.ErrorHandling(
ctx,
logger,
"templates/idp",
)
if err != nil {
logger.Fatalf("could not initialize request error handling: %v", err)
}
clientCertPool := x509.NewCertPool() clientCertPool := x509.NewCertPool()
pemBytes, err := ioutil.ReadFile(config.MustString("security.client.ca-file")) pemBytes, err := ioutil.ReadFile(config.MustString("security.client.ca-file"))
@ -155,7 +131,7 @@ func main() {
} }
server := &http.Server{ server := &http.Server{
Addr: fmt.Sprintf("%s:%d", config.String("server.name"), config.Int("server.port")), Addr: fmt.Sprintf("%s:%d", config.String("server.name"), config.Int("server.port")),
Handler: tracing(logging(hsts(csrfProtect(router)))), Handler: tracing(logging(hsts(errorMiddleware(csrfProtect(router))))),
ReadTimeout: 20 * time.Second, ReadTimeout: 20 * time.Second,
WriteTimeout: 20 * time.Second, WriteTimeout: 20 * time.Second,
IdleTimeout: 30 * time.Second, IdleTimeout: 30 * time.Second,

137
common/handlers/errors.go Normal file
View file

@ -0,0 +1,137 @@
package handlers
import (
"context"
"html/template"
"net/http"
"path"
"github.com/nicksnyder/go-i18n/v2/i18n"
log "github.com/sirupsen/logrus"
commonServices "git.cacert.org/oidc_login/common/services"
)
type errorKey int
const (
errorBucketKey errorKey = iota
)
type ErrorDetails struct {
ErrorMessage string
ErrorDetails []string
ErrorCode string
Error error
}
type ErrorBucket struct {
errorDetails *ErrorDetails
templates *template.Template
logger *log.Logger
bundle *i18n.Bundle
messageCatalog *commonServices.MessageCatalog
}
func (b *ErrorBucket) serveHTTP(w http.ResponseWriter, r *http.Request) {
if b.errorDetails != nil {
accept := r.Header.Get("Accept-Language")
localizer := i18n.NewLocalizer(b.bundle, accept)
err := b.templates.Lookup("base").Execute(w, map[string]interface{}{
"Title": b.messageCatalog.LookupMessage(
"ErrorTitle",
nil,
localizer,
),
"details": b.errorDetails,
})
if err != nil {
log.Errorf("error rendering error template: %v", err)
http.Error(
w,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError,
)
}
}
}
func GetErrorBucket(r *http.Request) *ErrorBucket {
return r.Context().Value(errorBucketKey).(*ErrorBucket)
}
// call this from your application's handler
func (b *ErrorBucket) AddError(details *ErrorDetails) {
b.errorDetails = details
}
type errorResponseWriter struct {
http.ResponseWriter
ctx context.Context
statusCode int
}
func (w *errorResponseWriter) WriteHeader(code int) {
w.statusCode = code
if code >= 400 {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
errorBucket := w.ctx.Value(errorBucketKey).(*ErrorBucket)
if errorBucket != nil && errorBucket.errorDetails == nil {
errorBucket.AddError(&ErrorDetails{
ErrorMessage: http.StatusText(code),
})
}
}
w.ResponseWriter.WriteHeader(code)
}
func (w *errorResponseWriter) Write(content []byte) (int, error) {
if w.statusCode > 400 {
errorBucket := w.ctx.Value(errorBucketKey).(*ErrorBucket)
if errorBucket != nil {
if errorBucket.errorDetails.ErrorDetails == nil {
errorBucket.errorDetails.ErrorDetails = make([]string, 0)
}
errorBucket.errorDetails.ErrorDetails = append(
errorBucket.errorDetails.ErrorDetails, string(content),
)
return len(content), nil
}
}
return w.ResponseWriter.Write(content)
}
func ErrorHandling(
handlerContext context.Context,
logger *log.Logger,
templateBaseDir string,
) (func(http.Handler) http.Handler, error) {
errorTemplates, err := template.ParseFiles(
path.Join(templateBaseDir, "base.gohtml"),
path.Join(templateBaseDir, "errors.gohtml"),
)
if err != nil {
return nil, err
}
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
errorBucket := &ErrorBucket{
templates: errorTemplates,
logger: logger,
bundle: commonServices.GetI18nBundle(handlerContext),
messageCatalog: commonServices.GetMessageCatalog(handlerContext),
}
ctx := context.WithValue(r.Context(), errorBucketKey, errorBucket)
interCeptingResponseWriter := &errorResponseWriter{
w,
ctx,
http.StatusOK,
}
next.ServeHTTP(
interCeptingResponseWriter,
r.WithContext(ctx),
)
errorBucket.serveHTTP(w, r)
})
}, nil
}

View file

@ -14,17 +14,44 @@ const (
requestIdKey key = iota requestIdKey key = iota
) )
type statusCodeInterceptor struct {
http.ResponseWriter
code int
count int
}
func (sci *statusCodeInterceptor) WriteHeader(code int) {
sci.code = code
sci.ResponseWriter.WriteHeader(code)
}
func (sci *statusCodeInterceptor) Write(content []byte) (int, error) {
count, err := sci.ResponseWriter.Write(content)
sci.count += count
return count, err
}
func Logging(logger *log.Logger) func(http.Handler) http.Handler { func Logging(logger *log.Logger) func(http.Handler) http.Handler {
return func(next http.Handler) http.Handler { return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
interceptor := &statusCodeInterceptor{w, http.StatusOK, 0}
defer func() { defer func() {
requestId, ok := r.Context().Value(requestIdKey).(string) requestId, ok := r.Context().Value(requestIdKey).(string)
if !ok { if !ok {
requestId = "unknown" requestId = "unknown"
} }
logger.Infoln(requestId, r.Method, r.URL.Path, r.RemoteAddr, r.UserAgent()) logger.Infof(
"%s %s \"%s %s\" %d %d \"%s\"",
requestId,
r.RemoteAddr,
r.Method,
r.URL.Path,
interceptor.code,
interceptor.count,
r.UserAgent(),
)
}() }()
next.ServeHTTP(w, r) next.ServeHTTP(interceptor, r)
}) })
} }
} }

View file

@ -0,0 +1,45 @@
package handlers
import (
"context"
"net/http"
"os"
"os/signal"
"sync/atomic"
"time"
"github.com/knadh/koanf"
"github.com/sirupsen/logrus"
)
func StartApplication(logger *logrus.Logger, ctx context.Context, server *http.Server, config *koanf.Koanf) {
done := make(chan bool)
quit := make(chan os.Signal, 1)
signal.Notify(quit, os.Interrupt)
go func() {
<-quit
logger.Infoln("Server is shutting down...")
atomic.StoreInt32(&Healthy, 0)
ctx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
server.SetKeepAlivesEnabled(false)
if err := server.Shutdown(ctx); err != nil {
logger.Fatalf("Could not gracefully shutdown the server: %v\n", err)
}
close(done)
}()
logger.Infof("Server is ready to handle requests at https://%s/", server.Addr)
atomic.StoreInt32(&Healthy, 1)
if err := server.ListenAndServeTLS(
config.String("server.certificate"), config.String("server.key"),
); err != nil && err != http.ErrServerClosed {
logger.Fatalf("Could not listen on %s: %v\n", server.Addr, err)
}
<-done
logger.Infoln("Server stopped")
}

View file

@ -0,0 +1,65 @@
package services
import (
"fmt"
"os"
"strings"
"github.com/knadh/koanf"
"github.com/knadh/koanf/parsers/toml"
"github.com/knadh/koanf/providers/confmap"
"github.com/knadh/koanf/providers/env"
"github.com/knadh/koanf/providers/file"
"github.com/knadh/koanf/providers/posflag"
"github.com/sirupsen/logrus"
"github.com/spf13/pflag"
)
func ConfigureApplication(
logger *logrus.Logger,
appName string,
defaultConfig map[string]interface{},
) (*koanf.Koanf, error) {
f := pflag.NewFlagSet("config", pflag.ContinueOnError)
f.Usage = func() {
fmt.Println(f.FlagUsages())
os.Exit(0)
}
f.StringSlice(
"conf",
[]string{fmt.Sprintf("%s.toml", strings.ToLower(appName))},
"path to one or more .toml files",
)
var err error
if err = f.Parse(os.Args[1:]); err != nil {
logger.Fatal(err)
}
config := koanf.New(".")
_ = config.Load(confmap.Provider(defaultConfig, "."), nil)
cFiles, _ := f.GetStringSlice("conf")
for _, c := range cFiles {
if err := config.Load(file.Provider(c), toml.Parser()); err != nil {
logger.Fatalf("error loading config file: %s", err)
}
}
if err := config.Load(posflag.Provider(f, ".", config), nil); err != nil {
logger.Fatalf("error loading configuration: %s", err)
}
if err := config.Load(
file.Provider("resource_app.toml"),
toml.Parser(),
); err != nil && !os.IsNotExist(err) {
logrus.Fatalf("error loading config: %v", err)
}
prefix := fmt.Sprintf("%s_", strings.ToUpper(appName))
if err := config.Load(env.Provider(prefix, ".", func(s string) string {
return strings.Replace(strings.ToLower(
strings.TrimPrefix(s, prefix)), "_", ".", -1)
}), nil); err != nil {
logrus.Fatalf("error loading config: %v", err)
}
return config, err
}

View file

@ -91,6 +91,10 @@ func InitI18n(ctx context.Context, logger *log.Logger, languages []string) conte
func initMessageCatalog(logger *log.Logger) *MessageCatalog { func initMessageCatalog(logger *log.Logger) *MessageCatalog {
messages := make(map[string]*i18n.Message) messages := make(map[string]*i18n.Message)
messages["ErrorTitle"] = &i18n.Message{
ID: "ErrorTitle",
Other: "An error has occurred",
}
return &MessageCatalog{messages: messages, logger: logger} return &MessageCatalog{messages: messages, logger: logger}
} }

View file

@ -11,8 +11,9 @@ body {
body.idp { body.idp {
display: -ms-flexbox; display: -ms-flexbox;
display: flex; display: flex;
// -ms-flex-align: center; }
// align-items: center;
.error-message, body.idp {
padding-top: 40px; padding-top: 40px;
padding-bottom: 40px; padding-bottom: 40px;
} }

View file

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"html/template" "html/template"
"net/http" "net/http"
"strconv"
"strings" "strings"
"time" "time"
@ -18,6 +19,7 @@ import (
"github.com/ory/hydra-client-go/models" "github.com/ory/hydra-client-go/models"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"git.cacert.org/oidc_login/common/handlers"
commonServices "git.cacert.org/oidc_login/common/services" commonServices "git.cacert.org/oidc_login/common/services"
"git.cacert.org/oidc_login/idp/services" "git.cacert.org/oidc_login/idp/services"
) )
@ -70,8 +72,25 @@ func (h *consentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
consentData, err := h.adminClient.GetConsentRequest( consentData, err := h.adminClient.GetConsentRequest(
admin.NewGetConsentRequestParams().WithConsentChallenge(challenge)) admin.NewGetConsentRequestParams().WithConsentChallenge(challenge))
if err != nil { if err != nil {
h.logger.Error("error getting consent information: %v", err) h.logger.Errorf("error getting consent information: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError) var errorDetails *handlers.ErrorDetails
switch v := err.(type) {
case *admin.GetConsentRequestConflict:
errorDetails = &handlers.ErrorDetails{
ErrorMessage: *v.Payload.Error,
ErrorDetails: []string{v.Payload.ErrorDescription},
}
if v.Payload.StatusCode != 0 {
errorDetails.ErrorCode = strconv.Itoa(int(v.Payload.StatusCode))
}
break
default:
errorDetails = &handlers.ErrorDetails{
ErrorMessage: "could not get consent details",
ErrorDetails: []string{http.StatusText(http.StatusInternalServerError)},
}
}
handlers.GetErrorBucket(r).AddError(errorDetails)
return return
} }
@ -86,7 +105,11 @@ func (h *consentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
decoder := form.NewDecoder() decoder := form.NewDecoder()
if err := decoder.Decode(&consentInfo, r.Form); err != nil { if err := decoder.Decode(&consentInfo, r.Form); err != nil {
h.logger.Error(err) h.logger.Error(err)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(
w,
http.StatusText(http.StatusInternalServerError),
http.StatusInternalServerError,
)
return return
} }
@ -99,8 +122,8 @@ func (h *consentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.Context(), r.Context(),
`SELECT email, verified, fname, mname, lname, dob, language, modified `SELECT email, verified, fname, mname, lname, dob, language, modified
FROM users FROM users
WHERE uniqueID = ? WHERE uniqueid = ?
AND LOCKED = 0`, AND locked = 0`,
) )
if err != nil { if err != nil {
h.logger.Errorf("error preparing user information SQL: %v", err) h.logger.Errorf("error preparing user information SQL: %v", err)
@ -181,7 +204,13 @@ WHERE uniqueID = ?
} }
} }
func (h *consentHandler) renderConsentForm(w http.ResponseWriter, r *http.Request, consentData *admin.GetConsentRequestOK, err error, localizer *i18n.Localizer) { func (h *consentHandler) renderConsentForm(
w http.ResponseWriter,
r *http.Request,
consentData *admin.GetConsentRequestOK,
err error,
localizer *i18n.Localizer,
) {
trans := func(id string, values ...map[string]interface{}) string { trans := func(id string, values ...map[string]interface{}) string {
if len(values) > 0 { if len(values) > 0 {
return h.messageCatalog.LookupMessage(id, values[0], localizer) return h.messageCatalog.LookupMessage(id, values[0], localizer)

View file

@ -7,9 +7,9 @@ import (
"encoding/hex" "encoding/hex"
"html/template" "html/template"
"net/http" "net/http"
"strconv"
"time" "time"
"github.com/go-openapi/runtime"
"github.com/go-playground/form/v4" "github.com/go-playground/form/v4"
"github.com/go-playground/validator/v10" "github.com/go-playground/validator/v10"
"github.com/gorilla/csrf" "github.com/gorilla/csrf"
@ -19,6 +19,7 @@ import (
"github.com/ory/hydra-client-go/models" "github.com/ory/hydra-client-go/models"
log "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus"
"git.cacert.org/oidc_login/common/handlers"
commonServices "git.cacert.org/oidc_login/common/services" commonServices "git.cacert.org/oidc_login/common/services"
"git.cacert.org/oidc_login/idp/services" "git.cacert.org/oidc_login/idp/services"
) )
@ -143,15 +144,33 @@ func (h *loginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// finish login and redirect to target // finish login and redirect to target
loginRequest, err := h.adminClient.AcceptLoginRequest( loginRequest, err := h.adminClient.AcceptLoginRequest(
admin.NewAcceptLoginRequestParams().WithLoginChallenge(challenge).WithBody(&models.AcceptLoginRequest{ admin.NewAcceptLoginRequestParams().WithLoginChallenge(challenge).WithBody(
Acr: string(authMethod), &models.AcceptLoginRequest{
Remember: true, Acr: string(authMethod),
RememberFor: 0, Remember: true,
Subject: userId, RememberFor: 0,
}).WithTimeout(time.Second * 10)) Subject: userId,
}).WithTimeout(time.Second * 10))
if err != nil { if err != nil {
h.logger.Errorf("error getting login request: %#v", err) h.logger.Errorf("error getting login request: %#v", err)
http.Error(w, err.Error(), err.(*runtime.APIError).Code) var errorDetails *handlers.ErrorDetails
switch v := err.(type) {
case *admin.AcceptLoginRequestNotFound:
errorDetails = &handlers.ErrorDetails{
ErrorMessage: *v.Payload.Error,
ErrorDetails: []string{v.Payload.ErrorDescription},
}
if v.Payload.StatusCode != 0 {
errorDetails.ErrorCode = strconv.Itoa(int(v.Payload.StatusCode))
}
break
default:
errorDetails = &handlers.ErrorDetails{
ErrorMessage: "could not accept login",
ErrorDetails: []string{err.Error()},
}
}
handlers.GetErrorBucket(r).AddError(errorDetails)
return return
} }
w.Header().Add("Location", *loginRequest.GetPayload().RedirectTo) w.Header().Add("Location", *loginRequest.GetPayload().RedirectTo)
@ -255,7 +274,7 @@ func (h *loginHandler) performCertificateLogin(emails []string, r *http.Request)
db := services.GetDb(h.context) db := services.GetDb(h.context)
query, args, err := sqlx.In( query, args, err := sqlx.In(
`SELECT DISTINCT u.uniqueID `SELECT DISTINCT u.uniqueid
FROM users u FROM users u
JOIN email e ON e.memid = u.id JOIN email e ON e.memid = u.id
WHERE e.email IN (?) WHERE e.email IN (?)

View file

@ -12,6 +12,7 @@
<link rel="icon" href="/images/favicon-128.png" sizes="128x128"> <link rel="icon" href="/images/favicon-128.png" sizes="128x128">
<link rel="icon" href="/images/favicon-192.png" sizes="192x192"> <link rel="icon" href="/images/favicon-192.png" sizes="192x192">
<link rel="icon" href="/images/favicon-228.png" sizes="228x228"> <link rel="icon" href="/images/favicon-228.png" sizes="228x228">
<link rel="icon" href="/images/favicon.ico">
<!-- Android --> <!-- Android -->
<link rel="shortcut icon" sizes="196x196" href="/images/favicon-196.png"> <link rel="shortcut icon" sizes="196x196" href="/images/favicon-196.png">
@ -22,10 +23,18 @@
<link rel="apple-touch-icon" href="/images/favicon-180.png" sizes="180x180"> <link rel="apple-touch-icon" href="/images/favicon-180.png" sizes="180x180">
<link rel="stylesheet" href="/css/cacert.bundle.css"> <link rel="stylesheet" href="/css/cacert.bundle.css">
<meta name="theme-color" content="#11568c">
<title>{{ .Title }}</title> <title>{{ .Title }}</title>
</head> </head>
<body> <body class="resource-app d-flex flex-column h-100">
{{ template "content" . }} <main role="main" class="flex-shrink-0">
{{ template "content" . }}
</main>
<footer class="footer mt-auto py-3">
<div class="container">
<span class="text-muted small">© 2020 <a href="https://www.cacert.org/">CAcert</a></span>
</div>
</footer>
<script type="text/javascript" src="/js/cacert.bundle.js"></script> <script type="text/javascript" src="/js/cacert.bundle.js"></script>
</body> </body>
</html> </html>

View file

@ -0,0 +1,13 @@
{{ define "content" }}
<div class="container text-center error-message">
<img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
<h1>{{ .Title }}</h1>
<h2>{{ if .details.ErrorCode }}
<strong>{{ .details.ErrorCode }}</strong> {{ end }}{{ .details.ErrorMessage }}</h2>
{{ if .details.ErrorDetails }}
{{ range .details.ErrorDetails }}
<p>{{ . }}</p>
{{ end }}
{{ end }}
</div>
{{ end }}

View file

@ -1,5 +1,8 @@
{{ define "content" }} {{ define "content" }}
<h1>{{ .Greeting }}</h1> <div class="container">
<p>{{ .IntroductionText }}</p> <img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
<a href="{{ .LogoutURL }}">{{ .LogoutLabel }}</a> <h1>{{ .Greeting }}</h1>
<p>{{ .IntroductionText }}</p>
<a class="btn btn-outline-primary" href="{{ .LogoutURL }}">{{ .LogoutLabel }}</a>
</div>
{{ end }} {{ end }}

View file

@ -26,8 +26,15 @@
<meta name="theme-color" content="#11568c"> <meta name="theme-color" content="#11568c">
<title>{{ .Title }}</title> <title>{{ .Title }}</title>
</head> </head>
<body class="text-center idp"> <body class="text-center idp d-flex flex-column h-100">
{{ template "content" . }} <main role="main" class="flex-shrink-0">
{{ template "content" . }}
</main>
<footer class="footer mt-auto py-3">
<div class="container">
<span class="text-muted small">© 2020 <a href="https://www.cacert.org/">CAcert</a></span>
</div>
</footer>
<script type="text/javascript" src="/js/cacert.bundle.js"></script> <script type="text/javascript" src="/js/cacert.bundle.js"></script>
</body> </body>
</html> </html>

View file

@ -0,0 +1,13 @@
{{ define "content" }}
<div class="container">
<img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
<h1>{{ .Title }}</h1>
<h2>{{ if .details.ErrorCode }}
<strong>{{ .details.ErrorCode }}</strong> {{ end }}{{ .details.ErrorMessage }}</h2>
{{ if .details.ErrorDetails }}
{{ range .details.ErrorDetails }}
<p>{{ . }}</p>
{{ end }}
{{ end }}
</div>
{{ end }}

View file

@ -1,4 +1,3 @@
[LabelAcceptCertLogin] [ErrorTitle]
description = "Label for a button to accept certificate login" hash = "sha1-736aec25a98f5ec5b71400bb0163f891f509b566"
hash = "sha1-95cf27f4bdee62b51ee8bc673d25a46bcceed452" other = "Es ist ein Fehler aufgetreten"
other = "Ja, bitte nutze das Zertifikat"