Skip to content

Commit

Permalink
feat(types): support define custom payload (#120)
Browse files Browse the repository at this point in the history
Co-authored-by: yinz <[email protected]>
  • Loading branch information
uinz and yinz authored Dec 18, 2020
1 parent c1575c1 commit 8a83257
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 118 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,9 @@ typings/

# package-lock
package-lock.json

# yarn.lock
yarn.lock

# vscode
.vscode
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
144 changes: 75 additions & 69 deletions jwt.d.ts
Original file line number Diff line number Diff line change
@@ -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<Decoded extends VerifyPayloadType> extends jwt.VerifyCallback {
(err: jwt.VerifyErrors, decoded: Decoded): void;
export interface VerifyCallback<Decoded extends VerifyPayloadType> 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<boolean> | SignPayloadType | Promise<SignPayloadType>
}

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<Decoded extends JWTTypes.VerifyPayloadType>(token: string, options?: jwt.VerifyOptions): Decoded;
verify<Decoded extends JWTTypes.VerifyPayloadType>(token: string, callback: JWTTypes.VerifyCallback<Decoded>): void;
verify<Decoded extends JWTTypes.VerifyPayloadType>(
token: string,
options: jwt.VerifyOptions,
callback: JWTTypes.VerifyCallback<Decoded>,
): void;

decode<Decoded extends JWTTypes.DecodePayloadType>(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<Decoded extends VerifyPayloadType>(token: string, options?: jwt.VerifyOptions): Decoded
verify<Decoded extends VerifyPayloadType>(token: string, callback: VerifyCallback<Decoded>): void
verify<Decoded extends VerifyPayloadType>(token: string, options: jwt.VerifyOptions, callback: VerifyCallback<Decoded>): void

decode<Decoded extends DecodePayloadType>(token: string, options?: jwt.DecodeOptions): null | Decoded
}

export const fastifyJWT: fastify.FastifyPluginCallback<FastifyJWTOptions>

export default fastifyJWT

declare module 'fastify' {
interface FastifyInstance {
jwt: JWT;
jwt: JWT
}

interface FastifyReply {
jwtSign(payload: JWTTypes.SignPayloadType, options?: jwt.SignOptions): Promise<string>;
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<string>
jwtSign(payload: SignPayloadType, callback: jwt.SignCallback): void
jwtSign(payload: SignPayloadType, options: jwt.SignOptions, callback: jwt.SignCallback): void
}

interface FastifyRequest {
jwtVerify<Decoded extends JWTTypes.VerifyPayloadType>(options?: jwt.VerifyOptions): Promise<Decoded>;
jwtVerify<Decoded extends JWTTypes.VerifyPayloadType>(callback: JWTTypes.VerifyCallback<Decoded>): void;
jwtVerify<Decoded extends JWTTypes.VerifyPayloadType>(
options: jwt.VerifyOptions,
callback: JWTTypes.VerifyCallback<Decoded>,
): void;
user: JWTTypes.SignPayloadType;
jwtVerify<Decoded extends VerifyPayloadType>(options?: jwt.VerifyOptions): Promise<Decoded>
jwtVerify<Decoded extends VerifyPayloadType>(callback: VerifyCallback<Decoded>): void
jwtVerify<Decoded extends VerifyPayloadType>(options: jwt.VerifyOptions, callback: VerifyCallback<Decoded>): 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<boolean> | JWTTypes.SignPayloadType | Promise<JWTTypes.SignPayloadType>
}

}

declare const fastifyJWT: fastify.FastifyPlugin<fastifyJWT.FastifyJWTOptions>;

export = fastifyJWT;
107 changes: 58 additions & 49 deletions jwt.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -55,22 +55,31 @@ expectAssignable<Function>(app.jwt.verify)
expectAssignable<Function>(app.jwt.decode)

app.addHook("preHandler", async (request, reply) => {
// assert request and reply specific interface merges
expectAssignable<Function>(request.jwtVerify)
expectAssignable<object | string | Buffer>(request.user)
expectAssignable<Function>(reply.jwtSign)
// assert request and reply specific interface merges
expectAssignable<Function>(request.jwtVerify)
expectAssignable<object | string | Buffer>(request.user)
expectAssignable<Function>(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
// }
// }
// }

0 comments on commit 8a83257

Please sign in to comment.