Compare commits
No commits in common. "main" and "0.2.0" have entirely different histories.
32 changed files with 382 additions and 909 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -5,7 +5,6 @@
|
||||||
/resource_app*.toml
|
/resource_app*.toml
|
||||||
/sessions
|
/sessions
|
||||||
/static
|
/static
|
||||||
/translations/translate.*.toml
|
|
||||||
/ui/css/
|
/ui/css/
|
||||||
/ui/images/
|
/ui/images/
|
||||||
/ui/js/
|
/ui/js/
|
||||||
|
|
|
@ -8,7 +8,7 @@ linters-settings:
|
||||||
const:
|
const:
|
||||||
ORGANIZATION: CAcert Inc.
|
ORGANIZATION: CAcert Inc.
|
||||||
template: |-
|
template: |-
|
||||||
Copyright {{ ORGANIZATION }}
|
Copyright {{ YEAR-RANGE }} {{ ORGANIZATION }}
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -47,7 +47,7 @@ linters:
|
||||||
- gofmt
|
- gofmt
|
||||||
- goheader
|
- goheader
|
||||||
- goimports
|
- goimports
|
||||||
- mnd
|
- gomnd
|
||||||
- gosec
|
- gosec
|
||||||
- lll
|
- lll
|
||||||
- makezero
|
- makezero
|
||||||
|
|
11
Makefile
11
Makefile
|
@ -18,12 +18,13 @@ go.sum: go.mod
|
||||||
go mod tidy -v
|
go mod tidy -v
|
||||||
|
|
||||||
translations: $(TRANSLATIONS) $(GOFILES)
|
translations: $(TRANSLATIONS) $(GOFILES)
|
||||||
if [ ! -z "$(wildcard translations/translate.*.toml)" ]; then \
|
cd translations ; \
|
||||||
|
goi18n extract .. ; \
|
||||||
|
goi18n merge active.*.toml ; \
|
||||||
|
if translate.*.toml 2>/dev/null; then \
|
||||||
echo "missing translations"; \
|
echo "missing translations"; \
|
||||||
goi18n merge -outdir translations translations/active.*.toml translations/translate.*.toml; \
|
goi18n merge active.*.toml translate.*.toml; \
|
||||||
fi ; \
|
fi
|
||||||
goi18n extract -outdir translations . ; \
|
|
||||||
goi18n merge -outdir translations translations/active.*.toml
|
|
||||||
|
|
||||||
lint: $(GOFILES)
|
lint: $(GOFILES)
|
||||||
golangci-lint run --verbose
|
golangci-lint run --verbose
|
||||||
|
|
10
changelog.md
10
changelog.md
|
@ -5,16 +5,6 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [0.3.0] - 2023-08-07
|
|
||||||
### Changed
|
|
||||||
- let the session expire when the token expires
|
|
||||||
- link from logo to start page
|
|
||||||
- move common page header to templates/base.gohtml
|
|
||||||
- implement caching for static resources
|
|
||||||
### Added
|
|
||||||
- add identity output to index page
|
|
||||||
- add a separate protected resource page
|
|
||||||
|
|
||||||
## [0.2.0]
|
## [0.2.0]
|
||||||
### Changed
|
### Changed
|
||||||
- re-order configuration precedence
|
- re-order configuration precedence
|
||||||
|
|
242
cmd/app/main.go
242
cmd/app/main.go
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -23,7 +23,6 @@ import (
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
@ -31,6 +30,7 @@ import (
|
||||||
"github.com/knadh/koanf"
|
"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"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
"code.cacert.org/cacert/oidc-demo-app/ui"
|
"code.cacert.org/cacert/oidc-demo-app/ui"
|
||||||
|
|
||||||
|
@ -53,89 +53,59 @@ var (
|
||||||
date = "unknown"
|
date = "unknown"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StaticFSWrapper struct {
|
func main() {
|
||||||
http.FileSystem
|
logger := log.New()
|
||||||
ModTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *StaticFSWrapper) Open(name string) (http.File, error) {
|
config, err := services.ConfigureApplication(
|
||||||
file, err := f.FileSystem.Open(name)
|
logger,
|
||||||
|
"RESOURCE_APP",
|
||||||
return &StaticFileWrapper{File: file, fixedModTime: f.ModTime}, err //nolint:wrapcheck
|
services.DefaultConfiguration,
|
||||||
}
|
|
||||||
|
|
||||||
type StaticFileWrapper struct {
|
|
||||||
http.File
|
|
||||||
fixedModTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *StaticFileWrapper) Stat() (os.FileInfo, error) {
|
|
||||||
fileInfo, err := f.File.Stat()
|
|
||||||
|
|
||||||
return &StaticFileInfoWrapper{FileInfo: fileInfo, fixedModTime: f.fixedModTime}, err //nolint:wrapcheck
|
|
||||||
}
|
|
||||||
|
|
||||||
type StaticFileInfoWrapper struct {
|
|
||||||
os.FileInfo
|
|
||||||
fixedModTime time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *StaticFileInfoWrapper) ModTime() time.Time {
|
|
||||||
return f.fixedModTime
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() { //nolint:cyclop
|
|
||||||
var (
|
|
||||||
logLevel = new(slog.LevelVar)
|
|
||||||
logHandler slog.Handler
|
|
||||||
logger *slog.Logger
|
|
||||||
)
|
)
|
||||||
|
|
||||||
logHandler = slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})
|
|
||||||
logger = slog.New(logHandler)
|
|
||||||
slog.SetDefault(logger)
|
|
||||||
|
|
||||||
config, err := services.ConfigureApplication("RESOURCE_APP", services.DefaultConfiguration)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("error loading configuration", "error", err)
|
log.Fatalf("error loading configuration: %v", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if level := config.Bytes("log.level"); level != nil {
|
|
||||||
if err := logLevel.UnmarshalText(level); err != nil {
|
|
||||||
logger.Error("could not parse log level", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
slog.SetLogLoggerLevel(logLevel.Level())
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Bool("log.json") {
|
|
||||||
logHandler = slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: logLevel})
|
|
||||||
logger = slog.New(logHandler)
|
|
||||||
slog.SetDefault(logger)
|
|
||||||
}
|
|
||||||
|
|
||||||
logLogger := slog.NewLogLogger(logger.Handler(), logLevel.Level())
|
|
||||||
|
|
||||||
oidcServer := config.MustString("oidc.server")
|
oidcServer := config.MustString("oidc.server")
|
||||||
oidcClientID := config.MustString("oidc.client-id")
|
oidcClientID := config.MustString("oidc.client-id")
|
||||||
oidcClientSecret := config.MustString("oidc.client-secret")
|
oidcClientSecret := config.MustString("oidc.client-secret")
|
||||||
|
|
||||||
logger.Info(
|
if level := config.String("log.level"); level != "" {
|
||||||
"Starting CAcert OpenID Connect demo application",
|
logLevel, err := log.ParseLevel(level)
|
||||||
"version", version, "commit", commit, "date", date,
|
if err != nil {
|
||||||
)
|
logger.WithError(err).Fatal("could not parse log level")
|
||||||
logger.Info("Server is starting")
|
}
|
||||||
|
|
||||||
|
logger.SetLevel(logLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Bool("log.json") {
|
||||||
|
logger.SetFormatter(&log.JSONFormatter{})
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.WithFields(log.Fields{
|
||||||
|
"version": version, "commit": commit, "date": date,
|
||||||
|
}).Info("Starting CAcert OpenID Connect demo application")
|
||||||
|
logger.Infoln("Server is starting")
|
||||||
|
|
||||||
bundle, catalog := services.InitI18n(logger, config.Strings("i18n.languages"))
|
bundle, catalog := services.InitI18n(logger, config.Strings("i18n.languages"))
|
||||||
|
|
||||||
services.AddMessages(catalog)
|
services.AddMessages(catalog)
|
||||||
|
|
||||||
tlsClientConfig, err := getTLSConfig(config)
|
tlsClientConfig := &tls.Config{
|
||||||
if err != nil {
|
MinVersion: tls.VersionTLS12,
|
||||||
logger.Error("error loading tls config", "error", err)
|
}
|
||||||
os.Exit(1)
|
|
||||||
|
if config.Exists("api-client.rootCAs") {
|
||||||
|
rootCAFile := config.MustString("api-client.rootCAs")
|
||||||
|
caCertPool := x509.NewCertPool()
|
||||||
|
|
||||||
|
pemBytes, err := os.ReadFile(rootCAFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not read CA certificate file: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
caCertPool.AppendCertsFromPEM(pemBytes)
|
||||||
|
tlsClientConfig.RootCAs = caCertPool
|
||||||
}
|
}
|
||||||
|
|
||||||
apiTransport := &http.Transport{TLSClientConfig: tlsClientConfig}
|
apiTransport := &http.Transport{TLSClientConfig: tlsClientConfig}
|
||||||
|
@ -148,77 +118,45 @@ func main() { //nolint:cyclop
|
||||||
APIClient: apiClient,
|
APIClient: apiClient,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("OpenID Connect discovery failed", "error", err)
|
log.Fatalf("OpenID Connect discovery failed: %s", err)
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionPath, sessionAuthKey, sessionEncKey, err := configureSessionParameters(logger, config)
|
sessionPath, sessionAuthKey, sessionEncKey := configureSessionParameters(config)
|
||||||
if err != nil {
|
services.InitSessionStore(logger, sessionPath, sessionAuthKey, sessionEncKey)
|
||||||
logger.Error("error configuring session parameters", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := services.InitSessionStore(sessionPath, sessionAuthKey, sessionEncKey); err != nil {
|
authMiddleware := handlers.Authenticate(logger, oidcInfo.OAuth2Config, oidcClientID)
|
||||||
logger.Error("could not initialize session store", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
authMiddleware := handlers.Authenticate(logger, oidcInfo.OAuth2Config)
|
|
||||||
|
|
||||||
publicURL := buildPublicURL(config.MustString("server.name"), config.MustInt("server.port"))
|
publicURL := buildPublicURL(config.MustString("server.name"), config.MustInt("server.port"))
|
||||||
|
|
||||||
tokenInfoService, err := services.InitTokenInfoService(logger, oidcInfo)
|
indexHandler, err := handlers.NewIndexHandler(bundle, catalog, ui.Templates, oidcInfo, publicURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("could not initialize token info service", "error", err)
|
logger.WithError(err).Fatal("could not initialize index handler")
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
indexHandler, err := handlers.NewIndexHandler(logger, bundle, catalog, oidcInfo, publicURL, tokenInfoService)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("could not initialize index handler", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
protectedResource, err := handlers.NewProtectedResourceHandler(
|
|
||||||
logger, bundle, catalog, oidcInfo, publicURL, tokenInfoService,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("could not initialize protected resource handler", "error", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
callbackHandler := handlers.NewCallbackHandler(logger, oidcInfo.KeySet, oidcInfo.OAuth2Config)
|
callbackHandler := handlers.NewCallbackHandler(logger, oidcInfo.KeySet, oidcInfo.OAuth2Config)
|
||||||
afterLogoutHandler := handlers.NewAfterLogoutHandler(logger)
|
afterLogoutHandler := handlers.NewAfterLogoutHandler(logger)
|
||||||
|
staticFiles := http.FileServer(http.FS(ui.Static))
|
||||||
staticFiles, err := staticFileHandler()
|
|
||||||
if err != nil {
|
|
||||||
logger.Error("could not initialize static file handler", "error", err)
|
|
||||||
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
router := http.NewServeMux()
|
router := http.NewServeMux()
|
||||||
router.Handle("/", indexHandler)
|
router.Handle("/", authMiddleware(indexHandler))
|
||||||
router.Handle("/login", authMiddleware(handlers.NewLoginHandler()))
|
|
||||||
router.Handle("/protected", authMiddleware(protectedResource))
|
|
||||||
router.Handle("/callback", callbackHandler)
|
router.Handle("/callback", callbackHandler)
|
||||||
router.Handle("/after-logout", afterLogoutHandler)
|
router.Handle("/after-logout", afterLogoutHandler)
|
||||||
router.Handle("/health", handlers.NewHealthHandler())
|
router.Handle("/health", handlers.NewHealthHandler())
|
||||||
router.HandleFunc("/images/", staticFiles)
|
router.Handle("/images/", staticFiles)
|
||||||
router.HandleFunc("/css/", staticFiles)
|
router.Handle("/css/", staticFiles)
|
||||||
router.HandleFunc("/js/", staticFiles)
|
router.Handle("/js/", staticFiles)
|
||||||
|
|
||||||
nextRequestID := func() string {
|
nextRequestID := func() string {
|
||||||
return fmt.Sprintf("%d", time.Now().UnixNano())
|
return fmt.Sprintf("%d", time.Now().UnixNano())
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing := handlers.Tracing(nextRequestID)
|
tracing := handlers.Tracing(nextRequestID)
|
||||||
logging := handlers.Logging(logLogger)
|
logging := handlers.Logging(logger)
|
||||||
hsts := handlers.EnableHSTS()
|
hsts := handlers.EnableHSTS()
|
||||||
|
|
||||||
errorMiddleware, err := handlers.ErrorHandling(logger, bundle, catalog)
|
errorMiddleware, err := handlers.ErrorHandling(logger, ui.Templates, bundle, catalog)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("could not initialize request error handling", "error", err)
|
logger.WithError(err).Fatal("could not initialize request error handling")
|
||||||
os.Exit(1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
|
@ -235,50 +173,7 @@ func main() { //nolint:cyclop
|
||||||
TLSConfig: tlsConfig,
|
TLSConfig: tlsConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := handlers.StartApplication(context.Background(), logger, server, publicURL, config); err != nil {
|
handlers.StartApplication(context.Background(), logger, server, publicURL, config)
|
||||||
logger.Error("could not start application", "error", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func staticFileHandler() (func(w http.ResponseWriter, r *http.Request), error) {
|
|
||||||
stat, err := os.Stat(os.Args[0])
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not use stat on binary: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileServer := http.FileServer(&StaticFSWrapper{FileSystem: http.FS(ui.Static), ModTime: stat.ModTime()})
|
|
||||||
|
|
||||||
staticFiles := func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.Header().Del("Expires")
|
|
||||||
w.Header().Del("Pragma")
|
|
||||||
w.Header().Set("Cache-Control", "max-age=3600")
|
|
||||||
|
|
||||||
fileServer.ServeHTTP(w, r)
|
|
||||||
}
|
|
||||||
|
|
||||||
return staticFiles, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getTLSConfig(config *koanf.Koanf) (*tls.Config, error) {
|
|
||||||
tlsClientConfig := &tls.Config{
|
|
||||||
MinVersion: tls.VersionTLS12,
|
|
||||||
}
|
|
||||||
|
|
||||||
if config.Exists("api-client.rootCAs") {
|
|
||||||
rootCAFile := config.MustString("api-client.rootCAs")
|
|
||||||
caCertPool := x509.NewCertPool()
|
|
||||||
|
|
||||||
pemBytes, err := os.ReadFile(rootCAFile)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not read CA certificate file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
caCertPool.AppendCertsFromPEM(pemBytes)
|
|
||||||
tlsClientConfig.RootCAs = caCertPool
|
|
||||||
}
|
|
||||||
|
|
||||||
return tlsClientConfig, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildPublicURL(hostname string, port int) string {
|
func buildPublicURL(hostname string, port int) string {
|
||||||
|
@ -291,36 +186,28 @@ func buildPublicURL(hostname string, port int) string {
|
||||||
return fmt.Sprintf("https://%s", hostname)
|
return fmt.Sprintf("https://%s", hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureSessionParameters(logger *slog.Logger, config *koanf.Koanf) (string, []byte, []byte, error) {
|
func configureSessionParameters(config *koanf.Koanf) (string, []byte, []byte) {
|
||||||
sessionPath := config.MustString("session.path")
|
sessionPath := config.MustString("session.path")
|
||||||
|
|
||||||
sessionAuthKey, err := base64.StdEncoding.DecodeString(config.String("session.auth-key"))
|
sessionAuthKey, err := base64.StdEncoding.DecodeString(config.String("session.auth-key"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, nil, fmt.Errorf("could not decode session authentication key: %w", err)
|
log.WithError(err).Fatal("could not decode session auth key")
|
||||||
}
|
}
|
||||||
|
|
||||||
sessionEncKey, err := base64.StdEncoding.DecodeString(config.String("session.enc-key"))
|
sessionEncKey, err := base64.StdEncoding.DecodeString(config.String("session.enc-key"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, nil, fmt.Errorf("could not decode session encryption key: %w", err)
|
log.WithError(err).Fatal("could not decode session encryption key")
|
||||||
}
|
}
|
||||||
|
|
||||||
generated := false
|
generated := false
|
||||||
|
|
||||||
if len(sessionAuthKey) != sessionAuthKeyLength {
|
if len(sessionAuthKey) != sessionAuthKeyLength {
|
||||||
sessionAuthKey, err = services.GenerateKey(sessionAuthKeyLength)
|
sessionAuthKey = services.GenerateKey(sessionAuthKeyLength)
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, fmt.Errorf("could not generate session authentication key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
generated = true
|
generated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(sessionEncKey) != sessionKeyLength {
|
if len(sessionEncKey) != sessionKeyLength {
|
||||||
sessionEncKey, err = services.GenerateKey(sessionKeyLength)
|
sessionEncKey = services.GenerateKey(sessionKeyLength)
|
||||||
if err != nil {
|
|
||||||
return "", nil, nil, fmt.Errorf("could not generate session encryption key: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
generated = true
|
generated = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -332,12 +219,11 @@ func configureSessionParameters(logger *slog.Logger, config *koanf.Koanf) (strin
|
||||||
|
|
||||||
tomlData, err := config.Marshal(toml.Parser())
|
tomlData, err := config.Marshal(toml.Parser())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, nil, fmt.Errorf("could not encode session configuration: %w", err)
|
log.WithError(err).Fatal("could not encode session config")
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Info("put the following in your resource_app.toml")
|
log.Infof("put the following in your resource_app.toml:\n%s", string(tomlData))
|
||||||
fmt.Print(string(tomlData)) //nolint:forbidigo
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return sessionPath, sessionAuthKey, sessionEncKey, nil
|
return sessionPath, sessionAuthKey, sessionEncKey
|
||||||
}
|
}
|
||||||
|
|
29
go.mod
29
go.mod
|
@ -1,25 +1,27 @@
|
||||||
module code.cacert.org/cacert/oidc-demo-app
|
module code.cacert.org/cacert/oidc-demo-app
|
||||||
|
|
||||||
go 1.22
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/BurntSushi/toml v1.3.2
|
github.com/BurntSushi/toml v1.3.2
|
||||||
github.com/gorilla/sessions v1.2.2
|
github.com/gorilla/sessions v1.2.1
|
||||||
github.com/knadh/koanf v1.5.0
|
github.com/knadh/koanf v1.5.0
|
||||||
github.com/lestrrat-go/jwx v1.2.29
|
github.com/lestrrat-go/jwx v1.2.26
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0
|
github.com/nicksnyder/go-i18n/v2 v2.2.1
|
||||||
|
github.com/sirupsen/logrus v1.9.3
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
golang.org/x/oauth2 v0.20.0
|
golang.org/x/oauth2 v0.10.0
|
||||||
golang.org/x/text v0.15.0
|
golang.org/x/text v0.11.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/goccy/go-json v0.10.2 // indirect
|
github.com/goccy/go-json v0.10.2 // indirect
|
||||||
github.com/gorilla/securecookie v1.1.2 // indirect
|
github.com/golang/protobuf v1.5.3 // indirect
|
||||||
|
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
github.com/lestrrat-go/backoff/v2 v2.0.8 // indirect
|
||||||
github.com/lestrrat-go/blackmagic v1.0.2 // indirect
|
github.com/lestrrat-go/blackmagic v1.0.1 // indirect
|
||||||
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
github.com/lestrrat-go/httpcc v1.0.1 // indirect
|
||||||
github.com/lestrrat-go/iter v1.0.2 // indirect
|
github.com/lestrrat-go/iter v1.0.2 // indirect
|
||||||
github.com/lestrrat-go/option v1.0.1 // indirect
|
github.com/lestrrat-go/option v1.0.1 // indirect
|
||||||
|
@ -28,6 +30,9 @@ require (
|
||||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
golang.org/x/crypto v0.23.0 // indirect
|
golang.org/x/crypto v0.11.0 // indirect
|
||||||
golang.org/x/sys v0.20.0 // indirect
|
golang.org/x/net v0.12.0 // indirect
|
||||||
|
golang.org/x/sys v0.10.0 // indirect
|
||||||
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
|
google.golang.org/protobuf v1.31.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
74
go.sum
74
go.sum
|
@ -1,6 +1,7 @@
|
||||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8=
|
||||||
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
|
@ -38,9 +39,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
github.com/decred/dcrd/crypto/blake256 v1.0.1/go.mod h1:2OfgNZ5wDpcsFmHmCK5gZTPcCXqlm2ArzUIkw9czNJo=
|
||||||
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0 h1:8UrgZ3GkP4i/CLijOJx79Yu+etlyjdBU4sfcs2WYQMs=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.2.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg=
|
|
||||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0=
|
|
||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||||
|
@ -51,8 +51,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv
|
||||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||||
|
@ -84,6 +84,8 @@ github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw
|
||||||
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
|
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
|
||||||
|
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
|
@ -96,15 +98,12 @@ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
|
|
||||||
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
|
||||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ=
|
||||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4=
|
||||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI=
|
||||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM=
|
||||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
|
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
|
||||||
|
@ -166,14 +165,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
|
github.com/lestrrat-go/backoff/v2 v2.0.8 h1:oNb5E5isby2kiro9AgdHLv5N5tint1AnDVVf2E2un5A=
|
||||||
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
github.com/lestrrat-go/backoff/v2 v2.0.8/go.mod h1:rHP/q/r9aT27n24JQLa7JhSQZCKBBOiM/uP402WwN8Y=
|
||||||
github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k=
|
github.com/lestrrat-go/blackmagic v1.0.1 h1:lS5Zts+5HIC/8og6cGHb0uCcNCa3OUt1ygh3Qz2Fe80=
|
||||||
github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
github.com/lestrrat-go/blackmagic v1.0.1/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU=
|
||||||
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE=
|
||||||
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E=
|
||||||
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI=
|
||||||
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4=
|
||||||
github.com/lestrrat-go/jwx v1.2.29 h1:QT0utmUJ4/12rmsVQrJ3u55bycPkKqGYuGT4tyRhxSQ=
|
github.com/lestrrat-go/jwx v1.2.26 h1:4iFo8FPRZGDYe1t19mQP0zTRqA7n8HnJ5lkIiDvJcB0=
|
||||||
github.com/lestrrat-go/jwx v1.2.29/go.mod h1:hU8k2l6WF0ncx20uQdOmik/Gjg6E3/wIRtXSNFeZuB8=
|
github.com/lestrrat-go/jwx v1.2.26/go.mod h1:MaiCdGbn3/cckbOFSCluJlJMmp9dmZm5hDuIkx8ftpQ=
|
||||||
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
github.com/lestrrat-go/option v1.0.0/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU=
|
||||||
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I=
|
||||||
|
@ -210,8 +209,8 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN
|
||||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1 h1:aOzRCdwsJuoExfZhoiXHy4bjruwCMdt5otbYojM/PaA=
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4=
|
github.com/nicksnyder/go-i18n/v2 v2.2.1/go.mod h1:fF2++lPHlo+/kPaj3nB0uxtPwzlPm+BlgwGX7MkeGj0=
|
||||||
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
|
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
|
||||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||||
|
@ -252,13 +251,14 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg
|
||||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||||
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
@ -267,9 +267,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
|
||||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||||
|
@ -286,10 +285,9 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||||
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
|
golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA=
|
||||||
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio=
|
||||||
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
|
@ -308,6 +306,7 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r
|
||||||
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
|
||||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||||
|
@ -321,12 +320,13 @@ golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
|
||||||
|
golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.20.0 h1:4mQdhULixXKP1rwYBW0vAijoXnkTG0BLCDRzfe1idMo=
|
golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
|
||||||
golang.org/x/oauth2 v0.20.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbhtI=
|
golang.org/x/oauth2 v0.10.0/go.mod h1:kTpgurOux7LqtuxjuyZa4Gj2gdezIt/jQtGnNFfypQI=
|
||||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
@ -370,19 +370,17 @@ golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
|
||||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
|
||||||
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||||
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo=
|
||||||
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
|
|
||||||
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
|
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||||
|
@ -390,11 +388,11 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
|
||||||
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
|
||||||
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
@ -415,6 +413,8 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
||||||
|
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
|
||||||
|
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
|
||||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||||
|
@ -440,6 +440,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
|
||||||
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
|
||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
|
||||||
|
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -18,18 +18,21 @@ limitations under the License.
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
type AfterLogoutHandler struct {
|
type AfterLogoutHandler struct {
|
||||||
logger *slog.Logger
|
logger *logrus.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *AfterLogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (h *AfterLogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
session, err := GetSession(r)
|
session, err := services.GetSessionStore().Get(r, sessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Error("could not get session", "error", err)
|
h.logger.WithError(err).Error("could not get session")
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -38,13 +41,13 @@ func (h *AfterLogoutHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
session.Options.MaxAge = -1
|
session.Options.MaxAge = -1
|
||||||
|
|
||||||
if err = session.Save(r, w); err != nil {
|
if err = session.Save(r, w); err != nil {
|
||||||
h.logger.Error("could not save session", "error", err)
|
h.logger.WithError(err).Error("could not save session")
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Location", "/")
|
w.Header().Set("Location", "/")
|
||||||
w.WriteHeader(http.StatusFound)
|
w.WriteHeader(http.StatusFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAfterLogoutHandler(logger *slog.Logger) *AfterLogoutHandler {
|
func NewAfterLogoutHandler(logger *logrus.Logger) *AfterLogoutHandler {
|
||||||
return &AfterLogoutHandler{logger: logger}
|
return &AfterLogoutHandler{logger: logger}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -18,94 +18,107 @@ limitations under the License.
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/lestrrat-go/jwx/jwk"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/lestrrat-go/jwx/jwt"
|
||||||
|
"github.com/lestrrat-go/jwx/jwt/openid"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
|
"code.cacert.org/cacert/oidc-demo-app/internal/models"
|
||||||
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
sessionName = "resource_session"
|
||||||
|
|
||||||
oauth2RedirectStateLength = 8
|
oauth2RedirectStateLength = 8
|
||||||
sessionName = "resource_app"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func Authenticate(logger *slog.Logger, oauth2Config *oauth2.Config) func(http.Handler) http.Handler {
|
func Authenticate(logger *log.Logger, oauth2Config *oauth2.Config, clientID string) 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) {
|
||||||
session, err := GetSession(r)
|
session, err := services.GetSessionStore().Get(r, sessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.ErrorContext(r.Context(), "failed to get session", "error", err)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := session.Values[services.SessionIDToken]; ok {
|
if _, ok := session.Values[sessionKeyIDToken]; ok {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session.Values[services.SessionRedirectTarget] = r.URL.String()
|
session.Values[sessionRedirectTarget] = r.URL.String()
|
||||||
|
|
||||||
if err = session.Save(r, w); err != nil {
|
if err = session.Save(r, w); err != nil {
|
||||||
logger.ErrorContext(r.Context(), "failed to save session", "error", err)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
state, err := services.GenerateKey(oauth2RedirectStateLength)
|
var authURL *url.URL
|
||||||
if err != nil {
|
|
||||||
logger.ErrorContext(
|
if authURL, err = url.Parse(oauth2Config.Endpoint.AuthURL); err != nil {
|
||||||
r.Context(), "failed to generate state for starting OIDC flow", "error", err,
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
)
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
authURL := oauth2Config.AuthCodeURL(base64.URLEncoding.EncodeToString(state))
|
queryValues := authURL.Query()
|
||||||
|
queryValues.Set("client_id", clientID)
|
||||||
|
queryValues.Set("response_type", "code")
|
||||||
|
queryValues.Set("scope", "openid offline_access profile email")
|
||||||
|
queryValues.Set("state", base64.URLEncoding.EncodeToString(services.GenerateKey(oauth2RedirectStateLength)))
|
||||||
|
queryValues.Set("claims", getRequestedClaims(logger))
|
||||||
|
authURL.RawQuery = queryValues.Encode()
|
||||||
|
|
||||||
w.Header().Set("Location", authURL)
|
w.Header().Set("Location", authURL.String())
|
||||||
w.WriteHeader(http.StatusFound)
|
w.WriteHeader(http.StatusFound)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSession(r *http.Request) (*sessions.Session, error) {
|
func getRequestedClaims(logger *log.Logger) string {
|
||||||
session, err := services.GetSessionStore().Get(r, sessionName)
|
claims := make(models.OIDCClaimsRequest)
|
||||||
|
claims["userinfo"] = make(models.ClaimElement)
|
||||||
|
essentialItem := make(models.IndividualClaimRequest)
|
||||||
|
essentialItem["essential"] = true
|
||||||
|
claims["userinfo"]["https://auth.cacert.org/groups"] = &essentialItem
|
||||||
|
|
||||||
if err != nil {
|
target := make([]byte, 0)
|
||||||
return nil, fmt.Errorf("could not get session")
|
buf := bytes.NewBuffer(target)
|
||||||
|
enc := json.NewEncoder(buf)
|
||||||
|
|
||||||
|
if err := enc.Encode(claims); err != nil {
|
||||||
|
logger.WithError(err).Warn("could not encode claims request parameter")
|
||||||
}
|
}
|
||||||
|
|
||||||
return session, nil
|
return buf.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
type I18NHandler interface {
|
func ParseIDToken(token string, keySet jwk.Set) (openid.Token, error) {
|
||||||
GetBundle() *i18n.Bundle
|
var (
|
||||||
GetCatalog() *services.MessageCatalog
|
parsedIDToken jwt.Token
|
||||||
}
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
func GetLocalizer(h I18NHandler, r *http.Request) *i18n.Localizer {
|
if parsedIDToken, err = jwt.ParseString(token, jwt.WithKeySet(keySet), jwt.WithToken(openid.New())); err != nil {
|
||||||
accept := r.Header.Get("Accept-Language")
|
return nil, fmt.Errorf("could not parse ID token: %w", err)
|
||||||
localizer := i18n.NewLocalizer(h.GetBundle(), accept)
|
|
||||||
|
|
||||||
return localizer
|
|
||||||
}
|
|
||||||
|
|
||||||
func BaseTemplateData(h I18NHandler, localizer *i18n.Localizer) map[string]interface{} {
|
|
||||||
msg := h.GetCatalog().LookupMessage
|
|
||||||
|
|
||||||
data := map[string]interface{}{
|
|
||||||
"WelcomeNavLabel": msg("IndexNavLabel", nil, localizer),
|
|
||||||
"ProtectedNavLabel": msg("ProtectedNavLabel", nil, localizer),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data
|
if v, ok := parsedIDToken.(openid.Token); ok {
|
||||||
|
return v, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, errors.New("ID token is no OpenID Connect Identity Token")
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -21,12 +21,11 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log/slog"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"code.cacert.org/cacert/oidc-demo-app/ui"
|
|
||||||
|
|
||||||
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
||||||
)
|
)
|
||||||
|
@ -47,34 +46,26 @@ type ErrorDetails struct {
|
||||||
type ErrorBucket struct {
|
type ErrorBucket struct {
|
||||||
errorDetails *ErrorDetails
|
errorDetails *ErrorDetails
|
||||||
templates *template.Template
|
templates *template.Template
|
||||||
logger *slog.Logger
|
logger *log.Logger
|
||||||
bundle *i18n.Bundle
|
bundle *i18n.Bundle
|
||||||
messageCatalog *services.MessageCatalog
|
messageCatalog *services.MessageCatalog
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *ErrorBucket) GetBundle() *i18n.Bundle {
|
|
||||||
return b.bundle
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *ErrorBucket) GetCatalog() *services.MessageCatalog {
|
|
||||||
return b.messageCatalog
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *ErrorBucket) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
func (b *ErrorBucket) serveHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
if b.errorDetails != nil {
|
if b.errorDetails != nil {
|
||||||
localizer := GetLocalizer(b, r)
|
accept := r.Header.Get("Accept-Language")
|
||||||
|
localizer := i18n.NewLocalizer(b.bundle, accept)
|
||||||
|
|
||||||
data := BaseTemplateData(b, localizer)
|
err := b.templates.Lookup("base").Execute(w, map[string]interface{}{
|
||||||
data["Title"] = b.messageCatalog.LookupMessage(
|
"Title": b.messageCatalog.LookupMessage(
|
||||||
"ErrorTitle",
|
"ErrorTitle",
|
||||||
nil,
|
nil,
|
||||||
localizer,
|
localizer,
|
||||||
)
|
),
|
||||||
data["details"] = b.errorDetails
|
"details": b.errorDetails,
|
||||||
|
})
|
||||||
err := b.templates.Lookup("base").Execute(w, data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.logger.Error("error rendering error template", "error", err)
|
log.WithError(err).Error("error rendering error template")
|
||||||
http.Error(
|
http.Error(
|
||||||
w,
|
w,
|
||||||
http.StatusText(http.StatusInternalServerError),
|
http.StatusText(http.StatusInternalServerError),
|
||||||
|
@ -143,12 +134,13 @@ func (w *errorResponseWriter) Write(content []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ErrorHandling(
|
func ErrorHandling(
|
||||||
logger *slog.Logger,
|
logger *log.Logger,
|
||||||
|
templateFS fs.FS,
|
||||||
bundle *i18n.Bundle,
|
bundle *i18n.Bundle,
|
||||||
messageCatalog *services.MessageCatalog,
|
messageCatalog *services.MessageCatalog,
|
||||||
) (func(http.Handler) http.Handler, error) {
|
) (func(http.Handler) http.Handler, error) {
|
||||||
errorTemplates, err := template.ParseFS(
|
errorTemplates, err := template.ParseFS(
|
||||||
ui.Templates,
|
templateFS,
|
||||||
"templates/base.gohtml",
|
"templates/base.gohtml",
|
||||||
"templates/errors.gohtml",
|
"templates/errors.gohtml",
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -20,136 +20,117 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"log/slog"
|
"io/fs"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
|
"github.com/lestrrat-go/jwx/jwk"
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
|
|
||||||
"code.cacert.org/cacert/oidc-demo-app/ui"
|
|
||||||
|
|
||||||
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexHandler struct {
|
type IndexHandler struct {
|
||||||
bundle *i18n.Bundle
|
bundle *i18n.Bundle
|
||||||
indexTemplate *template.Template
|
indexTemplate *template.Template
|
||||||
logger *slog.Logger
|
keySet jwk.Set
|
||||||
logoutURL string
|
logoutURL string
|
||||||
messageCatalog *services.MessageCatalog
|
messageCatalog *services.MessageCatalog
|
||||||
publicURL string
|
publicURL string
|
||||||
tokenInfo *services.TokenInfoService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *IndexHandler) GetBundle() *i18n.Bundle {
|
func (h *IndexHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
|
||||||
return h.bundle
|
if request.Method != http.MethodGet {
|
||||||
}
|
http.Error(writer, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
||||||
|
|
||||||
func (h *IndexHandler) GetCatalog() *services.MessageCatalog {
|
|
||||||
return h.messageCatalog
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *IndexHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.URL.Path != "/" {
|
if request.URL.Path != "/" {
|
||||||
http.NotFound(w, r)
|
http.NotFound(writer, request)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
localizer := GetLocalizer(h, r)
|
accept := request.Header.Get("Accept-Language")
|
||||||
|
localizer := i18n.NewLocalizer(h.bundle, accept)
|
||||||
|
|
||||||
|
writer.WriteHeader(http.StatusOK)
|
||||||
|
|
||||||
|
session, err := services.GetSessionStore().Get(request, sessionName)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
logoutURL, err := url.Parse(h.logoutURL)
|
logoutURL, err := url.Parse(h.logoutURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := GetSession(r)
|
var (
|
||||||
|
idToken string
|
||||||
|
ok bool
|
||||||
|
)
|
||||||
|
|
||||||
|
if idToken, ok = session.Values[sessionKeyIDToken].(string); ok {
|
||||||
|
logoutURL.RawQuery = url.Values{
|
||||||
|
"id_token_hint": []string{idToken},
|
||||||
|
"post_logout_redirect_uri": []string{fmt.Sprintf("%s/after-logout", h.publicURL)},
|
||||||
|
}.Encode()
|
||||||
|
} else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
oidcToken, err := ParseIDToken(idToken, h.keySet)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
tokenInfo, err := h.tokenInfo.GetTokenInfo(session)
|
writer.Header().Add("Content-Type", "text/html")
|
||||||
if err != nil {
|
|
||||||
h.logger.Error("failed to get token info for request", "error", err)
|
|
||||||
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tokenInfo.Expires.IsZero() {
|
|
||||||
h.logger.Info("id token expires at", "expires", tokenInfo.Expires)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "text/html")
|
|
||||||
|
|
||||||
msgLookup := h.messageCatalog.LookupMessage
|
msgLookup := h.messageCatalog.LookupMessage
|
||||||
|
|
||||||
data := BaseTemplateData(h, localizer)
|
err = h.indexTemplate.Lookup("base").Execute(writer, map[string]interface{}{
|
||||||
|
"Title": msgLookup("IndexTitle", nil, localizer),
|
||||||
data["Title"] = msgLookup("IndexTitle", nil, localizer)
|
"Greeting": msgLookup("IndexGreeting", map[string]interface{}{
|
||||||
data["Greeting"] = msgLookup("GreetingAnonymous", nil, localizer)
|
"User": oidcToken.Name(),
|
||||||
data["LoginLabel"] = msgLookup("LoginLabel", nil, localizer)
|
}, localizer),
|
||||||
data["IntroductionText"] = msgLookup("IndexIntroductionText", nil, localizer)
|
"IntroductionText": msgLookup("IndexIntroductionText", nil, localizer),
|
||||||
data["IsAuthenticated"] = false
|
"LogoutLabel": msgLookup("LogoutLabel", nil, localizer),
|
||||||
|
"LogoutURL": logoutURL.String(),
|
||||||
if tokenInfo.IDToken != "" {
|
})
|
||||||
logoutURL.RawQuery = url.Values{
|
|
||||||
"id_token_hint": []string{tokenInfo.IDToken},
|
|
||||||
"post_logout_redirect_uri": []string{fmt.Sprintf("%s/after-logout", h.publicURL)},
|
|
||||||
}.Encode()
|
|
||||||
|
|
||||||
data["Greeting"] = msgLookup(
|
|
||||||
"GreetingAuthenticated", map[string]interface{}{"Name": tokenInfo.Name}, localizer,
|
|
||||||
)
|
|
||||||
data["LogoutLabel"] = msgLookup("LogoutLabel", nil, localizer)
|
|
||||||
data["LogoutURL"] = logoutURL.String()
|
|
||||||
data["IsAuthenticated"] = true
|
|
||||||
data["AuthenticatedAs"] = msgLookup("AuthenticatedAs", map[string]interface{}{
|
|
||||||
"Name": tokenInfo.Name,
|
|
||||||
"Email": tokenInfo.Email,
|
|
||||||
}, localizer)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = h.indexTemplate.Lookup("base").Execute(w, data)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(writer, err.Error(), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIndexHandler(
|
func NewIndexHandler(
|
||||||
logger *slog.Logger,
|
|
||||||
bundle *i18n.Bundle,
|
bundle *i18n.Bundle,
|
||||||
catalog *services.MessageCatalog,
|
catalog *services.MessageCatalog,
|
||||||
|
templateFS fs.FS,
|
||||||
oidcInfo *services.OIDCInformation,
|
oidcInfo *services.OIDCInformation,
|
||||||
publicURL string,
|
publicURL string,
|
||||||
tokenInfoService *services.TokenInfoService,
|
|
||||||
) (*IndexHandler, error) {
|
) (*IndexHandler, error) {
|
||||||
indexTemplate, err := template.ParseFS(
|
indexTemplate, err := template.ParseFS(
|
||||||
ui.Templates,
|
templateFS,
|
||||||
"templates/base.gohtml", "templates/index.gohtml")
|
"templates/base.gohtml", "templates/index.gohtml")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not parse templates: %w", err)
|
return nil, fmt.Errorf("could not parse templates: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &IndexHandler{
|
return &IndexHandler{
|
||||||
logger: logger,
|
|
||||||
bundle: bundle,
|
bundle: bundle,
|
||||||
indexTemplate: indexTemplate,
|
indexTemplate: indexTemplate,
|
||||||
|
keySet: oidcInfo.KeySet,
|
||||||
logoutURL: oidcInfo.OIDCConfiguration.EndSessionEndpoint,
|
logoutURL: oidcInfo.OIDCConfiguration.EndSessionEndpoint,
|
||||||
tokenInfo: tokenInfoService,
|
|
||||||
messageCatalog: catalog,
|
messageCatalog: catalog,
|
||||||
publicURL: publicURL,
|
publicURL: publicURL,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright CAcert Inc.
|
|
||||||
SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LoginHandler struct{}
|
|
||||||
|
|
||||||
func (l *LoginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path != "/login" {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next := r.URL.Query().Get("next")
|
|
||||||
if next != "" {
|
|
||||||
http.Redirect(w, r, next, http.StatusFound)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
http.Redirect(w, r, "/", http.StatusFound)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLoginHandler() *LoginHandler {
|
|
||||||
return &LoginHandler{}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -20,9 +20,10 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type key int
|
type key int
|
||||||
|
@ -63,7 +64,7 @@ func Logging(logger *log.Logger) func(http.Handler) http.Handler {
|
||||||
requestID = "unknown"
|
requestID = "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Printf(
|
logger.Infof(
|
||||||
"[%s] %s \"%s %s\" %d %d \"%s\"",
|
"[%s] %s \"%s %s\" %d %d \"%s\"",
|
||||||
requestID,
|
requestID,
|
||||||
r.RemoteAddr,
|
r.RemoteAddr,
|
||||||
|
@ -96,7 +97,7 @@ func Tracing(nextRequestID func() string) func(http.Handler) http.Handler {
|
||||||
var Healthy int32
|
var Healthy int32
|
||||||
|
|
||||||
func NewHealthHandler() http.Handler {
|
func NewHealthHandler() http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if atomic.LoadInt32(&Healthy) == 1 {
|
if atomic.LoadInt32(&Healthy) == 1 {
|
||||||
w.WriteHeader(http.StatusNoContent)
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -19,20 +19,26 @@ package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/lestrrat-go/jwx/jwk"
|
"github.com/lestrrat-go/jwx/jwk"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
sessionKeyAccessToken = iota
|
||||||
|
sessionKeyRefreshToken
|
||||||
|
sessionKeyIDToken
|
||||||
|
sessionRedirectTarget
|
||||||
|
)
|
||||||
|
|
||||||
type OidcCallbackHandler struct {
|
type OidcCallbackHandler struct {
|
||||||
keySet jwk.Set
|
keySet jwk.Set
|
||||||
logger *slog.Logger
|
logger *log.Logger
|
||||||
oauth2Config *oauth2.Config
|
oauth2Config *oauth2.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,22 +65,22 @@ func (c *OidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||||
|
|
||||||
tok, err := c.oauth2Config.Exchange(r.Context(), code)
|
tok, err := c.oauth2Config.Exchange(r.Context(), code)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("could not perform token exchange", "error", err)
|
c.logger.WithError(err).Error("could not perform token exchange")
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := GetSession(r)
|
session, err := services.GetSessionStore().Get(r, "resource_session")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.logger.Error("could not get session", "error", err)
|
c.logger.WithError(err).Error("could not get session store")
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = c.storeTokens(session, tok); err != nil {
|
if err = c.storeTokens(session, tok); err != nil {
|
||||||
c.logger.Error("could not store token in session", "error", err)
|
c.logger.WithError(err).Error("could not store token in session")
|
||||||
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
|
@ -82,14 +88,14 @@ func (c *OidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = session.Save(r, w); err != nil {
|
if err = session.Save(r, w); err != nil {
|
||||||
c.logger.Error("could not save session", "error", err)
|
c.logger.WithError(err).Error("could not save session")
|
||||||
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if redirectTarget, ok := session.Values[services.SessionRedirectTarget]; ok {
|
if redirectTarget, ok := session.Values[sessionRedirectTarget]; ok {
|
||||||
if v, ok := redirectTarget.(string); ok {
|
if v, ok := redirectTarget.(string); ok {
|
||||||
w.Header().Set("Location", v)
|
w.Header().Set("Location", v)
|
||||||
w.WriteHeader(http.StatusFound)
|
w.WriteHeader(http.StatusFound)
|
||||||
|
@ -124,8 +130,8 @@ func (c *OidcCallbackHandler) storeTokens(
|
||||||
session *sessions.Session,
|
session *sessions.Session,
|
||||||
tok *oauth2.Token,
|
tok *oauth2.Token,
|
||||||
) error {
|
) error {
|
||||||
session.Values[services.SessionAccessToken] = tok.AccessToken
|
session.Values[sessionKeyAccessToken] = tok.AccessToken
|
||||||
session.Values[services.SessionRefreshToken] = tok.RefreshToken
|
session.Values[sessionKeyRefreshToken] = tok.RefreshToken
|
||||||
|
|
||||||
idTokenValue := tok.Extra("id_token")
|
idTokenValue := tok.Extra("id_token")
|
||||||
|
|
||||||
|
@ -134,29 +140,26 @@ func (c *OidcCallbackHandler) storeTokens(
|
||||||
return fmt.Errorf("ID token value %v is not a string", idTokenValue)
|
return fmt.Errorf("ID token value %v is not a string", idTokenValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
session.Values[services.SessionIDToken] = idToken
|
session.Values[sessionKeyIDToken] = idToken
|
||||||
|
|
||||||
session.Options.MaxAge = int(time.Until(tok.Expiry).Seconds())
|
oidcToken, err := ParseIDToken(idToken, c.keySet)
|
||||||
|
|
||||||
oidcToken, err := services.ParseIDToken(idToken, c.keySet)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("could not parse ID token: %w", err)
|
return fmt.Errorf("could not parse ID token: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.logger.Debug(
|
c.logger.WithFields(log.Fields{
|
||||||
"receive OpenID Connect ID Token",
|
"sub": oidcToken.Subject(),
|
||||||
"sub", oidcToken.Subject(),
|
"aud": oidcToken.Audience(),
|
||||||
"aud", oidcToken.Audience(),
|
"issued_at": oidcToken.IssuedAt(),
|
||||||
"issued_at", oidcToken.IssuedAt(),
|
"iss": oidcToken.Issuer(),
|
||||||
"iss", oidcToken.Issuer(),
|
"not_before": oidcToken.NotBefore(),
|
||||||
"not_before", oidcToken.NotBefore(),
|
"exp": oidcToken.Expiration(),
|
||||||
"exp", oidcToken.Expiration(),
|
}).Debug("receive OpenID Connect ID Token")
|
||||||
)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCallbackHandler(logger *slog.Logger, keySet jwk.Set, oauth2Config *oauth2.Config) *OidcCallbackHandler {
|
func NewCallbackHandler(logger *log.Logger, keySet jwk.Set, oauth2Config *oauth2.Config) *OidcCallbackHandler {
|
||||||
return &OidcCallbackHandler{
|
return &OidcCallbackHandler{
|
||||||
keySet: keySet,
|
keySet: keySet,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|
|
@ -1,155 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright CAcert Inc.
|
|
||||||
SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"html/template"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
|
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
|
||||||
|
|
||||||
"code.cacert.org/cacert/oidc-demo-app/internal/services"
|
|
||||||
"code.cacert.org/cacert/oidc-demo-app/ui"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProtectedResource struct {
|
|
||||||
bundle *i18n.Bundle
|
|
||||||
logger *slog.Logger
|
|
||||||
protectedTemplate *template.Template
|
|
||||||
logoutURL string
|
|
||||||
tokenInfo *services.TokenInfoService
|
|
||||||
messageCatalog *services.MessageCatalog
|
|
||||||
publicURL string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ProtectedResource) GetBundle() *i18n.Bundle {
|
|
||||||
return h.bundle
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ProtectedResource) GetCatalog() *services.MessageCatalog {
|
|
||||||
return h.messageCatalog
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *ProtectedResource) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method != http.MethodGet {
|
|
||||||
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if r.URL.Path != "/protected" {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
localizer := GetLocalizer(h, r)
|
|
||||||
|
|
||||||
logoutURL, err := url.Parse(h.logoutURL)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
session, err := GetSession(r)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenInfo, err := h.tokenInfo.GetTokenInfo(session)
|
|
||||||
if err != nil {
|
|
||||||
h.logger.Error("failed to get token info for request", "error", err)
|
|
||||||
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if !tokenInfo.Expires.IsZero() {
|
|
||||||
h.logger.Info("id token expires at", "expires", tokenInfo.Expires)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokenInfo.IDToken == "" {
|
|
||||||
http.Error(w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Add("Content-Type", "text/html")
|
|
||||||
|
|
||||||
msgLookup := h.messageCatalog.LookupMessage
|
|
||||||
|
|
||||||
logoutURL.RawQuery = url.Values{
|
|
||||||
"id_token_hint": []string{tokenInfo.IDToken},
|
|
||||||
"post_logout_redirect_uri": []string{fmt.Sprintf("%s/after-logout", h.publicURL)},
|
|
||||||
}.Encode()
|
|
||||||
|
|
||||||
data := BaseTemplateData(h, localizer)
|
|
||||||
|
|
||||||
data["Title"] = msgLookup("IndexTitle", nil, localizer)
|
|
||||||
data["Greeting"] = msgLookup(
|
|
||||||
"GreetingAuthenticated", map[string]interface{}{"Name": tokenInfo.Name}, localizer,
|
|
||||||
)
|
|
||||||
data["LoginLabel"] = msgLookup("LoginLabel", nil, localizer)
|
|
||||||
data["IntroductionText"] = msgLookup("ProtectedIntroductionText", nil, localizer)
|
|
||||||
data["IsAuthenticated"] = true
|
|
||||||
data["LogoutLabel"] = msgLookup("LogoutLabel", nil, localizer)
|
|
||||||
data["LogoutURL"] = logoutURL.String()
|
|
||||||
data["AuthenticatedAs"] = msgLookup("AuthenticatedAs", map[string]interface{}{
|
|
||||||
"Name": tokenInfo.Name,
|
|
||||||
"Email": tokenInfo.Email,
|
|
||||||
}, localizer)
|
|
||||||
|
|
||||||
err = h.protectedTemplate.Lookup("base").Execute(w, data)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewProtectedResourceHandler(
|
|
||||||
logger *slog.Logger,
|
|
||||||
bundle *i18n.Bundle,
|
|
||||||
catalog *services.MessageCatalog,
|
|
||||||
oidcInfo *services.OIDCInformation,
|
|
||||||
publicURL string, tokenInfoService *services.TokenInfoService,
|
|
||||||
) (*ProtectedResource, error) {
|
|
||||||
protectedTemplate, err := template.ParseFS(
|
|
||||||
ui.Templates,
|
|
||||||
"templates/base.gohtml", "templates/protected.gohtml")
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse templates: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ProtectedResource{
|
|
||||||
logger: logger,
|
|
||||||
bundle: bundle,
|
|
||||||
protectedTemplate: protectedTemplate,
|
|
||||||
logoutURL: oidcInfo.OIDCConfiguration.EndSessionEndpoint,
|
|
||||||
tokenInfo: tokenInfoService,
|
|
||||||
messageCatalog: catalog,
|
|
||||||
publicURL: publicURL,
|
|
||||||
}, nil
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -20,8 +20,6 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
@ -29,18 +27,23 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/knadh/koanf"
|
"github.com/knadh/koanf"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StartApplication(
|
func StartApplication(
|
||||||
ctx context.Context, logger *slog.Logger, server *http.Server, publicURL string, config *koanf.Koanf,
|
ctx context.Context,
|
||||||
) error {
|
logger *logrus.Logger,
|
||||||
|
server *http.Server,
|
||||||
|
publicURL string,
|
||||||
|
config *koanf.Koanf,
|
||||||
|
) {
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
signal.Notify(quit, os.Interrupt)
|
signal.Notify(quit, os.Interrupt)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
<-quit
|
<-quit
|
||||||
logger.Info("Server is shutting down...")
|
logger.Infoln("Server is shutting down...")
|
||||||
atomic.StoreInt32(&Healthy, 0)
|
atomic.StoreInt32(&Healthy, 0)
|
||||||
|
|
||||||
const shutdownWaitTime = 30 * time.Second
|
const shutdownWaitTime = 30 * time.Second
|
||||||
|
@ -52,25 +55,24 @@ func StartApplication(
|
||||||
server.SetKeepAlivesEnabled(false)
|
server.SetKeepAlivesEnabled(false)
|
||||||
|
|
||||||
if err := server.Shutdown(ctx); err != nil {
|
if err := server.Shutdown(ctx); err != nil {
|
||||||
logger.Error("Could not gracefully shutdown the server", "error", err)
|
logger.WithError(err).Fatal("Could not gracefully shutdown the server")
|
||||||
}
|
}
|
||||||
|
|
||||||
close(done)
|
close(done)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
logger.Info("Server is ready to handle requests", "public_url", publicURL)
|
logger.WithField("public_url", publicURL).Info("Server is ready to handle requests")
|
||||||
atomic.StoreInt32(&Healthy, 1)
|
atomic.StoreInt32(&Healthy, 1)
|
||||||
|
|
||||||
if err := server.ListenAndServeTLS(
|
if err := server.ListenAndServeTLS(
|
||||||
config.String("server.certificate"), config.String("server.key"),
|
config.String("server.certificate"), config.String("server.key"),
|
||||||
); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
logger.Error("Could not listen on requested address", "server_address", server.Addr)
|
logger.WithError(err).WithField(
|
||||||
|
"server_address",
|
||||||
return fmt.Errorf("listening failed: %w", err)
|
server.Addr,
|
||||||
|
).Fatal("Could not listen on requested address")
|
||||||
}
|
}
|
||||||
|
|
||||||
<-done
|
<-done
|
||||||
logger.Info("Server stopped")
|
logger.Infoln("Server stopped")
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -19,7 +19,6 @@ package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
@ -29,6 +28,7 @@ import (
|
||||||
"github.com/knadh/koanf/providers/env"
|
"github.com/knadh/koanf/providers/env"
|
||||||
"github.com/knadh/koanf/providers/file"
|
"github.com/knadh/koanf/providers/file"
|
||||||
"github.com/knadh/koanf/providers/posflag"
|
"github.com/knadh/koanf/providers/posflag"
|
||||||
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -51,12 +51,13 @@ var DefaultConfiguration = map[string]interface{}{
|
||||||
}
|
}
|
||||||
|
|
||||||
func ConfigureApplication(
|
func ConfigureApplication(
|
||||||
|
logger *logrus.Logger,
|
||||||
appName string,
|
appName string,
|
||||||
defaultConfig map[string]interface{},
|
defaultConfig map[string]interface{},
|
||||||
) (*koanf.Koanf, error) {
|
) (*koanf.Koanf, error) {
|
||||||
f := pflag.NewFlagSet("config", pflag.ContinueOnError)
|
f := pflag.NewFlagSet("config", pflag.ContinueOnError)
|
||||||
f.Usage = func() {
|
f.Usage = func() {
|
||||||
log.Print(f.FlagUsages())
|
logger.Info(f.FlagUsages())
|
||||||
|
|
||||||
os.Exit(0)
|
os.Exit(0)
|
||||||
}
|
}
|
||||||
|
@ -69,7 +70,7 @@ func ConfigureApplication(
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
if err = f.Parse(os.Args[1:]); err != nil {
|
if err = f.Parse(os.Args[1:]); err != nil {
|
||||||
return nil, fmt.Errorf("could not parse command line arguments: %w", err)
|
logger.WithError(err).Fatal("could not parse command line arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
config := koanf.New(".")
|
config := koanf.New(".")
|
||||||
|
@ -77,18 +78,18 @@ func ConfigureApplication(
|
||||||
_ = config.Load(confmap.Provider(defaultConfig, "."), nil)
|
_ = config.Load(confmap.Provider(defaultConfig, "."), nil)
|
||||||
|
|
||||||
if err = config.Load(file.Provider(defaultFile), toml.Parser()); err != nil && !os.IsNotExist(err) {
|
if err = config.Load(file.Provider(defaultFile), toml.Parser()); err != nil && !os.IsNotExist(err) {
|
||||||
return nil, fmt.Errorf("could not load configuration from file %s: %w", defaultFile, err)
|
logrus.WithError(err).WithField("file", defaultFile).Fatal("error loading configuration from file")
|
||||||
}
|
}
|
||||||
|
|
||||||
cFiles, _ := f.GetStringSlice("conf")
|
cFiles, _ := f.GetStringSlice("conf")
|
||||||
for _, c := range cFiles {
|
for _, c := range cFiles {
|
||||||
if err = config.Load(file.Provider(c), toml.Parser()); err != nil {
|
if err = config.Load(file.Provider(c), toml.Parser()); err != nil {
|
||||||
return nil, fmt.Errorf("error loading configuration from file %s: %w", c, err)
|
logger.WithError(err).WithField("file", c).Fatal("error loading configuration from file")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = config.Load(posflag.Provider(f, ".", config), nil); err != nil {
|
if err = config.Load(posflag.Provider(f, ".", config), nil); err != nil {
|
||||||
return nil, fmt.Errorf("error loading configuration from command line: %w", err)
|
logger.WithError(err).Fatal("error loading configuration from command line")
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix := fmt.Sprintf("%s_", strings.ToUpper(appName))
|
prefix := fmt.Sprintf("%s_", strings.ToUpper(appName))
|
||||||
|
@ -96,7 +97,7 @@ func ConfigureApplication(
|
||||||
if err = config.Load(env.Provider(prefix, ".", func(s string) string {
|
if err = config.Load(env.Provider(prefix, ".", func(s string) string {
|
||||||
return strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(s, prefix)), "_", ".")
|
return strings.ReplaceAll(strings.ToLower(strings.TrimPrefix(s, prefix)), "_", ".")
|
||||||
}), nil); err != nil {
|
}), nil); err != nil {
|
||||||
return nil, fmt.Errorf("error loading configuration from environment variables: %w", err)
|
logrus.WithError(err).Fatal("error loading configuration from environment")
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, nil
|
return config, nil
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -20,7 +20,6 @@ package services
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
|
|
||||||
"github.com/nicksnyder/go-i18n/v2/i18n"
|
"github.com/nicksnyder/go-i18n/v2/i18n"
|
||||||
"golang.org/x/text/language"
|
"golang.org/x/text/language"
|
||||||
|
@ -28,32 +27,19 @@ import (
|
||||||
"code.cacert.org/cacert/oidc-demo-app/translations"
|
"code.cacert.org/cacert/oidc-demo-app/translations"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func AddMessages(catalog *MessageCatalog) {
|
func AddMessages(catalog *MessageCatalog) {
|
||||||
messages := make(map[string]*i18n.Message)
|
messages := make(map[string]*i18n.Message)
|
||||||
messages["AuthenticatedAs"] = &i18n.Message{
|
messages["IndexGreeting"] = &i18n.Message{
|
||||||
ID: "AuthenticatedAs",
|
ID: "IndexGreeting",
|
||||||
Other: "The identity provider authenticated your identity as {{ .Name }}" +
|
Other: "Hello {{ .User }}",
|
||||||
" with the email address {{ .Email }}.",
|
|
||||||
}
|
|
||||||
messages["GreetingAnonymous"] = &i18n.Message{
|
|
||||||
ID: "GreetingAnonymous",
|
|
||||||
Other: "Hello",
|
|
||||||
}
|
|
||||||
messages["GreetingAuthenticated"] = &i18n.Message{
|
|
||||||
ID: "GreetingAuthenticated",
|
|
||||||
Other: "Hello {{ .Name }}",
|
|
||||||
}
|
}
|
||||||
messages["IndexTitle"] = &i18n.Message{
|
messages["IndexTitle"] = &i18n.Message{
|
||||||
ID: "IndexTitle",
|
ID: "IndexTitle",
|
||||||
Other: "Welcome to the Demo application",
|
Other: "Welcome to the Demo application",
|
||||||
}
|
}
|
||||||
messages["LoginLabel"] = &i18n.Message{
|
|
||||||
ID: "LoginLabel",
|
|
||||||
Description: "A label on a login button or link",
|
|
||||||
Other: "Login",
|
|
||||||
}
|
|
||||||
messages["LogoutLabel"] = &i18n.Message{
|
messages["LogoutLabel"] = &i18n.Message{
|
||||||
ID: "LogoutLabel",
|
ID: "LogoutLabel",
|
||||||
Description: "A label on a logout button or link",
|
Description: "A label on a logout button or link",
|
||||||
|
@ -61,21 +47,7 @@ func AddMessages(catalog *MessageCatalog) {
|
||||||
}
|
}
|
||||||
messages["IndexIntroductionText"] = &i18n.Message{
|
messages["IndexIntroductionText"] = &i18n.Message{
|
||||||
ID: "IndexIntroductionText",
|
ID: "IndexIntroductionText",
|
||||||
Other: "This is a public resource.",
|
Other: "This is an authorization protected resource",
|
||||||
}
|
|
||||||
messages["IndexNavLabel"] = &i18n.Message{
|
|
||||||
ID: "IndexNavLabel",
|
|
||||||
Description: "Label for the index page in the top navigation",
|
|
||||||
Other: "Welcome",
|
|
||||||
}
|
|
||||||
messages["ProtectedIntroductionText"] = &i18n.Message{
|
|
||||||
ID: "ProtectedIntroductionText",
|
|
||||||
Other: "This is an authorization protected resource.",
|
|
||||||
}
|
|
||||||
messages["ProtectedNavLabel"] = &i18n.Message{
|
|
||||||
ID: "ProtectedNavLabel",
|
|
||||||
Description: "Label for the protected resource page in the top navigation",
|
|
||||||
Other: "Protected area",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catalog.AddMessages(messages)
|
catalog.AddMessages(messages)
|
||||||
|
@ -83,7 +55,7 @@ func AddMessages(catalog *MessageCatalog) {
|
||||||
|
|
||||||
type MessageCatalog struct {
|
type MessageCatalog struct {
|
||||||
messages map[string]*i18n.Message
|
messages map[string]*i18n.Message
|
||||||
logger *slog.Logger
|
logger *log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MessageCatalog) AddMessages(messages map[string]*i18n.Message) {
|
func (m *MessageCatalog) AddMessages(messages map[string]*i18n.Message) {
|
||||||
|
@ -102,11 +74,11 @@ func (m *MessageCatalog) LookupErrorMessage(
|
||||||
|
|
||||||
message, ok := m.messages[fieldTag]
|
message, ok := m.messages[fieldTag]
|
||||||
if !ok {
|
if !ok {
|
||||||
m.logger.Info("no specific error message for field and tag", "field_tag", fieldTag)
|
m.logger.WithField("field_tag", fieldTag).Info("no specific error message for field and tag")
|
||||||
|
|
||||||
message, ok = m.messages[tag]
|
message, ok = m.messages[tag]
|
||||||
if !ok {
|
if !ok {
|
||||||
m.logger.Info("no specific error message for tag", "tag", tag)
|
m.logger.WithField("tag", tag).Info("no specific error message for tag")
|
||||||
|
|
||||||
message, ok = m.messages["unknown"]
|
message, ok = m.messages["unknown"]
|
||||||
if !ok {
|
if !ok {
|
||||||
|
@ -124,7 +96,7 @@ func (m *MessageCatalog) LookupErrorMessage(
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
m.logger.Error("localization failed", "error", err)
|
m.logger.WithError(err).Error("localization failed")
|
||||||
|
|
||||||
return tag
|
return tag
|
||||||
}
|
}
|
||||||
|
@ -149,7 +121,7 @@ func (m *MessageCatalog) LookupMessage(
|
||||||
return translation
|
return translation
|
||||||
}
|
}
|
||||||
|
|
||||||
m.logger.Warn("no translation found for id", "id", id)
|
m.logger.WithField("id", id).Warn("no translation found for id")
|
||||||
|
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
@ -158,19 +130,19 @@ func (m *MessageCatalog) handleLocalizeError(id string, translation string, err
|
||||||
var messageNotFound *i18n.MessageNotFoundErr
|
var messageNotFound *i18n.MessageNotFoundErr
|
||||||
|
|
||||||
if errors.As(err, &messageNotFound) {
|
if errors.As(err, &messageNotFound) {
|
||||||
m.logger.Warn("message not found", "error", err, "message", id)
|
m.logger.WithError(err).WithField("message", id).Warn("message not found")
|
||||||
|
|
||||||
if translation != "" {
|
if translation != "" {
|
||||||
return translation
|
return translation
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m.logger.Error("translation error", "error", err, "message", id)
|
m.logger.WithError(err).WithField("message", id).Error("translation error")
|
||||||
}
|
}
|
||||||
|
|
||||||
return id
|
return id
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitI18n(logger *slog.Logger, languages []string) (*i18n.Bundle, *MessageCatalog) {
|
func InitI18n(logger *log.Logger, languages []string) (*i18n.Bundle, *MessageCatalog) {
|
||||||
bundle := i18n.NewBundle(language.English)
|
bundle := i18n.NewBundle(language.English)
|
||||||
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal)
|
||||||
|
|
||||||
|
@ -179,7 +151,7 @@ func InitI18n(logger *slog.Logger, languages []string) (*i18n.Bundle, *MessageCa
|
||||||
|
|
||||||
bundleBytes, err := translations.Bundles.ReadFile(bundleName)
|
bundleBytes, err := translations.Bundles.ReadFile(bundleName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn("message bundle not found", "bundle", bundleName)
|
logger.WithField("bundle", bundleName).Warn("message bundle not found")
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -192,7 +164,7 @@ func InitI18n(logger *slog.Logger, languages []string) (*i18n.Bundle, *MessageCa
|
||||||
return bundle, catalog
|
return bundle, catalog
|
||||||
}
|
}
|
||||||
|
|
||||||
func initMessageCatalog(logger *slog.Logger) *MessageCatalog {
|
func initMessageCatalog(logger *log.Logger) *MessageCatalog {
|
||||||
messages := make(map[string]*i18n.Message)
|
messages := make(map[string]*i18n.Message)
|
||||||
messages["ErrorTitle"] = &i18n.Message{
|
messages["ErrorTitle"] = &i18n.Message{
|
||||||
ID: "ErrorTitle",
|
ID: "ErrorTitle",
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -22,12 +22,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/lestrrat-go/jwx/jwk"
|
"github.com/lestrrat-go/jwx/jwk"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
|
|
||||||
"code.cacert.org/cacert/oidc-demo-app/internal/models"
|
"code.cacert.org/cacert/oidc-demo-app/internal/models"
|
||||||
|
@ -57,19 +57,17 @@ type OIDCInformation struct {
|
||||||
// retrieved by GetOAuth2Config.
|
// retrieved by GetOAuth2Config.
|
||||||
//
|
//
|
||||||
// The JSON Web Key Set can be retrieved by GetJwkSet.
|
// The JSON Web Key Set can be retrieved by GetJwkSet.
|
||||||
func DiscoverOIDC(logger *slog.Logger, params *OidcParams) (*OIDCInformation, error) {
|
func DiscoverOIDC(logger *log.Logger, params *OidcParams) (*OIDCInformation, error) {
|
||||||
discoveryURL, err := url.Parse(params.OidcServer)
|
discoveryURL, err := url.Parse(params.OidcServer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error(
|
logger.WithError(err).WithField(
|
||||||
"could not parse parameter oidc.server as URL",
|
"oidc.server",
|
||||||
"oidc.server", params.OidcServer,
|
params.OidcServer,
|
||||||
)
|
).Fatal("could not parse parameter value")
|
||||||
|
} else {
|
||||||
return nil, fmt.Errorf("could not parse parameter value: %w", err)
|
discoveryURL.Path = "/.well-known/openid-configuration"
|
||||||
}
|
}
|
||||||
|
|
||||||
discoveryURL.Path = "/.well-known/openid-configuration"
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
body []byte
|
body []byte
|
||||||
req *http.Request
|
req *http.Request
|
||||||
|
@ -106,7 +104,7 @@ func DiscoverOIDC(logger *slog.Logger, params *OidcParams) (*OIDCInformation, er
|
||||||
AuthURL: discoveryResponse.AuthorizationEndpoint,
|
AuthURL: discoveryResponse.AuthorizationEndpoint,
|
||||||
TokenURL: discoveryResponse.TokenEndpoint,
|
TokenURL: discoveryResponse.TokenEndpoint,
|
||||||
},
|
},
|
||||||
Scopes: []string{"openid", "email", "profile"},
|
Scopes: []string{"openid", "offline"},
|
||||||
}
|
}
|
||||||
|
|
||||||
const jwkFetchTimeout = 10 * time.Second
|
const jwkFetchTimeout = 10 * time.Second
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -19,20 +19,21 @@ package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"fmt"
|
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GenerateKey(length int) ([]byte, error) {
|
func GenerateKey(length int) []byte {
|
||||||
key := make([]byte, length)
|
key := make([]byte, length)
|
||||||
|
|
||||||
read, err := rand.Read(key)
|
read, err := rand.Read(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("could not generate key: %w", err)
|
log.WithError(err).Fatal("could not generate key")
|
||||||
}
|
}
|
||||||
|
|
||||||
if read != length {
|
if read != length {
|
||||||
return nil, fmt.Errorf("read unexpected number of bytes, read %d, expected %d", read, length)
|
log.WithFields(log.Fields{"read": read, "expected": length}).Fatal("read unexpected number of bytes")
|
||||||
}
|
}
|
||||||
|
|
||||||
return key, nil
|
return key
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
@ -18,33 +18,24 @@ limitations under the License.
|
||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
var store *sessions.FilesystemStore
|
var store *sessions.FilesystemStore
|
||||||
|
|
||||||
const (
|
func InitSessionStore(logger *log.Logger, sessionPath string, keys ...[]byte) {
|
||||||
SessionAccessToken = iota
|
|
||||||
SessionRefreshToken
|
|
||||||
SessionIDToken
|
|
||||||
SessionRedirectTarget
|
|
||||||
)
|
|
||||||
|
|
||||||
func InitSessionStore(sessionPath string, keys ...[]byte) error {
|
|
||||||
store = sessions.NewFilesystemStore(sessionPath, keys...)
|
store = sessions.NewFilesystemStore(sessionPath, keys...)
|
||||||
|
|
||||||
if _, err := os.Stat(sessionPath); err != nil {
|
if _, err := os.Stat(sessionPath); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
if err = os.MkdirAll(sessionPath, 0700); err != nil { //nolint:mnd
|
if err = os.MkdirAll(sessionPath, 0700); err != nil { //nolint:gomnd
|
||||||
return fmt.Errorf("could not create session store director: %w", err)
|
logger.WithError(err).Fatal("could not create session store director")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSessionStore() *sessions.FilesystemStore {
|
func GetSessionStore() *sessions.FilesystemStore {
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright CAcert Inc.
|
|
||||||
SPDX-License-Identifier: Apache-2.0
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/gorilla/sessions"
|
|
||||||
"github.com/lestrrat-go/jwx/jwk"
|
|
||||||
"github.com/lestrrat-go/jwx/jwt"
|
|
||||||
"github.com/lestrrat-go/jwx/jwt/openid"
|
|
||||||
)
|
|
||||||
|
|
||||||
type OIDCTokenInfo struct {
|
|
||||||
AccessToken string
|
|
||||||
IDToken string
|
|
||||||
RefreshToken string
|
|
||||||
Expires time.Time
|
|
||||||
Name string
|
|
||||||
Email string
|
|
||||||
}
|
|
||||||
|
|
||||||
type TokenInfoService struct {
|
|
||||||
logger *slog.Logger
|
|
||||||
keySet jwk.Set
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TokenInfoService) GetTokenInfo(session *sessions.Session) (*OIDCTokenInfo, error) {
|
|
||||||
tokenInfo := &OIDCTokenInfo{}
|
|
||||||
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
if tokenInfo.AccessToken, ok = session.Values[SessionAccessToken].(string); ok {
|
|
||||||
s.logger.Debug("found access token in session", "access_token", tokenInfo.AccessToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokenInfo.RefreshToken, ok = session.Values[SessionRefreshToken].(string); ok {
|
|
||||||
s.logger.Debug("found refresh token in session", "refresh_token", tokenInfo.RefreshToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokenInfo.IDToken, ok = session.Values[SessionIDToken].(string); ok {
|
|
||||||
s.logger.Debug("found ID token in session", "id_token", tokenInfo.IDToken)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tokenInfo.IDToken == "" {
|
|
||||||
return tokenInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
oidcToken, err := ParseIDToken(tokenInfo.IDToken, s.keySet)
|
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse ID token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tokenInfo.Expires = oidcToken.Expiration()
|
|
||||||
|
|
||||||
tokenInfo.Name = oidcToken.Name()
|
|
||||||
|
|
||||||
tokenInfo.Email = oidcToken.Email()
|
|
||||||
|
|
||||||
return tokenInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func InitTokenInfoService(logger *slog.Logger, oidcInfo *OIDCInformation) (*TokenInfoService, error) {
|
|
||||||
return &TokenInfoService{logger: logger, keySet: oidcInfo.KeySet}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseIDToken(token string, keySet jwk.Set) (openid.Token, error) {
|
|
||||||
var (
|
|
||||||
parsedIDToken jwt.Token
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
if parsedIDToken, err = jwt.ParseString(token, jwt.WithKeySet(keySet), jwt.WithToken(openid.New())); err != nil {
|
|
||||||
return nil, fmt.Errorf("could not parse ID token: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if v, ok := parsedIDToken.(openid.Token); ok {
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, errors.New("ID token is no OpenID Connect Identity Token")
|
|
||||||
}
|
|
|
@ -1,47 +1,20 @@
|
||||||
[AuthenticatedAs]
|
|
||||||
hash = "sha1-58e33592c806dab9cddd3693c0cfee64a07a0a9b"
|
|
||||||
other = "Der Identity-Provider hat dich als {{ .Name }} mit der E-Mail-Adresse {{ .Email }} identifiziert."
|
|
||||||
|
|
||||||
[ErrorTitle]
|
[ErrorTitle]
|
||||||
hash = "sha1-736aec25a98f5ec5b71400bb0163f891f509b566"
|
hash = "sha1-736aec25a98f5ec5b71400bb0163f891f509b566"
|
||||||
other = "Es ist ein Fehler aufgetreten"
|
other = "Es ist ein Fehler aufgetreten"
|
||||||
|
|
||||||
[GreetingAnonymous]
|
[IndexGreeting]
|
||||||
hash = "sha1-f7ff9e8b7bb2e09b70935a5d785e0cc5d9d0abf0"
|
hash = "sha1-d4a13058e497fa24143ea96d50d82b818455ef61"
|
||||||
other = "Hallo"
|
other = "Hallo {{ .User }}"
|
||||||
|
|
||||||
[GreetingAuthenticated]
|
|
||||||
hash = "sha1-22e08bfee49f285ac06df7d582c2a65bab86fa35"
|
|
||||||
other = "Hallo {{ .Name }}"
|
|
||||||
|
|
||||||
[IndexIntroductionText]
|
[IndexIntroductionText]
|
||||||
hash = "sha1-e190189ce0b76d957315332b6ca336f90a4d3d8c"
|
hash = "sha1-c2c530e263fc9c38482338ed290aafb496794179"
|
||||||
other = "Das ist eine öffentliche Resource."
|
other = "Das ist eine zugriffsgeschützte Resource"
|
||||||
|
|
||||||
[IndexNavLabel]
|
|
||||||
description = "Label for the index page in the top navigation"
|
|
||||||
hash = "sha1-f99709e3c9205f21ca31811feec86519b6c1b452"
|
|
||||||
other = "Willkommen"
|
|
||||||
|
|
||||||
[IndexTitle]
|
[IndexTitle]
|
||||||
hash = "sha1-eccb2b889c068d3f25496c1dad3fb0f88d021bd9"
|
hash = "sha1-eccb2b889c068d3f25496c1dad3fb0f88d021bd9"
|
||||||
other = "Willkommen in der Demo-Anwendung"
|
other = "Willkommen in der Demo-Anwendung"
|
||||||
|
|
||||||
[LoginLabel]
|
|
||||||
description = "A label on a login button or link"
|
|
||||||
hash = "sha1-2049c9ee0610e70f7316f98415755b58067e68ee"
|
|
||||||
other = "Anmelden"
|
|
||||||
|
|
||||||
[LogoutLabel]
|
[LogoutLabel]
|
||||||
description = "A label on a logout button or link"
|
description = "A label on a logout button or link"
|
||||||
hash = "sha1-8acfdeb9a8286f00c8e5dd48471cfdc994807579"
|
hash = "sha1-8acfdeb9a8286f00c8e5dd48471cfdc994807579"
|
||||||
other = "Ausloggen"
|
other = "Ausloggen"
|
||||||
|
|
||||||
[ProtectedIntroductionText]
|
|
||||||
hash = "sha1-87cd7874f28dfcebcb5460d30b6c2c78dff8f6a4"
|
|
||||||
other = "Das ist eine zugriffsgeschützte Resource."
|
|
||||||
|
|
||||||
[ProtectedNavLabel]
|
|
||||||
description = "Label for the protected resource page in the top navigation"
|
|
||||||
hash = "sha1-185c4cf5675f44de72ff76f16e6cab2a82afa752"
|
|
||||||
other = "Geschützter Bereich"
|
|
||||||
|
|
|
@ -1,23 +1,8 @@
|
||||||
AuthenticatedAs = "The identity provider authenticated your identity as {{ .Name }} with the email address {{ .Email }}."
|
|
||||||
ErrorTitle = "An error has occurred"
|
ErrorTitle = "An error has occurred"
|
||||||
GreetingAnonymous = "Hello"
|
IndexGreeting = "Hello {{ .User }}"
|
||||||
GreetingAuthenticated = "Hello {{ .Name }}"
|
IndexIntroductionText = "This is an authorization protected resource"
|
||||||
IndexIntroductionText = "This is a public resource."
|
|
||||||
IndexTitle = "Welcome to the Demo application"
|
IndexTitle = "Welcome to the Demo application"
|
||||||
ProtectedIntroductionText = "This is an authorization protected resource."
|
|
||||||
|
|
||||||
[IndexNavLabel]
|
|
||||||
description = "Label for the index page in the top navigation"
|
|
||||||
other = "Welcome"
|
|
||||||
|
|
||||||
[LoginLabel]
|
|
||||||
description = "A label on a login button or link"
|
|
||||||
other = "Login"
|
|
||||||
|
|
||||||
[LogoutLabel]
|
[LogoutLabel]
|
||||||
description = "A label on a logout button or link"
|
description = "A label on a logout button or link"
|
||||||
other = "Logout"
|
other = "Logout"
|
||||||
|
|
||||||
[ProtectedNavLabel]
|
|
||||||
description = "Label for the protected resource page in the top navigation"
|
|
||||||
other = "Protected area"
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
|
@ -27,21 +27,12 @@
|
||||||
<title>{{ .Title }}</title>
|
<title>{{ .Title }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body class="resource-app d-flex flex-column h-100">
|
<body class="resource-app d-flex flex-column h-100">
|
||||||
<header class="container flex-shrink-0">
|
<main role="main" class="flex-shrink-0">
|
||||||
<a href="/"><img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4"></a>
|
{{ template "content" . }}
|
||||||
<ul class="nav">
|
</main>
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/">{{ .WelcomeNavLabel }}</a>
|
|
||||||
</li>
|
|
||||||
<li class="nav-item">
|
|
||||||
<a class="nav-link" href="/protected">{{ .ProtectedNavLabel }}</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</header>
|
|
||||||
{{ template "content" . }}
|
|
||||||
<footer class="footer mt-auto py-3">
|
<footer class="footer mt-auto py-3">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<span class="text-muted small">© <a href="https://www.cacert.org/">CAcert</a></span>
|
<span class="text-muted small">© 2020-2023 <a href="https://www.cacert.org/">CAcert</a></span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
<script type="text/javascript" src="/js/cacert.bundle.js"></script>
|
<script type="text/javascript" src="/js/cacert.bundle.js"></script>
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<main class="container error-message">
|
<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>
|
<h1>{{ .Title }}</h1>
|
||||||
<h2>{{ if .details.ErrorCode }}
|
<h2>{{ if .details.ErrorCode }}
|
||||||
<strong>{{ .details.ErrorCode }}</strong> {{ end }}{{ .details.ErrorMessage }}</h2>
|
<strong>{{ .details.ErrorCode }}</strong> {{ end }}{{ .details.ErrorMessage }}</h2>
|
||||||
|
@ -8,5 +9,5 @@
|
||||||
<p>{{ . }}</p>
|
<p>{{ . }}</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</main>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
|
@ -1,12 +1,8 @@
|
||||||
{{ define "content" }}
|
{{ define "content" }}
|
||||||
<main class="container">
|
<div class="container">
|
||||||
|
<img src="/images/CAcert-logo.svg" width="300" height="68" alt="CAcert" class="mb-4">
|
||||||
<h1>{{ .Greeting }}</h1>
|
<h1>{{ .Greeting }}</h1>
|
||||||
<p>{{ .IntroductionText }}</p>
|
<p>{{ .IntroductionText }}</p>
|
||||||
{{ if .IsAuthenticated }}
|
<a class="btn btn-outline-primary" href="{{ .LogoutURL }}">{{ .LogoutLabel }}</a>
|
||||||
<p>{{ .AuthenticatedAs }}</p>
|
</div>
|
||||||
<a class="btn btn-outline-primary" href="{{ .LogoutURL }}">{{ .LogoutLabel }}</a>
|
|
||||||
{{ else }}
|
|
||||||
<a class="btn btn-primary" href="/login">{{ .LoginLabel }}</a>
|
|
||||||
{{ end }}
|
|
||||||
</main>
|
|
||||||
{{ end }}
|
{{ end }}
|
|
@ -1,8 +0,0 @@
|
||||||
{{ define "content" }}
|
|
||||||
<main class="container">
|
|
||||||
<h1>{{ .Greeting }}</h1>
|
|
||||||
<p>{{ .IntroductionText }}</p>
|
|
||||||
<p>{{ .AuthenticatedAs }}</p>
|
|
||||||
<a class="btn btn-outline-primary" href="{{ .LogoutURL }}">{{ .LogoutLabel }}</a>
|
|
||||||
</main>
|
|
||||||
{{ end }}
|
|
2
ui/ui.go
2
ui/ui.go
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
Copyright CAcert Inc.
|
Copyright 2020-2023 CAcert Inc.
|
||||||
SPDX-License-Identifier: Apache-2.0
|
SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
|
Loading…
Reference in a new issue