All files in Nhost Storage are protected with Security Rules.
With security rules, you can set granular access on who is allowed to do what based on the user and the file.
All storage rules can be by-passed by admins using the Admin Secret. This is done by sending the Admin Secret as a header like:
x-admin-secret: <admin-secret>
. The Admin Secret is the same Admin Secret used to login to the Hasura console.
Security rules are written in yaml format.
Here is an example security rules.
functions:
isAuthenticated: "return !!request.auth"
isOwner: "return !!request.auth && userId === request.auth['user-id']"
paths:
/public*:
read: "true"
write: "isAuthenticated()"
/users/:userId/:fileName:
read: "public()"
write: "isOwner(userId)"
In the above example we have the following rules:
/public*
folder. Because we use a *
all paths that starts with /public
will be covered by this rule. Ex: /public/image.png
and /public/other-path/cv.pdf
./public*
./users/:userId/:fileName
. Ex: /users/1/image.png
.1
can upload a file to /users/1/image.png
but not to /users/2/image.png
.Storage Rules follows this structure:
functions:
functionName: "one line javascript that returns true or false"
paths:
/path/to/file.png:
operation: "true/false or functionName()"
operation: "true/false or functionName()"
operation: "true/false or functionName()"
/path/to/folder:
operation: "true/false or functionName()"
operation: "true/false or functionName()"
operation: "true/false or functionName()"
/path/to/folder*:
operation: "true/false or functionName()"
operation: "true/false or functionName()"
operation: "true/false or functionName()"
Paths are specified under the paths:
directive and specifies that path to the file or folder that you want to set rules for.
You can use path variables in the path to capture values specified at their position in the URL. These path variables can be used in the security rules functions.
Below is an example where we capture :userId
as a variable in the URL and use that same userId
variable to resolve userId === request.auth['user-id']
in the isOwner
function.
functions:
isOwner: "return !!request.auth && userId === request.auth['user-id']"
paths:
/users/:userId/:fileName:
write: "isOwner(userId)"
You can not nest paths like this:
paths:
/path/to/:
read: "true/false or functionName()"
/then/:
read: "true/false or functionName()"
You can use a function to resolve if a request should be allowed or not. A function can access information from the user making the request, file metadata and path variables.
A function must return true
or false
.
Functions are placed after the functions:
directive and can be used after an operation like this:
functions:
public: "return true"
paths:
/open-access*:
read: "public()"
write: "public()"
Functions can take arguments from the file path. In the example below we'll allow anyone to read a file located in /users/:userId
but only users are allowed to write in their own folder with the name of their user id.
functions:
public: "return true"
isOwner: "return !!request.auth && userId === request.auth['user-id']"
paths:
/users/:userId/:fileName:
read: "public()"
write: "isOwner(userId)"
Who is allowed to do what given the rules above?
User id | Path | Operation | Allowed? |
---|---|---|---|
1 | /users/1/image.png | read (GET) | Yes |
1 | /users/2/image.png | read (GET) | Yes |
1 | /users/1/image.png | write (POST) | Yes |
1 | /users/2/image.png | write (POST)) | No |
Not logged in | /users/2/image.png | read (GET)) | Yes |
Not logged in | /users/1/image.png | write (POST)) | No |
Not logged in | /users/2/image.png | write (POST)) | No |
Path variables can be accessed in function if they are present in the path (/users/:myVariable
) and sent as a parameter to the function isOwner(myVariable)
.
You can access user information at request.auth['']
. All variables from the JWT-token claim can be accessed here without the x-hasura
. For example, thi is how you access the user's id: request.auth['user-id']
.
The following operations can be used:
create
Upload new fileupdate
Overwrite existing fileget
- Get filelist
- List files in directorydelete
- Delete fileYou can also use helper operations:
write
is the same as create
and update
and delete
.read
is the same as get
and list
.When a file gets uploaded, Nhost Storage automatically generates a token
(access token) for the file. The token
will be added to the file's metadata.
Let's say you upload a file to /data/document.txt
and get this as return data:
{
"key": "/data/document.txt",
"AcceptRanges": "bytes",
"LastModified": "2020-01-01T01:02:03.000Z",
"ContentLength": 12345,
"ETag": "Etag",
"ContentType": "text/plain",
"Metadata": {
"token": "6a587e56-04d4-415d-bb2c-9a39491e31d2"
}
}
And have the following storage rules:
functions:
validToken: 'return request.query.token === resource.Metadata.token'`
paths:
/data*:
read: 'validToken()'
Now, given our validToken
function, anyone can access /data/document.txt
with the token
like this: https://backend-xxx.nhost.app/storage/o/data/document.txt?token=6a587e56-04d4-415d-bb2c-9a39491e31d2
.
Access tokens can be revoked which means the old access token gets deleted and a new access token created. Only admins with the admin secret (same as Hasura's admin secret) can revoke tokens.
Request to revoke an access token:
POST /storage/m/<path-to-file>
Headers
x-admin-secret: <admin-secret>
Body
{
"action": "revoke-token"
}
{
"key": "<path-to-file>",
"AcceptRanges": "bytes",
"LastModified": "2020-01-01T01:02:03.000Z",
"ContentLength": 12345,
"ETag": "Etag",
"ContentType": "<content-type>",
"Metadata": {
"token": "<new-auto-generated-access-token-uuid>"
}
}