16 changed files with 386 additions and 62 deletions
@ -0,0 +1,64 @@
|
||||
package db |
||||
|
||||
import ( |
||||
"io/ioutil" |
||||
"os" |
||||
"path" |
||||
"testing" |
||||
) |
||||
|
||||
const ( |
||||
key = "key" |
||||
value = "value" |
||||
) |
||||
|
||||
func initTestDB(t *testing.T) *DB { |
||||
dir, err := ioutil.TempDir("", "lowry_test") |
||||
if err != nil { |
||||
t.Fatalf("Can't create a temp dir: %v", err) |
||||
} |
||||
db, err := Init(path.Join(dir, "bolt.db")) |
||||
if err != nil { |
||||
t.Fatalf("Can't init db (%s): %v", dir, err) |
||||
} |
||||
return db |
||||
} |
||||
|
||||
func delTestDB(db *DB) { |
||||
dir := path.Dir(db.bolt.Path()) |
||||
db.Close() |
||||
os.RemoveAll(dir) |
||||
} |
||||
|
||||
func TestPutGet(t *testing.T) { |
||||
db := initTestDB(t) |
||||
defer delTestDB(db) |
||||
|
||||
err := db.get(inviteBucket, key, value) |
||||
if _, ok := err.(notFoundError); !ok { |
||||
t.Errorf("Got something else than notFoundError before put: %v", err) |
||||
} |
||||
|
||||
err = db.put(inviteBucket, key, value) |
||||
if err != nil { |
||||
t.Fatalf("Got an error putting: %v", err) |
||||
} |
||||
|
||||
var v string |
||||
err = db.get(inviteBucket, key, &v) |
||||
if err != nil { |
||||
t.Fatalf("Got an error getting: %v", err) |
||||
} |
||||
if v != value { |
||||
t.Fatalf("Expected %v got %v", value, v) |
||||
} |
||||
|
||||
err = db.del(inviteBucket, key) |
||||
if err != nil { |
||||
t.Fatalf("Got an error deleting: %v", err) |
||||
} |
||||
err = db.get(inviteBucket, key, value) |
||||
if _, ok := err.(notFoundError); !ok { |
||||
t.Errorf("Got something else than notFoundError after delete: %v", err) |
||||
} |
||||
} |
@ -0,0 +1,55 @@
|
||||
package db |
||||
|
||||
import ( |
||||
"encoding/json" |
||||
"log" |
||||
"time" |
||||
|
||||
"go.etcd.io/bbolt" |
||||
) |
||||
|
||||
var ( |
||||
inviteBucket = []byte("invite") |
||||
) |
||||
|
||||
type invite struct { |
||||
User string |
||||
CreationDate time.Time |
||||
} |
||||
|
||||
// AddInvite stores in the db the invite code generated by user
|
||||
func (db *DB) AddInvite(code string, user string) error { |
||||
return db.put(inviteBucket, code, invite{user, time.Now()}) |
||||
} |
||||
|
||||
// IsInviteValid checks if the invite code is in the database
|
||||
func (db *DB) IsInviteValid(code string) bool { |
||||
var inv invite |
||||
err := db.get(inviteBucket, code, &inv) |
||||
return err == nil |
||||
} |
||||
|
||||
// DelInvite code from the database
|
||||
func (db *DB) DelInvite(code string) error { |
||||
return db.del(inviteBucket, code) |
||||
} |
||||
|
||||
// ExpireInvites older than duration
|
||||
func (db *DB) ExpireInvites(duration time.Duration) error { |
||||
return db.bolt.Update(func(tx *bbolt.Tx) error { |
||||
b := tx.Bucket(inviteBucket) |
||||
return b.ForEach(func(k, v []byte) error { |
||||
var inv invite |
||||
err := json.Unmarshal(v, &inv) |
||||
if err != nil { |
||||
log.Printf("Error unmarshalling invite %s: %v", string(k), err) |
||||
return nil |
||||
} |
||||
|
||||
if inv.CreationDate.Add(duration).Before(time.Now()) { |
||||
return b.Delete(k) |
||||
} |
||||
return nil |
||||
}) |
||||
}) |
||||
} |
@ -0,0 +1,56 @@
|
||||
package db |
||||
|
||||
import ( |
||||
"testing" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
code = "code" |
||||
) |
||||
|
||||
func TestAddInvite(t *testing.T) { |
||||
db := initTestDB(t) |
||||
defer delTestDB(db) |
||||
|
||||
if db.IsInviteValid(code) { |
||||
t.Errorf("Got valid invite before adding it") |
||||
} |
||||
|
||||
err := db.AddInvite(code, "") |
||||
if err != nil { |
||||
t.Fatalf("Got an error adding invite: %v", err) |
||||
} |
||||
if !db.IsInviteValid(code) { |
||||
t.Errorf("Got invalid invite after adding it") |
||||
} |
||||
|
||||
err = db.DelInvite(code) |
||||
if err != nil { |
||||
t.Fatalf("Got an error deleting invite: %v", err) |
||||
} |
||||
if db.IsInviteValid(code) { |
||||
t.Errorf("Got valid invite deleting it") |
||||
} |
||||
} |
||||
|
||||
func TestExpireInvites(t *testing.T) { |
||||
db := initTestDB(t) |
||||
defer delTestDB(db) |
||||
|
||||
err := db.AddInvite(code, "") |
||||
if err != nil { |
||||
t.Fatalf("Got an error adding invite: %v", err) |
||||
} |
||||
if !db.IsInviteValid(code) { |
||||
t.Errorf("Got invalid invite after adding it") |
||||
} |
||||
|
||||
err = db.ExpireInvites(time.Microsecond) |
||||
if err != nil { |
||||
t.Fatalf("Got an error expiring invites: %v", err) |
||||
} |
||||
if db.IsInviteValid(code) { |
||||
t.Errorf("Got valid invite after expiring it") |
||||
} |
||||
} |
@ -0,0 +1,104 @@
|
||||
package server |
||||
|
||||
import ( |
||||
"crypto/rand" |
||||
"encoding/base64" |
||||
"fmt" |
||||
"log" |
||||
"net/http" |
||||
|
||||
"0xacab.org/sindominio/lowry/ldap" |
||||
"github.com/gorilla/mux" |
||||
) |
||||
|
||||
func (s *server) createInviteHandler(w http.ResponseWriter, r *http.Request) { |
||||
response := s.newResponse("invite", w, r) |
||||
if response.Role != ldap.Sindominante { |
||||
log.Printf("Non sindominante attemp to create an invite, user: %s", response.User) |
||||
s.forbiddenHandler(w, r) |
||||
return |
||||
} |
||||
|
||||
buff := make([]byte, 9) |
||||
_, err := rand.Read(buff) |
||||
if err != nil { |
||||
log.Printf("An error has ocurred generating a random invite: %v", err) |
||||
s.addUserGroupHandler(w, r) |
||||
return |
||||
} |
||||
invite := base64.URLEncoding.EncodeToString(buff) |
||||
|
||||
err = s.db.AddInvite(invite, response.User) |
||||
if err != nil { |
||||
log.Printf("An error has ocurred storing the invite (%s - %s): %v", invite, response.User, err) |
||||
s.addUserGroupHandler(w, r) |
||||
return |
||||
} |
||||
|
||||
inviteURL := fmt.Sprintf("https://%v/adduser/%v", r.Host, invite) |
||||
response.execute(struct { |
||||
InviteURL string |
||||
}{inviteURL}) |
||||
} |
||||
|
||||
func (s *server) addUserHandler(w http.ResponseWriter, r *http.Request) { |
||||
vars := mux.Vars(r) |
||||
invite := vars["invite"] |
||||
if !s.db.IsInviteValid(invite) { |
||||
log.Printf("Invalid invite code: %s", invite) |
||||
s.forbiddenHandler(w, r) |
||||
return |
||||
} |
||||
|
||||
response := s.newResponse("adduser", w, r) |
||||
if r.Method != "POST" { |
||||
response.execute("") |
||||
return |
||||
} |
||||
|
||||
name := r.FormValue("name") |
||||
pass := r.FormValue("password") |
||||
pass2 := r.FormValue("password2") |
||||
if pass != pass2 { |
||||
response.execute("WrongPass") |
||||
return |
||||
} |
||||
|
||||
if name == "" || pass == "" { |
||||
response.execute("empty") |
||||
return |
||||
} |
||||
_, err := s.ldap.GetUser(name) |
||||
if err == nil { |
||||
log.Println("Can't create user ", name, ": already exist") |
||||
response.execute("exsist") |
||||
return |
||||
} |
||||
|
||||
err = s.ldap.AddGroup(name) |
||||
if err != nil { |
||||
log.Println("Error adding group: ", err) |
||||
s.errorHandler(w, r) |
||||
return |
||||
} |
||||
group, err := s.ldap.GetGroup(name) |
||||
if err != nil { |
||||
log.Println("Error getting group: ", err) |
||||
s.errorHandler(w, r) |
||||
return |
||||
} |
||||
|
||||
err = s.ldap.AddUser(name, pass, group.GID) |
||||
if err != nil { |
||||
log.Println("Error adding user: ", err) |
||||
s.errorHandler(w, r) |
||||
return |
||||
} |
||||
err = s.db.DelInvite(invite) |
||||
if err != nil { |
||||
log.Println("Error deleting invite: ", err) |
||||
} |
||||
|
||||
response = s.newResponse("adduser_success", w, r) |
||||
response.execute("name") |
||||
} |
@ -0,0 +1,16 @@
|
||||
{{template "header.html"}} |
||||
|
||||
<div class="container"> |
||||
<br /> |
||||
<h1 class="row justify-content-center">Usuaria creada</h1> |
||||
|
||||
<br /> |
||||
<div class="row justify-content-center"> |
||||
<div class="col-md-8"> |
||||
<p>Bienvenida a SinDominio. Puedes entrar a <a href="https://lowry.sindominio.net/">gestionar tu cuenta</a>, o ir directamente a la <a href="https://sindominio.net/">portada a recorrer nuestros servicios</a>.<p> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
{{template "footer.html"}} |
@ -0,0 +1,18 @@
|
||||
{{template "header.html"}} |
||||
{{template "navbar.html" .}} |
||||
|
||||
<div class="container"> |
||||
<br /> |
||||
<h1 class="row justify-content-center">Invitacion a SinDominio</h1> |
||||
|
||||
<br /> |
||||
<div class="row justify-content-center"> |
||||
<div class="col-md-8"> |
||||
<p>Enviale a tu amiga la siguiente dirección para que se pueda crear su cuenta en sindominio: <br /> |
||||
{{.Data.InviteURL}}<b> |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
{{template "footer.html"}} |
Loading…
Reference in new issue