Secure Data Sources
How to control who can read, add, change, and delete records in a data source by setting up and testing security rules in Fliplet Studio.
A data source is a table of data stored in Fliplet. You can add security rules to a data source to control who can reach its records and what they can do with them.
This page applies to Fliplet Studio and is for administrators and developers. The Fliplet API enforces the rules for app users and API tokens.
Before you start
You need update permission on the data source. Without it, the Fliplet API answers Your role does not allow updating this data source.
> [!CAUTION] > Security: A data source's state affects everyone who uses it. A data source created in Studio starts with no rules, so no project can reach its data until you add one. If you delete or turn off every rule, every request from a project is refused. A data source whose stored rules value is null gives all access, and Studio displays that state as one All users rule with all four operations.
Rules aren't applied to these callers:
- Studio users outside preview
- Studio users in preview with Enforce security off
- App Tasks
The Entries tab therefore doesn't show what app users can reach.
Bookmarks, likes, and comments data sources have their security managed by the system, so Add new rule and the rules table are hidden for them.
Open the security rules
- Select Data Sources in the project sidebar.
- Select the data source.
- Select the Security rules tab.
The table shows each rule's Status, Allow, Users can, Request data requirements, Columns, Applies to, and Actions.
Add a rule
- Select Add new rule.
- Select Create my own rule.
- Under Allow, select who the rule covers.
- Under Users can, select each operation the rule grants.
- Under Applies to, select All projects or Specific projects.
Add rule stays disabled until you select at least one operation.
> [!CAUTION] > Security: Include doesn't protect a column on every kind of write, and neither list protects a column on every kind of write. Read Control columns on reads and writes before you rely on a list.
To limit columns:
- Under Include or exclude columns, select Exclude or Include.
- Type a column name in the field.
- Press return to add it, and repeat for each column.
To add a requirement, follow the steps in Add data requirements.
To finish:
- Select Add rule.
- Select Save & Apply. [UNVERIFIED: Studio UI label read from production code, because it appears only after a rule change]
A new, edited, reordered, turned-off, or deleted rule isn't stored until you select Save & Apply.
Choose who the rule covers
| Allow option | Who it covers |
|---|---|
| All users | Everyone, including people who aren't signed in. |
| Logged in users | Anyone whose session holds a login, whichever login data source they used. |
| Specific users | Signed-in users whose own record matches every condition. |
| Specific token | One API token, chosen under Select your API Token. |
Someone who isn't signed in never matches Specific users. To add a condition, select Add condition, then set a Field name, an operator (Equals, Not equals, or Contains), and a Value. [UNVERIFIED: Studio UI labels read from production code, because they appear only after Specific users is selected]
To create a token, see Manage API tokens and permissions.
Choose what the rule allows
| Users can option | What it grants |
|---|---|
| Read | Reading records. |
| Write | Adding new records. |
| Update | Changing existing records. |
| Delete | Deleting records. |
Write doesn't include changing an existing record.
Specific projects limits a rule to the projects you select, and the rule also matches a project's master or production copy. If you select Specific projects and tick no project, the rule is stored with no project limit, so it applies to all projects.
Control columns on reads and writes
A rule holds one list: the columns you enter are stored as either Exclude or Include.
On reads with connection.find() and connection.findById(), Exclude removes the listed columns from each record and Include returns only the listed columns. This holds for every request the rules apply to.
On writes, what happens depends on how your code saves the record:
| Write | Exclude | Include |
|---|---|---|
connection.insert() | Listed columns are removed. | Ignored, so a column left out of the list is saved. |
connection.update() | Listed columns are removed. | Ignored, so a column left out of the list is saved. |
connection.query() with type update | Listed columns are removed. | Ignored, so a column left out of the list is saved. |
connection.commit() | Listed columns are removed from the submitted values. | Only submitted columns in the list are kept, on new and updated records. |
connection.append() | Not applied. | Not applied. |
connection.replaceWith() | Not applied. | Not applied. |
connection.append() and connection.replaceWith() check the Write rule, then save every column sent. connection.replaceWith() also needs Delete, because it replaces every record.
When a column is removed on a write, the request still succeeds and no error names the column. On an update, the stored value of that column stays as it was.
> [!CAUTION] > Security: Studio's helper text for Include says "Only the columns specified here are readable and writable by users when this rule is matched". That holds on writes for connection.commit() only. Include can't protect a column on connection.insert() or connection.update(), and neither list protects a column on connection.append() or connection.replaceWith(). A user who has a Write rule can set any column through connection.append().
Use Exclude to protect a column on connection.insert(), connection.update(), and connection.commit().
Add data requirements
- Select Add data requirement.
- Enter the column name in Field name.
- Select an operator.
- Enter the Value, unless the operator is Is required. [UNVERIFIED: Studio UI label Is required read from production code, because it appears only after a requirement row is added]
Every requirement in a rule must pass, or the rule doesn't grant the request. A Value can use the signed-in user's data, for example {{user.[Email]}}. A requirement that uses the signed-in user's data fails when that value is empty.
| Operator | When it passes |
|---|---|
| Is required | The field is present in what the request supplies. |
| Equals | The whole value matches, ignoring case. |
| Not equals | The value doesn't match. |
| Contains | Compared with a plain value, the Value appears anywhere in the field's value, ignoring case. |
Contains works the same for a Specific users condition.
What each requirement checks depends on the operation:
| Operation | Is required checks | Equals, Not equals, and Contains check |
|---|---|---|
connection.find() | The query. | The query. |
connection.findById() | The stored record. | The stored record. |
| Write | The submitted records. | The submitted record. |
| Update | The submitted records. | The stored record. |
| Delete of one record | The stored record. | The stored record. |
On connection.find(), Equals and Contains don't filter the results: a query that doesn't ask for the required value is refused. A query that doesn't name a Not equals field passes, and records holding the blocked value are removed from the results.
On Update, a requirement that uses the signed-in user's data also checks the record as it will be after the update.
Example: limit users to their own records
For a data source with an Email column, add one rule with these settings:
- Allow: Logged in users
- Users can: Read, Write, Update, and Delete
- Requirement:
EmailEquals{{user.[Email]}}
Reads must then send that value, and new records must include it:
connection.find({ where: { Email: userEmail } });
Use a pre-configured rule
Selecting a pre-configured rule under Add new rule adds it to the list straight away and opens it for editing. You still need to select Save & Apply.
| Pre-configured rule | What it adds |
|---|---|
| All users can read | Read for All users. |
| Logged in users can write | Write for Logged in users, and no other operation. |
| Admins user types have full access and offline access | All four operations for users whose Admin field equals Yes. |
| Users can only retrieve some fields | Read for All users, excluding Password. |
| Users can only insert records if they provide their account ID | Read for All users, requiring Account ID. |
Despite its name, Users can only insert records if they provide their account ID doesn't grant Write.
Write a custom rule
A custom rule is checked for every operation. It has no Allow or Users can settings, so test type in the script.
- Select Add new rule.
- Select Create a custom rule using JavaScript.
- Enter a Security rule name.
- Enter the JavaScript under Custom definition.
- Select Add rule.
- Select Save & Apply.
The script can read type, user, session, query, entry, and DataSources. user is undefined when nobody is signed in.
The script grants access by returning an object with granted: true, and it can add exclude or include arrays. Anything else, including no return value, doesn't grant. The script has 3 seconds to run, and DataSources(id).find() inside a script returns at most 100 records.
if (type === 'select' && user && user.Role === 'Manager') {
return { granted: true, exclude: ['Salary'] };
}
return { granted: false };
Order, turn off, edit, and delete rules
Rules are checked from the top of the table. The first rule that grants a request decides it, and only that rule's columns apply. The exception is a Read rule with a Not equals requirement: checking continues, so a later rule that grants without a filter replaces it. A turned-off rule is ignored.
- To change a rule's position, drag it by the handle at the start of its row.
- To turn a rule on or off, select the toggle under Status.
- To remove a rule, select Delete.
To change a rule:
- Select Edit.
- Change the settings.
- Select Confirm.
After any of these changes, select Save & Apply.
Check the rules work
With Enforce security on, Studio users' requests in preview follow the rules. A login counts if its login data source belongs to the previewed project or is shared with it from the same organization.
- In the project editor, open Preview settings.
- Confirm Enforce security is on.
- Sign in to the project as a user the rule covers.
- Carry out the read or write in the preview.
- Select Clear next to Reset session to sign out.
- Sign in as a user the rule doesn't cover.
- Carry out the same read or write.
- Check that the request is refused, as long as no other rule covers that user.
A refused request shows one of these messages, where the last word before data changes to read, write, update, or delete. Requests made with an app token get the first message, and other requests get the second. [UNVERIFIED: messages read from API code, not seen live]
You are not allowed to read this data.
The security rules for the Data Source "Staff" do not allow this app to read data.
These steps depend on signing in, so they don't test a Specific token rule, which matches the API token making the request.
If a rule doesn't behave as expected
| What you see | Cause | Fix |
|---|---|---|
| Everything is refused. | No enabled rule covers the request. | Add a rule or turn one on, then select Save & Apply. |
| A change has no effect. | The change wasn't saved. | Select Save & Apply. |
| The rule works in one project only. | Applies to names other projects. | Edit Applies to. |
| A signed-in rule refuses the request. | Nobody is signed in. | Sign in, or use a rule that covers the user. |
| A read is refused though the user is covered. | A data requirement's value is missing from the query. | Send it in the where of connection.find(). |
| A user gets the wrong columns. | An earlier rule grants first. | Drag the narrower rule higher. |
| A column outside Include is saved. | The write uses connection.insert() or connection.update(). | Use Exclude for that column. |
| A listed column is saved anyway. | The write uses connection.append() or connection.replaceWith(). | Don't rely on a list to protect that column from users with Write. |
If a custom rule throws and no other rule grants, the request is refused with this message, followed by the error: [UNVERIFIED: message read from API code, not seen live]
A security rule has returned the following error:
See also
- How to use Data Sources: the parent guide to working with data sources.
- Manage API tokens and permissions: create the token a Specific token rule names.
- Test app security: test beyond one data source.
- Data Source access and permission errors: troubleshoot refused requests.
- Fliplet.DataSources: the methods named in the write table.