Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
PUBLIC
/
surfer-okd
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit 313dfe99
authored
Mar 03, 2020
by
Johannes Zellner
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Use custom public folder listing
1 parent
33ee47f3
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
263 additions
and
65 deletions
frontend/404.html
frontend/js/public.js
frontend/public.html
package-lock.json
package.json
server.js
src/auth.js
frontend/404.html
View file @
313dfe9
...
...
@@ -9,7 +9,7 @@
<body>
<div
class=
"container-center"
>
<p>
File not found
</p>
<p>
File
or directory
not found
</p>
</div>
</body>
...
...
frontend/js/public.js
0 → 100644
View file @
313dfe9
(
function
()
{
'use strict'
;
/* global superagent */
/* global Vue */
/* global $ */
/* global filesize */
function
sanitize
(
filePath
)
{
filePath
=
'/'
+
filePath
;
return
filePath
.
replace
(
/
\/
+/g
,
'/'
);
}
function
encode
(
filePath
)
{
return
filePath
.
split
(
'/'
).
map
(
encodeURIComponent
).
join
(
'/'
);
}
function
decode
(
filePath
)
{
return
filePath
.
split
(
'/'
).
map
(
decodeURIComponent
).
join
(
'/'
);
}
var
mimeTypes
=
{
images
:
[
'.png'
,
'.jpg'
,
'.jpeg'
,
'.tiff'
,
'.gif'
],
text
:
[
'.txt'
,
'.md'
],
pdf
:
[
'.pdf'
],
html
:
[
'.html'
,
'.htm'
,
'.php'
],
video
:
[
'.mp4'
,
'.mpg'
,
'.mpeg'
,
'.ogg'
,
'.mkv'
,
'.avi'
,
'.mov'
]
};
function
getPreviewUrl
(
entry
,
basePath
)
{
var
path
=
'/_admin/img/'
;
if
(
entry
.
isDirectory
)
return
path
+
'directory.png'
;
if
(
mimeTypes
.
images
.
some
(
function
(
e
)
{
return
entry
.
filePath
.
endsWith
(
e
);
}))
return
sanitize
(
basePath
+
'/'
+
entry
.
filePath
);
if
(
mimeTypes
.
text
.
some
(
function
(
e
)
{
return
entry
.
filePath
.
endsWith
(
e
);
}))
return
path
+
'text.png'
;
if
(
mimeTypes
.
pdf
.
some
(
function
(
e
)
{
return
entry
.
filePath
.
endsWith
(
e
);
}))
return
path
+
'pdf.png'
;
if
(
mimeTypes
.
html
.
some
(
function
(
e
)
{
return
entry
.
filePath
.
endsWith
(
e
);
}))
return
path
+
'html.png'
;
if
(
mimeTypes
.
video
.
some
(
function
(
e
)
{
return
entry
.
filePath
.
endsWith
(
e
);
}))
return
path
+
'video.png'
;
return
path
+
'unknown.png'
;
}
// simple extension detection, does not work with double extension like .tar.gz
function
getExtension
(
entry
)
{
if
(
entry
.
isFile
)
return
entry
.
filePath
.
slice
(
entry
.
filePath
.
lastIndexOf
(
'.'
)
+
1
);
return
''
;
}
function
loadDirectory
()
{
app
.
busy
=
true
;
var
filePath
=
sanitize
(
window
.
location
.
pathname
);
app
.
path
=
filePath
;
superagent
.
get
(
'/api/files/'
+
encode
(
filePath
)).
query
({
access_token
:
localStorage
.
accessToken
}).
end
(
function
(
error
,
result
)
{
app
.
busy
=
false
;
if
(
result
&&
result
.
statusCode
===
401
)
return
logout
();
if
(
error
)
return
console
.
error
(
error
);
result
.
body
.
entries
.
sort
(
function
(
a
,
b
)
{
return
a
.
isDirectory
&&
b
.
isFile
?
-
1
:
1
;
});
app
.
entries
=
result
.
body
.
entries
.
map
(
function
(
entry
)
{
entry
.
previewUrl
=
getPreviewUrl
(
entry
,
filePath
);
entry
.
extension
=
getExtension
(
entry
);
entry
.
rename
=
false
;
entry
.
filePathNew
=
entry
.
filePath
;
return
entry
;
});
app
.
path
=
filePath
;
app
.
pathParts
=
decode
(
filePath
).
split
(
'/'
).
filter
(
function
(
e
)
{
return
!!
e
;
}).
map
(
function
(
e
,
i
,
a
)
{
return
{
name
:
e
,
link
:
'#'
+
sanitize
(
'/'
+
a
.
slice
(
0
,
i
).
join
(
'/'
)
+
'/'
+
e
)
};
});
});
}
function
open
(
row
,
column
,
event
)
{
var
fullPath
=
encode
(
sanitize
(
app
.
path
+
'/'
+
row
.
filePath
));
if
(
row
.
isDirectory
)
return
window
.
location
.
href
=
fullPath
;
app
.
activeEntry
=
row
;
app
.
activeEntry
.
fullPath
=
fullPath
;
app
.
previewDrawerVisible
=
true
// need to wait for DOM element to exist
setTimeout
(
function
()
{
$
(
'iframe'
).
on
(
'load'
,
function
(
e
)
{
if
(
!
e
.
target
.
contentWindow
.
document
.
body
)
return
;
e
.
target
.
contentWindow
.
document
.
body
.
style
.
display
=
'flex'
e
.
target
.
contentWindow
.
document
.
body
.
style
.
justifyContent
=
'center'
});
},
0
);
}
var
app
=
new
Vue
({
el
:
'#app'
,
data
:
{
ready
:
false
,
busy
:
false
,
path
:
''
,
previewDrawerVisible
:
false
,
activeEntry
:
{},
entries
:
[]
},
methods
:
{
onDownload
:
function
(
entry
)
{
if
(
entry
.
isDirectory
)
return
;
window
.
location
.
href
=
encode
(
'/api/files/'
+
sanitize
(
this
.
path
+
'/'
+
entry
.
filePath
))
+
'?access_token='
+
localStorage
.
accessToken
;
},
prettyDate
:
function
(
row
,
column
,
cellValue
,
index
)
{
var
date
=
new
Date
(
cellValue
),
diff
=
(((
new
Date
()).
getTime
()
-
date
.
getTime
())
/
1000
),
day_diff
=
Math
.
floor
(
diff
/
86400
);
if
(
isNaN
(
day_diff
)
||
day_diff
<
0
)
return
;
return
day_diff
===
0
&&
(
diff
<
60
&&
'just now'
||
diff
<
120
&&
'1 minute ago'
||
diff
<
3600
&&
Math
.
floor
(
diff
/
60
)
+
' minutes ago'
||
diff
<
7200
&&
'1 hour ago'
||
diff
<
86400
&&
Math
.
floor
(
diff
/
3600
)
+
' hours ago'
)
||
day_diff
===
1
&&
'Yesterday'
||
day_diff
<
7
&&
day_diff
+
' days ago'
||
day_diff
<
31
&&
Math
.
ceil
(
day_diff
/
7
)
+
' weeks ago'
||
day_diff
<
365
&&
Math
.
round
(
day_diff
/
30
)
+
' months ago'
||
Math
.
round
(
day_diff
/
365
)
+
' years ago'
;
},
prettyFileSize
:
function
(
row
,
column
,
cellValue
,
index
)
{
return
filesize
(
cellValue
);
},
loadDirectory
:
loadDirectory
,
open
:
open
,
}
});
loadDirectory
();
})();
\ No newline at end of file
frontend/public.html
0 → 100644
View file @
313dfe9
<html>
<head>
<title>
Surfer
</title>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"
>
<link
rel=
"icon"
type=
"image/png"
href=
"/_admin/img/logo.png"
>
<link
rel=
"stylesheet"
href=
"/_admin/css/theme-chalk_2.11.1.css"
>
<link
rel=
"stylesheet"
href=
"/_admin/css/style.css"
>
<script
src=
"/_admin/js/jquery-1.12.1.min.js"
></script>
<script
src=
"/_admin/js/vue.min.js"
></script>
<script
src=
"/_admin/js/element-ui_2.11.1.min.js"
></script>
<script
src=
"/_admin/js/element-ui_en_2.11.1.min.js"
></script>
<script
src=
"/_admin/js/filesize.min.js"
></script>
<script
src=
"/_admin/js/superagent.js"
></script>
</head>
<body>
<div
id=
"app"
>
<el-container>
<el-header>
<el-row
type=
"flex"
justify=
"space-between"
>
<div
style=
"flex-grow: 2; padding: 0 7px;"
>
<p
style=
"font-size: 24px; margin: 4px 0;"
>
{{ path }}
</p>
</div>
<div>
<a
href=
"/_admin"
>
<el-button
type=
"primary"
icon=
"el-icon-user"
size=
"small"
>
Login
</el-button>
</a>
</div>
</el-row>
</el-header>
<el-main>
<div
v-show=
"busy"
>
<center><h1><i
class=
"el-icon-loading"
></i></h1></center>
</div>
<div
v-show=
"!busy && entries.length"
v-cloak
>
<center>
<el-table
:data=
"entries"
style=
"max-width: 1280px; width: 100%"
height=
"100%"
empty-text=
"Folder is emtpy"
:default-sort=
"{ prop: 'filePath', order: 'descending' }"
@
row-click=
"open"
>
<el-table-column
prop=
"previewUrl"
label=
"Type"
width=
"80px"
sortable
>
<template
slot-scope=
"scope"
>
<el-image
v-bind:src=
"scope.row.previewUrl"
class=
"list-icon"
style=
"width: 32px; height: 32px"
fit=
"cover"
></el-image>
</template>
</el-table-column>
<el-table-column
prop=
"filePath"
label=
"Name"
sortable
></el-table-column>
<el-table-column
prop=
"size"
label=
"Size"
width=
"150px"
sortable
:formatter=
"prettyFileSize"
></el-table-column>
<el-table-column
prop=
"mtime"
label=
"Modified"
width=
"150px"
sortable
:formatter=
"prettyDate"
></el-table-column>
<el-table-column
label=
"Actions"
align=
"right"
width=
"200px"
class-name=
"list-actions"
>
<template
slot-scope=
"scope"
>
<el-button
size=
"small"
icon=
"el-icon-download"
type=
"text"
plain
circle
v-show=
"scope.row.isFile"
@
click
.
stop=
"onDownload(scope.row)"
></el-button>
</template>
</el-table-column>
</el-table>
</center>
</div>
<div
v-show=
"!busy && !entries.length"
>
<center>
Folder is empty
</center>
</div>
<el-drawer
:title=
"activeEntry.filePath"
:with-header=
"false"
:visible
.
sync=
"previewDrawerVisible"
direction=
"rtl"
size=
"50%"
>
<div
style=
"display: flex; flex-direction: column; height: 100%;"
>
<iframe
:src=
"activeEntry.fullPath"
style=
"width: 100%; height: 100%; border: none; margin: 10px;"
></iframe>
<center>
<el-button
size=
"small"
icon=
"el-icon-download"
style=
"margin: 10px;"
@
click
.
stop=
"onDownload(activeEntry)"
>
Download
</el-button>
<a
:href=
"activeEntry.fullPath"
target=
"_blank"
>
<el-button
size=
"small"
icon=
"el-icon-link"
style=
"margin: 10px;"
>
Open
</el-button>
</a>
</center>
</div>
</el-drawer>
</el-main>
</el-container>
</div>
<script
src=
"/_admin/js/public.js"
></script>
</body>
</html>
package-lock.json
View file @
313dfe9
...
...
@@ -160,11 +160,6 @@
}
}
},
"batch"
:
{
"version"
:
"0.6.1"
,
"resolved"
:
"https://registry.npmjs.org/batch/-/batch-0.6.1.tgz"
,
"integrity"
:
"sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY="
},
"bcrypt-pbkdf"
:
{
"version"
:
"1.0.1"
,
"resolved"
:
"https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz"
,
...
...
@@ -1997,51 +1992,6 @@
}
}
},
"serve-index"
:
{
"version"
:
"1.9.1"
,
"resolved"
:
"https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz"
,
"integrity"
:
"sha1-03aNabHn2C5c4FD/9bRTvqEqkjk="
,
"requires"
:
{
"accepts"
:
"~1.3.4"
,
"batch"
:
"0.6.1"
,
"debug"
:
"2.6.9"
,
"escape-html"
:
"~1.0.3"
,
"http-errors"
:
"~1.6.2"
,
"mime-types"
:
"~2.1.17"
,
"parseurl"
:
"~1.3.2"
},
"dependencies"
:
{
"depd"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/depd/-/depd-1.1.1.tgz"
,
"integrity"
:
"sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k="
},
"http-errors"
:
{
"version"
:
"1.6.2"
,
"resolved"
:
"https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz"
,
"integrity"
:
"sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY="
,
"requires"
:
{
"depd"
:
"1.1.1"
,
"inherits"
:
"2.0.3"
,
"setprototypeof"
:
"1.0.3"
,
"statuses"
:
">= 1.3.1 < 2"
}
},
"mime-db"
:
{
"version"
:
"1.33.0"
,
"resolved"
:
"https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz"
,
"integrity"
:
"sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ=="
},
"mime-types"
:
{
"version"
:
"2.1.18"
,
"resolved"
:
"https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz"
,
"integrity"
:
"sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ=="
,
"requires"
:
{
"mime-db"
:
"~1.33.0"
}
}
}
},
"serve-static"
:
{
"version"
:
"1.13.1"
,
"resolved"
:
"https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz"
,
...
...
package.json
View file @
313dfe9
...
...
@@ -40,7 +40,6 @@
"readline-sync"
:
"^1.4.9"
,
"request"
:
"^2.83.0"
,
"safetydance"
:
"^0.1.1"
,
"serve-index"
:
"^1.9.1"
,
"superagent"
:
"^5.1.3"
,
"underscore"
:
"^1.8.3"
,
"uuid"
:
"^3.2.1"
,
...
...
server.js
View file @
313dfe9
...
...
@@ -16,7 +16,6 @@ var express = require('express'),
multipart
=
require
(
'./src/multipart'
),
mkdirp
=
require
(
'mkdirp'
),
auth
=
require
(
'./src/auth.js'
),
serveIndex
=
require
(
'serve-index'
),
webdav
=
require
(
'webdav-server'
).
v2
,
files
=
require
(
'./src/files.js'
)(
path
.
resolve
(
__dirname
,
process
.
argv
[
2
]
||
'files'
));
...
...
@@ -49,7 +48,7 @@ function setSettings(req, res, next) {
// Load the config file
try
{
console
.
log
(
`Using config file:
${
CONFIG_FILE
}
`
);
console
.
log
(
`Using config file
at
:
${
CONFIG_FILE
}
`
);
config
=
require
(
CONFIG_FILE
);
}
catch
(
e
)
{
if
(
e
.
code
===
'MODULE_NOT_FOUND'
)
console
.
log
(
`Config file
${
CONFIG_FILE
}
not found`
);
...
...
@@ -68,7 +67,7 @@ var webdavServer = new webdav.WebDAVServer({
});
webdavServer
.
setFileSystem
(
'/'
,
new
webdav
.
PhysicalFileSystem
(
ROOT_FOLDER
),
function
(
success
)
{
console
.
log
(
`Mounting
${
ROOT_FOLDER
}
as webdav resource`
,
success
);
if
(
success
)
console
.
log
(
`Mounting webdav resource from:
${
ROOT_FOLDER
}
`
);
});
var
multipart
=
multipart
({
maxFieldsSize
:
2
*
1024
,
limit
:
'512mb'
,
timeout
:
3
*
60
*
1000
});
...
...
@@ -81,7 +80,7 @@ router.get ('/api/tokens', auth.verify, auth.getTokens);
router
.
post
(
'/api/tokens'
,
auth
.
verify
,
auth
.
createToken
);
router
.
delete
(
'/api/tokens/:token'
,
auth
.
verify
,
auth
.
delToken
);
router
.
get
(
'/api/profile'
,
auth
.
verify
,
auth
.
getProfile
);
router
.
get
(
'/api/files/*'
,
auth
.
verify
,
files
.
get
);
router
.
get
(
'/api/files/*'
,
auth
.
verify
IfNeeded
,
files
.
get
);
router
.
post
(
'/api/files/*'
,
auth
.
verify
,
multipart
,
files
.
post
);
router
.
put
(
'/api/files/*'
,
auth
.
verify
,
files
.
put
);
router
.
delete
(
'/api/files/*'
,
auth
.
verify
,
files
.
del
);
...
...
@@ -101,17 +100,22 @@ app.use('/', function welcomePage(req, res, next) {
if
(
config
.
folderListingEnabled
||
req
.
path
!==
'/'
)
return
next
();
res
.
status
(
200
).
sendFile
(
path
.
join
(
__dirname
,
'/frontend/welcome.html'
));
});
app
.
use
(
'/'
,
function
(
req
,
res
,
next
)
{
if
(
config
.
folderListingEnabled
)
return
next
();
res
.
status
(
404
).
sendFile
(
__dirname
+
'/frontend/404.html'
);
app
.
use
(
'/'
,
function
(
req
,
res
)
{
if
(
!
config
.
folderListingEnabled
)
return
res
.
status
(
404
).
sendFile
(
__dirname
+
'/frontend/404.html'
);
if
(
!
fs
.
existsSync
(
path
.
join
(
ROOT_FOLDER
,
req
.
path
)))
return
res
.
status
(
404
).
sendFile
(
__dirname
+
'/frontend/404.html'
);
res
.
status
(
200
).
sendFile
(
__dirname
+
'/frontend/public.html'
);
});
app
.
use
(
'/'
,
serveIndex
(
ROOT_FOLDER
,
{
icons
:
true
}));
app
.
use
(
lastMile
());
var
server
=
app
.
listen
(
3000
,
function
()
{
var
host
=
server
.
address
().
address
;
var
port
=
server
.
address
().
port
;
console
.
log
(
'Surfer listening on http://%s:%s'
,
host
,
port
);
console
.
log
(
'Using base path'
,
ROOT_FOLDER
);
console
.
log
(
`Base path:
${
ROOT_FOLDER
}
`
);
console
.
log
();
console
.
log
(
`Listening on http://
${
host
}
:
${
port
}
`
);
auth
.
init
(
config
);
});
src/auth.js
View file @
313dfe9
...
...
@@ -19,11 +19,13 @@ const LOGIN_TOKEN_PREFIX = 'login-';
const
API_TOKEN_PREFIX
=
'api-'
;
if
(
AUTH_METHOD
===
'ldap'
)
{
console
.
log
(
'Us
e
ldap auth'
);
console
.
log
(
'Us
ing
ldap auth'
);
}
else
{
console
.
log
(
`Us
e local auth file
${
LOCAL_AUTH_FILE
}
`
);
console
.
log
(
`Us
ing local auth file at:
${
LOCAL_AUTH_FILE
}
`
);
}
var
gConfig
=
{};
var
tokenStore
=
{
data
:
{},
save
:
function
()
{
...
...
@@ -53,7 +55,7 @@ var tokenStore = {
// load token store data if any
try
{
console
.
log
(
`Using tokenstore file:
${
TOKENSTORE_FILE
}
`
);
console
.
log
(
`Using tokenstore file
at
:
${
TOKENSTORE_FILE
}
`
);
tokenStore
.
data
=
JSON
.
parse
(
fs
.
readFileSync
(
TOKENSTORE_FILE
,
'utf-8'
));
}
catch
(
e
)
{
// start with empty token store
...
...
@@ -103,6 +105,10 @@ function verifyUser(username, password, callback) {
}
}
exports
.
init
=
function
(
config
)
{
gConfig
=
config
;
};
exports
.
login
=
function
(
req
,
res
,
next
)
{
verifyUser
(
req
.
body
.
username
,
req
.
body
.
password
,
function
(
error
,
user
)
{
if
(
error
)
return
next
(
new
HttpError
(
401
,
'Invalid credentials'
));
...
...
@@ -130,6 +136,11 @@ exports.verify = function (req, res, next) {
};
exports
.
verifyIfNeeded
=
function
(
req
,
res
,
next
)
{
if
(
!
gConfig
.
folderListingEnabled
)
return
exports
.
verify
(
req
,
res
,
next
);
next
();
};
exports
.
logout
=
function
(
req
,
res
,
next
)
{
var
accessToken
=
req
.
query
.
access_token
||
req
.
body
.
accessToken
;
...
...
Write
Preview
Markdown
is supported
Attach a file
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to post a comment