feat: configurable logout page URL for proxy/hook auth (#3884)
Co-authored-by: Henrique Dias <mail@hacdias.com>
This commit is contained in:
parent
701522a060
commit
b9ac45d5da
@ -45,6 +45,7 @@ func addConfigFlags(flags *pflag.FlagSet) {
|
|||||||
flags.String("auth.method", string(auth.MethodJSONAuth), "authentication type")
|
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")
|
||||||
flags.String("auth.command", "", "command for auth.method=hook")
|
flags.String("auth.command", "", "command for auth.method=hook")
|
||||||
|
flags.String("auth.logoutPage", "", "url of custom logout page")
|
||||||
|
|
||||||
flags.String("recaptcha.host", "https://www.google.com", "use another host for ReCAPTCHA. recaptcha.net might be useful in China")
|
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")
|
flags.String("recaptcha.key", "", "ReCaptcha site key")
|
||||||
@ -201,6 +202,7 @@ func printSettings(ser *settings.Server, set *settings.Settings, auther auth.Aut
|
|||||||
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
fmt.Fprintf(w, "Sign up:\t%t\n", set.Signup)
|
||||||
fmt.Fprintf(w, "Hide Login Button:\t%t\n", set.HideLoginButton)
|
fmt.Fprintf(w, "Hide Login Button:\t%t\n", set.HideLoginButton)
|
||||||
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
fmt.Fprintf(w, "Create User Dir:\t%t\n", set.CreateUserDir)
|
||||||
|
fmt.Fprintf(w, "Logout Page:\t%s\n", set.LogoutPage)
|
||||||
fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
|
fmt.Fprintf(w, "Minimum Password Length:\t%d\n", set.MinimumPasswordLength)
|
||||||
fmt.Fprintf(w, "Auth Method:\t%s\n", set.AuthMethod)
|
fmt.Fprintf(w, "Auth Method:\t%s\n", set.AuthMethod)
|
||||||
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
fmt.Fprintf(w, "Shell:\t%s\t\n", strings.Join(set.Shell, " "))
|
||||||
@ -328,6 +330,8 @@ func getSettings(flags *pflag.FlagSet, set *settings.Settings, ser *settings.Ser
|
|||||||
set.DirMode, err = getAndParseFileMode(flags, flag.Name)
|
set.DirMode, err = getAndParseFileMode(flags, flag.Name)
|
||||||
case "auth.method":
|
case "auth.method":
|
||||||
hasAuth = true
|
hasAuth = true
|
||||||
|
case "auth.logoutPage":
|
||||||
|
set.LogoutPage, err = flags.GetString(flag.Name)
|
||||||
case "branding.name":
|
case "branding.name":
|
||||||
set.Branding.Name, err = flags.GetString(flag.Name)
|
set.Branding.Name, err = flags.GetString(flag.Name)
|
||||||
case "branding.theme":
|
case "branding.theme":
|
||||||
|
|||||||
@ -39,6 +39,7 @@
|
|||||||
DisableUsedPercentage: false,
|
DisableUsedPercentage: false,
|
||||||
EnableExec: true,
|
EnableExec: true,
|
||||||
EnableThumbs: true,
|
EnableThumbs: true,
|
||||||
|
LogoutPage: "",
|
||||||
LoginPage: true,
|
LoginPage: true,
|
||||||
Name: "",
|
Name: "",
|
||||||
NoAuth: false,
|
NoAuth: false,
|
||||||
|
|||||||
@ -129,6 +129,7 @@ import {
|
|||||||
disableExternal,
|
disableExternal,
|
||||||
disableUsedPercentage,
|
disableUsedPercentage,
|
||||||
noAuth,
|
noAuth,
|
||||||
|
logoutPage,
|
||||||
loginPage,
|
loginPage,
|
||||||
} from "@/utils/constants";
|
} from "@/utils/constants";
|
||||||
import { files as api } from "@/api";
|
import { files as api } from "@/api";
|
||||||
@ -159,7 +160,7 @@ export default {
|
|||||||
version: () => version,
|
version: () => version,
|
||||||
disableExternal: () => disableExternal,
|
disableExternal: () => disableExternal,
|
||||||
disableUsedPercentage: () => disableUsedPercentage,
|
disableUsedPercentage: () => disableUsedPercentage,
|
||||||
canLogout: () => !noAuth && loginPage,
|
canLogout: () => !noAuth && (loginPage || logoutPage !== "/login"),
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
|
...mapActions(useLayoutStore, ["closeHovers", "showHover"]),
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { useAuthStore } from "@/stores/auth";
|
|||||||
import router from "@/router";
|
import router from "@/router";
|
||||||
import type { JwtPayload } from "jwt-decode";
|
import type { JwtPayload } from "jwt-decode";
|
||||||
import { jwtDecode } from "jwt-decode";
|
import { jwtDecode } from "jwt-decode";
|
||||||
import { baseURL, noAuth } from "./constants";
|
import { authMethod, baseURL, noAuth, logoutPage } from "./constants";
|
||||||
import { StatusError } from "@/api/utils";
|
import { StatusError } from "@/api/utils";
|
||||||
import { setSafeTimeout } from "@/api/utils";
|
import { setSafeTimeout } from "@/api/utils";
|
||||||
|
|
||||||
@ -18,6 +18,12 @@ export function parseToken(token: string) {
|
|||||||
authStore.jwt = token;
|
authStore.jwt = token;
|
||||||
authStore.setUser(data.user);
|
authStore.setUser(data.user);
|
||||||
|
|
||||||
|
// proxy auth with custom logout subject to unknown external timeout
|
||||||
|
if (logoutPage !== "/login" && authMethod === "proxy") {
|
||||||
|
console.warn("idle timeout disabled with proxy auth and custom logout");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (authStore.logoutTimer) {
|
if (authStore.logoutTimer) {
|
||||||
clearTimeout(authStore.logoutTimer);
|
clearTimeout(authStore.logoutTimer);
|
||||||
}
|
}
|
||||||
@ -118,6 +124,8 @@ export function logout(reason?: string) {
|
|||||||
localStorage.setItem("jwt", "");
|
localStorage.setItem("jwt", "");
|
||||||
if (noAuth) {
|
if (noAuth) {
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
|
} else if (logoutPage !== "/login") {
|
||||||
|
document.location.href = `${logoutPage}`;
|
||||||
} else {
|
} else {
|
||||||
if (typeof reason === "string" && reason.trim() !== "") {
|
if (typeof reason === "string" && reason.trim() !== "") {
|
||||||
router.push({
|
router.push({
|
||||||
|
|||||||
@ -10,6 +10,7 @@ const version: string = window.FileBrowser.Version;
|
|||||||
const logoURL = `${staticURL}/img/logo.svg`;
|
const logoURL = `${staticURL}/img/logo.svg`;
|
||||||
const noAuth: boolean = window.FileBrowser.NoAuth;
|
const noAuth: boolean = window.FileBrowser.NoAuth;
|
||||||
const authMethod = window.FileBrowser.AuthMethod;
|
const authMethod = window.FileBrowser.AuthMethod;
|
||||||
|
const logoutPage: string = window.FileBrowser.LogoutPage;
|
||||||
const loginPage: boolean = window.FileBrowser.LoginPage;
|
const loginPage: boolean = window.FileBrowser.LoginPage;
|
||||||
const theme: UserTheme = window.FileBrowser.Theme;
|
const theme: UserTheme = window.FileBrowser.Theme;
|
||||||
const enableThumbs: boolean = window.FileBrowser.EnableThumbs;
|
const enableThumbs: boolean = window.FileBrowser.EnableThumbs;
|
||||||
@ -32,6 +33,7 @@ export {
|
|||||||
version,
|
version,
|
||||||
noAuth,
|
noAuth,
|
||||||
authMethod,
|
authMethod,
|
||||||
|
logoutPage,
|
||||||
loginPage,
|
loginPage,
|
||||||
theme,
|
theme,
|
||||||
enableThumbs,
|
enableThumbs,
|
||||||
|
|||||||
28
http/auth.go
28
http/auth.go
@ -12,7 +12,9 @@ import (
|
|||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/golang-jwt/jwt/v5/request"
|
"github.com/golang-jwt/jwt/v5/request"
|
||||||
|
|
||||||
|
fbAuth "github.com/filebrowser/filebrowser/v2/auth"
|
||||||
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
fbErrors "github.com/filebrowser/filebrowser/v2/errors"
|
||||||
|
"github.com/filebrowser/filebrowser/v2/settings"
|
||||||
"github.com/filebrowser/filebrowser/v2/users"
|
"github.com/filebrowser/filebrowser/v2/users"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,6 +63,22 @@ func (e extractor) ExtractToken(r *http.Request) (string, error) {
|
|||||||
return "", request.ErrNoTokenInRequest
|
return "", request.ErrNoTokenInRequest
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func renewableErr(err error, d *data) bool {
|
||||||
|
if d.settings.AuthMethod != fbAuth.MethodProxyAuth || err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.settings.LogoutPage == settings.DefaultLogoutPage {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if !errors.Is(err, jwt.ErrTokenExpired) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
func withUser(fn handleFunc) handleFunc {
|
func withUser(fn handleFunc) handleFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) {
|
||||||
keyFunc := func(_ *jwt.Token) (interface{}, error) {
|
keyFunc := func(_ *jwt.Token) (interface{}, error) {
|
||||||
@ -68,13 +86,9 @@ func withUser(fn handleFunc) handleFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var tk authToken
|
var tk authToken
|
||||||
token, err := request.ParseFromRequest(r, &extractor{}, keyFunc, request.WithClaims(&tk))
|
p := jwt.NewParser(jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}), jwt.WithExpirationRequired())
|
||||||
if err != nil || !token.Valid {
|
token, err := request.ParseFromRequest(r, &extractor{}, keyFunc, request.WithClaims(&tk), request.WithParser(p))
|
||||||
return http.StatusUnauthorized, nil
|
if (err != nil || !token.Valid) && !renewableErr(err, d) {
|
||||||
}
|
|
||||||
|
|
||||||
err = jwt.NewValidator(jwt.WithExpirationRequired()).Validate(tk)
|
|
||||||
if err != nil {
|
|
||||||
return http.StatusUnauthorized, nil
|
return http.StatusUnauthorized, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,6 +38,7 @@ func handleWithStaticData(w http.ResponseWriter, _ *http.Request, d *data, fSys
|
|||||||
"Signup": d.settings.Signup,
|
"Signup": d.settings.Signup,
|
||||||
"NoAuth": d.settings.AuthMethod == auth.MethodNoAuth,
|
"NoAuth": d.settings.AuthMethod == auth.MethodNoAuth,
|
||||||
"AuthMethod": d.settings.AuthMethod,
|
"AuthMethod": d.settings.AuthMethod,
|
||||||
|
"LogoutPage": d.settings.LogoutPage,
|
||||||
"LoginPage": auther.LoginPage(),
|
"LoginPage": auther.LoginPage(),
|
||||||
"CSS": false,
|
"CSS": false,
|
||||||
"ReCaptcha": false,
|
"ReCaptcha": false,
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const DefaultUsersHomeBasePath = "/users"
|
const DefaultUsersHomeBasePath = "/users"
|
||||||
|
const DefaultLogoutPage = "/login"
|
||||||
const DefaultMinimumPasswordLength = 12
|
const DefaultMinimumPasswordLength = 12
|
||||||
const DefaultFileMode = 0640
|
const DefaultFileMode = 0640
|
||||||
const DefaultDirMode = 0750
|
const DefaultDirMode = 0750
|
||||||
@ -27,6 +28,7 @@ type Settings struct {
|
|||||||
UserHomeBasePath string `json:"userHomeBasePath"`
|
UserHomeBasePath string `json:"userHomeBasePath"`
|
||||||
Defaults UserDefaults `json:"defaults"`
|
Defaults UserDefaults `json:"defaults"`
|
||||||
AuthMethod AuthMethod `json:"authMethod"`
|
AuthMethod AuthMethod `json:"authMethod"`
|
||||||
|
LogoutPage string `json:"logoutPage"`
|
||||||
Branding Branding `json:"branding"`
|
Branding Branding `json:"branding"`
|
||||||
Tus Tus `json:"tus"`
|
Tus Tus `json:"tus"`
|
||||||
Commands map[string][]string `json:"commands"`
|
Commands map[string][]string `json:"commands"`
|
||||||
|
|||||||
@ -30,24 +30,34 @@ func (s *Storage) Get() (*Settings, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if set.UserHomeBasePath == "" {
|
if set.UserHomeBasePath == "" {
|
||||||
set.UserHomeBasePath = DefaultUsersHomeBasePath
|
set.UserHomeBasePath = DefaultUsersHomeBasePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if set.LogoutPage == "" {
|
||||||
|
set.LogoutPage = DefaultLogoutPage
|
||||||
|
}
|
||||||
|
|
||||||
if set.MinimumPasswordLength == 0 {
|
if set.MinimumPasswordLength == 0 {
|
||||||
set.MinimumPasswordLength = DefaultMinimumPasswordLength
|
set.MinimumPasswordLength = DefaultMinimumPasswordLength
|
||||||
}
|
}
|
||||||
|
|
||||||
if set.Tus == (Tus{}) {
|
if set.Tus == (Tus{}) {
|
||||||
set.Tus = Tus{
|
set.Tus = Tus{
|
||||||
ChunkSize: DefaultTusChunkSize,
|
ChunkSize: DefaultTusChunkSize,
|
||||||
RetryCount: DefaultTusRetryCount,
|
RetryCount: DefaultTusRetryCount,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if set.FileMode == 0 {
|
if set.FileMode == 0 {
|
||||||
set.FileMode = DefaultFileMode
|
set.FileMode = DefaultFileMode
|
||||||
}
|
}
|
||||||
|
|
||||||
if set.DirMode == 0 {
|
if set.DirMode == 0 {
|
||||||
set.DirMode = DefaultDirMode
|
set.DirMode = DefaultDirMode
|
||||||
}
|
}
|
||||||
|
|
||||||
return set, nil
|
return set, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user