diff --git a/main.go b/main.go
index 433f778..3393ec0 100644
--- a/main.go
+++ b/main.go
@@ -691,6 +691,7 @@ func onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
// Find the collection for the command we got
for _, coll := range COLLECTIONS {
if scontains(parts[0], coll.Commands...) {
+ go deleteCommandMessage(s, m.ChannelID, m.ID)
// If they passed a specific sound effect, find and select that (otherwise play nothing)
var sound *Sound
@@ -707,7 +708,6 @@ func onMessageCreate(s *discordgo.Session, m *discordgo.MessageCreate) {
}
go enqueuePlay(m.Author, guild, coll, sound)
- go deleteCommandMessage(s, m.ChannelID, m.ID)
return
}
}
diff --git a/static/css/style.css b/static/css/style.css
new file mode 100644
index 0000000..dda9193
--- /dev/null
+++ b/static/css/style.css
@@ -0,0 +1,71 @@
+/* http://meyerweb.com/eric/tools/css/reset/
+ v2.0 | 20110126
+ License: none (public domain)
+*/
+
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ margin: 0;
+ padding: 0;
+ border: 0;
+ font-size: 100%;
+ font: inherit;
+ vertical-align: baseline;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+a {
+ text-decoration: none;
+}
+
+a:hover {
+ text-decoration: underline;
+}
+
+body {
+ font-size: 13px;
+ font-family: 'Open Sans', sans-serif;
+ padding: 0 30px;
+}
+
+header {
+ padding: 30px 0;
+ font-size: 15px;
+}
+
+header h1 {
+ font-size: 20px;
+}
diff --git a/templates/home.html b/templates/home.html
new file mode 100644
index 0000000..248a28f
--- /dev/null
+++ b/templates/home.html
@@ -0,0 +1,4 @@
+{{ define "title"}}
Example{{ end }}
+{{ define "content" }}
+Login with Discord
+{{ end }}
diff --git a/templates/layout.html b/templates/layout.html
new file mode 100644
index 0000000..81496a3
--- /dev/null
+++ b/templates/layout.html
@@ -0,0 +1,17 @@
+{{ define "base" }}
+
+
+
+
+ {{template "title" . }}
+
+
+
+
+ {{ template "content" . }}
+
+
+
+{{ end }}
diff --git a/webserver.go b/webserver.go
index 6265db1..46a7282 100644
--- a/webserver.go
+++ b/webserver.go
@@ -1,58 +1,103 @@
package main
import (
+ "crypto/rand"
+ "encoding/base64"
"fmt"
+ "html/template"
"net/http"
"github.com/bwmarrin/discordgo"
+ "github.com/gorilla/mux"
+ "github.com/gorilla/sessions"
"golang.org/x/oauth2"
)
-const htmlIndex = `
-Log in with discord
-
-`
+const (
+ defaultLayout = "templates/layout.html"
+ templateDir = "templates/"
+)
var (
discordOauthConfig = &oauth2.Config{
RedirectURL: "",
ClientID: "",
ClientSecret: "",
- Scopes: []string{"connections",
- "email",
- "identify",
- "guilds"},
- Endpoint: *endp,
+ Scopes: []string{"identify", "guilds"},
+ Endpoint: *endp,
}
// Some random string, random for each request
- oauthStateString = "random"
+ oauthStateString string
endp = &oauth2.Endpoint{
AuthURL: "https://discordapp.com/api/oauth2/authorize",
TokenURL: "https://discordapp.com/api/oauth2/token",
}
+ tmpls = map[string]*template.Template{}
+ // TODO change secret
+ store *sessions.CookieStore
)
-// startWebServer with the port provided
+// startWebServer
func startWebServer(port string, ci string, cs string, redirectURL string) {
+ tmpls["home.html"] = template.Must(template.ParseFiles(templateDir+"home.html", defaultLayout))
+ store = sessions.NewCookieStore([]byte(cs))
discordOauthConfig.ClientID = ci
discordOauthConfig.ClientSecret = cs
discordOauthConfig.RedirectURL = redirectURL + "/discordCallback"
- http.HandleFunc("/", handleMain)
- http.HandleFunc("/discordLogin", handlediscordLogin)
- http.HandleFunc("/discordCallback", handlediscordCallback)
- fmt.Println(http.ListenAndServe(":"+port, nil))
+
+ r := mux.NewRouter()
+ r.HandleFunc("/", handleMain)
+ r.HandleFunc("/logout", handleLogout)
+ r.HandleFunc("/discordLogin", handlediscordLogin)
+ r.HandleFunc("/discordCallback", handlediscordCallback)
+
+ http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static"))))
+ http.Handle("/", r)
+ http.ListenAndServe(":"+port, nil)
}
func handleMain(w http.ResponseWriter, r *http.Request) {
- fmt.Fprintf(w, htmlIndex)
+ session, _ := store.Get(r, "session-name")
+ if session.Values["discordUsername"] != nil {
+ fmt.Fprintf(w, "you are logged in as %s. logout", session.Values["discordUsername"])
+ return
+ }
+ tmpls["home.html"].ExecuteTemplate(w, "base", map[string]interface{}{})
+}
+func handleLogout(w http.ResponseWriter, r *http.Request) {
+ cookie := &http.Cookie{
+ Name: "session-name",
+ Value: "",
+ Path: "/",
+ MaxAge: -1,
+ }
+ http.SetCookie(w, cookie)
+ http.Redirect(w, r, "/", http.StatusFound)
}
-
func handlediscordLogin(w http.ResponseWriter, r *http.Request) {
+ b := make([]byte, 16)
+ rand.Read(b)
+ oauthStateString = base64.URLEncoding.EncodeToString(b)
+
+ session, _ := store.Get(r, "session-name")
+ session.Values["state"] = oauthStateString
+ session.Save(r, w)
+
url := discordOauthConfig.AuthCodeURL(oauthStateString)
http.Redirect(w, r, url, http.StatusTemporaryRedirect)
}
func handlediscordCallback(w http.ResponseWriter, r *http.Request) {
+ session, err := store.Get(r, "session-name")
+ if err != nil {
+ fmt.Fprintln(w, "aborted")
+ return
+ }
+ if r.URL.Query().Get("state") != session.Values["state"] {
+ fmt.Fprintln(w, "no state match; possible csrf OR cookies not enabled")
+ return
+ }
+
state := r.FormValue("state")
if state != oauthStateString {
fmt.Printf("invalid oauth state, expected '%s', got '%s'\n", oauthStateString, state)
@@ -60,11 +105,14 @@ func handlediscordCallback(w http.ResponseWriter, r *http.Request) {
return
}
- code := r.FormValue("code")
- token, err := discordOauthConfig.Exchange(oauth2.NoContext, code)
+ token, err := discordOauthConfig.Exchange(oauth2.NoContext, r.FormValue("code"))
if err != nil {
- fmt.Println("Code exchange failed with ", err)
- http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
+ fmt.Fprintln(w, "Code exchange (could not get token) failed with ", err)
+ return
+ }
+
+ if !token.Valid() {
+ fmt.Fprintln(w, "retrieved invalid token")
return
}
@@ -74,5 +122,11 @@ func handlediscordCallback(w http.ResponseWriter, r *http.Request) {
return
}
user, _ := dg.User("@me")
- fmt.Fprintf(w, user.Username)
+
+ session.Values["discordUserID"] = user.ID
+ session.Values["discordUsername"] = user.Username
+ session.Values["accessToken"] = token.AccessToken
+ session.Save(r, w)
+
+ http.Redirect(w, r, "/", http.StatusTemporaryRedirect)
}