Add support for openid with github and facebook

This commit is contained in:
Jonathan Leibiusky @xetorthio
2017-10-04 11:40:56 -03:00
parent eebe638227
commit 4c034812d2
25 changed files with 712 additions and 251 deletions

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"log"
"net/http"
"os"
"time"
"golang.org/x/crypto/acme/autocert"
@@ -16,7 +15,6 @@ import (
"github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/event"
"github.com/play-with-docker/play-with-docker/pwd"
"github.com/play-with-docker/play-with-docker/templates"
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/urfave/negroni"
)
@@ -33,9 +31,6 @@ func Bootstrap(c pwd.PWDApi, ev event.EventApi) {
}
func Register(extend HandlerExtender) {
bypassCaptcha := len(os.Getenv("GOOGLE_RECAPTCHA_DISABLED")) > 0
server, err := socketio.NewServer(nil)
if err != nil {
log.Fatal(err)
@@ -81,17 +76,13 @@ func Register(extend HandlerExtender) {
// Generic routes
r.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
if bypassCaptcha {
http.ServeFile(rw, r, "./www/bypass.html")
} else {
welcome, tmplErr := templates.GetWelcomeTemplate()
if tmplErr != nil {
log.Fatal(tmplErr)
}
rw.Write(welcome)
}
http.ServeFile(rw, r, "./www/landing.html")
}).Methods("GET")
r.HandleFunc("/oauth/providers", ListProviders).Methods("GET")
r.HandleFunc("/oauth/providers/{provider}/login", Login).Methods("GET")
r.HandleFunc("/oauth/providers/{provider}/callback", LoginCallback).Methods("GET")
corsRouter.HandleFunc("/", NewSession).Methods("POST")
if extend != nil {

40
handlers/cookie_id.go Normal file
View File

@@ -0,0 +1,40 @@
package handlers
import (
"net/http"
"github.com/play-with-docker/play-with-docker/config"
)
type CookieID struct {
Id string `json:"id"`
UserName string `json:"user_name"`
UserAvatar string `json:"user_avatar"`
}
func (c *CookieID) SetCookie(rw http.ResponseWriter) error {
if encoded, err := config.SecureCookie.Encode("id", c); err == nil {
cookie := &http.Cookie{
Name: "id",
Value: encoded,
Path: "/",
Secure: config.UseLetsEncrypt,
}
http.SetCookie(rw, cookie)
} else {
return err
}
return nil
}
func ReadCookie(r *http.Request) (*CookieID, error) {
if cookie, err := r.Cookie("id"); err == nil {
value := &CookieID{}
if err = config.SecureCookie.Decode("id", cookie.Value, &value); err == nil {
return value, nil
} else {
return nil, err
}
} else {
return nil, err
}
}

146
handlers/login.go Normal file
View File

@@ -0,0 +1,146 @@
package handlers
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"golang.org/x/oauth2"
"github.com/google/go-github/github"
"github.com/gorilla/mux"
fb "github.com/huandu/facebook"
"github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/pwd/types"
)
func ListProviders(rw http.ResponseWriter, req *http.Request) {
providers := []string{}
for name, _ := range config.Providers {
providers = append(providers, name)
}
json.NewEncoder(rw).Encode(providers)
}
func Login(rw http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
providerName := vars["provider"]
provider, found := config.Providers[providerName]
if !found {
log.Printf("Could not find provider %s\n", providerName)
rw.WriteHeader(http.StatusNotFound)
return
}
loginRequest, err := core.UserNewLoginRequest(providerName)
if err != nil {
log.Printf("Could not start a new user login request for provider %s. Got: %v\n", providerName, err)
rw.WriteHeader(http.StatusInternalServerError)
return
}
scheme := "http"
if req.URL.Scheme != "" {
scheme = req.URL.Scheme
}
host := "localhost"
if req.URL.Host != "" {
host = req.URL.Host
}
provider.RedirectURL = fmt.Sprintf("%s://%s/oauth/providers/%s/callback", scheme, host, providerName)
url := provider.AuthCodeURL(loginRequest.Id)
http.Redirect(rw, req, url, http.StatusFound)
}
func LoginCallback(rw http.ResponseWriter, req *http.Request) {
vars := mux.Vars(req)
providerName := vars["provider"]
provider, found := config.Providers[providerName]
if !found {
log.Printf("Could not find provider %s\n", providerName)
rw.WriteHeader(http.StatusNotFound)
return
}
query := req.URL.Query()
code := query.Get("code")
loginRequestId := query.Get("state")
loginRequest, err := core.UserGetLoginRequest(loginRequestId)
if err != nil {
log.Printf("Could not get login request %s for provider %s. Got: %v\n", loginRequestId, providerName, err)
rw.WriteHeader(http.StatusInternalServerError)
return
}
ctx := req.Context()
tok, err := provider.Exchange(ctx, code)
if err != nil {
log.Printf("Could not exchage code for access token for provider %s. Got: %v\n", providerName, err)
rw.WriteHeader(http.StatusInternalServerError)
return
}
user := &types.User{Provider: providerName}
if providerName == "github" {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: tok.AccessToken},
)
tc := oauth2.NewClient(ctx, ts)
client := github.NewClient(tc)
u, _, err := client.Users.Get(ctx, "")
if err != nil {
log.Printf("Could not get user from github. Got: %v\n", err)
rw.WriteHeader(http.StatusInternalServerError)
return
}
user.ProviderUserId = strconv.Itoa(u.GetID())
user.Name = u.GetName()
user.Avatar = u.GetAvatarURL()
user.Email = u.GetEmail()
} else if providerName == "facebook" {
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: tok.AccessToken},
)
tc := oauth2.NewClient(ctx, ts)
session := &fb.Session{
Version: "v2.10",
HttpClient: tc,
}
p := fb.Params{}
p["fields"] = "email,name,picture"
res, err := session.Get("/me", p)
if err != nil {
log.Printf("Could not get user from facebook. Got: %v\n", err)
rw.WriteHeader(http.StatusInternalServerError)
return
}
user.ProviderUserId = res.Get("id").(string)
user.Name = res.Get("name").(string)
user.Avatar = res.Get("picture.data.url").(string)
user.Email = res.Get("email").(string)
}
user, err = core.UserLogin(loginRequest, user)
if err != nil {
log.Printf("Could not login user. Got: %v\n", err)
rw.WriteHeader(http.StatusInternalServerError)
return
}
cookieData := CookieID{Id: user.Id, UserName: user.Name, UserAvatar: user.Avatar}
if err := cookieData.SetCookie(rw); err != nil {
log.Printf("Could not encode cookie. Got: %v\n", err)
rw.WriteHeader(http.StatusInternalServerError)
return
}
http.Redirect(rw, req, "/", http.StatusFound)
}

View File

@@ -10,7 +10,6 @@ import (
"github.com/play-with-docker/play-with-docker/config"
"github.com/play-with-docker/play-with-docker/provisioner"
"github.com/play-with-docker/play-with-docker/recaptcha"
)
type NewSessionResponse struct {
@@ -20,10 +19,16 @@ type NewSessionResponse struct {
func NewSession(rw http.ResponseWriter, req *http.Request) {
req.ParseForm()
if !recaptcha.IsHuman(req, rw) {
// User it not a human
rw.WriteHeader(http.StatusForbidden)
return
userId := ""
if len(config.Providers) > 0 {
cookie, err := ReadCookie(req)
if err != nil {
// User it not a human
rw.WriteHeader(http.StatusForbidden)
return
}
userId = cookie.Id
}
reqDur := req.Form.Get("session-duration")
@@ -45,7 +50,7 @@ func NewSession(rw http.ResponseWriter, req *http.Request) {
}
duration := config.GetDuration(reqDur)
s, err := core.SessionNew(duration, stack, stackName, imageName)
s, err := core.SessionNew(userId, duration, stack, stackName, imageName)
if err != nil {
if provisioner.OutOfCapacity(err) {
http.Redirect(rw, req, "/ooc", http.StatusFound)