122 lines
3.2 KiB
Go
122 lines
3.2 KiB
Go
package auth
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/render"
|
|
"github.com/gorilla/sessions"
|
|
"github.com/umputun/remark/app/store"
|
|
"golang.org/x/oauth2"
|
|
"golang.org/x/oauth2/github"
|
|
)
|
|
|
|
// Github provides oauth2 and session store
|
|
type Github struct {
|
|
*sessions.FilesystemStore
|
|
conf *oauth2.Config
|
|
}
|
|
|
|
// NewGithub makes auth with github
|
|
func NewGithub(p Params) *Github {
|
|
log.Printf("[INFO] create gihub auth, id=%s", p.Cid)
|
|
|
|
conf := oauth2.Config{
|
|
ClientID: p.Cid,
|
|
ClientSecret: p.Csecret,
|
|
RedirectURL: "http://remark.umputun.com:8080/auth/github",
|
|
Scopes: []string{
|
|
"user:email",
|
|
},
|
|
Endpoint: github.Endpoint,
|
|
}
|
|
|
|
return &Github{conf: &conf, FilesystemStore: p.SessionStore}
|
|
}
|
|
|
|
// LoginHandler - GET /login/github
|
|
func (a Github) LoginHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
// make state (random) and store in session
|
|
state := randToken()
|
|
session, err := a.Get(r, "remark")
|
|
if err != nil {
|
|
log.Printf("[WARN] %s", err)
|
|
}
|
|
session.Values["state-github"] = state
|
|
session.Save(r, w)
|
|
|
|
// return login url
|
|
log.Printf("[DEBUG] login url %s", a.conf.AuthCodeURL(state))
|
|
http.Redirect(w, r, a.conf.AuthCodeURL(state), http.StatusTemporaryRedirect)
|
|
}
|
|
|
|
// AuthHandler is redirect url. Should check state to prevent CSRF.
|
|
func (a Github) AuthHandler(w http.ResponseWriter, r *http.Request) {
|
|
session, err := a.Get(r, "remark")
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to get session, %s", err.Error()), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// compare saved state to the one from redirect url
|
|
retrievedState, ok := session.Values["state-github"]
|
|
if !ok || retrievedState != r.URL.Query().Get("state") {
|
|
http.Error(w, fmt.Sprintf("unexpected state %s", retrievedState.(string)), http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
tok, err := a.conf.Exchange(context.Background(), r.URL.Query().Get("code"))
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("exchange failed, %s", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
client := a.conf.Client(context.Background(), tok)
|
|
uinfo, err := client.Get("https://api.github.com/user")
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to get client info, %s", err), http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
defer uinfo.Body.Close()
|
|
data, err := ioutil.ReadAll(uinfo.Body)
|
|
if err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to read user info, %s", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
jData := map[string]interface{}{}
|
|
if e := json.Unmarshal(data, &jData); e != nil {
|
|
http.Error(w, fmt.Sprintf("failed to unmarshal user info, %s", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
log.Printf("[DEBUG] got raw user info %+v", jData)
|
|
|
|
session.Values["uinfo"] = a.makeUserInfo(jData)
|
|
if err = session.Save(r, w); err != nil {
|
|
http.Error(w, fmt.Sprintf("failed to save user info, %s", err), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
log.Printf("[DEBUG] %+v", jData)
|
|
render.JSON(w, r, jData)
|
|
}
|
|
|
|
func (a Github) makeUserInfo(jData map[string]interface{}) store.User {
|
|
userInfo := store.User{
|
|
ID: jData["login"].(string),
|
|
Name: jData["name"].(string),
|
|
Picture: jData["avatar_url"].(string),
|
|
Profile: jData["html_url"].(string),
|
|
}
|
|
if userInfo.Name == "" {
|
|
userInfo.Name = userInfo.ID
|
|
}
|
|
return userInfo
|
|
}
|