Skip to content

Commit

Permalink
feat: oauth flow in progress
Browse files Browse the repository at this point in the history
Signed-off-by: Manfred Touron <94029+moul@users.noreply.github.com>
  • Loading branch information
moul committed Jul 6, 2020
1 parent 7e68c39 commit d2c71ef
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 6 deletions.
2 changes: 2 additions & 0 deletions go.mod

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

2 changes: 2 additions & 0 deletions go.sum

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

118 changes: 115 additions & 3 deletions pkg/sgtm/auth.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,123 @@
package sgtm

import (
"context"
"crypto/sha256"
"encoding/base64"
"fmt"
"net/http"

"github.com/moogar0880/problems"
"go.uber.org/zap"
"golang.org/x/oauth2"
"moul.io/godev"
)

var (
invalidStateProblem = problems.NewDetailedProblem(http.StatusBadRequest, "invalid state")
codeExchangeProblem = problems.NewDetailedProblem(http.StatusInternalServerError, "oauth code exchange")
// internalProblem = problems.NewDetailedProblem(http.StatusInternalServerError, "internal problem")
)

const (
oauthTokenCookie = "oauth-token"
)

func httpAuthCallback(w http.ResponseWriter, r *http.Request) {
code := r.URL.Query().Get("code")
fmt.Fprintf(w, "yo%s !\n", code)
func (svc *Service) httpAuthLogin(w http.ResponseWriter, r *http.Request) {
conf := svc.authConfigFromReq(r)
state := svc.authGenerateState(r)
http.Redirect(w, r, conf.AuthCodeURL(state), http.StatusTemporaryRedirect)
}

func (svc *Service) httpAuthLogout(w http.ResponseWriter, r *http.Request) {
cookie := http.Cookie{
Name: oauthTokenCookie,
}
http.SetCookie(w, &cookie)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}

func (svc *Service) httpAuthCallback(w http.ResponseWriter, r *http.Request) {
conf := svc.authConfigFromReq(r)

// verifiy oauth2 state
{
got := r.URL.Query().Get("state")
expected := svc.authGenerateState(r)
if expected != got {
svc.logger.Warn("invalid oauth2 state", zap.String("expected", expected), zap.String("got", got))
problems.StatusProblemHandler(invalidStateProblem)(w, r)
return
}
}

// exchange the code
var token *oauth2.Token
{
code := r.URL.Query().Get("code")
var err error
token, err = conf.Exchange(context.Background(), code)
if err != nil {
svc.logger.Warn("code exchange failed", zap.Error(err))
problems.StatusProblemHandler(codeExchangeProblem)(w, r)
return
}
cookie := http.Cookie{
Name: oauthTokenCookie,
Value: token.AccessToken,
Expires: token.Expiry,
HttpOnly: true,
Path: "/",
//Domain: r.Host,
}
fmt.Println(godev.PrettyJSON(cookie))
http.SetCookie(w, &cookie)
http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}

// FIXME: configure jwt, embed username, email, create account if not exists, set roles in jwt
/*
// get user's info
{
res, err := conf.Client(context.Background(), token).Get("https://discordapp.com/api/v6/users/@me")
if err != nil {
svc.logger.Warn("init discord client", zap.Error(err))
problems.StatusProblemHandler(internalProblem)(w, r)
return
}
defer res.Body.Close()
body, err := ioutil.ReadAll(res.Body)
if err != nil {
svc.logger.Warn("init discord client", zap.Error(err))
problems.StatusProblemHandler(internalProblem)(w, r)
return
}
_, _ = w.Write(body)
}
*/
}

func (svc *Service) authConfigFromReq(r *http.Request) *oauth2.Config {
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
hostname := fmt.Sprintf("%s://%s", scheme, r.Host)
return &oauth2.Config{
Endpoint: oauth2.Endpoint{
AuthURL: "https://discordapp.com/api/oauth2/authorize",
TokenURL: "https://discordapp.com/api/oauth2/token",
AuthStyle: oauth2.AuthStyleInParams,
},
Scopes: []string{"identify", "email"},
RedirectURL: hostname + "/auth/callback",
ClientID: svc.opts.DiscordClientID,
ClientSecret: svc.opts.DiscordClientSecret,
}
}

func (svc *Service) authGenerateState(r *http.Request) string {
// FIXME: add IP too?
csum := sha256.Sum256([]byte(r.UserAgent() + svc.opts.DiscordClientSecret))
return base64.StdEncoding.EncodeToString(csum[:])
}
12 changes: 9 additions & 3 deletions pkg/sgtm/driver_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,12 +185,16 @@ func (svc *Service) httpServer() (*http.Server, error) {
tmpl := template.Must(template.New("index").Parse(src))
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
data := struct {
Title string
Date time.Time
Title string
Date time.Time
OAuthToken string
}{
Title: "SGTM",
Date: time.Now(),
}
if cookie, err := r.Cookie(oauthTokenCookie); err == nil {
data.OAuthToken = cookie.Value
}
if err := tmpl.Execute(w, data); err != nil {
svc.logger.Warn("failed to reply", zap.Error(err))
}
Expand All @@ -199,7 +203,9 @@ func (svc *Service) httpServer() (*http.Server, error) {

// auth
{
r.Get("/auth/callback", httpAuthCallback)
r.Get("/login", svc.httpAuthLogin)
r.Get("/logout", svc.httpAuthLogout)
r.Get("/auth/callback", svc.httpAuthCallback)
}

// static files & 404
Expand Down
2 changes: 2 additions & 0 deletions pkg/sgtm/opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type Opts struct {
Context context.Context
Logger *zap.Logger
DevMode bool
Seed string

/// Discord

Expand All @@ -21,6 +22,7 @@ type Opts struct {
DiscordClientSecret string

/// DB

DBPath string

/// Server
Expand Down
7 changes: 7 additions & 0 deletions static/index.tmpl.html
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,12 @@
<body>
<h1>SGTM</h1>
<h2>Date: {{.Date}}</h2>
{{if .OAuthToken}}
<hr />
<div>Token: {{.OAuthToken}}</div>
<div><a href="/logout">Logout</a></div>
{{else}}
<div><a href="/login">Login</a></div>
{{end}}
</body>
</html>

0 comments on commit d2c71ef

Please sign in to comment.