amuse.git

commit 4cb82971776c7a9f2589caf7abae33f758ba2a21

Author: Adam <git@apiote.tk>

sign up

 accounts/login.go | 1 
 accounts/signup.go | 93 ++++++++++++++++++++++++++++++++++++++++++++++++
 db/db.go | 20 ++++++++-
 libamuse/signup.go | 42 ++++++++++++++++++---
 router.go | 6 +-


diff --git a/accounts/login.go b/accounts/login.go
index 638679c6a1c33bd47bd7b94c7e719dfb3cb1fd2d..0d31568283e32610ad7b0383fbc314a5bd766036 100644
--- a/accounts/login.go
+++ b/accounts/login.go
@@ -19,7 +19,6 @@
 type AuthData struct {
 	username   string
 	password   string
-	passRepeat string
 	sfa        string
 	remember   bool
 }




diff --git a/accounts/signup.go b/accounts/signup.go
new file mode 100644
index 0000000000000000000000000000000000000000..98369f388be284660cab5a3d20a87459a6939596
--- /dev/null
+++ b/accounts/signup.go
@@ -0,0 +1,93 @@
+package accounts
+
+import (
+	"notabug.org/apiote/amuse/db"
+
+	"encoding/base64"
+	"errors"
+	"fmt"
+	"math/rand"
+	"strconv"
+	"strings"
+
+	"golang.org/x/crypto/argon2"
+	"notabug.org/apiote/gott"
+)
+
+func findNoUser(args ...interface{}) (interface{}, error) {
+	authData := args[0].(*AuthData)
+	authResult := args[1].(*AuthResult)
+	user, err := db.GetUser(authData.username)
+	authResult.user = user
+	if _, ok := err.(db.EmptyError); ok {
+		err = nil
+	} else if err == nil {
+		err = AuthError{
+			Err: errors.New("User exists"),
+		}
+	}
+	return gott.Tuple(args), err
+}
+
+func prepareSalt(args ...interface{}) (interface{}, error) {
+	argon := args[2].(*Argon)
+	salt := make([]byte, 16)
+	_, err := rand.Read(salt)
+	argon.salt = salt
+	return gott.Tuple(args), err
+}
+
+func hashPassword(args ...interface{}) interface{} {
+	authData := args[0].(*AuthData)
+	argon := args[2].(*Argon)
+	password := authData.password
+
+	hash := argon2.IDKey([]byte(password), argon.salt, 1, 64*1024, 4, 32)
+	b64Salt := base64.RawStdEncoding.EncodeToString(argon.salt)
+	b64Hash := base64.RawStdEncoding.EncodeToString(hash)
+	format := "$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s"
+	fullHash := fmt.Sprintf(format, argon2.Version, 64*1024, 1, 4, b64Salt, b64Hash)
+	authData.password = fullHash
+	return gott.Tuple(args)
+}
+
+func createRecoveryCodes(args ...interface{}) interface{} {
+	authData := args[0].(*AuthData)
+	sfaSecret := authData.sfa
+	if sfaSecret != "" {
+		result := args[1].(*AuthResult)
+		codes := []string{}
+		for i := 0; i < 12; i++ {
+			code := rand.Int63n(999999999999)
+			codeStr := strconv.FormatInt(code, 10)
+			codes = append(codes, codeStr)
+		}
+		result.recoveryCodesRaw = strings.Join(codes, ",")
+	}
+	return gott.Tuple(args)
+}
+
+func insertUser(args ...interface{}) (interface{}, error) {
+	authData := args[0].(*AuthData)
+	result := args[1].(*AuthResult)
+	sfaSecret := authData.sfa
+	err := db.InsertUser(authData.username, authData.password, sfaSecret, result.recoveryCodesRaw)
+	return gott.Tuple(args), err
+}
+
+func Signup(username, password, sfaSecret string) (string, error) {
+	r, err := gott.
+		NewResult(gott.Tuple{&AuthData{username: username, password: password,
+			sfa: sfaSecret}, &AuthResult{}, &Argon{}}).
+		Bind(findNoUser).
+		Bind(prepareSalt).
+		Map(hashPassword).
+		Map(createRecoveryCodes).
+		Bind(insertUser).
+		Finish()
+
+	if err != nil {
+		return "", err
+	}
+	return r.(gott.Tuple)[1].(*AuthResult).token, err
+}




diff --git a/db/db.go b/db/db.go
index b1c88b85f5c5f043839a597ed5464fc4bd0e45df..d1ea424da0ebcac6fdd59850d7f07178252357e7 100644
--- a/db/db.go
+++ b/db/db.go
@@ -59,7 +59,7 @@ 	_, err = db.Exec(`create table cache(uri text primary key, etag text, date date, response blob, last_hit date)`)
 	if err != nil && err.Error() != "table cache already exists" {
 		return err
 	}
-	_, err = db.Exec(`create table users(username text primary key, password text, sfa text, avatar blob, avatar_small blob, is_admin bool, recovery_codes text, timezone text)`)
+	_, err = db.Exec(`create table users(username text primary key, password text, sfa text, is_admin bool, recoveryCodes text, avatar blob, avatar_small blob, timezone text)`)
 	if err != nil && err.Error() != "table users already exists" {
 		return err
 	}
@@ -112,6 +112,22 @@ 		fmt.Fprintf(os.Stderr, "Scan err %v\n", err)
 		return err
 	}
 	fmt.Println(isAdmin)
+	return nil
+}
+
+func InsertUser(username, password, sfaSecret, recoveryCodes string) error {
+	db, err := sql.Open("sqlite3", utils.DataHome+"/amuse.db")
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "DB open err\n")
+		return err
+	}
+	defer db.Close()
+	_, err = db.Exec("insert into users values(?, ?, ?, 0, ?, '', '', 'UTC')", username, password, sfaSecret, recoveryCodes)
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "Inert err %v\n", err)
+		return err
+	}
+
 	return nil
 }
 
@@ -333,7 +349,6 @@ 		fmt.Fprintf(os.Stderr, "Insert err %v\n", err)
 		return 0, err
 	}
 
-
 	tx.Commit()
 
 	return int(insertedRowsNumber - deletedRowsNumber), nil
@@ -462,7 +477,6 @@ 	return isOnlist, nil
 }
 
 func SaveCacheItem(itemType datastructure.ItemType, itemId string, itemInfo datastructure.ItemInfo, refs int) error {
-	fmt.Println(refs)
 	if refs == 0 {
 		return nil
 	}




diff --git a/libamuse/signup.go b/libamuse/signup.go
index 232ebf8ee0df634e1fa8f78d92154842caebfe31..7530470cef7193767f8ad3fb5a54e512e11edb18 100644
--- a/libamuse/signup.go
+++ b/libamuse/signup.go
@@ -1,8 +1,10 @@
 package libamuse
 
 import (
-	"fmt"
-	
+	"notabug.org/apiote/amuse/accounts"
+
+	"errors"
+
 	"github.com/pquerna/otp"
 	"github.com/pquerna/otp/totp"
 	"notabug.org/apiote/gott"
@@ -10,8 +12,8 @@ )
 
 func createSecret(args ...interface{}) (interface{}, error) {
 	result := args[1].(*Result)
-	opts := totp.GenerateOpts{  // todo host
-		Issuer: "amuse.apiote.tk",
+	opts := totp.GenerateOpts{ // todo host
+		Issuer:      "amuse.apiote.tk",
 		AccountName: "nearly_headless_nick@amuse.apiote.tk",
 	}
 	key, err := totp.Generate(opts)
@@ -46,9 +48,35 @@ 		return r.(gott.Tuple)[1].(*Result).page, nil
 	}
 }
 
-func DoSignup(username, password, passwordConfirm string, sfaEnabled bool, sfaSecret, sfa string) error {
-	fmt.Println(username, password, passwordConfirm, sfaEnabled, sfaSecret, sfa)
-	return nil
+func DoSignup(username, password, passwordConfirm string, sfaEnabled bool, sfaSecret, sfa string) (string, error) {
+	if password != passwordConfirm {
+		return "", accounts.AuthError{
+			Err: errors.New("Passwords don’t match"),
+		}
+	}
+	if sfaEnabled {
+		if sfa == "" {
+			return "", accounts.AuthError{
+				Err: errors.New("Second factor authentication not confirmed"),
+			}
+		}
+		if !totp.Validate(sfa, sfaSecret) {
+			return "", accounts.AuthError{
+				Err: errors.New("Second factor code not correct"),
+			}
+		}
+	}
+	if username == "" || password == "" || sfaSecret == "" {
+		return "", accounts.AuthError{
+			Err: errors.New("Required info missing"),
+		}
+	}
+
+	if !sfaEnabled {
+		sfaSecret = ""
+	}
+
+	return accounts.Signup(username, password, sfaSecret)
 }
 
 func ShowSignedup(acceptLanguages, mimetype string) (string, error) {




diff --git a/router.go b/router.go
index 1876d086fdef738ee26884952f9c96c6a4d58b43..fceaed7aad9da52b91ccdee38db01550d03a3b11 100644
--- a/router.go
+++ b/router.go
@@ -245,7 +245,7 @@ 	sfaEnabled := r.PostForm.Get("sfaEnabled") == "true"
 	sfaSecret := r.PostForm.Get("sfaSecret")
 	sfa := r.PostForm.Get("sfa")
 
-	err := libamuse.DoSignup(username, password, passwordConfirm, sfaEnabled, sfaSecret, sfa)
+	recoveryCodes, err := libamuse.DoSignup(username, password, passwordConfirm, sfaEnabled, sfaSecret, sfa)
 	if err != nil {
 		fmt.Println(err)
 		if authErr, ok := err.(accounts.AuthError); ok {
@@ -262,7 +262,7 @@ 			render("", err, w, acceptLanguages, mimetype)
 		}
 	} else {
 		if mimetype == "text/html" {
-			w.Header().Add("Location", "/signedup")
+			w.Header().Add("Location", "/signedup?recoveryCodes="+recoveryCodes)
 			w.WriteHeader(303)
 		} else {
 			// todo send capnproto authed
@@ -463,7 +463,7 @@ func qr(w http.ResponseWriter, r *http.Request) {
 	query := r.URL.Query().Get("url")
 	acceptLanguages := r.Header.Get("Accept-Language")
 	mimetype := strings.Split(r.Header.Get("Accept"), ",")[0]
-	
+
 	keyUrl, err := url.QueryUnescape(query)
 	if err != nil {
 		render("", errors.New("400"), w, acceptLanguages, mimetype)