Skip to content

Commit

Permalink
feat: login with Google
Browse files Browse the repository at this point in the history
Allow users to login with Google
Display name of user along with their message in chat room
  • Loading branch information
kingisaac95 committed Apr 7, 2020
1 parent 9fb6456 commit 0e3fee9
Show file tree
Hide file tree
Showing 11 changed files with 206 additions and 62 deletions.
4 changes: 4 additions & 0 deletions .env-sample
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URL="http://localhost:8080/auth/callback/google"
SECURITY_KEY=<Any key of your choosing>
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Ignore build for now
build
build
.env
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@ they choose. These are done via different threads(goroutines).

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

## Enviroment Variables

```
touch .env; cp .env-sample .env
```

Visit Google Cloud Console, navigate to API & Services and click on the Credentials section to get the API key and secret.

## Meta

Orjiewuru Kingdom – [@kingisaac95](https://twitter.com/kingisaac95), [email protected]
Expand Down
53 changes: 45 additions & 8 deletions auth/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package auth

import (
"fmt"
"log"
"net/http"
"strings"

"github.com/stretchr/gomniauth"
"github.com/stretchr/objx"
)

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

// LoginHandler handles third-party login process
func LoginHandler(w http.ResponseWriter, r *http.Request) {
const (
LOGIN string = "login"
CALLBACK string = "callback"
)

segs := strings.Split(r.URL.Path, "/")

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

action := segs[2]
provider := segs[3]

switch action {
case LOGIN:
log.Println("TODO: handle login for", provider)
case "login":
provider, err := gomniauth.Provider(provider)
if err != nil {
http.Error(w, fmt.Sprintf("Error when trying to get provider %s: %s", provider, err), http.StatusBadRequest)
return
}

loginURL, err := provider.GetBeginAuthURL(nil, nil)
if err != nil {
http.Error(w, fmt.Sprintf("Error when trying to GetBeginAuthURL for %s:%s", provider, err), http.StatusInternalServerError)
return
}

w.Header().Set("Location", loginURL)
w.WriteHeader(http.StatusTemporaryRedirect)
case "callback":
provider, err := gomniauth.Provider(provider)
if err != nil {
http.Error(w, fmt.Sprintf("Error when trying to get provider %s: %s", provider, err), http.StatusBadRequest)
return
}
creds, err := provider.CompleteAuth(objx.MustFromURLQuery(r.URL.RawQuery))
if err != nil {
http.Error(w, fmt.Sprintf("Error when trying to complete auth for %s: %s", provider, err), http.StatusInternalServerError)
return
}
user, err := provider.GetUser(creds)
if err != nil {
http.Error(w, fmt.Sprintf("Error when trying to get user from %s: %s", provider, err), http.StatusInternalServerError)
return
}
authCookieValue := objx.New(map[string]interface{}{
"name": user.Name(),
}).MustBase64()
http.SetCookie(w, &http.Cookie{
Name: "auth",
Value: authCookieValue,
Path: "/",
})
w.Header().Set("Location", "/chat")
w.WriteHeader(http.StatusTemporaryRedirect)
default:
w.WriteHeader(http.StatusNotFound)
fmt.Fprintf(w, "Auth action %s not supported", action)
Expand Down
13 changes: 10 additions & 3 deletions chat/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package chat

import (
"time"

"github.com/gorilla/websocket"
)

Expand All @@ -9,26 +11,31 @@ type client struct {
// socket: the web socket for this client
socket *websocket.Conn
// send: channel on which the messages are sent
send chan []byte
send chan *message
// room: the room this client is chatting in
room *room
// userData holds information about the user
userData map[string]interface{}
}

func (c *client) read() {
defer c.socket.Close()
for {
_, msg, err := c.socket.ReadMessage()
var msg *message
err := c.socket.ReadJSON(&msg)
if err != nil {
return
}
msg.When = time.Now()
msg.Name = c.userData["name"].(string)
c.room.forward <- msg
}
}

func (c *client) write() {
defer c.socket.Close()
for msg := range c.send {
err := c.socket.WriteMessage(websocket.TextMessage, msg)
err := c.socket.WriteJSON(msg)
if err != nil {
return
}
Expand Down
9 changes: 9 additions & 0 deletions chat/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package chat

import "time"

type message struct {
Name string
Message string
When time.Time
}
23 changes: 16 additions & 7 deletions chat/room.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ package chat

import (
"chat_app/trace"
"github.com/gorilla/websocket"
"log"
"net/http"

"github.com/gorilla/websocket"
"github.com/stretchr/objx"
)

type room struct {
// forward: a channel that holds incoming messages
// that should be forwarded to the other clients.
forward chan []byte
forward chan *message
// join: a channel for clients wishing to join the room
join chan *client
// leave: a channel for clients wishing to leave the room
Expand All @@ -24,7 +26,7 @@ type room struct {
// NewRoom helper for creating rooms
func NewRoom() *room {
return &room{
forward: make(chan []byte),
forward: make(chan *message),
join: make(chan *client),
leave: make(chan *client),
clients: make(map[*client]bool),
Expand All @@ -45,7 +47,7 @@ func (r *room) Run() {
close(client.send)
r.Tracer.Trace("Client left.")
case msg := <-r.forward:
r.Tracer.Trace("Message received: ", string(msg))
r.Tracer.Trace("Message received: ", msg.Message)
for client := range r.clients {
client.send <- msg
r.Tracer.Trace(" -- sent to client.")
Expand Down Expand Up @@ -73,11 +75,18 @@ func (r *room) ServeHTTP(w http.ResponseWriter, req *http.Request) {
log.Fatal("ServeHTTP:", err)
return
}

authCookie, err := req.Cookie("auth")
if err != nil {
log.Fatal("Failed to get auth cookie:", err)
return
}
// create a client from the socket
client := &client{
socket: socket,
send: make(chan []byte, messageBufferSize),
room: r,
socket: socket,
send: make(chan *message, messageBufferSize),
room: r,
userData: objx.MustFromBase64(authCookie.Value),
}
// add the client to the current room
r.join <- client
Expand Down
14 changes: 13 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,16 @@ module chat_app

go 1.13

require github.com/gorilla/websocket v1.4.1
require (
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec // indirect
github.com/gorilla/websocket v1.4.1
github.com/joho/godotenv v1.3.0
github.com/stretchr/codecs v0.0.0-20170403063245-04a5b1e1910d // indirect
github.com/stretchr/gomniauth v0.0.0-20170717123514-4b6c822be2eb
github.com/stretchr/objx v0.2.0
github.com/stretchr/signature v0.0.0-20160104132143-168b2a1e1b56 // indirect
github.com/stretchr/stew v0.0.0-20130812190256-80ef0842b48b // indirect
github.com/stretchr/tracer v0.0.0-20140124184152-66d3696bba97 // indirect
github.com/ugorji/go v1.1.7 // indirect
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 // indirect
)
27 changes: 27 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,2 +1,29 @@
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec h1:EdRZT3IeKQmfCSrgo8SZ8V3MEnskuJP0wCYNpe+aiXo=
github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/codecs v0.0.0-20170403063245-04a5b1e1910d h1:gXQ+QS3q874pcayiqszimfHPQ7ySFcekgzBMoTaVawk=
github.com/stretchr/codecs v0.0.0-20170403063245-04a5b1e1910d/go.mod h1:RpfDhdqip2BYhzoE4esKm8axH5VywpvMW9o3wfcamek=
github.com/stretchr/gomniauth v0.0.0-20170717123514-4b6c822be2eb h1:6lYIg/SCrz3gsCsEpRpK0BW3tBGt4VuQKlAleoxCgCc=
github.com/stretchr/gomniauth v0.0.0-20170717123514-4b6c822be2eb/go.mod h1:5wVraTCYvqbNMRjmSIIcrJMASXdoHeXm8j8AON7WobA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/signature v0.0.0-20160104132143-168b2a1e1b56 h1:BTR9AeovoABP8KnaBkzNtp7y/+x1n5GbOHwp3QisE1k=
github.com/stretchr/signature v0.0.0-20160104132143-168b2a1e1b56/go.mod h1:p8v7xBdwApv7pgPN+8jQ3LpBQJDAusrtE+YBWBbab9Q=
github.com/stretchr/stew v0.0.0-20130812190256-80ef0842b48b h1:DmfFjW6pLdaJNVHfKgCxTdKFI6tM+0YbMd0kx7kE78s=
github.com/stretchr/stew v0.0.0-20130812190256-80ef0842b48b/go.mod h1:yS/5aMz+lfJhykLjlAGbnhUhZIvVapOvtmk0MtzHktE=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/tracer v0.0.0-20140124184152-66d3696bba97 h1:ZXZ3Ko4supnaInt/pSZnq3QL65Qx/KSZTUPMJH5RlIk=
github.com/stretchr/tracer v0.0.0-20140124184152-66d3696bba97/go.mod h1:H0mYc1JTiYc9K0keLMYcR2ybyeom20X4cOYrKya1M1Y=
github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22 h1:VpOs+IwYnYBaFnrNAeB8UUWtL3vEUnzSCL1nVjPhqrw=
gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
27 changes: 26 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,15 @@ import (
"flag"
"log"
"net/http"
"os"
"path/filepath"
"sync"
"text/template"

"github.com/joho/godotenv"
"github.com/stretchr/gomniauth"
"github.com/stretchr/gomniauth/providers/google"
"github.com/stretchr/objx"
)

// templ is a single template
Expand All @@ -22,13 +28,32 @@ func (t *templateHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
t.once.Do(func() {
t.templ = template.Must(template.ParseFiles(filepath.Join("templates", t.filename)))
})
t.templ.Execute(w, r)
data := map[string]interface{}{
"Host": r.Host,
}
if authCookie, err := r.Cookie("auth"); err == nil {
data["UserData"] = objx.MustFromBase64(authCookie.Value)
}
t.templ.Execute(w, data)
}

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

err := godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}

googleClientID := os.Getenv("GOOGLE_CLIENT_ID")
googleClientSecret := os.Getenv("GOOGLE_CLIENT_SECRET")
googleRedirectURL := os.Getenv("GOOGLE_REDIRECT_URL")
securityKey := os.Getenv("SECURITY_KEY")

gomniauth.SetSecurityKey(securityKey)
gomniauth.WithProviders(google.New(googleClientID, googleClientSecret, googleRedirectURL))

// new room setup
r := chat.NewRoom()

Expand Down
Loading

0 comments on commit 0e3fee9

Please sign in to comment.