From 8a83257ad61ae7ec42127c2d4ca2ae6d90d51756 Mon Sep 17 00:00:00 2001 From: yinz Date: Fri, 18 Dec 2020 23:40:31 +0800 Subject: [PATCH] feat(types): support define custom payload (#120) Co-authored-by: yinz --- .gitignore | 6 +++ README.md | 22 ++++++++ jwt.d.ts | 144 ++++++++++++++++++++++++++------------------------ jwt.test-d.ts | 107 ++++++++++++++++++++----------------- 4 files changed, 161 insertions(+), 118 deletions(-) diff --git a/.gitignore b/.gitignore index c0c441e..9f8420b 100644 --- a/.gitignore +++ b/.gitignore @@ -65,3 +65,9 @@ typings/ # package-lock package-lock.json + +# yarn.lock +yarn.lock + +# vscode +.vscode diff --git a/README.md b/README.md index dcbb15b..907eeb6 100644 --- a/README.md +++ b/README.md @@ -549,6 +549,28 @@ Import them like so: import fastifyJwt, { FastifyJWTOptions } from 'fastify-jwt' ``` + +Define custom Payload Type +> [typescript declaration merging](https://www.typescriptlang.org/docs/handbook/declaration-merging.html) + +```ts +declare "fastify-jwt" { + interface FastifyJWT { + payload: { name: string } + } +} + +fastify.get('/', async (request, replay) => { + request.user.name // string + + const token = await replay.jwtSign({ + name: 123 + // ^ Type 'number' is not assignable to type 'string'. + }); +}) + +``` + ## Acknowledgements This project is kindly sponsored by: diff --git a/jwt.d.ts b/jwt.d.ts index 0735ba9..6173dce 100644 --- a/jwt.d.ts +++ b/jwt.d.ts @@ -1,87 +1,93 @@ -import * as fastify from 'fastify'; -import * as jwt from 'jsonwebtoken'; +import * as fastify from 'fastify' +import * as jwt from 'jsonwebtoken' + +/** + * for declaration merging + * @example + * ``` + * declare module 'fastify-jwt' { + * interface Payload { + * type: { name: string; email: string } + * } + * } + * ``` + */ +export interface FastifyJWT { + // payload: ... +} + +export type SignPayloadType = FastifyJWT extends { payload: infer T } + ? T extends string | object | Buffer + ? T + : string | object | Buffer + : string | object | Buffer +export type Secret = jwt.Secret | ((request: fastify.FastifyRequest, reply: fastify.FastifyReply, cb: (e: Error | null, secret: string | undefined) => void) => void) -declare namespace JWTTypes { - type SignPayloadType = object | string | Buffer; - type VerifyPayloadType = object | string; - type DecodePayloadType = object | string; +export type VerifyPayloadType = object | string - interface SignCallback extends jwt.SignCallback { } +export type DecodePayloadType = object | string - interface VerifyCallback extends jwt.VerifyCallback { - (err: jwt.VerifyErrors, decoded: Decoded): void; +export interface VerifyCallback extends jwt.VerifyCallback { + (err: jwt.VerifyErrors, decoded: Decoded): void +} + +export interface FastifyJWTOptions { + secret: Secret | { public: Secret; private: Secret } + decode?: jwt.DecodeOptions + sign?: jwt.SignOptions + verify?: jwt.VerifyOptions & { extractToken?: (request: fastify.FastifyRequest) => string | void } + cookie?: { + cookieName: string + } + messages?: { + badRequestErrorMessage?: string + noAuthorizationInHeaderMessage?: string + authorizationTokenExpiredMessage?: string + authorizationTokenInvalid?: ((err: Error) => string) | string + authorizationTokenUntrusted?: string } + trusted?: (request: fastify.FastifyRequest, decodedToken: { [k: string]: any }) => boolean | Promise | SignPayloadType | Promise } -declare module 'fastify' { - interface JWT { - options: { - decode: jwt.DecodeOptions; - sign: jwt.SignOptions; - verify: jwt.VerifyOptions; - }; - secret: jwt.Secret; - - sign(payload: JWTTypes.SignPayloadType, options?: jwt.SignOptions): string; - sign(payload: JWTTypes.SignPayloadType, callback: JWTTypes.SignCallback): void; - sign(payload: JWTTypes.SignPayloadType, options: jwt.SignOptions, callback: JWTTypes.SignCallback): void; - - verify(token: string, options?: jwt.VerifyOptions): Decoded; - verify(token: string, callback: JWTTypes.VerifyCallback): void; - verify( - token: string, - options: jwt.VerifyOptions, - callback: JWTTypes.VerifyCallback, - ): void; - - decode(token: string, options?: jwt.DecodeOptions): null | Decoded; +export interface JWT { + options: { + decode: jwt.DecodeOptions + sign: jwt.SignOptions + verify: jwt.VerifyOptions } + secret: jwt.Secret + + sign(payload: SignPayloadType, options?: jwt.SignOptions): string + sign(payload: SignPayloadType, callback: jwt.SignCallback): void + sign(payload: SignPayloadType, options: jwt.SignOptions, callback: jwt.SignCallback): void + + verify(token: string, options?: jwt.VerifyOptions): Decoded + verify(token: string, callback: VerifyCallback): void + verify(token: string, options: jwt.VerifyOptions, callback: VerifyCallback): void + + decode(token: string, options?: jwt.DecodeOptions): null | Decoded +} +export const fastifyJWT: fastify.FastifyPluginCallback + +export default fastifyJWT + +declare module 'fastify' { interface FastifyInstance { - jwt: JWT; + jwt: JWT } interface FastifyReply { - jwtSign(payload: JWTTypes.SignPayloadType, options?: jwt.SignOptions): Promise; - jwtSign(payload: JWTTypes.SignPayloadType, callback: JWTTypes.SignCallback): void; - jwtSign(payload: JWTTypes.SignPayloadType, options: jwt.SignOptions, callback: JWTTypes.SignCallback): void; + jwtSign(payload: SignPayloadType, options?: jwt.SignOptions): Promise + jwtSign(payload: SignPayloadType, callback: jwt.SignCallback): void + jwtSign(payload: SignPayloadType, options: jwt.SignOptions, callback: jwt.SignCallback): void } interface FastifyRequest { - jwtVerify(options?: jwt.VerifyOptions): Promise; - jwtVerify(callback: JWTTypes.VerifyCallback): void; - jwtVerify( - options: jwt.VerifyOptions, - callback: JWTTypes.VerifyCallback, - ): void; - user: JWTTypes.SignPayloadType; + jwtVerify(options?: jwt.VerifyOptions): Promise + jwtVerify(callback: VerifyCallback): void + jwtVerify(options: jwt.VerifyOptions, callback: VerifyCallback): void + user: SignPayloadType } } - -type Secret = jwt.Secret | ((request: fastify.FastifyRequest, reply: fastify.FastifyReply, cb: (e: Error | null, secret: string | undefined) => void) => void); - -declare namespace fastifyJWT { - export interface FastifyJWTOptions { - secret: Secret | { public: Secret; private: Secret }; - decode?: jwt.DecodeOptions; - sign?: jwt.SignOptions; - verify?: jwt.VerifyOptions & { extractToken?: (request: fastify.FastifyRequest) => string | void; }; - cookie?: { - cookieName: string; - }; - messages?: { - badRequestErrorMessage?: string; - noAuthorizationInHeaderMessage?: string; - authorizationTokenExpiredMessage?: string; - authorizationTokenInvalid?: ((err: Error) => string) | string; - authorizationTokenUntrusted?: string; - } - trusted?: (request: fastify.FastifyRequest, decodedToken: { [k: string]: any }) => boolean | Promise | JWTTypes.SignPayloadType | Promise - } - -} - -declare const fastifyJWT: fastify.FastifyPlugin; - -export = fastifyJWT; diff --git a/jwt.test-d.ts b/jwt.test-d.ts index 585bb5e..b76a417 100644 --- a/jwt.test-d.ts +++ b/jwt.test-d.ts @@ -5,43 +5,43 @@ import { expectAssignable } from 'tsd' const app = fastify(); const jwtOptions: FastifyJWTOptions = { - secret: { - secret: 'supersecret', - publicPrivateKey: { - public: 'publicKey', - private: 'privateKey' - }, - secretFn: (_req, _rep, cb) => { cb(null, 'supersecret') }, - publicPrivateKeyFn: { - public: (_req, _rep, cb) => { cb(null, 'publicKey') }, - private: 'privateKey' - }, - publicPrivateKeyFn2: { - public: 'publicKey', - private: (_req, _rep, cb) => { cb(null, 'privateKey') }, - } - }[process.env.secretOption!], - sign: { - expiresIn: '1h' + secret: { + secret: 'supersecret', + publicPrivateKey: { + public: 'publicKey', + private: 'privateKey' }, - cookie: { - cookieName: 'jwt' + secretFn: (_req, _rep, cb) => { cb(null, 'supersecret') }, + publicPrivateKeyFn: { + public: (_req, _rep, cb) => { cb(null, 'publicKey') }, + private: 'privateKey' }, - verify: { - maxAge: '1h', - extractToken: (request) => 'token' - }, - decode: { - complete: true - }, - messages: { - badRequestErrorMessage: 'Bad Request', - noAuthorizationInHeaderMessage: 'No Header', - authorizationTokenExpiredMessage: 'Token Expired', - authorizationTokenInvalid: (err) => `${err.message}`, - authorizationTokenUntrusted: 'Token untrusted' - }, - trusted: () => false || '' || Buffer.from('foo') + publicPrivateKeyFn2: { + public: 'publicKey', + private: (_req, _rep, cb) => { cb(null, 'privateKey') }, + } + }[process.env.secretOption!], + sign: { + expiresIn: '1h' + }, + cookie: { + cookieName: 'jwt' + }, + verify: { + maxAge: '1h', + extractToken: (request) => 'token' + }, + decode: { + complete: true + }, + messages: { + badRequestErrorMessage: 'Bad Request', + noAuthorizationInHeaderMessage: 'No Header', + authorizationTokenExpiredMessage: 'Token Expired', + authorizationTokenInvalid: (err) => `${err.message}`, + authorizationTokenUntrusted: 'Token untrusted' + }, + trusted: () => false || '' || Buffer.from('foo') } app.register(fastifyJwt, jwtOptions); @@ -55,22 +55,31 @@ expectAssignable(app.jwt.verify) expectAssignable(app.jwt.decode) app.addHook("preHandler", async (request, reply) => { - // assert request and reply specific interface merges - expectAssignable(request.jwtVerify) - expectAssignable(request.user) - expectAssignable(reply.jwtSign) + // assert request and reply specific interface merges + expectAssignable(request.jwtVerify) + expectAssignable(request.user) + expectAssignable(reply.jwtSign) - try { - await request.jwtVerify(); - } - catch (err) { - reply.send(err); - } + try { + await request.jwtVerify(); + } + catch (err) { + reply.send(err); + } }); app.post('/signup', async (req, reply) => { - const token = app.jwt.sign({ user: "userName" }); - let data = await app.jwt.verify(token); - const user = req.user; - reply.send({ token }); + const token = app.jwt.sign({ user: "userName" }); + let data = await app.jwt.verify(token); + const user = req.user; + reply.send({ token }); }); + +// define custom payload +// declare module './jwt' { +// interface FastifyJWT { +// payload: { +// user: string +// } +// } +// }