Admin: Add user page
This commit is contained in:
parent
bc0e92bdac
commit
d34e85a18f
24 changed files with 456 additions and 77 deletions
|
|
@ -32,17 +32,7 @@ func (auth *Auth) Routes() []string { return []string{"/user/"} }
|
|||
func (auth *Auth) HandleRoute(ctx context.Context, route string) (http.Handler, error) {
|
||||
router := httprouter.New()
|
||||
|
||||
// setup the csrf handler (if needed)
|
||||
auth.csrf.Get(func() func(http.Handler) http.Handler {
|
||||
var opts []csrf.Option
|
||||
if !auth.Config.HTTPSEnabled() {
|
||||
opts = append(opts, csrf.Secure(false))
|
||||
}
|
||||
opts = append(opts, csrf.Path(route))
|
||||
return csrf.Protect(auth.Config.CSRFSecret(), opts...)
|
||||
})
|
||||
|
||||
router.Handler(http.MethodGet, route, auth.authHome(ctx))
|
||||
router.Handler(http.MethodGet, route, auth.authUser(ctx))
|
||||
|
||||
{
|
||||
login := auth.authLogin(ctx)
|
||||
|
|
@ -78,3 +68,14 @@ func (auth *Auth) HandleRoute(ctx context.Context, route string) (http.Handler,
|
|||
|
||||
return router, nil
|
||||
}
|
||||
|
||||
func (auth *Auth) CSRF() func(http.Handler) http.Handler {
|
||||
// setup the csrf handler (if needed)
|
||||
return auth.csrf.Get(func() func(http.Handler) http.Handler {
|
||||
var opts []csrf.Option
|
||||
if !auth.Config.HTTPSEnabled() {
|
||||
opts = append(opts, csrf.Secure(false))
|
||||
}
|
||||
return csrf.Protect(auth.Config.CSRFSecret(), opts...)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,5 +74,5 @@ func (auth *Auth) Protect(handler http.Handler, perm Permission) http.Handler {
|
|||
|
||||
// Admin represents a permission that checks if a user is an administrator and has totp enabled.
|
||||
var Admin Permission = func(user *AuthUser, r *http.Request) (ok Grant, err error) {
|
||||
return Bool2Grant(user != nil && user.Admin && user.TOTPEnabled, "user needs to have admin permissions and TOTP enabled"), nil
|
||||
return Bool2Grant(user != nil && user.IsAdmin() && user.IsTOTPEnabled(), "user needs to have admin permissions and TOTP enabled"), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,17 +10,17 @@ import (
|
|||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||
)
|
||||
|
||||
//go:embed "templates/home.html"
|
||||
var homeHTMLStr string
|
||||
var homeTemplate = static.AssetsHome.MustParseShared(
|
||||
"home.html",
|
||||
homeHTMLStr,
|
||||
//go:embed "templates/user.html"
|
||||
var userHTMLStr string
|
||||
var userTemplate = static.AssetsUser.MustParseShared(
|
||||
"user.html",
|
||||
userHTMLStr,
|
||||
)
|
||||
|
||||
func (auth *Auth) authHome(ctx context.Context) http.Handler {
|
||||
func (auth *Auth) authUser(ctx context.Context) http.Handler {
|
||||
return auth.Protect(&httpx.HTMLHandler[*AuthUser]{
|
||||
Handler: auth.UserOf,
|
||||
Template: homeTemplate,
|
||||
Template: userTemplate,
|
||||
}, nil)
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ func (auth *Auth) authPassword(ctx context.Context) http.Handler {
|
|||
},
|
||||
FieldTemplate: httpx.PureCSSFieldTemplate,
|
||||
|
||||
CSRF: auth.csrf.Get(nil),
|
||||
CSRF: auth.CSRF(),
|
||||
|
||||
RenderTemplate: passwordTemplate,
|
||||
RenderTemplateContext: auth.UserFormContext,
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func (auth *Auth) UserOf(r *http.Request) (user *AuthUser, err error) {
|
|||
}
|
||||
|
||||
// user isn't enabled
|
||||
if !user.Enabled {
|
||||
if !user.IsEnabled() {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
|
@ -122,7 +122,7 @@ func (auth *Auth) authLogin(ctx context.Context) http.Handler {
|
|||
},
|
||||
FieldTemplate: httpx.PureCSSFieldTemplate,
|
||||
|
||||
CSRF: auth.csrf.Get(nil),
|
||||
CSRF: auth.CSRF(),
|
||||
|
||||
RenderForm: func(context httpx.FormContext, w http.ResponseWriter, r *http.Request) {
|
||||
if context.Err != nil {
|
||||
|
|
|
|||
|
|
@ -13,12 +13,12 @@
|
|||
{{ define "content" }}
|
||||
<div class="pure-u-1">
|
||||
<p>
|
||||
{{ if .User.Admin }}
|
||||
{{ if .User.IsAdmin }}
|
||||
You are an administrator.
|
||||
{{ else }}
|
||||
You are a regular user.
|
||||
{{ end }}
|
||||
{{ if .User.TOTPEnabled }}
|
||||
{{ if .User.IsTOTPEnabled }}
|
||||
You have TOTP enabled.
|
||||
{{ else }}
|
||||
You do not have TOTP enabled.
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
</p>
|
||||
<div class="pure-button-group" role="group" role="Actions">
|
||||
<a class="pure-button" href="/user/password/">Change Password</a>
|
||||
{{ if .User.TOTPEnabled }}
|
||||
{{ if .User.IsTOTPEnabled }}
|
||||
<a class="pure-button" href="/user/totp/disable/">Disable TOTP</a>
|
||||
{{ else }}
|
||||
<a class="pure-button" href="/user/totp/enable/">Enable TOTP</a>
|
||||
|
|
@ -35,9 +35,9 @@
|
|||
<hr />
|
||||
</div>
|
||||
|
||||
{{ if .User.Admin }}
|
||||
{{ if .User.IsAdmin }}
|
||||
<div class="pure-u-1">
|
||||
{{ if (not .User.TOTPEnabled) }}
|
||||
{{ if (not .User.IsTOTPEnabled) }}
|
||||
<div>
|
||||
<p class="error-message">
|
||||
TOTP is required to access these.
|
||||
|
|
@ -22,11 +22,11 @@ func (auth *Auth) authTOTPEnable(ctx context.Context) http.Handler {
|
|||
},
|
||||
FieldTemplate: httpx.PureCSSFieldTemplate,
|
||||
|
||||
CSRF: auth.csrf.Get(nil),
|
||||
CSRF: auth.CSRF(),
|
||||
|
||||
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
|
||||
user, err := auth.UserOf(r)
|
||||
return struct{}{}, err == nil && user != nil && user.TOTPEnabled
|
||||
return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
|
||||
},
|
||||
|
||||
RenderTemplate: totpEnableTemplate,
|
||||
|
|
@ -81,11 +81,11 @@ func (auth *Auth) authTOTPEnroll(ctx context.Context) http.Handler {
|
|||
},
|
||||
FieldTemplate: httpx.PureCSSFieldTemplate,
|
||||
|
||||
CSRF: auth.csrf.Get(nil),
|
||||
CSRF: auth.CSRF(),
|
||||
|
||||
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
|
||||
user, _ := auth.UserOf(r)
|
||||
return struct{}{}, user != nil && user.TOTPEnabled
|
||||
user, err := auth.UserOf(r)
|
||||
return struct{}{}, err == nil && user != nil && user.IsTOTPEnabled()
|
||||
},
|
||||
RenderForm: func(context httpx.FormContext, w http.ResponseWriter, r *http.Request) {
|
||||
user, err := auth.UserOf(r)
|
||||
|
|
@ -152,11 +152,11 @@ func (auth *Auth) authTOTPDisable(ctx context.Context) http.Handler {
|
|||
},
|
||||
FieldTemplate: httpx.PureCSSFieldTemplate,
|
||||
|
||||
CSRF: auth.csrf.Get(nil),
|
||||
CSRF: auth.CSRF(),
|
||||
|
||||
SkipForm: func(r *http.Request) (data struct{}, skip bool) {
|
||||
user, _ := auth.UserOf(r)
|
||||
return struct{}{}, user != nil && !user.TOTPEnabled
|
||||
user, err := auth.UserOf(r)
|
||||
return struct{}{}, err == nil && user != nil && !user.IsTOTPEnabled()
|
||||
},
|
||||
RenderTemplate: totpDisableTemplate,
|
||||
RenderTemplateContext: auth.UserFormContext,
|
||||
|
|
|
|||
|
|
@ -4,13 +4,13 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image/png"
|
||||
"net/http"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/internal/models"
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pquerna/otp"
|
||||
"github.com/pquerna/otp/totp"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
|
|
@ -90,15 +90,17 @@ func (auth *Auth) CreateUser(ctx context.Context, name string) (user *AuthUser,
|
|||
|
||||
user = &AuthUser{
|
||||
User: models.User{
|
||||
User: name,
|
||||
Enabled: false,
|
||||
User: name,
|
||||
},
|
||||
}
|
||||
user.SetAdmin(false)
|
||||
user.SetEnabled(false)
|
||||
user.SetTOTPEnabled(false)
|
||||
|
||||
// do the create statement
|
||||
err = table.Create(&user.User).Error
|
||||
err = table.Select("*").Create(&user.User).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, errors.Wrapf(err, "Create")
|
||||
}
|
||||
|
||||
user.auth = auth
|
||||
|
|
@ -116,7 +118,7 @@ func (au *AuthUser) String() string {
|
|||
return "User{nil}"
|
||||
}
|
||||
hasPassword := len(au.PasswordHash) > 0
|
||||
return fmt.Sprintf("User{Name:%q,Enabled:%t,HasPassword:%t,Admin:%t}", au.User.User, au.User.Enabled, hasPassword, au.User.Admin)
|
||||
return fmt.Sprintf("User{Name:%q,Enabled:%t,HasPassword:%t,Admin:%t}", au.User.User, au.User.IsEnabled(), hasPassword, au.User.IsAdmin())
|
||||
}
|
||||
|
||||
var (
|
||||
|
|
@ -140,7 +142,7 @@ func (au *AuthUser) CheckTOTP(passcode string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
if au.TOTPEnabled && !totp.Validate(passcode, secret.Secret()) {
|
||||
if au.IsTOTPEnabled() && !totp.Validate(passcode, secret.Secret()) {
|
||||
return ErrTOTPFailed
|
||||
}
|
||||
return nil
|
||||
|
|
@ -148,7 +150,7 @@ func (au *AuthUser) CheckTOTP(passcode string) error {
|
|||
|
||||
// NewTOTP generates a new TOTP secret, returning a totp key.
|
||||
func (au *AuthUser) NewTOTP(ctx context.Context) (*otp.Key, error) {
|
||||
if au.User.TOTPEnabled {
|
||||
if au.User.IsTOTPEnabled() {
|
||||
return nil, ErrTOTPEnabled
|
||||
}
|
||||
|
||||
|
|
@ -191,14 +193,14 @@ func (au *AuthUser) EnableTOTP(ctx context.Context, passcode string) error {
|
|||
return ErrTOTPFailed
|
||||
}
|
||||
|
||||
au.User.TOTPEnabled = true
|
||||
au.User.SetTOTPEnabled(true)
|
||||
return au.Save(ctx)
|
||||
|
||||
}
|
||||
|
||||
// DisableTOTP disables totp for the given user
|
||||
func (au *AuthUser) DisableTOTP(ctx context.Context) (err error) {
|
||||
au.User.TOTPEnabled = false
|
||||
au.User.SetTOTPEnabled(false)
|
||||
au.User.TOTPURL = ""
|
||||
return au.Save(ctx)
|
||||
}
|
||||
|
|
@ -209,14 +211,14 @@ func (au *AuthUser) SetPassword(ctx context.Context, password []byte) (err error
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
au.User.Enabled = true
|
||||
au.User.SetEnabled(true)
|
||||
return au.Save(ctx)
|
||||
}
|
||||
|
||||
// UnsetPassword removes the password from this user, and disables them
|
||||
func (au *AuthUser) UnsetPassword(ctx context.Context) error {
|
||||
au.User.PasswordHash = nil
|
||||
au.User.Enabled = false
|
||||
au.User.SetEnabled(false)
|
||||
return au.Save(ctx)
|
||||
}
|
||||
|
||||
|
|
@ -230,7 +232,7 @@ func (au *AuthUser) CheckPassword(ctx context.Context, password []byte) error {
|
|||
if au == nil {
|
||||
return ErrNoUser
|
||||
}
|
||||
if !au.User.Enabled {
|
||||
if !au.User.IsEnabled() {
|
||||
return ErrUserDisabled
|
||||
}
|
||||
|
||||
|
|
@ -254,14 +256,14 @@ func (au *AuthUser) CheckCredentials(ctx context.Context, password []byte, passc
|
|||
// MakeAdmin makes this user an admin, and saves the update in the database.
|
||||
// If the user is already an admin, does not return an error.
|
||||
func (au *AuthUser) MakeAdmin(ctx context.Context) error {
|
||||
au.User.Admin = true
|
||||
au.User.SetAdmin(true)
|
||||
return au.Save(ctx)
|
||||
}
|
||||
|
||||
// MakeRegular removes admin rights from this user.
|
||||
// If this user is not an dmin, does not return an error.
|
||||
func (au *AuthUser) MakeRegular(ctx context.Context) error {
|
||||
au.User.Admin = true
|
||||
au.User.SetAdmin(false)
|
||||
return au.Save(ctx)
|
||||
}
|
||||
|
||||
|
|
@ -271,7 +273,7 @@ func (au *AuthUser) Save(ctx context.Context) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return table.Save(&au.User).Error
|
||||
return table.Select("*").Updates(&au.User).Error
|
||||
}
|
||||
|
||||
// Delete deletes the user from the database
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue