Skip to content

Commit

Permalink
chore: add github OAuth (#546)
Browse files Browse the repository at this point in the history
* add: pull button UI + param

* add: fetch feature

* fix: lint forgotten comment

* update: dvstore's unit tests

* refacto internal/dvserver/api.go

Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>

* fix: remove debug

Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>

* update: refacto internal/dvserver/api.go

Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>

* update: add error handling "internal/dvserver/api.go"

* fix: lint

* add: GitHub OAuth

* fix: remove .env data

* update: add new page to handle OAuth smoothly

Co-authored-by: Manfred Touron <94029+moul@users.noreply.github.com>
  • Loading branch information
Doozers and moul committed Aug 4, 2022
1 parent 1b4bacd commit 3f7f905
Show file tree
Hide file tree
Showing 25 changed files with 12,926 additions and 58 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Eugene Sysmanov <appigram@gmail.com>
Evgeny Sysmanov <appigram@gmail.com>
ImgBotApp <ImgBotHelp@gmail.com>
Isma <71719097+Doozers@users.noreply.github.com>
ismael FALL <ismael.fall@epitech.eu>
Lucas Germano <lucasgdev@gmail.com>
Manfred Touron <94029+moul@users.noreply.github.com>
Expand Down
1 change: 1 addition & 0 deletions api/dvserver.proto
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ message Graph {
bool without_isolated = 3;
bool without_prs = 4 [(gogoproto.customname) = "WithoutPRs"];
bool without_external_deps = 5;
bool with_fetch = 6;
}
message Output {
repeated depviz.model.Task tasks = 1;
Expand Down
4 changes: 4 additions & 0 deletions cmd/depviz/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ var (
serverAuth = serverFlags.String("auth", "", "authentication password")
serverRealm = serverFlags.String("realm", "DepViz", "server Realm")
serverAutoUpdateInterval = serverFlags.Duration("auto-update-interval", 2*time.Minute, "time between two auto-updates") // nolint:gomnd
serverGitHubClientID = serverFlags.String("github-client-id", "", "GitHub client ID")
serverGitHubClientSecret = serverFlags.String("github-client-secret", "", "GitHub client secret")

runFlags = flag.NewFlagSet("run", flag.ExitOnError)
runNoPull = runFlags.Bool("no-pull", false, "don't pull providers (graph only)")
Expand Down Expand Up @@ -315,6 +317,8 @@ func execServer(ctx context.Context, args []string) error {
NoAutoUpdate: *serverNoAutoUpdate,
AutoUpdateTargets: targets,
AutoUpdateInterval: *serverAutoUpdateInterval,
GitHubClientID: *serverGitHubClientID,
GitHubClientSecret: *serverGitHubClientSecret,
}
svc, err = dvserver.New(ctx, store, schemaConfig, opts)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions gen.sum

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

112 changes: 111 additions & 1 deletion internal/dvserver/api.go
Original file line number Diff line number Diff line change
@@ -1,18 +1,119 @@
package dvserver

import (
"bytes"
"context"
"encoding/base64"
"fmt"
"io/ioutil"
"net/http"

"go.uber.org/zap"
"google.golang.org/grpc/metadata"
"moul.io/depviz/v3/internal/dvcore"
"moul.io/depviz/v3/internal/dvmodel"
"moul.io/depviz/v3/internal/dvparser"
"moul.io/depviz/v3/internal/dvstore"
)

func gitHubOAuth(opts Opts, httpLogger *zap.Logger) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if opts.GitHubClientID == "" || opts.GitHubClientSecret == "" {
httpLogger.Error("GitHub OAuth: missing client ID or secret")
http.Error(w, "missing client ID or secret", http.StatusInternalServerError)
return
}

code, err := ioutil.ReadAll(r.Body)
if err != nil {
httpLogger.Error("get body", zap.Error(err))
http.Error(w, "failed to retrieve body", http.StatusInternalServerError)
return
}

if len(code) < 1 {
httpLogger.Error("Url Param 'code' is missing")
http.Error(w, "Url Param 'code' is missing", http.StatusBadRequest)
return
}
httpLogger.Info("github code received successfully")

// maybe switch to env variables
data := []byte(fmt.Sprintf(`{
"client_id": "%s",
"client_secret": "%s",
"code": "%s"
}`, opts.GitHubClientID, opts.GitHubClientSecret, string(code)))
req, err := http.NewRequestWithContext(context.Background(), "POST", "https://github.com/login/oauth/access_token", bytes.NewBuffer(data))
if err != nil {
httpLogger.Error("create request", zap.Error(err))
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}

req.Header.Add("Content-Type", "application/json")
req.Header.Add("Accept", "application/json")

gitHubResponse, err := http.DefaultClient.Do(req)
if err != nil {
httpLogger.Error("request token to github", zap.Error(err))
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}

defer gitHubResponse.Body.Close()

token, err := ioutil.ReadAll(gitHubResponse.Body)
if err != nil {
httpLogger.Error("get body", zap.Error(err))
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}

_, err = w.Write(token)
if err != nil {
httpLogger.Error("write response", zap.Error(err))
http.Error(w, "internal server error", http.StatusInternalServerError)
return
}
}
}

func getToken(ctx context.Context) (string, error) {
md, _ := metadata.FromIncomingContext(ctx)

var gitHubToken string
if md["authorization"] == nil {
return "", nil
}
// skip "Basic"
if len(md["authorization"][0]) <= len("Basic ") {
return "", fmt.Errorf("invalid authorization header")
}
gitHubToken = md["authorization"][1][6:]
// prevent empty token (skip prefix)
if gitHubToken == base64.StdEncoding.EncodeToString([]byte("depviz:")) {
return gitHubToken, nil
}
bytesGithubToken, err := base64.StdEncoding.DecodeString(gitHubToken)
if err != nil {
return "", fmt.Errorf("invalid github token: %w", err)
}
// len("depviz:") = 6
gitHubToken = string(bytesGithubToken[7:])

return gitHubToken, nil
}

func (s *service) Graph(ctx context.Context, in *Graph_Input) (*Graph_Output, error) {
s.opts.Logger.Debug("graph", zap.Any("in", in))

// retrieve token
gitHubToken, err := getToken(ctx)
if err != nil {
return nil, fmt.Errorf("get token: %w", err)
}

// validation
if len(in.Targets) == 0 {
return nil, fmt.Errorf("targets is required")
Expand All @@ -23,6 +124,7 @@ func (s *service) Graph(ctx context.Context, in *Graph_Input) (*Graph_Output, er
WithoutIsolated: in.WithoutIsolated,
WithoutPRs: in.WithoutPRs,
WithoutExternalDeps: in.WithoutExternalDeps,
WithFetch: in.WithFetch,
}
if len(in.Targets) == 1 && in.Targets[0] == "world" {
filters.TheWorld = true
Expand All @@ -35,7 +137,15 @@ func (s *service) Graph(ctx context.Context, in *Graph_Input) (*Graph_Output, er
}

// load tasks
tasks, err := dvstore.LoadTasks(s.h, s.schema, filters, s.opts.Logger)
if filters.WithFetch && gitHubToken != "" {
_, err := dvcore.PullAndSave(filters.Targets, s.h, s.schema, gitHubToken, false, s.opts.Logger)
if err != nil {
return nil, fmt.Errorf("pull: %w", err)
}
}

var tasks dvmodel.Tasks
tasks, err = dvstore.LoadTasks(s.h, s.schema, filters, s.opts.Logger)
if err != nil {
return nil, fmt.Errorf("load tasks: %w", err)
}
Expand Down
118 changes: 80 additions & 38 deletions internal/dvserver/dvserver.pb.go

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

14 changes: 14 additions & 0 deletions internal/dvserver/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ type Opts struct {
NoAutoUpdate bool
AutoUpdateTargets []multipmuri.Entity
AutoUpdateInterval time.Duration
GitHubClientID string
GitHubClientSecret string
}

type Service interface {
Expand Down Expand Up @@ -173,6 +175,14 @@ func New(ctx context.Context, h *cayley.Handle, schema *schema.Config, opts Opts
OrigName: true,
}),
runtime.WithProtoErrorHandler(runtime.DefaultHTTPProtoErrorHandler),
runtime.WithIncomingHeaderMatcher(func(key string) (string, bool) {
switch key {
case "Authorization":
return key, true
default:
return key, false
}
}),
)
grpcOpts := []grpc.DialOption{grpc.WithInsecure()}
if err := RegisterDepvizServiceHandlerFromEndpoint(ctx, gwmux, svc.grpcListenerAddr, grpcOpts); err != nil {
Expand All @@ -195,6 +205,9 @@ func New(ctx context.Context, h *cayley.Handle, schema *schema.Config, opts Opts
r.Mount("/", http.StripPrefix("/api", handler))
})

// OAuth2 GitHub
r.Post("/token", gitHubOAuth(opts, httpLogger))

// pprof endpoints
if opts.WithPprof {
r.HandleFunc("/debug/pprof/*", pprof.Index)
Expand All @@ -210,6 +223,7 @@ func New(ctx context.Context, h *cayley.Handle, schema *schema.Config, opts Opts

// pages
r.Get("/", homepage(box, opts))
r.Get("/githubOAuth", homepage(box, opts))

http.DefaultServeMux = http.NewServeMux() // disables default handlers registere by importing net/http/pprof for security reasons
listener, err := net.Listen("tcp", opts.HTTPBind)
Expand Down
1 change: 1 addition & 0 deletions internal/dvstore/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type LoadTasksFilters struct {
WithoutIsolated bool
WithoutPRs bool
WithoutExternalDeps bool
WithFetch bool
}

func LoadTasks(h *cayley.Handle, schema *schema.Config, filters LoadTasksFilters, logger *zap.Logger) (dvmodel.Tasks, error) {
Expand Down
Loading

0 comments on commit 3f7f905

Please sign in to comment.