Use authentication for Distillery control page

This commit is contained in:
Tom Wiesing 2022-12-29 16:34:45 +01:00
parent da32b67981
commit 1caecc0f19
No known key found for this signature in database
8 changed files with 122 additions and 82 deletions

View file

@ -0,0 +1,72 @@
package auth
import (
"context"
"net/http"
"net/url"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
)
// Permission represents a permission for a user
//
// The nil permission represents any authenticated user.
type Permission func(user *AuthUser, r *http.Request) (ok bool, err error)
// Protect returns a new handler which requires a user to be logged in and pass the perm function.
//
// If an unauthenticated user attempts to access the returned handler, they are redirected to the login endpoint.
// If an authenticated user is missing permissions, a Forbidden response is called.
// If an authenticated calls the endpoint, and they have the given permissions, the original handler is called.
func (auth *Auth) Protect(handler http.Handler, perm Permission) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// load the user in the session
user, err := auth.UserOf(r)
if err != nil {
goto err
}
// if there is no user in the session
// we need to login the user
if user == nil {
// we can't redirect anything other than GET
// (because it might be a form)
// => so we just return a forbidden
if r.Method != http.MethodGet {
goto forbidden
}
// redirect the user to the login endpoint, with the original URI as a return
dest := "/auth/login?next=" + url.QueryEscape(r.URL.RequestURI())
http.Redirect(w, r, dest, http.StatusSeeOther)
return
}
// if we have a permission check, we need to call it
// to find out if the user is actually allowed to access the page
if perm != nil {
ok, err := perm(user, r)
if err != nil {
goto err
}
if !ok {
goto forbidden
}
}
// store the user into the session
r = r.WithContext(context.WithValue(r.Context(), ctxUserKey, user))
handler.ServeHTTP(w, r)
return
forbidden:
httpx.HTMLInterceptor.Intercept(w, r, httpx.ErrForbidden)
return
err:
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
})
}
// 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 bool, err error) {
return user != nil && user.Admin && user.TOTPEnabled, nil
}

View file

@ -4,7 +4,6 @@ import (
"context"
"html/template"
"net/http"
"net/url"
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
@ -116,58 +115,6 @@ type authloginContext struct {
Form template.HTML
}
// Protect returns a new handler which requires a user to be logged in and pass the perm function.
//
// If an unauthenticated user attempts to access the returned handler, they are redirected to the login endpoint.
// When a user is logged in, and they pass the perm function (or the perm function is nil), the original handler is called.
func (auth *Auth) Protect(handler http.Handler, perm func(user *AuthUser, r *http.Request) (ok bool, err error)) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// load the user in the session
user, err := auth.UserOf(r)
if err != nil {
goto err
}
// if there is no user in the session
// we need to login the user
if user == nil {
// we can't redirect anything other than GET
// (because it might be a form)
// => so we just return a forbidden
if r.Method != http.MethodGet {
goto forbidden
}
// redirect the user to the login endpoint, with the original URI as a return
dest := "/auth/login?next=" + url.QueryEscape(r.URL.RequestURI())
http.Redirect(w, r, dest, http.StatusSeeOther)
return
}
// if we have a permission check, we need to call it
// to find out if the user is actually allowed to access the page
if perm != nil {
ok, err := perm(user, r)
if err != nil {
goto err
}
if !ok {
goto forbidden
}
}
// store the user into the session
r = r.WithContext(context.WithValue(r.Context(), ctxUserKey, user))
handler.ServeHTTP(w, r)
return
forbidden:
httpx.HTMLInterceptor.Intercept(w, r, httpx.ErrForbidden)
return
err:
httpx.HTMLInterceptor.Fallback.ServeHTTP(w, r)
})
}
// authLogin implements a view to login a user
func (auth *Auth) authLogin(ctx context.Context) http.Handler {
return &httpx.Form[*AuthUser]{

View file

@ -16,12 +16,19 @@
</div>
</div>
{{ if .User.Admin }}
<div class="pure-u-1">
<div class="pure-button-group" role="group" role="Actions">
<a class="pure-button" href="/auth/logout/">Logout</a>
{{ if (not .User.TOTPEnabled) }}
<div>
TOTP is required to access these.
</div>
<hr />
{{ end }}
<div class="pure-button-group" role="group" role="Actions">
<a class="pure-button" href="/dis/">Distillery Control Page</a>
</div>
<hr />
</div>
{{ end }}
<div class="pure-u-1">
<div class="pure-button-group" role="group" role="Actions">
@ -32,6 +39,13 @@
<a class="pure-button" href="/auth/totp/enable/">Enable TOTP</a>
{{ end }}
</div>
<hr />
</div>
<div class="pure-u-1">
<div class="pure-button-group" role="group" role="Actions">
<a class="pure-button" href="/auth/logout/">Logout</a>
</div>
</div>
{{ end }}