Author: Adam <git@apiote.tk>
keep data after error in signup
front/capnproto.go | 2 front/html.go | 11 ++++---- front/renderer.go | 4 +- go.mod | 2 go.sum | 4 +- libamuse/qr.go | 10 -------- libamuse/signup.go | 56 ++++++++++++++++++++++++++++++++++++++++---- router.go | 25 +++---------------- templates/signup.html | 6 ++--
diff --git a/front/capnproto.go b/front/capnproto.go index 47c382070fcbdc931c3ea86075a68b04991f401a..415ecbf730ac465a0c72e7c14d82a450eb7bc80e 100644 --- a/front/capnproto.go +++ b/front/capnproto.go @@ -56,7 +56,7 @@ // todo throw Wrong Accept return TODO("implement CapnprotoRenderer.RenderLogin").(string) } -func (CapnprotoRenderer) RenderSignup(languages []language.Tag, err error, otp *otp.Key) string { +func (CapnprotoRenderer) RenderSignup(languages []language.Tag, err error, otp *otp.Key, sfaEnabled bool, username, qr string) string { // todo throw Wrong Accept return TODO("implement CapnprotoRenderer.RenderSignup").(string) } diff --git a/front/html.go b/front/html.go index 5e3d6c17cad45814ae196a54e226f06d9552049e..4b7cf47308ddc67eacd01e9929435272c3bd04a5 100644 --- a/front/html.go +++ b/front/html.go @@ -11,7 +11,6 @@ "bytes" "golang.org/x/text/language" "html/template" - "net/url" "strings" "time" @@ -141,11 +140,13 @@ data.State.Error = authError return render(languages, data, "login") } -func (HtmlRenderer) RenderSignup(languages []language.Tag, authError error, key *otp.Key) string { +func (HtmlRenderer) RenderSignup(languages []language.Tag, authError error, key *otp.Key, sfaEnabled bool, username, qr string) string { secret := struct { - Secret string - Url string - }{key.Secret(), url.QueryEscape(key.URL())} + Secret string + SfaEnabled bool + Username string + Qr template.URL + }{key.Secret(), sfaEnabled, username, template.URL(qr)} data := RenderData{Data: secret} data.State.Error = authError return render(languages, data, "signup") diff --git a/front/renderer.go b/front/renderer.go index 9c97247f40f1ba5dddd13923cd4da31c7f9a90d3..92917be7e499e9a6b7326f3ad6063c76b8377491 100644 --- a/front/renderer.go +++ b/front/renderer.go @@ -2,9 +2,9 @@ package front import ( "notabug.org/apiote/amuse/accounts" + "notabug.org/apiote/amuse/datastructure" "notabug.org/apiote/amuse/tmdb" "notabug.org/apiote/amuse/wikidata" - "notabug.org/apiote/amuse/datastructure" "golang.org/x/text/language" @@ -30,7 +30,7 @@ RenderBookSerie(wikidata.BookSerie, []language.Tag) string RenderAbout([]language.Tag) string RenderErrorPage(int, []language.Tag) string RenderLogin([]language.Tag, error, string) string - RenderSignup([]language.Tag, error, *otp.Key) string + RenderSignup([]language.Tag, error, *otp.Key, bool, string, string) string RenderSignedup([]language.Tag, []string) string RenderWatchlist(datastructure.Watchlist, []language.Tag) string RenderTvQueue(datastructure.TvQueue, []language.Tag) string diff --git a/go.mod b/go.mod index bb857afb77b8b875145ee8d625f43792c7b9213d..b72aafa49b3fae1a42889bd08d3d0eded0001da6 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/BurntSushi/toml v0.3.1 github.com/alecthomas/chroma v0.7.2 // indirect github.com/bytesparadise/libasciidoc v0.4.0 + github.com/chai2010/webp v1.1.0 github.com/dlclark/regexp2 v1.2.0 // indirect github.com/go-python/gopy v0.3.1 github.com/knakk/digest v0.0.0-20160404164910-fd45becddc49 // indirect @@ -18,7 +19,6 @@ github.com/onsi/gomega v1.7.1 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pquerna/otp v1.2.0 github.com/sirupsen/logrus v1.5.0 // indirect - github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 github.com/stretchr/testify v1.4.0 // indirect golang.org/x/crypto v0.0.0-20200423211502-4bdfaf469ed5 golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f // indirect diff --git a/go.sum b/go.sum index 46fd581a60d78651ea4deabeb756cf39e266a46b..4011a704db57a6ac454fdb8116494b3507666825 100644 --- a/go.sum +++ b/go.sum @@ -14,6 +14,8 @@ github.com/bytesparadise/libasciidoc v0.2.0 h1:W+Yh4cXehuQvFA+Ncs4tIgwBXiH8ie+KhHmMXkBhIcc= github.com/bytesparadise/libasciidoc v0.2.0/go.mod h1:CZX8GIEkxy/LHrDZjPbNrE16RQFDrnG6hBjnjXcD34Y= github.com/bytesparadise/libasciidoc v0.4.0 h1:fse9nKBTZ1OcAltOhf5XJUxctakbiaDT3Jw6qCPaM7Y= github.com/bytesparadise/libasciidoc v0.4.0/go.mod h1:fNxeS06tJufiBEyZJXnO0ng4xv8EdlswK/tKStNz/MA= +github.com/chai2010/webp v1.1.0 h1:4Ei0/BRroMF9FaXDG2e4OxwFcuW2vcXd+A6tyqTJUQQ= +github.com/chai2010/webp v1.1.0/go.mod h1:LP12PG5IFmLGHUU26tBiCBKnghxx3toZFwDjOYvd3Ow= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -83,8 +85,6 @@ github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q= github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= -github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086 h1:RYiqpb2ii2Z6J4x0wxK46kvPBbFuZcdhS+CIztmYgZs= -github.com/skip2/go-qrcode v0.0.0-20191027152451-9434209cb086/go.mod h1:PLPIyL7ikehBD1OAjmKKiOEhbvWyHGaNDjquXMcYABo= github.com/sozorogami/gover v0.0.0-20171022184752-b58185e213c5 h1:TAPeDBsd52dRWoWzf5trgBzxzMYHTYjYI+4xNyCdoCU= github.com/sozorogami/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:nHNlDYIQZn44RvqH0kCpl/dMMVWXkav0QIgzGxV1Ab4= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= diff --git a/libamuse/qr.go b/libamuse/qr.go deleted file mode 100644 index 095a87e459decbc8739d5d2ecd2e63e3bc381878..0000000000000000000000000000000000000000 --- a/libamuse/qr.go +++ /dev/null @@ -1,10 +0,0 @@ -package libamuse - -import ( - "github.com/skip2/go-qrcode" -) - -func ShowQr(keyUrl string) (string, error){ - png, err := qrcode.Encode(keyUrl, qrcode.Low, 256) // todo webp - return string(png), err -} diff --git a/libamuse/signup.go b/libamuse/signup.go index de8aa6cbdfdb0b6da9f2996068ac579e8b487dba..0d61856af34772eafecd13a6ef65fa60f56fec79 100644 --- a/libamuse/signup.go +++ b/libamuse/signup.go @@ -3,41 +3,85 @@ import ( "notabug.org/apiote/amuse/accounts" + "bytes" + "encoding/base32" + "encoding/base64" "errors" + "image" "strings" + "github.com/chai2010/webp" "github.com/pquerna/otp" "github.com/pquerna/otp/totp" "notabug.org/apiote/gott" ) func createSecret(args ...interface{}) (interface{}, error) { + var ( + err error + secretB []byte + ) + result := args[1].(*Result) - opts := totp.GenerateOpts{ // todo host - Issuer: "amuse.apiote.tk", - AccountName: "nearly_headless_nick@amuse.apiote.tk", + secret := args[4].(string) + host := args[6].(string) + + if len(secret) > 0 { + secretB, err = base32.StdEncoding.DecodeString(secret) + } + opts := totp.GenerateOpts{ + Issuer: host, + AccountName: "nearly_headless_nick@" + host, + Secret: secretB, } key, err := totp.Generate(opts) result.result = key return gott.Tuple(args), err } +func createImage(args ...interface{}) (interface{}, error) { + result := args[1].(*Result) + secret := result.result.(*otp.Key) + image, err := secret.Image(256, 256) + result.result2 = image + return gott.Tuple(args), err +} + +func encodeWebp(args ...interface{}) (interface{}, error) { + result := args[1].(*Result) + image := result.result2.(image.Image) + var buf bytes.Buffer + err := webp.Encode(&buf, image, &webp.Options{Lossless: true}) + + data := "data:image/webp;base64," + data += base64.StdEncoding.EncodeToString(buf.Bytes()) + result.result2 = data + + return gott.Tuple(args), err +} + func renderSignup(args ...interface{}) interface{} { result := args[1].(*Result) secret := result.result.(*otp.Key) + qr := result.result2.(string) + sfaEnabled := args[3].(bool) + username := args[5].(string) + var authError error if args[2] != nil { authError = args[2].(error) } - result.page = result.renderer.RenderSignup(result.languages, authError, secret) + result.page = result.renderer.RenderSignup(result.languages, authError, secret, sfaEnabled, username, qr) return gott.Tuple(args) } -func ShowSignup(acceptLanguages, mimetype string, err error) (string, error) { +func ShowSignup(acceptLanguages, mimetype string, err error, sfaEnabled bool, sfaSecret, username, host string) (string, error) { r, err := gott. - NewResult(gott.Tuple{&RequestData{language: acceptLanguages, mimetype: mimetype}, &Result{}, err}). + NewResult(gott.Tuple{&RequestData{language: acceptLanguages, mimetype: mimetype}, &Result{}, err, sfaEnabled, sfaSecret, username, host}). Bind(parseLanguage). Bind(createSecret). + Bind(createImage). + Bind(encodeWebp). Bind(createRenderer). Map(renderSignup). Finish() diff --git a/router.go b/router.go index 5fc3a84ea1453e367b729a9816dd01cba40ab4a9..29955d0204f2aadcb6af8e609fd151e5f61ce8b9 100644 --- a/router.go +++ b/router.go @@ -226,12 +226,13 @@ func signupGet(w http.ResponseWriter, r *http.Request, acceptLanguages, mimetype string) { auth := getAuthToken(r) user, _ := libamuse.VerifyAuthToken(auth) + host := r.Host if !user.IsEmpty() { w.Header().Add("Location", "/") w.WriteHeader(303) return } - signup, err := libamuse.ShowSignup(acceptLanguages, mimetype, nil) + signup, err := libamuse.ShowSignup(acceptLanguages, mimetype, nil, false, "", "", host) render(signup, err, w, acceptLanguages, mimetype) } @@ -244,6 +245,7 @@ passwordConfirm := r.PostForm.Get("password2") sfaEnabled := r.PostForm.Get("sfaEnabled") == "true" sfaSecret := r.PostForm.Get("sfaSecret") sfa := r.PostForm.Get("sfa") + host := r.Host recoveryCodes, err := libamuse.DoSignup(username, password, passwordConfirm, sfaEnabled, sfaSecret, sfa) if err != nil { @@ -252,7 +254,7 @@ if authErr, ok := err.(accounts.AuthError); ok { var signup string var err error if mimetype == "text/html" { - signup, err = libamuse.ShowSignup(acceptLanguages, mimetype, &authErr) + signup, err = libamuse.ShowSignup(acceptLanguages, mimetype, &authErr, sfaEnabled, sfaSecret, username, host) } else { // todo send capnproto not authed } @@ -460,24 +462,6 @@ } } } -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) - } - - qr, err := libamuse.ShowQr(keyUrl) - w.Header().Set("Content-Type", "image/png; charset=utf-8") - if err != nil { - render("", err, w, acceptLanguages, mimetype) - } - w.Write([]byte(qr)) -} - func route(port int) { portStr := fmt.Sprintf(":%d", port) @@ -495,7 +479,6 @@ http.HandleFunc("/login", login) http.HandleFunc("/signup", signup) http.HandleFunc("/signedup", signedup) - http.HandleFunc("/qr/", qr) fmt.Printf("running on %s\n", portStr) e := http.ListenAndServe(portStr, nil) if e != nil { diff --git a/templates/signup.html b/templates/signup.html index 08e59561d9066056f7f8567272faf4afab2cb369..cf10cac54d08072a77aa5dec329658e593de89dc 100644 --- a/templates/signup.html +++ b/templates/signup.html @@ -25,18 +25,18 @@Error{{ end }} <form action="/signup" method="POST" class="clear-float"> <label for="username" class="sans block font-1 margin-top-1">Username</label> - <input autofocus type="text" required id="username" name="username" class="block bg-none border-none border-bottom text font-1_5" /> + <input autofocus type="text" required id="username" name="username" value="{{.Data.Username}}" class="block bg-none border-none border-bottom text font-1_5" /> <label for="password" class="sans block font-1 margin-top-1">Pasword</label> <input type="password" required id="password" name="password" class="block bg-none border-none border-bottom text font-1_5" /> <label for="password2" class="sans block font-1 margin-top-1">Confirm password</label> <input type="password" required id="password2" name="password2" class="block bg-none border-none border-bottom text font-1_5" /> <div class="margin-tb-2 border-_5 border-grey padding-tb-_5 padding-lr-_25 border-solid"> <label for="sfa-enabled" class="sans font-1 margin-top-1">Enable second factor authentication <span title="Use Your favourite TOTP app" class="material-icon"></span></label> - <input type="checkbox" id="sfa-enabled" class="" name="sfaEnabled" value="true"/> + <input type="checkbox" id="sfa-enabled" class="" name="sfaEnabled" value="true" {{if .Data.SfaEnabled}}checked{{end}}/> <div class="" id="sfa-box"> <input type="hidden" name="sfaSecret" class="margin-lr-_5 margin-tb-_5 text bg-none border-none" value="{{.Data.Secret}}" /> <div class="margin-tb-_5"> - <img src="/qr/?url={{.Data.Url}}" class="block margin-auto"/> + <img src="{{.Data.Qr}}" class="block margin-auto"/> </div> <span class="sans text-unimportant">{{.Data.Secret}}</span> <label for="sfa" class="sans block font-1 margin-top-1">Confirm second factor authentication</label>