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*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ install:
|
|||||||
- go get github.com/tsenart/deadcode
|
- go get github.com/tsenart/deadcode
|
||||||
|
|
||||||
script:
|
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
|
- go test ./... -timeout 30s
|
||||||
|
|
||||||
after_success:
|
after_success:
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
<meta name="staticgen" content="{{ .StaticGen }}">
|
<meta name="staticgen" content="{{ .StaticGen }}">
|
||||||
<meta name="noauth" content="{{ .NoAuth }}">
|
<meta name="noauth" content="{{ .NoAuth }}">
|
||||||
<meta name="version" content="{{ .Version }}">
|
<meta name="version" content="{{ .Version }}">
|
||||||
|
<meta name="recaptcha" content="{{ .ReCaptchaKey }}">
|
||||||
<title>File Manager</title>
|
<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="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">
|
<link rel="icon" type="image/png" sizes="16x16" href="{{ .BaseURL }}/static/img/icons/favicon-16x16.png">
|
||||||
@@ -27,6 +28,10 @@
|
|||||||
|
|
||||||
<script>CSS = "{{ .CSS }}"</script>
|
<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 chunk of webpack.chunks) {
|
||||||
for (var file of chunk.files) {
|
for (var file of chunk.files) {
|
||||||
if (file.match(/\.(js|css)$/)) { %>
|
if (file.match(/\.(js|css)$/)) { %>
|
||||||
|
|||||||
@@ -1,22 +1,48 @@
|
|||||||
<template>
|
<template>
|
||||||
<router-view @update:css="updateCSS" @clean:css="cleanCSS"></router-view>
|
<router-view :dependencies="loaded" @update:css="updateCSS" @clean:css="cleanCSS"></router-view>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'app',
|
name: 'app',
|
||||||
|
computed: mapState(['recaptcha']),
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
loaded: false
|
||||||
|
}
|
||||||
|
},
|
||||||
mounted () {
|
mounted () {
|
||||||
// Remove loading animation.
|
if (this.recaptcha.length === 0) {
|
||||||
let loading = document.getElementById('loading')
|
this.unload()
|
||||||
loading.classList.add('done')
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(function () {
|
let check = () => {
|
||||||
loading.parentNode.removeChild(loading)
|
if (typeof window.grecaptcha === 'undefined') {
|
||||||
}, 200)
|
setTimeout(check, 100)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
this.updateCSS()
|
this.unload()
|
||||||
|
}
|
||||||
|
|
||||||
|
check()
|
||||||
},
|
},
|
||||||
methods: {
|
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) {
|
updateCSS (global = false) {
|
||||||
let css = this.$store.state.css
|
let css = this.$store.state.css
|
||||||
|
|
||||||
|
|||||||
@@ -196,6 +196,10 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
paste (event) {
|
paste (event) {
|
||||||
|
if (event.target.tagName.toLowerCase() === 'input') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
|
||||||
let items = []
|
let items = []
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
<button class="flat"
|
<button class="flat"
|
||||||
@click="copy"
|
@click="copy"
|
||||||
|
:disabled="$route.path === dest"
|
||||||
:aria-label="$t('buttons.copy')"
|
:aria-label="$t('buttons.copy')"
|
||||||
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
|
:title="$t('buttons.copy')">{{ $t('buttons.copy') }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
:title="$t('buttons.cancel')">{{ $t('buttons.cancel') }}</button>
|
||||||
<button class="flat"
|
<button class="flat"
|
||||||
@click="move"
|
@click="move"
|
||||||
|
:disabled="$route.path === dest"
|
||||||
:aria-label="$t('buttons.move')"
|
:aria-label="$t('buttons.move')"
|
||||||
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
|
:title="$t('buttons.move')">{{ $t('buttons.move') }}</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -112,6 +112,11 @@ button.flat.cancel {
|
|||||||
color: #ccc;
|
color: #ccc;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.flat[disabled] {
|
||||||
|
color: #ccc;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
|
||||||
.mobile-only {
|
.mobile-only {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,14 @@
|
|||||||
width: 90%;
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#login.recaptcha form {
|
||||||
|
min-width: 304px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#login #recaptcha {
|
||||||
|
margin: .5em 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
#login input {
|
#login input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|||||||
@@ -9,8 +9,39 @@ import zhTW from './zh-tw.yaml'
|
|||||||
|
|
||||||
Vue.use(VueI18n)
|
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({
|
const i18n = new VueI18n({
|
||||||
locale: 'en',
|
locale: detectLocale(),
|
||||||
fallbackLocale: 'en',
|
fallbackLocale: 'en',
|
||||||
messages: {
|
messages: {
|
||||||
'en': en,
|
'en': en,
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ settings:
|
|||||||
examples: 例
|
examples: 例
|
||||||
globalSettings: グローバル設定
|
globalSettings: グローバル設定
|
||||||
language: 言語
|
language: 言語
|
||||||
lockPassowrd: 新しいパスワードを変更に禁止
|
lockPassword: 新しいパスワードを変更に禁止
|
||||||
newPassword: 新しいパスワード
|
newPassword: 新しいパスワード
|
||||||
newPasswordConfirm: 新しいパスワードを確認します
|
newPasswordConfirm: 新しいパスワードを確認します
|
||||||
newUser: 新しいユーザー
|
newUser: 新しいユーザー
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ settings:
|
|||||||
examples: 例子
|
examples: 例子
|
||||||
globalSettings: 全局设置
|
globalSettings: 全局设置
|
||||||
language: 语言
|
language: 语言
|
||||||
lockPassowrd: 禁止用户修改密码
|
lockPassword: 禁止用户修改密码
|
||||||
newPassword: 您的新密码
|
newPassword: 您的新密码
|
||||||
newPasswordConfirm: 重输一遍新密码
|
newPasswordConfirm: 重输一遍新密码
|
||||||
newUser: 新建用户
|
newUser: 新建用户
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Vue.prototype.$showError = function (error) {
|
|||||||
type: 'error',
|
type: 'error',
|
||||||
timeout: null,
|
timeout: null,
|
||||||
buttons: [
|
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')
|
window.open('https://github.com/hacdias/filemanager/issues/new')
|
||||||
}),
|
}),
|
||||||
Noty.button(i18n.t('buttons.close'), '', function () {
|
Noty.button(i18n.t('buttons.close'), '', function () {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import ProfileSettings from '@/views/settings/Profile'
|
|||||||
import Error403 from '@/views/errors/403'
|
import Error403 from '@/views/errors/403'
|
||||||
import Error404 from '@/views/errors/404'
|
import Error404 from '@/views/errors/404'
|
||||||
import Error500 from '@/views/errors/500'
|
import Error500 from '@/views/errors/500'
|
||||||
import auth from '@/utils/auth.js'
|
import auth from '@/utils/auth'
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
|
|
||||||
Vue.use(Router)
|
Vue.use(Router)
|
||||||
@@ -51,14 +51,13 @@ const router = new Router({
|
|||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
component: Settings,
|
component: Settings,
|
||||||
|
redirect: {
|
||||||
|
path: '/settings/profile'
|
||||||
|
},
|
||||||
|
meta: {
|
||||||
|
disableOnNoAuth: true
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
{
|
|
||||||
path: '/settings',
|
|
||||||
name: 'Settings',
|
|
||||||
redirect: {
|
|
||||||
path: '/settings/profile'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: '/settings/profile',
|
path: '/settings/profile',
|
||||||
name: 'Profile Settings',
|
name: 'Profile Settings',
|
||||||
@@ -131,16 +130,17 @@ router.beforeEach((to, from, next) => {
|
|||||||
auth.loggedIn()
|
auth.loggedIn()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (to.matched.some(record => record.meta.requiresAdmin)) {
|
if (to.matched.some(record => record.meta.requiresAdmin)) {
|
||||||
if (store.state.user.admin) {
|
if (!store.state.user.admin) {
|
||||||
next()
|
next({ path: '/403' })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
next({
|
if (to.matched.some(record => record.meta.disableOnNoAuth)) {
|
||||||
path: '/403'
|
if (store.state.noAuth) {
|
||||||
})
|
next({ path: '/403' })
|
||||||
|
return
|
||||||
return
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
next()
|
next()
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const state = {
|
|||||||
window.CSS = null
|
window.CSS = null
|
||||||
return css
|
return css
|
||||||
})(),
|
})(),
|
||||||
|
recaptcha: document.querySelector('meta[name="recaptcha"]').getAttribute('content'),
|
||||||
staticGen: document.querySelector('meta[name="staticgen"]').getAttribute('content'),
|
staticGen: document.querySelector('meta[name="staticgen"]').getAttribute('content'),
|
||||||
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
baseURL: document.querySelector('meta[name="base"]').getAttribute('content'),
|
||||||
noAuth: (document.querySelector('meta[name="noauth"]').getAttribute('content') === 'true'),
|
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'
|
import moment from 'moment'
|
||||||
|
|
||||||
const mutations = {
|
const mutations = {
|
||||||
@@ -27,8 +27,14 @@ const mutations = {
|
|||||||
setLoading: (state, value) => { state.loading = value },
|
setLoading: (state, value) => { state.loading = value },
|
||||||
setReload: (state, value) => { state.reload = value },
|
setReload: (state, value) => { state.reload = value },
|
||||||
setUser: (state, value) => {
|
setUser: (state, value) => {
|
||||||
moment.locale(value.locale)
|
let locale = value.locale
|
||||||
i18n.locale = value.locale
|
|
||||||
|
if (locale === '') {
|
||||||
|
locale = i18n.detectLocale()
|
||||||
|
}
|
||||||
|
|
||||||
|
moment.locale(locale)
|
||||||
|
i18n.default.locale = locale
|
||||||
state.user = value
|
state.user = value
|
||||||
},
|
},
|
||||||
setCSS: (state, value) => (state.css = value),
|
setCSS: (state, value) => (state.css = value),
|
||||||
|
|||||||
@@ -31,8 +31,8 @@ function loggedIn () {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function login (user, password) {
|
function login (user, password, captcha) {
|
||||||
let data = {username: user, password: password}
|
let data = {username: user, password: password, recaptcha: captcha}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let request = new window.XMLHttpRequest()
|
let request = new window.XMLHttpRequest()
|
||||||
request.open('POST', `${store.state.baseURL}/api/auth/get`, true)
|
request.open('POST', `${store.state.baseURL}/api/auth/get`, true)
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div id="login">
|
<div id="login" :class="{ recaptcha: recaptcha.length > 0 }">
|
||||||
<form @submit="submit">
|
<form @submit="submit">
|
||||||
<img src="../assets/logo.svg" alt="File Manager">
|
<img src="../assets/logo.svg" alt="File Manager">
|
||||||
<h1>File Manager</h1>
|
<h1>File Manager</h1>
|
||||||
<div v-if="wrong" class="wrong">{{ $t("login.wrongCredentials") }}</div>
|
<div v-if="wrong" class="wrong">{{ $t("login.wrongCredentials") }}</div>
|
||||||
<input type="text" v-model="username" :placeholder="$t('login.username')">
|
<input type="text" v-model="username" :placeholder="$t('login.username')">
|
||||||
<input type="password" v-model="password" :placeholder="$t('login.password')">
|
<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')">
|
<input type="submit" :value="$t('login.submit')">
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@@ -13,9 +14,12 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import auth from '@/utils/auth'
|
import auth from '@/utils/auth'
|
||||||
|
import { mapState } from 'vuex'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'login',
|
name: 'login',
|
||||||
|
props: ['dependencies'],
|
||||||
|
computed: mapState(['recaptcha']),
|
||||||
data: function () {
|
data: function () {
|
||||||
return {
|
return {
|
||||||
wrong: false,
|
wrong: false,
|
||||||
@@ -23,8 +27,23 @@ export default {
|
|||||||
password: ''
|
password: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.dependencies) this.setup()
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
dependencies: function (val) {
|
||||||
|
if (val) this.setup()
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
submit: function (event) {
|
setup () {
|
||||||
|
if (this.recaptcha.length === 0) return
|
||||||
|
|
||||||
|
window.grecaptcha.render('recaptcha', {
|
||||||
|
sitekey: this.recaptcha
|
||||||
|
})
|
||||||
|
},
|
||||||
|
submit (event) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
|
|
||||||
@@ -33,7 +52,17 @@ export default {
|
|||||||
redirect = '/files/'
|
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 }) })
|
.then(() => { this.$router.push({ path: redirect }) })
|
||||||
.catch(() => { this.wrong = true })
|
.catch(() => { this.wrong = true })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
<form class="card" @submit.prevent="saveStaticGen">
|
<form class="card" v-if="staticGen.length" @submit.prevent="saveStaticGen">
|
||||||
<div class="card-title">
|
<div class="card-title">
|
||||||
<h2>{{ capitalize($store.state.staticGen) }}</h2>
|
<h2>{{ capitalize($store.state.staticGen) }}</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -80,7 +80,6 @@ export default {
|
|||||||
created () {
|
created () {
|
||||||
getSettings()
|
getSettings()
|
||||||
.then(settings => {
|
.then(settings => {
|
||||||
console.log(settings)
|
|
||||||
if (this.$store.state.staticGen.length > 0) {
|
if (this.$store.state.staticGen.length > 0) {
|
||||||
this.parseStaticGen(settings.staticGen)
|
this.parseStaticGen(settings.staticGen)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
|
|||||||
scope := "."
|
scope := "."
|
||||||
database := ""
|
database := ""
|
||||||
noAuth := false
|
noAuth := false
|
||||||
|
reCaptchaKey := ""
|
||||||
|
reCaptchaSecret := ""
|
||||||
|
|
||||||
if plugin != "" {
|
if plugin != "" {
|
||||||
baseURL = "/admin"
|
baseURL = "/admin"
|
||||||
@@ -152,9 +154,21 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
u.ViewMode = c.Val()
|
u.ViewMode = c.Val()
|
||||||
if u.ViewMode != "mosaic" && u.ViewMode != "list" {
|
if u.ViewMode != filemanager.MosaicViewMode && u.ViewMode != filemanager.ListViewMode {
|
||||||
return nil, c.ArgErr()
|
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":
|
case "no_auth":
|
||||||
if !c.NextArg() {
|
if !c.NextArg() {
|
||||||
noAuth = true
|
noAuth = true
|
||||||
@@ -193,7 +207,7 @@ func Parse(c *caddy.Controller, plugin string) ([]*filemanager.FileManager, erro
|
|||||||
sha := hex.EncodeToString(hasher.Sum(nil))
|
sha := hex.EncodeToString(hasher.Sum(nil))
|
||||||
database = filepath.Join(path, sha+".db")
|
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")
|
". 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{
|
m := &filemanager.FileManager{
|
||||||
NoAuth: noAuth,
|
NoAuth: noAuth,
|
||||||
BaseURL: "",
|
BaseURL: "",
|
||||||
PrefixURL: "",
|
PrefixURL: "",
|
||||||
DefaultUser: u,
|
ReCaptchaKey: reCaptchaKey,
|
||||||
|
ReCaptchaSecret: reCaptchaSecret,
|
||||||
|
DefaultUser: u,
|
||||||
Store: &filemanager.Store{
|
Store: &filemanager.Store{
|
||||||
Config: bolt.ConfigStore{DB: db},
|
Config: bolt.ConfigStore{DB: db},
|
||||||
Users: bolt.UsersStore{DB: db},
|
Users: bolt.UsersStore{DB: db},
|
||||||
|
|||||||
@@ -24,24 +24,26 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
addr string
|
addr string
|
||||||
config string
|
config string
|
||||||
database string
|
database string
|
||||||
scope string
|
scope string
|
||||||
commands string
|
commands string
|
||||||
logfile string
|
logfile string
|
||||||
staticg string
|
staticg string
|
||||||
locale string
|
locale string
|
||||||
baseurl string
|
baseurl string
|
||||||
prefixurl string
|
prefixurl string
|
||||||
viewMode string
|
viewMode string
|
||||||
port int
|
recaptchakey string
|
||||||
noAuth bool
|
recaptchasecret string
|
||||||
allowCommands bool
|
port int
|
||||||
allowEdit bool
|
noAuth bool
|
||||||
allowNew bool
|
allowCommands bool
|
||||||
allowPublish bool
|
allowEdit bool
|
||||||
showVer bool
|
allowNew bool
|
||||||
|
allowPublish bool
|
||||||
|
showVer bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -55,12 +57,14 @@ func init() {
|
|||||||
flag.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
|
flag.StringVar(&commands, "commands", "git svn hg", "Default commands option for new users")
|
||||||
flag.StringVar(&prefixurl, "prefixurl", "", "Prefix URL")
|
flag.StringVar(&prefixurl, "prefixurl", "", "Prefix URL")
|
||||||
flag.StringVar(&viewMode, "view-mode", "mosaic", "Default view mode for new users")
|
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(&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(&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(&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(&allowNew, "allow-new", true, "Default allow new option for new users")
|
||||||
flag.BoolVar(&noAuth, "no-auth", false, "Disables authentication")
|
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.StringVar(&staticg, "staticgen", "", "Static Generator you want to enable")
|
||||||
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
|
flag.BoolVarP(&showVer, "version", "v", false, "Show version")
|
||||||
}
|
}
|
||||||
@@ -77,11 +81,13 @@ func setupViper() {
|
|||||||
viper.SetDefault("AllowNew", true)
|
viper.SetDefault("AllowNew", true)
|
||||||
viper.SetDefault("AllowPublish", true)
|
viper.SetDefault("AllowPublish", true)
|
||||||
viper.SetDefault("StaticGen", "")
|
viper.SetDefault("StaticGen", "")
|
||||||
viper.SetDefault("Locale", "en")
|
viper.SetDefault("Locale", "")
|
||||||
viper.SetDefault("NoAuth", false)
|
viper.SetDefault("NoAuth", false)
|
||||||
viper.SetDefault("BaseURL", "")
|
viper.SetDefault("BaseURL", "")
|
||||||
viper.SetDefault("PrefixURL", "")
|
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("Port", flag.Lookup("port"))
|
||||||
viper.BindPFlag("Address", flag.Lookup("address"))
|
viper.BindPFlag("Address", flag.Lookup("address"))
|
||||||
@@ -99,6 +105,8 @@ func setupViper() {
|
|||||||
viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
|
viper.BindPFlag("BaseURL", flag.Lookup("baseurl"))
|
||||||
viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
|
viper.BindPFlag("PrefixURL", flag.Lookup("prefixurl"))
|
||||||
viper.BindPFlag("ViewMode", flag.Lookup("view-mode"))
|
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.SetConfigName("filemanager")
|
||||||
viper.AddConfigPath(".")
|
viper.AddConfigPath(".")
|
||||||
@@ -179,9 +187,11 @@ func handler() http.Handler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fm := &filemanager.FileManager{
|
fm := &filemanager.FileManager{
|
||||||
NoAuth: viper.GetBool("NoAuth"),
|
NoAuth: viper.GetBool("NoAuth"),
|
||||||
BaseURL: viper.GetString("BaseURL"),
|
BaseURL: viper.GetString("BaseURL"),
|
||||||
PrefixURL: viper.GetString("PrefixURL"),
|
PrefixURL: viper.GetString("PrefixURL"),
|
||||||
|
ReCaptchaKey: viper.GetString("ReCaptchaKey"),
|
||||||
|
ReCaptchaSecret: viper.GetString("ReCaptchaSecret"),
|
||||||
DefaultUser: &filemanager.User{
|
DefaultUser: &filemanager.User{
|
||||||
AllowCommands: viper.GetBool("AllowCommands"),
|
AllowCommands: viper.GetBool("AllowCommands"),
|
||||||
AllowEdit: viper.GetBool("AllowEdit"),
|
AllowEdit: viper.GetBool("AllowEdit"),
|
||||||
|
|||||||
@@ -22,7 +22,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// Version is the current File Manager version.
|
// 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 (
|
var (
|
||||||
ErrExist = errors.New("the resource already exists")
|
ErrExist = errors.New("the resource already exists")
|
||||||
@@ -66,6 +72,10 @@ type FileManager struct {
|
|||||||
// there will only exist one user, called "admin".
|
// there will only exist one user, called "admin".
|
||||||
NoAuth bool
|
NoAuth bool
|
||||||
|
|
||||||
|
// ReCaptcha Site key and secret.
|
||||||
|
ReCaptchaKey string
|
||||||
|
ReCaptchaSecret string
|
||||||
|
|
||||||
// StaticGen is the static websit generator handler.
|
// StaticGen is the static websit generator handler.
|
||||||
StaticGen StaticGen
|
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.Username = ""
|
||||||
m.DefaultUser.Password = ""
|
m.DefaultUser.Password = ""
|
||||||
|
|
||||||
@@ -344,7 +362,7 @@ var DefaultUser = User{
|
|||||||
Rules: []*Rule{},
|
Rules: []*Rule{},
|
||||||
CSS: "",
|
CSS: "",
|
||||||
Admin: true,
|
Admin: true,
|
||||||
Locale: "en",
|
Locale: "",
|
||||||
Scope: ".",
|
Scope: ".",
|
||||||
FileSystem: fileutils.Dir("."),
|
FileSystem: fileutils.Dir("."),
|
||||||
ViewMode: "mosaic",
|
ViewMode: "mosaic",
|
||||||
|
|||||||
57
http/auth.go
57
http/auth.go
@@ -1,8 +1,11 @@
|
|||||||
package http
|
package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -11,6 +14,45 @@ import (
|
|||||||
fm "github.com/hacdias/filemanager"
|
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.
|
// authHandler proccesses the authentication for the user.
|
||||||
func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
func authHandler(c *fm.Context, w http.ResponseWriter, r *http.Request) (int, error) {
|
||||||
// NoAuth instances shouldn't call this method.
|
// 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.
|
// Receive the credentials from the request and unmarshal them.
|
||||||
var cred fm.User
|
var cred cred
|
||||||
if r.Body == nil {
|
if r.Body == nil {
|
||||||
return http.StatusForbidden, 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
|
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.
|
// Checks if the user exists.
|
||||||
u, err := c.Store.Users.GetByUsername(cred.Username, c.NewFS)
|
u, err := c.Store.Users.GetByUsername(cred.Username, c.NewFS)
|
||||||
if err != nil {
|
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 {
|
if code >= 400 {
|
||||||
w.WriteHeader(code)
|
w.WriteHeader(code)
|
||||||
|
|
||||||
if err == nil {
|
txt := http.StatusText(code)
|
||||||
txt := http.StatusText(code)
|
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
|
||||||
log.Printf("%v: %v %v\n", r.URL.Path, code, txt)
|
w.Write([]byte(txt + "\n"))
|
||||||
w.Write([]byte(txt))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Print(err)
|
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")
|
w.Header().Set("Content-Type", contentType+"; charset=utf-8")
|
||||||
|
|
||||||
data := map[string]interface{}{
|
data := map[string]interface{}{
|
||||||
"BaseURL": c.RootURL(),
|
"BaseURL": c.RootURL(),
|
||||||
"NoAuth": c.NoAuth,
|
"NoAuth": c.NoAuth,
|
||||||
"Version": fm.Version,
|
"Version": fm.Version,
|
||||||
"CSS": template.CSS(c.CSS),
|
"CSS": template.CSS(c.CSS),
|
||||||
|
"ReCaptcha": c.ReCaptchaKey != "" && c.ReCaptchaSecret != "",
|
||||||
|
"ReCaptchaKey": c.ReCaptchaKey,
|
||||||
|
"ReCaptchaSecret": c.ReCaptchaSecret,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.StaticGen != nil {
|
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)
|
info, err := os.Stat(s.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
c.Store.Share.Delete(s.Hash)
|
||||||
return ErrorToHTTP(err, false), err
|
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.Password = pw
|
||||||
|
u.ViewMode = fm.MosaicViewMode
|
||||||
|
|
||||||
// Saves the user to the database.
|
// Saves the user to the database.
|
||||||
err = c.Store.Users.Save(u)
|
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
|
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.
|
// Updates the CSS and locale.
|
||||||
if which == "partial" {
|
if which == "partial" {
|
||||||
c.User.CSS = u.CSS
|
c.User.CSS = u.CSS
|
||||||
|
|||||||
18
package-lock.json
generated
18
package-lock.json
generated
@@ -8055,15 +8055,6 @@
|
|||||||
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
|
"integrity": "sha1-J5siXfHVgrH1TmWt3UNS4Y+qBxM=",
|
||||||
"dev": true
|
"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": {
|
"string-length": {
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/string-length/-/string-length-1.0.1.tgz",
|
"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": {
|
"strip-ansi": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
36b9bba06c64cd83f3994bbb77fc36948d9d2dfe
|
882c59547968e4fb9a65aa8d1c838409f198e51b
|
||||||
Reference in New Issue
Block a user