/
main.go
148 lines (134 loc) · 4.38 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"net/http"
"os"
"github.com/dghubble/gologin"
"github.com/dghubble/gologin/github"
"github.com/dghubble/sessions"
"golang.org/x/oauth2"
githubOAuth2 "golang.org/x/oauth2/github"
)
const (
sessionName = "example-github-app"
sessionSecret = "example cookie signing secret"
sessionUserKey = "githubID"
)
// sessionStore encodes and decodes session data stored in signed cookies
var sessionStore = sessions.NewCookieStore([]byte(sessionSecret), nil)
// Config configures the main ServeMux.
type Config struct {
GithubClientID string
GithubClientSecret string
}
// New returns a new ServeMux with app routes.
func New(config *Config) *http.ServeMux {
mux := http.NewServeMux()
mux.HandleFunc("/", welcomeHandler)
mux.Handle("/profile", requireLogin(http.HandlerFunc(profileHandler)))
mux.HandleFunc("/logout", logoutHandler)
// 1. Register LoginHandler and CallbackHandler
oauth2Config := &oauth2.Config{
ClientID: config.GithubClientID,
ClientSecret: config.GithubClientSecret,
RedirectURL: "http://localhost:8080/github/callback",
Endpoint: githubOAuth2.Endpoint,
}
// state param cookies require HTTPS by default; disable for localhost development
stateConfig := gologin.DebugOnlyCookieConfig
mux.Handle("/github/login", github.StateHandler(stateConfig, github.LoginHandler(oauth2Config, nil)))
mux.Handle("/github/callback", github.StateHandler(stateConfig, github.CallbackHandler(oauth2Config, issueSession(), nil)))
return mux
}
// issueSession issues a cookie session after successful Github login
func issueSession() http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
ctx := req.Context()
githubUser, err := github.UserFromContext(ctx)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
// 2. Implement a success handler to issue some form of session
session := sessionStore.New(sessionName)
session.Values[sessionUserKey] = *githubUser.ID
session.Save(w)
http.Redirect(w, req, "/profile", http.StatusFound)
}
return http.HandlerFunc(fn)
}
// welcomeHandler shows a welcome message and login button.
func welcomeHandler(w http.ResponseWriter, req *http.Request) {
if req.URL.Path != "/" {
http.NotFound(w, req)
return
}
if isAuthenticated(req) {
http.Redirect(w, req, "/profile", http.StatusFound)
return
}
page, _ := ioutil.ReadFile("home.html")
fmt.Fprintf(w, string(page))
}
// profileHandler shows protected user content.
func profileHandler(w http.ResponseWriter, req *http.Request) {
fmt.Fprint(w, `<p>You are logged in!</p><form action="/logout" method="post"><input type="submit" value="Logout"></form>`)
}
// logoutHandler destroys the session on POSTs and redirects to home.
func logoutHandler(w http.ResponseWriter, req *http.Request) {
if req.Method == "POST" {
sessionStore.Destroy(w, sessionName)
}
http.Redirect(w, req, "/", http.StatusFound)
}
// requireLogin redirects unauthenticated users to the login route.
func requireLogin(next http.Handler) http.Handler {
fn := func(w http.ResponseWriter, req *http.Request) {
if !isAuthenticated(req) {
http.Redirect(w, req, "/", http.StatusFound)
return
}
next.ServeHTTP(w, req)
}
return http.HandlerFunc(fn)
}
// isAuthenticated returns true if the user has a signed session cookie.
func isAuthenticated(req *http.Request) bool {
if _, err := sessionStore.Get(req, sessionName); err == nil {
return true
}
return false
}
// main creates and starts a Server listening.
func main() {
const address = "localhost:8080"
// read credentials from environment variables if available
config := &Config{
GithubClientID: os.Getenv("GITHUB_CLIENT_ID"),
GithubClientSecret: os.Getenv("GITHUB_CLIENT_SECRET"),
}
// allow consumer credential flags to override config fields
clientID := flag.String("client-id", "", "Github Client ID")
clientSecret := flag.String("client-secret", "", "Github Client Secret")
flag.Parse()
if *clientID != "" {
config.GithubClientID = *clientID
}
if *clientSecret != "" {
config.GithubClientSecret = *clientSecret
}
if config.GithubClientID == "" {
log.Fatal("Missing Github Client ID")
}
if config.GithubClientSecret == "" {
log.Fatal("Missing Github Client Secret")
}
log.Printf("Starting Server listening on %s\n", address)
err := http.ListenAndServe(address, New(config))
if err != nil {
log.Fatal("ListenAndServe: ", err)
}
}