Skip to content

Commit

Permalink
add QR API endpoint for Telegram auth and notifications
Browse files Browse the repository at this point in the history
Telegram authentication requires you to open a chat on the phone.
It's convenient to have a QR code for the case when you want to
log in on the computer but have Telegram only on your phone
and would be able to scan the QR instead of copy-pasting the link
from the computer to the phone any other way.

Originally we thought of generating QR on the client but found
backend-generated QR a better alternative because we avoid adding
one more JavaScript dependency to the frontend that way.
  • Loading branch information
paskal authored and umputun committed Jan 31, 2022
1 parent 8d42d07 commit 603deca
Show file tree
Hide file tree
Showing 23 changed files with 5,918 additions and 0 deletions.
1 change: 1 addition & 0 deletions backend/_example/memory_store/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,7 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/slack-go/slack v0.10.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
Expand Down
1 change: 1 addition & 0 deletions backend/app/rest/api/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ func (s *Rest) routes() chi.Router {
ropen.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(10, nil)))
ropen.Use(authMiddleware.Trace, logInfoWithBody)
ropen.Get("/picture/{user}/{id}", s.pubRest.loadPictureCtrl)
ropen.Get("/qr/telegram", s.pubRest.telegramQrCtrl)
})

// protected routes, require auth
Expand Down
29 changes: 29 additions & 0 deletions backend/app/rest/api/rest_public.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
log "github.com/go-pkgz/lgr"
R "github.com/go-pkgz/rest"
"github.com/pkg/errors"
"github.com/skip2/go-qrcode"

"github.com/umputun/remark42/backend/app/rest"
"github.com/umputun/remark42/backend/app/store"
Expand Down Expand Up @@ -364,6 +365,34 @@ func (s *public) robotsCtrl(w http.ResponseWriter, r *http.Request) {
render.PlainText(w, r, "User-agent: *\nDisallow: /auth/\nDisallow: /api/\n"+strings.Join(allowed, "\n")+"\n")
}

// GET /qr/telegram - generates QR for provided URL, used for Telegram auth and notifications subscription. The first
// step of both is the user opening a link to Telegram and writing bot a message, and the user might try to log in
// from a computer but have Telegram installed only on the mobile device.
// Provided text should start with https://t.me/.
func (s *public) telegramQrCtrl(w http.ResponseWriter, r *http.Request) {
text := r.URL.Query().Get("url")
if text == "" {
rest.SendErrorJSON(w, r, http.StatusBadRequest, errors.New("missing parameter"), "text parameter is required", rest.ErrInternal)
return
}

if !strings.HasPrefix(text, "https://t.me/") {
rest.SendErrorJSON(w, r, http.StatusBadRequest, errors.New("wrong parameter"), "text parameter should start with https://t.me/", rest.ErrInternal)
return
}

png, err := qrcode.Encode(text, qrcode.Medium, 256)
if err != nil {
rest.SendErrorJSON(w, r, http.StatusInternalServerError, err, "can't generate QR", rest.ErrInternal)
return
}

w.Header().Set("Content-Type", "image/png")
if _, err = w.Write(png); err != nil {
log.Printf("[WARN] can't render qr, %v", err)
}
}

func (s *public) applyView(comments []store.Comment, view string) []store.Comment {
if strings.EqualFold(view, "user") {
projection := make([]store.Comment, 0, len(comments))
Expand Down
39 changes: 39 additions & 0 deletions backend/app/rest/api/rest_public_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"net/http"
"os"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -615,6 +616,44 @@ func TestRest_Config(t *testing.T) {
assert.Equal(t, false, j["admin_edit"].(bool))
}

func TestRest_QR(t *testing.T) {
ts, _, teardown := startupT(t)
defer teardown()

// missing parameter
body, code := get(t, ts.URL+"/api/v1/qr/telegram")
assert.Equal(t, 400, code)
assert.Equal(t, "{\"code\":0,\"details\":\"text parameter is required\",\"error\":\"missing parameter\"}\n", body)

// too long request to build the qr
body, code = get(t, ts.URL+"/api/v1/qr/telegram?url=https://t.me/"+strings.Repeat("string", 1000))
assert.Equal(t, 500, code)
assert.Equal(t, "{\"code\":0,\"details\":\"can't generate QR\",\"error\":\"content too long to encode\"}\n", body)

// wrong request
body, code = get(t, ts.URL+"/api/v1/qr/telegram?url=nonsense")
assert.Equal(t, 400, code)
assert.Equal(t, "{\"code\":0,\"details\":\"text parameter should start with https://t.me/\",\"error\":\"wrong parameter\"}\n", body)

// correct request
r, err := http.Get(ts.URL + "/api/v1/qr/telegram?url=https://t.me/BotFather")
require.NoError(t, err)
bdy, err := io.ReadAll(r.Body)
require.NoError(t, err)
require.NoError(t, r.Body.Close())
require.NotEmpty(t, bdy)
assert.Equal(t, "image/png", r.Header.Get("Content-Type"))
assert.Equal(t, 200, r.StatusCode)

// compare the image
fh, err := os.Open("testdata/qr_test.png")
defer func() { assert.NoError(t, fh.Close()) }()
assert.NoError(t, err)
img, err := io.ReadAll(fh)
assert.NoError(t, err)
assert.Equal(t, img, bdy)
}

func TestRest_Info(t *testing.T) {
ts, srv, teardown := startupT(t)
defer teardown()
Expand Down
Binary file added backend/app/rest/api/testdata/qr_test.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ require (
github.com/rakyll/statik v0.1.7
github.com/rs/xid v1.3.0
github.com/russross/blackfriday/v2 v2.1.0
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
github.com/slack-go/slack v0.10.1
github.com/stretchr/objx v0.3.0 // indirect
github.com/stretchr/testify v1.7.0
Expand Down
2 changes: 2 additions & 0 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
github.com/slack-go/slack v0.10.1 h1:BGbxa0kMsGEvLOEoZmYs8T1wWfoZXwmQFBb6FgYCXUA=
github.com/slack-go/slack v0.10.1/go.mod h1:wWL//kk0ho+FcQXcBTmEafUI5dz4qz5f4mMk8oIkioQ=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
Expand Down
3 changes: 3 additions & 0 deletions backend/remark.rest
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ X-JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJyZW1hcmsiLCJleHAiOjE5NzYw
### get config
GET {{host}}/api/v1/config?site={{site}}

### generate a QR code for Telegram url
GET {{host}}/api/v1/qr/telegram?url=https://t.me/BotFather

### deleteme (user's request). dev token for secret=12345, not admin
POST {{host}}/api/v1/deleteme?site_id={{site}}
X-JWT: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJyZW1hcmsiLCJleHAiOjE5NzYwNTY3NTYsImp0aSI6IjJlOGJmMTE5OTI0MjQxMDRjYjFhZGRlODllMWYwNGFiMTg4YWZjMzQiLCJpYXQiOjE1NzYwNTY0NTYsImlzcyI6InJlbWFyazQyIiwidXNlciI6eyJuYW1lIjoiZGV2X3VzZXIiLCJpZCI6ImRldl91c2VyIiwicGljdHVyZSI6Imh0dHA6Ly8xMjcuMC4wLjE6ODA4MC9hcGkvdjEvYXZhdGFyL2NjZmEyYWJkMDE2Njc2MDViNGUxZmM0ZmNiOTFiMWUxYWYzMjMyNDAuaW1hZ2UiLCJhdHRycyI6eyJhZG1pbiI6dHJ1ZSwiYmxvY2tlZCI6ZmFsc2V9fX0.6Qt5s2enBMRC-Jmsua01yViVYI95Dx6BPBMaNjj36d4
Expand Down
4 changes: 4 additions & 0 deletions backend/vendor/github.com/skip2/go-qrcode/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions backend/vendor/github.com/skip2/go-qrcode/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

19 changes: 19 additions & 0 deletions backend/vendor/github.com/skip2/go-qrcode/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

86 changes: 86 additions & 0 deletions backend/vendor/github.com/skip2/go-qrcode/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 603deca

Please sign in to comment.