forked from RealistikOsu/hanayo
/
oauth.go
171 lines (144 loc) · 4.72 KB
/
oauth.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
package oauth
import (
"crypto/sha256"
"database/sql"
"encoding/json"
"fmt"
"strconv"
"strings"
"github.com/RangelReale/osin"
"github.com/felipeweb/osin-mysql"
"github.com/gin-gonic/gin"
)
var osinServer *osin.Server
var rh RequestHandler
// Initialise initialises the oauth router.
func Initialise(handler RequestHandler) error {
store := mysql.New(handler.GetDB(), "osin_")
rh = handler
config := osin.NewServerConfig()
config.AllowClientSecretInParams = true
config.AllowGetAccessRequest = true
// Hmm... Wondering why we're making access tokens everlasting, and
// disabling refresh tokens? http://telegra.ph/On-refresh-tokens-06-10
config.AccessExpiration = 0
osinServer = osin.NewServer(config, storage{store})
return nil
}
// Client is the data passed to the template of the authorization page.
type Client struct {
ID string
Name string
CreatorID int
Avatar string
Authorizations []string
}
func clientFromOsinClient(oc osin.Client) Client {
userDataRaw := oc.GetUserData().(string)
var userData [3]string
json.Unmarshal([]byte(userDataRaw), &userData)
cid, _ := strconv.Atoi(userData[1])
return Client{
ID: oc.GetId(),
Name: userData[0],
CreatorID: cid,
Avatar: userData[2],
// Authorizations is managed by caller
}
}
// RequestHandler contains the functions to which Oauth will delegate.
type RequestHandler interface {
// Checks whether the user is logged in. If they are, it returns true. Otherwise, it redirects
// the user to the login page.
CheckLoggedInOrRedirect(c *gin.Context) bool
// DisplayAuthorizePage renders the page where the user can validate the request for authorization
DisplayAuthorizePage(client Client, c *gin.Context)
// CheckCSRF is a function that checks the POSTed parameter `csrf` is valid.
CheckCSRF(c *gin.Context, s string) bool
// GetDB retrieves MySQL's DB.
GetDB() *sql.DB
// GetUserID retrieves the ID of the currently logged in user.
GetUserID(c *gin.Context) int
}
// Authorize handles a request for user authorization
func Authorize(c *gin.Context) {
resp := osinServer.NewResponse()
defer resp.Close()
// first we let osinserver handle the authorize request
if ar := osinServer.HandleAuthorizeRequest(resp, c.Request); ar != nil {
// we then make sure to be logged in
if !rh.CheckLoggedInOrRedirect(c) {
return
}
// and show the authorization page
client := clientFromOsinClient(ar.Client)
client.Authorizations = safeScopes(strings.Split(ar.Scope, " "))
if c.PostForm("appid") != ar.Client.GetId() || !rh.CheckCSRF(c, c.PostForm("csrf")) {
rh.DisplayAuthorizePage(client, c)
return
}
// all good, authorization succeded
ar.Authorized = c.PostForm("approve") == "1"
ar.UserData = strconv.Itoa(rh.GetUserID(c))
ar.Scope = strings.Join(client.Authorizations, " ")
osinServer.FinishAuthorizeRequest(resp, c.Request, ar)
}
if resp.IsError && resp.InternalError != nil {
fmt.Println(resp.InternalError)
}
osin.OutputJSON(resp, c.Writer, c.Request)
}
// an "identify" scope is taken for granted
var scopes = [...]string{
"read_confidential",
"write",
}
// safeScopes removes duplicates and invalid elements from the raw scopes
func safeScopes(rawScopes []string) []string {
retScopes := make([]string, 0, len(scopes))
for _, el := range scopes {
for _, rawScope := range rawScopes {
if rawScope == el {
retScopes = append(retScopes, rawScope)
break
}
}
}
return retScopes
}
// Token handles a request from a client to obtain an access token.
func Token(c *gin.Context) {
resp := osinServer.NewResponse()
defer resp.Close()
_ = resp.Storage.(storage)
c.Request.ParseForm()
if ar := osinServer.HandleAccessRequest(resp, c.Request); ar != nil {
ar.Authorized = true
ar.GenerateRefresh = false
redirectURI := c.Query("redirect_uri")
// Get redirect uri from AccessRequest if it's there (e.g., refresh token request)
if ar.RedirectUri != "" {
redirectURI = ar.RedirectUri
}
ar.ForceAccessData = &osin.AccessData{
Client: ar.Client,
AuthorizeData: ar.AuthorizeData,
AccessData: ar.AccessData,
RedirectUri: redirectURI,
CreatedAt: osinServer.Now(),
ExpiresIn: ar.Expiration,
UserData: ar.UserData,
Scope: ar.Scope,
}
// generate access token
plainToken, _, _ := osinServer.AccessTokenGen.GenerateAccessToken(ar.ForceAccessData, ar.GenerateRefresh)
ar.ForceAccessData.AccessToken = fmt.Sprintf("%x", sha256.Sum256([]byte(plainToken)))
osinServer.FinishAccessRequest(resp, c.Request, ar)
resp.Output["access_token"] = plainToken
}
if resp.IsError && resp.InternalError != nil {
fmt.Printf("ERROR: %s\n", resp.InternalError)
}
delete(resp.Output, "expire_in")
osin.OutputJSON(resp, c.Writer, c.Request)
}