diff options
-rw-r--r-- | main.go | 49 | ||||
-rw-r--r-- | static/css/main.css | 5 | ||||
-rw-r--r-- | static/js/password_strength.js | 85 | ||||
-rw-r--r-- | templates/change_ssh.html | 2 | ||||
-rw-r--r-- | templates/changepw.html | 7 | ||||
-rw-r--r-- | templates/index.html | 10 | ||||
-rw-r--r-- | templates/layout/base.html | 6 | ||||
-rw-r--r-- | views.go | 5 |
8 files changed, 118 insertions, 51 deletions
@@ -17,6 +17,7 @@ type PwmanServer struct { Krb5Conf string ChangePwScript string RemoteUserHeader string + BasePath string } var pwman *PwmanServer @@ -24,7 +25,7 @@ var pwman *PwmanServer const csrf_base = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789._#%!&:;?+{}[]" func main() { - var ldapServer, ldapUser, ldapPassword, pwnedFile, krb5Conf, changePwScript, csrfSecret, serverAddr string + var ldapServer, ldapUser, ldapPassword, pwnedFile, krb5Conf, changePwScript, csrfSecret, serverAddr, basePath string var ldapPort int var ldapSkipSSLVerify, csrfInsecure, gennerateCsrfKey bool flag.StringVar(&ldapServer, "ldap-server", "localhost", "the ldap server address") @@ -37,6 +38,7 @@ func main() { flag.StringVar(&changePwScript, "changepw-script", "./create-kdc-principal.pl", "Path to the change password script") flag.StringVar(&csrfSecret, "csrf-secret", "", "Specify csrf 32 char secret") flag.StringVar(&serverAddr, "address", ":3000", "Server address to listen on") + flag.StringVar(&basePath, "base-path", "", "A base path that pwman lives under e.g. /sso") flag.BoolVar(&csrfInsecure, "csrf-insecure", false, "Allow csrf cookie to be sent over http") flag.BoolVar(&gennerateCsrfKey, "gennerate-csrf", false, "Gennerate a csrf secret") flag.Parse() @@ -58,19 +60,19 @@ func main() { Krb5Conf: krb5Conf, ChangePwScript: changePwScript, RemoteUserHeader: "X-Remote-User", + BasePath: basePath, } - base_path := "/sso" v := Views() mux := http.NewServeMux() - mux.Handle(base_path+"/", FlashMessage(RemoteUser(v.Index()))) - mux.Handle(base_path+"/sso", FlashMessage(RemoteUser(v.ChangePassword("SSO")))) - mux.Handle(base_path+"/tacacs", FlashMessage(RemoteUser(v.ChangePassword("TACACS")))) - mux.Handle(base_path+"/eduroam", FlashMessage(RemoteUser(v.ChangePassword("eduroam")))) - mux.Handle(base_path+"/pubkeys", FlashMessage(RemoteUser(v.ChangeSSHKeys()))) + mux.Handle(basePath+"/", http.StripPrefix(basePath, FlashMessage(RemoteUser(v.Index())))) + mux.Handle(basePath+"/changepw/sso/", FlashMessage(RemoteUser(v.ChangePassword("SSO")))) + mux.Handle(basePath+"/changepw/tacacs/", FlashMessage(RemoteUser(v.ChangePassword("TACACS")))) + mux.Handle(basePath+"/changepw/eduroam/", FlashMessage(RemoteUser(v.ChangePassword("eduroam")))) + mux.Handle(basePath+"/pubkeys/", FlashMessage(RemoteUser(v.ChangeSSHKeys()))) - mux.Handle(base_path+"/static/", http.StripPrefix(base_path+"/static", http.FileServer(http.Dir("static")))) + mux.Handle(basePath+"/static/", http.StripPrefix(basePath+"/static", http.FileServer(http.Dir("static")))) CSRF := csrf.Protect([]byte(csrfSecret), csrf.Secure(!csrfInsecure)) @@ -95,34 +97,3 @@ func gennerateCsrfSecret() string { } return string(b) } - -//type CustomMux struct { -// base_path string -// mux *http.ServeMux -//} -// -//func NewCustomMux(base_path string) *CustomMux { -// return &CustomMux{base_path, http.NewServeMux()} -//} -// -//func (m *CustomMux) Handle(path string, h http.Handler) { -// m.mux.Handle(path, h) -//} -// -//func (m *CustomMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { -// clean_path := filepath.Clean(r.URL.Path) -// log.Println(clean_path) -// if !strings.HasPrefix(clean_path, m.base_path) { -// http.NotFound(w, r) -// return -// } -// r.URL.Path = clean_path[len(m.base_path):] -// log.Println(clean_path[len(m.base_path):]) -// m.mux.ServeHTTP(w, r) -//} - -//type RemoteUserMux map[string] http.Handler -// -//func (m RemoteUserMux) ServeHTTP(w http.ResponseWriter, r *http.Request) { -// handler, ok := m[r.URL.Path -//} diff --git a/static/css/main.css b/static/css/main.css index 31caa19..f44e5a6 100644 --- a/static/css/main.css +++ b/static/css/main.css @@ -288,3 +288,8 @@ textarea { background-color: #fff3cd;
border-color: #ffeeba;
}
+
+.password_strength {
+ text-align: right;
+ display: block;
+}
diff --git a/static/js/password_strength.js b/static/js/password_strength.js new file mode 100644 index 0000000..af0b1c4 --- /dev/null +++ b/static/js/password_strength.js @@ -0,0 +1,85 @@ +(function(){ + +function count(val, rxp){ + const match = val.match(rxp) + return match ? match.length : 0 +} + +function passwordStrength(password, options={}) { + const opt = Object.assign(options,{ + "minLength": 10, + "minOther": 3, + "texts" : [ + 'Too weak', + 'Weak password', + 'Normal strength', + 'Strong password', + 'Very strong password' + ] + }) + + + const len = password.length + if (len < opt["minLength"]) { + return opt["texts"][0]; + } + + const num = count(password, /\d/g), + lower = count(password, /[a-z]/g), + upper = count(password, /[A-Z]/g), + special = len - num - lower - upper, + other = num + special + + if (lower == 0 || upper == 0 || other < opt["minOther"]) { + return opt["texts"][0]; + } + + // Strength is just based on password length + if (len < 11) { + return opt["texts"][1]; + }else if (len < 13) { + return opt["texts"][2]; + }else if (len < 16) { + return opt["texts"][3]; + }else if (len >= 16) { + return opt["texts"][4]; + } + + // falltrough + return opt["texts"][1]; +} + + +// auto setup for data-password-strength + +document.querySelectorAll("input[data-password-strength]").forEach( ($elm) => { + $elm.addEventListener("keyup", (e) => { + const val = $elm.value; + if (val.length > 0) { + // check if we have a .password_strength beside the field + let $info = $elm.parentNode.querySelector(".password_strength") + if (!$info) { + $info = document.createElement("span"); + $info.classList.add("password_strength"); + $elm.parentNode.appendChild($info); + } + $info.textContent = passwordStrength(val); + } + }, false) +}) + + +document.querySelectorAll("input[data-same-as]").forEach( ($elm) => { + const $target = document.querySelector("#"+$elm.dataset.sameAs) + if ($target) { + $elm.addEventListener("keyup", (e) => { + if ($elm.value != $target.value) { + $elm.setCustomValidity("Passwords do not match") + }else{ + $elm.setCustomValidity(""); + } + }) + } +}); + +})(); diff --git a/templates/change_ssh.html b/templates/change_ssh.html index 96231a7..f87afce 100644 --- a/templates/change_ssh.html +++ b/templates/change_ssh.html @@ -27,6 +27,6 @@ </form> <div class="column full"> - <a href=".">Back</a> + <a href="{{.BasePath}}/">Back</a> </div> {{ end }} diff --git a/templates/changepw.html b/templates/changepw.html index a7c1dc6..207ce7e 100644 --- a/templates/changepw.html +++ b/templates/changepw.html @@ -12,10 +12,10 @@ <br> {{ .CsrfField }} <label class="form-element-wrapper">New password - <input type="password" name="new_password" class="form-element form-field" /> + <input id="new_password" type="password" name="new_password" class="form-element form-field" data-password-strength /> </label> <label class="form-element-wrapper">Repeat password - <input type="password" name="new_password_again" class="form-element form-field" /> + <input type="password" name="new_password_again" class="form-element form-field" data-same-as="new_password" /> </label> <div class="form-element-wrapper"> <input type="submit" value="Change password" class="form-element form-button" /> @@ -23,6 +23,7 @@ </form> <div class="column full"> - <a href=".">Back</a> + <a href="{{.BasePath}}/">Back</a> </div> + <script src="{{.BasePath}}/static/js/password_strength.js"></script> {{ end }} diff --git a/templates/index.html b/templates/index.html index 218ffe2..afadf8b 100644 --- a/templates/index.html +++ b/templates/index.html @@ -17,24 +17,24 @@ <h3>Available actions</h3> <ul class="unstyled"> <li> - <a href="sso"><span class="item-marker">›</span> Change single sign on (SSO) password</a> + <a href="{{$.BasePath}}/changepw/sso/"><span class="item-marker">›</span> Change single sign on (SSO) password</a> </li> {{ if .Staff }} <li> - <a href="tacacs"><span class="item-marker">›</span> Change TACACS password</a> + <a href="{{$.BasePath}}/changepw/tacacs/"><span class="item-marker">›</span> Change TACACS password</a> </li> {{ end }} {{ if .Active }} <li> - <a href="eduroam"><span class="item-marker">›</span> Change eduroam password</a> + <a href="{{$.BasePath}}/changepw/eduroam/"><span class="item-marker">›</span> Change eduroam password</a> </li> {{ end }} {{ if .Staff }} <li> - <a href="pubkeys"><span class="item-marker">›</span> Update your public SSH keys</a> + <a href="{{$.BasePath}}/pubkeys/"><span class="item-marker">›</span> Update your public SSH keys</a> </li> <li> - <a href="ideviceconf" rel="external"><span class="item-marker">›</span> Configure eduroam on your iDevice</a> + <a href="{{$.BasePath}}/ideviceconf" rel="external"><span class="item-marker">›</span> Configure eduroam on your iDevice</a> </li> {{ end }} </ul> diff --git a/templates/layout/base.html b/templates/layout/base.html index f041321..95a1c86 100644 --- a/templates/layout/base.html +++ b/templates/layout/base.html @@ -5,14 +5,14 @@ <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1.0"> <title>{{block "title" .}}SSO Password Manager{{end}}</title> - <link rel="stylesheet" type="text/css" href="static/css/main.css"> - <link href="static/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon"> + <link rel="stylesheet" type="text/css" href="{{.BasePath}}/static/css/main.css"> + <link href="{{.BasePath}}/static/images/favicon.ico" rel="shortcut icon" type="image/vnd.microsoft.icon"> </head> <body> <div class="wrapper"> <header> <div class="container"> - <img src="static/images/nordunet.png" alt="NORDUnet Nordic Gateway for Research and Education"> + <img src="{{.BasePath}}/static/images/nordunet.png" alt="NORDUnet Nordic Gateway for Research and Education"> </div> </header> <div class="container flex-group"> @@ -25,6 +25,10 @@ func Views() *views { func (v *views) Index() http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + if req.URL.Path != "/" { + http.NotFound(w, req) + return + } if _, ok := req.Context().Value("user").(*User); ok { err := v.templates["index"].ExecuteTemplate(w, "base", NewPageCtx(req)) if err != nil { @@ -102,6 +106,7 @@ func NewPageCtx(req *http.Request) PageCtx { "FlashClass": flash_class, "User": user, "CsrfField": csrf.TemplateField(req), + "BasePath": pwman.BasePath, } } |