commit f5e140b41c1697f4b013dda1f1c1f7a3caec17fa
parent 41a3a5ecc97b942c463a8b8539ffd6930d3fc9fe
Author: Martin Schanzenbach <mschanzenbach@posteo.de>
Date: Tue, 19 Apr 2022 14:34:20 +0200
Cleanup code. Hash identities
Diffstat:
3 files changed, 85 insertions(+), 46 deletions(-)
diff --git a/config.json b/config.json
@@ -2,5 +2,6 @@
"production": false,
"db_backend": "sqlite",
"validators": ["email","phone"],
- "email_sender": "taldir@taler.net"
+ "email_sender": "taldir@taler.net",
+ "host": "https://taldir.net/"
}
diff --git a/go.mod b/go.mod
@@ -6,6 +6,7 @@ require (
github.com/gorilla/mux v1.8.0 // indirect
github.com/jinzhu/now v1.1.5 // indirect
github.com/mattn/go-sqlite3 v1.14.12 // indirect
+ golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 // indirect
gorm.io/driver/sqlite v1.3.1 // indirect
gorm.io/gorm v1.23.4 // indirect
)
diff --git a/taldir.go b/taldir.go
@@ -12,41 +12,51 @@ import (
"encoding/base32"
"math/rand"
"net/smtp"
+ "golang.org/x/crypto/argon2"
+ "crypto/sha256"
)
-type Validator interface {
- TriggerValidation() string
-}
-
type Configuration struct {
Production bool
DbBackend string `json:"db_backend"`
Validators []string
EmailSender string `json:"email_sender"`
+ Salt string `json:"salt"`
+ Host string `json:"host"`
}
+// A mappind entry from the identity key hash to a wallet key
+// The identity key hash is argon2(sha256(identity)) where identity is
+// one of the identity key types supported (e.g. email)
type Entry struct {
gorm.Model
- IdentityKey string `json:"identity_key"`
- IdentityKeyType string `json:"identity_key_type"`
+ IdentityKeyHash string `json:"identity_key_hash"`
TalerWalletKey string `json:"taler_wallet_key"`
- Status string `json:"status"`
}
+// A validation is created when a registration for an entry is initiated.
+// The validation stores the identity key (sha256(identity)) the secret
+// validation reference. The validation reference is sent to the identity
+// depending on the out-of-band chennel defined through the identity key type.
type Validation struct {
gorm.Model
IdentityKey string `json:"identity_key"`
- IdentityKeyType string `json:"type"`
+ IdentityKeyType string `json:"identity_key_type"`
ValidationReference string `json:"reference"`
+ TalerWalletKey string `json:"taler_wallet_key"`
}
-var Entries []Entry
-
+// The main DB handle
var db *gorm.DB
+// Our configuration from the config.json
var config Configuration
+// Map of supported validators as defined in the configuration
+var validators map[string]bool
+
+// Send an email for email identities
func sendEmail(recipient string, ref Validation) {
from := config.EmailSender
@@ -57,9 +67,9 @@ func sendEmail(recipient string, ref Validation) {
smtpHost := "localhost"
smtpPort := "587"
- message := []byte("Please click here to validate your Taldir identity: " + ref.ValidationReference)
+ message := fmt.Sprintf("Please click here to validate your Taldir identity: %s%s", config.Host, ref.ValidationReference)
- err := smtp.SendMail(smtpHost+":"+smtpPort, nil, from, to, message)
+ err := smtp.SendMail(smtpHost+":"+smtpPort, nil, from, to, []byte(message))
if err != nil {
fmt.Println(err)
return
@@ -70,7 +80,8 @@ func sendEmail(recipient string, ref Validation) {
func returnSingleEntry(w http.ResponseWriter, r *http.Request){
vars := mux.Vars(r)
var entry Entry
- var err = db.First(&entry, "identity_key = ?", vars["identity_key"]).Error
+ identityKeyHash := hashIdentityKey(vars["identity_key"])
+ var err = db.First(&entry, "identity_key_hash = ?", identityKeyHash).Error
if err == nil {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(entry)
@@ -79,23 +90,41 @@ func returnSingleEntry(w http.ResponseWriter, r *http.Request){
w.WriteHeader(http.StatusNotFound)
}
+func hashIdentityKey(idkey string) string {
+ salt := make([]byte, len(config.Salt))
+ return base32.StdEncoding.EncodeToString(argon2.IDKey([]byte(idkey), salt, 1, 64*1024, 4, 32))
+}
+
func validateSingleEntry(w http.ResponseWriter, r *http.Request){
vars := mux.Vars(r)
var entry Entry
- var ref Validation
+ var validation Validation
//TODO actually validate
- var err = db.First(&ref, "validation_reference = ?", vars["reference"]).Error
+ var err = db.First(&validation, "validation_reference = ?", vars["reference"]).Error
if err != nil {
w.WriteHeader(http.StatusNotFound)
}
- err = db.First(&entry, "identity_key = ?", entry.IdentityKey).Error
+ err = db.First(&validation, "identity_key = ?", validation.IdentityKey).Error
if err != nil {
w.WriteHeader(http.StatusNotFound)
}
- err = db.Model(&entry).Update("status", "validated").Error
+ err = db.Delete(&validation).Error
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
}
+ entry.IdentityKeyHash = hashIdentityKey(validation.IdentityKey)
+ entry.TalerWalletKey = validation.TalerWalletKey
+ err = db.First(&entry, "identity_key_hash = ?", entry.IdentityKeyHash).Error
+ if err == nil {
+ w.WriteHeader(http.StatusConflict)
+ return
+ }
+ err = db.Create(&entry).Error
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ w.WriteHeader(http.StatusCreated)
}
func generateToken() string {
@@ -107,45 +136,54 @@ func generateToken() string {
return base32.StdEncoding.EncodeToString(randBytes)
}
-func addSingleEntry(w http.ResponseWriter, r *http.Request){
- var entry Entry
+func addPendingValidation(w http.ResponseWriter, r *http.Request){
+ vars := mux.Vars(r)
+ var validation Validation
if r.Body == nil {
http.Error(w, "No request body", 400)
return
}
- err := json.NewDecoder(r.Body).Decode(&entry)
+ err := json.NewDecoder(r.Body).Decode(&validation)
if err != nil {
http.Error(w, err.Error(), 400)
return
}
- ref := Validation{IdentityKey: entry.IdentityKey, ValidationReference: generateToken() }
- fmt.Println("Got ID key:", entry.IdentityKey)
- err = db.First(&entry, "identity_key = ?", entry.IdentityKey).Error
- if err == nil {
- w.WriteHeader(http.StatusConflict)
+ fmt.Println(validators)
+ fmt.Println(validation)
+ if !validators[validation.IdentityKeyType] {
+ http.Error(w, "Identity key type not supported.", 400)
return
}
- entry.Status = "unvalidated"
- err = db.Create(&entry).Error
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
+ // TODO make sure sha256(vars["identity"]) == validation.IdentityKey
+ h := sha256.New()
+ h.Write([]byte(vars["identity"]))
+ identityKey := base32.StdEncoding.EncodeToString(h.Sum(nil))
+ if (identityKey != validation.IdentityKey) {
+ fmt.Printf("Identity key hash %s does not match identity %s\n", identityKey, validation.IdentityKey)
+ w.WriteHeader(http.StatusBadRequest)
+ return
+ }
+ validation.ValidationReference = generateToken()
+ fmt.Println("Got ID key:", validation.IdentityKey)
+ err = db.First(&validation, "identity_key = ?", validation.IdentityKey).Error
+ if err == nil {
+ w.WriteHeader(http.StatusConflict)
return
}
- err = db.Create(&ref).Error
+ err = db.Create(&validation).Error
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
return
}
- fmt.Println("Validation reference created:", ref)
- sendEmail(ref.IdentityKey, ref)
- json.NewEncoder(w).Encode(entry)
+ fmt.Println("Pending validation created:", validation)
+ sendEmail(vars["identity"], validation)
}
func handleRequests() {
myRouter := mux.NewRouter().StrictSlash(true)
- myRouter.HandleFunc("/directory/{id}", returnSingleEntry).Methods("GET")
- myRouter.HandleFunc("/validation/{reference}", validateSingleEntry).Methods("POST")
- myRouter.HandleFunc("/directory/", addSingleEntry).Methods("POST")
+ myRouter.HandleFunc("/directory/{identity_key}", returnSingleEntry).Methods("GET")
+ myRouter.HandleFunc("/validation/{reference}", validateSingleEntry).Methods("GET")
+ myRouter.HandleFunc("/register/{identity}", addPendingValidation).Methods("POST")
log.Fatal(http.ListenAndServe(":10000", myRouter))
}
@@ -158,7 +196,14 @@ func main() {
if err != nil {
fmt.Println("error:", err)
}
- fmt.Println("Configuration:", config)
+ if config.Production {
+ fmt.Println("Production mode enabled")
+ }
+ validators = make(map[string]bool)
+ fmt.Println("Enabled validators:", config.Validators)
+ for _, a := range config.Validators {
+ validators[a] = true
+ }
_db, err := gorm.Open(sqlite.Open("./taldir.db"), &gorm.Config{})
if err != nil {
panic(err)
@@ -170,13 +215,5 @@ func main() {
if err := db.AutoMigrate(&Validation{}); err != nil {
panic(err)
}
- var entry Entry;
- db.Create(&Entry{IdentityKey: "jdoe@example.com", IdentityKeyType: "email", TalerWalletKey: "OIU123"})
- db.Create(&Entry{IdentityKey: "+12345678", IdentityKeyType: "phone", TalerWalletKey: "OIU123"})
handleRequests()
- db.First(&entry, "identity_key = ?", "jdoe@example.com")
- db.Delete(&entry)
- db.First(&entry, "identity_key = ?", "+12345678")
- db.Delete(&entry)
-
}