Merge branch 'pass-validation'
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@ -12,3 +12,5 @@
|
|||||||
|
|
||||||
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
|
||||||
.glide/
|
.glide/
|
||||||
|
vendor/
|
||||||
|
*.lock
|
||||||
|
29
Gopkg.toml
Normal file
29
Gopkg.toml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
|
||||||
|
# Gopkg.toml example
|
||||||
|
#
|
||||||
|
# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
|
||||||
|
# for detailed Gopkg.toml documentation.
|
||||||
|
#
|
||||||
|
# required = ["github.com/user/thing/cmd/thing"]
|
||||||
|
# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project"
|
||||||
|
# version = "1.0.0"
|
||||||
|
#
|
||||||
|
# [[constraint]]
|
||||||
|
# name = "github.com/user/project2"
|
||||||
|
# branch = "dev"
|
||||||
|
# source = "github.com/myfork/project2"
|
||||||
|
#
|
||||||
|
# [[override]]
|
||||||
|
# name = "github.com/x/y"
|
||||||
|
# version = "2.4.0"
|
||||||
|
|
||||||
|
|
||||||
|
[[constraint]]
|
||||||
|
name = "gopkg.in/ldap.v2"
|
||||||
|
version = "2.5.1"
|
||||||
|
[[constraint]]
|
||||||
|
name = "github.com/dchest/captcha"
|
||||||
|
branch = "master"
|
5
Makefile
5
Makefile
@ -3,8 +3,11 @@ VER=1.2
|
|||||||
|
|
||||||
.PHONY: all build push
|
.PHONY: all build push
|
||||||
|
|
||||||
all: build docker push clean
|
all: init build docker push clean
|
||||||
|
|
||||||
|
init:
|
||||||
|
dep ensure
|
||||||
|
|
||||||
build:
|
build:
|
||||||
GOOS=linux go build -o ldap-pass-webui main.go
|
GOOS=linux go build -o ldap-pass-webui main.go
|
||||||
|
|
||||||
|
32
README.md
32
README.md
@ -4,7 +4,29 @@ WebUI Client capable of connecting to backend LDAP server and changing the users
|
|||||||
|
|
||||||
![Screenshot](screenshots/index.png)
|
![Screenshot](screenshots/index.png)
|
||||||
|
|
||||||
## Running in docker container
|
The configuration is made with environment variables:
|
||||||
|
|
||||||
|
|Env variable|Default value|Description|
|
||||||
|
|------------|-------------|-----------|
|
||||||
|
|LPW_TITLE|Change your global password for example.org|Title that will appear on the page|
|
||||||
|
|LPW_HOST||LDAP Host to connect to|
|
||||||
|
|LPW_PORT|636|LDAP Port (389|636 are default LDAP/LDAPS)|
|
||||||
|
|LPW_ENCRYPTED|true|Use enrypted communication|
|
||||||
|
|LPW_START_TLS|false|Start TLS communication|
|
||||||
|
|LPW_SSL_SKIP_VERIFY|true|Skip TLS CA verification|
|
||||||
|
|LPW_USER_DN|uid=%s,ou=people,dc=example,dc=org|Filter expression to search the user for Binding|
|
||||||
|
|LPW_USER_BASE|ou=people,dc=example,dc=org|Base to use when doing the binding|
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
```sh
|
||||||
|
dep ensure
|
||||||
|
LPW_HOST=ldap_host_ip go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
Browse [http://localhost:8080/](http://localhost:8080/)
|
||||||
|
|
||||||
|
### Running in docker container
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
docker run -d -p 8080:8080 --name ldap-passwd-webui \
|
docker run -d -p 8080:8080 --name ldap-passwd-webui \
|
||||||
@ -23,6 +45,11 @@ docker run -d -p 8080:8080 --name ldap-passwd-webui \
|
|||||||
|
|
||||||
## Building and tagging
|
## Building and tagging
|
||||||
|
|
||||||
|
Get [Godep](https://github.com/golang/dep)
|
||||||
|
```sh
|
||||||
|
go get -u github.com/golang/dep/cmd/dep
|
||||||
|
```
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make
|
make
|
||||||
```
|
```
|
||||||
@ -30,4 +57,5 @@ make
|
|||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
* [Web UI for changing LDAP password - python](https://github.com/jirutka/ldap-passwd-webui)
|
* [Web UI for changing LDAP password - python](https://github.com/jirutka/ldap-passwd-webui)
|
||||||
* [Gitea](https://github.com/go-gitea/gitea)
|
* [Gitea](https://github.com/go-gitea/gitea)
|
||||||
|
* [dchest/captcha](https://github.com/dchest/captcha)
|
18
app/web.go
18
app/web.go
@ -8,6 +8,8 @@ import (
|
|||||||
|
|
||||||
"html/template"
|
"html/template"
|
||||||
|
|
||||||
|
"github.com/dchest/captcha"
|
||||||
|
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -19,14 +21,17 @@ type route struct {
|
|||||||
handler http.Handler
|
handler http.Handler
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegexpHandler is used for http handler to bind using regular expressions
|
||||||
type RegexpHandler struct {
|
type RegexpHandler struct {
|
||||||
routes []*route
|
routes []*route
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handler binds http handler on RegexpHandler
|
||||||
func (h *RegexpHandler) Handler(pattern *regexp.Regexp, verb string, handler http.Handler) {
|
func (h *RegexpHandler) Handler(pattern *regexp.Regexp, verb string, handler http.Handler) {
|
||||||
h.routes = append(h.routes, &route{pattern, verb, handler})
|
h.routes = append(h.routes, &route{pattern, verb, handler})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// HandleFunc binds http handler function on RegexpHandler
|
||||||
func (h *RegexpHandler) HandleFunc(r string, v string, handler func(http.ResponseWriter, *http.Request)) {
|
func (h *RegexpHandler) HandleFunc(r string, v string, handler func(http.ResponseWriter, *http.Request)) {
|
||||||
re := regexp.MustCompile(r)
|
re := regexp.MustCompile(r)
|
||||||
h.routes = append(h.routes, &route{re, v, http.HandlerFunc(handler)})
|
h.routes = append(h.routes, &route{re, v, http.HandlerFunc(handler)})
|
||||||
@ -48,6 +53,7 @@ type pageData struct {
|
|||||||
PatternInfo string
|
PatternInfo string
|
||||||
Username string
|
Username string
|
||||||
Alerts map[string]string
|
Alerts map[string]string
|
||||||
|
CaptchaId string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeAssets : Serves the static assets
|
// ServeAssets : Serves the static assets
|
||||||
@ -57,7 +63,7 @@ func ServeAssets(w http.ResponseWriter, req *http.Request) {
|
|||||||
|
|
||||||
// ServeIndex : Serves index page on GET request
|
// ServeIndex : Serves index page on GET request
|
||||||
func ServeIndex(w http.ResponseWriter, req *http.Request) {
|
func ServeIndex(w http.ResponseWriter, req *http.Request) {
|
||||||
p := &pageData{Title: getTitle(), Pattern: getPattern(), PatternInfo: getPatternInfo()}
|
p := &pageData{Title: getTitle(), CaptchaId: captcha.New(), Pattern: getPattern(), PatternInfo: getPatternInfo()}
|
||||||
t, e := template.ParseFiles(path.Join("templates", "index.html"))
|
t, e := template.ParseFiles(path.Join("templates", "index.html"))
|
||||||
if e != nil {
|
if e != nil {
|
||||||
log.Printf("Error parsing file %v\n", e)
|
log.Printf("Error parsing file %v\n", e)
|
||||||
@ -74,6 +80,8 @@ func ChangePassword(w http.ResponseWriter, req *http.Request) {
|
|||||||
oldPassword := req.Form["old-password"]
|
oldPassword := req.Form["old-password"]
|
||||||
newPassword := req.Form["new-password"]
|
newPassword := req.Form["new-password"]
|
||||||
confirmPassword := req.Form["confirm-password"]
|
confirmPassword := req.Form["confirm-password"]
|
||||||
|
captchaID := req.Form["captchaId"]
|
||||||
|
captchaSolution := req.Form["captchaSolution"]
|
||||||
|
|
||||||
alerts := map[string]string{}
|
alerts := map[string]string{}
|
||||||
|
|
||||||
@ -100,6 +108,12 @@ func ChangePassword(w http.ResponseWriter, req *http.Request) {
|
|||||||
alerts["error"] = alerts["error"] + fmt.Sprintf("%s", getPatternInfo())
|
alerts["error"] = alerts["error"] + fmt.Sprintf("%s", getPatternInfo())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(captchaID) < 1 || captchaID[0] == "" ||
|
||||||
|
len(captchaSolution) < 1 || captchaSolution[0] == "" ||
|
||||||
|
!captcha.VerifyString(captchaID[0], captchaSolution[0]) {
|
||||||
|
alerts["error"] = "Wrong captcha."
|
||||||
|
}
|
||||||
|
|
||||||
if len(alerts) == 0 {
|
if len(alerts) == 0 {
|
||||||
client := NewLDAPClient()
|
client := NewLDAPClient()
|
||||||
if err := client.ModifyPassword(un, oldPassword[0], newPassword[0]); err != nil {
|
if err := client.ModifyPassword(un, oldPassword[0], newPassword[0]); err != nil {
|
||||||
@ -109,7 +123,7 @@ func ChangePassword(w http.ResponseWriter, req *http.Request) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
p := &pageData{Title: getTitle(), Alerts: alerts, Username: un}
|
p := &pageData{Title: getTitle(), Alerts: alerts, Username: un, CaptchaId: captcha.New()}
|
||||||
|
|
||||||
t, e := template.ParseFiles(path.Join("templates", "index.html"))
|
t, e := template.ParseFiles(path.Join("templates", "index.html"))
|
||||||
if e != nil {
|
if e != nil {
|
||||||
|
9
main.go
9
main.go
@ -2,8 +2,10 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/npenkov/ldap-passwd-webui/app"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/dchest/captcha"
|
||||||
|
"github.com/npenkov/ldap-passwd-webui/app"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
@ -12,7 +14,8 @@ func main() {
|
|||||||
reHandler.HandleFunc(".*.[js|css|png|eof|svg|ttf|woff]", "GET", app.ServeAssets)
|
reHandler.HandleFunc(".*.[js|css|png|eof|svg|ttf|woff]", "GET", app.ServeAssets)
|
||||||
reHandler.HandleFunc("/", "GET", app.ServeIndex)
|
reHandler.HandleFunc("/", "GET", app.ServeIndex)
|
||||||
reHandler.HandleFunc("/", "POST", app.ChangePassword)
|
reHandler.HandleFunc("/", "POST", app.ChangePassword)
|
||||||
|
http.Handle("/captcha/", captcha.Server(captcha.StdWidth, captcha.StdHeight))
|
||||||
|
http.Handle("/", reHandler)
|
||||||
fmt.Println("Starting server on port 8080")
|
fmt.Println("Starting server on port 8080")
|
||||||
http.ListenAndServe(":8080", reHandler)
|
http.ListenAndServe(":8080", nil)
|
||||||
}
|
}
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 109 KiB |
@ -12,6 +12,36 @@
|
|||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<script>
|
||||||
|
function setSrcQuery(e, q) {
|
||||||
|
var src = e.src;
|
||||||
|
var p = src.indexOf('?');
|
||||||
|
if (p >= 0) {
|
||||||
|
src = src.substr(0, p);
|
||||||
|
}
|
||||||
|
e.src = src + "?" + q
|
||||||
|
}
|
||||||
|
function playAudio() {
|
||||||
|
var le = document.getElementById("lang");
|
||||||
|
var lang = le.options[le.selectedIndex].value;
|
||||||
|
var e = document.getElementById('audio')
|
||||||
|
setSrcQuery(e, "lang=" + lang)
|
||||||
|
e.style.display = 'block';
|
||||||
|
e.autoplay = 'true';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
function changeLang() {
|
||||||
|
var e = document.getElementById('audio')
|
||||||
|
if (e.style.display == 'block') {
|
||||||
|
playAudio();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function reload() {
|
||||||
|
setSrcQuery(document.getElementById('image'), "reload=" + (new Date()).getTime());
|
||||||
|
setSrcQuery(document.getElementById('audio'), (new Date()).getTime());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<main>
|
<main>
|
||||||
<h1>{{.Title}}</h1>
|
<h1>{{.Title}}</h1>
|
||||||
|
|
||||||
@ -28,8 +58,16 @@
|
|||||||
|
|
||||||
<label for="confirm-password">Confirm new password</label>
|
<label for="confirm-password">Confirm new password</label>
|
||||||
<input id="confirm-password" name="confirm-password" type="password"
|
<input id="confirm-password" name="confirm-password" type="password"
|
||||||
pattern="{{.Pattern}}" x-moz-errormessage="{{.PatternInfo}}" required>
|
pattern="{{.Pattern}}" x-moz-errormessage="{{.PatternInfo}}" required>
|
||||||
<p>{{.PatternInfo}}</p>
|
<p>{{.PatternInfo}}</p>
|
||||||
|
<p>Type the numbers you see in the picture below:</p>
|
||||||
|
<p>
|
||||||
|
<img id=image src="/captcha/{{.CaptchaId}}.png" alt="Captcha image">
|
||||||
|
</p>
|
||||||
|
<a href="#" onclick="reload()">Reload</a>
|
||||||
|
<input type=hidden name=captchaId value="{{.CaptchaId}}">
|
||||||
|
<br>
|
||||||
|
<input id="captchaSolution" name="captchaSolution" type="text" required>
|
||||||
<button type="submit">Update password</button>
|
<button type="submit">Update password</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user