oauth
This commit is contained in:
parent
f6a72ff464
commit
35ac5a50d6
10
conf/wide.go
10
conf/wide.go
|
@ -18,7 +18,6 @@ package conf
|
|||
import (
|
||||
"encoding/json"
|
||||
"html/template"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
@ -66,6 +65,11 @@ type conf struct {
|
|||
Autocomplete bool // default autocomplete
|
||||
SiteStatCode template.HTML // site statistic code
|
||||
ReadOnly bool // read-only mode
|
||||
OAuthLoginURL string
|
||||
OAuthAccessTokenURL string
|
||||
OAuthUserInfoURL string
|
||||
OAuthClientID string
|
||||
OAuthClientSecret string
|
||||
}
|
||||
|
||||
// Logger.
|
||||
|
@ -124,7 +128,7 @@ func initUsers() {
|
|||
|
||||
user := &User{}
|
||||
|
||||
bytes, _ := ioutil.ReadFile(filepath.Join(Wide.Data, "users", name))
|
||||
bytes, _ := os.ReadFile(filepath.Join(Wide.Data, "users", name))
|
||||
err := json.Unmarshal(bytes, user)
|
||||
if err != nil {
|
||||
logger.Errorf("Parses [%s] error: %v, skip loading this user", name, err)
|
||||
|
@ -156,7 +160,7 @@ func initUsers() {
|
|||
}
|
||||
|
||||
func initWide(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
|
||||
bytes, err := ioutil.ReadFile(confPath)
|
||||
bytes, err := os.ReadFile(confPath)
|
||||
if nil != err {
|
||||
logger.Error(err)
|
||||
|
||||
|
|
|
@ -7,5 +7,10 @@
|
|||
"StaticResourceVersion": "${time}",
|
||||
"Locale": "zh_CN",
|
||||
"SiteStatCode": "",
|
||||
"ReadOnly": false
|
||||
"ReadOnly": false,
|
||||
"OAuthLoginURL": "",
|
||||
"OAuthAccessTokenURL": "",
|
||||
"OAuthUserInfoURL": "",
|
||||
"OAuthClientID": "",
|
||||
"OAuthClientSecret": ""
|
||||
}
|
10
go.mod
10
go.mod
|
@ -1,17 +1,23 @@
|
|||
module github.com/88250/wide
|
||||
|
||||
go 1.12
|
||||
go 1.20
|
||||
|
||||
require (
|
||||
github.com/88250/gulu v1.1.0
|
||||
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.9
|
||||
github.com/gorilla/sessions v1.2.0
|
||||
github.com/gorilla/websocket v1.4.2
|
||||
github.com/parnurzeal/gorequest v0.2.16
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davidebianchi/go-jsonclient v1.5.0 // indirect
|
||||
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1 // indirect
|
||||
github.com/gorilla/securecookie v1.1.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
|
||||
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b // indirect
|
||||
golang.org/x/text v0.3.2 // indirect
|
||||
moul.io/http2curl v1.0.0 // indirect
|
||||
)
|
||||
|
|
4
go.sum
4
go.sum
|
@ -1,8 +1,9 @@
|
|||
github.com/88250/gulu v1.1.0 h1:aU8HncW1XNssT1insCOz6rvmTUDrtsDwSnzeqTEqNFA=
|
||||
github.com/88250/gulu v1.1.0/go.mod h1:a2POIziN+QFeNT4Mj7FHH2lz1HEaFlMRF6wPE0NHM4U=
|
||||
github.com/davidebianchi/go-jsonclient v1.5.0 h1:PVDunAF/6c30D2SSx711efsrUP3hhml+uGT6oD3lzVY=
|
||||
github.com/davidebianchi/go-jsonclient v1.5.0/go.mod h1:lXd/hkx23H590Dod74j5GsmpgxF8RIAGZDt1P5+2mqo=
|
||||
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1 h1:TEmChtx8+IeOghiySC8kQIr0JZOdKUmRmmkuRDuYs3E=
|
||||
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM=
|
||||
github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2/go.mod h1:gNh8nYJoAm43RfaxurUnxr+N1PwuFV3ZMl/efxlIlY8=
|
||||
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
|
@ -31,7 +32,6 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e h1:3G+cUijn7XD+S4eJFddp53Pv7
|
|||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b h1:h03Ur1RlPrGTjua4koYdpGl8W0eYo8p1uI9w7RPlkdk=
|
||||
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"html/template"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
|
@ -34,11 +35,12 @@ var states = map[string]string{}
|
|||
|
||||
// LoginRedirectHandler redirects to HacPai auth page.
|
||||
func LoginRedirectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
loginAuthURL := "https://ld246.com/login?goto=" + conf.Wide.Server + "/login/callback"
|
||||
loginAuthURL := conf.Wide.OAuthLoginURL + "?response_type=code&redirect_uri=" + conf.Wide.Server + "/login/callback"
|
||||
|
||||
// надо будет добавить ttlcache для state и проверять для предотвращения атак CSRF
|
||||
state := gulu.Rand.String(16)
|
||||
states[state] = state
|
||||
path := loginAuthURL + "&state=" + state + "&v=" + conf.WideVersion
|
||||
path := loginAuthURL + "&state=" + state + "&client_id=" + conf.Wide.OAuthClientID
|
||||
http.Redirect(w, r, path, http.StatusSeeOther)
|
||||
}
|
||||
|
||||
|
@ -51,8 +53,25 @@ func LoginCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
delete(states, state)
|
||||
|
||||
accessToken := r.URL.Query().Get("access_token")
|
||||
userInfo := util.HacPaiUserInfo(accessToken)
|
||||
code := r.URL.Query().Get("code")
|
||||
accessToken, err := util.GetOAuthToken(
|
||||
conf.Wide.OAuthAccessTokenURL,
|
||||
conf.Wide.OAuthClientID,
|
||||
conf.Wide.OAuthClientSecret,
|
||||
code,
|
||||
conf.Wide.Server+`/login/callback`)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf(`get access_token failed. error: %s`, err.Error()), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
userInfo, err := util.OpenIdUserInfo(conf.Wide.OAuthUserInfoURL, accessToken)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf(`get user_info failed. error: %s`, err.Error()), http.StatusBadRequest)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
userId := userInfo["userId"].(string)
|
||||
userName := userInfo["userName"].(string)
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/davidebianchi/go-jsonclient"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type oAuthAccessTokenReq struct {
|
||||
ClientID string `json:"client_id"`
|
||||
ClientSecret string `json:"client_secret"`
|
||||
Code string `json:"code"`
|
||||
GrantType string `json:"grant_type"`
|
||||
RedirectURI string `json:"redirect_uri"`
|
||||
}
|
||||
type oAuthAccessTokenResp struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
func GetOAuthToken(oAuthAccessTokenURL, clientID, clientSecret, code, redirectURI string) (string, error) {
|
||||
u, err := url.Parse(oAuthAccessTokenURL)
|
||||
if err != nil {
|
||||
logger.Errorf(`failed to parse oAuthAccessTokenURL. error: %s`, err.Error())
|
||||
return ``, err
|
||||
}
|
||||
|
||||
client, err := jsonclient.New(jsonclient.Options{
|
||||
BaseURL: fmt.Sprintf(`%s://%s/`, u.Scheme, u.Host),
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf(`failed to create access_token client. error: %s`, err.Error())
|
||||
return ``, err
|
||||
}
|
||||
|
||||
req, err := client.NewRequest(http.MethodPost, u.Path, &oAuthAccessTokenReq{
|
||||
ClientID: clientID,
|
||||
ClientSecret: clientSecret,
|
||||
Code: code,
|
||||
GrantType: `authorization_code`,
|
||||
RedirectURI: redirectURI,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf(`failed to create access_token request. error: %s`, err.Error())
|
||||
return ``, err
|
||||
}
|
||||
|
||||
atResp := new(oAuthAccessTokenResp)
|
||||
|
||||
resp, err := client.Do(req, atResp)
|
||||
if err != nil {
|
||||
logger.Errorf(`failed to request access_token. error: %s`, err.Error())
|
||||
return ``, err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
logger.Errorf(`access_token request return wrong code. code: %d, err_msg: %s`, resp.StatusCode, resp.Status)
|
||||
return ``, err
|
||||
}
|
||||
|
||||
return atResp.AccessToken, nil
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/davidebianchi/go-jsonclient"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
type openIdUserInfoResp struct {
|
||||
Name string `json:"name"`
|
||||
GivenName string `json:"given_name"`
|
||||
FamilyName string `json:"family_name"`
|
||||
PreferredUsername string `json:"preferred_username"`
|
||||
Email string `json:"email"`
|
||||
Picture string `json:"picture"`
|
||||
}
|
||||
|
||||
func OpenIdUserInfo(openIdUserInfoURL, accessToken string) (map[string]any, error) {
|
||||
u, err := url.Parse(openIdUserInfoURL)
|
||||
if err != nil {
|
||||
logger.Errorf(`failed to parse openIdUserInfoURL. error: %s`, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
client, err := jsonclient.New(jsonclient.Options{
|
||||
BaseURL: fmt.Sprintf(`%s://%s/`, u.Scheme, u.Host),
|
||||
Headers: map[string]string{
|
||||
`Authorization`: fmt.Sprintf(`Bearer %s`, accessToken),
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
logger.Errorf(`failed to create user_info client. error: %s`, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req, err := client.NewRequest(http.MethodGet, u.Path, nil)
|
||||
if err != nil {
|
||||
logger.Errorf(`failed to create user_info request. error: %s`, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uiResp := new(openIdUserInfoResp)
|
||||
|
||||
resp, err := client.Do(req, uiResp)
|
||||
if err != nil {
|
||||
logger.Errorf(`failed to request user_info. error: %s`, err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode >= 300 {
|
||||
logger.Errorf(`user_info request return wrong code. code: %d, err_msg: %s`, resp.StatusCode, resp.Status)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
`userId`: uiResp.PreferredUsername,
|
||||
`userName`: uiResp.Name,
|
||||
`avatar`: uiResp.Picture,
|
||||
}, nil
|
||||
}
|
Loading…
Reference in New Issue