New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Allow to password protect shares #1252
Conversation
9d3a301
to
52a98c6
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This PR is not working correctly as the way it is, you need to do some testing. Also found some issues:
- Unprotected shares cannot be accessed.
- There's no feedback when user type password wrong.
@ramiresviana can you elaborate on that, what exactly is not working correctly? Or do you just mean the two issues you mentioned? |
The most important aspect of this feature was not working, the password validation. On my tests i was able to access any share by providing a wrong password. Also found another issue:
|
46fba45
to
a35b468
Compare
@ramiresviana addressed all feedback:
Please have another look. |
3d1b278
to
2d2d42e
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about using JWT based authentication? I think would be a more secure approach.
First, if you have conceptional remarks about a change, it would be awesome to give those in the first round of feedback, not in the second one. Second, can you elaborate on how exactly JWT would be more secure? The password is sent exactly once, that will be needed however auth works. The token can be any random value which must be hard to guess but making it a JWT or not will not make anything more or less secure. |
Just wanted to hear your opinion about JWT, that's not an requirement. I was worried about the fact that the share token is always the same, with the JWT standard the token needs to be refreshed periodically and the frontend |
Well, I guess that depends on the expectation. Right now the download links from the share will work indefinitely for as long as that share exists, which is nice for example if you want to be able to pass around a link that will work without making the share public. If I wanted to brute force a filebrowser share, I would most likely try to brute force the password instead, because I would assume that it is almost always shorter than the 96 byte of the token and that it will in most cases be easier to guess. |
@o1egl Can you have a look, please? |
@@ -2,7 +2,7 @@ version: 2 | |||
jobs: | |||
lint: | |||
docker: | |||
- image: golangci/golangci-lint:v1.27.0 | |||
- image: golangci/golangci-lint:v1.31.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The last version is v1.36.0. Either upgrade to the latest or revert it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might not look like that, but the version was a very conscious choice, it had to be updated to something with go 1.15 to have support for *testing.T.TempDir
which was added in go 1.15. This is the oldest version of the golangci-lint that uses go 1.15.
Updating it to v1.36.0 results in a bunch of new failures, because filebrowser doesn't have a golangci-lint config, so it uses whatever its default linters are, which is not stable across versions.
password: '', | ||
passwordPermalink: '' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it 2 different variables?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because its two different inputs
share/storage.go
Outdated
if len(links) > i+1 { | ||
links = append(links[:i], links[i+1:]...) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why this change is introduced?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because a bunch of methods in storage.go unconditionally access links[i+1:]
which results in an out of bounds panic if the last link expired. I will remove it from this PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This check doesn't save from data race
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, what data race do you mean? It stops accessing an array in an unsafe manner and risking an out of bounds
users/storage.go
Outdated
@@ -17,6 +17,15 @@ type StorageBackend interface { | |||
DeleteByUsername(string) error | |||
} | |||
|
|||
type Interface interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please give a proper name to this interface. UserStore
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The linter will refuse that due to stuttering, package symbols should not start with the package name. This ends up being used as users.Interface
which IMHO is totally reasonable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just gave an example. It can be named Store. An interface name should be meaningful and self explainable. Interface
name is too generic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed it to Store
, please take another look
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@o1egl all feedback addressed, ptal
@@ -2,7 +2,7 @@ version: 2 | |||
jobs: | |||
lint: | |||
docker: | |||
- image: golangci/golangci-lint:v1.27.0 | |||
- image: golangci/golangci-lint:v1.31.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might not look like that, but the version was a very conscious choice, it had to be updated to something with go 1.15 to have support for *testing.T.TempDir
which was added in go 1.15. This is the oldest version of the golangci-lint that uses go 1.15.
Updating it to v1.36.0 results in a bunch of new failures, because filebrowser doesn't have a golangci-lint config, so it uses whatever its default linters are, which is not stable across versions.
password: '', | ||
passwordPermalink: '' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because its two different inputs
http/public.go
Outdated
@@ -94,3 +104,26 @@ var publicDlHandler = withHashFile(func(w http.ResponseWriter, r *http.Request, | |||
|
|||
return rawDirHandler(w, r, d, file) | |||
}) | |||
|
|||
func authenticateShareRequest(r *http.Request, l *share.Link, salt string) (int, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An error here means something went wrong with our code and yes, in that case the status code can be ignored. Statuscode != 0 means the verification failed. The distinction is important, because they result in different http codes. Our code erroring is a 5XX, the verification failing is a 4XX, if we return an error in both cases, we can not distinguish those two anymore
share/storage.go
Outdated
if len(links) > i+1 { | ||
links = append(links[:i], links[i+1:]...) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because a bunch of methods in storage.go unconditionally access links[i+1:]
which results in an out of bounds panic if the last link expired. I will remove it from this PR.
users/storage.go
Outdated
@@ -17,6 +17,15 @@ type StorageBackend interface { | |||
DeleteByUsername(string) error | |||
} | |||
|
|||
type Interface interface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The linter will refuse that due to stuttering, package symbols should not start with the package name. This ends up being used as users.Interface
which IMHO is totally reasonable
ef55a1b
to
2409822
Compare
@o1egl any chance you can have another look? |
@alvaroaleman |
frontend/src/api/share.js
Outdated
url = removePrefix(url) | ||
url = `/api/share${url}` | ||
if (expires !== '') { | ||
url += `?expires=${expires}&unit=${unit}` | ||
} | ||
|
||
let body = ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The server expects to receive a JSON. An empty string is not a correct JSON.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@o1egl fixed
This changes allows to password protect shares. It works by: * Allowing to optionally pass a password when creating a share * If set, the password + salt that is configured via a new flag will be hashed via bcrypt and the hash stored together with the rest of the share * Additionally, a random 96 byte long token gets generated and stored as part of the share * When the backend retrieves an unauthenticated request for a share that has authentication configured, it will return a http 401 * The frontend detects this and will show a login prompt * The actual download links are protected via an url arg that contains the previously generated token. This allows us to avoid buffering the download in the browser and allows pasting the link without breaking it
This changes allows to password protect shares. It works by: * Allowing to optionally pass a password when creating a share * If set, the password + salt that is configured via a new flag will be hashed via bcrypt and the hash stored together with the rest of the share * Additionally, a random 96 byte long token gets generated and stored as part of the share * When the backend retrieves an unauthenticated request for a share that has authentication configured, it will return a http 401 * The frontend detects this and will show a login prompt * The actual download links are protected via an url arg that contains the previously generated token. This allows us to avoid buffering the download in the browser and allows pasting the link without breaking it
This changes allows to password protect shares. It works by:
hashed via bcrypt and the hash stored together with the rest of the
share
as part of the share
that has authentication configured, it will return a http 401
the previously generated token. This allows us to avoid buffering the
download in the browser and allows pasting the link without breaking
it
Thanks a lot for all the existing work on filebrowser btw, it's great!
Fixes #1223