Creating Simple Authentication With Rio
Tags: typescript, beginners, tutorial
Published: 2022-11-28 10:38:33

We will have 2 classes, `Authenticator` and `User`.  Authenticator will be used for login and sending OTP mail. User will be used for keep user info for each user. Lets begin.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wndd7ctooe0a768nz6ke.png)

# Authenticator Class
  Authenticator will get an email on initial payload. On init we will save that email to state. And sendOTP will generate otp and send it to the email from state.

#### Dependencies
We will use `mailjet` to send otp mails and `zod` for models.
```json
"node-mailjet": "5.1.1",
"zod": "3.19.1"
```
#### Models
Turn this zod models into Rio models by following instructions on this [link](https://dev.to/retterio/how-to-turn-zod-models-into-rio-models-1g77).
```typescript
// classes/Authenticator/types.ts

import { z } from 'zod'

export const initInputModel = z.object({
    email: z.string().email()
})

export const loginModel = z.object({
    otp: z.number()
})

// Optional
export const privateState = z.object({
    email: z.string().email(),
    otp: z.number()
})

export type InitInputModel = z.infer<typeof initInputModel>
export type Login = z.infer<typeof loginModel>
export type PrivateState = z.infer<typeof privateState>
```
#### Template.yml
You have to convert zod models above to rio models to use as an input model. As you can see we have models for `init` and `login` function.
```yml
# classes/Authenticator/template.yml

init: 
  handler: index.init
  inputModel: AuthInitInputModel
getInstanceId: index.getInstanceId
getState: index.getState
methods:
  - method: login
    type: WRITE
    inputModel: LoginModel
    handler: index.login
    
  - method: sendOTP
    type: WRITE
    handler: index.sendOTP
```
## Index.ts
We will use `mailjet` to send mails. So let's configure `mailjet`.
```typescript
// classes/Authenticator/index.ts

import mailjet from 'node-mailjet'

const mailjetClient = new mailjet({
    apiKey: 'YOUR_API_KEY',
    apiSecret: 'YOUR_API_SECRET'
});
```
### Init Function
We are getting email here and saving it to state, later we will get email from state.
```typescript
// classes/Authenticator/index.ts

export async function init(data: Data): Promise<Data> {
    data.state.private.email = data.request.body.email
    return data
}
```
### GetInstanceId Function
Making instanceId same as email, this way we will not create a billion instances.
```typescript
// classes/Authenticator/index.ts

export async function getInstanceId(data: Data): Promise<string> {
    return data.request.body.email
}
```
### sendOTP Function
Then lets create a function that handles sending mails. This function gets email from state. And generating a six digit otp. 
```typescript
// classes/Authenticator/index.ts

export async function sendOTP(data: Data): Promise<Data> {
    try {
        const { email } = data.state.private
        const otp = Math.floor(100000 + Math.random() * 900000)
        await mailjetClient
            .post("send", { 'version': 'v3.1' })
            .request({
                "Messages": [
                    {
                        "From": {
                            "Email": "bahadir@rettermobile.com",
                            "Name": "Bahadır"
                        },
                        "To": [
                            {
                                "Email": email
                            }
                        ],
                        "Subject": "Greetings from Retter.",
                        "TextPart": "OTP Validation Email",
                        "HTMLPart": `OTP: ${otp}`,
                        "CustomID": "AppGettingStartedTest"
                    }
                ]
            })
        data.state.private.otp = otp
        data.response = {
            statusCode: 200,
            body: { emailSent: true },
        }
    } catch (error) {
        console.log(error)
        data.response = {
            statusCode: 400,
            body: { error: error.message },
        };
    }
    return data
}
```
### Login Function
First checking if received otp is a match. If not just throws an error.
```typescript
// classes/Authenticator/index.ts

const { otp: recivedOtp } = data.request.body as Login
const { otp, email } = data.state.private as PrivateState

if (recivedOtp !== otp) throw new Error(`OTP is wrong`);
```
After checking `otp` We are looking for an instance in `User` class with our email lookup key. If can't find it we are initializing a new instance. Checking if maybe initialization failed.

Giving email inside body because when user class initializing it will set `email` lookup key with this email and we will keep that email in state.
```typescript
// classes/Authenticator/index.ts

// Get existing USER INSTANCE
let getUser = await rdk.getInstance({
    classId: "User",
    body: {
        email
    },
    lookupKey: {
        name: "email",
        value: email
    }
})

if (getUser.statusCode > 299) {
    // CREATE USER INSTANCE -> User class will connect email as lookup key itself
    getUser = await rdk.getInstance({
        classId: "User",
        body: {
        email
    }
    })

    if (getUser.statusCode > 299) throw new Error('Couldnt create user instance')
}
```
After that, generating custom token and Using `instanceId` as `userId` and returning it inside body. 
```typescript
// classes/Authenticator/index.ts

const customToken = await rdk.generateCustomToken({
    userId: getUser.body.instanceId,
    identity: 'enduser'
})

data.state.private.relatedUser = getUser.body.instanceId
data.state.private.otp = undefined
data.response = {
    statusCode: 200,
    body: customToken
};
``` 
### Authorizer Function
Here we are allowing `init` and `get`. Allowing getting state if developer because state keeps ours otp. For `login` and `sendOTP`  checking if we have `instanceId`, `æction` is `"CALL"`.
```typescript
// classes/Authenticator/index.ts

export async function authorizer(data: Data): Promise<Response> {
    const { methodName, identity, instanceId, action } = data.context

    switch (methodName) {  
        case 'INIT':
        case 'GET': {
            return { statusCode: 200 }
        }

        case 'STATE': {
            if (identity === 'developer') return { statusCode: 200 }
        }

        case 'login':
        case 'sendOTP': {
            if (instanceId && action === 'CALL') return { statusCode: 200 }
        }
    }

    return { statusCode: 403 };
}
```
# User Class
#### Dependencies
I used `uuid` library to generate userId's. Instance id's will be created with this.
```json
"uuid": "9.0.0",
"zod": "3.19.1"
```
#### Models
Turn this zod models into Rio models by following instructions on this [link](https://dev.to/retterio/how-to-turn-zod-models-into-rio-models-1g77).
```typescript
// classes/User/types.ts

import { z } from 'zod'

export const privateState = z.object({
    email: z.string().email(),
    userId: z.string()
})

export const userInitModel = z.object({
    email: z.string().email()
})

export type PrivateState = z.infer<typeof privateState>
export type UserInitModel = z.infer<typeof userInitModel>
```
#### Template.yml
You have to convert zod models above to rio models to use as an input model. We have input model for `init` function here.
```yml
# classes/User/template.yml

init: 
  handler: index.init
  inputModel: UserInitInputModel
getState: index.getState
getInstanceId: index.getInstanceId
methods:
  - method: getProfile
    type: READ
    handler: index.getProfile
```
## Index.ts
### GetInstanceId Function
Generating userId and making it instance id.
```typescript
// classes/User/index.ts

import { v4 as uuidv4 } from 'uuid';

export async function getInstanceId(): Promise<string> {
    return uuidv4()
}
```
### Init Function
Saving email and userId to state. And setting lookup key so we can find this instance with email.
```typescript
// classes/User/index.ts

export async function init(data: Data): Promise<Data> {
    const { email } = data.request.body as UserInitModel
    data.state.private = {
        email,
        userId: data.context.instanceId
    } as PrivateState
    await rdk.setLookUpKey({ key: { name: 'email', value: email } })
    return data
}
```
### GetProfile Function
This function just returns state because user data is in state.
```typescript
// classes/User/index.ts

export async function getProfile(data: Data): Promise<Data> {
    data.response = {
        statusCode: 200,
        body: data.state.private,
    };
    return data;
}
```
### Authorizer Function
State keeps user data so we don't want everyone to access it. And allowing getProfile if it fulfills the requirements.
```typescript
// classes/User/index.ts

export async function authorizer(data: Data): Promise<Response> {
    const { identity, methodName,instanceId, userId } = data.context
    if (identity === "developer" && methodName === "getState") {
        return { statusCode: 200 };
    }
    if (identity === "enduser" && methodName === "getProfile" && userId === instanceId) {
        return { statusCode: 200 };
    }
    return { statusCode: 403 };
}
```
Here is the all [code](https://github.com/retterio/authentication-example) for the project .

Now you have an idea of how to create an authentication system. Im glad if I helped. Thanks!

## References
[Rio Docs](https://docs.retter.io)
[Zod Github](https://github.com/colinhacks/zod)
[Mailjet Github](https://github.com/mailjet)
[Uuid Github](https://github.com/thenativeweb/uuidv4)