|
@@ -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
|
|
|
+}
|