Hasura SELECT permissions for table relationships - permissions

I'm building a forum. I have a really simple database setup:
Users: id, display_name, email, profile
Posts: id, title, content, user_id
The user_id is a foreign key to the Users table.
Permissions:
For inserting/updating, X-Hasura-User-Id must equal id and user_id for inserting into the Users and Posts table. (so they can only modify their own posts)
For selecting, I have it so a user can read any post, but they can only select the row of the User if id = X-Hasura-User-Id. This is so a User can only read their profile data.
However, for selecting, I obviously need the user to be able to access display_name of the user's table, to display the post's author.
Now I can obviously make it so for select, they only have access to this field, and everything works fine. I can return a GQL query that displays the posts and the author.
But doesn't this also mean that a user can just run a query to the Users table and get a list of all the display_names, essentially showing how many users I have?
Is there a way to set it up so that a user can only select their own info from the User's table, but like, if the query is 'coming from' the server, it can access the display_name? I know there are Admin roles etc but I don't think this applies here.

But doesn't this also mean that a user can just run a query to the Users table and get a list of all the display_names, essentially showing how many users I have?
Yes
Is there a way to set it up so that a user can only select their own info from the User's table, but like, if the query is 'coming from' the server, it can access the display_name?
No
It's a valid concern to worry about data leakage in terms of how many users you have. But in general I would not worry about it.
However, there are a few things you could do to prevent this problem.
What you can do is:
Limit the number of rows per request (https://hasura.io/docs/1.0/graphql/manual/deployment/production-checklist.html#limit-number-of-rows-returned)
Make sure users are not allowed to aggregation queries (https://hasura.io/docs/1.0/graphql/manual/queries/aggregation-queries.html#aggregate-fields)
Also what you can do is create a VIEW where the display_name is joined and added to the posts table.

Related

Best way to mimic inheritance in postgresql?

For an application I am writing, there are two types of "users", those who have made accounts and those who have not, virtual_users. These two types are nearly identical, except account_users have a password, and email is required and must be unique amongst all account_users, although it can be the same as any number for virtual_users. A large number of tables have a column that references users, which should include both, and 90% of app functionality treats them as interchangeable. What is the best way of handling this? Some options I have considered:
-Put both types of users in the same table and have a complicated constraints regarding uniqueness, basically, if password is not NULL, email must be unique among all users where password is not NULL. I have no idea how I would write this constraint. On the few occasions I only want account_users query for only users who have a password. This seems like the best solution if I can figure out how to write the constraint.
-Have Account_users inherit from Virtual_usersand Virtual_users has an additional column password and unique constraints on email. From here there are two potential options:
---Have a Users table which includes two columns account_user_id and virtual_user_id one of which is NULL and one of which corresponds to the appropriate user. When other tables need to reference a user, they reference this table. Have all my queries server side for users query both tables and combine.
---When other tables need to reference they reference either table. I don't think this is possible. Have all my queries server side for users query both tables and combine.
Any advice would be appreciated.
I assume the scenario is you have a system which some parts require the user to be signed into a registered account, and others do not, but you'd still like to track users.
Postgres has table inheritance. You could use that, but I'd be concerned about the caveats.
You could put them all into one table and use some sort of flag, like Single Table Inheritance, but then you run into constraint issues. You would then enforce constraints in the model. This should be fine if you have a strong model.
You could have separate accounts and users. Rather than one being a special case of the other, they key is thinking of them as two conceptually different things. In OO terms, an account has a user.
-- Visitors to the site who haven't signed up.
create table users (
id serial,
-- The unverified email they might have given you.
email text,
-- Any other common information like a tracking token
token text
);
-- Users who have registered.
create table accounts (
id serial,
user_id int references users(id),
-- Their verified email.
email text not null,
-- Hashed, of course.
password text not null
-- any additional information only for accounts
);
I like this because there are no flags involved, users and accounts can have separate constraints, and the accounts table doesn't get bloated with users that showed up once and never came back.
You'd access complete account information with a simple join.
select accounts.*, users.token
from accounts
join users on accounts.user_id = users.id
where accounts.id = ?
If you want to delete an account you can do so without losing the underlying user information.
delete from accounts where accounts.id = ?
Systems which require an account use accounts. Systems which don't use users. Systems which behave differently for users and accounts can check if a user has an account easily.
select accounts.id
from accounts
where accounts.user_id = ?
User tracking is associated with the users table, so you have a consistent record of a user's behavior before and after they register.

Update column value modified from another user

I have a column called Note, roleName and there are two roles, admin and engineer.
The engineer updates the notes and saves it , at the same time when admin logs in and modifies the same record (notes), he should be able to do it. How can I achieve this using case sql?
If two users update the same field in the same table, then the user which updates last, will win. If you are trying to do something like Google Docs, where many users can update the same data, that is going to be more than just a update to a table.

How to get random unread article?

Database table articles contains 10.000 rows (articles)
I want to get a random article, mark it as read and never get it again,
so my next get request should return random article except that one.
There are thousands of users like me and all of them are using this table.
How do I implement this? How do I mark those articles, and how do I search only unread articles?
I was trying to create the column relatedUsers in the Articles table, which is filled out with relations to user objects, who has read the article. I was using search query: relatedUsers.objectId!=currentUserId But when this column get 2+ relatedUsers, this solutions stops working, because the database returns articles, which has at least one relatedUser, who's objectId isn't equal to currentUserId (means all users).
I'm using Backendless.com right now, but if I guess, the solution should be applicable to any database, including backendless. If not, then what kind of database should I use for this?
What you can do now is:
Create a table named UserArticle with fields articleId and userId
When you add a user or an article, you should also add to this table relations to all articles or users respectively; this way your UserArticle table will contain the relations between articles and users who haven't yet read those articles
And in order to get a random not read article for user, you:
Retrieve items from UserArticle where userId = yourCurrentUserId
Randomly select one of them and retrieve an article by its ID
Remove the selected article from UserArticle by articleId and userId, meaning that the user has read the article
This approach is the most suitable in your case. It involves a little more than one request, but for now your requirements cannot be fit better.
Also we (the Backendless team) are working on a keyword like contains, using which you would be able to have only a relation to Users table and get not read articles in one request, so the suggested approach is pretty temporary.
Getting a random number depends on the database. But the basic idea is to have a table called UserArticles with one row per user and per article already read (and perhaps other information such as the time).
Then, you can do:
select a.*
from articles a
where not exists (select 1
from userarticles ua
where a.article_id = ua.article_id and ua.user_id = $user_id
)
order by rand()
limit 1;
The order by rand() limit 1 is definitely database-dependent. But, it gives you the idea of how to approach the problem.
Once you have selected the article, then you insert this information into UserArticles:
insert into UserArticles(user_id, article_id)
values ($user_id, $article_id);
where $article_id refers to the article retrieved in the previous step.
Note: this should be fine with respect to race conditions, unless a single user can have multiple simultaneous connections to the database asking for the same information. Handling that case requires more knowledge about the database being used.

Database design relations in User and Profile

I'm designing a web application for a school. So far, I'm stuck with the database which has these tables:
users
id
username
password
profile
user_id (FK)
name
last_name
sex
group_id (FK)
(other basic information)
... And other tables irrelevant now, like events, comitees, groups and so on.
So, the users table stores basic information about the login, and the profiles table stores all the personal data about the user.
Now, the *group_id* column in the profile table has a foreign key that references the ID column of the group in which the user is currently enrolled, in the groups table. A user can only be enrolled in one group at once, so there's no need for any additional tables.
The thing is that it doesn't make much sense to me declaring a relation like group HAS MANY profiles. Instead, the relation should be group HAS MANY users, but then, I would have to put a *group_id* column on the users table, which doesn't really fit in, since the users table only stores auth information.
On the other side, I would like to list all the users enrolled in a group using an ORM and getting the a users collection and not profiles. The way I see it, is that the users table is like the 'parent' and the profiles table extends the users table.
The same problem would occur when setting attendances for events. Should I reference the profile as a foreign key in the events_attendance table? Or should I reference the user ID?
Of course both solutions could be implemented and work, but which of them is the best choice?
I have dug a little and found that both solutions would comply with 3NF, so in theory, would be correct, but I'm having a hard time designing the right way my database.
This is a question of your own conventions. You need to decide what is the main entity, right after that you can easiy find a proper solution. Both ways are good, but if you think of User as of the main entity while Profile is a property then you should put GroupId into User, otherwise, if you mean User and Profile as a single entity, you can leave GroupId in Profile, and by this you're not saying group HAS MANY profiles but group HAS MANY users.
By setting a proper one-to-one relation (User-Profile) you can force your data integrity good enough.

How to change one-to-one relationship to one-to-many relationship in MySQL?

I currently have a user's table which contains a one-to-one relationship for Youtube OAuth tokens. However, I now want to support multiple video sites and want to break this into a one-to-many relationship.
I have setup the new tables:
tokens - cols: id, site, username (the user's username on Youtube), oauth_token, oauth_secret
user_tokens - cols: id, user_id, token_id
Is there a way I can SELECT from my current user's table INTO these tables to import the username, oauth_token and oauth_secret columns while also setting up the user_tokens table with the appropriate id's?
In the past I have written short PHP scripts to do this, but have always been curious about whether I can do it directly in MySQL.
You don't need a relation table for a one-to-many relationship, you just need a user_id field in the tokens table. That also makes it easier to poultate the table:
insert into tokens (site, user_id, username, oauth_token, oauth_secret)
select site, user_id, username, oauth_token, oauth_secret
from users
(As I don't know exactly what's in your user table and what the field names are, it might need some adjusting.)
Checkout MySQL documentation. I think that should help you.