-
Notifications
You must be signed in to change notification settings - Fork 3
/
auth.go
151 lines (128 loc) · 3.48 KB
/
auth.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
149
150
151
package main
import (
"encoding/json"
"net/http"
"strings"
"time"
"github.com/chances/chances-party/models"
"github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin"
"github.com/twinj/uuid"
"github.com/zmb3/spotify"
)
var (
auth spotify.Authenticator
scopes string
jwtSigningKey []byte
)
func setupAuth() {
auth = spotify.NewAuthenticator(
getenvOrFatal("SPOTIFY_CALLBACK"),
spotify.ScopeUserReadPrivate,
spotify.ScopePlaylistReadPrivate,
spotify.ScopePlaylistReadCollaborative,
)
auth.SetAuthInfo(getenvOrFatal("SPOTIFY_APP_KEY"), getenvOrFatal("SPOTIFY_APP_SECRET"))
s := make([]string, 3)
s[0] = spotify.ScopeUserReadPrivate
s[1] = spotify.ScopePlaylistReadPrivate
s[2] = spotify.ScopePlaylistReadCollaborative
scopes = strings.Join(s, " ")
jwtSigningKey = []byte(getenvOrFatal("JWT_SECRET"))
}
// AuthRequired guards against unauthenticated sessions
func AuthRequired() gin.HandlerFunc {
return func(c *gin.Context) {
if !IsLoggedIn(c) {
c.Error(errUnauthorized)
c.Abort()
return
}
c.Next()
}
}
func authGuest() (string, error) {
// TODO: Auth a guest session given a party "room" or after OAuth
jwtSessionID := uuid.NewV4().String()
// TODO: Store token ID is Redis with expiration
// TODO: Goroutine to clean expired JWTs
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"id": jwtSessionID,
})
tokenString, err := token.SignedString(jwtSigningKey)
if err != nil {
return "", err
}
return tokenString, nil
}
func login(c *gin.Context) {
state := uuid.NewV4().String()
session := DefaultSession(c)
err := session.Set("AUTH_STATE", state)
if err != nil {
c.Error(errAuth.WithDetail("Couldn't save session").CausedBy(err))
c.Abort()
return
}
c.Redirect(http.StatusSeeOther, auth.AuthURL(state))
}
// IDEA: Convert error handling shenanigans to Observable chain
func spotifyCallback(c *gin.Context) {
session := DefaultSession(c)
sessionState, err := session.Get("AUTH_STATE")
if err != nil {
c.Error(errAuth.WithDetail("Could not retrieve auth state").CausedBy(err))
c.Abort()
return
}
session.Delete("AUTH_STATE")
// Validate OAuth state
oauthState := c.Request.FormValue("state")
if oauthState != sessionState {
c.Error(errAuth.WithDetail("Auth failed, mismatched state"))
c.Abort()
return
}
// Retrieve token
token, err := auth.Token(sessionState, c.Request)
if err != nil {
c.Error(errAuth.WithDetail("Token request failed").CausedBy(err))
c.Abort()
return
}
// Get logged in user
client := auth.NewClient(token)
spotifyUser, err := client.CurrentUser()
if err != nil {
c.Error(errAuth.WithDetail("Could not get user").CausedBy(err))
c.Abort()
return
}
spotifyUserJSON, err := json.Marshal(spotifyUser)
if err != nil {
c.Error(errAuth.WithDetail("Could not serialize user").CausedBy(err))
c.Abort()
return
}
var user models.User
user.Username = spotifyUser.ID
user.SpotifyUser = spotifyUserJSON
user.AccessToken = token.AccessToken
user.RefreshToken = token.RefreshToken
user.TokenExpiryDate = token.Expiry
user.TokenScope = scopes
user.UpdatedAt = time.Now()
err = user.Upsert(db, true, []string{"username"}, []string{
"spotify_user", "access_token", "refresh_token",
"token_expiry_date", "token_scope", "updated_at",
})
if err != nil {
c.Error(errAuth.WithDetail("Could not create/update user").CausedBy(err))
c.Abort()
return
}
c.Set("user", user)
session.Set("USER", user.Username)
// Successfully logged in
c.Redirect(http.StatusSeeOther, "/")
}