Conditional read access to DynamoDB table with AWS Amplify - database

I'm building an application with AWS Amplify, where I have three DynamoDB tables: Users, Posts and Subscriptions.
users can make posts
users subscribe to other users
user A can only see posts by user B if user A is subscribed to user B
Points 1. and 2. are easy to implement with standard graphQL mutations. But I'm stuck at how to implement 3. in an elegant way. Currently what I do is to use a lambda resolver.
Given inputs "user A wants to see user B", the lambda resolver does the following:
Query Subscriptions table to see if there's a document for "user A subscribed to user B"
if such a row exists, query Posts table and return documents. If not, return nothing.
This logic required two round trips, but since dynamo is fast I'm OK with this trade-off. There are other downsides though, so I'm wondering if there's a more Amplify-native way to do this? Some magic DynamoDB and #auth trickery perhaps?
Thank you!

If you are using multiple tables to store the data, the multiple query approach is your only option.
You can use transactions when mutating items across multiple tables, which is useful when you want to perform an operation based on a condition on an item in another table(s). But when it comes to a read operation, you have no such option.
Aside from re-designing your tables to support this access pattern, I don't think two reads is particularly bad.
If you wanted to handle authorization logic outside of DDB, you may want to look into AWS IAM and it's documentation on Fine-Grained Access Control. Among other features, IAM can restrict access to specific items in a table based on certain primary key values.

Related

FaunaDB, make connection between different users, I.e. become friends

I have a use case where I want to connect two different user roles, and if they accept and want to connect, new features will open up. It is very similar to how friend requests work at Facebook or LinkedIn, opening up and showing more content. Let's call them role1 and role2.
All users are stored within a "users" collection with an id. Depending on their provided role within the document attached to the "users" collection, they can store additional data in their respective role-collection, i.e., role1 collection or role2 collection.
What is the best approach and structure to connect the two users, i.e., become "friends"? Should I have the connection stored in a new collection, named perhaps connections-collection, or multiple collections?
I'm using Next.js, NextAuth for user authentication, and FaunaDB as a database. I'm using Fauna's query language, FQL.
Have you perhaps seen fireship's video RE: fauna db? I think it covers what you want to do and how you can proceed.
Edit: There are many ways to implement this. Based on my understanding, perhaps you can have "Friends" and "Requests" arrays stored under a user document. That way you can differentiate between confirmed friends or a just request.
Example: When user1 initiates a friend request with user2, you store user1's ref under "Requests" of user2's document. When user2 confirms, you move user1's ref to the "Friends" array.
This is just a overly simplified idea and you may need to consider your options and the implications. You would need to plan and define the predicate in both roles so you would only see what is necessary.

What is the right way to maintain user audit in a system designed to use external authentication?

I am using AWS cognito for user authentication in the application that I designed. And where ever there is a need for user audit in the application, I have used the id from cognito as if it is a foreign key from another table(I am using a relational DB).
Even though this works, this approach somehow feels improper. Is there any other proper way to design this?
In my application, the user logs in with his email address (common scenario). Hence, by construction the email address is a unique identifier both in cognito and in my database.
My database creates a user id for each new user, and that is the main identifier I use in my app (note that this identifier has nothing to do with cognito).
Cognito also assigns an id to each user (which it calls "username"), but I never reference that id (nor have I ever felt the need to reference it). I have been in production for several years, and I have never regretted this decision.
Upside of not linking user ids:
full flexibility (e.g. I can decide that I want to create a new user Object in my database for a particular cognito user. I can keep the previous user e.g. as a backup, even though it is not linked to the cognito user).
less work: i don't need to make sure the ids in my system are in line with those in cognito.
Downside of not linking user ids:
maybe it's faster to query cognito using the username field than the email field? maybe that could be an advantage for some use cases?

Automated DynamoDB Database Checks | ReactJS + AWS Amplify

My team and I are working on a Full-Stack Application using ReactJS on the frontend and AWS Amplify on the backend. We are using AWS AppSync to Query data in our DynamoDB tables (through GraphQL Queries), Cognito for User Authentication, and SES to send out emails to users. Basically, the user inputs some info (DynamoDB Table #1), and that is matched against an opportunity database (DynamoDB table #2), and the top 3 opportunities are shown to the user. If none are found, an email is sent to inform the user that they will receive an email when opportunities are found. Now for the Question: I wanted to know if there is a way to automatically Query a DynamoDB table (Like once a day or every time a new opportunity is added to the DynamoDB Table #2) and send out emails with matching opportunities to users who were waiting for them? I tried using Lambda Triggers but the only way I could do it was by querying each row of DynamoDB Table #1 against DynamoDB Table #2. That is computationally infeasible as there will be too many resources being used up. I am asking for advice on how I can go about making that daily check because I haven't been able to figure it out yet! Any responses are appreciated, and let me know if you need any additional information from my side! Thank you!
You could look into using DynamoDB Streams. When a new Opportunity is added to DynamoDB, the stream would trigger a lambda to be called. Your lambda could then execute your business logic to match the opportunity with the appropriate user.

SQL - Methods of storing intermediate sign-up information for users

I am designing a Node + MySQL backend for a service that requires two-factor authentication to be set up for all users as a part of the sign-up process. Users first submit a primary phone number and password (with the phone number being used for sign-in), upon which the server sends them an SMS with a six-digit code. Only after they enter the code is their account fully created.
This poses the problem of how to store data for users in the intermediate state after entering username/password but before verifying the code. Such "pending users" will not be shown in searches, do not need to store all the data a regular user would, and will be deleted after a few days if never verified. Furthermore, since every user must enable TFA to fully sign up, there will be orders of magnitude more users than "pending users". Thus, it does not seem sensible to me to have all users simply store an "isVerified" flag. I am familiar with SQL on a technical level but have no experience designing databases for production, and I am wondering what alternate mechanisms I should consider to solve the aforementioned.
My current idea is to have a separate table of pending users, having only those columns necessary to store intermediate signup information. When a user verifies, a row is created in the real user table, the signup information is copied over, and the corresponding is row deleted from the pending users table. However, this makes it somewhat ugly to ensure that the primary phone numbers are unique across both tables (which is an additional requirement of the service).
What methods/techniques should I consider to improve my solution?
You can store this data same way as store session information on server.

GAE datastore -- proper ways to implement search/data retrieval in response to a user request?

I am writing a web app and I am trying to improve the performance of search/displaying results. I am relatively new to programming this sort of thing, so I apologize in advance if these are simple questions/concepts.
Right now I have a database of ~20,000 sites, each with properties, and I have a search form that (for now) just asks the database to pull all sites within a set distance (for this example, say 50km). I have put the data into an index and use the Search API to find sites.
I am noticing that the database search takes ~2-3 seconds to:
1) Search the index
2) Get a list of key names (this is stored in the search index)
3) Using key names, pull from datastore (in a loop) and extract data properties to be displayed to the user
4) Transmit data to the user via jinja template variables
This is also only getting 20 results (the default maximum for a Search API query.. I haven't implemented cursors here yet, although I will have to).
For whatever reason, it feels quite slow.. I am wondering what websites do to make the process seem faster. Do they implement some kind of "asynchronous" search, where a page loads while in the background the search/data pulls are processed, and then subsequently shown to the user...?
Are there "standard" ways of performing searches here where the processing/loading feels seamless to the user?
Thanks.
edit
Would doing something like just passing a "query ID" via the page work, and then using AJAX to get data from the datastore via JSON work? Like... can app engine redirect the user to the final page, pass in only a "query ID", and then search in the meantime, and then once the data is ready, pass the information the user via JSON?
Make sure you are getting entities from the datastore in parallel. Since you already have the key names, you just have to pass your list of keys to the appropriate method.
For db:
MyModel.get_by_key_name(key_names)
For ndb:
ndb.get_multi([ndb.Key.from_path('MyModel', key_name) for key_name in key_names])
If you needed to do datastore queries, you could enable parallel fetches with the query.run (db) and query.fetch_async (ndb) methods.

Resources