Skip to content
This repository has been archived by the owner on Nov 13, 2024. It is now read-only.

Commit

Permalink
Add support for multiple hostnames
Browse files Browse the repository at this point in the history
* Enable ProxyHeader handler to get accurate scheme/hosts/etc
* Figure out hostname from hosts header
* Add tests for above
* Support legacy REDIRECT_URI
  • Loading branch information
halkeye committed Feb 18, 2019
1 parent da3a64f commit c728bd5
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 8 deletions.
4 changes: 2 additions & 2 deletions lib/trakt/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ import (
"github.com/xanderstrike/goplaxt/lib/store"
)

func AuthRequest(username, code, refreshToken, grantType string) map[string]interface{} {
func AuthRequest(root, username, code, refreshToken, grantType string) map[string]interface{} {
values := map[string]string{
"code": code,
"refresh_token": refreshToken,
"client_id": os.Getenv("TRAKT_ID"),
"client_secret": os.Getenv("TRAKT_SECRET"),
"redirect_uri": fmt.Sprintf("%s/authorize?username=%s", os.Getenv("REDIRECT_URI"), username),
"redirect_uri": fmt.Sprintf("%s/authorize?username=%s", root, username),
"grant_type": grantType,
}
jsonValue, _ := json.Marshal(values)
Expand Down
57 changes: 54 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,48 @@ import (
"io/ioutil"
"log"
"net/http"
"net/url"
"os"
"regexp"
"strings"
"time"

"github.com/gorilla/handlers"
"github.com/gorilla/mux"
"github.com/xanderstrike/goplaxt/lib/plex"
"github.com/xanderstrike/goplaxt/lib/store"
"github.com/xanderstrike/goplaxt/lib/trakt"
)

type AuthorizePage struct {
SelfRoot string
Authorized bool
URL string
ClientID string
}

func SelfRoot(r *http.Request) string {
u, _ := url.Parse("")
u.Host = r.Host
u.Scheme = r.URL.Scheme
u.Path = ""
if u.Scheme == "" {
u.Scheme = "http"

proto := r.Header.Get("X-Forwarded-Proto")
if proto == "https" {
u.Scheme = "https"
}
}
return u.String()
}

func authorize(w http.ResponseWriter, r *http.Request) {
args := r.URL.Query()
username := strings.ToLower(args["username"][0])
log.Print(fmt.Sprintf("Handling auth request for %s", username))
code := args["code"][0]
result := trakt.AuthRequest(username, code, "", "authorization_code")
result := trakt.AuthRequest(SelfRoot(r), username, code, "", "authorization_code")

user := store.NewUser(username, result["access_token"].(string), result["refresh_token"].(string))

Expand All @@ -38,6 +58,7 @@ func authorize(w http.ResponseWriter, r *http.Request) {

tmpl := template.Must(template.ParseFiles("static/index.html"))
data := AuthorizePage{
SelfRoot: SelfRoot(r),
Authorized: true,
URL: url,
ClientID: os.Getenv("TRAKT_ID"),
Expand All @@ -55,7 +76,7 @@ func api(w http.ResponseWriter, r *http.Request) {
tokenAge := time.Since(user.Updated).Hours()
if tokenAge > 1440 { // tokens expire after 3 months, so we refresh after 2
log.Println("User access token outdated, refreshing...")
result := trakt.AuthRequest(user.Username, "", user.RefreshToken, "refresh_token")
result := trakt.AuthRequest(SelfRoot(r), user.Username, "", user.RefreshToken, "refresh_token")
user = store.UpdateUser(user, result["access_token"].(string), result["refresh_token"].(string))
log.Println("Refreshed, continuing")
}
Expand All @@ -76,14 +97,44 @@ func api(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode("success")
}

func AllowedHostsHandler(allowedHostnames string) func(http.Handler) http.Handler {
allowedHosts := strings.Split(regexp.MustCompile("https://|http://").ReplaceAllString(strings.ToLower(allowedHostnames), ""), ",")
log.Println("Allowed Hostnames:", allowedHosts)
return func(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
isAllowedHost := false
lcHost := strings.ToLower(r.Host)
for _, value := range allowedHosts {
if lcHost == value {
isAllowedHost = true
break
}
}
if !isAllowedHost {
w.WriteHeader(http.StatusBadRequest)
w.Header().Set("Content-Type", "text/plain")
fmt.Fprintf(w, "Oh no!")
return
}
h.ServeHTTP(w, r)
}

return http.HandlerFunc(fn)
}
}

func main() {
log.Print("Started!")
router := mux.NewRouter()
// Assumption: Behind a proper web server (nginx/traefik, etc) that removes/replaces trusted headers
router.Use(handlers.ProxyHeaders)
// which hostnames we are allowing
router.Use(AllowedHostsHandler(os.Getenv("REDIRECT_URI")))
router.HandleFunc("/authorize", authorize).Methods("GET")
router.HandleFunc("/api", api).Methods("POST")
router.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("static/index.html"))
data := AuthorizePage{
SelfRoot: SelfRoot(r),
Authorized: false,
URL: "https://plaxt.astandke.com/api?id=generate-your-own-silly",
ClientID: os.Getenv("TRAKT_ID"),
Expand Down
43 changes: 43 additions & 0 deletions main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package main

import (
"github.com/gorilla/handlers"

"github.com/stretchr/testify/assert"

"net/http"
"net/http/httptest"
"testing"
)

func TestSelfRoot(t *testing.T) {
var (
r *http.Request
err error
)

// Test Default
r, err = http.NewRequest("GET", "/authorize", nil)
r.Host = "foo.bar"
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "http://foo.bar", SelfRoot(r))

// Test Manual forwarded proto
r, err = http.NewRequest("GET", "/validate", nil)
r.Host = "foo.bar"
r.Header.Set("X-Forwarded-Proto", "https")
if err != nil {
t.Fatal(err)
}
assert.Equal(t, "https://foo.bar", SelfRoot(r))

// Test ProxyHeader handler
rr := httptest.NewRecorder()
r, err = http.NewRequest("GET", "/validate", nil)
r.Header.Set("X-Forwarded-Host", "foo.bar")
r.Header.Set("X-Forwarded-Proto", "https")
handlers.ProxyHeaders(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {})).ServeHTTP(rr, r)
assert.Equal(t, "https://foo.bar", SelfRoot(r))
}
4 changes: 1 addition & 3 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,7 @@ <h3>More Options</h3>
crossorigin="anonymous"></script>

<script>
var full = location.protocol+'//'+location.hostname+(location.port ? ':'+location.port: '');
console.log(full);
var authorization_link = "https://trakt.tv/oauth/authorize?client_id={{.ClientID}}&redirect_uri=" + full + "/authorize%3fusername=USERNAME&response_type=code";
var authorization_link = "https://trakt.tv/oauth/authorize?client_id={{.ClientID}}&redirect_uri={{.SelfRoot}}/authorize%3fusername=USERNAME&response_type=code";

$('.js-authorize').click(function() {
var username = $('.js-username').val().toLowerCase();
Expand Down

0 comments on commit c728bd5

Please sign in to comment.