Skip to content

Commit 0e3fee9

Browse files
committed
feat: login with Google
Allow users to login with Google Display name of user along with their message in chat room
1 parent 9fb6456 commit 0e3fee9

File tree

11 files changed

+206
-62
lines changed

11 files changed

+206
-62
lines changed

.env-sample

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
GOOGLE_CLIENT_ID=
2+
GOOGLE_CLIENT_SECRET=
3+
GOOGLE_REDIRECT_URL="http://localhost:8080/auth/callback/google"
4+
SECURITY_KEY=<Any key of your choosing>

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
# Ignore build for now
2-
build
2+
build
3+
.env

README.md

+8
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,14 @@ they choose. These are done via different threads(goroutines).
99

1010
This is still largely a WIP, but I'll keep updating it till I'm done
1111

12+
## Enviroment Variables
13+
14+
```
15+
touch .env; cp .env-sample .env
16+
```
17+
18+
Visit Google Cloud Console, navigate to API & Services and click on the Credentials section to get the API key and secret.
19+
1220
## Meta
1321

1422
Orjiewuru Kingdom – [@kingisaac95](https://twitter.com/kingisaac95), [email protected]

auth/auth.go

+45-8
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package auth
22

33
import (
44
"fmt"
5-
"log"
65
"net/http"
76
"strings"
7+
8+
"github.com/stretchr/gomniauth"
9+
"github.com/stretchr/objx"
810
)
911

1012
type authHandler struct {
@@ -37,11 +39,6 @@ func Required(handler http.Handler) http.Handler {
3739

3840
// LoginHandler handles third-party login process
3941
func LoginHandler(w http.ResponseWriter, r *http.Request) {
40-
const (
41-
LOGIN string = "login"
42-
CALLBACK string = "callback"
43-
)
44-
4542
segs := strings.Split(r.URL.Path, "/")
4643

4744
if len(segs) < 4 {
@@ -51,9 +48,49 @@ func LoginHandler(w http.ResponseWriter, r *http.Request) {
5148

5249
action := segs[2]
5350
provider := segs[3]
51+
5452
switch action {
55-
case LOGIN:
56-
log.Println("TODO: handle login for", provider)
53+
case "login":
54+
provider, err := gomniauth.Provider(provider)
55+
if err != nil {
56+
http.Error(w, fmt.Sprintf("Error when trying to get provider %s: %s", provider, err), http.StatusBadRequest)
57+
return
58+
}
59+
60+
loginURL, err := provider.GetBeginAuthURL(nil, nil)
61+
if err != nil {
62+
http.Error(w, fmt.Sprintf("Error when trying to GetBeginAuthURL for %s:%s", provider, err), http.StatusInternalServerError)
63+
return
64+
}
65+
66+
w.Header().Set("Location", loginURL)
67+
w.WriteHeader(http.StatusTemporaryRedirect)
68+
case "callback":
69+
provider, err := gomniauth.Provider(provider)
70+
if err != nil {
71+
http.Error(w, fmt.Sprintf("Error when trying to get provider %s: %s", provider, err), http.StatusBadRequest)
72+
return
73+
}
74+
creds, err := provider.CompleteAuth(objx.MustFromURLQuery(r.URL.RawQuery))
75+
if err != nil {
76+
http.Error(w, fmt.Sprintf("Error when trying to complete auth for %s: %s", provider, err), http.StatusInternalServerError)
77+
return
78+
}
79+
user, err := provider.GetUser(creds)
80+
if err != nil {
81+
http.Error(w, fmt.Sprintf("Error when trying to get user from %s: %s", provider, err), http.StatusInternalServerError)
82+
return
83+
}
84+
authCookieValue := objx.New(map[string]interface{}{
85+
"name": user.Name(),
86+
}).MustBase64()
87+
http.SetCookie(w, &http.Cookie{
88+
Name: "auth",
89+
Value: authCookieValue,
90+
Path: "/",
91+
})
92+
w.Header().Set("Location", "/chat")
93+
w.WriteHeader(http.StatusTemporaryRedirect)
5794
default:
5895
w.WriteHeader(http.StatusNotFound)
5996
fmt.Fprintf(w, "Auth action %s not supported", action)

chat/client.go

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package chat
22

33
import (
4+
"time"
5+
46
"github.com/gorilla/websocket"
57
)
68

@@ -9,26 +11,31 @@ type client struct {
911
// socket: the web socket for this client
1012
socket *websocket.Conn
1113
// send: channel on which the messages are sent
12-
send chan []byte
14+
send chan *message
1315
// room: the room this client is chatting in
1416
room *room
17+
// userData holds information about the user
18+
userData map[string]interface{}
1519
}
1620

1721
func (c *client) read() {
1822
defer c.socket.Close()
1923
for {
20-
_, msg, err := c.socket.ReadMessage()
24+
var msg *message
25+
err := c.socket.ReadJSON(&msg)
2126
if err != nil {
2227
return
2328
}
29+
msg.When = time.Now()
30+
msg.Name = c.userData["name"].(string)
2431
c.room.forward <- msg
2532
}
2633
}
2734

2835
func (c *client) write() {
2936
defer c.socket.Close()
3037
for msg := range c.send {
31-
err := c.socket.WriteMessage(websocket.TextMessage, msg)
38+
err := c.socket.WriteJSON(msg)
3239
if err != nil {
3340
return
3441
}

chat/message.go

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package chat
2+
3+
import "time"
4+
5+
type message struct {
6+
Name string
7+
Message string
8+
When time.Time
9+
}

chat/room.go

+16-7
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ package chat
22

33
import (
44
"chat_app/trace"
5-
"github.com/gorilla/websocket"
65
"log"
76
"net/http"
7+
8+
"github.com/gorilla/websocket"
9+
"github.com/stretchr/objx"
810
)
911

1012
type room struct {
1113
// forward: a channel that holds incoming messages
1214
// that should be forwarded to the other clients.
13-
forward chan []byte
15+
forward chan *message
1416
// join: a channel for clients wishing to join the room
1517
join chan *client
1618
// leave: a channel for clients wishing to leave the room
@@ -24,7 +26,7 @@ type room struct {
2426
// NewRoom helper for creating rooms
2527
func NewRoom() *room {
2628
return &room{
27-
forward: make(chan []byte),
29+
forward: make(chan *message),
2830
join: make(chan *client),
2931
leave: make(chan *client),
3032
clients: make(map[*client]bool),
@@ -45,7 +47,7 @@ func (r *room) Run() {
4547
close(client.send)
4648
r.Tracer.Trace("Client left.")
4749
case msg := <-r.forward:
48-
r.Tracer.Trace("Message received: ", string(msg))
50+
r.Tracer.Trace("Message received: ", msg.Message)
4951
for client := range r.clients {
5052
client.send <- msg
5153
r.Tracer.Trace(" -- sent to client.")
@@ -73,11 +75,18 @@ func (r *room) ServeHTTP(w http.ResponseWriter, req *http.Request) {
7375
log.Fatal("ServeHTTP:", err)
7476
return
7577
}
78+
79+
authCookie, err := req.Cookie("auth")
80+
if err != nil {
81+
log.Fatal("Failed to get auth cookie:", err)
82+
return
83+
}
7684
// create a client from the socket
7785
client := &client{
78-
socket: socket,
79-
send: make(chan []byte, messageBufferSize),
80-
room: r,
86+
socket: socket,
87+
send: make(chan *message, messageBufferSize),
88+
room: r,
89+
userData: objx.MustFromBase64(authCookie.Value),
8190
}
8291
// add the client to the current room
8392
r.join <- client

go.mod

+13-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,16 @@ module chat_app
22

33
go 1.13
44

5-
require github.com/gorilla/websocket v1.4.1
5+
require (
6+
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec // indirect
7+
github.com/gorilla/websocket v1.4.1
8+
github.com/joho/godotenv v1.3.0
9+
github.com/stretchr/codecs v0.0.0-20170403063245-04a5b1e1910d // indirect
10+
github.com/stretchr/gomniauth v0.0.0-20170717123514-4b6c822be2eb
11+
github.com/stretchr/objx v0.2.0
12+
github.com/stretchr/signature v0.0.0-20160104132143-168b2a1e1b56 // indirect
13+
github.com/stretchr/stew v0.0.0-20130812190256-80ef0842b48b // indirect
14+
github.com/stretchr/tracer v0.0.0-20140124184152-66d3696bba97 // indirect
15+
github.com/ugorji/go v1.1.7 // indirect
16+
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
17+
)

go.sum

+27
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,29 @@
1+
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec h1:EdRZT3IeKQmfCSrgo8SZ8V3MEnskuJP0wCYNpe+aiXo=
2+
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
3+
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
15
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
26
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
7+
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
8+
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
9+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
10+
github.com/stretchr/codecs v0.0.0-20170403063245-04a5b1e1910d h1:gXQ+QS3q874pcayiqszimfHPQ7ySFcekgzBMoTaVawk=
11+
github.com/stretchr/codecs v0.0.0-20170403063245-04a5b1e1910d/go.mod h1:RpfDhdqip2BYhzoE4esKm8axH5VywpvMW9o3wfcamek=
12+
github.com/stretchr/gomniauth v0.0.0-20170717123514-4b6c822be2eb h1:6lYIg/SCrz3gsCsEpRpK0BW3tBGt4VuQKlAleoxCgCc=
13+
github.com/stretchr/gomniauth v0.0.0-20170717123514-4b6c822be2eb/go.mod h1:5wVraTCYvqbNMRjmSIIcrJMASXdoHeXm8j8AON7WobA=
14+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
15+
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
16+
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
17+
github.com/stretchr/signature v0.0.0-20160104132143-168b2a1e1b56 h1:BTR9AeovoABP8KnaBkzNtp7y/+x1n5GbOHwp3QisE1k=
18+
github.com/stretchr/signature v0.0.0-20160104132143-168b2a1e1b56/go.mod h1:p8v7xBdwApv7pgPN+8jQ3LpBQJDAusrtE+YBWBbab9Q=
19+
github.com/stretchr/stew v0.0.0-20130812190256-80ef0842b48b h1:DmfFjW6pLdaJNVHfKgCxTdKFI6tM+0YbMd0kx7kE78s=
20+
github.com/stretchr/stew v0.0.0-20130812190256-80ef0842b48b/go.mod h1:yS/5aMz+lfJhykLjlAGbnhUhZIvVapOvtmk0MtzHktE=
21+
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
22+
github.com/stretchr/tracer v0.0.0-20140124184152-66d3696bba97 h1:ZXZ3Ko4supnaInt/pSZnq3QL65Qx/KSZTUPMJH5RlIk=
23+
github.com/stretchr/tracer v0.0.0-20140124184152-66d3696bba97/go.mod h1:H0mYc1JTiYc9K0keLMYcR2ybyeom20X4cOYrKya1M1Y=
24+
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
25+
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
26+
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
27+
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
28+
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
29+
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=

main.go

+26-1
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,15 @@ import (
66
"flag"
77
"log"
88
"net/http"
9+
"os"
910
"path/filepath"
1011
"sync"
1112
"text/template"
13+
14+
"github.com/joho/godotenv"
15+
"github.com/stretchr/gomniauth"
16+
"github.com/stretchr/gomniauth/providers/google"
17+
"github.com/stretchr/objx"
1218
)
1319

1420
// templ is a single template
@@ -22,13 +28,32 @@ func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
2228
t.once.Do(func() {
2329
t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)))
2430
})
25-
t.templ.Execute(w, r)
31+
data := map[string]interface{}{
32+
"Host": r.Host,
33+
}
34+
if authCookie, err := r.Cookie("auth"); err == nil {
35+
data["UserData"] = objx.MustFromBase64(authCookie.Value)
36+
}
37+
t.templ.Execute(w, data)
2638
}
2739

2840
func main() {
2941
var port = flag.String("port", ":8080", "The port where our application runs.")
3042
flag.Parse() // parse flags and extract appropriate information
3143

44+
err := godotenv.Load()
45+
if err != nil {
46+
log.Fatal("Error loading .env file")
47+
}
48+
49+
googleClientID := os.Getenv("GOOGLE_CLIENT_ID")
50+
googleClientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")
51+
googleRedirectURL := os.Getenv("GOOGLE_REDIRECT_URL")
52+
securityKey := os.Getenv("SECURITY_KEY")
53+
54+
gomniauth.SetSecurityKey(securityKey)
55+
gomniauth.WithProviders(google.New(googleClientID, googleClientSecret, googleRedirectURL))
56+
3257
// new room setup
3358
r := chat.NewRoom()
3459

0 commit comments

Comments
 (0)