Compare commits
26 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1077d5cd6b | ||
|
|
57cc174b3d | ||
|
|
cf61baa273 | ||
|
|
2d5cd2d1d3 | ||
|
|
9472aad877 | ||
|
|
dd0f3ef144 | ||
|
|
44e492160b | ||
|
|
0137b03887 | ||
|
|
92c9b134c3 | ||
|
|
1737702c7c | ||
|
|
ece52ecf7c | ||
|
|
a7d6a72718 | ||
|
|
3fa9286238 | ||
|
|
ad5ff4cfe0 | ||
|
|
9aee1ebd2a | ||
|
|
f8ed1b41d6 | ||
|
|
bb9b0dfd2b | ||
|
|
cc2ce884fc | ||
|
|
e7e7679002 | ||
|
|
9a829fd594 | ||
|
|
624d61930c | ||
|
|
ee30e7711f | ||
|
|
6e5116aa27 | ||
|
|
34acffbb7b | ||
|
|
eb6f26c191 | ||
|
|
06f3e9744a |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -6,3 +6,5 @@ node_modules/
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
@@ -15,7 +15,7 @@ install:
|
||||
- go get github.com/tsenart/deadcode
|
||||
|
||||
script:
|
||||
- gometalinter --disable-all -E vet -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --exclude="rice-box.go" --tests ./...
|
||||
- gometalinter --disable-all -E gofmt -E misspell -E ineffassign -E goimports -E deadcode --exclude="rice-box.go" --tests ./...
|
||||
- go test ./... -timeout 30s
|
||||
|
||||
after_success:
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
<meta name="staticgen" content="{{ .StaticGen }}">
|
||||
<meta name="noauth" content="{{ .NoAuth }}">
|
||||
<meta name="version" content="{{ .Version }}">
|
||||
<meta name="recaptcha" content="{{ .ReCaptchaKey }}">
|
||||
<title>File Manager</title>
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="{{ .BaseURL }}/static/img/icons/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
||||
@@ -27,6 +28,10 @@
|
||||
|
||||
<script>CSS = "{{ .CSS }}"</script>
|
||||
|
||||
{{ if .ReCaptcha -}}
|
||||
<script src='https://www.google.com/recaptcha/api.js?render=explicit'></script>
|
||||
{{ end }}
|
||||
|
||||
<% for (var chunk of webpack.chunks) {
|
||||
for (var file of chunk.files) {
|
||||
if (file.match(/\.(js|css)$/)) { %>
|
||||
|
||||
@@ -1,22 +1,48 @@
|
||||
<template>
|
||||
<router-view @update:css="updateCSS" @clean:css="cleanCSS"></router-view>
|
||||
<router-view :dependencies="loaded" @update:css="updateCSS" @clean:css="cleanCSS"></router-view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'app',
|
||||
computed: mapState(['recaptcha']),
|
||||
data () {
|
||||
return {
|
||||
loaded: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
// Remove loading animation.
|
||||
let loading = document.getElementById('loading')
|
||||
loading.classList.add('done')
|
||||
if (this.recaptcha.length === 0) {
|
||||
this.unload()
|
||||
return
|
||||
}
|
||||
|
||||
setTimeout(function () {
|
||||
loading.parentNode.removeChild(loading)
|
||||
}, 200)
|
||||
let check = () => {
|
||||
if (typeof window.grecaptcha === 'undefined') {
|
||||
setTimeout(check, 100)
|
||||
return
|
||||
}
|
||||
|
||||
this.updateCSS()
|
||||
this.unload()
|
||||
}
|
||||
|
||||
check()
|
||||
},
|
||||
methods: {
|
||||
unload () {
|
||||
this.loaded = true
|
||||
// Remove loading animation.
|
||||
let loading = document.getElementById('loading')
|
||||
loading.classList.add('done')
|
||||
|
||||
setTimeout(function () {
|
||||
loading.parentNode.removeChild(loading)
|
||||
}, 200)
|
||||
|
||||
this.updateCSS()
|
||||
},
|
||||
updateCSS (global = false) {
|
||||
let css = this.$store.state.css
|
||||
|
||||
|
||||
@@ -196,6 +196,10 @@ export default {
|
||||
})
|
||||
},
|
||||
paste (event) {
|
||||
if (event.target.tagName.toLowerCase() === 'input') {
|
||||
return
|
||||
}
|
||||
|
||||
event.preventDefault()
|
||||
|
||||
let items = []
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="flat"
|
||||
@click="copy"
|
||||
:disabled="$route.path === dest"
|
||||
:aria-label="$t('buttons.copy')"
|
||||
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
|
||||
</div>
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||
<button class="flat"
|
||||
@click="move"
|
||||
:disabled="$route.path === dest"
|
||||
:aria-label="$t('buttons.move')"
|
||||
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
|
||||
</div>
|
||||
|
||||
@@ -112,6 +112,11 @@ button.flat.cancel {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
button.flat[disabled] {
|
||||
color: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.mobile-only {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,14 @@
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
#login.recaptcha form {
|
||||
min-width: 304px;
|
||||
}
|
||||
|
||||
#login #recaptcha {
|
||||
margin: .5em 0 0;
|
||||
}
|
||||
|
||||
#login input {
|
||||
width: 100%;
|
||||
width: 100%;
|
||||
|
||||
@@ -9,8 +9,39 @@ import zhTW from './zh-tw.yaml'
|
||||
|
||||
Vue.use(VueI18n)
|
||||
|
||||
export function detectLocale () {
|
||||
let locale = (navigator.language || navigator.browserLangugae).toLowerCase()
|
||||
switch (true) {
|
||||
case /^en.*/i.test(locale):
|
||||
locale = 'en'
|
||||
break
|
||||
case /^fr.*/i.test(locale):
|
||||
locale = 'fr'
|
||||
break
|
||||
case /^pt.*/i.test(locale):
|
||||
locale = 'pt'
|
||||
break
|
||||
case /^ja.*/i.test(locale):
|
||||
locale = 'ja'
|
||||
break
|
||||
case /^zh-CN/i.test(locale):
|
||||
locale = 'zh-cn'
|
||||
break
|
||||
case /^zh-TW/i.test(locale):
|
||||
locale = 'zh-tw'
|
||||
break
|
||||
case /^zh.*/i.test(locale):
|
||||
locale = 'zh-cn'
|
||||
break
|
||||
default:
|
||||
locale = 'en'
|
||||
}
|
||||
|
||||
return locale
|
||||
}
|
||||
|
||||
const i18n = new VueI18n({
|
||||
locale: 'en',
|
||||
locale: detectLocale(),
|
||||
fallbackLocale: 'en',
|
||||
messages: {
|
||||
'en': en,
|
||||
|
||||
@@ -124,7 +124,7 @@ settings:
|
||||
examples: 例
|
||||
globalSettings: グローバル設定
|
||||
language: 言語
|
||||
lockPassowrd: 新しいパスワードを変更に禁止
|
||||
lockPassword: 新しいパスワードを変更に禁止
|
||||
newPassword: 新しいパスワード
|
||||
newPasswordConfirm: 新しいパスワードを確認します
|
||||
newUser: 新しいユーザー
|
||||
|
||||
@@ -123,7 +123,7 @@ settings:
|
||||
examples: 例子
|
||||
globalSettings: 全局设置
|
||||
language: 语言
|
||||
lockPassowrd: 禁止用户修改密码
|
||||
lockPassword: 禁止用户修改密码
|
||||
newPassword: 您的新密码
|
||||
newPasswordConfirm: 重输一遍新密码
|
||||
newUser: 新建用户
|
||||
|
||||
@@ -31,7 +31,7 @@ Vue.prototype.$showError = function (error) {
|
||||
type: 'error',
|
||||
timeout: null,
|
||||
buttons: [
|
||||
Noty.button(i18n.t('buttons.reportIssue'), 'cancel', function () {
|
||||
Noty.button(i18n.t('buttons.reportIssue'), '', function () {
|
||||
window.open('https://github.com/hacdias/filemanager/issues/new')
|
||||
}),
|
||||
Noty.button(i18n.t('buttons.close'), '', function () {
|
||||
|
||||
@@ -11,7 +11,7 @@ import ProfileSettings from '@/views/settings/Profile'
|
||||
import Error403 from '@/views/errors/403'
|
||||
import Error404 from '@/views/errors/404'
|
||||
import Error500 from '@/views/errors/500'
|
||||
import auth from '@/utils/auth.js'
|
||||
import auth from '@/utils/auth'
|
||||
import store from '@/store'
|
||||
|
||||
Vue.use(Router)
|
||||
@@ -51,14 +51,13 @@ const router = new Router({
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
component: Settings,
|
||||
redirect: {
|
||||
path: '/settings/profile'
|
||||
},
|
||||
meta: {
|
||||
disableOnNoAuth: true
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: '/settings',
|
||||
name: 'Settings',
|
||||
redirect: {
|
||||
path: '/settings/profile'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/settings/profile',
|
||||
name: 'Profile Settings',
|
||||
@@ -131,16 +130,17 @@ router.beforeEach((to, from, next) => {
|
||||
auth.loggedIn()
|
||||
.then(() => {
|
||||
if (to.matched.some(record => record.meta.requiresAdmin)) {
|
||||
if (store.state.user.admin) {
|
||||
next()
|
||||
if (!store.state.user.admin) {
|
||||
next({ path: '/403' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
next({
|
||||
path: '/403'
|
||||
})
|
||||
|
||||
return
|
||||
if (to.matched.some(record => record.meta.disableOnNoAuth)) {
|
||||
if (store.state.noAuth) {
|
||||
next({ path: '/403' })
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
next()
|
||||
|
||||
@@ -17,6 +17,7 @@ const state = {
|
||||
window.CSS = null
|
||||
return css
|
||||
})(),
|
||||
recaptcha: document.querySelector('meta[name="recaptcha"]').getAttribute('content'),
|
||||
staticGen: document.querySelector('meta[name="staticgen"]').getAttribute('content'),
|
||||
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
||||
noAuth: (document.querySelector('meta[name="noauth"]').getAttribute('content') === 'true'),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import i18n from '@/i18n'
|
||||
import * as i18n from '@/i18n'
|
||||
import moment from 'moment'
|
||||
|
||||
const mutations = {
|
||||
@@ -27,8 +27,14 @@ const mutations = {
|
||||
setLoading: (state, value) => { state.loading = value },
|
||||
setReload: (state, value) => { state.reload = value },
|
||||
setUser: (state, value) => {
|
||||
moment.locale(value.locale)
|
||||
i18n.locale = value.locale
|
||||
let locale = value.locale
|
||||
|
||||
if (locale === '') {
|
||||
locale = i18n.detectLocale()
|
||||
}
|
||||
|
||||
moment.locale(locale)
|
||||
i18n.default.locale = locale
|
||||
state.user = value
|
||||
},
|
||||
setCSS: (state, value) => (state.css = value),
|
||||
|
||||
@@ -31,8 +31,8 @@ function loggedIn () {
|
||||
})
|
||||
}
|
||||
|
||||
function login (user, password) {
|
||||
let data = {username: user, password: password}
|
||||
function login (user, password, captcha) {
|
||||
let data = {username: user, password: password, recaptcha: captcha}
|
||||
return new Promise((resolve, reject) => {
|
||||
let request = new window.XMLHttpRequest()
|
||||
request.open('POST', `${store.state.baseURL}/api/auth/get`, true)
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<template>
|
||||
<div id="login">
|
||||
<div id="login" :class="{ recaptcha: recaptcha.length > 0 }">
|
||||
<form @submit="submit">
|
||||
<img src="../assets/logo.svg" alt="File Manager">
|
||||
<h1>File Manager</h1>
|
||||
<div v-if="wrong" class="wrong">{{ $t("login.wrongCredentials") }}</div>
|
||||
<input type="text" v-model="username" :placeholder="$t('login.username')">
|
||||
<input type="password" v-model="password" :placeholder="$t('login.password')">
|
||||
<div v-if="recaptcha.length" id="recaptcha"></div>
|
||||
<input type="submit" :value="$t('login.submit')">
|
||||
</form>
|
||||
</div>
|
||||
@@ -13,9 +14,12 @@
|
||||
|
||||
<script>
|
||||
import auth from '@/utils/auth'
|
||||
import { mapState } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'login',
|
||||
props: ['dependencies'],
|
||||
computed: mapState(['recaptcha']),
|
||||
data: function () {
|
||||
return {
|
||||
wrong: false,
|
||||
@@ -23,8 +27,23 @@ export default {
|
||||
password: ''
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.dependencies) this.setup()
|
||||
},
|
||||
watch: {
|
||||
dependencies: function (val) {
|
||||
if (val) this.setup()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
submit: function (event) {
|
||||
setup () {
|
||||
if (this.recaptcha.length === 0) return
|
||||
|
||||
window.grecaptcha.render('recaptcha', {
|
||||
sitekey: this.recaptcha
|
||||
})
|
||||
},
|
||||
submit (event) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
@@ -33,7 +52,17 @@ export default {
|
||||
redirect = '/files/'
|
||||
}
|
||||
|
||||
auth.login(this.username, this.password)
|
||||
let captcha = ''
|
||||
if (this.recaptcha.length > 0) {
|
||||
captcha = window.grecaptcha.getResponse()
|
||||
|
||||
if (captcha === '') {
|
||||
this.wrong = true
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
auth.login(this.username, this.password, captcha)
|
||||
.then(() => { this.$router.push({ path: redirect }) })
|
||||
.catch(() => { this.wrong = true })
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<div class="dashboard">
|
||||
<form class="card" @submit.prevent="saveStaticGen">
|
||||
<form class="card" v-if="staticGen.length" @submit.prevent="saveStaticGen">
|
||||
<div class="card-title">
|
||||
<h2>{{ capitalize($store.state.staticGen) }}</h2>
|
||||
</div>
|
||||
@@ -80,7 +80,6 @@ export default {
|
||||
created () {
|
||||
getSettings()
|
||||
.then(settings => {
|
||||
console.log(settings)
|
||||
if (this.$store.state.staticGen.length > 0) {
|
||||
this.parseStaticGen(settings.staticGen)
|
||||
}
|
||||
|
||||
@@ -48,6 +48,8 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
|
||||
scope := "."
|
||||
database := ""
|
||||
noAuth := false
|
||||
reCaptchaKey := ""
|
||||
reCaptchaSecret := ""
|
||||
|
||||
if plugin != "" {
|
||||
baseURL = "/admin"
|
||||
@@ -152,9 +154,21 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
|
||||
}
|
||||
|
||||
u.ViewMode = c.Val()
|
||||
if u.ViewMode != "mosaic" && u.ViewMode != "list" {
|
||||
if u.ViewMode != filemanager.MosaicViewMode && u.ViewMode != filemanager.ListViewMode {
|
||||
return nil, c.ArgErr()
|
||||
}
|
||||
case "recaptcha_key":
|
||||
if !c.NextArg() {
|
||||
return nil, c.ArgErr()
|
||||
}
|
||||
|
||||
reCaptchaKey = c.Val()
|
||||
case "recaptcha_secret":
|
||||
if !c.NextArg() {
|
||||
return nil, c.ArgErr()
|
||||
}
|
||||
|
||||
reCaptchaSecret = c.Val()
|
||||
case "no_auth":
|
||||
if !c.NextArg() {
|
||||
noAuth = true
|
||||
@@ -193,7 +207,7 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
|
||||
sha := hex.EncodeToString(hasher.Sum(nil))
|
||||
database = filepath.Join(path, sha+".db")
|
||||
|
||||
fmt.Println("[WARNING] A database is going to be created for your File Manager instace at " + database +
|
||||
fmt.Println("[WARNING] A database is going to be created for your File Manager instance at " + database +
|
||||
". It is highly recommended that you set the 'database' option to '" + sha + ".db'\n")
|
||||
}
|
||||
|
||||
@@ -213,10 +227,12 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
|
||||
}
|
||||
|
||||
m := &filemanager.FileManager{
|
||||
NoAuth: noAuth,
|
||||
BaseURL: "",
|
||||
PrefixURL: "",
|
||||
DefaultUser: u,
|
||||
NoAuth: noAuth,
|
||||
BaseURL: "",
|
||||
PrefixURL: "",
|
||||
ReCaptchaKey: reCaptchaKey,
|
||||
ReCaptchaSecret: reCaptchaSecret,
|
||||
DefaultUser: u,
|
||||
Store: &filemanager.Store{
|
||||
Config: bolt.ConfigStore{DB: db},
|
||||
Users: bolt.UsersStore{DB: db},
|
||||
|
||||
@@ -24,24 +24,26 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
addr string
|
||||
config string
|
||||
database string
|
||||
scope string
|
||||
commands string
|
||||
logfile string
|
||||
staticg string
|
||||
locale string
|
||||
baseurl string
|
||||
prefixurl string
|
||||
viewMode string
|
||||
port int
|
||||
noAuth bool
|
||||
allowCommands bool
|
||||
allowEdit bool
|
||||
allowNew bool
|
||||
allowPublish bool
|
||||
showVer bool
|
||||
addr string
|
||||
config string
|
||||
database string
|
||||
scope string
|
||||
commands string
|
||||
logfile string
|
||||
staticg string
|
||||
locale string
|
||||
baseurl string
|
||||
prefixurl string
|
||||
viewMode string
|
||||
recaptchakey string
|
||||
recaptchasecret string
|
||||
port int
|
||||
noAuth bool
|
||||
allowCommands bool
|
||||
allowEdit bool
|
||||
allowNew bool
|
||||
allowPublish bool
|
||||
showVer bool
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -55,12 +57,14 @@ func init() {
|
||||
flag.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
|
||||
flag.StringVar(&prefixurl, "prefixurl", "", "Prefix URL")
|
||||
flag.StringVar(&viewMode, "view-mode", "mosaic", "Default view mode for new users")
|
||||
flag.StringVar(&recaptchakey, "recaptcha-key", "", "ReCaptcha site key")
|
||||
flag.StringVar(&recaptchasecret, "recaptcha-secret", "", "ReCaptcha secret")
|
||||
flag.BoolVar(&allowCommands, "allow-commands", true, "Default allow commands option for new users")
|
||||
flag.BoolVar(&allowEdit, "allow-edit", true, "Default allow edit option for new users")
|
||||
flag.BoolVar(&allowPublish, "allow-publish", true, "Default allow publish option for new users")
|
||||
flag.BoolVar(&allowNew, "allow-new", true, "Default allow new option for new users")
|
||||
flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
|
||||
flag.StringVar(&locale, "locale", "en", "Default locale for new users")
|
||||
flag.StringVar(&locale, "locale", "", "Default locale for new users, set it empty to enable auto detect from browser")
|
||||
flag.StringVar(&staticg, "staticgen", "", "Static Generator you want to enable")
|
||||
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
|
||||
}
|
||||
@@ -77,11 +81,13 @@ func setupViper() {
|
||||
viper.SetDefault("AllowNew", true)
|
||||
viper.SetDefault("AllowPublish", true)
|
||||
viper.SetDefault("StaticGen", "")
|
||||
viper.SetDefault("Locale", "en")
|
||||
viper.SetDefault("Locale", "")
|
||||
viper.SetDefault("NoAuth", false)
|
||||
viper.SetDefault("BaseURL", "")
|
||||
viper.SetDefault("PrefixURL", "")
|
||||
viper.SetDefault("ViewMode", "mosaic")
|
||||
viper.SetDefault("ViewMode", filemanager.MosaicViewMode)
|
||||
viper.SetDefault("ReCaptchaKey", "")
|
||||
viper.SetDefault("ReCaptchaSecret", "")
|
||||
|
||||
viper.BindPFlag("Port", flag.Lookup("port"))
|
||||
viper.BindPFlag("Address", flag.Lookup("address"))
|
||||
@@ -99,6 +105,8 @@ func setupViper() {
|
||||
viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
|
||||
viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
|
||||
viper.BindPFlag("ViewMode", flag.Lookup("view-mode"))
|
||||
viper.BindPFlag("ReCaptchaKey", flag.Lookup("recaptcha-key"))
|
||||
viper.BindPFlag("ReCaptchaSecret", flag.Lookup("recaptcha-secret"))
|
||||
|
||||
viper.SetConfigName("filemanager")
|
||||
viper.AddConfigPath(".")
|
||||
@@ -179,9 +187,11 @@ func handler() http.Handler {
|
||||
}
|
||||
|
||||
fm := &filemanager.FileManager{
|
||||
NoAuth: viper.GetBool("NoAuth"),
|
||||
BaseURL: viper.GetString("BaseURL"),
|
||||
PrefixURL: viper.GetString("PrefixURL"),
|
||||
NoAuth: viper.GetBool("NoAuth"),
|
||||
BaseURL: viper.GetString("BaseURL"),
|
||||
PrefixURL: viper.GetString("PrefixURL"),
|
||||
ReCaptchaKey: viper.GetString("ReCaptchaKey"),
|
||||
ReCaptchaSecret: viper.GetString("ReCaptchaSecret"),
|
||||
DefaultUser: &filemanager.User{
|
||||
AllowCommands: viper.GetBool("AllowCommands"),
|
||||
AllowEdit: viper.GetBool("AllowEdit"),
|
||||
|
||||
@@ -22,7 +22,13 @@ import (
|
||||
)
|
||||
|
||||
// Version is the current File Manager version.
|
||||
const Version = "1.3.2"
|
||||
const (
|
||||
// Version is the current File Manager version.
|
||||
Version = "1.3.7"
|
||||
|
||||
ListViewMode = "list"
|
||||
MosaicViewMode = "mosaic"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrExist = errors.New("the resource already exists")
|
||||
@@ -66,6 +72,10 @@ type FileManager struct {
|
||||
// there will only exist one user, called "admin".
|
||||
NoAuth bool
|
||||
|
||||
// ReCaptcha Site key and secret.
|
||||
ReCaptchaKey string
|
||||
ReCaptchaSecret string
|
||||
|
||||
// StaticGen is the static websit generator handler.
|
||||
StaticGen StaticGen
|
||||
|
||||
@@ -201,6 +211,14 @@ func (m *FileManager) Setup() error {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this after 1.5
|
||||
for _, user := range users {
|
||||
if user.ViewMode != ListViewMode && user.ViewMode != MosaicViewMode {
|
||||
user.ViewMode = ListViewMode
|
||||
m.Store.Users.Update(user, "ViewMode")
|
||||
}
|
||||
}
|
||||
|
||||
m.DefaultUser.Username = ""
|
||||
m.DefaultUser.Password = ""
|
||||
|
||||
@@ -344,7 +362,7 @@ var DefaultUser = User{
|
||||
Rules: []*Rule{},
|
||||
CSS: "",
|
||||
Admin: true,
|
||||
Locale: "en",
|
||||
Locale: "",
|
||||
Scope: ".",
|
||||
FileSystem: fileutils.Dir("."),
|
||||
ViewMode: "mosaic",
|
||||
|
||||
57
http/auth.go
57
http/auth.go
@@ -1,8 +1,11 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@@ -11,6 +14,45 @@ import (
|
||||
fm "github.com/hacdias/filemanager"
|
||||
)
|
||||
|
||||
type cred struct {
|
||||
Password string `json:"password"`
|
||||
Username string `json:"username"`
|
||||
Recaptcha string `json:"recaptcha"`
|
||||
}
|
||||
|
||||
// recaptcha checks the recaptcha code.
|
||||
func recaptcha(secret string, response string) (bool, error) {
|
||||
api := "https://www.google.com/recaptcha/api/siteverify"
|
||||
|
||||
body := url.Values{}
|
||||
body.Set("secret", secret)
|
||||
body.Add("response", response)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Post(api, "application/x-www-form-urlencoded", bytes.NewBufferString(body.Encode()))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
var data struct {
|
||||
Success bool `json:"success"`
|
||||
ChallengeTS time.Time `json:"challenge_ts"`
|
||||
Hostname string `json:"hostname"`
|
||||
ErrorCodes interface{} `json:"error-codes"`
|
||||
}
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&data)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return data.Success, nil
|
||||
}
|
||||
|
||||
// authHandler proccesses the authentication for the user.
|
||||
func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||
// NoAuth instances shouldn't call this method.
|
||||
@@ -19,7 +61,7 @@ func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, er
|
||||
}
|
||||
|
||||
// Receive the credentials from the request and unmarshal them.
|
||||
var cred fm.User
|
||||
var cred cred
|
||||
if r.Body == nil {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
@@ -29,6 +71,19 @@ func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, er
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
|
||||
// If ReCaptcha is enabled, check the code.
|
||||
if len(c.ReCaptchaSecret) > 0 {
|
||||
ok, err := recaptcha(c.ReCaptchaSecret, cred.Recaptcha)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return http.StatusForbidden, err
|
||||
}
|
||||
|
||||
if !ok {
|
||||
return http.StatusForbidden, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if the user exists.
|
||||
u, err := c.Store.Users.GetByUsername(cred.Username, c.NewFS)
|
||||
if err != nil {
|
||||
|
||||
21
http/http.go
21
http/http.go
@@ -25,16 +25,13 @@ func Handler(m *fm.FileManager) http.Handler {
|
||||
if code >= 400 {
|
||||
w.WriteHeader(code)
|
||||
|
||||
if err == nil {
|
||||
txt := http.StatusText(code)
|
||||
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
|
||||
w.Write([]byte(txt))
|
||||
}
|
||||
txt := http.StatusText(code)
|
||||
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
|
||||
w.Write([]byte(txt + "\n"))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
w.Write([]byte(err.Error()))
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -226,10 +223,13 @@ func renderFile(c *fm.Context, w http.ResponseWriter, file string) (int, error)
|
||||
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||
|
||||
data := map[string]interface{}{
|
||||
"BaseURL": c.RootURL(),
|
||||
"NoAuth": c.NoAuth,
|
||||
"Version": fm.Version,
|
||||
"CSS": template.CSS(c.CSS),
|
||||
"BaseURL": c.RootURL(),
|
||||
"NoAuth": c.NoAuth,
|
||||
"Version": fm.Version,
|
||||
"CSS": template.CSS(c.CSS),
|
||||
"ReCaptcha": c.ReCaptchaKey != "" && c.ReCaptchaSecret != "",
|
||||
"ReCaptchaKey": c.ReCaptchaKey,
|
||||
"ReCaptchaSecret": c.ReCaptchaSecret,
|
||||
}
|
||||
|
||||
if c.StaticGen != nil {
|
||||
@@ -267,6 +267,7 @@ func sharePage(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, erro
|
||||
|
||||
info, err := os.Stat(s.Path)
|
||||
if err != nil {
|
||||
c.Store.Share.Delete(s.Hash)
|
||||
return ErrorToHTTP(err, false), err
|
||||
}
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ func usersPostHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (in
|
||||
}
|
||||
|
||||
u.Password = pw
|
||||
u.ViewMode = fm.MosaicViewMode
|
||||
|
||||
// Saves the user to the database.
|
||||
err = c.Store.Users.Save(u)
|
||||
@@ -268,6 +269,13 @@ func usersPutHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int
|
||||
return http.StatusBadRequest, err
|
||||
}
|
||||
|
||||
// If we're updating the default user. Only for NoAuth
|
||||
// implementations. Used to change the viewMode.
|
||||
if id == 0 && c.NoAuth {
|
||||
c.DefaultUser.ViewMode = u.ViewMode
|
||||
return http.StatusOK, nil
|
||||
}
|
||||
|
||||
// Updates the CSS and locale.
|
||||
if which == "partial" {
|
||||
c.User.CSS = u.CSS
|
||||
|
||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -8055,15 +8055,6 @@
|
||||
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
|
||||
"dev": true
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"string-length": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",
|
||||
@@ -8100,6 +8091,15 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"string_decoder": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz",
|
||||
"integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"safe-buffer": "5.1.1"
|
||||
}
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||
|
||||
@@ -1 +1 @@
|
||||
36b9bba06c64cd83f3994bbb77fc36948d9d2dfe
|
||||
882c59547968e4fb9a65aa8d1c838409f198e51b
|
||||
Reference in New Issue
Block a user