forked from Deuxfleurs/guichet
Mechanism to create new account
This commit is contained in:
parent
ad00d0623e
commit
768f2de916
5 changed files with 246 additions and 11 deletions
2
Makefile
2
Makefile
|
@ -1,5 +1,5 @@
|
||||||
BIN=guichet
|
BIN=guichet
|
||||||
SRC=main.go ssha.go profile.go admin.go
|
SRC=main.go ssha.go profile.go admin.go invite.go
|
||||||
DOCKER=lxpz/guichet_amd64
|
DOCKER=lxpz/guichet_amd64
|
||||||
|
|
||||||
all: $(BIN)
|
all: $(BIN)
|
||||||
|
|
144
invite.go
Normal file
144
invite.go
Normal file
|
@ -0,0 +1,144 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"html/template"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkInviterLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
||||||
|
login := checkLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !login.CanInvite {
|
||||||
|
http.Error(w, "Not authorized to invite new users.", http.StatusUnauthorized)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return login
|
||||||
|
}
|
||||||
|
|
||||||
|
type NewAccountData struct {
|
||||||
|
Username string
|
||||||
|
DisplayName string
|
||||||
|
GivenName string
|
||||||
|
Surname string
|
||||||
|
|
||||||
|
ErrorUsernameTaken bool
|
||||||
|
ErrorInvalidUsername bool
|
||||||
|
ErrorPasswordTooShort bool
|
||||||
|
ErrorPasswordMismatch bool
|
||||||
|
ErrorMessage string
|
||||||
|
WarningMessage string
|
||||||
|
Success bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleInviteNewAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateInviteNewAccount := template.Must(template.ParseFiles("templates/layout.html", "templates/invite_new_account.html"))
|
||||||
|
|
||||||
|
login := checkInviterLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &NewAccountData{}
|
||||||
|
|
||||||
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
data.Username = strings.TrimSpace(strings.Join(r.Form["username"], ""))
|
||||||
|
data.DisplayName = strings.TrimSpace(strings.Join(r.Form["displayname"], ""))
|
||||||
|
data.GivenName = strings.TrimSpace(strings.Join(r.Form["givenname"], ""))
|
||||||
|
data.Surname = strings.TrimSpace(strings.Join(r.Form["surname"], ""))
|
||||||
|
|
||||||
|
password1 := strings.Join(r.Form["password"], "")
|
||||||
|
password2 := strings.Join(r.Form["password2"], "")
|
||||||
|
|
||||||
|
tryCreateAccount(login.conn, data, password1, password2)
|
||||||
|
}
|
||||||
|
|
||||||
|
templateInviteNewAccount.Execute(w, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryCreateAccount(l *ldap.Conn, data *NewAccountData, pass1 string, pass2 string) {
|
||||||
|
// Check if username is correct
|
||||||
|
if match, err := regexp.MatchString("^[a-zA-Z0-9._-]+$", data.Username); !(err == nil && match) {
|
||||||
|
data.ErrorInvalidUsername = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user exists
|
||||||
|
userDn := config.UserNameAttr + "=" + data.Username + "," + config.UserBaseDN
|
||||||
|
searchRq := ldap.NewSearchRequest(
|
||||||
|
userDn,
|
||||||
|
ldap.ScopeBaseObject, ldap.NeverDerefAliases, 0, 0, false,
|
||||||
|
"(objectclass=*)",
|
||||||
|
[]string{"dn"},
|
||||||
|
nil)
|
||||||
|
|
||||||
|
sr, err := l.Search(searchRq)
|
||||||
|
if err != nil {
|
||||||
|
data.ErrorMessage = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(sr.Entries) > 0 {
|
||||||
|
data.ErrorUsernameTaken = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that password is long enough
|
||||||
|
if len(pass1) < 8 {
|
||||||
|
data.ErrorPasswordTooShort = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if pass1 != pass2 {
|
||||||
|
data.ErrorPasswordMismatch = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actually create user
|
||||||
|
req := ldap.NewAddRequest(userDn, nil)
|
||||||
|
req.Attribute("objectclass", []string{"inetOrgPerson", "organizationalPerson", "person", "top"})
|
||||||
|
req.Attribute("structuralobjectclass", []string{"inetOrgPerson"})
|
||||||
|
req.Attribute("userpassword", []string{SSHAEncode([]byte(pass1))})
|
||||||
|
if len(data.DisplayName) > 0 {
|
||||||
|
req.Attribute("displayname", []string{data.DisplayName})
|
||||||
|
}
|
||||||
|
if len(data.GivenName) > 0 {
|
||||||
|
req.Attribute("givenname", []string{data.GivenName})
|
||||||
|
}
|
||||||
|
if len(data.Surname) > 0 {
|
||||||
|
req.Attribute("sn", []string{data.Surname})
|
||||||
|
}
|
||||||
|
if len(config.InvitedMailFormat) > 0 {
|
||||||
|
email := strings.ReplaceAll(config.InvitedMailFormat, "{}", data.Username)
|
||||||
|
req.Attribute("mail", []string{email})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = l.Add(req)
|
||||||
|
if err != nil {
|
||||||
|
data.ErrorMessage = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, group := range config.InvitedAutoGroups {
|
||||||
|
req := ldap.NewModifyRequest(group, nil)
|
||||||
|
req.Add("member", []string{userDn})
|
||||||
|
err = l.Modify(req)
|
||||||
|
if err != nil {
|
||||||
|
data.WarningMessage += fmt.Sprintf("Cannot add to %s: %s\n", group, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.Success = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleInviteSendCode(w http.ResponseWriter, r *http.Request) {
|
||||||
|
}
|
20
main.go
20
main.go
|
@ -23,11 +23,15 @@ type ConfigFile struct {
|
||||||
LdapServerAddr string `json:"ldap_server_addr"`
|
LdapServerAddr string `json:"ldap_server_addr"`
|
||||||
LdapTLS bool `json:"ldap_tls"`
|
LdapTLS bool `json:"ldap_tls"`
|
||||||
|
|
||||||
BaseDN string `json:"base_dn"`
|
BaseDN string `json:"base_dn"`
|
||||||
UserBaseDN string `json:"user_base_dn"`
|
UserBaseDN string `json:"user_base_dn"`
|
||||||
UserNameAttr string `json:"user_name_attr"`
|
UserNameAttr string `json:"user_name_attr"`
|
||||||
GroupBaseDN string `json:"group_base_dn"`
|
GroupBaseDN string `json:"group_base_dn"`
|
||||||
GroupNameAttr string `json:"group_name_attr"`
|
GroupNameAttr string `json:"group_name_attr"`
|
||||||
|
InvitationBaseDN string `json:"invitation_base_dn"`
|
||||||
|
InvitationNameAttr string `json:"invitation_name_attr"`
|
||||||
|
InvitedMailFormat string `json:"invited_mail_format"`
|
||||||
|
InvitedAutoGroups []string `json:"invited_auto_groups"`
|
||||||
|
|
||||||
AdminAccount string `json:"admin_account"`
|
AdminAccount string `json:"admin_account"`
|
||||||
GroupCanInvite string `json:"group_can_invite"`
|
GroupCanInvite string `json:"group_can_invite"`
|
||||||
|
@ -111,6 +115,9 @@ func main() {
|
||||||
r.HandleFunc("/profile", handleProfile)
|
r.HandleFunc("/profile", handleProfile)
|
||||||
r.HandleFunc("/passwd", handlePasswd)
|
r.HandleFunc("/passwd", handlePasswd)
|
||||||
|
|
||||||
|
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
||||||
|
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
||||||
|
|
||||||
r.HandleFunc("/admin/users", handleAdminUsers)
|
r.HandleFunc("/admin/users", handleAdminUsers)
|
||||||
r.HandleFunc("/admin/groups", handleAdminGroups)
|
r.HandleFunc("/admin/groups", handleAdminGroups)
|
||||||
r.HandleFunc("/admin/ldap/{dn}", handleAdminLDAP)
|
r.HandleFunc("/admin/ldap/{dn}", handleAdminLDAP)
|
||||||
|
@ -290,8 +297,7 @@ func handleHome(w http.ResponseWriter, r *http.Request) {
|
||||||
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
func handleLogout(w http.ResponseWriter, r *http.Request) {
|
||||||
session, err := store.Get(r, SESSION_NAME)
|
session, err := store.Get(r, SESSION_NAME)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
session, _ = store.New(r, SESSION_NAME)
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(session.Values, "login_username")
|
delete(session.Values, "login_username")
|
||||||
|
|
|
@ -16,12 +16,21 @@
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
<a class="list-group-item list-group-item-action" href="/profile">Modifier mon profil</a>
|
<a class="list-group-item list-group-item-action" href="/profile">Modifier mon profil</a>
|
||||||
<a class="list-group-item list-group-item-action" href="/passwd">Modifier mon mot de passe</a>
|
<a class="list-group-item list-group-item-action" href="/passwd">Modifier mon mot de passe</a>
|
||||||
{{if .Login.CanInvite}}
|
|
||||||
<a class="list-group-item list-group-item-action" href="/invite">Inviter quelqu'un</a>
|
|
||||||
{{end}}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{{if .Login.CanInvite}}
|
||||||
|
<div class="card mt-3">
|
||||||
|
<div class="card-header">
|
||||||
|
Inviter des gens sur Deuxfleurs
|
||||||
|
</div>
|
||||||
|
<div class="list-group list-group-flush">
|
||||||
|
<a class="list-group-item list-group-item-action" href="/invite/send_code">Envoyer un code d'invitation</a>
|
||||||
|
<a class="list-group-item list-group-item-action" href="/invite/new_account">Créer un nouveau compte directement</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
{{if .Login.CanAdmin}}
|
{{if .Login.CanAdmin}}
|
||||||
<div class="card mt-3">
|
<div class="card mt-3">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
|
|
76
templates/invite_new_account.html
Normal file
76
templates/invite_new_account.html
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
{{define "title"}}Créer un compte |{{end}}
|
||||||
|
|
||||||
|
{{define "body"}}
|
||||||
|
<div class="d-flex">
|
||||||
|
<h4>Création d'un nouveau compte</h4>
|
||||||
|
<a class="ml-auto btn btn-info" href="/">Retour</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .ErrorMessage}}
|
||||||
|
<div class="alert alert-danger mt-4">Impossible de créer le compte.
|
||||||
|
<div style="font-size: 0.8em">{{ .ErrorMessage }}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .WarningMessage}}
|
||||||
|
<div class="alert alert-danger mt-4">Des erreurs se sont produtes, le compte pourrait ne pas être totalement fonctionnel.
|
||||||
|
<div style="font-size: 0.8em">{{ .WarningMessage }}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .Success}}
|
||||||
|
<div class="alert alert-success mt-4">
|
||||||
|
Le compe a été créé !
|
||||||
|
Rendez-vous <a href="/logout">sur la page d'accueil</a> pour vous connecter avec ce nouveau compte.
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<form method="POST" class="mt-4">
|
||||||
|
<h5>Renseignements obligatoires</h5>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="username">Nom d'utilisateur souhaité :</label>
|
||||||
|
<input type="text" id="username" name="username" class="form-control" value="{{ .Username }}" />
|
||||||
|
</div>
|
||||||
|
{{if .ErrorInvalidUsername}}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
Nom d'utilisateur invalide. Ne peut contenir que les caractères suivants : chiffres, lettres, point, tiret bas (_) et tiret du milieu (-).
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .ErrorUsernameTaken}}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
Ce nom d'utilisateur est déjà pris.
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password">Mot de passe :</label>
|
||||||
|
<input type="password" id="password" name="password" class="form-control" />
|
||||||
|
</div>
|
||||||
|
{{if .ErrorPasswordTooShort}}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
Le mot de passe choisi est trop court (minimum 8 caractères).
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="password2">Répéter le mot de passe :</label>
|
||||||
|
<input type="password" id="password2" name="password2" class="form-control" />
|
||||||
|
</div>
|
||||||
|
{{if .ErrorPasswordMismatch}}
|
||||||
|
<div class="alert alert-warning">
|
||||||
|
Les deux mots de passe entrés ne correspondent pas.
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
<h5>Renseignements optionnels</h5>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="displayname">Nom complet :</label>
|
||||||
|
<input type="text" id="displayname" name="displayname" class="form-control" value="{{ .DisplayName }}" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="givenname">Prénom :</label>
|
||||||
|
<input type="text" id="givenname" name="givenname" class="form-control" value="{{ .GivenName }}" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="surname">Nom de famille :</label>
|
||||||
|
<input type="text" id="surname" name="surname" class="form-control" value="{{ .Surname }}" />
|
||||||
|
</div>
|
||||||
|
<button type="submit" class="btn btn-primary">Créer le compte</button>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
Loading…
Reference in a new issue