Split "auth" and "user" routes
This commit is contained in:
parent
f3939c5016
commit
59b565ae19
15 changed files with 148 additions and 99 deletions
|
|
@ -27,13 +27,10 @@ var (
|
||||||
_ component.Routeable = (*Auth)(nil)
|
_ component.Routeable = (*Auth)(nil)
|
||||||
)
|
)
|
||||||
|
|
||||||
func (auth *Auth) Routes() []string { return []string{"/user/"} }
|
func (auth *Auth) Routes() []string { return []string{"/auth/"} }
|
||||||
|
|
||||||
func (auth *Auth) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
func (auth *Auth) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||||
router := httprouter.New()
|
router := httprouter.New()
|
||||||
|
|
||||||
router.Handler(http.MethodGet, route, auth.authUser(ctx))
|
|
||||||
|
|
||||||
{
|
{
|
||||||
login := auth.authLogin(ctx)
|
login := auth.authLogin(ctx)
|
||||||
router.Handler(http.MethodGet, route+"login", login)
|
router.Handler(http.MethodGet, route+"login", login)
|
||||||
|
|
@ -42,35 +39,12 @@ func (auth *Auth) HandleRoute(ctx context.Context, route string) (http.Handler,
|
||||||
|
|
||||||
router.Handler(http.MethodGet, route+"logout", auth.authLogout(ctx))
|
router.Handler(http.MethodGet, route+"logout", auth.authLogout(ctx))
|
||||||
|
|
||||||
{
|
|
||||||
password := auth.authPassword(ctx)
|
|
||||||
router.Handler(http.MethodGet, route+"password", password)
|
|
||||||
router.Handler(http.MethodPost, route+"password", password)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
totpenable := auth.authTOTPEnable(ctx)
|
|
||||||
router.Handler(http.MethodGet, route+"totp/enable", totpenable)
|
|
||||||
router.Handler(http.MethodPost, route+"totp/enable", totpenable)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
totpenroll := auth.authTOTPEnroll(ctx)
|
|
||||||
router.Handler(http.MethodGet, route+"totp/enroll", totpenroll)
|
|
||||||
router.Handler(http.MethodPost, route+"totp/enroll", totpenroll)
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
totpdisable := auth.authTOTPDisable(ctx)
|
|
||||||
router.Handler(http.MethodGet, route+"totp/disable", totpdisable)
|
|
||||||
router.Handler(http.MethodPost, route+"totp/disable", totpdisable)
|
|
||||||
}
|
|
||||||
|
|
||||||
return router, nil
|
return router, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) CSRF() func(http.Handler) http.Handler {
|
func (auth *Auth) CSRF() func(http.Handler) http.Handler {
|
||||||
// setup the csrf handler (if needed)
|
// setup the csrf handler (if needed)
|
||||||
|
// TOOD: This should move to the server handler
|
||||||
return auth.csrf.Get(func() func(http.Handler) http.Handler {
|
return auth.csrf.Get(func() func(http.Handler) http.Handler {
|
||||||
var opts []csrf.Option
|
var opts []csrf.Option
|
||||||
if !auth.Config.HTTPSEnabled() {
|
if !auth.Config.HTTPSEnabled() {
|
||||||
|
|
|
||||||
76
internal/dis/component/auth/panel/panel.go
Normal file
76
internal/dis/component/auth/panel/panel.go
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
package panel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
|
"github.com/julienschmidt/httprouter"
|
||||||
|
)
|
||||||
|
|
||||||
|
type UserPanel struct {
|
||||||
|
component.Base
|
||||||
|
Dependencies struct {
|
||||||
|
Auth *auth.Auth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ component.Routeable = (*UserPanel)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (panel *UserPanel) Routes() []string { return []string{"/user/"} }
|
||||||
|
|
||||||
|
func (panel *UserPanel) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||||
|
router := httprouter.New()
|
||||||
|
|
||||||
|
{
|
||||||
|
user := panel.routeUser(ctx)
|
||||||
|
router.Handler(http.MethodGet, route, user)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
password := panel.routePassword(ctx)
|
||||||
|
router.Handler(http.MethodGet, route+"password", password)
|
||||||
|
router.Handler(http.MethodPost, route+"password", password)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
totpenable := panel.routeTOTPEnable(ctx)
|
||||||
|
router.Handler(http.MethodGet, route+"totp/enable", totpenable)
|
||||||
|
router.Handler(http.MethodPost, route+"totp/enable", totpenable)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
totpenroll := panel.routeTOTPEnroll(ctx)
|
||||||
|
router.Handler(http.MethodGet, route+"totp/enroll", totpenroll)
|
||||||
|
router.Handler(http.MethodPost, route+"totp/enroll", totpenroll)
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
totpdisable := panel.routeTOTPDisable(ctx)
|
||||||
|
router.Handler(http.MethodGet, route+"totp/disable", totpdisable)
|
||||||
|
router.Handler(http.MethodPost, route+"totp/disable", totpdisable)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensure that the user is logged in!
|
||||||
|
return panel.Dependencies.Auth.Protect(router, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type userFormContext struct {
|
||||||
|
httpx.FormContext
|
||||||
|
User *models.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (panel *UserPanel) UserFormContext(ctx httpx.FormContext, r *http.Request) any {
|
||||||
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
|
|
||||||
|
uctx := userFormContext{FormContext: ctx}
|
||||||
|
if err == nil {
|
||||||
|
uctx.User = &user.User
|
||||||
|
}
|
||||||
|
return uctx
|
||||||
|
}
|
||||||
|
|
@ -1,29 +1,16 @@
|
||||||
package auth
|
package panel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
_ "embed"
|
|
||||||
"errors"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed "templates/user.html"
|
|
||||||
var userHTMLStr string
|
|
||||||
var userTemplate = static.AssetsUser.MustParseShared(
|
|
||||||
"user.html",
|
|
||||||
userHTMLStr,
|
|
||||||
)
|
|
||||||
|
|
||||||
func (auth *Auth) authUser(ctx context.Context) http.Handler {
|
|
||||||
return auth.Protect(&httpx.HTMLHandler[*AuthUser]{
|
|
||||||
Handler: auth.UserOf,
|
|
||||||
Template: userTemplate,
|
|
||||||
}, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
//go:embed "templates/password.html"
|
//go:embed "templates/password.html"
|
||||||
var passwordHTMLString string
|
var passwordHTMLString string
|
||||||
var passwordTemplate = static.AssetsUser.MustParseShared("password.html", passwordHTMLString)
|
var passwordTemplate = static.AssetsUser.MustParseShared("password.html", passwordHTMLString)
|
||||||
|
|
@ -37,23 +24,23 @@ var (
|
||||||
errPasswordSet = errors.New("password was updated")
|
errPasswordSet = errors.New("password was updated")
|
||||||
)
|
)
|
||||||
|
|
||||||
func (auth *Auth) authPassword(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routePassword(ctx context.Context) http.Handler {
|
||||||
return &httpx.Form[struct{}]{
|
return &httpx.Form[struct{}]{
|
||||||
Fields: []httpx.Field{
|
Fields: []httpx.Field{
|
||||||
{Name: "old", Type: httpx.PasswordField, EmptyOnError: true, Label: "Current Password"},
|
{Name: "old", Type: httpx.PasswordField, EmptyOnError: true, Label: "Current Password"},
|
||||||
{Name: "passcode", Type: httpx.TextField, EmptyOnError: true, Label: "Current Passcode (optional)"},
|
{Name: "otp", Type: httpx.TextField, EmptyOnError: true, Label: "Current Passcode (optional)"},
|
||||||
{Name: "new", Type: httpx.PasswordField, EmptyOnError: true, Label: "New Password"},
|
{Name: "new", Type: httpx.PasswordField, EmptyOnError: true, Label: "New Password"},
|
||||||
{Name: "new2", Type: httpx.PasswordField, EmptyOnError: true, Label: "New Password (again)"},
|
{Name: "new2", Type: httpx.PasswordField, EmptyOnError: true, Label: "New Password (again)"},
|
||||||
},
|
},
|
||||||
FieldTemplate: httpx.PureCSSFieldTemplate,
|
FieldTemplate: httpx.PureCSSFieldTemplate,
|
||||||
|
|
||||||
CSRF: auth.CSRF(),
|
CSRF: panel.Dependencies.Auth.CSRF(),
|
||||||
|
|
||||||
RenderTemplate: passwordTemplate,
|
RenderTemplate: passwordTemplate,
|
||||||
RenderTemplateContext: auth.UserFormContext,
|
RenderTemplateContext: panel.UserFormContext,
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
||||||
old, passcode, new, new2 := values["old"], values["passcode"], values["new"], values["new2"]
|
old, passcode, new, new2 := values["old"], values["otp"], values["new"], values["new2"]
|
||||||
|
|
||||||
if new != new2 {
|
if new != new2 {
|
||||||
return struct{}{}, errPasswordsNotIdentical
|
return struct{}{}, errPasswordsNotIdentical
|
||||||
|
|
@ -63,7 +50,7 @@ func (auth *Auth) authPassword(ctx context.Context) http.Handler {
|
||||||
return struct{}{}, errPasswordIsEmpty
|
return struct{}{}, errPasswordIsEmpty
|
||||||
}
|
}
|
||||||
|
|
||||||
user, err := auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return struct{}{}, err
|
return struct{}{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,6 @@
|
||||||
<a class="pure-button pure-button-primary" href="/user/password/">Change Password</a>
|
<a class="pure-button pure-button-primary" href="/user/password/">Change Password</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a class="pure-button pure-button-small" href="/user/logout/">Logout</a>
|
<a class="pure-button pure-button-small" href="/auth/logout/">Logout</a>
|
||||||
</p>
|
</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
<a class="pure-button pure-button-primary" href="/user/totp/disable/">Disable TOTP</a>
|
<a class="pure-button pure-button-primary" href="/user/totp/disable/">Disable TOTP</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a class="pure-button pure-button-small" href="/user/logout/">Logout</a>
|
<a class="pure-button pure-button-small" href="/auth/logout/">Logout</a>
|
||||||
</p>
|
</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<a class="pure-button pure-button-primary" href="/user/totp/enable/">Enable TOTP</a>
|
<a class="pure-button pure-button-primary" href="/user/totp/enable/">Enable TOTP</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a class="pure-button pure-button-small" href="/user/logout/">Logout</a>
|
<a class="pure-button pure-button-small" href="/auth/logout/">Logout</a>
|
||||||
</p>
|
</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "form/inside" }}
|
{{ define "form/inside" }}
|
||||||
|
|
@ -7,7 +7,7 @@
|
||||||
<a class="pure-button pure-button-primary" href="/user/totp/enroll/">Enroll TOTP</a>
|
<a class="pure-button pure-button-primary" href="/user/totp/enroll/">Enroll TOTP</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a class="pure-button pure-button-small" href="/user/logout/">Logout</a>
|
<a class="pure-button pure-button-small" href="/auth/logout/">Logout</a>
|
||||||
</p>
|
</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ define "form/inside" }}
|
{{ define "form/inside" }}
|
||||||
|
|
@ -6,7 +6,7 @@
|
||||||
<a class="pure-button pure-button-primary" href="/user/">{{ .User.User }}</a>
|
<a class="pure-button pure-button-primary" href="/user/">{{ .User.User }}</a>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
<a class="pure-button pure-button-small" href="/user/logout/">Logout</a>
|
<a class="pure-button pure-button-small" href="/auth/logout/">Logout</a>
|
||||||
</p>
|
</p>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
|
|
@ -1,10 +1,11 @@
|
||||||
package auth
|
package panel
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"html/template"
|
"html/template"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
|
|
||||||
|
|
@ -15,27 +16,27 @@ import (
|
||||||
var totpEnableStr string
|
var totpEnableStr string
|
||||||
var totpEnableTemplate = static.AssetsUser.MustParseShared("totp_enable.html", totpEnableStr)
|
var totpEnableTemplate = static.AssetsUser.MustParseShared("totp_enable.html", totpEnableStr)
|
||||||
|
|
||||||
func (auth *Auth) authTOTPEnable(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeTOTPEnable(ctx context.Context) http.Handler {
|
||||||
return &httpx.Form[struct{}]{
|
return &httpx.Form[struct{}]{
|
||||||
Fields: []httpx.Field{
|
Fields: []httpx.Field{
|
||||||
{Name: "password", Type: httpx.PasswordField, EmptyOnError: true, Label: "Current Password"},
|
{Name: "password", Type: httpx.PasswordField, EmptyOnError: true, Label: "Current Password"},
|
||||||
},
|
},
|
||||||
FieldTemplate: httpx.PureCSSFieldTemplate,
|
FieldTemplate: httpx.PureCSSFieldTemplate,
|
||||||
|
|
||||||
CSRF: auth.CSRF(),
|
CSRF: panel.Dependencies.Auth.CSRF(),
|
||||||
|
|
||||||
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
|
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
|
||||||
user, err := auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
|
return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
|
||||||
},
|
},
|
||||||
|
|
||||||
RenderTemplate: totpEnableTemplate,
|
RenderTemplate: totpEnableTemplate,
|
||||||
RenderTemplateContext: auth.UserFormContext,
|
RenderTemplateContext: panel.UserFormContext,
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
||||||
password := values["password"]
|
password := values["password"]
|
||||||
|
|
||||||
user, err := auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return struct{}{}, err
|
return struct{}{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -73,22 +74,22 @@ type totpEnrollContext struct {
|
||||||
TOTPURL template.URL
|
TOTPURL template.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *Auth) authTOTPEnroll(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
return &httpx.Form[struct{}]{
|
return &httpx.Form[struct{}]{
|
||||||
Fields: []httpx.Field{
|
Fields: []httpx.Field{
|
||||||
{Name: "password", Type: httpx.PasswordField, EmptyOnError: true, Label: "Current Password"},
|
{Name: "password", Type: httpx.PasswordField, EmptyOnError: true, Label: "Current Password"},
|
||||||
{Name: "passcode", Type: httpx.TextField, EmptyOnError: true, Label: "Passcode"},
|
{Name: "otp", Type: httpx.TextField, EmptyOnError: true, Label: "Passcode"},
|
||||||
},
|
},
|
||||||
FieldTemplate: httpx.PureCSSFieldTemplate,
|
FieldTemplate: httpx.PureCSSFieldTemplate,
|
||||||
|
|
||||||
CSRF: auth.CSRF(),
|
CSRF: panel.Dependencies.Auth.CSRF(),
|
||||||
|
|
||||||
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
|
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
|
||||||
user, err := auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
|
return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
|
||||||
},
|
},
|
||||||
RenderForm: func(context httpx.FormContext, w http.ResponseWriter, r *http.Request) {
|
RenderForm: func(context httpx.FormContext, w http.ResponseWriter, r *http.Request) {
|
||||||
user, err := auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
|
|
||||||
ctx := totpEnrollContext{
|
ctx := totpEnrollContext{
|
||||||
userFormContext: userFormContext{
|
userFormContext: userFormContext{
|
||||||
|
|
@ -100,7 +101,7 @@ func (auth *Auth) authTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
ctx.userFormContext.User = &user.User
|
ctx.userFormContext.User = &user.User
|
||||||
secret, err := user.TOTP()
|
secret, err := user.TOTP()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
img, _ := TOTPLink(secret, 500, 500)
|
img, _ := auth.TOTPLink(secret, 500, 500)
|
||||||
|
|
||||||
ctx.TOTPImage = template.URL(img)
|
ctx.TOTPImage = template.URL(img)
|
||||||
ctx.TOTPURL = template.URL(secret.URL())
|
ctx.TOTPURL = template.URL(secret.URL())
|
||||||
|
|
@ -110,9 +111,9 @@ func (auth *Auth) authTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
},
|
},
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
||||||
password, passcode := values["password"], values["passcode"]
|
password, otp := values["password"], values["otp"]
|
||||||
|
|
||||||
user, err := auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return struct{}{}, err
|
return struct{}{}, err
|
||||||
}
|
}
|
||||||
|
|
@ -124,7 +125,7 @@ func (auth *Auth) authTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
err := user.EnableTOTP(r.Context(), passcode)
|
err := user.EnableTOTP(r.Context(), otp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return struct{}{}, errTOTPSetFailure
|
return struct{}{}, errTOTPSetFailure
|
||||||
}
|
}
|
||||||
|
|
@ -144,33 +145,33 @@ func (auth *Auth) authTOTPEnroll(ctx context.Context) http.Handler {
|
||||||
var totpDisableStr string
|
var totpDisableStr string
|
||||||
var totpDisableTemplate = static.AssetsUser.MustParseShared("totp_disable.html", totpDisableStr)
|
var totpDisableTemplate = static.AssetsUser.MustParseShared("totp_disable.html", totpDisableStr)
|
||||||
|
|
||||||
func (auth *Auth) authTOTPDisable(ctx context.Context) http.Handler {
|
func (panel *UserPanel) routeTOTPDisable(ctx context.Context) http.Handler {
|
||||||
return &httpx.Form[struct{}]{
|
return &httpx.Form[struct{}]{
|
||||||
Fields: []httpx.Field{
|
Fields: []httpx.Field{
|
||||||
{Name: "password", Type: httpx.PasswordField, EmptyOnError: true, Label: "Current Password"},
|
{Name: "password", Type: httpx.PasswordField, EmptyOnError: true, Label: "Current Password"},
|
||||||
{Name: "passcode", Type: httpx.TextField, EmptyOnError: true, Label: "Current Passcode"},
|
{Name: "otp", Type: httpx.TextField, EmptyOnError: true, Label: "Current Passcode"},
|
||||||
},
|
},
|
||||||
FieldTemplate: httpx.PureCSSFieldTemplate,
|
FieldTemplate: httpx.PureCSSFieldTemplate,
|
||||||
|
|
||||||
CSRF: auth.CSRF(),
|
CSRF: panel.Dependencies.Auth.CSRF(),
|
||||||
|
|
||||||
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
|
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
|
||||||
user, err := auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled()
|
return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled()
|
||||||
},
|
},
|
||||||
RenderTemplate: totpDisableTemplate,
|
RenderTemplate: totpDisableTemplate,
|
||||||
RenderTemplateContext: auth.UserFormContext,
|
RenderTemplateContext: panel.UserFormContext,
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
Validate: func(r *http.Request, values map[string]string) (struct{}, error) {
|
||||||
password, passcode := values["password"], values["passcode"]
|
password, otp := values["password"], values["otp"]
|
||||||
|
|
||||||
user, err := auth.UserOf(r)
|
user, err := panel.Dependencies.Auth.UserOf(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return struct{}{}, err
|
return struct{}{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
err := user.CheckCredentials(r.Context(), []byte(password), passcode)
|
err := user.CheckCredentials(r.Context(), []byte(password), otp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return struct{}{}, errCredentialsIncorrect
|
return struct{}{}, errCredentialsIncorrect
|
||||||
}
|
}
|
||||||
26
internal/dis/component/auth/panel/user.go
Normal file
26
internal/dis/component/auth/panel/user.go
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package panel
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
_ "embed"
|
||||||
|
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed "templates/user.html"
|
||||||
|
var userHTMLStr string
|
||||||
|
var userTemplate = static.AssetsUser.MustParseShared(
|
||||||
|
"user.html",
|
||||||
|
userHTMLStr,
|
||||||
|
)
|
||||||
|
|
||||||
|
func (panel *UserPanel) routeUser(ctx context.Context) http.Handler {
|
||||||
|
return &httpx.HTMLHandler[*auth.AuthUser]{
|
||||||
|
Handler: panel.Dependencies.Auth.UserOf,
|
||||||
|
Template: userTemplate,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ func (auth *Auth) Protect(handler http.Handler, perm Permission) http.Handler {
|
||||||
}
|
}
|
||||||
|
|
||||||
// redirect the user to the login endpoint, with the original URI as a return
|
// redirect the user to the login endpoint, with the original URI as a return
|
||||||
dest := "/user/login?next=" + url.QueryEscape(r.URL.RequestURI())
|
dest := "/auth/login?next=" + url.QueryEscape(r.URL.RequestURI())
|
||||||
http.Redirect(w, r, dest, http.StatusSeeOther)
|
http.Redirect(w, r, dest, http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ func (auth *Auth) Logout(w http.ResponseWriter, r *http.Request) error {
|
||||||
return sess.Save(r, w)
|
return sess.Save(r, w)
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed "templates/login.html"
|
//go:embed "login.html"
|
||||||
var loginHTMLStr string
|
var loginHTMLStr string
|
||||||
var loginTemplate = static.AssetsUser.MustParseShared("login.html", loginHTMLStr)
|
var loginTemplate = static.AssetsUser.MustParseShared("login.html", loginHTMLStr)
|
||||||
|
|
||||||
|
|
@ -118,7 +118,7 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler {
|
||||||
Fields: []httpx.Field{
|
Fields: []httpx.Field{
|
||||||
{Name: "username", Type: httpx.TextField, Label: "Username"},
|
{Name: "username", Type: httpx.TextField, Label: "Username"},
|
||||||
{Name: "password", Type: httpx.PasswordField, EmptyOnError: true, Label: "Password"},
|
{Name: "password", Type: httpx.PasswordField, EmptyOnError: true, Label: "Password"},
|
||||||
{Name: "passcode", Type: httpx.TextField, EmptyOnError: true, Label: "Passcode (optional)"},
|
{Name: "otp", Type: httpx.TextField, EmptyOnError: true, Label: "Passcode (optional)"},
|
||||||
},
|
},
|
||||||
FieldTemplate: httpx.PureCSSFieldTemplate,
|
FieldTemplate: httpx.PureCSSFieldTemplate,
|
||||||
|
|
||||||
|
|
@ -132,7 +132,7 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler {
|
||||||
},
|
},
|
||||||
|
|
||||||
Validate: func(r *http.Request, values map[string]string) (*AuthUser, error) {
|
Validate: func(r *http.Request, values map[string]string) (*AuthUser, error) {
|
||||||
username, password, passcode := values["username"], values["password"], values["passcode"]
|
username, password, passcode := values["username"], values["password"], values["otp"]
|
||||||
|
|
||||||
// make sure that the user exists
|
// make sure that the user exists
|
||||||
user, err := auth.User(ctx, username)
|
user, err := auth.User(ctx, username)
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,8 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"fmt"
|
"fmt"
|
||||||
"image/png"
|
"image/png"
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/pquerna/otp"
|
"github.com/pquerna/otp"
|
||||||
"github.com/pquerna/otp/totp"
|
"github.com/pquerna/otp/totp"
|
||||||
|
|
@ -292,18 +290,3 @@ func (au *AuthUser) Delete(ctx context.Context) error {
|
||||||
|
|
||||||
return table.Delete(&au.User).Error
|
return table.Delete(&au.User).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
type userFormContext struct {
|
|
||||||
httpx.FormContext
|
|
||||||
User *models.User
|
|
||||||
}
|
|
||||||
|
|
||||||
func (au *Auth) UserFormContext(ctx httpx.FormContext, r *http.Request) any {
|
|
||||||
user, err := au.UserOf(r)
|
|
||||||
|
|
||||||
uctx := userFormContext{FormContext: ctx}
|
|
||||||
if err == nil {
|
|
||||||
uctx.User = &user.User
|
|
||||||
}
|
|
||||||
return uctx
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
|
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth"
|
||||||
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/panel"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/auth/policy"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control"
|
||||||
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/admin"
|
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/admin"
|
||||||
|
|
@ -132,6 +133,7 @@ func (dis *Distillery) allComponents() []initFunc {
|
||||||
// auth
|
// auth
|
||||||
auto[*auth.Auth],
|
auto[*auth.Auth],
|
||||||
auto[*policy.Policy],
|
auto[*policy.Policy],
|
||||||
|
auto[*panel.UserPanel],
|
||||||
|
|
||||||
// instances
|
// instances
|
||||||
auto[*instances.Instances],
|
auto[*instances.Instances],
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue