Skip to content

Commit

Permalink
Add OAuth start + finish
Browse files Browse the repository at this point in the history
  • Loading branch information
thehowl committed Apr 29, 2018
1 parent 1c72547 commit 7e3bebd
Show file tree
Hide file tree
Showing 1,032 changed files with 264,365 additions and 14 deletions.
78 changes: 74 additions & 4 deletions Gopkg.lock

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

12 changes: 12 additions & 0 deletions Gopkg.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,15 @@
[[constraint]]
name = "github.com/jinzhu/gorm"
version = "v1.9.1"

[[constraint]]
branch = "master"
name = "golang.org/x/oauth2"

[[constraint]]
name = "github.com/go-redis/redis"
version = "6.10.2"

[[constraint]]
branch = "master"
name = "zxq.co/x/ripple"
112 changes: 112 additions & 0 deletions http/api/oauth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package api

import (
"context"
"crypto/rand"
"encoding/base64"
"time"

"golang.org/x/oauth2"
"zxq.co/ripple/misirlou-api/http"
"zxq.co/ripple/misirlou-api/models"
"zxq.co/x/ripple"
)

var oauthEndpoint = oauth2.Endpoint{
AuthURL: "https://ripple.moe/oauth/authorize",
TokenURL: "https://ripple.moe/oauth/token",
}

func getConfig(c *http.Context) oauth2.Config {
return oauth2.Config{
ClientID: c.OAuth2ClientID,
ClientSecret: c.OAuth2ClientSecret,
Endpoint: oauthEndpoint,
RedirectURL: c.BaseURL + "/oauth/finish",
}
}

// OAuthStart starts the oauth flow.
func OAuthStart(c *http.Context) {
conf := getConfig(c)

id := randomStr(9)
val := randomStr(15)

err := c.Redis.Set("misirlou:oauth_state:"+id, val, time.Minute*40).Err()
if err != nil {
c.Error(err)
return
}

c.SetCookie("oauth_state", id, time.Minute*40)

c.Redirect(302, conf.AuthCodeURL(val))
}

func randomStr(l int) string {
b := make([]byte, l)
rand.Read(b)
return base64.RawURLEncoding.EncodeToString(b)
}

// OAuthFinish starts the oauth flow.
func OAuthFinish(c *http.Context) {
// Check that the state is valid
sec, err := c.Redis.Get("misirlou:oauth_state:" + c.Cookie("oauth_state")).
Result()
if err != nil {
c.Error(err)
return
}
defer c.DeleteCookie("oauth_state")
if c.Query("state") != sec {
c.SetCode(401)
c.WriteString("State is invalid; please try logging again!")
return
}

// Exchange token with Ripple OAuth
conf := getConfig(c)
ctx, _ := context.WithTimeout(context.Background(), 5000*time.Millisecond)
code, err := conf.Exchange(ctx, c.Query("code"))
if err != nil {
c.Error(err)
return
}
// Create temporary Ripple Client to get info about self
rc := &ripple.Client{
IsBearer: true,
Token: code.AccessToken,
}
u, err := rc.User(ripple.Self)
if err != nil {
c.SetCode(403)
c.WriteString("Error from Ripple API: " + err.Error())
return
}
if u == nil {
c.SetCode(404)
c.WriteString("User not found?")
return
}

// Save session
sess := &models.Session{
ID: randomStr(15),
UserID: u.ID,
AccessToken: code.AccessToken,
}
err = c.DB.SetSession(sess)
if err != nil {
c.Error(err)
return
}

c.Redirect(302, c.StoreTokensURL+"?session="+sess.ID+"&access="+sess.AccessToken)
}

func init() {
http.GET("/oauth/start", OAuthStart)
http.GET("/oauth/finish", OAuthFinish)
}
43 changes: 39 additions & 4 deletions http/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"time"

"github.com/erikdubbelboer/fasthttp"
"github.com/go-redis/redis"
"github.com/thehowl/fasthttprouter"
"zxq.co/ripple/misirlou-api/models"
)
Expand Down Expand Up @@ -45,7 +46,15 @@ func PUT(path string, handler func(c *Context)) {
// Options is a struct which is embedded in every context and contains
// information passed to the handlers directly by the main package.
type Options struct {
DB *models.DB
DB *models.DB
Redis *redis.Client

OAuth2ClientID string
OAuth2ClientSecret string

BaseURL string
StoreTokensURL string
HTTPS bool
}

// Handler creates an HTTP request handler using httprouter.
Expand Down Expand Up @@ -173,14 +182,35 @@ func (c *Context) SetJSON(v interface{}, is404 bool) {
c.SetJSONWithCode(v, code)
}

// SetCookie sets a cookie in the user's browser.
func (c *Context) SetCookie(k, v string, expire time.Duration) {
cookie := fasthttp.AcquireCookie()
cookie.SetKey(k)
cookie.SetValue(v)
cookie.SetExpire(time.Now().Add(expire))
cookie.SetSecure(c.HTTPS)
c.ctx.Response.Header.SetCookie(cookie)
fasthttp.ReleaseCookie(cookie)
}

// DeleteCookie removes a cookie from the client.
func (c *Context) DeleteCookie(k string) {
c.ctx.Response.Header.DelClientCookie(k)
}

// Redirect redirects the client to the given page, using the given response code.
func (c *Context) Redirect(code int, location string) {
c.ctx.Redirect(location, code)
}

// Query retrieves a value from the query string.
func (c *Context) Query(s string) []byte {
return c.ctx.QueryArgs().PeekBytes(s2b(s))
func (c *Context) Query(s string) string {
return string(c.ctx.QueryArgs().PeekBytes(s2b(s)))
}

// QueryInt retrieves a value from the query int, and parses it as an int.
func (c *Context) QueryInt(s string) int {
i, _ := strconv.Atoi(b2s(c.Query(s)))
i, _ := strconv.Atoi(c.Query(s))
return i
}

Expand All @@ -201,6 +231,11 @@ func (c *Context) JSON(v interface{}) error {
return err
}

// Cookie returns the requested cookie.
func (c *Context) Cookie(s string) string {
return string(c.ctx.Request.Header.Cookie(s))
}

var ipHeaders = [...][]byte{
[]byte("X-Forwarded-For"),
[]byte("X-Real-IP"),
Expand Down
43 changes: 37 additions & 6 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,65 @@ package main
import (
"fmt"
"os"
"strconv"

"github.com/erikdubbelboer/fasthttp"
"github.com/go-redis/redis"
"zxq.co/ripple/misirlou-api/http"
"zxq.co/ripple/misirlou-api/models"

_ "zxq.co/ripple/misirlou-api/http/api"
)

func main() {
db, err := models.CreateDB(os.Getenv("MYSQL_DSN"))
// set up mysql db
db, err := models.CreateDB(getenvdefault("MYSQL_DSN",
"root@/misirlou?multiStatements=true&parseTime=true"))
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}

port := os.Getenv("PORT")
if port == "" {
port = ":8511"
}
// set up redis
rdb := redis.NewClient(&redis.Options{
Network: os.Getenv("REDIS_NETWORK"),
Addr: getenvdefault("REDIS_ADDR", "localhost:6379"),
Password: os.Getenv("REDIS_PASSWORD"),
DB: getenvint("REDIS_DB", 0),
})

port := getenvdefault("PORT", ":8511")

fmt.Println("Listening on", port)

handler := http.Handler(http.Options{
DB: db,
DB: db,
Redis: rdb,
OAuth2ClientID: os.Getenv("OAUTH2_CLIENT_ID"),
OAuth2ClientSecret: os.Getenv("OAUTH2_CLIENT_SECRET"),
BaseURL: getenvdefault("BASE_URL", "http://localhost"+port),
StoreTokensURL: getenvdefault("STORE_TOKENS_URL", "http://localhost"),
})
err = fasthttp.ListenAndServe(port, handler)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

func getenvdefault(env, def string) string {
v := os.Getenv(env)
if v == "" {
return def
}
return v
}

func getenvint(env string, def int) int {
v := os.Getenv(env)
if v == "" {
return def
}
i, _ := strconv.Atoi(v)
return i
}
2 changes: 2 additions & 0 deletions vendor/github.com/go-redis/redis/.gitignore

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

Loading

0 comments on commit 7e3bebd

Please sign in to comment.