⚡️ Implemented login management
This commit is contained in:
parent
99bce4d1fc
commit
6993a9f58b
|
@ -84,6 +84,8 @@
|
||||||
<span><a href="shares">Shares</a></span>
|
<span><a href="shares">Shares</a></span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<hr/>
|
<hr/>
|
||||||
|
<span><a href="my-shares">My Shares</a></span>
|
||||||
|
<hr/>
|
||||||
<span><a href="logout">Logout</a></span>
|
<span><a href="logout">Logout</a></span>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
{{ define "page-styles"}}
|
||||||
|
<style>
|
||||||
|
div.share-user, div.share-login {
|
||||||
|
margin-left: 2em;
|
||||||
|
margin-top: .5em;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.share-add-user, a.share-add-login {
|
||||||
|
margin-left: 2em;
|
||||||
|
margin-top: .5em;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.share {
|
||||||
|
margin-bottom: 2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
:target {
|
||||||
|
background-color: #c4e1ff;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ define "page-content" }}
|
||||||
|
<div id="shares">
|
||||||
|
{{ range $share := .ShareInfos }}
|
||||||
|
<div id="share-{{$share.UUID}}" class="share">
|
||||||
|
UUID: {{ $share.UUID }} {{ if $share.IsAdmin }}
|
||||||
|
<form style="display: inline-block;" action="delete-share" method="post">
|
||||||
|
<input type="hidden" name="share" value="{{ $share.UUID }}"/>
|
||||||
|
<input type="submit" value="Delete" class="delete"/>
|
||||||
|
</form>{{ end }}
|
||||||
|
<br/>
|
||||||
|
Name: {{ $share.Name }}<br/>
|
||||||
|
<hr/>
|
||||||
|
{{ range $login := .Logins }}
|
||||||
|
<div id="login-{{$share.UUID}}-{{$login.LoginName}}" class="share-login">
|
||||||
|
Login {{ $login.LoginName }} {{ if $login.ReadOnly }}(ReadOnly){{ end }}
|
||||||
|
<form style="display: inline-block;" action="share-delete-login" method="post">
|
||||||
|
<input type="hidden" name="share" value="{{ $share.UUID }}"/>
|
||||||
|
<input type="hidden" name="login" value="{{ $login.LoginName }}"/>
|
||||||
|
<input type="submit" value="Delete" class="delete"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
<a href="share-add-login?share={{ $share.UUID }}" class="share-add-login">Add Login</a>
|
||||||
|
<hr/>
|
||||||
|
{{ if $share.IsAdmin }}
|
||||||
|
{{ range $user := .Users }}
|
||||||
|
<div id="user-{{$share.UUID}}-{{$user.Username}}" class="share-user">
|
||||||
|
User {{ $user.Username }} ({{ $user.Role }})
|
||||||
|
<form style="display: inline-block;" action="share-delete-user" method="post">
|
||||||
|
<input type="hidden" name="source" value="my-shares"/>
|
||||||
|
<input type="hidden" name="share" value="{{ $share.UUID }}"/>
|
||||||
|
<input type="hidden" name="user" value="{{ $user.Username }}"/>
|
||||||
|
<input type="submit" value="Delete" class="delete"/>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
<a href="share-add-user?share={{ $share.UUID }}" class="share-add-user">Add User</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
||||||
|
<a href="create-share" class="create-share">Create Share</a>
|
||||||
|
</div>
|
||||||
|
{{ end }}
|
|
@ -0,0 +1,15 @@
|
||||||
|
{{ define "page-content" }}
|
||||||
|
<form method="post">
|
||||||
|
<input name="share" type="hidden" value="{{ .ShareId }}"/>
|
||||||
|
<label>
|
||||||
|
Login: <input name="login" placeholder="Login name"/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
Password: <input type="password" name="password" placeholder="Password"/>
|
||||||
|
</label>
|
||||||
|
<label>
|
||||||
|
ReadOnly: <input type="checkbox" name="readonly"/>
|
||||||
|
</label>
|
||||||
|
<input type="submit" value="Add Login"/>
|
||||||
|
</form>
|
||||||
|
{{ end }}
|
|
@ -5,14 +5,17 @@
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
a.share-add-user {
|
a.share-add-user {
|
||||||
margin-left: 2em;
|
margin-left: 2em;
|
||||||
margin-top: .5em;
|
margin-top: .5em;
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.share {
|
div.share {
|
||||||
margin-bottom: 2em;
|
margin-bottom: 2em;
|
||||||
}
|
}
|
||||||
|
|
||||||
:target {
|
:target {
|
||||||
background-color: #c4e1ff;
|
background-color: #c4e1ff;
|
||||||
}
|
}
|
||||||
|
@ -23,7 +26,8 @@
|
||||||
<div id="shares">
|
<div id="shares">
|
||||||
{{ range $share := .ShareInfos }}
|
{{ range $share := .ShareInfos }}
|
||||||
<div id="share-{{$share.UUID}}" class="share">
|
<div id="share-{{$share.UUID}}" class="share">
|
||||||
UUID: {{ $share.UUID }} <form style="display: inline-block;" action="delete-share" method="post">
|
UUID: {{ $share.UUID }}
|
||||||
|
<form style="display: inline-block;" action="delete-share" method="post">
|
||||||
<input type="hidden" name="share" value="{{ $share.UUID }}"/>
|
<input type="hidden" name="share" value="{{ $share.UUID }}"/>
|
||||||
<input type="submit" value="Delete" class="delete"/>
|
<input type="submit" value="Delete" class="delete"/>
|
||||||
</form>
|
</form>
|
||||||
|
@ -33,6 +37,7 @@
|
||||||
<div id="user-{{$share.UUID}}-{{$user.Username}}" class="share-user">
|
<div id="user-{{$share.UUID}}-{{$user.Username}}" class="share-user">
|
||||||
User {{ $user.Username }} ({{ $user.Role }})
|
User {{ $user.Username }} ({{ $user.Role }})
|
||||||
<form style="display: inline-block;" action="share-delete-user" method="post">
|
<form style="display: inline-block;" action="share-delete-user" method="post">
|
||||||
|
<input type="hidden" name="source" value="shares"/>
|
||||||
<input type="hidden" name="share" value="{{ $share.UUID }}"/>
|
<input type="hidden" name="share" value="{{ $share.UUID }}"/>
|
||||||
<input type="hidden" name="user" value="{{ $user.Username }}"/>
|
<input type="hidden" name="user" value="{{ $user.Username }}"/>
|
||||||
<input type="submit" value="Delete" class="delete"/>
|
<input type="submit" value="Delete" class="delete"/>
|
||||||
|
|
187
webadmin.go
187
webadmin.go
|
@ -43,13 +43,16 @@ import (
|
||||||
|
|
||||||
type webAdminHandler struct {
|
type webAdminHandler struct {
|
||||||
router chi.Router
|
router chi.Router
|
||||||
|
shareStore ShareStore
|
||||||
tplError *template.Template
|
tplError *template.Template
|
||||||
tplConfirm *template.Template
|
tplConfirm *template.Template
|
||||||
tplLogin *template.Template
|
tplLogin *template.Template
|
||||||
tplIndex *template.Template
|
tplIndex *template.Template
|
||||||
tplUsers *template.Template
|
tplUsers *template.Template
|
||||||
tplShares *template.Template
|
tplShares *template.Template
|
||||||
|
tplMyShares *template.Template
|
||||||
tplShareAddUser *template.Template
|
tplShareAddUser *template.Template
|
||||||
|
tplShareAddLogin *template.Template
|
||||||
tplCreateShare *template.Template
|
tplCreateShare *template.Template
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,13 +96,16 @@ func newWebAdminHandler(app *app) *webAdminHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
h := &webAdminHandler{
|
h := &webAdminHandler{
|
||||||
|
shareStore: app.shareStore,
|
||||||
tplError: loadTemplate("error"),
|
tplError: loadTemplate("error"),
|
||||||
tplConfirm: loadTemplate("confirm"),
|
tplConfirm: loadTemplate("confirm"),
|
||||||
tplLogin: loadSingleTemplate("login"),
|
tplLogin: loadSingleTemplate("login"),
|
||||||
tplIndex: loadTemplate("index"),
|
tplIndex: loadTemplate("index"),
|
||||||
tplUsers: loadTemplate("users"),
|
tplUsers: loadTemplate("users"),
|
||||||
tplShares: loadTemplate("shares"),
|
tplShares: loadTemplate("shares"),
|
||||||
|
tplMyShares: loadTemplate("my-shares"),
|
||||||
tplShareAddUser: loadTemplate("share-add-user"),
|
tplShareAddUser: loadTemplate("share-add-user"),
|
||||||
|
tplShareAddLogin: loadTemplate("share-add-login"),
|
||||||
tplCreateShare: loadTemplate("create-share"),
|
tplCreateShare: loadTemplate("create-share"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,6 +237,54 @@ func newWebAdminHandler(app *app) *webAdminHandler {
|
||||||
"ShareInfos": shareInfos,
|
"ShareInfos": shareInfos,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
ar.Get("/my-shares", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sessionContext := h.buildSessionContext(w, r)
|
||||||
|
|
||||||
|
shares, err := app.shareStore.FindSharesByUser(sessionContext.user.Username)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type shareInfo struct {
|
||||||
|
Share
|
||||||
|
IsAdmin bool
|
||||||
|
Logins []Login
|
||||||
|
Users []ShareUser
|
||||||
|
}
|
||||||
|
|
||||||
|
shareInfos := make([]shareInfo, len(shares))
|
||||||
|
for i := range shares {
|
||||||
|
shareInfos[i].Share = shares[i].Share
|
||||||
|
|
||||||
|
shareRole, err := app.shareStore.GetShareAccess(shares[i].Share, sessionContext.user.Username)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shareInfos[i].IsAdmin = sessionContext.user.Role == GlobalRoleAdmin ||
|
||||||
|
shareRole == ShareRoleAdmin
|
||||||
|
|
||||||
|
if shareInfos[i].IsAdmin {
|
||||||
|
users, err := app.shareStore.GetShareUsers(shares[i].Share)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shareInfos[i].Users = users
|
||||||
|
}
|
||||||
|
|
||||||
|
logins, err := app.shareStore.GetShareLogins(shares[i].Share, sessionContext.user.Username)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
shareInfos[i].Logins = logins
|
||||||
|
}
|
||||||
|
sessionContext.RenderPage(h.tplMyShares, map[string]interface{}{
|
||||||
|
"ShareInfos": shareInfos,
|
||||||
|
})
|
||||||
|
})
|
||||||
ar.Route("/share-add-user", func(r chi.Router) {
|
ar.Route("/share-add-user", func(r chi.Router) {
|
||||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
sessionContext := h.buildSessionContext(w, r)
|
sessionContext := h.buildSessionContext(w, r)
|
||||||
|
@ -241,13 +295,10 @@ func newWebAdminHandler(app *app) *webAdminHandler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if sessionContext.user.Role != GlobalRoleAdmin {
|
if !sessionContext.IsAdmin(share) {
|
||||||
shareRole, err := app.shareStore.GetShareAccess(share, sessionContext.user.Username)
|
|
||||||
if err != nil || shareRole != ShareRoleAdmin {
|
|
||||||
sessionContext.Unauthorized()
|
sessionContext.Unauthorized()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
users, err := app.userStore.GetUsers()
|
users, err := app.userStore.GetUsers()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -271,13 +322,10 @@ func newWebAdminHandler(app *app) *webAdminHandler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if sessionContext.user.Role != GlobalRoleAdmin {
|
if !sessionContext.IsAdmin(share) {
|
||||||
shareRole, err := app.shareStore.GetShareAccess(share, sessionContext.user.Username)
|
|
||||||
if err != nil || shareRole != ShareRoleAdmin {
|
|
||||||
sessionContext.Unauthorized()
|
sessionContext.Unauthorized()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
user, err := app.userStore.GetUser(r.FormValue("user"))
|
user, err := app.userStore.GetUser(r.FormValue("user"))
|
||||||
if err == ErrUserNotFound {
|
if err == ErrUserNotFound {
|
||||||
|
@ -300,6 +348,94 @@ func newWebAdminHandler(app *app) *webAdminHandler {
|
||||||
ar.Post("/share-delete-user", func(w http.ResponseWriter, r *http.Request) {
|
ar.Post("/share-delete-user", func(w http.ResponseWriter, r *http.Request) {
|
||||||
sessionContext := h.buildSessionContext(w, r)
|
sessionContext := h.buildSessionContext(w, r)
|
||||||
|
|
||||||
|
returnURL := r.FormValue("source")
|
||||||
|
|
||||||
|
share, err := app.shareStore.GetShare(r.FormValue("share"))
|
||||||
|
if err != nil {
|
||||||
|
sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sessionContext.IsAdmin(share) {
|
||||||
|
sessionContext.Unauthorized()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
username := r.FormValue("user")
|
||||||
|
|
||||||
|
message := fmt.Sprintf(`You are about to delete the user "%s" from the share %s.<br/>
|
||||||
|
This will irrevocably also remove all logins of that user.<br/><br/>
|
||||||
|
Are you sure you want to continue?`, username, share.Name)
|
||||||
|
if confirmStatus := sessionContext.RequestConfirmation(template.HTML(message), "share-delete-user"); confirmStatus == confirmRequested {
|
||||||
|
// We have already rendered. Nothing to do.
|
||||||
|
return
|
||||||
|
} else if confirmStatus == confirmAccepted {
|
||||||
|
err = app.shareStore.RemoveUserFromShare(share, username)
|
||||||
|
if err != nil {
|
||||||
|
sessionContext.RenderError(template.HTML("Cannot remove user from share: "+err.Error()), returnURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Redirect(w, r, returnURL, http.StatusFound)
|
||||||
|
})
|
||||||
|
ar.Route("/share-add-login", func(r chi.Router) {
|
||||||
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sessionContext := h.buildSessionContext(w, r)
|
||||||
|
|
||||||
|
share, err := app.shareStore.GetShare(r.URL.Query().Get("share"))
|
||||||
|
if err != nil {
|
||||||
|
sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), "")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sessionContext.IsAdmin(share) {
|
||||||
|
sessionContext.Unauthorized()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionContext.RenderPage(h.tplShareAddLogin, map[string]interface{}{
|
||||||
|
"ShareId": share.UUID,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
r.Post("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sessionContext := h.buildSessionContext(w, r)
|
||||||
|
|
||||||
|
returnURL := "share-add-login?share=" + r.FormValue("share")
|
||||||
|
|
||||||
|
share, err := app.shareStore.GetShare(r.FormValue("share"))
|
||||||
|
if err != nil {
|
||||||
|
sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), returnURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sessionContext.IsAdmin(share) {
|
||||||
|
sessionContext.Unauthorized()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pwHash, err := bcrypt.GenerateFromPassword([]byte(r.FormValue("password")), bcrypt.DefaultCost)
|
||||||
|
if err != nil {
|
||||||
|
sessionContext.RenderError(template.HTML("Internal error: "+err.Error()), returnURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = app.shareStore.AddLogin(share, sessionContext.user.Username, Login{
|
||||||
|
LoginName: r.FormValue("login"),
|
||||||
|
Password: string(pwHash),
|
||||||
|
ReadOnly: r.FormValue("readonly") == "on",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
sessionContext.RenderError(template.HTML("Cannot add login: "+err.Error()), returnURL)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionContext.Redirect("my-shares")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
ar.Post("/share-delete-login", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
sessionContext := h.buildSessionContext(w, r)
|
||||||
|
|
||||||
returnURL := "shares"
|
returnURL := "shares"
|
||||||
|
|
||||||
share, err := app.shareStore.GetShare(r.FormValue("share"))
|
share, err := app.shareStore.GetShare(r.FormValue("share"))
|
||||||
|
@ -308,21 +444,27 @@ func newWebAdminHandler(app *app) *webAdminHandler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if sessionContext.user.Role != GlobalRoleAdmin {
|
if !sessionContext.IsAdmin(share) {
|
||||||
shareRole, err := app.shareStore.GetShareAccess(share, sessionContext.user.Username)
|
|
||||||
if err != nil || shareRole != ShareRoleAdmin {
|
|
||||||
sessionContext.Unauthorized()
|
sessionContext.Unauthorized()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
err = app.shareStore.RemoveUserFromShare(share, r.FormValue("user"))
|
loginName := r.FormValue("login")
|
||||||
|
|
||||||
|
message := fmt.Sprintf(`You are about to delete the login "%s".<br/><br/>
|
||||||
|
Are you sure you want to continue?`, loginName)
|
||||||
|
if confirmStatus := sessionContext.RequestConfirmation(template.HTML(message), "share-delete-login"); confirmStatus == confirmRequested {
|
||||||
|
// We have already rendered. Nothing to do.
|
||||||
|
return
|
||||||
|
} else if confirmStatus == confirmAccepted {
|
||||||
|
err = app.shareStore.RemoveLogin(share, sessionContext.user.Username, loginName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
sessionContext.RenderError(template.HTML("Cannot remove user from share: "+err.Error()), returnURL)
|
sessionContext.RenderError(template.HTML("Cannot remove login from share: "+err.Error()), returnURL)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
http.Redirect(w, r, "shares", http.StatusFound)
|
http.Redirect(w, r, "my-shares", http.StatusFound)
|
||||||
})
|
})
|
||||||
ar.Route("/create-share", func(r chi.Router) {
|
ar.Route("/create-share", func(r chi.Router) {
|
||||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -358,13 +500,10 @@ func newWebAdminHandler(app *app) *webAdminHandler {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if sessionContext.user.Role != GlobalRoleAdmin {
|
if !sessionContext.IsAdmin(share) {
|
||||||
shareRole, err := app.shareStore.GetShareAccess(share, sessionContext.user.Username)
|
|
||||||
if err != nil || shareRole != ShareRoleAdmin {
|
|
||||||
sessionContext.Unauthorized()
|
sessionContext.Unauthorized()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
message := fmt.Sprintf(`You are about to delete the share %s (%s).<br/>
|
message := fmt.Sprintf(`You are about to delete the share %s (%s).<br/>
|
||||||
This will delete all data permanently.<br/><br/>
|
This will delete all data permanently.<br/><br/>
|
||||||
|
@ -429,6 +568,16 @@ func (s *sessionContext) RenderError(msg template.HTML, returnURL string) {
|
||||||
s.RenderPage(s.h.tplError, model)
|
s.RenderPage(s.h.tplError, model)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *sessionContext) IsAdmin(share Share) bool {
|
||||||
|
if s.user.Role != GlobalRoleAdmin {
|
||||||
|
shareRole, err := s.h.shareStore.GetShareAccess(share, s.user.Username)
|
||||||
|
if err != nil || shareRole != ShareRoleAdmin {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func (s *sessionContext) Unauthorized() {
|
func (s *sessionContext) Unauthorized() {
|
||||||
http.Error(s.w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
http.Error(s.w, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue