diff --git a/app/handlers/common.go b/app/handlers/common.go index 59a1a53..6809567 100644 --- a/app/handlers/common.go +++ b/app/handlers/common.go @@ -5,6 +5,9 @@ import ( "net/http" "net/url" + "github.com/lestrrat-go/jwx/jwk" + "github.com/lestrrat-go/jwx/jwt" + "github.com/lestrrat-go/jwx/jwt/openid" "golang.org/x/oauth2" "git.cacert.org/oidc_login/app/services" @@ -21,7 +24,7 @@ func Authenticate(oauth2Config *oauth2.Config, clientId string) func(http.Handle http.Error(w, err.Error(), http.StatusInternalServerError) return } - if _, ok := session.Values[sessionKeyUserId]; ok { + if _, ok := session.Values[sessionKeyIdToken]; ok { next.ServeHTTP(w, r) return } @@ -47,3 +50,12 @@ func Authenticate(oauth2Config *oauth2.Config, clientId string) func(http.Handle }) } } + +func ParseIdToken(token string, keySet *jwk.Set) (openid.Token, error) { + if parsedIdToken, err := jwt.ParseString(token, jwt.WithKeySet(keySet), jwt.WithOpenIDClaims()); err != nil { + return nil, err + } else { + return parsedIdToken.(openid.Token), nil + } + +} diff --git a/app/handlers/index.go b/app/handlers/index.go index 293bc9b..5dbc595 100644 --- a/app/handlers/index.go +++ b/app/handlers/index.go @@ -6,12 +6,15 @@ import ( "net/http" "net/url" + "github.com/lestrrat-go/jwx/jwk" + "git.cacert.org/oidc_login/app/services" ) type indexHandler struct { logoutUrl string serverAddr string + keySet *jwk.Set } func (h *indexHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { @@ -51,21 +54,26 @@ func (h *indexHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque http.Error(writer, err.Error(), http.StatusInternalServerError) return } - var user string + var idToken string var ok bool - if user, ok = session.Values[sessionKeyUsername].(string); ok { - - } - if idToken, ok := session.Values[sessionKeyIdToken].(string); ok { + if idToken, ok = session.Values[sessionKeyIdToken].(string); ok { logoutUrl.RawQuery = url.Values{ "id_token_hint": []string{idToken}, "post_logout_redirect_uri": []string{fmt.Sprintf("https://%s/after-logout", h.serverAddr)}, }.Encode() + } else { + return + } + + oidcToken, err := ParseIdToken(idToken, h.keySet) + if err != nil { + http.Error(writer, err.Error(), http.StatusInternalServerError) + return } writer.Header().Add("Content-Type", "text/html") err = page.Execute(writer, map[string]interface{}{ - "User": user, + "User": oidcToken.Name(), "LogoutURL": logoutUrl.String(), }) if err != nil { @@ -74,6 +82,6 @@ func (h *indexHandler) ServeHTTP(writer http.ResponseWriter, request *http.Reque } } -func NewIndexHandler(logoutUrl string, serverAddr string) *indexHandler { - return &indexHandler{logoutUrl: logoutUrl, serverAddr: serverAddr} +func NewIndexHandler(logoutUrl string, serverAddr string, keySet *jwk.Set) *indexHandler { + return &indexHandler{logoutUrl: logoutUrl, serverAddr: serverAddr, keySet: keySet} } diff --git a/app/handlers/oidc_callback.go b/app/handlers/oidc_callback.go index cf46dc1..d017f16 100644 --- a/app/handlers/oidc_callback.go +++ b/app/handlers/oidc_callback.go @@ -6,8 +6,7 @@ import ( "github.com/go-openapi/runtime/client" "github.com/lestrrat-go/jwx/jwk" - "github.com/lestrrat-go/jwx/jwt" - "github.com/sirupsen/logrus" + log "github.com/sirupsen/logrus" "golang.org/x/oauth2" "git.cacert.org/oidc_login/app/services" @@ -17,10 +16,6 @@ const ( sessionKeyAccessToken = iota sessionKeyRefreshToken sessionKeyIdToken - sessionKeyUserId - sessionKeyRoles - sessionKeyEmail - sessionKeyUsername sessionRedirectTarget ) @@ -29,17 +24,24 @@ type oidcCallbackHandler struct { oauth2Config *oauth2.Config } -func (c *oidcCallbackHandler) ServeHTTP(writer http.ResponseWriter, request *http.Request) { - if request.Method != http.MethodGet { - http.Error(writer, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) +func (c *oidcCallbackHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } - if request.URL.Path != "/callback" { - http.NotFound(writer, request) + if r.URL.Path != "/callback" { + http.NotFound(w, r) return } - code := request.URL.Query().Get("code") + errorText := r.URL.Query().Get("error") + errorDescription := r.URL.Query().Get("error_description") + if errorText != "" { + c.RenderErrorTemplate(w, r, errorText, errorDescription) + return + } + + code := r.URL.Query().Get("code") ctx := context.Background() httpClient, err := client.TLSClient(client.TLSClientOptions{InsecureSkipVerify: true}) @@ -47,28 +49,29 @@ func (c *oidcCallbackHandler) ServeHTTP(writer http.ResponseWriter, request *htt tok, err := c.oauth2Config.Exchange(ctx, code) if err != nil { - logrus.Error(err) - http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + log.Error(err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) return } - session, err := services.GetSessionStore().Get(request, "resource_session") + session, err := services.GetSessionStore().Get(r, "resource_session") if err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) + http.Error(w, err.Error(), http.StatusInternalServerError) return } session.Values[sessionKeyAccessToken] = tok.AccessToken session.Values[sessionKeyRefreshToken] = tok.RefreshToken - session.Values[sessionKeyIdToken] = tok.Extra("id_token").(string) - idToken := tok.Extra("id_token") - if parsedIdToken, err := jwt.ParseString(idToken.(string), jwt.WithKeySet(c.keySet), jwt.WithOpenIDClaims()); err != nil { - logrus.Error(err) - http.Error(writer, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + idToken := tok.Extra("id_token").(string) + session.Values[sessionKeyIdToken] = idToken + + if oidcToken, err := ParseIdToken(idToken, c.keySet); err != nil { + log.Error(err) + http.Error(w, err.Error(), http.StatusInternalServerError) return } else { - logrus.Infof(` + log.Infof(` ID Token ======== @@ -80,36 +83,33 @@ Not valid before: %s Not valid after: %s `, - parsedIdToken.Subject(), - parsedIdToken.Audience(), - parsedIdToken.IssuedAt(), - parsedIdToken.Issuer(), - parsedIdToken.NotBefore(), - parsedIdToken.Expiration(), + oidcToken.Subject(), + oidcToken.Audience(), + oidcToken.IssuedAt(), + oidcToken.Issuer(), + oidcToken.NotBefore(), + oidcToken.Expiration(), ) - - session.Values[sessionKeyUserId] = parsedIdToken.Subject() - - if roles, ok := parsedIdToken.Get("Groups"); ok { - session.Values[sessionKeyRoles] = roles - } - if username, ok := parsedIdToken.Get("Username"); ok { - session.Values[sessionKeyUsername] = username - } - if email, ok := parsedIdToken.Get("Email"); ok { - session.Values[sessionKeyEmail] = email - } } - if err = session.Save(request, writer); err != nil { - http.Error(writer, err.Error(), http.StatusInternalServerError) + + if err = session.Save(r, w); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) } if redirectTarget, ok := session.Values[sessionRedirectTarget]; ok { - writer.Header().Set("Location", redirectTarget.(string)) + w.Header().Set("Location", redirectTarget.(string)) } else { - writer.Header().Set("Location", "/") + w.Header().Set("Location", "/") } - writer.WriteHeader(http.StatusFound) + w.WriteHeader(http.StatusFound) +} + +func (c *oidcCallbackHandler) RenderErrorTemplate(w http.ResponseWriter, r *http.Request, errorText string, errorDescription string) { + if errorDescription != "" { + http.Error(w, errorDescription, http.StatusForbidden) + } else { + http.Error(w, errorText, http.StatusForbidden) + } } func NewCallbackHandler(keySet *jwk.Set, oauth2Config *oauth2.Config) *oidcCallbackHandler { diff --git a/cmd/app/main.go b/cmd/app/main.go index 7d7349d..9fd99d3 100644 --- a/cmd/app/main.go +++ b/cmd/app/main.go @@ -133,7 +133,7 @@ func main() { authMiddleware := handlers.Authenticate(oauth2Config, config.MustString("oidc.client-id")) serverAddr := fmt.Sprintf("%s:%d", config.String("server.name"), config.Int("server.port")) - indexHandler := handlers.NewIndexHandler(discoveryResponse.EndSessionEndpoint, serverAddr) + indexHandler := handlers.NewIndexHandler(discoveryResponse.EndSessionEndpoint, serverAddr, keySet) callbackHandler := handlers.NewCallbackHandler(keySet, oauth2Config) afterLogoutHandler := handlers.NewAfterLogoutHandler(logger) diff --git a/cmd/idp/main.go b/cmd/idp/main.go index 2a3bf05..120a076 100644 --- a/cmd/idp/main.go +++ b/cmd/idp/main.go @@ -86,7 +86,10 @@ func main() { if err != nil { logger.Fatalf("error initializing login handler: %v", err) } - consentHandler := handlers.NewConsentHandler(logger, handlerContext) + consentHandler, err := handlers.NewConsentHandler(logger, handlerContext) + if err != nil { + logger.Fatalf("error initializing consent handler: %v", err) + } logoutHandler := handlers.NewLogoutHandler(logger, handlerContext) logoutSuccessHandler := handlers.NewLogoutSuccessHandler() errorHandler := handlers.NewErrorHandler() diff --git a/idp/handlers/consent.go b/idp/handlers/consent.go index 3e78189..fd6f1e2 100644 --- a/idp/handlers/consent.go +++ b/idp/handlers/consent.go @@ -2,40 +2,166 @@ package handlers import ( "context" + "fmt" + "html/template" "net/http" "time" + "github.com/go-playground/form/v4" + "github.com/gorilla/csrf" + "github.com/lestrrat-go/jwx/jwt/openid" + "github.com/nicksnyder/go-i18n/v2/i18n" "github.com/ory/hydra-client-go/client/admin" "github.com/ory/hydra-client-go/models" log "github.com/sirupsen/logrus" + + "git.cacert.org/oidc_login/idp/services" ) type consentHandler struct { - adminClient *admin.Client - logger *log.Logger + adminClient *admin.Client + bundle *i18n.Bundle + consentTemplate *template.Template + logger *log.Logger + messageCatalog *services.MessageCatalog +} + +type ConsentInformation struct { + ConsentChecked bool `form:"consent"` } func (h *consentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { - consentChallenge := r.URL.Query().Get("consent_challenge") - consentRequest, err := h.adminClient.AcceptConsentRequest( - admin.NewAcceptConsentRequestParams().WithConsentChallenge(consentChallenge).WithBody( - &models.AcceptConsentRequest{ - GrantAccessTokenAudience: nil, - GrantScope: []string{"openid", "offline"}, - HandledAt: models.NullTime(time.Now()), - Remember: true, - RememberFor: 86400, - }).WithTimeout(time.Second * 10)) + challenge := r.URL.Query().Get("consent_challenge") + h.logger.Debugf("received consent challenge %s", challenge) + accept := r.Header.Get("Accept-Language") + localizer := i18n.NewLocalizer(h.bundle, accept) + + // retrieve consent information + consentData, err := h.adminClient.GetConsentRequest( + admin.NewGetConsentRequestParams().WithConsentChallenge(challenge)) if err != nil { - h.logger.Panic(err) + h.logger.Error("error getting consent information: %v", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + switch r.Method { + case http.MethodGet: + trans := h.messageCatalog.LookupMessage + + // render consent form + client := consentData.GetPayload().Client + err = h.consentTemplate.Lookup("base").Execute(w, map[string]interface{}{ + "Title": trans("TitleRequestConsent", nil, localizer), + csrf.TemplateTag: csrf.TemplateField(r), + "errors": map[string]string{}, + "client": client, + "requestedScope": h.mapRequestedScope(consentData.GetPayload().RequestedScope, localizer), + "LabelSubmit": trans("LabelSubmit", nil, localizer), + "LabelConsent": trans("LabelConsent", nil, localizer), + "IntroMoreInformation": template.HTML(trans("IntroConsentMoreInformation", map[string]interface{}{ + "client": client.ClientName, + "clientLink": client.ClientURI, + }, localizer)), + "IntroConsentRequested": template.HTML(trans("IntroConsentRequested", map[string]interface{}{ + "client": client.ClientName, + }, localizer)), + }) + break + case http.MethodPost: + var consentInfo ConsentInformation + + // validate input + decoder := form.NewDecoder() + if err := decoder.Decode(&consentInfo, r.Form); err != nil { + h.logger.Error(err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + + if consentInfo.ConsentChecked { + idTokenData := make(map[string]interface{}, 0) + + for _, scope := range consentData.GetPayload().RequestedScope { + switch scope { + case "email": + idTokenData[openid.EmailKey] = "john@theripper.mil" + idTokenData[openid.EmailVerifiedKey] = true + break + case "profile": + idTokenData[openid.GivenNameKey] = "John" + idTokenData[openid.FamilyNameKey] = "The ripper" + idTokenData[openid.MiddleNameKey] = "" + idTokenData[openid.NameKey] = "John the Ripper" + idTokenData[openid.BirthdateKey] = "1970-01-01" + idTokenData[openid.ZoneinfoKey] = "Europe/London" + idTokenData[openid.LocaleKey] = "en_UK" + idTokenData["https://cacert.localhost/groups"] = []string{"admin", "user"} + break + } + } + + sessionData := &models.ConsentRequestSession{ + AccessToken: nil, + IDToken: idTokenData, + } + consentRequest, err := h.adminClient.AcceptConsentRequest( + admin.NewAcceptConsentRequestParams().WithConsentChallenge(challenge).WithBody( + &models.AcceptConsentRequest{ + GrantAccessTokenAudience: nil, + GrantScope: consentData.GetPayload().RequestedScope, + HandledAt: models.NullTime(time.Now()), + Remember: true, + RememberFor: 86400, + Session: sessionData, + }).WithTimeout(time.Second * 10)) + if err != nil { + h.logger.Error(err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo) + w.WriteHeader(http.StatusFound) + } else { + consentRequest, err := h.adminClient.RejectConsentRequest( + admin.NewRejectConsentRequestParams().WithConsentChallenge(challenge).WithBody( + &models.RejectRequest{})) + if err != nil { + h.logger.Error(err) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } + w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo) + w.WriteHeader(http.StatusFound) + } } - w.Header().Add("Location", *consentRequest.GetPayload().RedirectTo) - w.WriteHeader(http.StatusFound) } -func NewConsentHandler(logger *log.Logger, ctx context.Context) *consentHandler { - return &consentHandler{ - logger: logger, - adminClient: ctx.Value(CtxAdminClient).(*admin.Client), - } +type scopeWithLabel struct { + Name string + Label string +} + +func (h *consentHandler) mapRequestedScope(scope models.StringSlicePipeDelimiter, localizer *i18n.Localizer) []*scopeWithLabel { + result := make([]*scopeWithLabel, 0) + for _, scopeName := range scope { + result = append(result, &scopeWithLabel{Name: scopeName, Label: h.messageCatalog.LookupMessage( + fmt.Sprintf("Scope-%s-Description", scopeName), nil, localizer)}) + } + return result +} + +func NewConsentHandler(logger *log.Logger, ctx context.Context) (*consentHandler, error) { + consentTemplate, err := template.ParseFiles("templates/base.gohtml", "templates/consent.gohtml") + if err != nil { + return nil, err + } + + return &consentHandler{ + adminClient: ctx.Value(CtxAdminClient).(*admin.Client), + bundle: ctx.Value(services.CtxI18nBundle).(*i18n.Bundle), + consentTemplate: consentTemplate, + logger: logger, + messageCatalog: ctx.Value(services.CtxI18nCatalog).(*services.MessageCatalog), + }, nil } diff --git a/idp/handlers/login.go b/idp/handlers/login.go index 15eb76b..e913d2f 100644 --- a/idp/handlers/login.go +++ b/idp/handlers/login.go @@ -2,11 +2,11 @@ package handlers import ( "context" - "fmt" "html/template" "net/http" "time" + "github.com/go-openapi/runtime" "github.com/go-playground/form/v4" "github.com/go-playground/validator/v10" "github.com/gorilla/csrf" @@ -19,13 +19,25 @@ import ( ) type loginHandler struct { - loginTemplate *template.Template - bundle *i18n.Bundle - messageCatalog map[string]*i18n.Message adminClient *admin.Client + bundle *i18n.Bundle logger *log.Logger + loginTemplate *template.Template + messageCatalog *services.MessageCatalog } +type acrType string + +const ( + NoCredentials acrType = "none" + ClientCertificate acrType = "cert" + ClientCertificateOTP acrType = "cert+otp" + ClientCertificateToken acrType = "cert+token" + Password acrType = "password" + PasswordOTP acrType = "password+otp" + PasswordToken acrType = "password+token" +) + type LoginInformation struct { Email string `form:"email" validate:"required,email"` Password string `form:"password" validate:"required"` @@ -34,13 +46,12 @@ type LoginInformation struct { func (h *loginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { var err error challenge := r.URL.Query().Get("login_challenge") - h.logger.Debugf("received challenge %s\n", challenge) + h.logger.Debugf("received login challenge %s\n", challenge) validate := validator.New() switch r.Method { case http.MethodGet: - // GET should render login form - + // render login form err = h.loginTemplate.Lookup("base").Execute(w, map[string]interface{}{ "Title": "Title", csrf.TemplateTag: csrf.TemplateField(r), @@ -56,7 +67,6 @@ func (h *loginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } break case http.MethodPost: - // POST should perform the action var loginInfo LoginInformation // validate input @@ -72,7 +82,7 @@ func (h *loginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { errors := make(map[string]string) for _, err := range err.(validator.ValidationErrors) { accept := r.Header.Get("Accept-Language") - errors[err.Field()] = h.lookupErrorMessage(err.Tag(), err.Field(), err.Value(), i18n.NewLocalizer(h.bundle, accept)) + errors[err.Field()] = h.messageCatalog.LookupErrorMessage(err.Tag(), err.Field(), err.Value(), i18n.NewLocalizer(h.bundle, accept)) } err = h.loginTemplate.Lookup("base").Execute(w, map[string]interface{}{ @@ -98,14 +108,15 @@ func (h *loginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { subject := "a-user-with-an-id" loginRequest, err := h.adminClient.AcceptLoginRequest( admin.NewAcceptLoginRequestParams().WithLoginChallenge(challenge).WithBody(&models.AcceptLoginRequest{ - Acr: "no-creds", + Acr: string(NoCredentials), Remember: true, RememberFor: 0, Subject: &subject, }).WithTimeout(time.Second * 10)) if err != nil { - h.logger.Errorf("error getting logout requests: %v", err) - http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + h.logger.Errorf("error getting login request: %#v", err) + http.Error(w, err.Error(), err.(*runtime.APIError).Code) + return } w.Header().Add("Location", *loginRequest.GetPayload().RedirectTo) w.WriteHeader(http.StatusFound) @@ -116,45 +127,16 @@ func (h *loginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { } } -func (h *loginHandler) lookupErrorMessage(tag string, field string, value interface{}, l *i18n.Localizer) string { - var message *i18n.Message - message, ok := h.messageCatalog[fmt.Sprintf("%s-%s", field, tag)] - if !ok { - h.logger.Infof("no specific error message %s-%s", field, tag) - message, ok = h.messageCatalog[tag] - if !ok { - h.logger.Infof("no specific error message %s", tag) - message, ok = h.messageCatalog["unknown"] - if !ok { - h.logger.Error("no default translation found") - return tag - } - } - } - - translation, err := l.Localize(&i18n.LocalizeConfig{ - DefaultMessage: message, - TemplateData: map[string]interface{}{ - "Value": value, - }, - }) - if err != nil { - h.logger.Error(err) - return tag - } - return translation -} - func NewLoginHandler(logger *log.Logger, ctx context.Context) (*loginHandler, error) { - loginTemplate, err := template.ParseFiles("templates/base.html", "templates/login.html") + loginTemplate, err := template.ParseFiles("templates/base.gohtml", "templates/login.gohtml") if err != nil { return nil, err } return &loginHandler{ + adminClient: ctx.Value(CtxAdminClient).(*admin.Client), + bundle: ctx.Value(services.CtxI18nBundle).(*i18n.Bundle), logger: logger, loginTemplate: loginTemplate, - bundle: ctx.Value(services.CtxI18nBundle).(*i18n.Bundle), - messageCatalog: ctx.Value(services.CtxI18nCatalog).(map[string]*i18n.Message), - adminClient: ctx.Value(CtxAdminClient).(*admin.Client), + messageCatalog: ctx.Value(services.CtxI18nCatalog).(*services.MessageCatalog), }, nil } diff --git a/idp/services/i18n.go b/idp/services/i18n.go index edb25dd..894b6a4 100644 --- a/idp/services/i18n.go +++ b/idp/services/i18n.go @@ -2,6 +2,7 @@ package services import ( "context" + "fmt" "github.com/BurntSushi/toml" "github.com/nicksnyder/go-i18n/v2/i18n" @@ -16,6 +17,56 @@ const ( CtxI18nCatalog ) +type MessageCatalog struct { + messages map[string]*i18n.Message + logger *log.Logger +} + +func (m *MessageCatalog) LookupErrorMessage(tag string, field string, value interface{}, localizer *i18n.Localizer) string { + var message *i18n.Message + message, ok := m.messages[fmt.Sprintf("%s-%s", field, tag)] + if !ok { + m.logger.Infof("no specific error message %s-%s", field, tag) + message, ok = m.messages[tag] + if !ok { + m.logger.Infof("no specific error message %s", tag) + message, ok = m.messages["unknown"] + if !ok { + m.logger.Error("no default translation found") + return tag + } + } + } + + translation, err := localizer.Localize(&i18n.LocalizeConfig{ + DefaultMessage: message, + TemplateData: map[string]interface{}{ + "Value": value, + }, + }) + if err != nil { + m.logger.Error(err) + return tag + } + return translation +} + +func (m *MessageCatalog) LookupMessage(id string, templateData map[string]interface{}, localizer *i18n.Localizer) string { + if message, ok := m.messages[id]; ok { + translation, err := localizer.Localize(&i18n.LocalizeConfig{ + DefaultMessage: message, + TemplateData: templateData, + }) + if err != nil { + m.logger.Error(err) + return id + } + return translation + } else { + return id + } +} + func InitI18n(ctx context.Context, logger *log.Logger) context.Context { bundle := i18n.NewBundle(language.English) bundle.RegisterUnmarshalFunc("toml", toml.Unmarshal) @@ -23,33 +74,69 @@ func InitI18n(ctx context.Context, logger *log.Logger) context.Context { if err != nil { logger.Warnln("message bundle de.toml not found") } - catalog := initMessageCatalog() + catalog := initMessageCatalog(logger) ctx = context.WithValue(ctx, CtxI18nBundle, bundle) ctx = context.WithValue(ctx, CtxI18nCatalog, catalog) return ctx } -func initMessageCatalog() map[string]*i18n.Message { - messageCatalog := make(map[string]*i18n.Message) - messageCatalog["unknown"] = &i18n.Message{ +func initMessageCatalog(logger *log.Logger) *MessageCatalog { + messages := make(map[string]*i18n.Message) + messages["unknown"] = &i18n.Message{ ID: "ErrorUnknown", Other: "Unknown error", } - messageCatalog["email"] = &i18n.Message{ + messages["email"] = &i18n.Message{ ID: "ErrorEmail", Other: "Please enter a valid email address.", } - messageCatalog["Email-required"] = &i18n.Message{ + messages["Email-required"] = &i18n.Message{ ID: "ErrorEmailRequired", Other: "Please enter an email address.", } - messageCatalog["required"] = &i18n.Message{ + messages["required"] = &i18n.Message{ ID: "ErrorRequired", Other: "Please enter a value", } - messageCatalog["Password-required"] = &i18n.Message{ + messages["Password-required"] = &i18n.Message{ ID: "ErrorPasswordRequired", Other: "Please enter a password.", } - return messageCatalog + messages["TitleRequestConsent"] = &i18n.Message{ + ID: "TitleRequestConsent", + Other: "Application requests your consent", + } + messages["LabelSubmit"] = &i18n.Message{ + ID: "LabelSubmit", + Other: "Submit", + } + messages["LabelConsent"] = &i18n.Message{ + ID: "LabelConsent", + Other: "I hereby agree that the application may get the requested permissions.", + } + messages["IntroConsentRequested"] = &i18n.Message{ + ID: "IntroConsentRequested", + Other: "The {{ .client }} application wants your consent for the requested set of permissions.", + } + messages["IntroConsentMoreInformation"] = &i18n.Message{ + ID: "IntroConsentMoreInformation", + Other: "You can find more information about {{ .client }} at its description page.", + } + messages["Scope-openid-Description"] = &i18n.Message{ + ID: "Scope-openid-Description", + Other: "Request information about your identity.", + } + messages["Scope-offline_access-Description"] = &i18n.Message{ + ID: "Scope-offline_access-Description", + Other: "Keep access to your information until you revoke the permission.", + } + messages["Scope-profile-Description"] = &i18n.Message{ + ID: "Scope-profile-Description", + Other: "Access your user profile information including your name, birth date and locale.", + } + messages["Scope-email-Description"] = &i18n.Message{ + ID: "Scope-email-Description", + Other: "Access your primary email address.", + } + return &MessageCatalog{messages: messages, logger: logger} } diff --git a/templates/base.gohtml b/templates/base.gohtml new file mode 100644 index 0000000..6a2c32e --- /dev/null +++ b/templates/base.gohtml @@ -0,0 +1,10 @@ +{{ define "base" }} + + + {{ .Title }} + +

{{ .Title }}

+ {{ template "content" . }} + + +{{ end }} \ No newline at end of file diff --git a/templates/base.html b/templates/base.html deleted file mode 100644 index 40d0b04..0000000 --- a/templates/base.html +++ /dev/null @@ -1,10 +0,0 @@ -{{ define "base" }} - - -{{ .Title }} - -

{{ .Title }}

-{{ template "content" . }} - - -{{ end }} \ No newline at end of file diff --git a/templates/consent.gohtml b/templates/consent.gohtml new file mode 100644 index 0000000..4c47ff1 --- /dev/null +++ b/templates/consent.gohtml @@ -0,0 +1,24 @@ +{{ define "content" }} +

{{ .IntroConsentRequested }}

+ {{ if .client.LogoURI }} +

+ {{ .client.ClientName }} +

+ {{ end }} +

{{ .IntroMoreInformation }}

+
+ {{ .csrfField }} + + +

+ +

+ + +
+{{ end }} \ No newline at end of file diff --git a/templates/login.html b/templates/login.gohtml similarity index 100% rename from templates/login.html rename to templates/login.gohtml