Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type augmentation for namespace methods? #348

Open
2 tasks done
58bits opened this issue Sep 24, 2024 · 2 comments
Open
2 tasks done

Type augmentation for namespace methods? #348

58bits opened this issue Sep 24, 2024 · 2 comments

Comments

@58bits
Copy link

58bits commented Sep 24, 2024

Prerequisites

  • I have written a descriptive issue title
  • I have searched existing issues to ensure the issue has not already been raised

Issue

EDIT: Simplifying this question a little.

We've created the following namespaced jwt plugin based on the docs here... https://github.com/fastify/fastify-jwt?tab=readme-ov-file#namespace

import fp from 'fastify-plugin'
import { FastifyPluginAsync, FastifyInstance } from 'fastify'
import fastifyJwt, { FastifyJWTOptions, JWT } from '@fastify/jwt'

// https://fastify.dev/docs/latest/Reference/TypeScript/#creating-a-typescript-fastify-plugin
declare module 'fastify' {
  interface FastifyInstance {
    jwt: {
      accessToken: JWT
      refreshToken: JWT
    }
  }

  interface FastifyReply {
    accessTokenSign: JWT['sign']
    refreshTokenSign: JWT['sign']
  }

  interface FastifyRequest {
    accessTokenVerify: JWT['verify']
    accessTokenDecode: JWT['decode']
    refreshTokenVerify: JWT['verify']
    refreshTokenDecode: JWT['decode']
  }
}

const jwtPlugin: FastifyPluginAsync<FastifyJWTOptions> = async (app: FastifyInstance, options) => {
  app.register<FastifyJWTOptions>(fastifyJwt, {
    // secret: Buffer.from(app.config.jwt.secret as string, 'base64'),
    secret: {
      private: app.config.jwt.access.secret.private,
      public: app.config.jwt.access.secret.public,
    },
    sign: {
      algorithm: 'RS256',
      iss: app.config.jwt.access.issuer,
      aud: app.config.jwt.access.audience,
      expiresIn: app.config.jwt.access.expiresIn,
    },
    verify: {
      algorithms: ['RS256'],
      allowedIss: app.config.jwt.access.issuer,
    },
    namespace: 'accessToken',
    jwtDecode: 'accessTokenDecode',
    jwtSign: 'accessTokenSign',
    jwtVerify: 'accessTokenVerify',
  })

  app.register<FastifyJWTOptions>(fastifyJwt, {
    // secret: Buffer.from(app.config.jwt.secret as string, 'base64'),
    secret: {
      private: app.config.jwt.refresh.secret.private,
      public: app.config.jwt.refresh.secret.public,
    },
    sign: {
      algorithm: 'RS256',
      iss: app.config.jwt.refresh.issuer,
      aud: app.config.jwt.refresh.audience,
      expiresIn: app.config.jwt.refresh.expiresIn,
    },
    verify: {
      algorithms: ['RS256'],
      allowedIss: app.config.jwt.refresh.issuer,
    },
    namespace: 'refreshToken',
    jwtDecode: 'refreshTokenDecode',
    jwtSign: 'refreshTokenSign',
    jwtVerify: 'refreshTokenVerify',
  })
}

export default fp(jwtPlugin)

With the above, when calling either of the namespaced sign methods, our augmented type definition above, and the signature on the actual method don't exactly match - which means we suspect, that

accessTokenSign: JWT['sign'] is not the correct way to assign a type to the accessTokenSign and other methods. - since the reply and request augmented methods are asynchronous - i.e. require either a callback, or return a promise.

accessTokenVerify: JWT['verify'] is also not the correct type annotation, as the request.accessTokenVerify method does not need a token as a parameter (it looks up the token from the request header).

And so our question really is - what's the correct way to augment the FastifyReply and FastifyRequest types with correct sign, verify and decode methods when using namespaces?

@58bits 58bits changed the title Namespace registration methods must use callback or async await? Type augmentation for namespace methods? Sep 24, 2024
@climba03003
Copy link
Member

You are requesting to expose the below types.

interface FastifyReply {
jwtSign(payload: fastifyJwt.SignPayloadType, options?: fastifyJwt.FastifyJwtSignOptions): Promise<string>
jwtSign(payload: fastifyJwt.SignPayloadType, callback: SignerCallback): void
jwtSign(payload: fastifyJwt.SignPayloadType, options: fastifyJwt.FastifyJwtSignOptions, callback: SignerCallback): void
jwtSign(payload: fastifyJwt.SignPayloadType, options?: Partial<fastifyJwt.SignOptions>): Promise<string>
jwtSign(payload: fastifyJwt.SignPayloadType, options: Partial<fastifyJwt.SignOptions>, callback: SignerCallback): void
}
interface FastifyRequest {
jwtVerify<Decoded extends fastifyJwt.VerifyPayloadType>(options?: fastifyJwt.FastifyJwtVerifyOptions): Promise<Decoded>
jwtVerify<Decoded extends fastifyJwt.VerifyPayloadType>(callback: VerifierCallback): void
jwtVerify<Decoded extends fastifyJwt.VerifyPayloadType>(options: fastifyJwt.FastifyJwtVerifyOptions, callback: VerifierCallback): void
jwtVerify<Decoded extends fastifyJwt.VerifyPayloadType>(options?: Partial<fastifyJwt.VerifyOptions>): Promise<Decoded>
jwtVerify<Decoded extends fastifyJwt.VerifyPayloadType>(options: Partial<fastifyJwt.VerifyOptions>, callback: VerifierCallback): void
jwtDecode<Decoded extends fastifyJwt.DecodePayloadType>(options?: fastifyJwt.FastifyJwtDecodeOptions): Promise<Decoded>
jwtDecode<Decoded extends fastifyJwt.DecodePayloadType>(callback: fastifyJwt.DecodeCallback<Decoded>): void
jwtDecode<Decoded extends fastifyJwt.DecodePayloadType>(options: fastifyJwt.FastifyJwtDecodeOptions, callback: fastifyJwt.DecodeCallback<Decoded>): void
user: fastifyJwt.UserType
}

@58bits
Copy link
Author

58bits commented Sep 24, 2024

@climba03003 - yes I think you're right. I've also updated the above to include our augmented FastifyInstance .

Would the exported function types look like this? (courtesy ChatGPT). If so I'd be glad to submit a PR

EDIT: Okay we're actually working on this, and the types are more likely to be something like:

export interface JwtSignFunction {
  (
    payload: SignPayloadType,
    options?: FastifyJwtSignOptions | Partial<SignOptions>
  ): Promise<string>
  (payload: SignPayloadType, callback: SignerCallback): void
  (
    payload: SignPayloadType,
    options: FastifyJwtSignOptions | Partial<SignOptions>,
    callback: SignerCallback
  ): void
}

export interface JwtVerifyFunction {
  <Decoded extends VerifyPayloadType>(options?: FastifyJwtVerifyOptions): Promise<Decoded>
  <Decoded extends VerifyPayloadType>(callback: VerifierCallback): void
  <Decoded extends VerifyPayloadType>(
    options: FastifyJwtVerifyOptions,
    callback: VerifierCallback
  ): void
  <Decoded extends VerifyPayloadType>(options?: Partial<VerifyOptions>): Promise<Decoded>
  <Decoded extends VerifyPayloadType>(
    options: Partial<VerifyOptions>,
    callback: VerifierCallback
  ): void
}

export interface JwtDecodeFunction {
  <Decoded extends DecodePayloadType>(options?: FastifyJwtDecodeOptions): Promise<Decoded>
  <Decoded extends DecodePayloadType>(callback: DecodeCallback<Decoded>): void
  <Decoded extends DecodePayloadType>(
    options: FastifyJwtDecodeOptions,
    callback: DecodeCallback<Decoded>
  ): void
}

And then implemented in our augmented types as :

declare module 'fastify' {
  interface FastifyInstance {
    jwt: {
      accessToken: JWT
      refreshToken: JWT
    }
  }

  interface FastifyReply {
    accessTokenSign: JwtSignFunction
    refreshTokenSign: JwtSignFunction
  }

  interface FastifyRequest {
    accessTokenVerify: JwtVerifyFunction
    accessTokenDecode: JwtDecodeFunction
    refreshTokenVerify: JwtVerifyFunction
    refreshTokenDecode: JwtDecodeFunction
  }
}

Again - if this is close, and I can help by submitting a PR then I'd be glad to.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants