Skip to content
Permalink
Browse files

login and a simple dashboard and activation flow.

  • Loading branch information...
fiatjaf committed Jul 24, 2018
1 parent 2da0f11 commit 35f3e6361e7e895b4f6d3b71d07029886406b1d1
Showing with 471 additions and 194 deletions.
  1. +180 −0 handlers.go
  2. +1 −5 helpers.go
  3. +32 −41 main.go
  4. +89 −0 methods.go
  5. +4 −4 postgres.sql
  6. +0 −132 schema.go
  7. +60 −0 templates/account.html
  8. +54 −0 templates/head.html
  9. +34 −0 templates/index.html
  10. +15 −11 types.go
  11. +2 −1 webhooks.go
@@ -0,0 +1,180 @@
package main

import (
"database/sql"
"net/http"
"net/url"
"strings"

"github.com/mrjones/oauth"
"gopkg.in/jmcvetta/napping.v3"
)

func ServeIndex(w http.ResponseWriter, r *http.Request) {
err = tmpl.ExecuteTemplate(w, "index.html", nil)
if err != nil {
log.Warn().Err(err).Msg("failed to render /")
}
}

func TrelloAuth(w http.ResponseWriter, r *http.Request) {
sess, _ := store.Get(r, "auth-session")
if v, ok := sess.Values["id"]; ok && v != "" {
// already logged
http.Redirect(w, r, "/account", http.StatusFound)
return
}

token, url, err := c.GetRequestTokenAndUrl("http://" + r.Host + "/auth/callback")
if err != nil {
log.Print(err)
http.Error(w, "Couldn't redirect you to Trello.", 503)
return
}

sess.Values[token.Token] = token.Secret
sess.Save(r, w)

http.Redirect(w, r, url, http.StatusFound)
}

func TrelloAuthCallback(w http.ResponseWriter, r *http.Request) {
values := r.URL.Query()
verificationCode := values.Get("oauth_verifier")
tokenKey := values.Get("oauth_token")

sess, _ := store.Get(r, "auth-session")
tokenI := sess.Values[tokenKey]
if tokenI == nil {
http.Redirect(w, r, "/", http.StatusFound)
return
}
requestToken := &oauth.RequestToken{
tokenKey,
tokenI.(string),
}

accessToken, err := c.AuthorizeToken(requestToken, verificationCode)
if err != nil {
http.Error(w, "Invalid token, did something went wrong on your Trello login?", 401)
return
}

var profile struct {
Id string `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
}
response, err := napping.Get("https://trello.com/1/members/me", &url.Values{
"key": []string{s.TrelloApiKey},
"token": []string{accessToken.Token},
"fields": []string{"username,id,email"},
}, &profile, nil)
if err != nil || response.Status() > 299 {
http.Error(w, "Failed to fetch your profile info from Trello. This is odd.", 503)
return
}

delete(sess.Values, tokenKey)
sess.Values["id"] = profile.Id
sess.Values["username"] = profile.Username
sess.Values["email"] = profile.Email
sess.Values["token"] = accessToken.Token
sess.Save(r, w)

http.Redirect(w, r, "/account", http.StatusFound)
}

func ServeAccount(w http.ResponseWriter, r *http.Request) {
sess, _ := store.Get(r, "auth-session")
username, ok1 := sess.Values["username"]
token, ok2 := sess.Values["token"]
email, ok3 := sess.Values["email"]
if !ok1 || !ok2 || !ok3 {
http.Redirect(w, r, "/auth", http.StatusFound)
return
}

trello := makeTrelloClient(token.(string))

// get all boards for which this user is an admin
var allboards []Board
err := trello("get",
"/1/members/"+username.(string)+
"/boards?filter=open&fields=id,shortLink,name,memberships&memberships=me",
nil, &allboards)
if err != nil {
http.Error(w, "failed to fetch trello boards: "+err.Error(), 503)
return
}

var boards []Board
for _, board := range allboards {
m := board.Memberships[0]
if m.MemberType == "admin" || m.OrgMemberType == "admin" {
boards = append(boards, board)
}
}

// make an array of board ids so we can query
boardids := make([]string, len(boards))
for i, board := range boards {
boardids[i] = board.Id
}

// from all possible boards, which ones are enabled
// even if they are enabled by a different trello user
var enabledboards []Board
err = pg.Select(&enabledboards, `
SELECT * FROM boards
WHERE id = ANY (string_to_array($1, ','))
`, strings.Join(boardids, ","))
if err != nil && err != sql.ErrNoRows {
http.Error(w, "failed to fetch enabled boards: "+err.Error(), 500)
return
}

// merge enabled properties on full boards list
for i, iboard := range boards {
for _, jboard := range enabledboards {
if iboard.Id == jboard.Id {
boards[i].Email = jboard.Email
boards[i].Enabled = true
}
}
}

err = tmpl.ExecuteTemplate(w, "account.html", struct {
Username string
Email string
Boards []Board
}{username.(string), email.(string), boards})
if err != nil {
log.Warn().Err(err).Msg("failed to render /account")
}
}

func handleSetupBoard(w http.ResponseWriter, r *http.Request) {
sess, _ := store.Get(r, "auth-session")
email, ok1 := sess.Values["email"]
token, ok2 := sess.Values["token"]
id, ok3 := sess.Values["id"]
if !ok1 || !ok2 || !ok3 {
http.Redirect(w, r, "/auth", http.StatusFound)
return
}
board := r.FormValue("board")
enabled := r.FormValue("enabled") != "false"

err := setupBoard(board, id.(string), email.(string), token.(string), enabled)
if err != nil {
http.Error(w, "failed to set permissions on board: "+err.Error(), 500)
return
}

http.Redirect(w, r, "/account", http.StatusFound)
}

func returnOk(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}
@@ -50,11 +50,7 @@ func userAllowed(trello trelloClient, userId, boardId, cardId string) bool {
}

// check board and team admins
var br []struct {
IdMember string `json:"idMember"`
MemberType string `json:"memberType"`
OrgMemberType string `json:"orgMemberType"`
}
var br []Membership
err = trello("get", "/1/boards/"+boardId+"/memberships?member=false&orgMemberType=true", nil, &br)
if err != nil {
log.Warn().Str("board", boardId).Err(err).Msg("failed to fetch memberships")
73 main.go
@@ -1,22 +1,20 @@
package main

import (
"context"
"crypto/sha256"
"fmt"
"net/http"
"net/url"
"os"
"strings"
"text/template"
"time"

"github.com/gorilla/mux"
"github.com/gorilla/sessions"
"github.com/graphql-go/graphql"
"github.com/graphql-go/handler"
"github.com/jmoiron/sqlx"
"github.com/kelseyhightower/envconfig"
_ "github.com/lib/pq"
"github.com/minio/minio-go"
"github.com/mrjones/oauth"
"github.com/rs/zerolog"
"gopkg.in/redis.v5"
"gopkg.in/tylerb/graceful.v1"
@@ -37,9 +35,12 @@ type Settings struct {

var err error
var s Settings
var c *oauth.Consumer
var pg *sqlx.DB
var rds *redis.Client
var ms3 *minio.Client
var tmpl *template.Template
var store sessions.Store
var router *mux.Router
var schema graphql.Schema
var log = zerolog.New(os.Stderr).Output(zerolog.ConsoleWriter{Out: os.Stderr})
@@ -51,6 +52,10 @@ func main() {
}

zerolog.SetGlobalLevel(zerolog.DebugLevel)
log = log.With().Timestamp().Logger()

// cookie store
store = sessions.NewCookieStore([]byte(s.SecretKey))

// minio s3 client
ms3, _ = minio.New(
@@ -60,12 +65,22 @@ func main() {
true,
)

// graphql schema
schema, err = graphql.NewSchema(schemaConfig)
if err != nil {
log.Fatal().Err(err).Msg("failed to create graphql schema")
}
handler := handler.New(&handler.Config{Schema: &schema})
// templates
tmpl = template.Must(template.New("~").ParseGlob("templates/*.html"))

// oauth consumer
c = oauth.NewConsumer(
s.TrelloApiKey,
s.TrelloApiSecret,
oauth.ServiceProvider{
RequestTokenUrl: "https://trello.com/1/OAuthGetRequestToken",
AuthorizeTokenUrl: "https://trello.com/1/OAuthAuthorizeToken",
AccessTokenUrl: "https://trello.com/1/OAuthGetAccessToken",
},
)
c.AdditionalAuthorizationUrlParams["name"] = "Permissions for Trello"
c.AdditionalAuthorizationUrlParams["scope"] = "read,write,account"
c.AdditionalAuthorizationUrlParams["expiration"] = "never"

// postgres connection
pg, err = sqlx.Connect("postgres", s.PostgresURL)
@@ -91,32 +106,12 @@ func main() {
// define routes
router = mux.NewRouter()

router.Path("/_graphql").Methods("POST").HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
var secret string
if p := strings.Split(r.Header.Get("Authorization"), " "); len(p) == 2 {
secret = p[1]
}

var board string

if fmt.Sprintf("%x",
sha256.Sum256([]byte(s.SecretKey+":"+r.Header.Get("Board"))),
) == secret {
board = r.Header.Get("Board")
}

ctx := context.WithValue(
context.TODO(),
"board", board,
)

w.Header().Set("Content-Type", "application/json")

handler.ContextHandler(ctx, w, r)
},
)
router.Path("/_/webhooks/board").Methods("HEAD").HandlerFunc(ReturnOk)
router.Path("/").Methods("GET").HandlerFunc(ServeIndex)
router.Path("/auth").Methods("GET").HandlerFunc(TrelloAuth)
router.Path("/auth/callback").Methods("GET").HandlerFunc(TrelloAuthCallback)
router.Path("/account").Methods("GET").HandlerFunc(ServeAccount)
router.Path("/setBoard").Methods("POST").HandlerFunc(handleSetupBoard)
router.Path("/_/webhooks/board").Methods("HEAD").HandlerFunc(returnOk)
router.Path("/_/webhooks/board").Methods("POST").HandlerFunc(handleWebhook)
router.PathPrefix("/powerup/").Methods("GET").HandlerFunc(
func(w http.ResponseWriter, r *http.Request) {
@@ -151,7 +146,3 @@ func main() {
log.Info().Str("port", os.Getenv("PORT")).Msg("listening.")
graceful.Run(":"+os.Getenv("PORT"), 10*time.Second, router)
}

func ReturnOk(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
}
Oops, something went wrong.

0 comments on commit 35f3e63

Please sign in to comment.
You can’t perform that action at this time.