refactor as JWT header auth
This commit is contained in:
parent
9097bcda6e
commit
ff7201c00c
@ -1,63 +0,0 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/filebrowser/filebrowser/v2/errors"
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
"github.com/filebrowser/filebrowser/v2/users"
|
||||
)
|
||||
|
||||
// MethodCloudflareAuth is used to identify no auth.
|
||||
const MethodCloudflareAuth settings.AuthMethod = "cloudflare-access"
|
||||
|
||||
// CloudflareAuth is a proxy implementation of an auther.
|
||||
type CloudflareAuth struct {
|
||||
Team string `json:"team"`
|
||||
Aud string `json:"aud"`
|
||||
}
|
||||
|
||||
type CloudflareTokenPayload struct {
|
||||
Email string
|
||||
}
|
||||
|
||||
// Auth authenticates the user via an HTTP header.
|
||||
func (a CloudflareAuth) Auth(r *http.Request, usr users.Store, stg *settings.Settings, srv *settings.Server) (*users.User, error) {
|
||||
accessJWT := r.Header.Get("Cf-Access-Jwt-Assertion")
|
||||
if accessJWT == "" {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
|
||||
// The Application Audience (AUD) tag for your application
|
||||
config := &oidc.Config{
|
||||
ClientID: a.Aud,
|
||||
}
|
||||
|
||||
teamDomain := fmt.Sprintf("https://%s.cloudflareaccess.com", a.Team)
|
||||
certsURL := fmt.Sprintf("%s/cdn-cgi/access/certs", teamDomain)
|
||||
keySet := oidc.NewRemoteKeySet(r.Context(), certsURL)
|
||||
verifier := oidc.NewVerifier(teamDomain, keySet, config)
|
||||
|
||||
token, err := verifier.Verify(r.Context(), accessJWT)
|
||||
if err != nil {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
|
||||
payload := new(CloudflareTokenPayload)
|
||||
token.Claims(&payload)
|
||||
|
||||
user, err := usr.Get(srv.Root, payload.Email)
|
||||
if err == errors.ErrNotExist {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
|
||||
return user, err
|
||||
}
|
||||
|
||||
// LoginPage tells that proxy auth doesn't require a login page.
|
||||
func (a CloudflareAuth) LoginPage() bool {
|
||||
return false
|
||||
}
|
||||
66
auth/jwt.go
Normal file
66
auth/jwt.go
Normal file
@ -0,0 +1,66 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/filebrowser/filebrowser/v2/errors"
|
||||
"github.com/filebrowser/filebrowser/v2/settings"
|
||||
"github.com/filebrowser/filebrowser/v2/users"
|
||||
)
|
||||
|
||||
// MethodJWTAuth is used to identify JWTAuth auth.
|
||||
const MethodJWTAuth settings.AuthMethod = "jwt-header"
|
||||
|
||||
// JWTAuth is a JWTAuth implementation of an auther.
|
||||
type JWTAuth struct {
|
||||
CertsURL string `json:"certsurl"`
|
||||
Aud string `json:"aud"`
|
||||
Iss string `json:"iss"`
|
||||
Claim string `json:"claim"`
|
||||
Header string `json:"header"`
|
||||
remoteKeySet *oidc.RemoteKeySet
|
||||
init *sync.Once
|
||||
}
|
||||
|
||||
// Auth authenticates the user via a JWT token in an HTTP header.
|
||||
func (a JWTAuth) Auth(r *http.Request, usr users.Store, stg *settings.Settings, srv *settings.Server) (*users.User, error) {
|
||||
a.init.Do(func() {
|
||||
a.remoteKeySet = oidc.NewRemoteKeySet(context.Background(), a.CertsURL)
|
||||
})
|
||||
|
||||
accessJWT := r.Header.Get(a.Header)
|
||||
if accessJWT == "" {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
|
||||
// The Application Audience (AUD) tag for your application
|
||||
config := &oidc.Config{
|
||||
ClientID: a.Aud,
|
||||
}
|
||||
|
||||
verifier := oidc.NewVerifier(a.Iss, a.remoteKeySet, config)
|
||||
|
||||
token, err := verifier.Verify(r.Context(), accessJWT)
|
||||
if err != nil {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
|
||||
payload := map[string]string{}
|
||||
token.Claims(&payload)
|
||||
|
||||
user, err := usr.Get(srv.Root, payload[a.Claim])
|
||||
if err == errors.ErrNotExist {
|
||||
return nil, os.ErrPermission
|
||||
}
|
||||
|
||||
return user, err
|
||||
}
|
||||
|
||||
// LoginPage tells that proxy auth doesn't require a login page.
|
||||
func (a JWTAuth) LoginPage() bool {
|
||||
return false
|
||||
}
|
||||
@ -34,10 +34,12 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
||||
flags.String("shell", "", "shell command to which other commands should be appended")
|
||||
|
||||
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
||||
flags.String("auth.header", "", "HTTP header for auth.method=proxy")
|
||||
flags.String("auth.header", "", "HTTP header for auth.method=proxy and auth.method=jwt-header")
|
||||
flags.String("auth.command", "", "command for auth.method=hook")
|
||||
flags.String("auth.team", "", "Cloudflare Access Team name for auth.method=cloudflare-access")
|
||||
flags.String("auth.team", "", "The Application Audience (AUD) tag for your application for auth.method=cloudflare-access")
|
||||
flags.String("auth.aud", "", "The Application Audience (AUD) tag for JWT validation auth.method=jwt-header")
|
||||
flags.String("auth.iss", "", "The Issuer (AUD) for JWT validation auth.method=jwt-header")
|
||||
flags.String("auth.certsurl", "", "The URL to download certs from for JWT validation auth.method=jwt-header")
|
||||
flags.String("auth.claim", "", "The claim which will contain the username auth.method=jwt-header")
|
||||
|
||||
flags.String("recaptcha.host", "https://www.google.com", "use another host for ReCAPTCHA. recaptcha.net might be useful in China")
|
||||
flags.String("recaptcha.key", "", "ReCaptcha site key")
|
||||
@ -86,18 +88,30 @@ func getAuthentication(flags *pflag.FlagSet, defaults ...interface{}) (settings.
|
||||
auther = &auth.ProxyAuth{Header: header}
|
||||
}
|
||||
|
||||
if method == auth.MethodCloudflareAuth {
|
||||
team := mustGetString(flags, "auth.team")
|
||||
if method == auth.MethodJWTAuth {
|
||||
header := mustGetString(flags, "auth.header")
|
||||
aud := mustGetString(flags, "auth.aud")
|
||||
iss := mustGetString(flags, "auth.iss")
|
||||
certsurl := mustGetString(flags, "auth.certsurl")
|
||||
claim := mustGetString(flags, "auth.claim")
|
||||
|
||||
if team == "" {
|
||||
checkErr(nerrors.New("you must set the flag 'auth.team' for method 'cloudflare-access'"))
|
||||
if header == "" {
|
||||
checkErr(nerrors.New("you must set the flag 'auth.header' for method 'jwt-header'"))
|
||||
}
|
||||
if aud == "" {
|
||||
checkErr(nerrors.New("you must set the flag 'auth.aud' for method 'cloudflare-access'"))
|
||||
checkErr(nerrors.New("you must set the flag 'auth.aud' for method 'jwt-header'"))
|
||||
}
|
||||
if iss == "" {
|
||||
checkErr(nerrors.New("you must set the flag 'auth.iss' for method 'jwt-header'"))
|
||||
}
|
||||
if certsurl == "" {
|
||||
checkErr(nerrors.New("you must set the flag 'auth.certsurl' for method 'jwt-header'"))
|
||||
}
|
||||
if claim == "" {
|
||||
checkErr(nerrors.New("you must set the flag 'auth.claim' for method 'jwt-header'"))
|
||||
}
|
||||
|
||||
auther = &auth.CloudflareAuth{Team: team, Aud: aud}
|
||||
auther = &auth.JWTAuth{}
|
||||
}
|
||||
|
||||
if method == auth.MethodNoAuth {
|
||||
|
||||
@ -22,8 +22,8 @@ func (s authBackend) Get(t settings.AuthMethod) (auth.Auther, error) {
|
||||
auther = &auth.ProxyAuth{}
|
||||
case auth.MethodHookAuth:
|
||||
auther = &auth.HookAuth{}
|
||||
case auth.MethodCloudflareAuth:
|
||||
auther = &auth.CloudflareAuth{}
|
||||
case auth.MethodJWTAuth:
|
||||
auther = &auth.JWTAuth{}
|
||||
case auth.MethodNoAuth:
|
||||
auther = &auth.NoAuth{}
|
||||
default:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user