forked from Deuxfleurs/guichet
Invitation mechanism with codes etc
This commit is contained in:
parent
768f2de916
commit
151a31a425
9 changed files with 329 additions and 33 deletions
4
admin.go
4
admin.go
|
@ -600,9 +600,9 @@ func handleAdminCreate(w http.ResponseWriter, r *http.Request) {
|
||||||
} else {
|
} else {
|
||||||
dn := data.IdType + "=" + data.IdValue + "," + super_dn
|
dn := data.IdType + "=" + data.IdValue + "," + super_dn
|
||||||
req := ldap.NewAddRequest(dn, nil)
|
req := ldap.NewAddRequest(dn, nil)
|
||||||
req.Attribute("objectClass", object_class)
|
req.Attribute("objectclass", object_class)
|
||||||
if data.StructuralObjectClass != "" {
|
if data.StructuralObjectClass != "" {
|
||||||
req.Attribute("structuralObjectClass", []string{data.StructuralObjectClass})
|
req.Attribute("structuralobjectclass", []string{data.StructuralObjectClass})
|
||||||
}
|
}
|
||||||
if data.DisplayName != "" {
|
if data.DisplayName != "" {
|
||||||
req.Attribute("displayname", []string{data.DisplayName})
|
req.Attribute("displayname", []string{data.DisplayName})
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -3,6 +3,8 @@ module deuxfleurs.fr/Deuxfleurs/guichet
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b
|
||||||
|
github.com/emersion/go-smtp v0.12.1
|
||||||
github.com/go-ldap/ldap v3.0.3+incompatible
|
github.com/go-ldap/ldap v3.0.3+incompatible
|
||||||
github.com/go-ldap/ldap/v3 v3.1.6
|
github.com/go-ldap/ldap/v3 v3.1.6
|
||||||
github.com/gorilla/mux v1.7.3
|
github.com/gorilla/mux v1.7.3
|
||||||
|
|
5
go.sum
5
go.sum
|
@ -1,4 +1,9 @@
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20190817083125-240c8404624e/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b h1:uhWtEWBHgop1rqEk2klKaxPAkVDCXexai6hSuRQ7Nvs=
|
||||||
|
github.com/emersion/go-sasl v0.0.0-20191210011802-430746ea8b9b/go.mod h1:G/dpzLu16WtQpBfQ/z3LYiYJn3ZhKSGWn83fyoyQe/k=
|
||||||
|
github.com/emersion/go-smtp v0.12.1 h1:1R8BDqrR2HhlGwgFYcOi+BVTvK1bMjAB65QcVpJ5sNA=
|
||||||
|
github.com/emersion/go-smtp v0.12.1/go.mod h1:SD9V/xa4ndMw77lR3Mf7htkp8RBNYuPh9UeuBs9tpUQ=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
|
github.com/go-asn1-ber/asn1-ber v1.3.1 h1:gvPdv/Hr++TRFCl0UbPFHC54P9N9jgsRPnmnr419Uck=
|
||||||
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
github.com/go-asn1-ber/asn1-ber v1.3.1/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0=
|
||||||
github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
|
github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk=
|
||||||
|
|
196
invite.go
196
invite.go
|
@ -1,15 +1,26 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/rand"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/emersion/go-sasl"
|
||||||
|
"github.com/emersion/go-smtp"
|
||||||
"github.com/go-ldap/ldap/v3"
|
"github.com/go-ldap/ldap/v3"
|
||||||
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var EMAIL_REGEXP = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+\\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
|
||||||
|
|
||||||
func checkInviterLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
func checkInviterLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
||||||
login := checkLogin(w, r)
|
login := checkLogin(w, r)
|
||||||
if login == nil {
|
if login == nil {
|
||||||
|
@ -24,6 +35,47 @@ func checkInviterLogin(w http.ResponseWriter, r *http.Request) *LoginStatus {
|
||||||
return login
|
return login
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// New account creation directly from interface
|
||||||
|
|
||||||
|
func handleInviteNewAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
|
login := checkInviterLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNewAccount(w, r, login.conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New account creation using code
|
||||||
|
|
||||||
|
func handleInvitationCode(w http.ResponseWriter, r *http.Request) {
|
||||||
|
code := mux.Vars(r)["code"]
|
||||||
|
code_id, code_pw := readCode(code)
|
||||||
|
|
||||||
|
l := ldapOpen(w)
|
||||||
|
if l == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN
|
||||||
|
err := l.Bind(inviteDn, code_pw)
|
||||||
|
if err != nil {
|
||||||
|
templateInviteInvalidCode := template.Must(template.ParseFiles("templates/layout.html", "templates/invite_invalid_code.html"))
|
||||||
|
templateInviteInvalidCode.Execute(w, nil)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if handleNewAccount(w, r, l) {
|
||||||
|
del_req := ldap.NewDelRequest(inviteDn, nil)
|
||||||
|
err = l.Del(del_req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Could not delete invitation %s: %s", inviteDn, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common functions for new account
|
||||||
|
|
||||||
type NewAccountData struct {
|
type NewAccountData struct {
|
||||||
Username string
|
Username string
|
||||||
DisplayName string
|
DisplayName string
|
||||||
|
@ -39,14 +91,9 @@ type NewAccountData struct {
|
||||||
Success bool
|
Success bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleInviteNewAccount(w http.ResponseWriter, r *http.Request) {
|
func handleNewAccount(w http.ResponseWriter, r *http.Request, l *ldap.Conn) bool {
|
||||||
templateInviteNewAccount := template.Must(template.ParseFiles("templates/layout.html", "templates/invite_new_account.html"))
|
templateInviteNewAccount := template.Must(template.ParseFiles("templates/layout.html", "templates/invite_new_account.html"))
|
||||||
|
|
||||||
login := checkInviterLogin(w, r)
|
|
||||||
if login == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
data := &NewAccountData{}
|
data := &NewAccountData{}
|
||||||
|
|
||||||
if r.Method == "POST" {
|
if r.Method == "POST" {
|
||||||
|
@ -60,10 +107,11 @@ func handleInviteNewAccount(w http.ResponseWriter, r *http.Request) {
|
||||||
password1 := strings.Join(r.Form["password"], "")
|
password1 := strings.Join(r.Form["password"], "")
|
||||||
password2 := strings.Join(r.Form["password2"], "")
|
password2 := strings.Join(r.Form["password2"], "")
|
||||||
|
|
||||||
tryCreateAccount(login.conn, data, password1, password2)
|
tryCreateAccount(l, data, password1, password2)
|
||||||
}
|
}
|
||||||
|
|
||||||
templateInviteNewAccount.Execute(w, data)
|
templateInviteNewAccount.Execute(w, data)
|
||||||
|
return data.Success
|
||||||
}
|
}
|
||||||
|
|
||||||
func tryCreateAccount(l *ldap.Conn, data *NewAccountData, pass1 string, pass2 string) {
|
func tryCreateAccount(l *ldap.Conn, data *NewAccountData, pass1 string, pass2 string) {
|
||||||
|
@ -140,5 +188,137 @@ func tryCreateAccount(l *ldap.Conn, data *NewAccountData, pass1 string, pass2 st
|
||||||
data.Success = true
|
data.Success = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleInviteSendCode(w http.ResponseWriter, r *http.Request) {
|
// ---- Code generation ----
|
||||||
|
|
||||||
|
type SendCodeData struct {
|
||||||
|
ErrorMessage string
|
||||||
|
ErrorInvalidEmail bool
|
||||||
|
Success bool
|
||||||
|
CodeDisplay string
|
||||||
|
CodeSentTo string
|
||||||
|
WebBaseAddress string
|
||||||
|
}
|
||||||
|
|
||||||
|
type CodeMailFields struct {
|
||||||
|
From string
|
||||||
|
To string
|
||||||
|
Code string
|
||||||
|
InviteFrom string
|
||||||
|
WebBaseAddress string
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleInviteSendCode(w http.ResponseWriter, r *http.Request) {
|
||||||
|
templateInviteSendCode := template.Must(template.ParseFiles("templates/layout.html", "templates/invite_send_code.html"))
|
||||||
|
|
||||||
|
login := checkInviterLogin(w, r)
|
||||||
|
if login == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data := &SendCodeData{
|
||||||
|
WebBaseAddress: config.WebAddress,
|
||||||
|
}
|
||||||
|
|
||||||
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
|
||||||
|
choice := strings.Join(r.Form["choice"], "")
|
||||||
|
if choice != "display" && choice != "send" {
|
||||||
|
http.Error(w, "Invalid entry", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
sendto := strings.Join(r.Form["sendto"], "")
|
||||||
|
|
||||||
|
trySendCode(login, choice, sendto, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
templateInviteSendCode.Execute(w, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func trySendCode(login *LoginStatus, choice string, sendto string, data *SendCodeData) {
|
||||||
|
// Generate code
|
||||||
|
code, code_id, code_pw := genCode()
|
||||||
|
|
||||||
|
// Create invitation object in database
|
||||||
|
inviteDn := config.InvitationNameAttr + "=" + code_id + "," + config.InvitationBaseDN
|
||||||
|
req := ldap.NewAddRequest(inviteDn, nil)
|
||||||
|
req.Attribute("userpassword", []string{SSHAEncode([]byte(code_pw))})
|
||||||
|
req.Attribute("objectclass", []string{"top", "invitationCode"})
|
||||||
|
|
||||||
|
err := login.conn.Add(req)
|
||||||
|
if err != nil {
|
||||||
|
data.ErrorMessage = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we want to display it, do so
|
||||||
|
if choice == "display" {
|
||||||
|
data.Success = true
|
||||||
|
data.CodeDisplay = code
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, we are sending a mail
|
||||||
|
if !EMAIL_REGEXP.MatchString(sendto) {
|
||||||
|
data.ErrorInvalidEmail = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
templateMail := template.Must(template.ParseFiles("templates/invite_mail.txt"))
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
templateMail.Execute(buf, &CodeMailFields{
|
||||||
|
To: sendto,
|
||||||
|
From: config.MailFrom,
|
||||||
|
InviteFrom: login.WelcomeName(),
|
||||||
|
Code: code,
|
||||||
|
WebBaseAddress: config.WebAddress,
|
||||||
|
})
|
||||||
|
|
||||||
|
log.Printf("Sending mail to: %s", sendto)
|
||||||
|
var auth sasl.Client = nil
|
||||||
|
if config.SMTPUsername != "" {
|
||||||
|
auth = sasl.NewPlainClient("", config.SMTPUsername, config.SMTPPassword)
|
||||||
|
}
|
||||||
|
err = smtp.SendMail(config.SMTPServer, auth, config.MailFrom, []string{sendto}, buf)
|
||||||
|
if err != nil {
|
||||||
|
data.ErrorMessage = err.Error()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("Mail sent.")
|
||||||
|
|
||||||
|
data.Success = true
|
||||||
|
data.CodeSentTo = sendto
|
||||||
|
}
|
||||||
|
|
||||||
|
func genCode() (code string, code_id string, code_pw string) {
|
||||||
|
random := make([]byte, 32)
|
||||||
|
n, err := rand.Read(random)
|
||||||
|
if err != nil || n != 32 {
|
||||||
|
log.Fatalf("Could not generate random bytes: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
a := binary.BigEndian.Uint32(random[0:4])
|
||||||
|
b := binary.BigEndian.Uint32(random[4:8])
|
||||||
|
c := binary.BigEndian.Uint32(random[8:12])
|
||||||
|
|
||||||
|
code = fmt.Sprintf("%03d-%03d-%03d", a%1000, b%1000, c%1000)
|
||||||
|
code_id, code_pw = readCode(code)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func readCode(code string) (code_id string, code_pw string) {
|
||||||
|
// Strip everything that is not a digit
|
||||||
|
code_digits := ""
|
||||||
|
for _, c := range code {
|
||||||
|
if c >= '0' && c <= '9' {
|
||||||
|
code_digits = code_digits + string(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
id_hash := sha256.Sum256([]byte("Guichet ID " + code_digits))
|
||||||
|
pw_hash := sha256.Sum256([]byte("Guichet PW " + code_digits))
|
||||||
|
|
||||||
|
code_id = hex.EncodeToString(id_hash[:8])
|
||||||
|
code_pw = hex.EncodeToString(pw_hash[:16])
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
38
main.go
38
main.go
|
@ -28,11 +28,18 @@ type ConfigFile struct {
|
||||||
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"`
|
InvitationBaseDN string `json:"invitation_base_dn"`
|
||||||
InvitationNameAttr string `json:"invitation_name_attr"`
|
InvitationNameAttr string `json:"invitation_name_attr"`
|
||||||
InvitedMailFormat string `json:"invited_mail_format"`
|
InvitedMailFormat string `json:"invited_mail_format"`
|
||||||
InvitedAutoGroups []string `json:"invited_auto_groups"`
|
InvitedAutoGroups []string `json:"invited_auto_groups"`
|
||||||
|
|
||||||
|
WebAddress string `json:"web_address"`
|
||||||
|
MailFrom string `json:"mail_from"`
|
||||||
|
SMTPServer string `json:"smtp_server"`
|
||||||
|
SMTPUsername string `json:"smtp_username"`
|
||||||
|
SMTPPassword string `json:"smtp_password"`
|
||||||
|
|
||||||
AdminAccount string `json:"admin_account"`
|
AdminAccount string `json:"admin_account"`
|
||||||
GroupCanInvite string `json:"group_can_invite"`
|
GroupCanInvite string `json:"group_can_invite"`
|
||||||
GroupCanAdmin string `json:"group_can_admin"`
|
GroupCanAdmin string `json:"group_can_admin"`
|
||||||
|
@ -51,11 +58,22 @@ func readConfig() ConfigFile {
|
||||||
HttpBindAddr: ":9991",
|
HttpBindAddr: ":9991",
|
||||||
LdapServerAddr: "ldap://127.0.0.1:389",
|
LdapServerAddr: "ldap://127.0.0.1:389",
|
||||||
LdapTLS: false,
|
LdapTLS: false,
|
||||||
|
|
||||||
BaseDN: "dc=example,dc=com",
|
BaseDN: "dc=example,dc=com",
|
||||||
UserBaseDN: "ou=users,dc=example,dc=com",
|
UserBaseDN: "ou=users,dc=example,dc=com",
|
||||||
UserNameAttr: "uid",
|
UserNameAttr: "uid",
|
||||||
GroupBaseDN: "ou=groups,dc=example,dc=com",
|
GroupBaseDN: "ou=groups,dc=example,dc=com",
|
||||||
GroupNameAttr: "gid",
|
GroupNameAttr: "gid",
|
||||||
|
|
||||||
|
InvitationBaseDN: "ou=invitations,dc=example,dc=com",
|
||||||
|
InvitationNameAttr: "cn",
|
||||||
|
InvitedMailFormat: "{}@example.com",
|
||||||
|
InvitedAutoGroups: []string{},
|
||||||
|
|
||||||
|
WebAddress: "https://guichet.example.com",
|
||||||
|
MailFrom: "guichet@example.com",
|
||||||
|
SMTPServer: "smtp.example.com",
|
||||||
|
|
||||||
AdminAccount: "uid=admin,dc=example,dc=com",
|
AdminAccount: "uid=admin,dc=example,dc=com",
|
||||||
GroupCanInvite: "",
|
GroupCanInvite: "",
|
||||||
GroupCanAdmin: "gid=admin,ou=groups,dc=example,dc=com",
|
GroupCanAdmin: "gid=admin,ou=groups,dc=example,dc=com",
|
||||||
|
@ -117,6 +135,7 @@ func main() {
|
||||||
|
|
||||||
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
|
||||||
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
r.HandleFunc("/invite/send_code", handleInviteSendCode)
|
||||||
|
r.HandleFunc("/invitation/{code}", handleInvitationCode)
|
||||||
|
|
||||||
r.HandleFunc("/admin/users", handleAdminUsers)
|
r.HandleFunc("/admin/users", handleAdminUsers)
|
||||||
r.HandleFunc("/admin/groups", handleAdminGroups)
|
r.HandleFunc("/admin/groups", handleAdminGroups)
|
||||||
|
@ -147,6 +166,17 @@ type LoginStatus struct {
|
||||||
CanInvite bool
|
CanInvite bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (login *LoginStatus) WelcomeName() string {
|
||||||
|
ret := login.UserEntry.GetAttributeValue("givenname")
|
||||||
|
if ret == "" {
|
||||||
|
ret = login.UserEntry.GetAttributeValue("displayname")
|
||||||
|
}
|
||||||
|
if ret == "" {
|
||||||
|
ret = login.Info.Username
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
func logRequest(handler http.Handler) http.Handler {
|
func logRequest(handler http.Handler) http.Handler {
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
|
log.Printf("%s %s %s\n", r.RemoteAddr, r.Method, r.URL)
|
||||||
|
@ -267,7 +297,6 @@ func ldapOpen(w http.ResponseWriter) *ldap.Conn {
|
||||||
|
|
||||||
type HomePageData struct {
|
type HomePageData struct {
|
||||||
Login *LoginStatus
|
Login *LoginStatus
|
||||||
WelcomeName string
|
|
||||||
BaseDN string
|
BaseDN string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,13 +311,6 @@ func handleHome(w http.ResponseWriter, r *http.Request) {
|
||||||
data := &HomePageData{
|
data := &HomePageData{
|
||||||
Login: login,
|
Login: login,
|
||||||
BaseDN: config.BaseDN,
|
BaseDN: config.BaseDN,
|
||||||
WelcomeName: login.UserEntry.GetAttributeValue("givenname"),
|
|
||||||
}
|
|
||||||
if data.WelcomeName == "" {
|
|
||||||
data.WelcomeName = login.UserEntry.GetAttributeValue("displayname")
|
|
||||||
}
|
|
||||||
if data.WelcomeName == "" {
|
|
||||||
data.WelcomeName = login.Info.Username
|
|
||||||
}
|
}
|
||||||
|
|
||||||
templateHome.Execute(w, data)
|
templateHome.Execute(w, data)
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
Bienvenue, <strong>{{ .WelcomeName }}</strong> !
|
Bienvenue, <strong>{{ .Login.WelcomeName }}</strong> !
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex">
|
<div class="d-flex">
|
||||||
<a class="ml-auto btn btn-sm btn-dark" href="/logout">Se déconnecter</a>
|
<a class="ml-auto btn btn-sm btn-dark" href="/logout">Se déconnecter</a>
|
||||||
|
|
12
templates/invite_invalid_code.html
Normal file
12
templates/invite_invalid_code.html
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{{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>
|
||||||
|
|
||||||
|
<div class="alert alert-danger mt-4">
|
||||||
|
Code d'invitation invalide.
|
||||||
|
</div>
|
||||||
|
{{end}}
|
13
templates/invite_mail.txt
Normal file
13
templates/invite_mail.txt
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
From: {{.From}}
|
||||||
|
To: {{.To}}
|
||||||
|
Subject: Code d'invitation Deuxfleurs
|
||||||
|
Content-type: text/plain; charset=utf-8
|
||||||
|
|
||||||
|
Vous avez été invité à créer un compte sur Deuxfleurs par {{.InviteFrom}} :)
|
||||||
|
|
||||||
|
Pour créer votre compte, rendez-vous à l'addresse suivante:
|
||||||
|
|
||||||
|
{{.WebBaseAddress}}/invite/{{.Code}}
|
||||||
|
|
||||||
|
À bientôt sur Deuxfleurs !
|
||||||
|
|
62
templates/invite_send_code.html
Normal file
62
templates/invite_send_code.html
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
{{define "title"}}Envoyer un code d'invitation |{{end}}
|
||||||
|
|
||||||
|
{{define "body"}}
|
||||||
|
<div class="d-flex">
|
||||||
|
<h4>Envoyer un code d'invitation</h4>
|
||||||
|
<a class="ml-auto btn btn-info" href="/">Retour</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{if .ErrorMessage}}
|
||||||
|
<div class="alert alert-danger mt-4">Impossible de génerer ou d'envoyer le code.
|
||||||
|
<div style="font-size: 0.8em">{{ .ErrorMessage }}</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{if .Success}}
|
||||||
|
<div class="alert alert-success mt-4">
|
||||||
|
{{if .CodeSentTo}}
|
||||||
|
Un code d'invitation a bien été envoyé à <code>{{ .CodeSentTo }}</code>.
|
||||||
|
{{end}}
|
||||||
|
{{if .CodeDisplay}}
|
||||||
|
Le code généré est le suivant:
|
||||||
|
|
||||||
|
<p style="text-align: center; font-size: 1.6em;" class="mt-4 mb-4">
|
||||||
|
{{ .CodeDisplay }}
|
||||||
|
</p>
|
||||||
|
<p style="text-align: center" class="mt-4 mb-4">
|
||||||
|
<a href="{{.WebBaseAddress}}/invitation/{{ .CodeDisplay }}">{{.WebBaseAddress}}/invitation/{{.CodeDisplay}}</a>
|
||||||
|
</p>
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<form method="POST" class="mt-4">
|
||||||
|
Choisissez une option:
|
||||||
|
|
||||||
|
<div class="input-group mt-4">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<input type="radio" name="choice" value="display" id="choice_display" checked="true">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label class="form-control" for="choice_display">
|
||||||
|
Afficher le code et me laisser l'envoyer
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="input-group mt-4">
|
||||||
|
<div class="input-group-prepend">
|
||||||
|
<div class="input-group-text">
|
||||||
|
<input type="radio" name="choice" value="send" id="choice_send" aria-label="Checkbox for following text input">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<label class="form-control" for="choice_send">
|
||||||
|
Envoyer le code à l'addresse suivante:
|
||||||
|
</label>
|
||||||
|
<input class="form-control" type="text" name="sendto" id="sendto" placeholder="Addresse mail..." onclick="document.getElementById('choice_send').checked = true;" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group mt-4">
|
||||||
|
<button type="submit" class="btn btn-primary">Génerer le code</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
Loading…
Reference in a new issue