diff --git a/client/components/alert.scss b/client/components/alert.scss
index 9f8bc08e..a336e89b 100644
--- a/client/components/alert.scss
+++ b/client/components/alert.scss
@@ -14,6 +14,10 @@
&.success{
background: var(--success);
}
+ &.error{
+ background: var(--error);
+ color: var(--bg-color);
+ }
img{
max-width: 100%;
diff --git a/client/components/decorator.js b/client/components/decorator.js
index 64e8916c..a1f1dcef 100644
--- a/client/components/decorator.js
+++ b/client/components/decorator.js
@@ -53,6 +53,8 @@ export function ErrorPage(WrappedComponent) {
super(props);
this.state = {
error: null,
+ trace: null,
+ showTrace: false,
has_back_button: false,
};
}
@@ -69,7 +71,10 @@ export function ErrorPage(WrappedComponent) {
}
update(obj) {
- this.setState({ error: obj });
+ this.setState({
+ error: obj,
+ trace: new URLSearchParams(location.search).get("trace") || null,
+ });
}
navigate(e) {
@@ -84,17 +89,19 @@ export function ErrorPage(WrappedComponent) {
const message = this.state.error.message || t("There is nothing in here");
return (
-
{
this.state.has_back_button ? "back" : "home"
}
-
+
-
+
this.setState({showTrace: true})}>
{ t("Oops!") }
{ message }
+ { this.state.showTrace && this.state.trace &&
+ { this.state.trace } }
diff --git a/client/components/formbuilder.js b/client/components/formbuilder.js
index 8cb50f48..988f43f8 100644
--- a/client/components/formbuilder.js
+++ b/client/components/formbuilder.js
@@ -106,7 +106,7 @@ export class FormBuilder extends React.Component {
+ autoComplete="off" />
);
}
@@ -119,7 +119,6 @@ export class FormBuilder extends React.Component {
const FormElement = (props) => {
const id = props.id !== undefined ? { id: props.id } : {};
const struct = props.params;
- const autoCompleteProp = props.autoComplete || "off";
let $input = (
props.onChange(e.target.value)} {...id} name={struct.label}
type="text" defaultValue={struct.value} placeholder={ t(struct.placeholder) } />
@@ -138,7 +137,7 @@ const FormElement = (props) => {
onTextChange(e.target.value)} {...id}
name={struct.label} type="text" value={struct.value || ""}
placeholder={ t(struct.placeholder) } readOnly={struct.readonly}
- autoComplete={autoCompleteProp} autoCorrect="off" autoCapitalize="off"
+ autoComplete="off" autoCorrect="off" autoCapitalize="off"
spellCheck="false" />
);
if (list_id != null) {
@@ -190,7 +189,7 @@ const FormElement = (props) => {
$input = (
onPasswordChange(e.target.value)} {...id} name={struct.label}
type="password" value={struct.value || ""} placeholder={ t(struct.placeholder) }
- autoComplete={autoCompleteProp} autoCorrect="off" autoCapitalize="off"
+ autoComplete="off" autoCorrect="off" autoCapitalize="off"
spellCheck="false"/>
);
break;
@@ -206,7 +205,7 @@ const FormElement = (props) => {
);
}
+
+
+function LoaderWithTimeout({ callback = nop, timeout = 0 }) {
+ useEffect(() => {
+ const t = setTimeout(() => callback(), timeout);
+ return () => {
+ clearTimeout(t);
+ }
+ });
+ return (
+
+ )
+}
diff --git a/client/pages/connectpage/form.scss b/client/pages/connectpage/form.scss
index d6289f8f..726c5a1d 100644
--- a/client/pages/connectpage/form.scss
+++ b/client/pages/connectpage/form.scss
@@ -64,6 +64,9 @@
display: none;
}
}
+ .component_loader {
+ margin: 45px 0;
+ }
}
.component_page_connection_form.form-appear{
diff --git a/client/pages/error.scss b/client/pages/error.scss
index 538307fe..7115977e 100644
--- a/client/pages/error.scss
+++ b/client/pages/error.scss
@@ -5,7 +5,8 @@
flex-direction: column;
h1{margin: 5px 0; font-size: 3.1em;}
- h2{margin: 10px 0; font-weight: normal; opacity: 0.9; font-weight: 100;}
+ h2{margin: 10px 0; font-weight: normal; opacity: 0.9; font-weight: 100; }
+ code{margin-top: 20px; display: block; background:rgba(255,255,255,0.3);padding: 10px; border: 2px dashed rgba(0,0,0,0.1);}
p{font-style: italic;}
a{border-bottom: 1px dashed;}
}
diff --git a/client/pages/homepage.js b/client/pages/homepage.js
index e1b4ccda..3260f0c7 100644
--- a/client/pages/homepage.js
+++ b/client/pages/homepage.js
@@ -2,12 +2,19 @@ import React, { useState, useEffect } from "react";
import { Redirect } from "react-router";
import { Session } from "../model/";
-import { Loader } from "../components/";
+import { Loader, ErrorPage } from "../components/";
+import { t } from "../locales/";
-export function HomePage() {
+export function HomePageComponent({ error }) {
const [redirection, setRedirection] = useState(null);
useEffect(() => {
+ const p = new URLSearchParams(location.search);
+ if(p.get("error")) {
+ error(new Error(t(p.get("error"))));
+ return;
+ }
+
Session.currentUser().then((res) => {
if (res && res.is_authenticated === true) {
setRedirection(res.home ? `/files${res.home}` : "/files");
@@ -22,3 +29,5 @@ export function HomePage() {
}
return ( );
}
+
+export const HomePage = ErrorPage(HomePageComponent);
diff --git a/server/common/config.go b/server/common/config.go
index bd53cd90..78ac89c4 100644
--- a/server/common/config.go
+++ b/server/common/config.go
@@ -372,6 +372,7 @@ func (this Configuration) Export() interface{} {
RefreshAfterUpload bool `json:"refresh_after_upload"`
FilePageDefaultSort string `json:"default_sort"`
FilePageDefaultView string `json:"default_view"`
+ AuthMiddleware interface{} `json:"auth"`
}{
Editor: this.Get("general.editor").String(),
ForkButton: this.Get("general.fork_button").Bool(),
@@ -389,6 +390,12 @@ func (this Configuration) Export() interface{} {
RefreshAfterUpload: this.Get("general.refresh_after_upload").Bool(),
FilePageDefaultSort: this.Get("general.filepage_default_sort").String(),
FilePageDefaultView: this.Get("general.filepage_default_view").String(),
+ AuthMiddleware: func() string {
+ if this.Get("middleware.identity_provider.type").String() == "" {
+ return ""
+ }
+ return this.Get("middleware.attribute_mapping.related_backend").String()
+ }(),
}
}
diff --git a/server/common/plugin.go b/server/common/plugin.go
index f66d2917..f09375d5 100644
--- a/server/common/plugin.go
+++ b/server/common/plugin.go
@@ -6,11 +6,6 @@ import (
"net/http"
)
-const (
- PluginTypeBackend = "backend"
- PluginTypeMiddleware = "middleware"
-)
-
type Plugin struct {
Type string
Enable bool
@@ -18,13 +13,16 @@ type Plugin struct {
type Register struct{}
type Get struct{}
+type All struct{}
var Hooks = struct {
Get Get
Register Register
+ All All
}{
Get: Get{},
Register: Register{},
+ All: All{},
}
var process_file_content_before_send []func(io.ReadCloser, *App, *http.ResponseWriter, *http.Request) (io.ReadCloser, error)
@@ -54,6 +52,16 @@ func (this Get) Starter() []func(*mux.Router) {
return starter_process
}
+var authentication_middleware map[string]IAuth = make(map[string]IAuth, 0)
+
+func (this Register) AuthenticationMiddleware(id string, am IAuth) {
+ authentication_middleware[id] = am
+}
+
+func (this All) AuthenticationMiddleware() map[string]IAuth {
+ return authentication_middleware
+}
+
/*
* UI Overrides
* They are the means by which server plugin change the frontend behaviors.
diff --git a/server/common/response.go b/server/common/response.go
index 0df60be7..1392a2a3 100644
--- a/server/common/response.go
+++ b/server/common/response.go
@@ -102,6 +102,9 @@ func Page(stuff string) string {
body { text-align: center; padding-top: 50px; text-align: center; margin: 0; }
h1 { font-weight: 200; line-height: 1em; font-size: 40px; }
p { opacity: 0.8; font-size: 1.05em; }
+ form { max-width: 500px; margin: 0 auto; padding: 0 10px; text-align: left; }
+ button { padding: 7px 0px; width: 100%; margin-top: 5px; cursor: pointer; }
+ input, textarea { display: block; margin: 5px 0; border-radius: 3px; border: 2px solid rgba(0,0,0,0.1); outline: none; padding: 8px 10px; min-width: 100%; max-width: 100%; max-height: 80px; box-sizing: border-box; }
diff --git a/server/common/types.go b/server/common/types.go
index be8a60ed..894b4c8b 100644
--- a/server/common/types.go
+++ b/server/common/types.go
@@ -3,6 +3,7 @@ package common
import (
"encoding/json"
"io"
+ "net/http"
"os"
"time"
)
@@ -19,6 +20,12 @@ type IBackend interface {
LoginForm() Form
}
+type IAuth interface {
+ Setup() Form
+ EntryPoint(req *http.Request, res http.ResponseWriter)
+ Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error)
+}
+
type File struct {
FName string `json:"name"`
FType string `json:"type"`
diff --git a/server/ctrl/admin.go b/server/ctrl/admin.go
index 83242b77..78242828 100644
--- a/server/ctrl/admin.go
+++ b/server/ctrl/admin.go
@@ -84,3 +84,13 @@ func AdminBackend(ctx App, res http.ResponseWriter, req *http.Request) {
SendSuccessResultWithEtagAndGzip(res, req, backends)
return
}
+
+func AdminAuthenticationMiddleware(ctx App, res http.ResponseWriter, req *http.Request) {
+ drivers := Hooks.All.AuthenticationMiddleware()
+ middlewares := make(map[string]Form, len(drivers))
+ for id, driver := range drivers {
+ middlewares[id] = driver.Setup()
+ }
+ SendSuccessResultWithEtagAndGzip(res, req, middlewares)
+ return
+}
diff --git a/server/ctrl/session.go b/server/ctrl/session.go
index 25081bb0..c2ff577d 100644
--- a/server/ctrl/session.go
+++ b/server/ctrl/session.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"github.com/gorilla/mux"
. "github.com/mickael-kerjean/filestash/server/common"
+ . "github.com/mickael-kerjean/filestash/server/middleware"
"github.com/mickael-kerjean/filestash/server/model"
"net/http"
"strings"
@@ -19,7 +20,6 @@ func SessionGet(ctx App, res http.ResponseWriter, req *http.Request) {
r := Session{
IsAuth: false,
}
-
if ctx.Backend == nil {
SendSuccessResult(res, r)
return
@@ -100,11 +100,21 @@ func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) {
}
func SessionLogout(ctx App, res http.ResponseWriter, req *http.Request) {
- if ctx.Backend != nil {
- if obj, ok := ctx.Backend.(interface{ Close() error }); ok {
- go obj.Close()
- }
- }
+ go func() {
+ // user typically expect the logout to feel instant but in our case we still need to make sure
+ // the connection is closed as lot of backend requires to hold an active session which we cache.
+ // Whenever somebody logout after say 30 minutes idle, the logout would first create a connection
+ // then close which can take a few seconds and make for a bad user experience.
+ // By pushing that connection close in a goroutine, we make sure the logout is much faster for
+ // the user while still retaining that functionality.
+ SessionTry(func(c App, _res http.ResponseWriter, _req *http.Request) {
+ if c.Backend != nil {
+ if obj, ok := c.Backend.(interface{ Close() error }); ok {
+ go obj.Close()
+ }
+ }
+ })(ctx, res, req)
+ }()
http.SetCookie(res, &http.Cookie{
Name: COOKIE_NAME_AUTH,
Value: "",
@@ -149,3 +159,165 @@ func SessionOAuthBackend(ctx App, res http.ResponseWriter, req *http.Request) {
}
SendSuccessResult(res, obj.OAuthURL())
}
+
+func SessionAuthMiddleware(ctx App, res http.ResponseWriter, req *http.Request) {
+ SSOCookieName := "ssoref"
+
+ // Step0: Initialisation
+ _get := req.URL.Query()
+ plugin := func() IAuth {
+ selectedPluginId := Config.Get("middleware.identity_provider.type").String()
+ if selectedPluginId == "" {
+ return nil
+ }
+ for key, plugin := range Hooks.All.AuthenticationMiddleware() {
+ if key == selectedPluginId {
+ return plugin
+ }
+ }
+ return nil
+ }()
+ if plugin == nil {
+ http.Redirect(
+ res, req,
+ "/?error=Not%20Found&trace=middleware not found",
+ http.StatusTemporaryRedirect,
+ )
+ return
+ }
+ formData := map[string]string{}
+ switch req.Method {
+ case "GET":
+ for key, element := range _get {
+ if len(element) == 0 {
+ continue
+ }
+ formData[key] = element[0]
+ }
+ case "POST":
+ if err := req.ParseForm(); err != nil {
+ http.Redirect(
+ res, req,
+ "/?error=Not%20Valid&trace=parsing body - "+err.Error(),
+ http.StatusTemporaryRedirect,
+ )
+ return
+ }
+ for key, values := range req.Form {
+ if len(values) == 0 {
+ continue
+ }
+ formData[key] = values[0]
+ }
+ }
+
+ // Step1: Entrypoint of the authentication process is handled by the plugin
+ if req.Method == "GET" && _get.Get("action") == "redirect" {
+ if label := _get.Get("label"); label != "" {
+ http.SetCookie(res, &http.Cookie{
+ Name: SSOCookieName,
+ Value: label,
+ MaxAge: 60 * 10,
+ Path: COOKIE_PATH,
+ HttpOnly: true,
+ SameSite: http.SameSiteStrictMode,
+ })
+ }
+ plugin.EntryPoint(req, res)
+ return
+ }
+
+ // Step2: End of the authentication process. Could come from:
+ // - target of a html form. eg: ldap, mysql, ...
+ // - identity provider redirection uri. eg: oauth2, openid, ...
+ idpParams := map[string]string{}
+ if err := json.Unmarshal(
+ []byte(Config.Get("middleware.identity_provider.params").String()),
+ &idpParams,
+ ); err != nil {
+ http.Redirect(
+ res, req,
+ "/?error=Not%20Valid&trace=unpacking idp - "+err.Error(),
+ http.StatusTemporaryRedirect,
+ )
+ return
+ }
+
+ templateBind, err := plugin.Callback(formData, idpParams, res)
+ if err != nil {
+ Log.Debug("session::authMiddleware 'callback error - %s'", err.Error())
+ http.Redirect(res, req, req.URL.Path+"?action=redirect", http.StatusSeeOther)
+ return
+ }
+ Log.Debug("session::authMiddleware 'template bind - \"%+v\"'", templateBind)
+
+ // Step3: create a backend connection object
+ session, err := func(tb map[string]string) (map[string]string, error) {
+ refCookie, err := req.Cookie(SSOCookieName)
+ if err != nil {
+ return map[string]string{}, err
+ }
+ globalMapping := map[string]map[string]interface{}{}
+ if err = json.Unmarshal(
+ []byte(Config.Get("middleware.attribute_mapping.params").String()),
+ &globalMapping,
+ ); err != nil {
+ return map[string]string{}, err
+ }
+ mappingToUse := map[string]string{}
+ for k, v := range globalMapping[refCookie.Value] {
+ mappingToUse[k] = NewStringFromInterface(v)
+ }
+ mappingToUse["timestamp"] = time.Now().String()
+ return mappingToUse, nil
+ }(templateBind)
+ if err != nil {
+ Log.Debug("session::authMiddleware 'auth mapping failed %s'", err.Error())
+ http.Redirect(
+ res, req,
+ "/?error=Not%20Valid&trace=mapping_error - "+err.Error(),
+ http.StatusTemporaryRedirect,
+ )
+ return
+ }
+ if _, err := model.NewBackend(&ctx, session); err != nil {
+ Log.Debug("session::authMiddleware 'backend connection failed %+v - %s'", session, err.Error())
+ http.Redirect(
+ res, req,
+ "/?error=Not%20Valid&trace=backend error - "+err.Error(),
+ http.StatusTemporaryRedirect,
+ )
+ return
+ }
+
+ // Step4: persist connection with a cookie
+ s, err := json.Marshal(session)
+ if err != nil {
+ Log.Debug("session::authMiddleware 'session marshal error %+v'", session)
+ SendErrorResult(res, ErrNotValid)
+ return
+ }
+ obfuscate, err := EncryptString(SECRET_KEY_DERIVATE_FOR_USER, string(s))
+ if err != nil {
+ Log.Debug("session::authMiddleware 'encryption error - %s", err.Error())
+ SendErrorResult(res, ErrNotValid)
+ return
+ }
+ http.SetCookie(res, &http.Cookie{
+ Name: COOKIE_NAME_AUTH,
+ Value: obfuscate,
+ MaxAge: 60 * Config.Get("general.cookie_timeout").Int(),
+ Path: COOKIE_PATH,
+ HttpOnly: true,
+ SameSite: http.SameSiteStrictMode,
+ })
+ http.SetCookie(res, &http.Cookie{
+ Name: SSOCookieName,
+ Value: "",
+ MaxAge: -1,
+ Path: COOKIE_PATH,
+ HttpOnly: true,
+ SameSite: http.SameSiteStrictMode,
+ })
+ http.Redirect(res, req, "/", http.StatusTemporaryRedirect)
+}
diff --git a/server/main.go b/server/main.go
index 40ef016e..76fe9399 100644
--- a/server/main.go
+++ b/server/main.go
@@ -30,10 +30,11 @@ func Init(a *App) {
session.HandleFunc("", NewMiddlewareChain(SessionGet, middlewares, *a)).Methods("GET")
middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureAjax, BodyParser}
session.HandleFunc("", NewMiddlewareChain(SessionAuthenticate, middlewares, *a)).Methods("POST")
- middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureAjax, SessionTry}
+ middlewares = []Middleware{ApiHeaders, SecureHeaders, SecureAjax}
session.HandleFunc("", NewMiddlewareChain(SessionLogout, middlewares, *a)).Methods("DELETE")
middlewares = []Middleware{ApiHeaders, SecureHeaders}
session.HandleFunc("/auth/{service}", NewMiddlewareChain(SessionOAuthBackend, middlewares, *a)).Methods("GET")
+ session.HandleFunc("/auth/", NewMiddlewareChain(SessionAuthMiddleware, middlewares, *a)).Methods("GET", "POST")
// API for admin
middlewares = []Middleware{ApiHeaders, SecureAjax}
@@ -87,6 +88,7 @@ func Init(a *App) {
middlewares = []Middleware{ApiHeaders}
r.HandleFunc("/api/config", NewMiddlewareChain(PublicConfigHandler, middlewares, *a)).Methods("GET")
r.HandleFunc("/api/backend", NewMiddlewareChain(AdminBackend, middlewares, *a)).Methods("GET")
+ r.HandleFunc("/api/middlewares/authentication", NewMiddlewareChain(AdminAuthenticationMiddleware, middlewares, *a)).Methods("GET")
middlewares = []Middleware{StaticHeaders}
r.PathPrefix("/assets").Handler(http.HandlerFunc(NewMiddlewareChain(StaticHandler(FILE_ASSETS), middlewares, *a))).Methods("GET")
r.HandleFunc("/favicon.ico", NewMiddlewareChain(StaticHandler(FILE_ASSETS+"/assets/logo/"), middlewares, *a)).Methods("GET")
@@ -110,7 +112,7 @@ func Init(a *App) {
initPluginsRoutes(r, a)
r.PathPrefix("/admin").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")
- r.PathPrefix("/").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET")
+ r.PathPrefix("/").Handler(http.HandlerFunc(NewMiddlewareChain(IndexHandler(FILE_INDEX), middlewares, *a))).Methods("GET", "POST")
// Routes are served via plugins to avoid getting stuck with plain HTTP. The idea is to
// support many more protocols in the future: HTTPS, HTTP2, TOR or whatever that sounds
diff --git a/server/plugin/index.go b/server/plugin/index.go
index 8b0015c2..0266f5d5 100644
--- a/server/plugin/index.go
+++ b/server/plugin/index.go
@@ -2,6 +2,10 @@ package plugin
import (
. "github.com/mickael-kerjean/filestash/server/common"
+ _ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_admin"
+ _ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_ldap"
+ _ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_openid"
+ _ "github.com/mickael-kerjean/filestash/server/plugin/plg_authenticate_saml"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_backblaze"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dav"
_ "github.com/mickael-kerjean/filestash/server/plugin/plg_backend_dropbox"
diff --git a/server/plugin/plg_authenticate_ldap/index.go b/server/plugin/plg_authenticate_ldap/index.go
new file mode 100644
index 00000000..92c92dca
--- /dev/null
+++ b/server/plugin/plg_authenticate_ldap/index.go
@@ -0,0 +1,66 @@
+package plg_authenticate_ldap
+
+import (
+ . "github.com/mickael-kerjean/filestash/server/common"
+ "net/http"
+)
+
+func init() {
+ Hooks.Register.AuthenticationMiddleware("ldap", Ldap{})
+}
+
+type Ldap struct{}
+
+func (this Ldap) Setup() Form {
+ return Form{
+ Elmnts: []FormElement{
+ {
+ Name: "type",
+ Type: "hidden",
+ Value: "ldap",
+ },
+ {
+ Name: "Hostname",
+ Type: "text",
+ Value: "",
+ Placeholder: "eg: ldap.example.com",
+ },
+ {
+ Name: "Port",
+ Type: "text",
+ Value: "",
+ Placeholder: "eg: 389",
+ },
+ {
+ Name: "Bind DN",
+ Type: "text",
+ Value: "",
+ Placeholder: "Bind DN",
+ },
+ {
+ Name: "Bind DN Password",
+ Type: "text",
+ Value: "",
+ Placeholder: "Bind CN Password",
+ },
+ {
+ Name: "Base DN",
+ Type: "text",
+ Value: "",
+ Placeholder: "Base DN",
+ },
+ },
+ }
+}
+
+func (this Ldap) EntryPoint(req *http.Request, res http.ResponseWriter) {
+ http.Redirect(
+ res, req,
+ "/?error=ldap is available for enterprise customer, see https://www.filestash.app/pricing/?modal=enterprise",
+ http.StatusTemporaryRedirect,
+ )
+}
+
+func (this Ldap) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) {
+ return nil, ErrNotImplemented
+}
diff --git a/server/plugin/plg_authenticate_openid/index.go b/server/plugin/plg_authenticate_openid/index.go
new file mode 100644
index 00000000..63f78d99
--- /dev/null
+++ b/server/plugin/plg_authenticate_openid/index.go
@@ -0,0 +1,57 @@
+package plg_authenticate_openid
+
+import (
+ . "github.com/mickael-kerjean/filestash/server/common"
+ "net/http"
+)
+
+func init() {
+ Hooks.Register.AuthenticationMiddleware("openid", OpenID{})
+}
+
+type OpenID struct{}
+
+func (this OpenID) Setup() Form {
+ return Form{
+ Elmnts: []FormElement{
+ {
+ Name: "type",
+ Type: "hidden",
+ Value: "openid",
+ },
+ {
+ Name: "OpenID Config URL",
+ Type: "text",
+ ReadOnly: true,
+ Value: "",
+ Placeholder: "OpenID Configuration URL",
+ },
+ {
+ Name: "Client ID",
+ Type: "text",
+ ReadOnly: true,
+ Value: "",
+ Placeholder: "Client ID",
+ },
+ {
+ Name: "Scope",
+ Type: "text",
+ ReadOnly: true,
+ Value: "",
+ Placeholder: "OpenID Scope: default 'openid'",
+ },
+ },
+ }
+}
+
+func (this OpenID) EntryPoint(req *http.Request, res http.ResponseWriter) {
+ http.Redirect(
+ res, req,
+ "/?error=oidc is available for enterprise customer, see https://www.filestash.app/pricing/?modal=enterprise",
+ http.StatusTemporaryRedirect,
+ )
+}
+
+func (this OpenID) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) {
+ return nil, ErrNotImplemented
+}
diff --git a/server/plugin/plg_authenticate_saml/index.go b/server/plugin/plg_authenticate_saml/index.go
new file mode 100644
index 00000000..a4a34435
--- /dev/null
+++ b/server/plugin/plg_authenticate_saml/index.go
@@ -0,0 +1,50 @@
+package plg_authenticate_saml
+
+import (
+ . "github.com/mickael-kerjean/filestash/server/common"
+ "net/http"
+)
+
+func init() {
+ Hooks.Register.AuthenticationMiddleware("saml", Saml{})
+}
+
+type Saml struct{}
+
+func (this Saml) Setup() Form {
+ return Form{
+ Elmnts: []FormElement{
+ {
+ Name: "type",
+ Type: "hidden",
+ Value: "saml",
+ },
+ {
+ Name: "SP Metadata",
+ Type: "text",
+ ReadOnly: true,
+ Value: "",
+ Placeholder: "Metadata you need to import onto your IDP",
+ },
+ {
+ Name: "IDP Metadata",
+ Type: "text",
+ ReadOnly: true,
+ Value: "",
+ Placeholder: "Metadata url given by your IDP",
+ },
+ },
+ }
+}
+
+func (this Saml) EntryPoint(req *http.Request, res http.ResponseWriter) {
+ http.Redirect(
+ res, req,
+ "/?error=saml is available for enterprise customer, see https://www.filestash.app/pricing/?modal=enterprise",
+ http.StatusTemporaryRedirect,
+ )
+}
+
+func (this Saml) Callback(formData map[string]string, idpParams map[string]string, res http.ResponseWriter) (map[string]string, error) {
+ return nil, ErrNotImplemented
+}