add password change form
This commit is contained in:
parent
3d9c567cf6
commit
653d0926f7
1
go.mod
1
go.mod
|
@ -15,6 +15,7 @@ require (
|
|||
github.com/gorilla/sessions v1.2.1
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/keks/nocomment v0.0.0-20181007001506-30c6dcb4a472
|
||||
github.com/mattevans/pwned-passwords v0.3.0 // indirect
|
||||
github.com/mattn/go-sqlite3 v2.0.3+incompatible
|
||||
github.com/maxbrunsfeld/counterfeiter/v6 v6.3.0
|
||||
github.com/nicksnyder/go-i18n/v2 v2.1.2
|
||||
|
|
5
go.sum
5
go.sum
|
@ -264,6 +264,8 @@ github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0Q
|
|||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
|
||||
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mattevans/pwned-passwords v0.3.0 h1:PFUAQXHH6NVugTiQ3Uh/iUY5dUljtEmzdg2kE8a7cXI=
|
||||
github.com/mattevans/pwned-passwords v0.3.0/go.mod h1:waUnV5nlikMlUqnjQtFV+DAgFPUQNPabvMGv8NG2IaQ=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
|
@ -318,6 +320,7 @@ github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108
|
|||
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
|
||||
github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
|
||||
github.com/onsi/gomega v1.8.1/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
|
||||
github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
|
||||
github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
|
||||
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
|
||||
|
@ -333,6 +336,8 @@ github.com/oxtoacart/bpool v0.0.0-20190524125616-8c0b41497736 h1:C9bEdTfu5QY+TIf
|
|||
github.com/oxtoacart/bpool v0.0.0-20190524125616-8c0b41497736/go.mod h1:L3UMQOThbttwfYRNFOWLLVXMhk5Lkio4GGOtw5UrxS0=
|
||||
github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
|
||||
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
|
||||
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
|
||||
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
|
||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||
|
|
|
@ -9,10 +9,12 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
// ErrRedirect decide to not render a page during the controller
|
||||
ErrNotAuthorized = errors.New("rooms/web: not authorized")
|
||||
|
||||
ErrDenied = errors.New("rooms: this key has been banned")
|
||||
|
||||
ErrInsecurePassword = errors.New("room: password was found on the insecure password list of have-i-been-pwned")
|
||||
ErrPasswordMissmatch = errors.New("room: the entered password did not match the repeated one")
|
||||
)
|
||||
|
||||
type ErrNotFound struct{ What string }
|
||||
|
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/go-kit/kit/log/level"
|
||||
"github.com/gorilla/csrf"
|
||||
"github.com/gorilla/sessions"
|
||||
hibp "github.com/mattevans/pwned-passwords"
|
||||
"github.com/russross/blackfriday/v2"
|
||||
"go.mindeco.de/http/auth"
|
||||
"go.mindeco.de/http/render"
|
||||
|
@ -37,6 +38,8 @@ var HTMLTemplates = []string{
|
|||
"landing/about.tmpl",
|
||||
"alias.tmpl",
|
||||
|
||||
"change-member-password.tmpl",
|
||||
|
||||
"invite/consumed.tmpl",
|
||||
"invite/facade.tmpl",
|
||||
"invite/facade-fallback.tmpl",
|
||||
|
@ -231,6 +234,10 @@ func New(
|
|||
})),
|
||||
)
|
||||
|
||||
// Init the have-i-been-pwned client for insecure password checks.
|
||||
const storeExpiry = 1 * time.Hour
|
||||
hibpClient := hibp.NewClient(storeExpiry)
|
||||
|
||||
// this router is a bit of a qurik
|
||||
// TODO: explain problem between gorilla/mux named routers and authentication
|
||||
mainMux := &http.ServeMux{}
|
||||
|
@ -295,6 +302,73 @@ func New(
|
|||
)
|
||||
mainMux.Handle("/admin/", members.AuthenticateFromContext(r)(adminHandler))
|
||||
|
||||
m.Get(router.MembersChangePasswordForm).HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if members.FromContext(req.Context()) == nil {
|
||||
r.Error(w, req, http.StatusUnauthorized, weberrs.ErrNotAuthorized)
|
||||
return
|
||||
}
|
||||
|
||||
var pageData = make(map[string]interface{})
|
||||
pageData[csrf.TemplateTag] = csrf.TemplateField(req)
|
||||
|
||||
pageData["Flashes"], err = flashHelper.GetAll(w, req)
|
||||
if err != nil {
|
||||
r.Error(w, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: add resetToken to render
|
||||
|
||||
err = r.Render(w, req, "change-member-password.tmpl", http.StatusOK, pageData)
|
||||
if err != nil {
|
||||
r.Error(w, req, http.StatusInternalServerError, err)
|
||||
}
|
||||
})
|
||||
|
||||
m.Get(router.MembersChangePassword).HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
redirectURL := req.Header.Get("Referer")
|
||||
if redirectURL == "" {
|
||||
http.Error(w, "TODO: add correct redirect handling", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
err := req.ParseForm()
|
||||
if err != nil {
|
||||
r.Error(w, req, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
repeat := req.FormValue("repeat-password")
|
||||
newpw := req.FormValue("new-password")
|
||||
|
||||
if newpw != repeat {
|
||||
flashHelper.AddError(w, req, weberrs.ErrPasswordMissmatch)
|
||||
http.Redirect(w, req, redirectURL, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
if len(newpw) < 10 {
|
||||
flashHelper.AddError(w, req, fmt.Errorf("password too short"))
|
||||
http.Redirect(w, req, redirectURL, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
isPwned, err := hibpClient.Pwned.Compromised(newpw)
|
||||
if err != nil {
|
||||
r.Error(w, req, http.StatusInternalServerError, fmt.Errorf("have-i-been-pwned client failed: %w", err))
|
||||
return
|
||||
}
|
||||
|
||||
if isPwned {
|
||||
flashHelper.AddError(w, req, weberrs.ErrInsecurePassword)
|
||||
http.Redirect(w, req, redirectURL, http.StatusSeeOther)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Fprintln(w, "password looks okay!")
|
||||
// TODO: update password db
|
||||
})
|
||||
|
||||
// handle setting language
|
||||
m.Get(router.CompleteSetLanguage).HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
lang := req.FormValue("lang")
|
||||
|
|
|
@ -55,6 +55,9 @@ AuthFallbackTitle = "Passwort anmelden"
|
|||
AuthFallbackWelcome = "Eine Anmeldung mit Benutzername und Passwort ist nur möglich, wenn der Administrator Ihnen eines gegeben hat, da wir die Benutzerregistrierung nicht unterstützen."
|
||||
AuthFallbackInstruct = "Diese Methode ist ein akzeptabler Fallback, wenn Sie einen Benutzernamen und ein Passwort haben."
|
||||
|
||||
AuthFallbackPasswordChangeFormTitle = "Change Password"
|
||||
AuthFallbackPasswordChangeWelcome = "Here you can change your fallback password. Make sure it's longer then 10 characters and that they match."
|
||||
|
||||
# general dashboard stuff
|
||||
#########################
|
||||
|
||||
|
@ -113,6 +116,9 @@ AdminMemberDetailsRole = "Berechtigungsstufe"
|
|||
AdminMemberDetailsAliases = "Aliase"
|
||||
AdminMemberDetailsAliasRevoke = "Widerrufen"
|
||||
AdminMemberDetailsAliasRevoked = "Alias wurde widerrufen"
|
||||
AdminMemberDetailsInitiatePasswordChange = "Zurücksetzen des Plan-B Passworts"
|
||||
AdminMemberDetailsChangePassword = "Passwort ändern"
|
||||
AdminMemberDetailsCreatePasswordResetLink = "Reset Link erzeugen"
|
||||
AdminMemberDetailsExclusion = "Ausschluss aus diesem Raum"
|
||||
AdminMemberDetailsRemove = "Mitglied entfernen"
|
||||
|
||||
|
|
|
@ -62,6 +62,9 @@ AuthFallbackTitle = "Password sign-in"
|
|||
AuthFallbackWelcome = "Signing in with username and password is only possible if the administrator has given you one, because we do not support user registration."
|
||||
AuthFallbackInstruct = "This method is an acceptable fallback, if you have a username and password."
|
||||
|
||||
AuthFallbackPasswordChangeFormTitle = "Change Password"
|
||||
AuthFallbackPasswordChangeWelcome = "Here you can change your fallback password. Make sure it's longer then 10 characters and that they match."
|
||||
|
||||
# general dashboard stuff
|
||||
#########################
|
||||
|
||||
|
@ -120,6 +123,9 @@ AdminMemberDetailsRole = "Permission level"
|
|||
AdminMemberDetailsAliases = "Aliases"
|
||||
AdminMemberDetailsAliasRevoke = "Revoke"
|
||||
AdminMemberDetailsAliasRevoked = "Alias was revoked"
|
||||
AdminMemberDetailsInitiatePasswordChange = "Re-set Fallback password"
|
||||
AdminMemberDetailsChangePassword = "Change password"
|
||||
AdminMemberDetailsCreatePasswordResetLink = "Create password reset link"
|
||||
AdminMemberDetailsExclusion = "Exclusion from this room"
|
||||
AdminMemberDetailsRemove = "Remove member"
|
||||
|
||||
|
|
|
@ -23,11 +23,12 @@ const (
|
|||
|
||||
AdminMemberDetails = "admin:member:details"
|
||||
|
||||
AdminMembersOverview = "admin:members:overview"
|
||||
AdminMembersAdd = "admin:members:add"
|
||||
AdminMembersChangeRole = "admin:members:change-role"
|
||||
AdminMembersRemoveConfirm = "admin:members:remove:confirm"
|
||||
AdminMembersRemove = "admin:members:remove"
|
||||
AdminMembersOverview = "admin:members:overview"
|
||||
AdminMembersAdd = "admin:members:add"
|
||||
AdminMembersChangeRole = "admin:members:change-role"
|
||||
AdminMembersCreateFallbackReset = "admin:members:create-password-reset-link"
|
||||
AdminMembersRemoveConfirm = "admin:members:remove:confirm"
|
||||
AdminMembersRemove = "admin:members:remove"
|
||||
|
||||
AdminInvitesOverview = "admin:invites:overview"
|
||||
AdminInvitesRevokeConfirm = "admin:invites:revoke:confirm"
|
||||
|
@ -67,6 +68,7 @@ func Admin(m *mux.Router) *mux.Router {
|
|||
m.Path("/members").Methods("GET").Name(AdminMembersOverview)
|
||||
m.Path("/members/add").Methods("POST").Name(AdminMembersAdd)
|
||||
m.Path("/members/change-role").Methods("POST").Name(AdminMembersChangeRole)
|
||||
m.Path("/members/create-fallback-reset-link").Methods("POST").Name(AdminMembersCreateFallbackReset)
|
||||
m.Path("/members/remove/confirm").Methods("GET").Name(AdminMembersRemoveConfirm)
|
||||
m.Path("/members/remove").Methods("POST").Name(AdminMembersRemove)
|
||||
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
// constant names for the named routes
|
||||
const (
|
||||
CompleteIndex = "complete:index"
|
||||
CompleteAbout = "complete:about"
|
||||
|
||||
CompleteNoticeShow = "complete:notice:show"
|
||||
CompleteNoticeList = "complete:notice:list"
|
||||
|
@ -22,6 +21,9 @@ const (
|
|||
CompleteInviteFacadeFallback = "complete:invite:accept:fallback"
|
||||
CompleteInviteInsertID = "complete:invite:insert-id"
|
||||
CompleteInviteConsume = "complete:invite:consume"
|
||||
|
||||
MembersChangePasswordForm = "members:change-password:form"
|
||||
MembersChangePassword = "members:change-password"
|
||||
)
|
||||
|
||||
// CompleteApp constructs a mux.Router containing the routes for batch Complete html frontend
|
||||
|
@ -32,10 +34,12 @@ func CompleteApp() *mux.Router {
|
|||
Admin(m.PathPrefix("/admin").Subrouter())
|
||||
|
||||
m.Path("/").Methods("GET").Name(CompleteIndex)
|
||||
m.Path("/about").Methods("GET").Name(CompleteAbout)
|
||||
|
||||
m.Path("/alias/{alias}").Methods("GET").Name(CompleteAliasResolve)
|
||||
|
||||
m.Path("/members/change-password").Methods("GET").Name(MembersChangePasswordForm)
|
||||
m.Path("/members/change-password").Methods("POST").Name(MembersChangePassword)
|
||||
|
||||
m.Path("/join").Methods("GET").Name(CompleteInviteFacade)
|
||||
m.Path("/join-fallback").Methods("GET").Name(CompleteInviteFacadeFallback)
|
||||
m.Path("/join-manually").Methods("GET").Name(CompleteInviteInsertID)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
<label class="mt-2 mb-1 font-bold text-gray-400 text-sm">{{i18n "AdminMemberDetailsRole"}}</label>
|
||||
{{ $user := is_logged_in }}
|
||||
{{ $aliasBelongsToUser := eq $user.PubKey.Ref .Member.PubKey.Ref }}
|
||||
{{ $viewerIsSameAsMember := eq $user.PubKey.Ref .Member.PubKey.Ref }}
|
||||
{{ if member_is_elevated }}
|
||||
<details class="mb-8 self-start w-40" id="change-role">
|
||||
<summary class="px-3 py-1 rounded shadow bg-white ring-1 ring-gray-300 hover:bg-gray-100 cursor-pointer">
|
||||
|
@ -69,7 +69,7 @@
|
|||
>{{.Name}}</a>
|
||||
</div>
|
||||
|
||||
{{ if or member_is_elevated $aliasBelongsToUser }}
|
||||
{{ if or member_is_elevated $viewerIsSameAsMember }}
|
||||
<a
|
||||
href="{{urlTo "admin:aliases:revoke:confirm" "id" .ID}}"
|
||||
class="w-20 py-2 text-sm text-center text-gray-400 hover:text-red-600 font-bold cursor-pointer"
|
||||
|
@ -79,6 +79,24 @@
|
|||
</div>
|
||||
{{end}}
|
||||
|
||||
|
||||
{{ if $viewerIsSameAsMember }}
|
||||
<label class="mt-10 mb-1 font-bold text-gray-400 text-sm">{{i18n "AdminMemberDetailsInitiatePasswordChange"}}</label>
|
||||
<a
|
||||
id="change-password"
|
||||
href="{{urlTo "members:change-password:form" "id" .Member.ID}}"
|
||||
class="mb-8 self-start shadow rounded px-3 py-1 text-yellow-600 ring-1 ring-yellow-400 bg-white hover:bg-yellow-600 hover:text-gray-100 focus:outline-none focus:ring-2 focus:ring-red-400 cursor-pointer"
|
||||
>{{i18n "AdminMemberDetailsChangePassword"}}</a>
|
||||
{{ else if member_is_elevated }}
|
||||
<label class="mt-10 mb-1 font-bold text-gray-400 text-sm">{{i18n "AdminMemberDetailsInitiatePasswordChange"}}</label>
|
||||
<form method="POST" action="{{urlTo "admin:members:create-password-reset-link" "id" .Member.ID}}">
|
||||
{{ .csrfField }}
|
||||
<input type="submit"
|
||||
class="mb-8 self-start shadow rounded px-3 py-1 text-yellow-600 ring-1 ring-yellow-400 bg-white hover:bg-yellow-600 hover:text-gray-100 focus:outline-none focus:ring-2 focus:ring-red-400 cursor-pointer"
|
||||
value="{{i18n "AdminMemberDetailsCreatePasswordResetLink"}}">
|
||||
</form>
|
||||
{{ end }}
|
||||
|
||||
{{ if member_is_elevated }}
|
||||
<label class="mt-10 mb-1 font-bold text-gray-400 text-sm">{{i18n "AdminMemberDetailsExclusion"}}</label>
|
||||
<a
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
{{ define "title" }}{{ i18n "AuthFallbackPasswordChangeFormTitle" }}{{ end }}
|
||||
{{ define "content" }}
|
||||
<div class="flex flex-col justify-center items-center self-center max-w-lg">
|
||||
<span id="welcome" class="text-center mt-8">{{i18n "AuthFallbackPasswordChangeWelcome"}}</span>
|
||||
|
||||
{{ template "flashes" . }}
|
||||
|
||||
<form
|
||||
id="change-password"
|
||||
action="{{urlTo "members:change-password"}}"
|
||||
method="POST"
|
||||
class="flex flex-col items-center self-stretch"
|
||||
>
|
||||
{{.csrfField}}
|
||||
<input type="hidden" name="member-id" value="member-id"}}>
|
||||
|
||||
<input
|
||||
type="password"
|
||||
name="new-password"
|
||||
class="mt-8 self-stretch shadow rounded border border-transparent h-10 p-1 pl-4 font-mono truncate flex-auto text-gray-600 focus:outline-none focus:ring-2 focus:ring-purple-400 focus:border-transparent">
|
||||
|
||||
<input
|
||||
type="password"
|
||||
name="repeat-password"
|
||||
class="mt-8 self-stretch shadow rounded border border-transparent h-10 p-1 pl-4 font-mono truncate flex-auto text-gray-600 focus:outline-none focus:ring-2 focus:ring-purple-400 focus:border-transparent">
|
||||
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="my-8 w-32 shadow rounded px-4 h-8 text-gray-100 bg-purple-500 hover:bg-purple-600 focus:outline-none focus:ring-2 focus:ring-purple-600 focus:ring-opacity-50"
|
||||
>{{i18n "GenericSubmit"}}</button>
|
||||
</form>
|
||||
</div>
|
||||
{{ end }}
|
Loading…
Reference in New Issue