elysiajs / elysia

Ergonomic Framework for Humans
https://elysiajs.com
MIT License
9.09k stars 193 forks source link

Macro modify schema #666

Open mdbetancourt opened 1 month ago

mdbetancourt commented 1 month ago

What is the problem this feature would solve?

import { Elysia } from 'elysia'
import { auth } from './auth'

const app = new Elysia()
    .use(auth)
    .get('/', () => 'hi', {
        isAuth: true,
        role: 'admin'
    })

in this example how the macro included by auth plugin could add schemas for authentication like

{
headers: 'auth.Header',
    detail: {
      security: [
        {
          bearerAuth: []
        }
      ],
      parameters: []
    },
    response: {
      401: 'errors.Unauthorized'
    }
}

What is the feature you are proposing to solve the problem?

import { Elysia } from 'elysia'
import { auth } from './auth'

const app = new Elysia()
    .use(auth)
    .macro(() => ({
      isAuth(requireAuth: boolean) {
        // all logic in beforeHandle

        return {
          headers: 'auth.Header',
          detail: {
            security: [
              {
                bearerAuth: []
              }
            ],
            parameters: []
          },
          response: {
            401: 'errors.Unauthorized'
          }
        }
      }
    }))
    .get('/', () => 'hi', {
        isAuth: true,
        role: 'admin'
    })

What alternatives have you considered?

export const Authentication = new Elysia({ name: 'Service.Authentication' })
  .use(HttpErrors)
  .use(Credential)
  .model('auth.Header', t.Object({
    authorization: t.TemplateLiteral('Bearer ${string}')
  }))
  .guard({
    headers: 'auth.Header',
    detail: {
      security: [
        {
          bearerAuth: []
        }
      ],
      parameters: []
    },
    response: {
      401: 'errors.Unauthorized'
    }
  })
  .resolve({ as: 'global' }, async ({ headers: { authorization }, error, jwt }) => {
    if(!authorization) {
      return error('Unauthorized', {
        type: 'authentication',
        errors: [
          { message: 'invalid token' }
        ]
      })
    }
    const [_, token] = authorization.split(' ', 1)

    const isValidToken = await jwt.verify(token)

    if(!isValidToken) return error('Unauthorized', {
      type: 'authentication',
      errors: [
        { message: 'invalid token' }
      ]
    })

    return {
      user: isValidToken.payload.user as User,
      authorizationToken: token
    }
  })
app
  // unprotected routes
  .get(...)
  .guard(app => app
     .use(Authentication)
     .get(....)
     // endpoint to protect

i considered this way too but i'am not able to make the thing works

export function secured<H>(guarded: H): <const T extends Elysia, const O extends Elysia>(app: T) => AnyElysia {
  return (app) => app
    .use(HttpErrors)
    .use(Credential)
    .model('auth.Header', t.Object({
      authorization: t.TemplateLiteral('Bearer ${string}')
    }))
    .guard({
      headers: 'auth.Header',
      detail: {
        security: [
          {
            bearerAuth: []
          }
        ],
        parameters: []
      },
      response: {
        401: 'errors.Unauthorized'
      },
    }, guarded)
}

used as

app
.use(secured(app => app.get(...)))
kravetsone commented 1 week ago

+1