<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Oscar&apos;s Dev Blog</title>
    <link>https://blog.oscars.dev</link>
    <description>Oscar&apos;s thoughts too large for a tweet</description>
    <language>en-gb</language>
    <managingEditor>Oscar Poke</managingEditor>
    <webMaster>Oscar Poke</webMaster>
    <lastBuildDate>Fri, 08 Sep 2023 00:00:00 GMT</lastBuildDate>
    <atom:link href="https://blog.oscars.dev/rss.xml" rel="self" type="application/rss+xml" xmlns:atom="http://www.w3.org/2005/Atom" />
    <item>
      <title>How to deploy a NextJS app on AWS Fargate with Terraform</title>
      <link>https://blog.oscars.dev/posts/deploy_nextjs_app_on_fargate_with_terraform</link>
      <guid isPermaLink="true">https://blog.oscars.dev/posts/deploy_nextjs_app_on_fargate_with_terraform</guid>
      <pubDate>Fri, 08 Sep 2023 00:00:00 GMT</pubDate>
      <description>Introduction In this guide, you will learn how to deploy a NextJS Node server on AWS ECS Fargate. This is an easy and scalable way to deploy a Dockerised NextJS project. It also makes it easy to add more environments and keep a clean separation between them while making changes to them all through a git repository (no…</description>
      <content:encoded><![CDATA[
## Introduction

In this guide, you will learn how to deploy a NextJS Node server on AWS ECS Fargate. This is an easy and scalable way to deploy a Dockerised NextJS project. It also makes it easy to add more environments and keep a clean separation between them while making changes to them all through a git repository (no dashboard fiddling!). This is my personal approach to deploying new projects 

If you’re reading this, then you’ve probably already got your own reason to want to deploy NextJS on AWS (with Terraform). If not, here are some good ones:

- No Github account restrictions - no matter if you are deploying from a org repo or a private, AWS treats it all the same (unlike Vercel).
- Easily manage sensitive environment variables with Github secrets and spin up multiple separate environments.
- Use node in-memory cache straight out of the box. Your server will have a persistent state (unlike Lambda!) which means database connections are also no problem.
- Infrastructure as code - clone the sample repo, add your own AWS keys and push to Github. Simple as that!
- Use up some of those AWS credits you have lying around and collecting dust.

You will learn how to get started right away, and then the steps after, I will try to explain what is going on.

## Quickstart

I will cut to the business and show you how to get up and running as quickly as possible. It will be a simple process and you will not have to touch the AWS console… Much!

1. **Get your AWS keys** — Get hold of your `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` credentials from your AWS account. Store these in a safe place, you will need them later.
2. **Create an S3 bucket** — This will be needed for your remote Terraform state. Leave everything as default and give it a unique name like “**[your name]-terraform-state**”. Remember this name!
3. [Clone my repo](https://github.com/OZCAP/nextjs-fargate-terraform) (make sure to give it a star first ⭐️) and push a private copy to your own Github account.
4. In the cloned repo, edit the backend.tf file, change `bucket = \"YOUR_BUCKET_NAME”` to the value you created earlier. Commit and push.
5. In the settings tab for your newly-published repository, click on **Secrets and variables** > **Actions**. Add the following secrets secrets: `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` (the ones from step 1).
6. You are now ready to deploy — In the **Actions** tab of your repo, you should see an action called “Deploy Prod” on the sidebar. Click the action and then press run workflow. Wait for it to complete and **your app should now be deployed!** But where is it?
7. You can find the temporary URL that hosts your site by going onto your **AWS dashboard > EC2 > Load balancers > (your load balancer)**. Where it says **DNS Name**, that is your app’s live url. We will talk later about routing this through a domain with a DNS of your choice (like Cloudflare).

## Architecture

So you’ve just deployed the app, but what is actually going on? If you read through the Github workflow, you can get a rough idea, but here is a diagram to show in slightly more detail.

![CI of NextJS deployment on AWS Fargate](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gjkd5f8ll6jwv2rmf158.jpg)

Firstly, an ECR repository is provisioned with terraform (`ecr.tf`). An ECR repo is just a directory which holds tagged docker images. The Docker image is then built and pushed to the ECR for later use. Afterwards, the rest of the terraform infrastructure if provisioned (load balancer, ECS, security groups, etc) and a new ECS task is created, which references the just-uploaded Docker image. The ECS the spins up the docker image as a Fargate task with the given parameters (and environment variables) specified in the task definition.

## Terraform and multiple environments

Looking in the `terraform` directory, you will see that there are two subdirectories: `module` and `prod`. The prod directory is the entry-point of the Terraform. If you look in `main.tf`, you will see that the main sources the `module` dir while injecting a series of custom variables. The idea is that if you copy and paste the `prod` directory as `dev` (for example) and find and replace all instances of prod in that dir to dev, you will be able to spin up a second environment which can source the terraform module in the same way!

Runtime environment variables are injected into the task definition (`task.tf`) and are loaded in on the `terraform plan` workflow step of the Github action. You can make any variable visible to Terraform by adding a `TF_VAR_` prefix. Terraform will ignore the TF_VAR_ prefix and interpret the rest as a variable. All variables loaded from actions need to be defined in **prod/variables.tf** and all variables referenced in **terraform/module** need to be injected by `main.tf` and also defined in **module/variables.tf**.

## Using a custom domain

Hosting your app from an AWS-generated, non-HTTPS load balancer url is not ideal. If you want to use a custom domain, you can add that domain to Cloudflare, and then create a `cname` DNS record which points to this load balancer url. You should now be able to access your app from a HTTPS connection on your custom domain. To prevent internet access from the original load balancer domain, you can blacklist all IP addresses, apart from IP ranges owned by Cloudflare. This ensures that the only route to your site is through Cloudflare’s network.

## Adding a Postgresql database

The great thing about Terraform is that when you want to add new infra, you don’t have to lay a finger on the AWS dashboard. We can spin up an RDS database and load the `DATABASE_URL` straight into our app, just by changing a few lines. 

To add the database, search in the whole repo for the words **“UNCOMMENT FOR DATABASE”** and you will see indicated lines to uncomment. After doing this, a postgres database should spin up on the next deployment.

If you want to pass the environment variable `DATABASE_URL` into your app, add the following lines to the environment array in your ECS task definition.

```json
// task.tf
{
    "name": "DATABASE_URL",
    "value": "postgresql://${var.db_username}:${var.db_password}@${aws_db_instance.db.endpoint}"
}
// ...
```

> **WARNING:** This RDS instance will be an all-open access. In a production environment, you will not want to expose your database to the internet and instead hide it behind a VPC or narrowly limit the IP addresses which can connect to it.
> 

## Summary

Hopefully this has been a useful guide to get you up and running with Terraform and AWS Fargate. Terraform can be a little bit of a learning curve, but once you understand how it executes and how it manages state, it becomes an valuable and flexible tool. If you have any specific questions or feedback about this article, you can get in touch with me via [oscars.dev](https://oscars.dev) and in case you missed it before, here is a link to my repo to get started with NextJS, Fargate and Terraform. Enjoy! https://github.com/OZCAP/nextjs-fargate-terraform
]]></content:encoded>
    </item>
    <item>
      <title>Building bulletproof ExpressJS APIs with Zod</title>
      <link>https://blog.oscars.dev/posts/building-bulletproof-expressjs-apis-with-zod</link>
      <guid isPermaLink="true">https://blog.oscars.dev/posts/building-bulletproof-expressjs-apis-with-zod</guid>
      <pubDate>Sat, 18 Feb 2023 00:00:00 GMT</pubDate>
      <description>Introduction As a fullstack developer, building robust API endpoints with good error handling is a very important skill. This post will show how the Zod schema validation/declaration library can be used to make solid API endpoints with good error feedback in a few lines of code. What is Zod? . Sounds neat but why do we…</description>
      <content:encoded><![CDATA[
## Introduction

As a fullstack developer, building robust API endpoints with good error handling is a very important skill. This post will show how the Zod schema validation/declaration library can be used to make solid API endpoints with good error feedback in a few lines of code.

## What is Zod?

[Zod is a schema validation npm library used to check types at runtime](https://www.npmjs.com/package/zod). Sounds neat but why do we need this? Isn’t that exactly what Typescript is for? Well Typescript types are great, but they only really help during development. While Typescript types do offer some runtime checking, it is not as comprehensive as Zod. Specifically, Typescript types only validate that the expected properties exist, while Zod can validate the values of those properties.

Here’s an example of a Zod schema object:

```tsx
const schema = z.object({
\tuser: z.object({
\t\tfirstName: z.string().min(2),
\t\tlastName:z.string().optional(),
\t\tage: z.int(),
\t\temail: z.string().email()
\t})
})
```

We can then process some data using this schema with the safeParse() function

```tsx
const data: unknown = ...

const parsedData = schema.safeParse(data);

if (parsedData.success) {
\tconst safeData = parsedData.data
\t// safeData passes validation and is safe to use! ☑️
}
```

By parsing the data through our Zod schema object, we can guarantee that `safeData` object contains a user with a firstName string property of at least 2 characters long, a lastName property (either string or undefined), an integer age and a valid email address. All this is done in a few lines of code. For the next step, we will look at how we can implement this on an Express JS endpoint.

## Using Zod with Express

For this demo, we will validate common request headers on an Express route using Zod using middleware. Any handler in the same context of this middleware will also have the same validation. We will start this task by making a schema for our validation.

### Schema

We want to make sure every request has a header `guest-user-id` with a prefix of `gid-` and a minimum length of 12 characters.

```tsx
const requestSchema = z.object({
  headers: z.object({
    "guest-user-id": z.string().min(12).startsWith("gid-"),
  })
});
```

### Middleware

Next, we want to create a middleware function which checks the input data against the schema and returns a `400 (bad request)`  if the input is invalid.

```tsx
app.use((req, res, next) => {
  const input = requestSchema.safeParse(req);
  if (!input.success) {
    return res.status(400).send(input.error.issues);
  }
  res.locals = input;
  next();
});
```

This function sends all of the issues with the Zod parsing as a response which outlines the parameters which did were not valid and why.  We then set the cleaned data in `res.locals`. This does not send any data back but it is just a way of setting custom data in our request lifecycle which can be picked up at any point later down the line.

For example, if we send a bad request with the `guest-user-id` header set with a value of `guest-123`, we get a full list of all of the issues with our request. This makes debugging issues very easy.

```json
[
    {
        "code": "too_small",
        "minimum": 12,
        "type": "string",
        "inclusive": true,
        "exact": false,
        "message": "String must contain at least 12 character(s)",
        "path": [
            "headers",
            "guest-user-id"
        ]
    },
    {
        "code": "invalid_string",
        "validation": {
            "startsWith": "gid-"
        },
        "message": "Invalid input: must start with \"gid-\"",
        "path": [
            "headers",
            "guest-user-id"
        ]
    }
]
```

> Warning: Do not include sensitive information in the Zod validation schema (like API tokens) while returning `input.error.issues`, as requestors will be able to see why their request was rejected and thus, exposing the sensitive information. These checks should be handled separately with the appropriate error code (like HTTP 401) and Zod errors should not be sent back.
> 

### Endpoint handler

Finally, we can access the safe input on the handler from the `res.locals` where we set it in the middleware. We can also make this input Typescript safe by defining the input as the inferred type of the Zod schema.

```tsx
app.get("/", (req, res) => {
  const input = res.locals as z.infer<typeof requestSchema>;
  const guestUserId = input.headers["guest-user-id"];

  return res.send({ message: `Your guest user ID is ${guestUserId}` });
});
```

## All together now!

Here is the full endpoint with all of the above steps put together. As you can see, in not very many lines of code, we have a robust and strongly-typed API.

```tsx
// schema
const requestSchema = z.object({
  headers: z.object({
    "guest-user-id": z.string().min(12).startsWith("gid-"),
  })
});

// middleware
app.use((req, res, next) => {
  const input = requestSchema.safeParse(req);
  if (!input.success) {
    return res.status(400).send(input.error.issues);
  }
  res.locals = input;
  next();
});

// endpoint handler
app.get("/", (req, res) => {
  const input = res.locals as z.infer<typeof requestSchema>;
  const guestUserId = input.headers["guest-user-id"];

  return res.send({ message: `Your guest user ID is ${guestUserId}` });
});
```

## The old-school alternative

If we wanted to implement the same safety without Zod, our middleware function would look something like this. That’s a lot of code to validate one property. If we have multiple properties, you can imagine how large this validation function would be! This type of validation is prone to errors and can quickly become unwieldy when dealing with multiple properties, whereas Zod provides a more streamlined and less error-prone solution.

```tsx
router.use((req, res, next) => {
  const guestUserId = req.headers.guestUserId;
  if (typeof guestUserId !== "string") {
    return res.status(400).send({ message: "guestUserId is missing" });
  }
  if (guestUserId.length < 12) {
    return res.status(400).send({ message: "guestUserId is too short" });
  }
  if (!guestUserId.startsWith("gid-")) {
    return res.status(400).send({ message: "guestUserId is invalid" });
  }

  res.locals = { guestUserId };
  next();
});
```

## Summary

In this blog post, we explored how to use Zod, a TypeScript-first schema validation library, to validate data in Express APIs. We looked at how Zod makes it easy to define and validate data schemas on the fly, and how it can help catch errors early in the development process. By integrating Zod with Express, we can ensure that only valid data is accepted by our APIs, and that any invalid data is rejected with informative error messages in few lines of code.

Validating data is an essential part of building secure and reliable APIs, and Zod provides a powerful and convenient way to do so. By adopting best practices like data validation, we can build more robust applications and avoid common security vulnerabilities.

If you're interested in learning more about building secure and reliable software, [be sure to check out my other article on hacking BeReal](https://blog.oscars.dev/posts/hacking_bereal_with_man_in_the_middle/). In it, we explore Bereal’s API endpoints and intercept requests to upload whatever photos we like.
]]></content:encoded>
    </item>
    <item>
      <title>Create Vite/NextJS projects with Tailwind pre-configured, in a single command!</title>
      <link>https://blog.oscars.dev/posts/Create_ViteNextJS_projects_with_Tailwind_pre-configured</link>
      <guid isPermaLink="true">https://blog.oscars.dev/posts/Create_ViteNextJS_projects_with_Tailwind_pre-configured</guid>
      <pubDate>Fri, 19 Aug 2022 00:00:00 GMT</pubDate>
      <description>While boiler plating react projects over and over again with the same dependencies, I decided to make a tool which would make my life (and hopefully some other peoples’ lives!) slightly easier. Not only does this tool install Tailwind as a dependancy but it also configures your project&apos;s CSS and Tailwind config asynchr…</description>
      <content:encoded><![CDATA[
While boiler-plating react projects over and over again with the same dependencies, I decided to make a tool which would make my life (and hopefully some other peoples’ lives!) slightly easier. Not only does this tool install Tailwind as a dependancy but it also configures your project's CSS and Tailwind config asynchronously so it works straight out of the box!
I also decided to make this tool Typescript as default because we all love **strict** type-safety ❤.

### Introducing Quick Init!

Quick-init makes creating a Vite/Next-Typescript-Tailwind project as easy as `quick-init [name]`! ⚡ *(Just add `-t next` for NextJs).* 

![Quick-init working example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8tjigb3vpwp6d3rnq32t.png)

Aside from wanting to build a tool which I would personally find useful, I took the opportunity to build Quick-init in Rust in order to improve my *lacking* familiarity with the language and to also make it *blazingly fast™*, as building something useful with a clear functionality goal is the best way to learn something!

It is also fully-customisable with a local configuration file so you can define which dependencies you want to be auto-installed for each template.

## Installation (Homebrew)

If you want to install this on Mac OSX with Homebrew, you can install it via a single line on your favourite terminal!

```
brew tap ozcap/quick-init && brew install quick-init
```

## Source Code

If you want to build this from source, make a pull request or simply browse the code behind this then you are more than welcome to see the [Github repository!](https://github.com/ozcap/quick-init) (Feel free to give a star... Or not).
]]></content:encoded>
    </item>
    <item>
      <title>Hacking BeReal - A practical lesson on “Man in the Middle” attacks</title>
      <link>https://blog.oscars.dev/posts/hacking_bereal_with_man_in_the_middle</link>
      <guid isPermaLink="true">https://blog.oscars.dev/posts/hacking_bereal_with_man_in_the_middle</guid>
      <pubDate>Thu, 18 Aug 2022 00:00:00 GMT</pubDate>
      <description>What is BeReal? is a new social media app which is said to be more “in the moment” compared to conventional social media platforms. The premise of the app is that once per day, everyone gets a notification (at the same time) saying “It’s time to Be Real!” Everyone then has two minutes to take a picture of what they’re…</description>
      <content:encoded><![CDATA[
## What is BeReal?

[BeReal](https://bereal.com/en) is a new social media app which is said to be more “in the moment” compared to conventional social media platforms. The premise of the app is that once per day, everyone gets a notification **(at the same time)** saying *“It’s time to Be Real!”* Everyone then has two minutes to take a picture of what they’re doing at that moment. The phone simultaneously snaps a front and back camera photo, before uploading it to their feed for all (friends) to see. There is no option to choose existing photos to upload or make edits to photos. You must post as you are, in the moment.

I wanted to see how much I could manipulate BeReal with the use of a software called [Mitmproxy](https://mitmproxy.org/) between my iPhone and my Macbook. This software allows me to see all unencrypted HTTPS requests made between my phone and the internet. With this tool, I have the ability to view, pause, edit and cancel any requests at my will. This software also has a Python API for writing custom scripts which I will touch on later.

## Notable domains

I spent some time using the app and learning which endpoints were associated with which functions. With this information, I am able to build a profile of how this app works. I knew already that BeReal was built on top of Firebase but there seem to be an additional endpoint to handle certain requests.

- **firebasestorage.googleapis.com** - Firebase cloud storage, used to upload files (images).
- **us-central1-alexisbarreyat-bereal.cloudfunctions.net** - Firebase cloud function endpoint, used to create posts, delete posts, set post captions, send reactions, etc.
- **mobile.bereal.com** - Secondary endpoint - Used to get posts feed, view friends, add friends.

## Hijacking authentication tokens

The requests made from the phone are authenticated using a [(JSON Web Token) JWT](https://jwt.io/) which is sent in the “Authorization” header of each request. this header expires after a few minutes, however until then, I can use this token to do whatever I like as an authenticated user.

BeReal’s most commonly fetched endpoint is **mobile.bereal.com/api/feeds/friends** which gets the list of friends’ posts on a user’s feed. If I call this endpoint from Postman without the authorization header, I instantly get a `403 - Forbidden` status code which is expected, as I have no credentials to say who I am.

On the Mitmproxy feed, I can just copy and paste the “Authorization” JWT included in any of the BeReal API requests and use that for myself. Upon including this header in Postman, I am allowed through the security and I get greeted with a nice JSON of all of my friends’ posts with links to all of their Images, location data, how many retakes they took, etc.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yhshgj30m8wf9jp2v81n.png)

I can use this same approach to set the caption of my BeReal post as many times as I like. Normally, you are only allowed to set it one time and then it is permanent; however, by making a `POST` request to the **/setCaptionPost** endpoint I can bypass this rule and set my caption over and over again at my leisure.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/o2ghprlipq7uu9qdfybh.png)

## Custom RealMoji

To show appreciation in BeReal, instead of sending a “Like”, you send a “RealMoji” which is an image of your face as a reaction. This adds a level of personality to a reaction which is normally missed. The fact that you can retake RealMojis as much as you like makes it a good opportunity to insert custom images as reactions as we are not only limited to one per day, unlike the BeReal post. Let’s start by looking at what is happening here when we send a RealMoji.

A `POST` request is made to the firebase storage endpoint which initiates a file upload request.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d8jmop8z7zfyl8m84c5p.png)

A `PUT` request is made to firebase, using the upload ID returned from the previous request to upload the image. The actual image data is sent in the body of this request.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ttgjmxhoqzr928ihiqwg.png)

A final POST request is sent to a cloud function with details of the reaction to be sent (Image storage location, reaction type, etc). Atotal of three requests are necessary to send a RealMoji reaction.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/azpwr7x1y44ntwg32rf2.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhuycflc1svzwv8uec6t.png)


### Manipulating expected content size

Without fully understanding the workings of Firebase file uploads, I started by simply modifying the data of the `PUT` request, as that is when the image content is sent to the server.

I set a filter in Mitmproxy to pause the request as it comes through and then modified the body of the request to contain the data of my chosen Jpeg

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bxdguznhwf8wjpoi2vzh.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sq5je72hyw9ol16h1or5.png)

I was then met with a 400 status error, stating that the size of the content did not match what it was expecting. Hmm…

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wvt12qjo6xnsh3ugwlb7.png)

I searched for this number **45243** and found it defined on the prior request. For my second attempt, I also modified this `POST` request in order to match the content size of the file to be uploaded.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7ezqd0peb210mn30k65u.png)

After doing this, I received a 200 status on both requests. Success. My manipulated payload has been uploaded! I then refreshed the app to see my “FakeMoji” in all its glory.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p7jv8y44tj5jh4a9rlnx.png)

## Custom BeReal Post

The flow of uploading a BeReal and sending a RealMoji is mostly identical. After working out how to manipulate the firebase upload, creating a custom post was basically no different.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bczscgu8jsfu7n2uxgvw.png)

## Automating

Mitmproxy also has a Python API which is great for manipulating HTTP requests automatically. Based on the process above, I wrote a script which automates the process of intercepting the request and replacing the data. 

```python
import json
import os
import mitmproxy.http

BASE_PATH = os.path.dirname(os.path.abspath(__file__))
FAKEMOJI = os.path.join(BASE_PATH, 'fakemoji.jpg')

def read_content(file_path):
       with open(file_path, 'rb') as f:
            data = f.read()
            f.close()
            return data   

class Interceptor:
    def __init__(self):
        self.upload_type = None

    # http request trigger
    def request(self, flow: mitmproxy.http.HTTPFlow):

        method  = flow.request.method
        url     = flow.request.url

        # Firebase upload domain
        if ("firebasestorage.googleapis.com" in url):

            # handle upload request (POST)
            if method == "POST":
                # content body
                json_body = json.loads(flow.request.content)
                
                # record upload type (necessary for following PUT request)
                self.upload_type = json_body["metadata"]["type"]

                # replace content length definition
                flow.request.headers["x-goog-upload-content-length"] = os.stat(FAKEMOJI).st_size

            # handle sending file content (PUT)
            elif method == "PUT":

                # only replace image if realmoji upload
                if (self.upload_type == "realmoji"):
                    
                    # replace file content
                    flow.request.content = read_content(FAKEMOJI)

addons = [
    Interceptor()
]
```

The script listens out for Firebase requests with a metadata type of “RealMoji” and then intercepts and manipulates them accordingly. The expected content size for the upload is altered and the content of the `PUT` request is automatically replaced with the data of a local file: *fakemoji.jpg.*

## How to protect yourself from a MITM attack

> If you use a company-managed phone which has custom certificates installed, there is a chance that they can see you making these sorts of requests.
> 

In reality, performing this man-in-the-middle attack on someone else’s phone maliciously would be easier said than done. To get it working for me required configuration of the iPhone’s proxy settings as well as approval of certain device certificates which would not be practical for an attacker to do remotely; however, with a compromised device, something like this would be more than possible.

If you use a company-managed phone which has custom certificates installed, there is a chance that they can see you making these sorts of requests. Obviously, this can’t be said for all companies but it can be hard to tell how “big brother” they are and how much of a grasp they have on your data and internet usage.

### Use a VPN.

In order to prevent such attacks, you can try to protect your physical device from malicious fingers and for an added layer of security, [use a VPN!](https://www.expressrefer.com/refer-a-friend/30-days-free?referrer_id=20340565&utm_campaign=referrals&utm_medium=copy_link&utm_source=referral_dashboard) (Shameless referral link here. Get 30 days free!). Yes, VPNs are **not** perfect, and 90% of the time they are advertised to the general public incorrectly but for this instance, they are effective at masking your actions from prying network eyes.

When I turn my VPN on and then send a RealMoji again, nothing is detected by Mitmproxy. I would include a screenshot to example but there is literally nothing to see. The requests which were shown before simply do not appear, as if the phone is not doing anything at all.

## Closing thoughts

This fun experiment shows how you can manipulate your own device HTTP traffic and see what requests apps are making behind the curtain. It is just another proof of how you can never trust the front end, even from a native app. As a developer, in order to have real security in place, you must install effective security measures on your back end to prevent misuse; otherwise, your production app is not much better than a sandbox environment!
]]></content:encoded>
    </item>
    <item>
      <title>How to deploy an AWS Lambda Stack under a custom domain name</title>
      <link>https://blog.oscars.dev/posts/how_to_deploy_aws_lambda_under_custom_domain</link>
      <guid isPermaLink="true">https://blog.oscars.dev/posts/how_to_deploy_aws_lambda_under_custom_domain</guid>
      <pubDate>Mon, 15 Aug 2022 00:00:00 GMT</pubDate>
      <description>Lambda functions are really cool. They are tiny cloud based compute instances which get created and destroyed on each API call. They automatically scale and can be distributed across the globe and executed close to the user which can deliver very fast response times. In this guide, you will learn how to deploy a basic…</description>
      <content:encoded><![CDATA[
Lambda functions are really cool. They are tiny cloud-based compute instances which get created and destroyed on each API call. They automatically scale and can be distributed across the globe and executed close to the user which can deliver very fast response times.
In this guide, you will learn how to deploy a basic hello world function and how to link it to a domain name which you already own.

## Prerequisites

- Amazon AWS Account
- [AWS CLI tool](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
- [AWS SAM CLI tool](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-install-windows.html)

- [AWS Lambda function deployed to your AWS account](https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-getting-started-hello-world.html)
- A domain name with configurable DNS (I’m using Cloudflare)

For this tutorial, I will be deploying a simple lambda function to my custom domain: **api-helloworld.oscars.dev**.

## Step 1 - Create lambda function

```bash
# initialise boilerplate AWS Lambda project
sam init
...
> 1 - AWS Quick Start Templates
> 1 - Hello World Example
> Use popular package type? (y/N): N
> 10 - nodejs16.x
> 1 - Zip
> 2 - Hello World Example TypeScript
> Project name: hello-world

```

```bash
# enter the directory and build the project
cd hello-world

# build project
sam build --beta-features

# deploy project to aws
sam deploy --guided
...
> Stack Name: hello-world
> AWS Region: eu-west-1
[... "yes" for everything else]
```

Your AWS Lambda function is now deployed to the cloud! You can see your new app under your console.

![Lambda applications in console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilqlrwhgyyicxunf7n8e.png)

Clicking on the app will then show you the URL from where the function can be invoked. In my case, I can directly execute my function with the URL below.


![Lambda function endpoint](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6p6c2cgxb0vopsgag961.png)

## Step 2 - Configure Your Domain

So we have a function which we can invoke from an API endpoint. great! But how do we link a domain to that?

### Certificate Manager

Head to Certificate Manager in your AWS console and then click on `request` in the top-right to request a new certificate.

**Certificate type** > Select “Request public certificate” and click next.


![Public certificate requesting form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hzwhu81kj17pc0tishz1.png)

Input the domain name which you want (from a domain which you own), select DNS validation and then press `Request`.

After proceeding, you should be taken back to the certificates list where after refreshing, you should see your newly requested certificate with a “pending validation”. We’re not done yet!

Click on the certificate to get to the detailed page. In the “Domains” section you should see two columns named `CNAME Name` and `CNAME Value` as below. Take a note of these values. you will need them later!

![CNAME name and value in console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3lxl4vilcqwyt8mbpf83.png)

**If your table is not displaying values like this, wait a little bit and then refresh the page. These entries normally get populated after a minute or so.**

## Step 3 - Validate Certificate with DNS

Go into your DNS settings for the domain you would like to configure. In my case I am using Cloudflare as it is free and it makes it really easy for me to manage my domains.

Add a new CNAME record to your DNS and copy and paste the NAME and VALUE data from previously. After you hit confirm, you should then see a DNS entry which looks similar to this:

![CNAME verification entry](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2zydy48sx5wke8hp1dai.png)

### Wait for a bit

Go and make yourself a cup of tea as this will take a few minutes. It can depend on your DNS provider but it shouldn’t take longer than an hour. When the certificate is validated, you should see a green tick by the entry in Certificate manager.

![Issued certificate in list](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rgekqa19f5q1pnmxorz0.png)

## Step 4 - Link your function

When you’re all validated, go ahead and open up `API Gateway` from your AWS dashboard.

Along the left bar, go to `Custom domain names` and then click `Create`.

In Domain name, put the same domain you got the certificate for earlier. Leave everything else as default and select the corresponding certificate from the `ACM Certificate` dropdown.

Press `Create domain name` and then under `API Mappings`, create a new mapping with your function and the desired stage (Prod).

![API mapping configuraion](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4g0ammpmhjxwhi9ap2v2.png)

## Step 5 - Find the correct URL

This bit caught me out when I first tried to set up a custom domain. The URL displayed on the function control panel isn’t actually the one which you use to forward the requests to.

Open up a terminal and execute the following command to receive a response object with details about your domain.

```bash
aws apigateway get-domain-name --domain-name "<YOUR DOMAIN>"
...
{
    "domainName": "<DOMAIN_NAME>",
    "certificateUploadDate": "<Date>",
    "regionalDomainName": "<API_GATEWAY_ID>.execute-api.eu-west-1.amazonaws.com",
    "regionalHostedZoneId": "...",
    "regionalCertificateArn": "arn:aws:acm:eu-west-1:<ACCOUNT>:certificate/<CERT_ID>",
    "endpointConfiguration": {
        "types": [
            "REGIONAL"
        ]
    },
\t\t"domainNameStatus": "AVAILABLE",
}
```

If your response looks like this, then it's looking good!
Copy your “regionalDomainName”. You will need this for the final step.

## Step 6 - Link DNS Record

Return to your DNS provider once again and another CNAME record (last one this time, I promise) with your **subdomain as the name** (in my case api-helloworld) and your Target/Value as the regionalDomainName from earlier.


![DNS Record of CNAME route](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ky1t1mh1tuzpiw7u3t8q.png)

## Finishing up

Maybe make yourself another cup of tea while the DNS changes take effect. When the changes have applied, you should be able to execute your function via your domain!

![Domain function call example](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ic11l4f8h0oscubcz500.png)
]]></content:encoded>
    </item>
    <item>
      <title>Learn Redux the right way: With Redux Toolkit</title>
      <link>https://blog.oscars.dev/posts/learn_redux_the_right_way_with_redux_toolkit</link>
      <guid isPermaLink="true">https://blog.oscars.dev/posts/learn_redux_the_right_way_with_redux_toolkit</guid>
      <pubDate>Mon, 27 Jun 2022 00:00:00 GMT</pubDate>
      <description>Introduction Redux! The word which you&apos;ve been hearing over and over again. What is it? Why is it needed? Maybe you&apos;ve glanced at the daunting Redux docs and thought &quot;Nah, I&apos;ll learn that some other day&quot;. Well, today is that day and I guarantee that it will be easier than expected. Many existing Redux tutorials are out…</description>
      <content:encoded><![CDATA[
## Introduction
**Redux!** The word which you've been hearing over and over again. What is it? Why is it needed? Maybe you've glanced at the daunting Redux docs and thought "Nah, I'll learn that some other day". Well, today is that day and I guarantee that it will be easier than expected. 

Many existing Redux tutorials are outdated, but this guide will show you the latest, recommended way to implement the framework into your app using redux-toolkit. We will go over it in 5 simple steps. It is recommended that you follow along as we learn how to set up, read and write data to Redux. The rest, you can only learn through further experimenting.

## What is Redux?
Redux is a global state management system for your entire web/react app. Imagine a state which is shared by every component which can be read and updated at any level, on any page. No callback functions.

## 1. Set-up
Lets get started. For this tutorial we be using NextJs and TypeScript. Start by initialising a project.
`npx create-next-app@latest --ts`

In the root of your project, install the dependencies **react-redux** and **@reduxjs/toolkit**.
`npm i react-redux @reduxjs/toolkit`

If you would rather just start playing around with the tutorial code now, the repository can be found [on my github](https://github.com/OZCAP/nextjs-redux-example).

## 2. Create a slice
Slices are the functions which define how a global state is managed. In a slice, we define the **initial state** and also the **reducers** which define how the data is manipulated. Create the file `src/reducers/FooReducer.tsx` containing the code, below.

```
// fooReducer.tsx

import { createSlice } from '@reduxjs/toolkit';

const initialValue = { name: "Nigel", age: 63 };

export const fooSlice = createSlice({
    name: 'foo',
    initialState: {value: initialValue},
    reducers: {
        changeAll: (state, action) => {
            state.value = action.payload;
        },
        agePlusOne: (state, action) => {
            state.value.age += 1;
        }
    }
})

export const { changeAll, agePlusOne } = fooSlice.actions;
export default fooSlice.reducer;
```
It looks like there is a lot going on, but it will become clearer. Trust me. Let's focus on what matters here.
We have an `initialValue` which defines the initial value of an object containing a 'name' and 'age' value.
Under `reducers` we have two special functions which show how the data can be manipulated. We may add as many of these reducer functions as we need.

The function `changeAll` takes in an object with new key values e.g. `{name: 'Bob', age: 44}` and replaces the current key values.

The function `getOlder` takes no parameters, and increases the `age` value by 1.

## 3. Set up Provider
In order for the Redux state to be synchronised across the app, we must nest everything inside a `<Provider/>` component. Copy the code below into `pages/_app.tsx`.

```
// _app.tsx
import type { AppProps } from 'next/app'

import { configureStore } from '@reduxjs/toolkit';
import { Provider } from 'react-redux';
import fooReducer from '../src/reducers/FooReducer'

const store = configureStore({
  reducer:  {
    foo: fooReducer,
  }
});

function MyApp({ Component, pageProps }: AppProps) {
  return (
  <Provider store={store}>
    <Component {...pageProps} />
  </Provider>
  )
}

export default MyApp
```


If you are familiar with NextJs, you will know that `_app.tsx` is the root component of the application. Any page loaded from `/pages` is rendered inside `<Component {...pageProps} />` which means that all routes will always be within the `<Provider/>` component, allowing access to the global state(s) defined by `store` on any page.

## 4. Reading the global state
Go ahead and copy the following code inside your `pages/index.tsx`

```
// index.tsx
import type { NextPage } from 'next'
import { useSelector } from 'react-redux';

const Home: NextPage = () => {
  const foo = useSelector(state => state.foo.value);

  return (
    <main>
      <p>{foo.name}</p>
      <p>{foo.age}</p>
    </main>
  )
}

export default Home
```
When accessing the dev environment, we are now greeted with the text 'Nigel' and '63'. This is the initial state of the object we defined in `FooReducer.tsx`!

The `useSelector()` function this global state from the `store` we set up in our `_app.tsx`.

## 5. Writing to the global state
Edit your index.tsx and add the following code.

```
// index.tsx
//...
import { useDispatch } from 'react-redux';
import { agePlusOne } from '../src/reducers/FooReducer';

const Home: NextPage = () => {
  //...
  const dispatch = useDispatch();

  return (
    <main>
      {foo.name}
      <button onClick={() => {
        dispatch(agePlusOne(null));
      }}>Plus One</button>
      <br />
      {foo.age}
    </main>
  )
}
```
`useDispatch()` is the function which is used to execute the functions outlined in our `FooReducer.tsx`. Here, we have imported the `agePlusOne` function which adds 1 to the current age value. When we click the button, the age will increase by 1. The function takes no arguments.

If we want to do the same with the `changeAll` function, we must import it like we did with the `agePlusOne` function and call it with an argument of the new state that we want e.g. `dispatch(changeAll({name: 'Bob', age: 44}))`.

## Wrapping up
If you want to add more reducers, all you need to do is simply create additional components, for example `BarReducer.tsx` and then include it in the `store` constant defined in `_app.tsx` as below.

```
const store = configureStore({
  reducer:  {
    foo: fooReducer,
    bar: barReducer
  }
})
```
After doing this, you can then reference `bar`, as you did with `foo` and have multiple global states!
]]></content:encoded>
    </item>
    <item>
      <title>Welcome to my &quot;ugly&quot; blog</title>
      <link>https://blog.oscars.dev/posts/welcome-to-my-ugly-blog</link>
      <guid isPermaLink="true">https://blog.oscars.dev/posts/welcome-to-my-ugly-blog</guid>
      <pubDate>Fri, 06 May 2022 00:00:00 GMT</pubDate>
      <description>You may have stumbled upon this Times New Roman, pale background, lightning fast website and thought &quot;what on earth is this? Really? In 2022?&quot; And yes, this may not be the most coventionally beautiful website in the world; however, to me it is perfect. It rakes in sexy 100 page speed scores all round (on both mobile an…</description>
      <content:encoded><![CDATA[
You may have stumbled upon this Times New Roman, pale background, ***lightning-fast*** website and thought *"what on earth is this? Really? In 2022?"* And yes, this may not be the most **coventionally** beautiful website in the world; however, to me it is perfect.

It rakes in sexy 100 page-speed scores all round (on both mobile and desktop), all of the content is CMS loaded with [Supabase](https://supabase.com/), it’s perfectly readable and it’s fully responsive for mobile. What more could I want, than a practical way to convey my thoughts into a single-source which is accessible to all.

![](https://cpqgggswxufbyvaoaryi.supabase.co/storage/v1/object/public/blog-images/001/B001-001)

A trend is currently re-emerging for deveopers to step back from the 2010s idea of a virtual DOM content and return to the era of pre-rendered HTML. Funky JavaScript is great sometimes; however, what we have seen over the past 10 years is a tidal wave of JS-based web content which is (mostly) unnecessary bloat for user experience.

[Hacker News](https://news.ycombinator.com/) is one of the most popular forums for developers and programmers at the moment. During my first visit, I didn’t really know what to think. It looked like some ugly old 90s website. Where were the strobing gifs and neon colours? *(I thought to myself).* What I saw instead, was a really active community with constant, engaging discussions.

![](https://cpqgggswxufbyvaoaryi.supabase.co/storage/v1/object/public/blog-images/001/B001-002)

The more time I spent there, the more I realised that the only thing that matters on a website like this is the content. If that’s what you came there for, then everything else becomes annoying after a short amount of time. You just need to look at websites like [Reddit](https://reddit.com/) which have gone the opposite direction and swapped out their fast, pre-rendered website, to a js-heavy react alternative which *"looks nice"*. They do still have the old version live, but it has been moved to [old.reddit.com](https://old.reddit.com/). Using this mirror, I compared the page speed scores and found that they originally went from a “pass” score of **91%** with the old site, to actually failing the test with an overall desktop score of **43%.** I find this quite shocking but not really surprising, given the browsing experience.

> The buzz-words of “web3” and “blockchain” get thrown around a lot, but the most refreshing change to see is the return of simpler and faster websites with a content-focus opposed to sensory-stimulating styling.

Don’t get me wrong, I love React. I use it for most of my projects. If you want to see nice CSS animations and fancy JavaScript then feel free to look at [my portfolio](https://oscars.dev/). Knock yourself out. If you are building a small web app, then most of the time, ReactJs is the logical solution. The no-framework/library approach is only really sustainable up to a point. Even this simple website, I have built using NextJs with server-side rendering because I find the component-approach to building websites very intuitive and easily maintainable which is something that you don’t easily get without the use of a framework/library.

To summarise, it is interesting to see the direction that web development is going in 2022. The buzz-words of “web3” and “blockchain” get thrown around a lot, but the most refreshing change to see is the return of simpler and faster websites with a content-focus opposed to sensory-stimulating styling. This more basic approach may ward some people away, but on the flip-side, it may prove beneficial to the communities which they house, as it improves the experience for the users who matter the most.
]]></content:encoded>
    </item>
  </channel>
</rss>
