This repository has been archived by the owner on Dec 14, 2024. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 60
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: Allow usage of express middlewares
- Loading branch information
1 parent
ea23a55
commit a37b8f5
Showing
6 changed files
with
138 additions
and
129 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
import { RequestHandler } from 'express'; | ||
|
||
type AsyncVoid = void | Promise<void>; | ||
|
||
type NowFunction<Req, Res> = (req: Req, res: Res) => AsyncVoid; | ||
|
||
/** | ||
* Combine multiple middleware together. | ||
* | ||
* @param middlewares - Functions of form: `function(req, res, next) { ... }`, aka | ||
* express middlewares. | ||
* | ||
* @return - Single combined middleware | ||
*/ | ||
function combineMiddleware(middlewares: RequestHandler[]): RequestHandler { | ||
return middlewares.reduce((acc, mid) => { | ||
return function(req, res, next): void { | ||
acc(req, res, err => { | ||
if (err) { | ||
return next(err); | ||
} | ||
|
||
mid(req, res, next); | ||
}); | ||
}; | ||
}); | ||
} | ||
|
||
/** | ||
* Chain middlewares together, and expose them to be consumed by a `@now/node` | ||
* serverless function. | ||
* | ||
* @param middlewares - Functions of form: `function(req, res, next) { ... }`, aka | ||
* express middlewares. | ||
*/ | ||
export function chain<Req, Res>( | ||
...middlewares: RequestHandler[] | ||
): (fn: NowFunction<Req, Res>) => NowFunction<Req, Res> { | ||
return function(fn: NowFunction<Req, Res>): NowFunction<Req, Res> { | ||
return function(req: Req, res: Res): AsyncVoid { | ||
// eslint-disable-next-line | ||
// @ts-ignore Need to cast (and verify everything works) from a | ||
// express.Request to a NowRequest | ||
return combineMiddleware(middlewares)(req, res, () => { | ||
fn(req, res); | ||
}); | ||
}; | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
export * from './chain'; | ||
export * from './db'; | ||
export * from './jwt'; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,92 +1,39 @@ | ||
import { NowRequest, NowResponse } from '@now/node'; | ||
import { verify, VerifyErrors } from 'jsonwebtoken'; | ||
import jwksClient, { | ||
JwksClient, | ||
CertSigningKey, | ||
RsaSigningKey | ||
} from 'jwks-rsa'; | ||
import expressJwt from 'express-jwt'; | ||
import { expressJwtSecret } from 'jwks-rsa'; | ||
|
||
/** | ||
* Once the middleware passes, the `req` object will have an `auth0` field with | ||
* the following properties | ||
*/ | ||
export interface WithJwt { | ||
auth0: { | ||
iss: string; | ||
sub: string; | ||
aud: string[]; | ||
iat: number; | ||
exp: number; | ||
azp: string; | ||
scope: string; | ||
}; | ||
} | ||
|
||
// Set up Auth0 configuration | ||
const authConfig = { | ||
domain: process.env.AUTH0_DOMAIN, | ||
audience: process.env.AUTH0_API_IDENTIFIER | ||
}; | ||
|
||
// Define middleware that validates incoming bearer tokens using JWKS from | ||
// auth0 domain | ||
const client: JwksClient = jwksClient({ | ||
cache: true, | ||
rateLimit: true, | ||
jwksRequestsPerMinute: 5, | ||
jwksUri: `https://${authConfig.domain}/.well-known/jwks.json` | ||
// Define middleware that validates incoming bearer tokens | ||
// using JWKS from YOUR_DOMAIN | ||
export const checkJwt = expressJwt({ | ||
algorithm: ['RS256'], | ||
audience: authConfig.audience, | ||
issuer: `https://${authConfig.domain}/`, | ||
requestProperty: 'auth0', | ||
secret: expressJwtSecret({ | ||
cache: true, | ||
rateLimit: true, | ||
jwksRequestsPerMinute: 5, | ||
jwksUri: `https://${authConfig.domain}/.well-known/jwks.json` | ||
}) | ||
}); | ||
|
||
/** | ||
* Verify using getKey callback | ||
* @see https://github.com/auth0/node-jsonwebtoken | ||
*/ | ||
function getKey( | ||
header: { kid?: string }, | ||
callback: (err: Error | null, result: string | undefined) => void | ||
): void { | ||
console.log('header.kid', header.kid); | ||
if (!header.kid) { | ||
callback(new Error('No `kid` field in header'), undefined); | ||
|
||
return; | ||
} | ||
|
||
client.getSigningKey(header.kid, function(err, key) { | ||
if (err) { | ||
callback(err, undefined); | ||
|
||
return; | ||
} | ||
|
||
const signingKey = | ||
(key as CertSigningKey).publicKey || (key as RsaSigningKey).rsaPublicKey; | ||
|
||
console.log('signingKey', signingKey); | ||
callback(null, signingKey); | ||
}); | ||
} | ||
|
||
export async function jwt( | ||
req: NowRequest, | ||
res: NowResponse | ||
): Promise<string | object> { | ||
try { | ||
const bearerToken = req.headers.authorization; | ||
|
||
if (!bearerToken) { | ||
throw new Error('Missing Authorization header'); | ||
} | ||
|
||
const token = bearerToken.replace('Bearer ', ''); | ||
console.log('TOKEN', token, authConfig); | ||
const decoded = await new Promise<string | object>((resolve, reject) => | ||
verify( | ||
token, | ||
getKey, | ||
{ | ||
algorithms: ['RS256'], | ||
audience: authConfig.audience, | ||
issuer: `https://${authConfig.domain}/` | ||
}, | ||
(err: VerifyErrors, decoded: string | object) => { | ||
if (err) { | ||
return reject(err); | ||
} | ||
|
||
resolve(decoded); | ||
} | ||
) | ||
); | ||
|
||
return decoded; | ||
} catch (error) { | ||
res.status(401).send(`Invalid token: ${error.message}`); | ||
|
||
throw error; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters