cloudflare / chanfana

OpenAPI 3 and 3.1 schema generator and validator for Hono, itty-router and more!
https://chanfana.pages.dev
MIT License
275 stars 37 forks source link

Is is possible to access `data.body` when using middlewares? #84

Open ashishjullia opened 11 months ago

ashishjullia commented 11 months ago

Hi,

I've the following code in which I'm trying to run a middleware on data.body.email but unable to do so, am I missing something or is it just possible with request.

index.js

import { OpenAPIRouter } from "@cloudflare/itty-router-openapi";
import { SignUp } from "./Users/controller";
import isValidEmail from "./utils/email"

const router = OpenAPIRouter({
    schema: {
        info: {
            title: "Worker OpenAPI Example",
            version: "1.0",
        },
    },
});

router.post("/api/users", isValidEmail, SignUp)

// Redirect root request to the /docs page
router.original.get("/", (request) =>
    Response.redirect(`${request.url}docs`, 302)
);

// 404 for everything else
router.all("*", () => new Response("Not Found.", { status: 404 }));

export default {
    fetch: router.handle,
};

./utils/email.js

const EMAIL_REGEXP = new RegExp(
    /^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$/
);

const isValidEmail = (request,
    env,
    context,
    data) => {
    console.log(data)
    return EMAIL_REGEXP.test(data.body.email);
}

export default isValidEmail

./users/controller.js

import {
    Bool,
    OpenAPIRoute,
    Path,
    Str
} from "@cloudflare/itty-router-openapi";

import isValidEmail from "../utils/email"

const SignUpResponse = {
    message: "User Added!"
}

export class SignUp extends OpenAPIRoute {
    static schema = {
        tags: ["Users"],
        summary: "User Sign Up",
        requestBody: {
            contentType: 'application/json',
            firstname: new Str({
                description: "User Firstname",
                required: true,
                example: "Ashish"
            }),
            lastname: new Str({
                description: "User Lastname",
                required: true,
                example: "Jullia"
            }),
            email: new Str({
                description: "User Email",
                required: true,
                example: "username@example.com"
            }),
            password: new Str({
                description: "User Password",
                required: true,
                example: "As@121212121212"
            })
        },
        responses: {
            "200": {
                contentType: 'application/json',
                schema: {
                    Response: SignUpResponse,
                },
            },
        },
    };

    async handle(
        request,
        env,
        context,
        data
    ) {
        // Retrieve the validated slug
        const { firstname, lastname, email, password } = data.body

        // console.log(isValidEmail(email))

        return {
            message: "User Added!"
        };
    }
}
ashishjullia commented 11 months ago

Also, the contentType: 'application/json' is not working for requestBody, not sure how to pass it correctly.

I've an issue opened for confirmation: https://github.com/cloudflare/itty-router-openapi/issues/85

ashishjullia commented 11 months ago

With a framework like expressjs (out of context of cf workers) which provides you (req, res, next) and makes it easier to follow middleware pattern and extracting say (email key) from body as req.body.email.

Is is possible to achieve the same with this itty-rotuer-openapi? I'm aware that it has (request, env, context, data) but unable to get the data.body.email and next in the similar fashion :/

G4brym commented 11 months ago

Hey there, in order to validate emails you should use the Email type like this:

import {
    Email
} from "@cloudflare/itty-router-openapi";

...

            email: new Email({
                description: "User Email",
                required: true,
            }),

Currently middleware support is limited, and you cannot access the data object, you can read more about whats supported here

itty-router-openapi doesn't support the next() function like express, but you should be able to modify the request, env, context parameters in middlewares and these changes will be applied in downstream endpoints

ashishjullia commented 11 months ago

@G4brym Hmm, thanks for the information but by providing the email example I wanted to know whether how to play on data.body when working with middleware because a lot of powerful things like authentication/authorization can be done via these middlewares or more such things.

Sad to know that it won't be possible with this. :/

G4brym commented 11 months ago

You can do middlewares the "old fashion", because the endpoints are all class based, you can just build a base class that does authentication and then extend your endpoint from there.

otherwise you can still do authentication with the current middleware support here is a snipped taken from here

export function getBearer(request: Request): null | string {
    const authHeader = request.headers.get('Authorization')
    if (!authHeader || authHeader.substring(0, 6) !== 'Bearer') {
        return null
    }
    return authHeader.substring(6).trim()
}

export async function authenticateUser(request: Request, env: any, context: any) {
    const token = getBearer(request)
    let session

    if (token) {
        session = await context.qb.fetchOne({
            tableName: 'users_sessions',
            fields: '*',
            where: {
                conditions: [
                    'token = ?1',
                    'expires_at > ?2',
                ],
                params: [
                    token,
                    new Date().getTime()
                ]
            },
        }).execute()
    }

    if (!token || !session.results) {
        return new Response(JSON.stringify({
            success: false,
            errors: "Authentication error"
        }), {
            headers: {
                'content-type': 'application/json;charset=UTF-8',
            },
            status: 401,
        })
    }

    // set the user_id for endpoint routes to be able to reference it
    env.user_id = session.results.user_id
}

...

// Authentication middleware
router.all('/api/*', authenticateUser)