patreon auth
This commit is contained in:
@@ -80,6 +80,7 @@ type ServerCommand struct {
|
||||
SimpleView bool `long:"simpler-view" env:"SIMPLE_VIEW" description:"minimal comment editor mode"`
|
||||
ProxyCORS bool `long:"proxy-cors" env:"PROXY_CORS" description:"disable internal CORS and delegate it to proxy"`
|
||||
AllowedHosts []string `long:"allowed-hosts" env:"ALLOWED_HOSTS" description:"limit hosts/sources allowed to embed comments"`
|
||||
SubscribersOnly bool `long:"subscribers-only" env:"SUBSCRIBERS_ONLY" description:"enable commenting only for Patreon subscribers"`
|
||||
|
||||
Auth struct {
|
||||
TTL struct {
|
||||
@@ -96,6 +97,7 @@ type ServerCommand struct {
|
||||
Microsoft AuthGroup `group:"microsoft" namespace:"microsoft" env-namespace:"MICROSOFT" description:"Microsoft OAuth"`
|
||||
Yandex AuthGroup `group:"yandex" namespace:"yandex" env-namespace:"YANDEX" description:"Yandex OAuth"`
|
||||
Twitter AuthGroup `group:"twitter" namespace:"twitter" env-namespace:"TWITTER" description:"Twitter OAuth"`
|
||||
Patreon AuthGroup `group:"patreon" namespace:"patreon" env-namespace:"PATREON" description:"Patreon OAuth"`
|
||||
Telegram bool `long:"telegram" env:"TELEGRAM" description:"Enable Telegram auth (using token from telegram.token)"`
|
||||
Dev bool `long:"dev" env:"DEV" description:"enable dev (local) oauth2"`
|
||||
Anonymous bool `long:"anon" env:"ANON" description:"enable anonymous login"`
|
||||
@@ -291,6 +293,7 @@ func (s *ServerCommand) Execute(_ []string) error {
|
||||
"AUTH_MICROSOFT_CSEC",
|
||||
"AUTH_TWITTER_CSEC",
|
||||
"AUTH_YANDEX_CSEC",
|
||||
"AUTH_PATREON_CSEC",
|
||||
"TELEGRAM_TOKEN",
|
||||
"SMTP_PASSWORD",
|
||||
"ADMIN_PASSWD",
|
||||
@@ -531,6 +534,7 @@ func (s *ServerCommand) newServerApp(ctx context.Context) (*serverApp, error) {
|
||||
ProxyCORS: s.ProxyCORS,
|
||||
AllowedAncestors: s.AllowedHosts,
|
||||
SendJWTHeader: s.Auth.SendJWTHeader,
|
||||
SubscribersOnly: s.SubscribersOnly,
|
||||
}
|
||||
|
||||
srv.ScoreThresholds.Low, srv.ScoreThresholds.Critical = s.LowScore, s.CriticalScore
|
||||
@@ -799,6 +803,10 @@ func (s *ServerCommand) addAuthProviders(ctx context.Context, authenticator *aut
|
||||
authenticator.AddProvider("twitter", s.Auth.Twitter.CID, s.Auth.Twitter.CSEC)
|
||||
providers++
|
||||
}
|
||||
if s.Auth.Patreon.CID != "" && s.Auth.Patreon.CSEC != "" {
|
||||
authenticator.AddProvider("patreon", s.Auth.Patreon.CID, s.Auth.Patreon.CSEC)
|
||||
providers++
|
||||
}
|
||||
if s.Auth.Telegram {
|
||||
telegram := &provider.TelegramHandler{
|
||||
ProviderName: "telegram",
|
||||
|
||||
@@ -78,7 +78,7 @@ func TestServerApp_DevMode(t *testing.T) {
|
||||
waitForHTTPServerStart(port)
|
||||
|
||||
providers := app.restSrv.Authenticator.Providers()
|
||||
require.Equal(t, 8+1, len(providers), "extra auth provider")
|
||||
require.Equal(t, 9+1, len(providers), "extra auth provider")
|
||||
assert.Equal(t, "dev", providers[len(providers)-2].Name(), "dev auth provider")
|
||||
// send ping
|
||||
resp, err := http.Get(fmt.Sprintf("http://localhost:%d/api/v1/ping", port))
|
||||
@@ -105,7 +105,7 @@ func TestServerApp_AnonMode(t *testing.T) {
|
||||
waitForHTTPServerStart(port)
|
||||
|
||||
providers := app.restSrv.Authenticator.Providers()
|
||||
require.Equal(t, 8+1, len(providers), "extra auth provider for anon")
|
||||
require.Equal(t, 9+1, len(providers), "extra auth provider for anon")
|
||||
assert.Equal(t, "anonymous", providers[len(providers)-1].Name(), "anon auth provider")
|
||||
|
||||
// send ping
|
||||
@@ -694,6 +694,7 @@ func prepServerApp(t *testing.T, fn func(o ServerCommand) ServerCommand) (*serve
|
||||
cmd.Auth.Yandex.CSEC, cmd.Auth.Yandex.CID = "csec", "cid"
|
||||
cmd.Auth.Microsoft.CSEC, cmd.Auth.Microsoft.CID = "csec", "cid"
|
||||
cmd.Auth.Twitter.CSEC, cmd.Auth.Twitter.CID = "csec", "cid"
|
||||
cmd.Auth.Patreon.CSEC, cmd.Auth.Patreon.CID = "csec", "cid"
|
||||
cmd.Auth.Telegram = true
|
||||
cmd.Telegram.Token = "token"
|
||||
cmd.Auth.Email.Enable = true
|
||||
|
||||
@@ -63,6 +63,7 @@ type Rest struct {
|
||||
ProxyCORS bool
|
||||
SendJWTHeader bool
|
||||
AllowedAncestors []string // sets Content-Security-Policy "frame-ancestors ..."
|
||||
SubscribersOnly bool
|
||||
|
||||
SSLConfig SSLConfig
|
||||
httpsServer *http.Server
|
||||
@@ -312,7 +313,7 @@ func (s *Rest) routes() chi.Router {
|
||||
rapi.Group(func(rauth chi.Router) {
|
||||
rauth.Use(middleware.Timeout(10 * time.Second))
|
||||
rauth.Use(tollbooth_chi.LimitHandler(tollbooth.NewLimiter(s.updateLimiter(), nil)))
|
||||
rauth.Use(authMiddleware.Auth, matchSiteID)
|
||||
rauth.Use(authMiddleware.Auth, matchSiteID, subscribersOnly(s.SubscribersOnly))
|
||||
rauth.Use(middleware.NoCache, logInfoWithBody)
|
||||
|
||||
rauth.Put("/comment/{id}", s.privRest.updateCommentCtrl)
|
||||
@@ -429,6 +430,7 @@ func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
EmojiEnabled bool `json:"emoji_enabled"`
|
||||
SimpleView bool `json:"simple_view"`
|
||||
SendJWTHeader bool `json:"send_jwt_header"`
|
||||
SubscribersOnly bool `json:"subscribers_only"`
|
||||
}{
|
||||
Version: s.Version,
|
||||
EditDuration: int(s.DataService.EditDuration.Seconds()),
|
||||
@@ -447,6 +449,7 @@ func (s *Rest) configCtrl(w http.ResponseWriter, r *http.Request) {
|
||||
AnonVote: s.AnonVote,
|
||||
SimpleView: s.SimpleView,
|
||||
SendJWTHeader: s.SendJWTHeader,
|
||||
SubscribersOnly: s.SubscribersOnly,
|
||||
}
|
||||
|
||||
cnf.Auth = []string{}
|
||||
@@ -628,6 +631,27 @@ func frameAncestors(hosts []string) func(http.Handler) http.Handler {
|
||||
}
|
||||
}
|
||||
|
||||
// subscribersOnly is a middleware rejecting non-paid_sub users
|
||||
func subscribersOnly(enable bool) func(http.Handler) http.Handler {
|
||||
return func(h http.Handler) http.Handler {
|
||||
fn := func(w http.ResponseWriter, r *http.Request) {
|
||||
if enable {
|
||||
user, err := rest.GetUserInfo(r)
|
||||
if err != nil {
|
||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
if !user.PaidSub {
|
||||
http.Error(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
h.ServeHTTP(w, r)
|
||||
}
|
||||
return http.HandlerFunc(fn)
|
||||
}
|
||||
}
|
||||
|
||||
func parseError(err error, defaultCode int) (code int) {
|
||||
code = defaultCode
|
||||
|
||||
|
||||
@@ -364,6 +364,41 @@ func TestRest_frameAncestors(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestRest_subscribersOnly(t *testing.T) {
|
||||
|
||||
paidSubUser := &token.User{}
|
||||
paidSubUser.SetPaidSub(true)
|
||||
|
||||
tbl := []struct {
|
||||
subsOnly bool
|
||||
user token.User
|
||||
setUser bool
|
||||
status int
|
||||
}{
|
||||
{true, token.User{}, false, http.StatusUnauthorized},
|
||||
{true, token.User{}, true, http.StatusForbidden},
|
||||
{false, token.User{}, false, http.StatusOK},
|
||||
{false, token.User{}, true, http.StatusOK},
|
||||
{true, *paidSubUser, true, http.StatusOK},
|
||||
}
|
||||
|
||||
for i, tt := range tbl {
|
||||
tt := tt
|
||||
t.Run(strconv.Itoa(i), func(t *testing.T) {
|
||||
req := httptest.NewRequest("GET", "http://example.com", nil)
|
||||
if tt.setUser {
|
||||
req = token.SetUserInfo(req, tt.user)
|
||||
}
|
||||
w := httptest.NewRecorder()
|
||||
h := subscribersOnly(tt.subsOnly)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
|
||||
h.ServeHTTP(w, req)
|
||||
resp := w.Result()
|
||||
assert.Equal(t, tt.status, resp.StatusCode)
|
||||
assert.NoError(t, resp.Body.Close())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// randomPath pick a file or folder name which is not in use for sure
|
||||
func randomPath(tempDir, basename, suffix string) (string, error) {
|
||||
for i := 0; i < 10; i++ {
|
||||
|
||||
@@ -36,6 +36,7 @@ func GetUserInfo(r *http.Request) (user store.User, err error) {
|
||||
Verified: u.BoolAttr("verified"),
|
||||
Blocked: u.BoolAttr("blocked"),
|
||||
SiteID: u.Audience,
|
||||
PaidSub: u.IsPaidSub(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -62,6 +63,7 @@ func SetUserInfo(r *http.Request, user store.User) *http.Request {
|
||||
},
|
||||
}
|
||||
u.SetAdmin(user.Admin)
|
||||
u.SetPaidSub(user.PaidSub)
|
||||
|
||||
return token.SetUserInfo(r, u)
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ type User struct {
|
||||
Verified bool `json:"verified,omitempty"`
|
||||
EmailSubscription bool `json:"email_subscription,omitempty"`
|
||||
SiteID string `json:"site_id,omitempty"`
|
||||
PaidSub bool `json:"paid_sub,omitempty"`
|
||||
}
|
||||
|
||||
var reValidSha = regexp.MustCompile("^[a-fA-F0-9]{40}$")
|
||||
|
||||
Reference in New Issue
Block a user