commit e1c19e4ba3d77654c5f34ced7a1aa1518fdba0d3
parent 3c812bf1152b04bf227794a1afee42a49c0362ae
Author: Martin Schanzenbach <schanzen@gnunet.org>
Date: Sat, 25 Jan 2025 14:40:43 +0100
add deletion through revalidation; more i18n
Diffstat:
7 files changed, 386 insertions(+), 340 deletions(-)
diff --git a/locales/de-DE/taldir.yml b/locales/de-DE/taldir.yml
@@ -9,8 +9,14 @@ pleaseCompleteValidation: "Bitte vervollständige die Alias-Validierung wie in d
notYetLinked: "`%s` ist noch nicht mit einer Bezahlsystemadresse verlinkt. Du kannst eine IBAN oder Taler wallet Adresse verlinken:"
isLinked: "`%s` ist bereits mit einer Bezahlsystemadresse verlinkt"
paymentSystemAddress: "Bezahlsystemadresse"
+paymentSystemAddressExample: "z.B. payto://iban/DE07123412341234123412"
linkIt: "Verlinken"
+deleteIt: "Löschen"
aliasInvalid: "Alias '%s´ ist ungültig."
+confirmation: "Validierung abschliessen"
+confirm: "Bestätigen"
+confirmReg: "Bestätige, dass \"<i>%s</i>\" mit \"<i>%s</i>\" assoziiert werden soll."
+confirmDelete: "Bestätige, dass das Alias \"<i>%s</i>\" gelöscht werden soll."
lookup: "Suchen"
lookupEmail: "E-Mail-Adresse nachschlagen"
lookupEmailDescription: "Gib eine E-Mail-Adresse (z.B. `jdoe@example.com`) ein um die dazugehörige Bezahlsystemadresse nachzuschlagen. Wenn diese noch nicht verlinkt ist, und das Alias unter Dir gehört, hast du dann die Chance dieses mit einer Bazahlsystemadresse zu verlinken."
@@ -19,4 +25,6 @@ lookupPhoneNr: "Telefonnummer nachschlagen"
lookupPhoneDescription: "Gib eine Telefonnummer ohne das `+` und ohne Leerzeichen ein (z.B. `49123456789`) um die dazugehörige Bezahlsystemadresse nachzuschlagen. Wenn diese noch nicht verlinkt ist, und das Alias unter Dir gehört, hast du dann die Chance dieses mit einer Bazahlsystemadresse zu verlinken."
lookupResultFound: "Suchergebnis"
productDisclaimer: 'Dies ist eine Demo-Instanz des <b>Tal</b>er <b>Dir</b>ectory (TalDir) Dienstes. Der Dienst implementiert die <a href="https://docs.taler.net/core/api-taldir.html">Taler TalDir API-Spezifikation</a>. TalDir erlaubt es Dir dein Identitäts-Alias (wie z.B. Email-Adresse oder Telefonnummer) mit einer Bezahlsystemadresse (z.B. einer <a href="https://en.wikipedia.org/wiki/International_Bank_Account_Number">IBAN</a> oder <a href="https://www.rfc-editor.org/rfc/rfc8905">Taler Wallet-Adresse</a>) zu verlinken. <p><b>ACHTUNG:</b> Dieser Dienst ist eine Demo. Es ist kein fertiges Produkt. Die Entwicklung dieses Dienstes dauert an and wird von <a href="https://nlnet.nl/project/TALER-LookupService/">NLnet and NGI TALER</a> gefördert.</p>'
+taldirRegTopic: "Taldir Alias Validierung"
+taldirRegMessage: "Hi,\\n\\nWillkommen bei TalDir. Please folge diesem Link um dein Alias zu bestätigen und deine Anfrage abzuschliessen: %s\\n\\nViele Grüße,\\nTalDir"
diff --git a/locales/en-US/taldir.yml b/locales/en-US/taldir.yml
@@ -9,8 +9,14 @@ pleaseCompleteValidation: "Please complete your alias validation according to th
notYetLinked: "`%s` is not yet linked with any Payment System Address. You may use an IBAN or a Taler wallet address:"
isLinked: "`%s` is linked with a Payment System Address"
paymentSystemAddress: "Payment System Address"
+paymentSystemAddressExample: "i.e. payto://iban/DE07123412341234123412"
linkIt: "Link it"
+deleteIt: "Unlink it"
aliasInvalid: "Alias '%s´ is invalid."
+confirmation: "Confirm Validation"
+confirm: "Confirm"
+confirmReg: "Click to confirm that you want to associate \"<i>%s</i>\" with \"<i>%s</i>\"."
+confirmDelete: "Click to confirm that you want to delete the alias \"<i>%s</i>\"."
lookup: "Lookup"
lookupEmail: "Lookup Email address"
lookupEmailDescription: "Enter an email address (e.g. `jdoe@example.com`) to look up the associated Payment System Address. If it is still unlinked and under your control you will be given the option to link it with a Payment System Address after lookup."
@@ -19,3 +25,5 @@ lookupPhoneNr: "Lookup phone number"
lookupPhoneDescription: "Enter a phone number without the `+` and no spaces (e.g. `49123456789`) to look up the associated Payment System Address. If it is still unlinked and under your control you will be given the option to link it with a Payment System Address after lookup."
lookupResultFound: "Lookup result"
productDisclaimer: 'This is a demo instance of the <b>Tal</b>er <b>Dir</b>ectory (TalDir) service. It implements the <a href="https://docs.taler.net/core/api-taldir.html">Taler TalDir API specification</a>. TalDir allows you to link your identity aliases (such as email addresses or phone numbers) with a payment system address (e.g. an <a href="https://en.wikipedia.org/wiki/International_Bank_Account_Number">IBAN</a> or a <a href="https://www.rfc-editor.org/rfc/rfc8905">Taler wallet address</a>). <p><b>IMPORTANT:</b> This is a demo service. It is not production-ready. The development of this service is ongoing and is funded by <a href="https://nlnet.nl/project/TALER-LookupService/">NLnet and NGI TALER</a>.</p>'
+taldirRegTopic: "Taldir Alias Validation"
+taldirRegMessage: "Hi,\\n\\nwelcome to TalDir. Please follow this link validate your alias and complete your request: %s\\n\\nBest,\\nTalDir"
diff --git a/pkg/rest/taldir.go b/pkg/rest/taldir.go
@@ -37,6 +37,7 @@ import (
"time"
"errors"
"regexp"
+ "net/url"
gnunetutil "gnunet/util"
@@ -88,6 +89,9 @@ type Taldir struct {
// The address salt
Salt string
+ // The host base url
+ Host string
+
// Valid Payment System Address
ValidPMSRegex string
@@ -420,12 +424,25 @@ func (t *Taldir) validationRequest(w http.ResponseWriter, r *http.Request) {
entry.Duration = time.Duration(tmpDuration)
err = t.Db.First(&entry, "hs_address = ?", entry.HsAddress).Error
if err == nil {
- t.Db.Save(&entry)
+ if validation.TargetUri == "" {
+ log.Printf("Deleted entry for '%s´\n", entry.HsAddress);
+ err = t.Db.Delete(&entry).Error
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+ } else {
+ t.Db.Save(&entry)
+ }
} else {
- err = t.Db.Create(&entry).Error
- if err != nil {
- w.WriteHeader(http.StatusInternalServerError)
- return
+ if validation.TargetUri == "" {
+ log.Printf("Validated a deletion request but no entry found for '%s´\n", entry.HsAddress);
+ } else {
+ err = t.Db.Create(&entry).Error
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
}
}
w.WriteHeader(http.StatusNoContent)
@@ -474,15 +491,17 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) {
return
}
- err = t.isPMSValid(req.TargetUri)
- if nil != err {
- errDetail.Code = gana.GENERIC_JSON_INVALID
- errDetail.Hint = err.Error()
- w.Header().Set("Content-Type", "application/json")
- resp, _ := json.Marshal(errDetail)
- w.WriteHeader(400)
- w.Write(resp)
- return
+ if req.TargetUri != "" {
+ err = t.isPMSValid(req.TargetUri)
+ if nil != err {
+ errDetail.Code = gana.GENERIC_JSON_INVALID
+ errDetail.Hint = err.Error()
+ w.Header().Set("Content-Type", "application/json")
+ resp, _ := json.Marshal(errDetail)
+ w.WriteHeader(400)
+ w.Write(resp)
+ return
+ }
}
// Setup validation object. Retrieve object from DB if it already
@@ -594,10 +613,13 @@ func (t *Taldir) registerRequest(w http.ResponseWriter, r *http.Request) {
return
}
log.Printf("Found `%s` in path as `%s`", validator.Command, path)
- out, err := exec.Command(path, req.Address, validation.Challenge).Output()
- log.Printf("Executing `%s %s %s`", path, req.Address, validation.Challenge)
+ topic := t.I18n.GetLocale(r).GetMessage("taldirRegTopic")
+ link := t.Host + "/register/" + url.QueryEscape(validation.HAddress) + "/" + url.QueryEscape(validation.Challenge) + "?address=" + url.QueryEscape(req.Address)
+ message := t.I18n.GetLocale(r).GetMessage("taldirRegMessage", link)
+ out, err := exec.Command(path, req.Address, validation.Challenge, topic, message).Output()
+ log.Printf("Executing `%s %s %s %s %s`\n", path, req.Address, validation.Challenge, topic, message)
if err != nil {
- fmt.Printf("%s, %v", out, err)
+ fmt.Printf("%s, %v\n", out, err)
t.Db.Delete(&validation)
w.WriteHeader(500)
return
@@ -674,334 +696,342 @@ func (t *Taldir) validationPage(w http.ResponseWriter, r *http.Request) {
"version": t.Version,
"QRCode": template.URL("data:image/png;base64," + encodedPng),
"WalletLink": template.URL(walletLink),
- "productDisclaimer": template.HTML(t.I18n.GetLocale(r).GetMessage("productDisclaimer")),
- }
- t.ValidationTpl.Execute(w, fullData)
- } else {
- expectedSolution := util.GenerateSolution(validation.TargetUri, validation.Challenge)
- fullData := map[string]interface{}{
- "version": t.Version,
- "error": r.URL.Query().Get("error"),
- "target_uri": template.URL(validation.TargetUri),
- "address": template.URL(address),
- "haddress": template.URL(validation.HAddress),
- "solution": template.URL(expectedSolution),
- "productDisclaimer": template.HTML(t.I18n.GetLocale(r).GetMessage("productDisclaimer")),
- "tr": t.I18n.GetLocale(r).GetMessage,
- }
- t.ValidationTpl.Execute(w, fullData)
- }
- return
+ "productDisclaimer": template.HTML(t.I18n.GetLocale(r).GetMessage("productDisclaimer")),
+ }
+ t.ValidationTpl.Execute(w, fullData)
+ } else {
+ expectedSolution := util.GenerateSolution(validation.TargetUri, validation.Challenge)
+ confirmDeletionOrRegistration := ""
+ if validation.TargetUri == "" {
+ confirmDeletionOrRegistration = t.I18n.GetLocale(r).GetMessage("confirmDelete", address)
+ } else {
+ confirmDeletionOrRegistration = t.I18n.GetLocale(r).GetMessage("confirmReg", address, validation.TargetUri)
+ }
+ fullData := map[string]interface{}{
+ "version": t.Version,
+ "error": r.URL.Query().Get("error"),
+ "target_uri": template.URL(validation.TargetUri),
+ "address": template.URL(address),
+ "haddress": template.URL(validation.HAddress),
+ "solution": template.URL(expectedSolution),
+ "confirmDeletionOrRegistration": template.HTML(confirmDeletionOrRegistration),
+ "productDisclaimer": template.HTML(t.I18n.GetLocale(r).GetMessage("productDisclaimer")),
+ "tr": t.I18n.GetLocale(r).GetMessage,
+ }
+ t.ValidationTpl.Execute(w, fullData)
+ }
+ return
}
// ClearDatabase nukes the database (for tests)
func (t *Taldir) ClearDatabase() {
- t.Db.Where("1 = 1").Delete(&entry{})
- t.Db.Where("1 = 1").Delete(&validation{})
+ t.Db.Where("1 = 1").Delete(&entry{})
+ t.Db.Where("1 = 1").Delete(&validation{})
}
func (t *Taldir) termsResponse(w http.ResponseWriter, r *http.Request) {
- tos.ServiceTermsResponse(t.Cfg.Section("taldir"), w, r)
+ tos.ServiceTermsResponse(t.Cfg.Section("taldir"), w, r)
}
func (t *Taldir) privacyResponse(w http.ResponseWriter, r *http.Request) {
- tos.PrivacyPolicyResponse(t.Cfg.Section("taldir"), w, r)
+ tos.PrivacyPolicyResponse(t.Cfg.Section("taldir"), w, r)
}
func (t *Taldir) landingPage(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- translateFunc := t.I18n.GetLocale(r).GetMessage
- fullData := map[string]interface{}{
- "validators": t.Validators,
- "version": t.Version,
- "lookupOrRegisterCardTitle": template.HTML(translateFunc("lookupOrRegister")),
- "selectAliasToLookupOrLinkCardText": template.HTML(translateFunc("selectAliasToLookupOrLink")),
- "productDisclaimer": template.HTML(translateFunc("productDisclaimer")),
- "error": translateFunc(r.URL.Query().Get("error")),
- "tr": translateFunc,
- }
- err := t.LandingPageTpl.Execute(w, fullData)
- if err != nil {
- fmt.Println(err)
- }
- return
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ translateFunc := t.I18n.GetLocale(r).GetMessage
+ fullData := map[string]interface{}{
+ "validators": t.Validators,
+ "version": t.Version,
+ "lookupOrRegisterCardTitle": template.HTML(translateFunc("lookupOrRegister")),
+ "selectAliasToLookupOrLinkCardText": template.HTML(translateFunc("selectAliasToLookupOrLink")),
+ "productDisclaimer": template.HTML(translateFunc("productDisclaimer")),
+ "error": translateFunc(r.URL.Query().Get("error")),
+ "tr": translateFunc,
+ }
+ err := t.LandingPageTpl.Execute(w, fullData)
+ if err != nil {
+ fmt.Println(err)
+ }
+ return
}
func (t *Taldir) imprintPage(w http.ResponseWriter, r *http.Request) {
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
- translateFunc := t.I18n.GetLocale(r).GetMessage
- fullData := map[string]interface{}{
- "validators": t.Validators,
- "version": t.Version,
- "productDisclaimer": template.HTML(translateFunc("productDisclaimer")),
- "error": translateFunc(r.URL.Query().Get("error")),
- "tr": translateFunc,
- }
- err := t.ImprintTpl.Execute(w, fullData)
- if err != nil {
- fmt.Println(err)
- }
- return
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ translateFunc := t.I18n.GetLocale(r).GetMessage
+ fullData := map[string]interface{}{
+ "validators": t.Validators,
+ "version": t.Version,
+ "productDisclaimer": template.HTML(translateFunc("productDisclaimer")),
+ "error": translateFunc(r.URL.Query().Get("error")),
+ "tr": translateFunc,
+ }
+ err := t.ImprintTpl.Execute(w, fullData)
+ if err != nil {
+ fmt.Println(err)
+ }
+ return
}
func (t *Taldir) methodLookupResultPage(w http.ResponseWriter, r *http.Request) {
- var entry entry
- vars := mux.Vars(r)
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
-
- // Check if this validation method is supported or not.
- val, ok := t.Validators[vars["method"]]
- if !ok {
- w.WriteHeader(404)
- return
- }
-
- // Check if alias is valid
- alias := r.URL.Query().Get("address")
- err := val.isAliasValid(alias)
- emsg := ""
- found := false
- if nil != err {
- log.Printf("Not a valid alias\n")
- emsg = t.I18n.GetLocale(r).GetMessage("aliasInvalid", alias)
- http.Redirect(w, r, fmt.Sprintf("/landing/" + val.Name + "?error=%s", emsg), http.StatusSeeOther)
- return
- } else {
- hAddressBin := sha512.Sum512([]byte(r.URL.Query().Get("address")))
- hAddress := gnunetutil.EncodeBinaryToString(hAddressBin[:])
- hsAddress := saltHAddress(hAddress, t.Salt)
- err = t.Db.First(&entry, "hs_address = ?", hsAddress).Error
- if err != nil {
- log.Printf("`%s` not found.\n", hAddress)
- } else {
- found = true
- }
- }
- fullData := map[string]interface{}{
- "version": t.Version,
- "available": !found,
- "method": val.Name,
- "address": r.URL.Query().Get("address"),
- "result": entry.TargetUri,
- "error": emsg,
- "productDisclaimer": template.HTML(t.I18n.GetLocale(r).GetMessage("productDisclaimer")),
- "tr": t.I18n.GetLocale(r).GetMessage,
- }
- err = t.LookupResultPageTpl.Execute(w, fullData)
- if err != nil {
- fmt.Println(err)
- }
- return
+ var entry entry
+ vars := mux.Vars(r)
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+
+ // Check if this validation method is supported or not.
+ val, ok := t.Validators[vars["method"]]
+ if !ok {
+ w.WriteHeader(404)
+ return
+ }
+
+ // Check if alias is valid
+ alias := r.URL.Query().Get("address")
+ err := val.isAliasValid(alias)
+ emsg := ""
+ found := false
+ if nil != err {
+ log.Printf("Not a valid alias\n")
+ emsg = t.I18n.GetLocale(r).GetMessage("aliasInvalid", alias)
+ http.Redirect(w, r, fmt.Sprintf("/landing/" + val.Name + "?error=%s", emsg), http.StatusSeeOther)
+ return
+ } else {
+ hAddressBin := sha512.Sum512([]byte(r.URL.Query().Get("address")))
+ hAddress := gnunetutil.EncodeBinaryToString(hAddressBin[:])
+ hsAddress := saltHAddress(hAddress, t.Salt)
+ err = t.Db.First(&entry, "hs_address = ?", hsAddress).Error
+ if err != nil {
+ log.Printf("`%s` not found.\n", hAddress)
+ } else {
+ found = true
+ }
+ }
+ fullData := map[string]interface{}{
+ "version": t.Version,
+ "available": !found,
+ "method": val.Name,
+ "address": r.URL.Query().Get("address"),
+ "result": entry.TargetUri,
+ "error": emsg,
+ "productDisclaimer": template.HTML(t.I18n.GetLocale(r).GetMessage("productDisclaimer")),
+ "tr": t.I18n.GetLocale(r).GetMessage,
+ }
+ err = t.LookupResultPageTpl.Execute(w, fullData)
+ if err != nil {
+ fmt.Println(err)
+ }
+ return
}
func (t *Taldir) methodLandingPage(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
- w.Header().Set("Content-Type", "text/html; charset=utf-8")
-
- // Check if this validation method is supported or not.
- val, ok := t.Validators[vars["method"]]
- if !ok {
- w.WriteHeader(404)
- return
- }
- fullData := map[string]interface{}{
- "version": t.Version,
- "error": r.URL.Query().Get("error"),
- "productDisclaimer": template.HTML(t.I18n.GetLocale(r).GetMessage("productDisclaimer")),
- "tr": t.I18n.GetLocale(r).GetMessage,
- }
- err := val.LandingPageTpl.Execute(w, fullData)
- if err != nil {
- fmt.Println(err)
- }
- return
+ vars := mux.Vars(r)
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+
+ // Check if this validation method is supported or not.
+ val, ok := t.Validators[vars["method"]]
+ if !ok {
+ w.WriteHeader(404)
+ return
+ }
+ fullData := map[string]interface{}{
+ "version": t.Version,
+ "error": r.URL.Query().Get("error"),
+ "productDisclaimer": template.HTML(t.I18n.GetLocale(r).GetMessage("productDisclaimer")),
+ "tr": t.I18n.GetLocale(r).GetMessage,
+ }
+ err := val.LandingPageTpl.Execute(w, fullData)
+ if err != nil {
+ fmt.Println(err)
+ }
+ return
}
func (t *Taldir) setupHandlers() {
- t.Router = mux.NewRouter().StrictSlash(true)
+ t.Router = mux.NewRouter().StrictSlash(true)
- /* ToS API */
- t.Router.HandleFunc("/terms", t.termsResponse).Methods("GET")
- t.Router.HandleFunc("/privacy", t.privacyResponse).Methods("GET")
- t.Router.HandleFunc("/imprint", t.imprintPage).Methods("GET")
+ /* ToS API */
+ t.Router.HandleFunc("/terms", t.termsResponse).Methods("GET")
+ t.Router.HandleFunc("/privacy", t.privacyResponse).Methods("GET")
+ t.Router.HandleFunc("/imprint", t.imprintPage).Methods("GET")
- /* Config API */
- t.Router.HandleFunc("/config", t.configResponse).Methods("GET")
+ /* Config API */
+ t.Router.HandleFunc("/config", t.configResponse).Methods("GET")
- /* Assets HTML */
- t.Router.PathPrefix("/css").Handler(http.StripPrefix("/css", http.FileServer(http.Dir("./static/css"))))
- t.Router.PathPrefix("/fontawesome").Handler(http.StripPrefix("/fontawesome", http.FileServer(http.Dir("./static/fontawesome"))))
+ /* Assets HTML */
+ t.Router.PathPrefix("/css").Handler(http.StripPrefix("/css", http.FileServer(http.Dir("./static/css"))))
+ t.Router.PathPrefix("/fontawesome").Handler(http.StripPrefix("/fontawesome", http.FileServer(http.Dir("./static/fontawesome"))))
- /* Registration API */
- t.Router.HandleFunc("/", t.landingPage).Methods("GET")
- t.Router.HandleFunc("/{h_address}", t.getSingleEntry).Methods("GET")
- t.Router.HandleFunc("/lookup/{method}", t.methodLookupResultPage).Methods("GET")
- t.Router.HandleFunc("/landing/{method}", t.methodLandingPage).Methods("GET")
- t.Router.HandleFunc("/register/{method}", t.registerRequest).Methods("POST")
- t.Router.HandleFunc("/register/{h_address}/{challenge}", t.validationPage).Methods("GET")
- t.Router.HandleFunc("/{h_address}", t.validationRequest).Methods("POST")
+ /* Registration API */
+ t.Router.HandleFunc("/", t.landingPage).Methods("GET")
+ t.Router.HandleFunc("/{h_address}", t.getSingleEntry).Methods("GET")
+ t.Router.HandleFunc("/lookup/{method}", t.methodLookupResultPage).Methods("GET")
+ t.Router.HandleFunc("/landing/{method}", t.methodLandingPage).Methods("GET")
+ t.Router.HandleFunc("/register/{method}", t.registerRequest).Methods("POST")
+ t.Router.HandleFunc("/register/{h_address}/{challenge}", t.validationPage).Methods("GET")
+ t.Router.HandleFunc("/{h_address}", t.validationRequest).Methods("POST")
}
var pluralizeClient = pluralize.NewClient()
func getFuncs(current *i18n.Locale) template.FuncMap {
- return template.FuncMap{
- "plural": func(word string, count int) string {
- return pluralizeClient.Pluralize(word, count, true)
- },
- }
+ return template.FuncMap{
+ "plural": func(word string, count int) string {
+ return pluralizeClient.Pluralize(word, count, true)
+ },
+ }
}
// Initialize the Taldir instance with cfgfile
func (t *Taldir) Initialize(cfgfile string, version string) {
- _cfg, err := ini.LooseLoad(cfgfile)
- if err != nil {
- log.Fatalf("Failed to read config: %v", err)
- os.Exit(1)
- }
- t.Cfg = _cfg
- t.I18n, err = i18n.New(i18n.Glob("./locales/*/*", i18n.LoaderConfig{
- // Set custom functions per locale!
- Funcs: getFuncs,
- }), "en-US", "de-DE")
- if err != nil {
- panic(err)
- }
- if t.Cfg.Section("taldir").Key("production").MustBool(false) {
- fmt.Println("Production mode enabled")
- }
-
- navTplFile := t.Cfg.Section("taldir").Key("navigation").MustString("web/templates/nav.html")
- footerTplFile := t.Cfg.Section("taldir").Key("footer").MustString("web/templates/footer.html")
- t.Version = version
- t.BaseUrl = t.Cfg.Section("taldir").Key("base_url").MustString("http://localhost:11000")
- t.Validators = make(map[string]Validator)
- for _, sec := range t.Cfg.Sections() {
- if !strings.HasPrefix(sec.Name(), "taldir-validator-") {
- continue
- }
- if !sec.HasKey("type") {
- log.Printf("`type` key in section `[%s]` not found, disabling validator.\n", sec.Name())
- continue
- }
- vname := strings.TrimPrefix(sec.Name(), "taldir-validator-")
- vlandingPageTplFile := sec.Key("registration_page").MustString("web/templates/landing_" + vname + ".html")
- vlandingPageTpl, err := template.ParseFiles(vlandingPageTplFile, navTplFile, footerTplFile)
- if err != nil {
- log.Printf("`%s` template not found, disabling validator `%s`.\n", vlandingPageTplFile, vname)
- continue
- }
- t.Validators[vname] = Validator{
- Name: vname,
- LandingPageTpl: vlandingPageTpl,
- ChallengeFee: sec.Key("challenge_fee").MustString("KUDOS:0"),
- PaymentRequired: sec.Key("enabled").MustBool(false),
- Command: sec.Key("command").MustString(""),
- Type: ValidatorType(sec.Key("type").MustString("")),
- ValidAliasRegex: sec.Key("valid_alias_regex").MustString(""),
- }
- }
- t.ChallengeBytes = t.Cfg.Section("taldir").Key("challenge_bytes").MustInt(16)
- t.ValidationInitiationMax = t.Cfg.Section("taldir").Key("validation_initiation_max").MustInt64(3)
- t.SolutionAttemptsMax = t.Cfg.Section("taldir").Key("solution_attempt_max").MustInt(3)
-
- validationTTLStr := t.Cfg.Section("taldir").Key("validation_timeframe").MustString("5m")
- t.ValidPMSRegex = t.Cfg.Section("taldir").Key("valid_payment_system_address_regex").MustString("[A-Z]+")
- t.ValidationTimeframe, err = time.ParseDuration(validationTTLStr)
- if err != nil {
- log.Fatal(err)
- os.Exit(1)
- }
-
- retryTimeframeStr := t.Cfg.Section("taldir").Key("solution_attempt_timeframe").MustString("1h")
- t.SolutionTimeframe, err = time.ParseDuration(retryTimeframeStr)
- if err != nil {
- log.Fatal(err)
- os.Exit(1)
- }
- t.MonthlyFee = t.Cfg.Section("taldir").Key("monthly_fee").MustString("KUDOS:0")
-
- psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
- t.Cfg.Section("taldir-pq").Key("host").MustString("localhost"),
- t.Cfg.Section("taldir-pq").Key("port").MustInt64(5432),
- t.Cfg.Section("taldir-pq").Key("user").MustString("taldir"),
- t.Cfg.Section("taldir-pq").Key("password").MustString("secret"),
- t.Cfg.Section("taldir-pq").Key("db_name").MustString("taldir"))
- _db, err := gorm.Open(postgres.Open(psqlconn), &gorm.Config{
- Logger: logger.Default.LogMode(logger.Silent),
- })
- if err != nil {
- panic(err)
- }
- t.Db = _db
- if err := t.Db.AutoMigrate(&entry{}); err != nil {
- panic(err)
- }
- if err := t.Db.AutoMigrate(&validation{}); err != nil {
- panic(err)
- }
- if t.Cfg.Section("taldir").Key("purge_mappings_on_startup_dangerous").MustBool(false) {
- log.Println("DANGER Purging mappings!")
- tx := t.Db.Where("1 = 1").Delete(&entry{})
- log.Printf("Deleted %d entries.\n", tx.RowsAffected)
- }
- // Clean up validations
- validationExpStr := t.Cfg.Section("taldir").Key("validation_expiration").MustString("24h")
- validationExp, err := time.ParseDuration(validationExpStr)
- if err != nil {
- log.Fatal(err)
- os.Exit(1)
- }
- go func() {
- for true {
- tx := t.Db.Where("created_at < ?", time.Now().Add(-validationExp)).Delete(&validation{})
- log.Printf("Cleaned up %d stale validations.\n", tx.RowsAffected)
- time.Sleep(validationExp)
- }
- }()
- imprintTplFile := t.Cfg.Section("taldir").Key("imprint_page").MustString("web/templates/imprint.html")
- t.ImprintTpl, err = template.ParseFiles(imprintTplFile, navTplFile, footerTplFile)
- if err != nil {
- log.Fatal(err)
- os.Exit(1)
- }
- validationLandingTplFile := t.Cfg.Section("taldir").Key("validation_landing").MustString("web/templates/validation_landing.html")
- t.ValidationTpl, err = template.ParseFiles(validationLandingTplFile, navTplFile, footerTplFile)
- if err != nil {
- log.Fatal(err)
- os.Exit(1)
- }
- landingTplFile := t.Cfg.Section("taldir").Key("landing_page").MustString("web/templates/landing.html")
- t.LandingPageTpl, err = template.ParseFiles(landingTplFile, navTplFile, footerTplFile)
- if err != nil {
- log.Fatal(err)
- os.Exit(1)
- }
- lookupResultTplFile := t.Cfg.Section("taldir").Key("lookup_result_page").MustString("web/templates/lookup_result.html")
- t.LookupResultPageTpl, err = template.ParseFiles(lookupResultTplFile, navTplFile, footerTplFile)
- if err != nil {
- log.Fatal(err)
- os.Exit(1)
- }
- t.Salt = os.Getenv("TALDIR_SALT")
- if "" == t.Salt {
- t.Salt = t.Cfg.Section("taldir").Key("salt").MustString("ChangeMe")
- }
- //merchURL := t.Cfg.Section("taldir").Key("merchant_base_url").MustString("http://merchant.taldir/")
- //merchToken := t.Cfg.Section("taldir").Key("merchant_token").MustString("secretAccessToken")
- //t.Merchant = merchant.NewMerchant(merchURL, merchToken)
- //merchConfig, err := t.Merchant.GetConfig()
- //if nil != err {
- // log.Fatal("Failed to get merchant config")
- // os.Exit(1)
- //}
- //registrationCost, _ := talerutil.ParseAmount(t.MonthlyFee)
- //currencySpec, currencySupported := merchConfig.Currencies[registrationCost.Currency]
- //for !currencySupported {
- // log.Fatalf("Currency `%s' not supported by merchant!\n", registrationCost.Currency)
- // os.Exit(1)
- //}
- //t.CurrencySpec = currencySpec
- t.setupHandlers()
+ _cfg, err := ini.LooseLoad(cfgfile)
+ if err != nil {
+ log.Fatalf("Failed to read config: %v", err)
+ os.Exit(1)
+ }
+ t.Cfg = _cfg
+ t.I18n, err = i18n.New(i18n.Glob("./locales/*/*", i18n.LoaderConfig{
+ // Set custom functions per locale!
+ Funcs: getFuncs,
+ }), "en-US", "de-DE")
+ if err != nil {
+ panic(err)
+ }
+ if t.Cfg.Section("taldir").Key("production").MustBool(false) {
+ fmt.Println("Production mode enabled")
+ }
+
+ navTplFile := t.Cfg.Section("taldir").Key("navigation").MustString("web/templates/nav.html")
+ footerTplFile := t.Cfg.Section("taldir").Key("footer").MustString("web/templates/footer.html")
+ t.Version = version
+ t.BaseUrl = t.Cfg.Section("taldir").Key("base_url").MustString("http://localhost:11000")
+ t.Validators = make(map[string]Validator)
+ for _, sec := range t.Cfg.Sections() {
+ if !strings.HasPrefix(sec.Name(), "taldir-validator-") {
+ continue
+ }
+ if !sec.HasKey("type") {
+ log.Printf("`type` key in section `[%s]` not found, disabling validator.\n", sec.Name())
+ continue
+ }
+ vname := strings.TrimPrefix(sec.Name(), "taldir-validator-")
+ vlandingPageTplFile := sec.Key("registration_page").MustString("web/templates/landing_" + vname + ".html")
+ vlandingPageTpl, err := template.ParseFiles(vlandingPageTplFile, navTplFile, footerTplFile)
+ if err != nil {
+ log.Printf("`%s` template not found, disabling validator `%s`.\n", vlandingPageTplFile, vname)
+ continue
+ }
+ t.Validators[vname] = Validator{
+ Name: vname,
+ LandingPageTpl: vlandingPageTpl,
+ ChallengeFee: sec.Key("challenge_fee").MustString("KUDOS:0"),
+ PaymentRequired: sec.Key("enabled").MustBool(false),
+ Command: sec.Key("command").MustString(""),
+ Type: ValidatorType(sec.Key("type").MustString("")),
+ ValidAliasRegex: sec.Key("valid_alias_regex").MustString(""),
+ }
+ }
+ t.ChallengeBytes = t.Cfg.Section("taldir").Key("challenge_bytes").MustInt(16)
+ t.ValidationInitiationMax = t.Cfg.Section("taldir").Key("validation_initiation_max").MustInt64(3)
+ t.SolutionAttemptsMax = t.Cfg.Section("taldir").Key("solution_attempt_max").MustInt(3)
+
+ validationTTLStr := t.Cfg.Section("taldir").Key("validation_timeframe").MustString("5m")
+ t.ValidPMSRegex = t.Cfg.Section("taldir").Key("valid_payment_system_address_regex").MustString("[A-Z]+")
+ t.ValidationTimeframe, err = time.ParseDuration(validationTTLStr)
+ if err != nil {
+ log.Fatal(err)
+ os.Exit(1)
+ }
+
+ retryTimeframeStr := t.Cfg.Section("taldir").Key("solution_attempt_timeframe").MustString("1h")
+ t.SolutionTimeframe, err = time.ParseDuration(retryTimeframeStr)
+ if err != nil {
+ log.Fatal(err)
+ os.Exit(1)
+ }
+ t.MonthlyFee = t.Cfg.Section("taldir").Key("monthly_fee").MustString("KUDOS:0")
+
+ psqlconn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable",
+ t.Cfg.Section("taldir-pq").Key("host").MustString("localhost"),
+ t.Cfg.Section("taldir-pq").Key("port").MustInt64(5432),
+ t.Cfg.Section("taldir-pq").Key("user").MustString("taldir"),
+ t.Cfg.Section("taldir-pq").Key("password").MustString("secret"),
+ t.Cfg.Section("taldir-pq").Key("db_name").MustString("taldir"))
+ _db, err := gorm.Open(postgres.Open(psqlconn), &gorm.Config{
+ Logger: logger.Default.LogMode(logger.Silent),
+ })
+ if err != nil {
+ panic(err)
+ }
+ t.Db = _db
+ if err := t.Db.AutoMigrate(&entry{}); err != nil {
+ panic(err)
+ }
+ if err := t.Db.AutoMigrate(&validation{}); err != nil {
+ panic(err)
+ }
+ if t.Cfg.Section("taldir").Key("purge_mappings_on_startup_dangerous").MustBool(false) {
+ log.Println("DANGER Purging mappings!")
+ tx := t.Db.Where("1 = 1").Delete(&entry{})
+ log.Printf("Deleted %d entries.\n", tx.RowsAffected)
+ }
+ // Clean up validations
+ validationExpStr := t.Cfg.Section("taldir").Key("validation_expiration").MustString("24h")
+ validationExp, err := time.ParseDuration(validationExpStr)
+ if err != nil {
+ log.Fatal(err)
+ os.Exit(1)
+ }
+ go func() {
+ for true {
+ tx := t.Db.Where("created_at < ?", time.Now().Add(-validationExp)).Delete(&validation{})
+ log.Printf("Cleaned up %d stale validations.\n", tx.RowsAffected)
+ time.Sleep(validationExp)
+ }
+ }()
+ imprintTplFile := t.Cfg.Section("taldir").Key("imprint_page").MustString("web/templates/imprint.html")
+ t.ImprintTpl, err = template.ParseFiles(imprintTplFile, navTplFile, footerTplFile)
+ if err != nil {
+ log.Fatal(err)
+ os.Exit(1)
+ }
+ validationLandingTplFile := t.Cfg.Section("taldir").Key("validation_landing").MustString("web/templates/validation_landing.html")
+ t.ValidationTpl, err = template.ParseFiles(validationLandingTplFile, navTplFile, footerTplFile)
+ if err != nil {
+ log.Fatal(err)
+ os.Exit(1)
+ }
+ landingTplFile := t.Cfg.Section("taldir").Key("landing_page").MustString("web/templates/landing.html")
+ t.LandingPageTpl, err = template.ParseFiles(landingTplFile, navTplFile, footerTplFile)
+ if err != nil {
+ log.Fatal(err)
+ os.Exit(1)
+ }
+ lookupResultTplFile := t.Cfg.Section("taldir").Key("lookup_result_page").MustString("web/templates/lookup_result.html")
+ t.LookupResultPageTpl, err = template.ParseFiles(lookupResultTplFile, navTplFile, footerTplFile)
+ if err != nil {
+ log.Fatal(err)
+ os.Exit(1)
+ }
+ t.Salt = os.Getenv("TALDIR_SALT")
+ if "" == t.Salt {
+ t.Salt = t.Cfg.Section("taldir").Key("salt").MustString("ChangeMe")
+ }
+ t.Host = t.Cfg.Section("taldir").Key("base_url").MustString("http://localhost")
+ //merchURL := t.Cfg.Section("taldir").Key("merchant_base_url").MustString("http://merchant.taldir/")
+ //merchToken := t.Cfg.Section("taldir").Key("merchant_token").MustString("secretAccessToken")
+ //t.Merchant = merchant.NewMerchant(merchURL, merchToken)
+ //merchConfig, err := t.Merchant.GetConfig()
+ //if nil != err {
+ // log.Fatal("Failed to get merchant config")
+ // os.Exit(1)
+ //}
+ //registrationCost, _ := talerutil.ParseAmount(t.MonthlyFee)
+ //currencySpec, currencySupported := merchConfig.Currencies[registrationCost.Currency]
+ //for !currencySupported {
+ // log.Fatalf("Currency `%s' not supported by merchant!\n", registrationCost.Currency)
+ // os.Exit(1)
+ //}
+ //t.CurrencySpec = currencySpec
+ t.setupHandlers()
}
diff --git a/scripts/validators/taldir-validate-clidummy b/scripts/validators/taldir-validate-clidummy
@@ -3,13 +3,11 @@
USER_MAIL=$1
CODE=$2
LINK=$(taldir-cli -l -a $1 -c $2)
-echo "Taldir Activation" > clidummy.$CODE
-echo "To: "$USER_MAIL >> clidummy.$CODE
-echo "Hi,
+TOPIC=$(echo $3)
+MESSAGE=$4
-welcome to TalDir. You are almost there.
-Follow this link to complete your Taldir registration: $LINK
+echo -e $MESSAGE
-Best,
-TalDir
-">> clidummy.$CODE
+echo $TOPIC > clidummy.$CODE
+echo "To: "$USER_MAIL >> clidummy.$CODE
+echo -e $MESSAGE >> clidummy.$CODE
diff --git a/scripts/validators/taldir-validate-mail b/scripts/validators/taldir-validate-mail
@@ -3,7 +3,9 @@
USER_MAIL=$1
CODE=$2
LINK=$(taldir-cli -l -a $1 -c $2)
-echo $MESSAGE | mail -s "Taldir Activation" $USER_MAIL << EOF
+TOPIC=$3
+MESSAGE=$(echo -e $4) # Interpret backslash (newline etc)
+echo $MESSAGE | mail -s "Taldir Registration Confirmation" $USER_MAIL << EOF
Hi,
welcome to TalDir. You are almost there.
diff --git a/web/templates/lookup_result.html b/web/templates/lookup_result.html
@@ -26,9 +26,9 @@
{{ call .tr "pleaseCompleteValidation" }}
</div>
</div>
- {{if .available}}
<div class="container pt-5">
<div class="card">
+ {{if .available}}
<div class="card-body">
<h4 class="card-title">{{ call .tr "available" }}</h4>
<hr>
@@ -38,35 +38,35 @@
<input id="methodInput" type="hidden" name="method" value="{{.method}}">
<input id="addrInput" type="hidden" name="address" value="{{.address}}">
<div class="row">
- <div class="col-lg-6 offset-lg-3 text-center">
+ <div class="col-lg-8 offset-lg-2 text-center">
<div class="input-group mb-3">
<span class="input-group-text" id="inputGroup-sizing-default">{{.address}}: </span>
- <input id="uriInput" name="target_uri" type="text" class="form-control" placeholder="{{ call .tr "paymentSystemAddress" }}" aria-label="Default" aria-describedby="inputGroup-sizing-default">
+ <input id="uriInput" name="target_uri" type="text" class="form-control" placeholder="{{ call .tr "paymentSystemAddressExample" }}" aria-label="Default" aria-describedby="inputGroup-sizing-default">
<input class="input-group-text btn btn-primary" type="submit" value="{{ call .tr "linkIt" }}">
</div>
</div>
</div>
</form>
{{else}}
- <div class="container pt-5">
- <div class="card">
- <div class="card-body" role="alert">
- <h4 class="card-title">{{ call .tr "lookupResultFound" }}</h4>
- <hr>
- <p class="card-text">{{ call .tr "isLinked" .address }}</p>
- </div>
- <div class="container pt-5">
- <div class="row">
- <div class="col-lg-6 offset-lg-3 text-center">
- <div class="input-group mb-3">
- <span class="input-group-text" id="inputGroup-sizing-default">{{.address}}: </span>
- <input disabled="disabled" name="target_uri" type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" value="{{.result}}">
- </div>
- </div>
+ <div class="card-body" role="alert">
+ <h4 class="card-title">{{ call .tr "lookupResultFound" }}</h4>
+ <hr>
+ <p class="card-text">{{ call .tr "isLinked" .address }}</p>
+ </div>
+ <form id="regform">
+ <input id="methodInput" type="hidden" name="method" value="{{.method}}">
+ <input id="addrInput" type="hidden" name="address" value="{{.address}}">
+ <input id="uriInput" type="hidden" name="target_uri" value="">
+ <div class="row">
+ <div class="col-lg-6 offset-lg-3 text-center">
+ <div class="input-group mb-3">
+ <span class="input-group-text" id="inputGroup-sizing-default">{{.address}}: </span>
+ <input disabled="disabled" type="text" class="form-control" aria-label="Default" aria-describedby="inputGroup-sizing-default" value="{{.result}}">
+ <input class="input-group-text btn btn-danger" type="submit" value="{{ call .tr "deleteIt" }}">
</div>
</div>
</div>
- </div>
+ </form>
{{end}}
</div>
</div>
diff --git a/web/templates/validation_landing.html b/web/templates/validation_landing.html
@@ -35,15 +35,15 @@
{{else}}
<div id="regcontainer" class="container pt-5">
<div class="alert alert-info" role="alert">
- <h4 class="alert-heading">Confirm registration</h4>
+ <h4 class="alert-heading">{{ call .tr "confirmation" }}</h4>
<hr>
- Click to confirm that you want to associate "<i>{{.address}}</i>" with "<i>{{.target_uri}}</i>".
- <div class="row">
+ {{ .confirmDeletionOrRegistration }}
+ <div class="row mt-2">
<div class="col-lg-6 offset-lg-3 text-center">
<form id="regform">
<input id="haddrInput" name="haddr" type="hidden" value="{{.haddress}}">
<input id="solutionInput" name="solution" type="hidden" value="{{.solution}}">
- <input class="btn btn-primary" type="submit" value="Register!">
+ <input class="btn btn-primary" type="submit" value="{{ call .tr "confirm" }}">
</form>
</div>
</div>