WebAdmin: use the new theme for the login and setup page
Signed-off-by: Nicola Murino <nicola.murino@gmail.com>
This commit is contained in:
parent
7318d1f32a
commit
3e47a4f664
25 changed files with 514 additions and 919 deletions
|
@ -366,7 +366,7 @@ SFTPGo makes use of the third party libraries listed inside [go.mod](./go.mod).
|
|||
|
||||
We are very grateful to all the people who contributed with ideas and/or pull requests.
|
||||
|
||||
Thank you [ysura](https://www.ysura.com/) for granting us stable access to a test AWS S3 account.
|
||||
Thank you to [ysura](https://www.ysura.com/) for granting us stable access to a test AWS S3 account.
|
||||
|
||||
Thank you to [KeenThemes](https://keenthemes.com/) for granting us a custom license to use their amazing [Mega Bundle](https://keenthemes.com/products/templates-mega-bundle) for SFTPGo UI.
|
||||
|
||||
|
@ -374,7 +374,7 @@ Thank you to [KeenThemes](https://keenthemes.com/) for granting us a custom lice
|
|||
|
||||
GNU AGPL-3.0-only
|
||||
|
||||
The [theme](https://keenthemes.com/products/templates-mega-bundle) used in WebClient UI is proprietary, this means:
|
||||
The [theme](https://keenthemes.com/products/templates-mega-bundle) used in WebAdmin and WebClient user interfaces is proprietary, this means:
|
||||
|
||||
- KeenThemes HTML/CSS/JS components are allowed for use only within the SFTPGo product and restricted to be used in a resealable HTML template that can compete with KeenThemes products anyhow.
|
||||
- The SFTPGo WebClient UI (HTML, CSS and JS components) based on this theme is allowed for use only within the SFTPGo product and therefore cannot be used in derivative works/products without an explicit grant from the [SFTPGo Team](mailto:support@sftpgo.com).
|
||||
- The SFTPGo WebAdmin and WebClient user interfaces (HTML, CSS and JS components) based on this theme are allowed for use only within the SFTPGo product and therefore cannot be used in derivative works/products without an explicit grant from the [SFTPGo Team](mailto:support@sftpgo.com).
|
||||
|
|
|
@ -262,7 +262,7 @@ func resetAdminPassword(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_, _, err = handleResetPassword(r, req.Code, req.Password, true)
|
||||
_, _, err = handleResetPassword(r, req.Code, req.Password, req.Password, true)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
|
|
|
@ -243,7 +243,7 @@ func resetUserPassword(w http.ResponseWriter, r *http.Request) {
|
|||
sendAPIResponse(w, r, err, "", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
_, _, err = handleResetPassword(r, req.Code, req.Password, false)
|
||||
_, _, err = handleResetPassword(r, req.Code, req.Password, req.Password, false)
|
||||
if err != nil {
|
||||
sendAPIResponse(w, r, err, "", getRespStatus(err))
|
||||
return
|
||||
|
|
|
@ -721,7 +721,7 @@ func handleForgotPassword(r *http.Request, username string, isAdmin bool) error
|
|||
return resetCodesMgr.Add(c)
|
||||
}
|
||||
|
||||
func handleResetPassword(r *http.Request, code, newPassword string, isAdmin bool) (
|
||||
func handleResetPassword(r *http.Request, code, newPassword, confirmPassword string, isAdmin bool) (
|
||||
*dataprovider.Admin, *dataprovider.User, error,
|
||||
) {
|
||||
var admin dataprovider.Admin
|
||||
|
@ -734,6 +734,10 @@ func handleResetPassword(r *http.Request, code, newPassword string, isAdmin bool
|
|||
if code == "" {
|
||||
return &admin, &user, util.NewValidationError("please set a confirmation code")
|
||||
}
|
||||
if newPassword != confirmPassword {
|
||||
return &admin, &user, util.NewI18nError(errors.New("the two password fields do not match"), util.I18nErrorChangePwdNoMatch)
|
||||
}
|
||||
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
resetCode, err := resetCodesMgr.Get(code)
|
||||
if err != nil {
|
||||
|
|
|
@ -441,13 +441,9 @@ func (b *UIBranding) check(isWebClient bool) {
|
|||
b.DefaultCSS[idx] = util.CleanPath(b.DefaultCSS[idx])
|
||||
}
|
||||
} else {
|
||||
if isWebClient {
|
||||
b.DefaultCSS = []string{
|
||||
"/assets/plugins/global/plugins.bundle.css",
|
||||
"/assets/css/style.bundle.css",
|
||||
}
|
||||
} else {
|
||||
b.DefaultCSS = []string{"/css/sb-admin-2.min.css"}
|
||||
b.DefaultCSS = []string{
|
||||
"/assets/plugins/global/plugins.bundle.css",
|
||||
"/assets/css/style.bundle.css",
|
||||
}
|
||||
}
|
||||
for idx := range b.ExtraCSS {
|
||||
|
|
|
@ -161,7 +161,7 @@ func (s *httpdServer) refreshCookie(next http.Handler) http.Handler {
|
|||
}
|
||||
|
||||
func (s *httpdServer) renderClientLoginPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) {
|
||||
data := clientLoginPage{
|
||||
data := loginPage{
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
Title: util.I18nLoginTitle,
|
||||
CurrentURL: webClientLoginPath,
|
||||
|
@ -183,7 +183,7 @@ func (s *httpdServer) renderClientLoginPage(w http.ResponseWriter, r *http.Reque
|
|||
if s.binding.OIDC.isEnabled() && !s.binding.isWebClientOIDCLoginDisabled() {
|
||||
data.OpenIDLoginURL = webClientOIDCLoginPath
|
||||
}
|
||||
renderClientTemplate(w, templateClientLogin, data)
|
||||
renderClientTemplate(w, templateCommonLogin, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebClientLogout(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -296,14 +296,8 @@ func (s *httpdServer) handleWebClientPasswordResetPost(w http.ResponseWriter, r
|
|||
}
|
||||
newPassword := strings.TrimSpace(r.Form.Get("password"))
|
||||
confirmPassword := strings.TrimSpace(r.Form.Get("confirm_password"))
|
||||
if newPassword != confirmPassword {
|
||||
s.renderClientResetPwdPage(w, r, util.NewI18nError(
|
||||
errors.New("the two password fields do not match"),
|
||||
util.I18nErrorChangePwdNoMatch), ipAddr)
|
||||
return
|
||||
}
|
||||
_, user, err := handleResetPassword(r, strings.TrimSpace(r.Form.Get("code")),
|
||||
newPassword, false)
|
||||
newPassword, confirmPassword, false)
|
||||
if err != nil {
|
||||
s.renderClientResetPwdPage(w, r, util.NewI18nError(err, util.I18nErrorChangePwdGeneric), ipAddr)
|
||||
return
|
||||
|
@ -457,17 +451,18 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter,
|
|||
}
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
if err := r.ParseForm(); err != nil {
|
||||
s.renderTwoFactorRecoveryPage(w, r, err.Error(), ipAddr)
|
||||
s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm), ipAddr)
|
||||
return
|
||||
}
|
||||
username := claims.Username
|
||||
recoveryCode := strings.TrimSpace(r.Form.Get("recovery_code"))
|
||||
if username == "" || recoveryCode == "" {
|
||||
s.renderTwoFactorRecoveryPage(w, r, "Invalid credentials", ipAddr)
|
||||
s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials),
|
||||
ipAddr)
|
||||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
|
||||
s.renderTwoFactorRecoveryPage(w, r, err.Error(), ipAddr)
|
||||
s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF), ipAddr)
|
||||
return
|
||||
}
|
||||
admin, err := dataprovider.AdminExists(username)
|
||||
|
@ -475,11 +470,12 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter,
|
|||
if errors.Is(err, util.ErrNotFound) {
|
||||
handleDefenderEventLoginFailed(ipAddr, err) //nolint:errcheck
|
||||
}
|
||||
s.renderTwoFactorRecoveryPage(w, r, "Invalid credentials", ipAddr)
|
||||
s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials),
|
||||
ipAddr)
|
||||
return
|
||||
}
|
||||
if !admin.Filters.TOTPConfig.Enabled {
|
||||
s.renderTwoFactorRecoveryPage(w, r, "Two factory authentication is not enabled", ipAddr)
|
||||
s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(util.NewValidationError("two factory authentication is not enabled"), util.I18n2FADisabled), ipAddr)
|
||||
return
|
||||
}
|
||||
for idx, code := range admin.Filters.RecoveryCodes {
|
||||
|
@ -489,7 +485,8 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter,
|
|||
}
|
||||
if code.Secret.GetPayload() == recoveryCode {
|
||||
if code.Used {
|
||||
s.renderTwoFactorRecoveryPage(w, r, "This recovery code was already used", ipAddr)
|
||||
s.renderTwoFactorRecoveryPage(w, r,
|
||||
util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials), ipAddr)
|
||||
return
|
||||
}
|
||||
admin.Filters.RecoveryCodes[idx].Used = true
|
||||
|
@ -504,7 +501,8 @@ func (s *httpdServer) handleWebAdminTwoFactorRecoveryPost(w http.ResponseWriter,
|
|||
}
|
||||
}
|
||||
handleDefenderEventLoginFailed(ipAddr, dataprovider.ErrInvalidCredentials) //nolint:errcheck
|
||||
s.renderTwoFactorRecoveryPage(w, r, "Invalid recovery code", ipAddr)
|
||||
s.renderTwoFactorRecoveryPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials),
|
||||
ipAddr)
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -516,18 +514,19 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http
|
|||
}
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
if err := r.ParseForm(); err != nil {
|
||||
s.renderTwoFactorPage(w, r, err.Error(), ipAddr)
|
||||
s.renderTwoFactorPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm), ipAddr)
|
||||
return
|
||||
}
|
||||
username := claims.Username
|
||||
passcode := strings.TrimSpace(r.Form.Get("passcode"))
|
||||
if username == "" || passcode == "" {
|
||||
s.renderTwoFactorPage(w, r, "Invalid credentials", ipAddr)
|
||||
s.renderTwoFactorPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials),
|
||||
ipAddr)
|
||||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
|
||||
err = handleDefenderEventLoginFailed(ipAddr, err)
|
||||
s.renderTwoFactorPage(w, r, err.Error(), ipAddr)
|
||||
s.renderTwoFactorPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF), ipAddr)
|
||||
return
|
||||
}
|
||||
admin, err := dataprovider.AdminExists(username)
|
||||
|
@ -535,11 +534,11 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http
|
|||
if errors.Is(err, util.ErrNotFound) {
|
||||
handleDefenderEventLoginFailed(ipAddr, err) //nolint:errcheck
|
||||
}
|
||||
s.renderTwoFactorPage(w, r, "Invalid credentials", ipAddr)
|
||||
s.renderTwoFactorPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCredentials), ipAddr)
|
||||
return
|
||||
}
|
||||
if !admin.Filters.TOTPConfig.Enabled {
|
||||
s.renderTwoFactorPage(w, r, "Two factory authentication is not enabled", ipAddr)
|
||||
s.renderTwoFactorPage(w, r, util.NewI18nError(common.ErrInternalFailure, util.I18n2FADisabled), ipAddr)
|
||||
return
|
||||
}
|
||||
err = admin.Filters.TOTPConfig.Secret.Decrypt()
|
||||
|
@ -551,7 +550,8 @@ func (s *httpdServer) handleWebAdminTwoFactorPost(w http.ResponseWriter, r *http
|
|||
admin.Filters.TOTPConfig.Secret.GetPayload())
|
||||
if !match || err != nil {
|
||||
handleDefenderEventLoginFailed(ipAddr, dataprovider.ErrInvalidCredentials) //nolint:errcheck
|
||||
s.renderTwoFactorPage(w, r, "Invalid authentication code", ipAddr)
|
||||
s.renderTwoFactorPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials),
|
||||
ipAddr)
|
||||
return
|
||||
}
|
||||
s.loginAdmin(w, r, &admin, true, s.renderTwoFactorPage, ipAddr)
|
||||
|
@ -562,34 +562,36 @@ func (s *httpdServer) handleWebAdminLoginPost(w http.ResponseWriter, r *http.Req
|
|||
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
if err := r.ParseForm(); err != nil {
|
||||
s.renderAdminLoginPage(w, r, err.Error(), ipAddr)
|
||||
s.renderAdminLoginPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm), ipAddr)
|
||||
return
|
||||
}
|
||||
username := strings.TrimSpace(r.Form.Get("username"))
|
||||
password := strings.TrimSpace(r.Form.Get("password"))
|
||||
if username == "" || password == "" {
|
||||
s.renderAdminLoginPage(w, r, "Invalid credentials", ipAddr)
|
||||
s.renderAdminLoginPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials),
|
||||
ipAddr)
|
||||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
|
||||
s.renderAdminLoginPage(w, r, err.Error(), ipAddr)
|
||||
s.renderAdminLoginPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidCSRF), ipAddr)
|
||||
return
|
||||
}
|
||||
admin, err := dataprovider.CheckAdminAndPass(username, password, ipAddr)
|
||||
if err != nil {
|
||||
err = handleDefenderEventLoginFailed(ipAddr, err)
|
||||
s.renderAdminLoginPage(w, r, err.Error(), ipAddr)
|
||||
handleDefenderEventLoginFailed(ipAddr, err)
|
||||
s.renderAdminLoginPage(w, r, util.NewI18nError(dataprovider.ErrInvalidCredentials, util.I18nErrorInvalidCredentials),
|
||||
ipAddr)
|
||||
return
|
||||
}
|
||||
s.loginAdmin(w, r, &admin, false, s.renderAdminLoginPage, ipAddr)
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, r *http.Request, error, ip string) {
|
||||
func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) {
|
||||
data := loginPage{
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
Title: util.I18nLoginTitle,
|
||||
CurrentURL: webAdminLoginPath,
|
||||
Error: error,
|
||||
Error: err,
|
||||
CSRFToken: createCSRFToken(ip),
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
FormDisabled: s.binding.isWebAdminLoginFormDisabled(),
|
||||
|
@ -604,7 +606,7 @@ func (s *httpdServer) renderAdminLoginPage(w http.ResponseWriter, r *http.Reques
|
|||
if s.binding.OIDC.hasRoles() && !s.binding.isWebAdminOIDCLoginDisabled() {
|
||||
data.OpenIDLoginURL = webAdminOIDCLoginPath
|
||||
}
|
||||
renderAdminTemplate(w, templateLogin, data)
|
||||
renderAdminTemplate(w, templateCommonLogin, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAdminLogin(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -613,7 +615,8 @@ func (s *httpdServer) handleWebAdminLogin(w http.ResponseWriter, r *http.Request
|
|||
http.Redirect(w, r, webAdminSetupPath, http.StatusFound)
|
||||
return
|
||||
}
|
||||
s.renderAdminLoginPage(w, r, getFlashMessage(w, r).ErrorString, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
msg := getFlashMessage(w, r)
|
||||
s.renderAdminLoginPage(w, r, msg.getI18nError(), util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAdminLogout(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -651,22 +654,19 @@ func (s *httpdServer) handleWebAdminPasswordResetPost(w http.ResponseWriter, r *
|
|||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
s.renderResetPwdPage(w, r, err.Error(), ipAddr)
|
||||
s.renderResetPwdPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm), ipAddr)
|
||||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
|
||||
s.renderForbiddenPage(w, r, err.Error())
|
||||
return
|
||||
}
|
||||
newPassword := strings.TrimSpace(r.Form.Get("password"))
|
||||
confirmPassword := strings.TrimSpace(r.Form.Get("confirm_password"))
|
||||
admin, _, err := handleResetPassword(r, strings.TrimSpace(r.Form.Get("code")),
|
||||
strings.TrimSpace(r.Form.Get("password")), true)
|
||||
newPassword, confirmPassword, true)
|
||||
if err != nil {
|
||||
var e *util.ValidationError
|
||||
if errors.As(err, &e) {
|
||||
s.renderResetPwdPage(w, r, e.GetErrorString(), ipAddr)
|
||||
return
|
||||
}
|
||||
s.renderResetPwdPage(w, r, err.Error(), ipAddr)
|
||||
s.renderResetPwdPage(w, r, util.NewI18nError(err, util.I18nErrorChangePwdGeneric), ipAddr)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -679,12 +679,12 @@ func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Req
|
|||
s.renderBadRequestPage(w, r, errors.New("an admin user already exists"))
|
||||
return
|
||||
}
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
s.renderAdminSetupPage(w, r, "", err.Error())
|
||||
s.renderAdminSetupPage(w, r, "", ipAddr, util.NewI18nError(err, util.I18nErrorInvalidForm))
|
||||
return
|
||||
}
|
||||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
|
||||
s.renderForbiddenPage(w, r, err.Error())
|
||||
return
|
||||
|
@ -694,19 +694,26 @@ func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Req
|
|||
confirmPassword := strings.TrimSpace(r.Form.Get("confirm_password"))
|
||||
installCode := strings.TrimSpace(r.Form.Get("install_code"))
|
||||
if installationCode != "" && installCode != resolveInstallationCode() {
|
||||
s.renderAdminSetupPage(w, r, username, fmt.Sprintf("%v mismatch", installationCodeHint))
|
||||
s.renderAdminSetupPage(w, r, username, ipAddr,
|
||||
util.NewI18nError(
|
||||
util.NewValidationError(fmt.Sprintf("%v mismatch", installationCodeHint)),
|
||||
util.I18nErrorSetupInstallCode),
|
||||
)
|
||||
return
|
||||
}
|
||||
if username == "" {
|
||||
s.renderAdminSetupPage(w, r, username, "Please set a username")
|
||||
s.renderAdminSetupPage(w, r, username, ipAddr,
|
||||
util.NewI18nError(util.NewValidationError("please set a username"), util.I18nError500Message))
|
||||
return
|
||||
}
|
||||
if password == "" {
|
||||
s.renderAdminSetupPage(w, r, username, "Please set a password")
|
||||
s.renderAdminSetupPage(w, r, username, ipAddr,
|
||||
util.NewI18nError(util.NewValidationError("please set a password"), util.I18nError500Message))
|
||||
return
|
||||
}
|
||||
if password != confirmPassword {
|
||||
s.renderAdminSetupPage(w, r, username, "Passwords mismatch")
|
||||
s.renderAdminSetupPage(w, r, username, ipAddr,
|
||||
util.NewI18nError(errors.New("the two password fields do not match"), util.I18nErrorChangePwdNoMatch))
|
||||
return
|
||||
}
|
||||
admin := dataprovider.Admin{
|
||||
|
@ -717,7 +724,7 @@ func (s *httpdServer) handleWebAdminSetupPost(w http.ResponseWriter, r *http.Req
|
|||
}
|
||||
err = dataprovider.AddAdmin(&admin, username, ipAddr, "")
|
||||
if err != nil {
|
||||
s.renderAdminSetupPage(w, r, username, err.Error())
|
||||
s.renderAdminSetupPage(w, r, username, ipAddr, util.NewI18nError(err, util.I18nError500Message))
|
||||
return
|
||||
}
|
||||
s.loginAdmin(w, r, &admin, false, nil, ipAddr)
|
||||
|
@ -772,7 +779,7 @@ func (s *httpdServer) loginUser(
|
|||
|
||||
func (s *httpdServer) loginAdmin(
|
||||
w http.ResponseWriter, r *http.Request, admin *dataprovider.Admin,
|
||||
isSecondFactorAuth bool, errorFunc func(w http.ResponseWriter, r *http.Request, error, ip string),
|
||||
isSecondFactorAuth bool, errorFunc func(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string),
|
||||
ipAddr string,
|
||||
) {
|
||||
c := jwtTokenClaims{
|
||||
|
@ -792,10 +799,10 @@ func (s *httpdServer) loginAdmin(
|
|||
if err != nil {
|
||||
logger.Warn(logSender, "", "unable to set admin login cookie %v", err)
|
||||
if errorFunc == nil {
|
||||
s.renderAdminSetupPage(w, r, admin.Username, err.Error())
|
||||
s.renderAdminSetupPage(w, r, admin.Username, ipAddr, util.NewI18nError(err, util.I18nError500Message))
|
||||
return
|
||||
}
|
||||
errorFunc(w, r, err.Error(), ipAddr)
|
||||
errorFunc(w, r, util.NewI18nError(err, util.I18nError500Message), ipAddr)
|
||||
return
|
||||
}
|
||||
if isSecondFactorAuth {
|
||||
|
|
|
@ -49,6 +49,7 @@ const (
|
|||
templateCommonCSS = "sftpgo.css"
|
||||
templateCommonBase = "base.html"
|
||||
templateCommonBaseLogin = "baselogin.html"
|
||||
templateCommonLogin = "login.html"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -64,7 +65,7 @@ type commonBasePage struct {
|
|||
type loginPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
AltLoginURL string
|
||||
AltLoginName string
|
||||
|
@ -78,7 +79,7 @@ type loginPage struct {
|
|||
type twoFactorPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
RecoveryURL string
|
||||
Title string
|
||||
|
@ -88,7 +89,7 @@ type twoFactorPage struct {
|
|||
type forgotPwdPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
LoginURL string
|
||||
Title string
|
||||
|
@ -98,7 +99,7 @@ type forgotPwdPage struct {
|
|||
type resetPwdPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
LoginURL string
|
||||
Title string
|
||||
|
|
|
@ -72,7 +72,6 @@ const (
|
|||
const (
|
||||
templateAdminDir = "webadmin"
|
||||
templateBase = "base.html"
|
||||
templateBaseLogin = "baselogin.html"
|
||||
templateFsConfig = "fsconfig.html"
|
||||
templateSharedComponents = "sharedcomponents.html"
|
||||
templateUsers = "users.html"
|
||||
|
@ -93,7 +92,6 @@ const (
|
|||
templateEvents = "events.html"
|
||||
templateMessage = "message.html"
|
||||
templateStatus = "status.html"
|
||||
templateLogin = "login.html"
|
||||
templateDefender = "defender.html"
|
||||
templateIPLists = "iplists.html"
|
||||
templateIPList = "iplist.html"
|
||||
|
@ -119,8 +117,6 @@ const (
|
|||
pageIPListsTitle = "IP Lists"
|
||||
pageEventsTitle = "Logs"
|
||||
pageConfigsTitle = "Configurations"
|
||||
pageForgotPwdTitle = "Forgot password"
|
||||
pageResetPwdTitle = "Reset password"
|
||||
pageSetupTitle = "Create first admin user"
|
||||
defaultQueryLimit = 1000
|
||||
inversePatternType = "inverse"
|
||||
|
@ -323,12 +319,16 @@ type ipListPage struct {
|
|||
}
|
||||
|
||||
type setupPage struct {
|
||||
basePage
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
Username string
|
||||
HasInstallationCode bool
|
||||
InstallationCodeHint string
|
||||
HideSupportLink bool
|
||||
Error string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
type folderPage struct {
|
||||
|
@ -506,9 +506,9 @@ func loadAdminTemplates(templatesPath string) {
|
|||
filepath.Join(templatesPath, templateAdminDir, templateStatus),
|
||||
}
|
||||
loginPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonCSS),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBaseLogin),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonLogin),
|
||||
}
|
||||
maintenancePaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonCSS),
|
||||
|
@ -536,26 +536,28 @@ func loadAdminTemplates(templatesPath string) {
|
|||
filepath.Join(templatesPath, templateAdminDir, templateMFA),
|
||||
}
|
||||
twoFactorPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonCSS),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBaseLogin),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateTwoFactor),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateTwoFactor),
|
||||
}
|
||||
twoFactorRecoveryPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonCSS),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBaseLogin),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateTwoFactorRecovery),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateTwoFactorRecovery),
|
||||
}
|
||||
setupPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonCSS),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateAdminDir, templateSetup),
|
||||
}
|
||||
forgotPwdPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonCSS),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateForgotPassword),
|
||||
}
|
||||
resetPwdPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonCSS),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateResetPassword),
|
||||
}
|
||||
rolesPaths := []string{
|
||||
|
@ -636,7 +638,7 @@ func loadAdminTemplates(templatesPath string) {
|
|||
adminTemplates[templateEventActions] = eventActionsTmpl
|
||||
adminTemplates[templateEventAction] = eventActionTmpl
|
||||
adminTemplates[templateStatus] = statusTmpl
|
||||
adminTemplates[templateLogin] = loginTmpl
|
||||
adminTemplates[templateCommonLogin] = loginTmpl
|
||||
adminTemplates[templateProfile] = profileTmpl
|
||||
adminTemplates[templateChangePwd] = changePwdTmpl
|
||||
adminTemplates[templateMaintenance] = maintenanceTmpl
|
||||
|
@ -797,36 +799,38 @@ func (s *httpdServer) renderNotFoundPage(w http.ResponseWriter, r *http.Request,
|
|||
s.renderMessagePage(w, r, page404Title, page404Body, http.StatusNotFound, err, "")
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderForgotPwdPage(w http.ResponseWriter, r *http.Request, error, ip string) {
|
||||
func (s *httpdServer) renderForgotPwdPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) {
|
||||
data := forgotPwdPage{
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
CurrentURL: webAdminForgotPwdPath,
|
||||
Error: error,
|
||||
Error: err,
|
||||
CSRFToken: createCSRFToken(ip),
|
||||
Title: pageForgotPwdTitle,
|
||||
LoginURL: webAdminLoginPath,
|
||||
Title: util.I18nForgotPwdTitle,
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
}
|
||||
renderAdminTemplate(w, templateForgotPassword, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderResetPwdPage(w http.ResponseWriter, r *http.Request, error, ip string) {
|
||||
func (s *httpdServer) renderResetPwdPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) {
|
||||
data := resetPwdPage{
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
CurrentURL: webAdminResetPwdPath,
|
||||
Error: error,
|
||||
Error: err,
|
||||
CSRFToken: createCSRFToken(ip),
|
||||
Title: pageResetPwdTitle,
|
||||
LoginURL: webAdminLoginPath,
|
||||
Title: util.I18nResetPwdTitle,
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
}
|
||||
renderAdminTemplate(w, templateResetPassword, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, r *http.Request, error, ip string) {
|
||||
func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) {
|
||||
data := twoFactorPage{
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
Title: pageTwoFactorTitle,
|
||||
CurrentURL: webAdminTwoFactorPath,
|
||||
Error: error,
|
||||
Error: err,
|
||||
CSRFToken: createCSRFToken(ip),
|
||||
RecoveryURL: webAdminTwoFactorRecoveryPath,
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
|
@ -834,12 +838,12 @@ func (s *httpdServer) renderTwoFactorPage(w http.ResponseWriter, r *http.Request
|
|||
renderAdminTemplate(w, templateTwoFactor, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderTwoFactorRecoveryPage(w http.ResponseWriter, r *http.Request, error, ip string) {
|
||||
func (s *httpdServer) renderTwoFactorRecoveryPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) {
|
||||
data := twoFactorPage{
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
Title: pageTwoFactorRecoveryTitle,
|
||||
CurrentURL: webAdminTwoFactorRecoveryPath,
|
||||
Error: error,
|
||||
Error: err,
|
||||
CSRFToken: createCSRFToken(ip),
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
}
|
||||
|
@ -926,14 +930,18 @@ func (s *httpdServer) renderConfigsPage(w http.ResponseWriter, r *http.Request,
|
|||
renderAdminTemplate(w, templateConfigs, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderAdminSetupPage(w http.ResponseWriter, r *http.Request, username, error string) {
|
||||
func (s *httpdServer) renderAdminSetupPage(w http.ResponseWriter, r *http.Request, username, ip string, err *util.I18nError) {
|
||||
data := setupPage{
|
||||
basePage: s.getBasePageData(pageSetupTitle, webAdminSetupPath, r),
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
Title: util.I18nSetupTitle,
|
||||
CurrentURL: webAdminSetupPath,
|
||||
CSRFToken: createCSRFToken(ip),
|
||||
Username: username,
|
||||
HasInstallationCode: installationCode != "",
|
||||
InstallationCodeHint: installationCodeHint,
|
||||
HideSupportLink: hideSupportLink,
|
||||
Error: error,
|
||||
Error: err,
|
||||
Branding: s.binding.Branding.WebAdmin,
|
||||
}
|
||||
|
||||
renderAdminTemplate(w, templateSetup, data)
|
||||
|
@ -2634,7 +2642,7 @@ func (s *httpdServer) handleWebAdminForgotPwd(w http.ResponseWriter, r *http.Req
|
|||
s.renderNotFoundPage(w, r, errors.New("this page does not exist"))
|
||||
return
|
||||
}
|
||||
s.renderForgotPwdPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
s.renderForgotPwdPage(w, r, nil, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -2643,7 +2651,7 @@ func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http
|
|||
ipAddr := util.GetIPFromRemoteAddress(r.RemoteAddr)
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
s.renderForgotPwdPage(w, r, err.Error(), ipAddr)
|
||||
s.renderForgotPwdPage(w, r, util.NewI18nError(err, util.I18nErrorInvalidForm), ipAddr)
|
||||
return
|
||||
}
|
||||
if err := verifyCSRFToken(r.Form.Get(csrfFormToken), ipAddr); err != nil {
|
||||
|
@ -2652,12 +2660,7 @@ func (s *httpdServer) handleWebAdminForgotPwdPost(w http.ResponseWriter, r *http
|
|||
}
|
||||
err = handleForgotPassword(r, r.Form.Get("username"), true)
|
||||
if err != nil {
|
||||
var e *util.ValidationError
|
||||
if errors.As(err, &e) {
|
||||
s.renderForgotPwdPage(w, r, e.GetErrorString(), ipAddr)
|
||||
return
|
||||
}
|
||||
s.renderForgotPwdPage(w, r, err.Error(), ipAddr)
|
||||
s.renderForgotPwdPage(w, r, util.NewI18nError(err, util.I18nErrorPwdResetGeneric), ipAddr)
|
||||
return
|
||||
}
|
||||
http.Redirect(w, r, webAdminResetPwdPath, http.StatusFound)
|
||||
|
@ -2669,17 +2672,17 @@ func (s *httpdServer) handleWebAdminPasswordReset(w http.ResponseWriter, r *http
|
|||
s.renderNotFoundPage(w, r, errors.New("this page does not exist"))
|
||||
return
|
||||
}
|
||||
s.renderResetPwdPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
s.renderResetPwdPage(w, r, nil, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAdminTwoFactor(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
s.renderTwoFactorPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
s.renderTwoFactorPage(w, r, nil, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAdminTwoFactorRecovery(w http.ResponseWriter, r *http.Request) {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, maxRequestSize)
|
||||
s.renderTwoFactorRecoveryPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
s.renderTwoFactorRecoveryPage(w, r, nil, util.GetIPFromRemoteAddress(r.RemoteAddr))
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAdminMFA(w http.ResponseWriter, r *http.Request) {
|
||||
|
@ -2824,7 +2827,7 @@ func (s *httpdServer) handleWebAdminSetupGet(w http.ResponseWriter, r *http.Requ
|
|||
http.Redirect(w, r, webAdminLoginPath, http.StatusFound)
|
||||
return
|
||||
}
|
||||
s.renderAdminSetupPage(w, r, "", "")
|
||||
s.renderAdminSetupPage(w, r, "", util.GetIPFromRemoteAddress(r.RemoteAddr), nil)
|
||||
}
|
||||
|
||||
func (s *httpdServer) handleWebAddAdminGet(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -45,23 +45,20 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
templateClientDir = "webclient"
|
||||
templateClientBase = "base.html"
|
||||
templateClientLogin = "login.html"
|
||||
templateClientFiles = "files.html"
|
||||
templateClientMessage = "message.html"
|
||||
templateClientProfile = "profile.html"
|
||||
templateClientChangePwd = "changepassword.html"
|
||||
templateClientTwoFactor = "twofactor.html"
|
||||
templateClientTwoFactorRecovery = "twofactor-recovery.html"
|
||||
templateClientMFA = "mfa.html"
|
||||
templateClientEditFile = "editfile.html"
|
||||
templateClientShare = "share.html"
|
||||
templateClientShares = "shares.html"
|
||||
templateClientViewPDF = "viewpdf.html"
|
||||
templateShareLogin = "sharelogin.html"
|
||||
templateShareDownload = "sharedownload.html"
|
||||
templateUploadToShare = "shareupload.html"
|
||||
templateClientDir = "webclient"
|
||||
templateClientBase = "base.html"
|
||||
templateClientFiles = "files.html"
|
||||
templateClientMessage = "message.html"
|
||||
templateClientProfile = "profile.html"
|
||||
templateClientChangePwd = "changepassword.html"
|
||||
templateClientMFA = "mfa.html"
|
||||
templateClientEditFile = "editfile.html"
|
||||
templateClientShare = "share.html"
|
||||
templateClientShares = "shares.html"
|
||||
templateClientViewPDF = "viewpdf.html"
|
||||
templateShareLogin = "sharelogin.html"
|
||||
templateShareDownload = "sharedownload.html"
|
||||
templateUploadToShare = "shareupload.html"
|
||||
)
|
||||
|
||||
// condResult is the result of an HTTP request precondition check.
|
||||
|
@ -212,51 +209,6 @@ type clientSharePage struct {
|
|||
IsAdd bool
|
||||
}
|
||||
|
||||
// TODO: merge with loginPage once the WebAdmin supports localization
|
||||
type clientLoginPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
AltLoginURL string
|
||||
AltLoginName string
|
||||
ForgotPwdURL string
|
||||
OpenIDLoginURL string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
FormDisabled bool
|
||||
}
|
||||
|
||||
type clientResetPwdPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
LoginURL string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
type clientTwoFactorPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
RecoveryURL string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
type clientForgotPwdPage struct {
|
||||
commonBasePage
|
||||
CurrentURL string
|
||||
Error *util.I18nError
|
||||
CSRFToken string
|
||||
LoginURL string
|
||||
Title string
|
||||
Branding UIBranding
|
||||
}
|
||||
|
||||
type userQuotaUsage struct {
|
||||
QuotaSize int64
|
||||
QuotaFiles int
|
||||
|
@ -478,40 +430,40 @@ func loadClientTemplates(templatesPath string) {
|
|||
filepath.Join(templatesPath, templateClientDir, templateClientBase),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientChangePwd),
|
||||
}
|
||||
loginPath := []string{
|
||||
loginPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateBaseLogin),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonLogin),
|
||||
}
|
||||
messagePath := []string{
|
||||
messagePaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientBase),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientMessage),
|
||||
}
|
||||
mfaPath := []string{
|
||||
mfaPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientBase),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientMFA),
|
||||
}
|
||||
twoFactorPath := []string{
|
||||
twoFactorPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateBaseLogin),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientTwoFactor),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateTwoFactor),
|
||||
}
|
||||
twoFactorRecoveryPath := []string{
|
||||
twoFactorRecoveryPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateBaseLogin),
|
||||
filepath.Join(templatesPath, templateClientDir, templateClientTwoFactorRecovery),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateTwoFactorRecovery),
|
||||
}
|
||||
forgotPwdPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateBaseLogin),
|
||||
filepath.Join(templatesPath, templateClientDir, templateForgotPassword),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateForgotPassword),
|
||||
}
|
||||
resetPwdPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateBaseLogin),
|
||||
filepath.Join(templatesPath, templateClientDir, templateResetPassword),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateResetPassword),
|
||||
}
|
||||
viewPDFPaths := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
|
@ -519,7 +471,7 @@ func loadClientTemplates(templatesPath string) {
|
|||
}
|
||||
shareLoginPath := []string{
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBase),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateBaseLogin),
|
||||
filepath.Join(templatesPath, templateCommonDir, templateCommonBaseLogin),
|
||||
filepath.Join(templatesPath, templateClientDir, templateShareLogin),
|
||||
}
|
||||
shareUploadPath := []string{
|
||||
|
@ -536,11 +488,11 @@ func loadClientTemplates(templatesPath string) {
|
|||
filesTmpl := util.LoadTemplate(nil, filesPaths...)
|
||||
profileTmpl := util.LoadTemplate(nil, profilePaths...)
|
||||
changePwdTmpl := util.LoadTemplate(nil, changePwdPaths...)
|
||||
loginTmpl := util.LoadTemplate(nil, loginPath...)
|
||||
messageTmpl := util.LoadTemplate(nil, messagePath...)
|
||||
mfaTmpl := util.LoadTemplate(nil, mfaPath...)
|
||||
twoFactorTmpl := util.LoadTemplate(nil, twoFactorPath...)
|
||||
twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPath...)
|
||||
loginTmpl := util.LoadTemplate(nil, loginPaths...)
|
||||
messageTmpl := util.LoadTemplate(nil, messagePaths...)
|
||||
mfaTmpl := util.LoadTemplate(nil, mfaPaths...)
|
||||
twoFactorTmpl := util.LoadTemplate(nil, twoFactorPaths...)
|
||||
twoFactorRecoveryTmpl := util.LoadTemplate(nil, twoFactorRecoveryPaths...)
|
||||
editFileTmpl := util.LoadTemplate(nil, editFilePath...)
|
||||
shareLoginTmpl := util.LoadTemplate(nil, shareLoginPath...)
|
||||
sharesTmpl := util.LoadTemplate(nil, sharesPaths...)
|
||||
|
@ -554,11 +506,11 @@ func loadClientTemplates(templatesPath string) {
|
|||
clientTemplates[templateClientFiles] = filesTmpl
|
||||
clientTemplates[templateClientProfile] = profileTmpl
|
||||
clientTemplates[templateClientChangePwd] = changePwdTmpl
|
||||
clientTemplates[templateClientLogin] = loginTmpl
|
||||
clientTemplates[templateCommonLogin] = loginTmpl
|
||||
clientTemplates[templateClientMessage] = messageTmpl
|
||||
clientTemplates[templateClientMFA] = mfaTmpl
|
||||
clientTemplates[templateClientTwoFactor] = twoFactorTmpl
|
||||
clientTemplates[templateClientTwoFactorRecovery] = twoFactorRecoveryTmpl
|
||||
clientTemplates[templateTwoFactor] = twoFactorTmpl
|
||||
clientTemplates[templateTwoFactorRecovery] = twoFactorRecoveryTmpl
|
||||
clientTemplates[templateClientEditFile] = editFileTmpl
|
||||
clientTemplates[templateClientShares] = sharesTmpl
|
||||
clientTemplates[templateClientShare] = shareTmpl
|
||||
|
@ -600,7 +552,7 @@ func (s *httpdServer) getBaseClientPageData(title, currentURL string, r *http.Re
|
|||
}
|
||||
|
||||
func (s *httpdServer) renderClientForgotPwdPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) {
|
||||
data := clientForgotPwdPage{
|
||||
data := forgotPwdPage{
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
CurrentURL: webClientForgotPwdPath,
|
||||
Error: err,
|
||||
|
@ -613,7 +565,7 @@ func (s *httpdServer) renderClientForgotPwdPage(w http.ResponseWriter, r *http.R
|
|||
}
|
||||
|
||||
func (s *httpdServer) renderClientResetPwdPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) {
|
||||
data := clientResetPwdPage{
|
||||
data := resetPwdPage{
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
CurrentURL: webClientResetPwdPath,
|
||||
Error: err,
|
||||
|
@ -679,7 +631,7 @@ func (s *httpdServer) renderClientNotFoundPage(w http.ResponseWriter, r *http.Re
|
|||
}
|
||||
|
||||
func (s *httpdServer) renderClientTwoFactorPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) {
|
||||
data := clientTwoFactorPage{
|
||||
data := twoFactorPage{
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
Title: pageTwoFactorTitle,
|
||||
CurrentURL: webClientTwoFactorPath,
|
||||
|
@ -691,11 +643,11 @@ func (s *httpdServer) renderClientTwoFactorPage(w http.ResponseWriter, r *http.R
|
|||
if next := r.URL.Query().Get("next"); strings.HasPrefix(next, webClientFilesPath) {
|
||||
data.CurrentURL += "?next=" + url.QueryEscape(next)
|
||||
}
|
||||
renderClientTemplate(w, templateClientTwoFactor, data)
|
||||
renderClientTemplate(w, templateTwoFactor, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderClientTwoFactorRecoveryPage(w http.ResponseWriter, r *http.Request, err *util.I18nError, ip string) {
|
||||
data := clientTwoFactorPage{
|
||||
data := twoFactorPage{
|
||||
commonBasePage: getCommonBasePage(r),
|
||||
Title: pageTwoFactorRecoveryTitle,
|
||||
CurrentURL: webClientTwoFactorRecoveryPath,
|
||||
|
@ -703,7 +655,7 @@ func (s *httpdServer) renderClientTwoFactorRecoveryPage(w http.ResponseWriter, r
|
|||
CSRFToken: createCSRFToken(ip),
|
||||
Branding: s.binding.Branding.WebClient,
|
||||
}
|
||||
renderClientTemplate(w, templateClientTwoFactorRecovery, data)
|
||||
renderClientTemplate(w, templateTwoFactorRecovery, data)
|
||||
}
|
||||
|
||||
func (s *httpdServer) renderClientMFAPage(w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
@ -21,6 +21,7 @@ import (
|
|||
|
||||
// localization id for the Web frontend
|
||||
const (
|
||||
I18nSetupTitle = "title.setup"
|
||||
I18nLoginTitle = "title.login"
|
||||
I18nShareLoginTitle = "title.share_login"
|
||||
I18nFilesTitle = "title.files"
|
||||
|
@ -47,6 +48,7 @@ const (
|
|||
I18nError500Title = "title.error500"
|
||||
I18nErrorPDFTitle = "title.errorPDF"
|
||||
I18nErrorEditorTitle = "title.error_editor"
|
||||
I18nErrorSetupInstallCode = "setup.install_code_mismatch"
|
||||
I18nInvalidAuth = "general.invalid_auth_request"
|
||||
I18nError429Message = "general.error429"
|
||||
I18nError400Message = "general.error400"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"title": {
|
||||
"setup": "Initial Setup",
|
||||
"login": "Login",
|
||||
"share_login": "Share Login",
|
||||
"profile": "Profile",
|
||||
|
@ -28,6 +29,12 @@
|
|||
"errorPDF": "Unable to show PDF file",
|
||||
"error_editor": "Cannot open file editor"
|
||||
},
|
||||
"setup": {
|
||||
"desc": "To start using SFTPGo you need to create an administrator user",
|
||||
"submit": "Create admin and Sign in",
|
||||
"install_code_mismatch": "The installation code does not match",
|
||||
"help_text": "SFTPGo needs your help"
|
||||
},
|
||||
"login": {
|
||||
"username": "Username",
|
||||
"password": "Password",
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"title": {
|
||||
"setup": "Configurazione iniziale",
|
||||
"login": "Accedi",
|
||||
"share_login": "Accedi alla condivisione",
|
||||
"profile": "Profilo",
|
||||
|
@ -28,6 +29,12 @@
|
|||
"errorPDF": "Impossibile mostrare il file PDF",
|
||||
"error_editor": "Impossibile aprire l'editor di file"
|
||||
},
|
||||
"setup": {
|
||||
"desc": "Per iniziare a utilizzare SFTPGo devi creare un utente amministratore",
|
||||
"submit": "Crea amministratore e accedi",
|
||||
"install_code_mismatch": "Il codice di installazione non corrisponde",
|
||||
"help_text": "SFTPGo ha bisogno del tuo aiuto"
|
||||
},
|
||||
"login": {
|
||||
"username": "Nome utente",
|
||||
"password": "Password",
|
||||
|
@ -353,7 +360,7 @@
|
|||
"info": "Inserisci la password attuale, per ragioni di sicurezza, e poi la nuova password due volte, per verificare di averla scritta correttamente. Verrai disconnesso dopo aver modificato la tua password",
|
||||
"current": "Password attuale",
|
||||
"new": "Nuova password",
|
||||
"confirm": "Conferma nuova password",
|
||||
"confirm": "Conferma password",
|
||||
"save": "Modifica la mia password",
|
||||
"required_fields": "Si prega di fornire la password attuale e quella nuova due volte",
|
||||
"no_match": "I due campi della password non corrispondono",
|
||||
|
|
|
@ -128,6 +128,7 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
};
|
||||
|
||||
const renderI18n = () => {
|
||||
$('title').text('{{.Branding.Name}} - '+$.t('{{.Title}}'));
|
||||
$('body').localize();
|
||||
let select2elements = [].slice.call(document.querySelectorAll('[data-control="i18n-select2"]'));
|
||||
select2elements.map(function (element){
|
||||
|
@ -209,7 +210,6 @@ explicit grant from the SFTPGo Team (support@sftpgo.com).
|
|||
}
|
||||
|
||||
renderI18n();
|
||||
$('title').text('{{.Branding.Name}} - '+$.t('{{.Title}}'));
|
||||
$.event.trigger({
|
||||
type: "i18nload"
|
||||
});
|
||||
|
|
|
@ -1,104 +1,59 @@
|
|||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||
SFTPGo product and restricted to be used in a resealable HTML template
|
||||
that can compete with KeenThemes products anyhow.
|
||||
|
||||
This WebUI is allowed for use only within the SFTPGo product and
|
||||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{{- template "baselogin" .}}
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>{{.Branding.Name}} - {{.Title}}</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
{{- range .Branding.DefaultCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{- end}}
|
||||
<style>
|
||||
{{template "commoncss" .}}
|
||||
</style>
|
||||
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
||||
<body class="bg-gradient-primary">
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Outer Row -->
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="col-xl-6 col-lg-7 col-md-9">
|
||||
|
||||
<div class="card o-hidden border-0 shadow-lg my-5">
|
||||
<div class="card-body p-0">
|
||||
<!-- Nested Row within Card Body -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="p-5">
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">Forgot Your Password?</h1>
|
||||
<p class="mb-4">Enter your account username below, you will receive a password reset code by email.</p>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="forgot_password_form" action="{{.CurrentURL}}" method="POST" autocomplete="off"
|
||||
class="user-custom">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control form-control-user-custom"
|
||||
id="inputUsername" name="username" placeholder="Your username" spellcheck="false" required>
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary btn-user-custom btn-block">
|
||||
Send Reset Code
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<a href="{{.LoginURL}}">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<a href="{{.LoginURL}}" class="text-gray-900 mb-3 ms-3 fs-1 fw-bold">
|
||||
{{.Branding.ShortName}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript-->
|
||||
<script src="{{.StaticURL}}/vendor/jquery/jquery.min.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Core plugin JavaScript-->
|
||||
<script src="{{.StaticURL}}/vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
|
||||
<!-- Custom scripts for all pages-->
|
||||
<script src="{{.StaticURL}}/js/sb-admin-2.min.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<div class="text-center mb-10">
|
||||
<h2 data-i18n="login.forgot_password" class="text-gray-900 mb-3">
|
||||
Forgot Password ?
|
||||
</h2>
|
||||
<div class="text-gray-700 fw-semibold fs-4">
|
||||
<span data-i18n="login.forgot_password_msg">
|
||||
Enter your account username below, you will receive a password reset code by email.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{- template "errmsg" .Error}}
|
||||
<div class="fv-row mb-10">
|
||||
<input data-i18n="[placeholder]login.your_username" class="form-control form-control-lg form-control-solid" type="text" placeholder="Your username" name="username" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span data-i18n="login.send_reset_code" class="indicator-label">Send Reset Code</span>
|
||||
<span data-i18n="general.wait" class="indicator-progress">
|
||||
Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{- end}}
|
|
@ -1,108 +1,97 @@
|
|||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||
SFTPGo product and restricted to be used in a resealable HTML template
|
||||
that can compete with KeenThemes products anyhow.
|
||||
|
||||
This WebUI is allowed for use only within the SFTPGo product and
|
||||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{{- template "baselogin" .}}
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>{{.Branding.Name}} - {{.Title}}</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
{{- range .Branding.DefaultCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{- end}}
|
||||
<style>
|
||||
{{template "commoncss" .}}
|
||||
</style>
|
||||
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
||||
<body class="bg-gradient-primary">
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Outer Row -->
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="col-xl-6 col-lg-7 col-md-9">
|
||||
|
||||
<div class="card o-hidden border-0 shadow-lg my-5">
|
||||
<div class="card-body p-0">
|
||||
<!-- Nested Row within Card Body -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="p-5">
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">Reset Password</h1>
|
||||
<p class="mb-4">Check your email for the confirmation code</p>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="forgot_password_form" action="{{.CurrentURL}}" method="POST" autocomplete="off"
|
||||
class="user-custom">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control form-control-user-custom"
|
||||
id="inputCode" name="code" placeholder="Confirmation code" spellcheck="false" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control form-control-user-custom"
|
||||
id="inputPassword" name="password" placeholder="New Password" autocomplete="new-password" spellcheck="false" required>
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary btn-user-custom btn-block">
|
||||
Update Password & Login
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<a href="{{.LoginURL}}">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<a href="{{.LoginURL}}" class="text-gray-900 mb-3 ms-3 fs-1 fw-bold">
|
||||
{{.Branding.ShortName}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript-->
|
||||
<script src="{{.StaticURL}}/vendor/jquery/jquery.min.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Core plugin JavaScript-->
|
||||
<script src="{{.StaticURL}}/vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
|
||||
<!-- Custom scripts for all pages-->
|
||||
<script src="{{.StaticURL}}/js/sb-admin-2.min.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<div class="text-center mb-10">
|
||||
<h2 data-i18n="login.reset_password" class="text-gray-900 mb-3">
|
||||
Reset Password
|
||||
</h2>
|
||||
<div class="text-gray-700 fw-semibold fs-4">
|
||||
<span data-i18n="login.reset_pwd_msg">
|
||||
Check your email for the confirmation code
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{- template "errmsg" .Error}}
|
||||
<div class="fv-row mb-10">
|
||||
<input data-i18n="[placeholder]login.confirm_code" class="form-control form-control-lg form-control-solid" type="text" placeholder="Confirmation code" name="code" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="fv-row mb-10">
|
||||
<div class="position-relative" data-password-control="container">
|
||||
<input data-i18n="[placeholder]general.new_password" data-password-control="input" class="form-control form-control-lg form-control-solid"
|
||||
type="password" name="password" placeholder="New Password" autocomplete="new-password" spellcheck="false" required />
|
||||
<span class="btn btn-sm btn-icon position-absolute translate-middle top-50 end-0 me-n2" data-password-control="visibility">
|
||||
<i class="ki-duotone ki-eye-slash fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
<i class="ki-duotone ki-eye d-none fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fv-row mb-10">
|
||||
<div class="position-relative" data-password-control="container">
|
||||
<input data-i18n="[placeholder]change_pwd.confirm" data-password-control="input" class="form-control form-control-lg form-control-solid"
|
||||
type="password" name="confirm_password" placeholder="Confirm Password" autocomplete="new-password" spellcheck="false" required />
|
||||
<span class="btn btn-sm btn-icon position-absolute translate-middle top-50 end-0 me-n2" data-password-control="visibility">
|
||||
<i class="ki-duotone ki-eye-slash fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
<i class="ki-duotone ki-eye d-none fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span data-i18n="login.reset_submit" class="indicator-label">Update Password & Login</span>
|
||||
<span data-i18n="general.wait" class="indicator-progress">
|
||||
Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{- end}}
|
|
@ -1,119 +1,109 @@
|
|||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||
SFTPGo product and restricted to be used in a resealable HTML template
|
||||
that can compete with KeenThemes products anyhow.
|
||||
|
||||
This WebUI is allowed for use only within the SFTPGo product and
|
||||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
{{- template "baselogin" .}}
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>SFTPGo - Setup</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
{{- range .Branding.DefaultCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{- end}}
|
||||
<style>
|
||||
{{template "commoncss" .}}
|
||||
</style>
|
||||
|
||||
</head>
|
||||
|
||||
<body class="bg-gradient-primary">
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Outer Row -->
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="col-xl-7 col-lg-8 col-md-10">
|
||||
|
||||
<div class="card o-hidden border-0 shadow-lg my-5">
|
||||
<div class="card-body p-0">
|
||||
<!-- Nested Row within Card Body -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="p-5">
|
||||
<div class="text-center">
|
||||
<h1 class="h5 text-gray-900 mb-4">To start using SFTPGo you need to create an admin user</h1>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="login_form" action="{{.CurrentURL}}" method="POST" autocomplete="off"
|
||||
class="user-custom">
|
||||
{{if .HasInstallationCode}}
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control form-control-user-custom" id="inputInstallCode"
|
||||
name="install_code" placeholder="{{.InstallationCodeHint}}" value="" required>
|
||||
</div>
|
||||
{{end}}
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control form-control-user-custom" id="inputUsername"
|
||||
name="username" placeholder="Username" value="{{.Username}}" spellcheck="false" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control form-control-user-custom" id="inputPassword"
|
||||
name="password" placeholder="Password" autocomplete="new-password" spellcheck="false" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control form-control-user-custom" id="inputConfirmPassword"
|
||||
name="confirm_password" placeholder="Repeat password" autocomplete="new-password" spellcheck="false" required>
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary btn-user-custom btn-block">
|
||||
Create admin
|
||||
</button>
|
||||
</form>
|
||||
{{if not .HideSupportLink}}
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<a class="small" href="https://github.com/drakkan/sftpgo#sponsors" target="_blank">SFTPGo needs your help</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<span class="text-gray-900 mb-3 ms-3 fs-1 fw-bold">
|
||||
{{.Branding.ShortName}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript-->
|
||||
<script src="{{.StaticURL}}/vendor/jquery/jquery.min.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Core plugin JavaScript-->
|
||||
<script src="{{.StaticURL}}/vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
|
||||
<!-- Custom scripts for all pages-->
|
||||
<script src="{{.StaticURL}}/js/sb-admin-2.min.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<div class="text-center mb-10">
|
||||
<h2 data-i18n="title.setup" class="text-gray-900 mb-3"></h2>
|
||||
<div class="text-gray-700 fw-semibold fs-4">
|
||||
<span data-i18n="setup.desc"></span>
|
||||
</div>
|
||||
</div>
|
||||
{{- template "errmsg" .Error}}
|
||||
{{- if .HasInstallationCode}}
|
||||
<div class="fv-row mb-10">
|
||||
<input class="form-control form-control-lg form-control-solid" type="text" placeholder="{{.InstallationCodeHint}}" name="install_code" spellcheck="false" required />
|
||||
</div>
|
||||
{{- end}}
|
||||
<div class="fv-row mb-10">
|
||||
<input data-i18n="[placeholder]login.username" class="form-control form-control-lg form-control-solid" type="text" name="username" placeholder="Username" autocomplete="on" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="fv-row mb-10">
|
||||
<div class="position-relative" data-password-control="container">
|
||||
<input data-i18n="[placeholder]login.password" data-password-control="input" class="form-control form-control-lg form-control-solid"
|
||||
type="password" name="password" placeholder="Password" autocomplete="password" spellcheck="false" required />
|
||||
<span class="btn btn-sm btn-icon position-absolute translate-middle top-50 end-0 me-n2" data-password-control="visibility">
|
||||
<i class="ki-duotone ki-eye-slash fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
<i class="ki-duotone ki-eye d-none fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fv-row mb-10">
|
||||
<div class="position-relative" data-password-control="container">
|
||||
<input data-i18n="[placeholder]change_pwd.confirm" data-password-control="input" class="form-control form-control-lg form-control-solid"
|
||||
type="password" name="confirm_password" placeholder="Confirm Password" autocomplete="new-password" spellcheck="false" required />
|
||||
<span class="btn btn-sm btn-icon position-absolute translate-middle top-50 end-0 me-n2" data-password-control="visibility">
|
||||
<i class="ki-duotone ki-eye-slash fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
<i class="ki-duotone ki-eye d-none fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span data-i18n="setup.submit" class="indicator-label"></span>
|
||||
<span data-i18n="general.wait" class="indicator-progress">
|
||||
Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<div class="d-flex flex-stack pt-5 mt-3">
|
||||
<div class="me-10">
|
||||
<select id="languageSwitcher" name="language" class="form-select form-select-solid form-select-sm" data-control="i18n-select2" data-hide-search="true"></select>
|
||||
</div>
|
||||
{{- if not .HideSupportLink}}
|
||||
<div class="d-flex fw-semibold text-primary">
|
||||
<a href="https://github.com/drakkan/sftpgo#sponsors" target="_blank" class="px-2">
|
||||
<span data-i18n="setup.help_text"></span>
|
||||
</a>
|
||||
</div>
|
||||
{{- end}}
|
||||
</div>
|
||||
{{- end}}
|
|
@ -1,90 +0,0 @@
|
|||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
{{define "baselogin"}}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="">
|
||||
<meta name="author" content="">
|
||||
|
||||
<title>{{.Branding.Name}} - {{template "title" .}}</title>
|
||||
|
||||
<link rel="shortcut icon" href="{{.StaticURL}}{{.Branding.FaviconPath}}" />
|
||||
|
||||
<!-- Custom styles for this template-->
|
||||
{{- range .Branding.DefaultCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{- end}}
|
||||
<style>
|
||||
{{template "commoncss" .}}
|
||||
</style>
|
||||
|
||||
{{range .Branding.ExtraCSS}}
|
||||
<link href="{{$.StaticURL}}{{.}}" rel="stylesheet" type="text/css">
|
||||
{{end}}
|
||||
|
||||
</head>
|
||||
|
||||
<body class="bg-gradient-primary">
|
||||
|
||||
<div class="container">
|
||||
|
||||
<!-- Outer Row -->
|
||||
<div class="row justify-content-center">
|
||||
|
||||
<div class="col-xl-10 col-lg-12 col-md-9">
|
||||
|
||||
<div class="card o-hidden border-0 shadow-lg my-5">
|
||||
<div class="card-body p-0">
|
||||
<!-- Nested Row within Card Body -->
|
||||
<div class="row d-lg-none login-image">
|
||||
<div class="col-lg-12 d-block d-lg-none bg-login-image">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-5 d-none d-lg-block bg-login-image">
|
||||
</div>
|
||||
<div class="col-lg-7">
|
||||
<div class="p-5">
|
||||
{{template "content" .}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript-->
|
||||
<script src="{{.StaticURL}}/vendor/jquery/jquery.min.js"></script>
|
||||
<script src="{{.StaticURL}}/vendor/bootstrap/js/bootstrap.bundle.min.js"></script>
|
||||
|
||||
<!-- Core plugin JavaScript-->
|
||||
<script src="{{.StaticURL}}/vendor/jquery-easing/jquery.easing.min.js"></script>
|
||||
|
||||
<!-- Custom scripts for all pages-->
|
||||
<script src="{{.StaticURL}}/js/sb-admin-2.min.js"></script>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
||||
{{end}}
|
|
@ -1,72 +1,99 @@
|
|||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
Copyright (C) 2023 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||
SFTPGo product and restricted to be used in a resealable HTML template
|
||||
that can compete with KeenThemes products anyhow.
|
||||
|
||||
This WebUI is allowed for use only within the SFTPGo product and
|
||||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
{{template "baselogin" .}}
|
||||
{{- template "baselogin" .}}
|
||||
|
||||
{{define "title"}}Login{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">{{.Branding.ShortName}}</h1>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="login_form" action="{{.CurrentURL}}" method="POST"
|
||||
class="user-custom">
|
||||
{{if not .FormDisabled}}
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control form-control-user-custom"
|
||||
id="inputUsername" name="username" placeholder="Username" spellcheck="false" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<input type="password" class="form-control form-control-user-custom"
|
||||
id="inputPassword" name="password" placeholder="Password" autocomplete="current-password" spellcheck="false" required>
|
||||
{{if .ForgotPwdURL}}
|
||||
<div class="text-right">
|
||||
<a class="small" href="{{.ForgotPwdURL}}">Forgot password?</a>
|
||||
</div>
|
||||
{{end}}
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary btn-user-custom btn-block">
|
||||
Login
|
||||
</button>
|
||||
{{end}}
|
||||
{{if .OpenIDLoginURL}}
|
||||
<hr>
|
||||
<a href="{{.OpenIDLoginURL}}" class="btn btn-secondary btn-user-custom btn-block">
|
||||
Login with OpenID
|
||||
</a>
|
||||
{{end}}
|
||||
</form>
|
||||
{{if .AltLoginURL}}
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<a class="small" href="{{.AltLoginURL}}">{{.AltLoginName}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{if and .Branding.DisclaimerName .Branding.DisclaimerPath}}
|
||||
<hr>
|
||||
<div class="text-center">
|
||||
<a class="small" href="{{.Branding.DisclaimerPath}}" target="_blank">{{.Branding.DisclaimerName}}</a>
|
||||
</div>
|
||||
{{end}}
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<h1 class="text-gray-900 mb-3 ms-3">
|
||||
{{.Branding.ShortName}}
|
||||
</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{- template "errmsg" .Error}}
|
||||
{{- if not .FormDisabled}}
|
||||
<div class="fv-row mb-10">
|
||||
<input data-i18n="[placeholder]login.username" class="form-control form-control-lg form-control-solid" type="text" name="username" placeholder="Username" autocomplete="on" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="fv-row mb-10">
|
||||
<div class="position-relative" data-password-control="container">
|
||||
<input data-i18n="[placeholder]login.password" data-password-control="input" class="form-control form-control-lg form-control-solid" type="password" name="password" placeholder="Password" autocomplete="current-password" spellcheck="false" required />
|
||||
<span class="btn btn-sm btn-icon position-absolute translate-middle top-50 end-0 me-n2" data-password-control="visibility">
|
||||
<i class="ki-duotone ki-eye-slash fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
<i class="ki-duotone ki-eye d-none fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-end mt-2">
|
||||
{{- if .ForgotPwdURL}}
|
||||
<a data-i18n="login.forgot_password" href="{{.ForgotPwdURL}}" class="link-primary fs-6 fw-bold">Forgot Password ?</a>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
{{- end}}
|
||||
<div class="text-center">
|
||||
{{- if not .FormDisabled}}
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span data-i18n="login.signin" class="indicator-label">Sign in</span>
|
||||
<span data-i18n="general.wait" class="indicator-progress">
|
||||
Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
{{- end}}
|
||||
{{- if .OpenIDLoginURL}}
|
||||
<a href="{{.OpenIDLoginURL}}" class="btn btn-flex btn-outline flex-center {{if .FormDisabled}}btn-primary{{else}}btn-active-color-primary bg-state-light{{end}} btn-lg w-100 my-5">
|
||||
<img alt="Logo" src="{{.StaticURL}}/img/openid-logo.png" class="h-20px me-3" />
|
||||
<span data-i18n="login.signin_openid">Sign in with OpenID</span>
|
||||
</a>
|
||||
{{- end}}
|
||||
</div>
|
||||
</form>
|
||||
<hr>
|
||||
<div class="d-flex flex-stack pt-5 mt-3">
|
||||
<div class="me-10">
|
||||
<select id="languageSwitcher" name="language" class="form-select form-select-solid form-select-sm" data-control="i18n-select2" data-hide-search="true">
|
||||
</select>
|
||||
</div>
|
||||
<div class="d-flex fw-semibold text-primary">
|
||||
{{- if .AltLoginURL}}
|
||||
<a href="{{.AltLoginURL}}" class="px-2">
|
||||
<span data-i18n="login.link" data-i18n-options='{ "link": "{{.AltLoginName}}" }'></span>
|
||||
</a>
|
||||
{{- end}}
|
||||
{{- if and .Branding.DisclaimerName .Branding.DisclaimerPath}}
|
||||
<a href="{{.Branding.DisclaimerPath}}" target="_blank" class="px-2">
|
||||
<span data-i18n="custom.disclaimer_webclient">{{.Branding.DisclaimerName}}</span>
|
||||
</a>
|
||||
{{- end}}
|
||||
</div>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,47 +0,0 @@
|
|||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
{{template "baselogin" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">{{.Branding.Name}}</h1>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="login_form" action="{{.CurrentURL}}" method="POST" autocomplete="off"
|
||||
class="user-custom">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control form-control-user-custom"
|
||||
id="inputRecoveryCode" name="recovery_code" placeholder="Recovery code" spellcheck="false" required>
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary btn-user-custom btn-block">
|
||||
Verify
|
||||
</button>
|
||||
</form>
|
||||
<hr>
|
||||
<div>
|
||||
<p>You can enter one of your recovery codes in case you lost access to your mobile device.</p>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,52 +0,0 @@
|
|||
<!--
|
||||
Copyright (C) 2019-2023 Nicola Murino
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, version 3.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
{{template "baselogin" .}}
|
||||
|
||||
{{define "title"}}{{.Title}}{{end}}
|
||||
|
||||
{{define "content"}}
|
||||
<div class="text-center">
|
||||
<h1 class="h4 text-gray-900 mb-4">{{.Branding.Name}}</h1>
|
||||
</div>
|
||||
{{if .Error}}
|
||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
||||
{{.Error}}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{{end}}
|
||||
<form id="login_form" action="{{.CurrentURL}}" method="POST" autocomplete="off"
|
||||
class="user-custom">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control form-control-user-custom"
|
||||
id="inputPasscode" name="passcode" placeholder="Authentication code" spellcheck="false" required>
|
||||
</div>
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" class="btn btn-primary btn-user-custom btn-block">
|
||||
Verify
|
||||
</button>
|
||||
</form>
|
||||
<hr>
|
||||
<div>
|
||||
<p>Open the two-factor authentication app on your device to view your authentication code and verify your identity.</p>
|
||||
</div>
|
||||
<hr>
|
||||
<div>
|
||||
<p><strong>Having problems?</strong></p>
|
||||
<p><a href="{{.RecoveryURL}}">Enter a two-factor recovery code</a></p>
|
||||
</div>
|
||||
{{end}}
|
|
@ -1,59 +0,0 @@
|
|||
<!--
|
||||
Copyright (C) 2023 Nicola Murino
|
||||
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||
SFTPGo product and restricted to be used in a resealable HTML template
|
||||
that can compete with KeenThemes products anyhow.
|
||||
|
||||
This WebUI is allowed for use only within the SFTPGo product and
|
||||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
{{- template "baselogin" .}}
|
||||
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<a href="{{.LoginURL}}">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<a href="{{.LoginURL}}" class="text-gray-900 mb-3 ms-3 fs-1 fw-bold">
|
||||
{{.Branding.ShortName}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mb-10">
|
||||
<h2 data-i18n="login.forgot_password" class="text-gray-900 mb-3">
|
||||
Forgot Password ?
|
||||
</h2>
|
||||
<div class="text-gray-600 fw-semibold fs-4">
|
||||
<span data-i18n="login.forgot_password_msg">
|
||||
Enter your account username below, you will receive a password reset code by email.
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{- template "errmsg" .Error}}
|
||||
<div class="fv-row mb-10">
|
||||
<input data-i18n="[placeholder]login.your_username" class="form-control form-control-lg form-control-solid" type="text" placeholder="Your username" name="username" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span data-i18n="login.send_reset_code" class="indicator-label">Send Reset Code</span>
|
||||
<span data-i18n="general.wait" class="indicator-progress">
|
||||
Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{- end}}
|
|
@ -1,97 +0,0 @@
|
|||
<!--
|
||||
Copyright (C) 2023 Nicola Murino
|
||||
|
||||
This WebUI uses the KeenThemes Mega Bundle, a proprietary theme:
|
||||
|
||||
https://keenthemes.com/products/templates-mega-bundle
|
||||
|
||||
KeenThemes HTML/CSS/JS components are allowed for use only within the
|
||||
SFTPGo product and restricted to be used in a resealable HTML template
|
||||
that can compete with KeenThemes products anyhow.
|
||||
|
||||
This WebUI is allowed for use only within the SFTPGo product and
|
||||
therefore cannot be used in derivative works/products without an
|
||||
explicit grant from the SFTPGo Team (support@sftpgo.com).
|
||||
-->
|
||||
{{- template "baselogin" .}}
|
||||
|
||||
{{- define "content"}}
|
||||
<form class="form w-100" id="sign_in_form" action="{{.CurrentURL}}" method="POST">
|
||||
<div class="container mb-10">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-5 align-items-center">
|
||||
<a href="{{.LoginURL}}">
|
||||
<img alt="Logo" src="{{.StaticURL}}{{.Branding.LogoPath}}" class="h-80px h-md-90px h-lg-100px" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-7">
|
||||
<a href="{{.LoginURL}}" class="text-gray-900 mb-3 ms-3 fs-1 fw-bold">
|
||||
{{.Branding.ShortName}}
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center mb-10">
|
||||
<h2 data-i18n="login.reset_password" class="text-gray-900 mb-3">
|
||||
Reset Password
|
||||
</h2>
|
||||
<div class="text-gray-600 fw-semibold fs-4">
|
||||
<span data-i18n="login.reset_pwd_msg">
|
||||
Check your email for the confirmation code
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
{{- template "errmsg" .Error}}
|
||||
<div class="fv-row mb-10">
|
||||
<input data-i18n="[placeholder]login.confirm_code" class="form-control form-control-lg form-control-solid" type="text" placeholder="Confirmation code" name="code" spellcheck="false" required />
|
||||
</div>
|
||||
<div class="fv-row mb-10">
|
||||
<div class="position-relative" data-password-control="container">
|
||||
<input data-i18n="[placeholder]general.new_password" data-password-control="input" class="form-control form-control-lg form-control-solid"
|
||||
type="password" name="password" placeholder="New Password" autocomplete="new-password" spellcheck="false" required />
|
||||
<span class="btn btn-sm btn-icon position-absolute translate-middle top-50 end-0 me-n2" data-password-control="visibility">
|
||||
<i class="ki-duotone ki-eye-slash fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
<i class="ki-duotone ki-eye d-none fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="fv-row mb-10">
|
||||
<div class="position-relative" data-password-control="container">
|
||||
<input data-i18n="[placeholder]general.confirm_password" data-password-control="input" class="form-control form-control-lg form-control-solid"
|
||||
type="password" name="confirm_password" placeholder="Confirm Password" autocomplete="new-password" spellcheck="false" required />
|
||||
<span class="btn btn-sm btn-icon position-absolute translate-middle top-50 end-0 me-n2" data-password-control="visibility">
|
||||
<i class="ki-duotone ki-eye-slash fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
<span class="path4"></span>
|
||||
</i>
|
||||
<i class="ki-duotone ki-eye d-none fs-1">
|
||||
<span class="path1"></span>
|
||||
<span class="path2"></span>
|
||||
<span class="path3"></span>
|
||||
</i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<input type="hidden" name="_form_token" value="{{.CSRFToken}}">
|
||||
<button type="submit" id="sign_in_submit" class="btn btn-lg btn-primary w-100 mb-5">
|
||||
<span data-i18n="login.reset_submit" class="indicator-label">Update Password & Login</span>
|
||||
<span data-i18n="general.wait" class="indicator-progress">
|
||||
Please wait...
|
||||
<span class="spinner-border spinner-border-sm align-middle ms-2"></span>
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{{- end}}
|
Loading…
Reference in a new issue