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)