2020-12-31 09:42:48 +01:00
|
|
|
package handlers
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"html/template"
|
|
|
|
"net/http"
|
|
|
|
"time"
|
|
|
|
|
2020-12-31 19:11:06 +01:00
|
|
|
"github.com/go-openapi/runtime"
|
2020-12-31 09:42:48 +01:00
|
|
|
"github.com/go-playground/form/v4"
|
|
|
|
"github.com/go-playground/validator/v10"
|
|
|
|
"github.com/gorilla/csrf"
|
|
|
|
"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 loginHandler struct {
|
|
|
|
adminClient *admin.Client
|
2020-12-31 19:11:06 +01:00
|
|
|
bundle *i18n.Bundle
|
2020-12-31 13:19:21 +01:00
|
|
|
logger *log.Logger
|
2020-12-31 19:11:06 +01:00
|
|
|
loginTemplate *template.Template
|
|
|
|
messageCatalog *services.MessageCatalog
|
2020-12-31 09:42:48 +01:00
|
|
|
}
|
|
|
|
|
2020-12-31 19:11:06 +01:00
|
|
|
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"
|
|
|
|
)
|
|
|
|
|
2020-12-31 09:42:48 +01:00
|
|
|
type LoginInformation struct {
|
|
|
|
Email string `form:"email" validate:"required,email"`
|
|
|
|
Password string `form:"password" validate:"required"`
|
|
|
|
}
|
|
|
|
|
2020-12-31 13:19:21 +01:00
|
|
|
func (h *loginHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
2020-12-31 09:42:48 +01:00
|
|
|
var err error
|
2020-12-31 13:19:21 +01:00
|
|
|
challenge := r.URL.Query().Get("login_challenge")
|
2020-12-31 19:11:06 +01:00
|
|
|
h.logger.Debugf("received login challenge %s\n", challenge)
|
2020-12-31 09:42:48 +01:00
|
|
|
validate := validator.New()
|
|
|
|
|
2020-12-31 13:19:21 +01:00
|
|
|
switch r.Method {
|
2020-12-31 09:42:48 +01:00
|
|
|
case http.MethodGet:
|
2020-12-31 19:11:06 +01:00
|
|
|
// render login form
|
2020-12-31 13:19:21 +01:00
|
|
|
err = h.loginTemplate.Lookup("base").Execute(w, map[string]interface{}{
|
2020-12-31 09:42:48 +01:00
|
|
|
"Title": "Title",
|
2020-12-31 13:19:21 +01:00
|
|
|
csrf.TemplateTag: csrf.TemplateField(r),
|
2020-12-31 09:42:48 +01:00
|
|
|
"LabelEmail": "Email",
|
|
|
|
"LabelPassword": "Password",
|
|
|
|
"LabelLogin": "Login",
|
|
|
|
"errors": map[string]string{},
|
|
|
|
})
|
|
|
|
if err != nil {
|
2020-12-31 13:19:21 +01:00
|
|
|
h.logger.Error(err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
2020-12-31 09:42:48 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
break
|
|
|
|
case http.MethodPost:
|
|
|
|
var loginInfo LoginInformation
|
|
|
|
|
|
|
|
// validate input
|
|
|
|
decoder := form.NewDecoder()
|
2020-12-31 13:19:21 +01:00
|
|
|
err = decoder.Decode(&loginInfo, r.Form)
|
2020-12-31 09:42:48 +01:00
|
|
|
if err != nil {
|
2020-12-31 13:19:21 +01:00
|
|
|
h.logger.Error(err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
2020-12-31 09:42:48 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
err := validate.Struct(&loginInfo)
|
|
|
|
if err != nil {
|
|
|
|
errors := make(map[string]string)
|
|
|
|
for _, err := range err.(validator.ValidationErrors) {
|
2020-12-31 13:19:21 +01:00
|
|
|
accept := r.Header.Get("Accept-Language")
|
2020-12-31 19:11:06 +01:00
|
|
|
errors[err.Field()] = h.messageCatalog.LookupErrorMessage(err.Tag(), err.Field(), err.Value(), i18n.NewLocalizer(h.bundle, accept))
|
2020-12-31 09:42:48 +01:00
|
|
|
}
|
|
|
|
|
2020-12-31 13:19:21 +01:00
|
|
|
err = h.loginTemplate.Lookup("base").Execute(w, map[string]interface{}{
|
2020-12-31 09:42:48 +01:00
|
|
|
"Title": "Title",
|
2020-12-31 13:19:21 +01:00
|
|
|
csrf.TemplateTag: csrf.TemplateField(r),
|
2020-12-31 09:42:48 +01:00
|
|
|
"LabelEmail": "Email",
|
|
|
|
"LabelPassword": "Password",
|
|
|
|
"LabelLogin": "Login",
|
|
|
|
"Email": loginInfo.Email,
|
|
|
|
"errors": errors,
|
|
|
|
})
|
|
|
|
if err != nil {
|
2020-12-31 13:19:21 +01:00
|
|
|
h.logger.Error(err)
|
|
|
|
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
2020-12-31 09:42:48 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// GET user data
|
|
|
|
// finish login and redirect to target
|
|
|
|
// TODO: get or generate a user id
|
|
|
|
subject := "a-user-with-an-id"
|
|
|
|
loginRequest, err := h.adminClient.AcceptLoginRequest(
|
|
|
|
admin.NewAcceptLoginRequestParams().WithLoginChallenge(challenge).WithBody(&models.AcceptLoginRequest{
|
2020-12-31 19:11:06 +01:00
|
|
|
Acr: string(NoCredentials),
|
2020-12-31 09:42:48 +01:00
|
|
|
Remember: true,
|
|
|
|
RememberFor: 0,
|
|
|
|
Subject: &subject,
|
|
|
|
}).WithTimeout(time.Second * 10))
|
|
|
|
if err != nil {
|
2020-12-31 19:11:06 +01:00
|
|
|
h.logger.Errorf("error getting login request: %#v", err)
|
|
|
|
http.Error(w, err.Error(), err.(*runtime.APIError).Code)
|
|
|
|
return
|
2020-12-31 09:42:48 +01:00
|
|
|
}
|
2020-12-31 13:19:21 +01:00
|
|
|
w.Header().Add("Location", *loginRequest.GetPayload().RedirectTo)
|
|
|
|
w.WriteHeader(http.StatusFound)
|
2020-12-31 09:42:48 +01:00
|
|
|
break
|
|
|
|
default:
|
2020-12-31 13:19:21 +01:00
|
|
|
http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed)
|
2020-12-31 09:42:48 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-31 13:19:21 +01:00
|
|
|
func NewLoginHandler(logger *log.Logger, ctx context.Context) (*loginHandler, error) {
|
2020-12-31 19:11:06 +01:00
|
|
|
loginTemplate, err := template.ParseFiles("templates/base.gohtml", "templates/login.gohtml")
|
2020-12-31 09:42:48 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return &loginHandler{
|
2020-12-31 19:11:06 +01:00
|
|
|
adminClient: ctx.Value(CtxAdminClient).(*admin.Client),
|
|
|
|
bundle: ctx.Value(services.CtxI18nBundle).(*i18n.Bundle),
|
2020-12-31 13:19:21 +01:00
|
|
|
logger: logger,
|
2020-12-31 09:42:48 +01:00
|
|
|
loginTemplate: loginTemplate,
|
2020-12-31 19:11:06 +01:00
|
|
|
messageCatalog: ctx.Value(services.CtxI18nCatalog).(*services.MessageCatalog),
|
2020-12-31 09:42:48 +01:00
|
|
|
}, nil
|
|
|
|
}
|