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 (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
@ -66,6 +65,11 @@ type conf struct {
|
||||||
Autocomplete bool // default autocomplete
|
Autocomplete bool // default autocomplete
|
||||||
SiteStatCode template.HTML // site statistic code
|
SiteStatCode template.HTML // site statistic code
|
||||||
ReadOnly bool // read-only mode
|
ReadOnly bool // read-only mode
|
||||||
|
OAuthLoginURL string
|
||||||
|
OAuthAccessTokenURL string
|
||||||
|
OAuthUserInfoURL string
|
||||||
|
OAuthClientID string
|
||||||
|
OAuthClientSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Logger.
|
// Logger.
|
||||||
|
@ -124,7 +128,7 @@ func initUsers() {
|
||||||
|
|
||||||
user := &User{}
|
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)
|
err := json.Unmarshal(bytes, user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Parses [%s] error: %v, skip loading this user", name, err)
|
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) {
|
func initWide(confPath, confData, confServer, confLogLevel, confReadOnly string, confSiteStatCode template.HTML) {
|
||||||
bytes, err := ioutil.ReadFile(confPath)
|
bytes, err := os.ReadFile(confPath)
|
||||||
if nil != err {
|
if nil != err {
|
||||||
logger.Error(err)
|
logger.Error(err)
|
||||||
|
|
||||||
|
|
|
@ -7,5 +7,10 @@
|
||||||
"StaticResourceVersion": "${time}",
|
"StaticResourceVersion": "${time}",
|
||||||
"Locale": "zh_CN",
|
"Locale": "zh_CN",
|
||||||
"SiteStatCode": "",
|
"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
|
module github.com/88250/wide
|
||||||
|
|
||||||
go 1.12
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/88250/gulu v1.1.0
|
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/fsnotify/fsnotify v1.4.9
|
||||||
github.com/gorilla/sessions v1.2.0
|
github.com/gorilla/sessions v1.2.0
|
||||||
github.com/gorilla/websocket v1.4.2
|
github.com/gorilla/websocket v1.4.2
|
||||||
github.com/parnurzeal/gorequest v0.2.16
|
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/pkg/errors v0.9.1 // indirect
|
||||||
github.com/smartystreets/goconvey v1.6.4 // indirect
|
github.com/smartystreets/goconvey v1.6.4 // indirect
|
||||||
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
|
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e // indirect
|
||||||
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b // 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
|
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 h1:aU8HncW1XNssT1insCOz6rvmTUDrtsDwSnzeqTEqNFA=
|
||||||
github.com/88250/gulu v1.1.0/go.mod h1:a2POIziN+QFeNT4Mj7FHH2lz1HEaFlMRF6wPE0NHM4U=
|
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 h1:TEmChtx8+IeOghiySC8kQIr0JZOdKUmRmmkuRDuYs3E=
|
||||||
github.com/elazarl/goproxy v0.0.0-20200426045556-49ad98f6dac1/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
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/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 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
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/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-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-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-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 h1:h03Ur1RlPrGTjua4koYdpGl8W0eYo8p1uI9w7RPlkdk=
|
||||||
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200408040146-ea54a3c99b9b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
package session
|
package session
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -34,11 +35,12 @@ var states = map[string]string{}
|
||||||
|
|
||||||
// LoginRedirectHandler redirects to HacPai auth page.
|
// LoginRedirectHandler redirects to HacPai auth page.
|
||||||
func LoginRedirectHandler(w http.ResponseWriter, r *http.Request) {
|
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)
|
state := gulu.Rand.String(16)
|
||||||
states[state] = state
|
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)
|
http.Redirect(w, r, path, http.StatusSeeOther)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,8 +53,25 @@ func LoginCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
delete(states, state)
|
delete(states, state)
|
||||||
|
|
||||||
accessToken := r.URL.Query().Get("access_token")
|
code := r.URL.Query().Get("code")
|
||||||
userInfo := util.HacPaiUserInfo(accessToken)
|
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)
|
userId := userInfo["userId"].(string)
|
||||||
userName := userInfo["userName"].(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