Skip to content

Commit

Permalink
✨ Add TypeScript support
Browse files Browse the repository at this point in the history
* Add TypeScript types declaration file

* Reference declaration file in package.json

* Update README.md to give some hints about usage in TypeScript

* 📝 Remove semicolons from the documentation examples

* 🎨 Remove semicolons from typescript definitions
  • Loading branch information
Morgan Touverey-Quilling authored and LionC committed May 29, 2017
1 parent 170d5fd commit 2310f82
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 0 deletions.
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
[![npm version](https://badge.fury.io/js/express-basic-auth.svg)](https://badge.fury.io/js/express-basic-auth)
[![npm](https://img.shields.io/npm/dm/express-basic-auth.svg)]()
[![David](https://img.shields.io/david/strongloop/express.svg)]()
![TypeScript compatible](https://img.shields.io/badge/typescript-compatible-brightgreen.svg)
[![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php)

Simple plug & play HTTP basic auth middleware for Express.
Expand Down Expand Up @@ -154,6 +155,47 @@ node example.js
This will start a small express server listening at port 8080. Just look at the file,
try out the requests and play around with the options.

## TypeScript usage

A declaration file is bundled with the library. You don't have to install a `@types/` package.

```typescript
import * as basicAuth from 'express-basic-auth'
```

:bulb: **Using `req.auth`**

express-basic-auth sets `req.auth` to an object containing the authorized credentials like `{ user: 'admin', password: 'supersecret' }`.

In order to use that `req.auth` property in TypeScript without an unknown property error, use covariance to downcast the request type:

```typescript
app.use(basicAuth(options), (req: basicAuth.IBasicAuthedRequest, res, next) => {
res.end(`Welcome ${req.auth.user} (your password is ${req.auth.password})`)
next()
})
```

:bulb: **A note about type inference on synchronous authorizers**

Due to some TypeScript's type-system limitation, the arguments' type of the synchronous authorizers are not inferred.
For example, on an asynchronous authorizer, the three arguments are correctly inferred:

```typescript
basicAuth({
authorizeAsync: true,
authorizer: (user, password, authorize) => authorize(null, password == 'secret')
})
```

However, on a synchronous authorizer, you'll have to type the arguments yourself:

```typescript
basicAuth({
authorizer: (user: string, password: string) => (password == 'secret')
})
```

## Tests

The cases in the `example.js` are also used for automated testing. So if you want
Expand Down
137 changes: 137 additions & 0 deletions express-basic-auth.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
/// <reference types="express" />

import { Request, RequestHandler } from 'express'

/**
* This is the middleware builder.
*
* Example:
* const users = { alice: '1234', bob: 'correcthorsebatterystaple' }
* app.use(basicAuth({ users, challenge: true }), myHandler)
*
* @param options The middleware's options (at least 'users' or 'authorizer' are mandatory).
*/
function expressBasicAuth(options: expressBasicAuth.BasicAuthMiddlewareOptions): RequestHandler

namespace expressBasicAuth {
/**
* The configuration you pass to the middleware can take three forms, either:
* - A map of static users ({ bob: 'pa$$w0rd', ... }) ;
* - An authorizer function
* - An asynchronous authorizer function
*/
export type BasicAuthMiddlewareOptions = IUsersOptions | (IAuthorizerOptions | IAsyncAuthorizerOptions)

/**
* express-basic-auth patches the request object to set an `auth` property that lets you retrieve the authed user.
*
* Example (TypeScript):
* app.use(basicAuth({ ... }), (req: basicAuth.IBasicAuthedRequest, res, next) => {
* res.end(`Welcome ${req.auth.user} (your password is ${req.auth.password})`)
* next()
* })
*/
export interface IBasicAuthedRequest extends Request {
auth: { user: string, password: string }
}

type Authorizer = (username: string, password: string) => boolean

type AsyncAuthorizerCallback = (err: any, authed?: boolean) => void

type AsyncAuthorizer = (username: string, password: string, callback: AsyncAuthorizerCallback) => void

type ValueOrFunction<T> = T | ((req: IBasicAuthedRequest) => T)

interface IBaseOptions {
/**
* Per default the middleware will not add a WWW-Authenticate challenge header to responses of unauthorized requests.
* You can enable that by setting this to true, causing most browsers to show a popup to enter credentials
* on unauthorized responses.
*
* @default false
*/
challenge?: boolean

/**
* You can set the realm (the realm identifies the system to authenticate against and can be used by clients to
* save credentials) of the challenge by passing a string or a function that gets passed the request and is
* expected to return the realm.
*
* @default undefined
*/
realm?: ValueOrFunction<string>

/**
* Per default, the response body for unauthorized responses will be empty.
* It can be configured using the unauthorizedResponse option. You can either pass a static response or a
* function that gets passed the express request object and is expected to return the response body.
* If the response body is a string, it will be used as-is, otherwise it will be sent as JSON.
*
* @default ''
*/
unauthorizedResponse?: ValueOrFunction<any>
}

interface IUsersOptions extends IBaseOptions {
/**
* If you simply want to check basic auth against one or multiple static credentials, you can pass those
* credentials in the users option.
*
* Example:
* const users = { alice: '1234', bob: 'correcthorsebatterystaple' }
* app.use(basicAuth({ users, challenge: true }), myHandler)
*/
users: { [username: string]: string }
}

interface IAuthorizerOptions extends IBaseOptions {
/**
* Set to true if your authorizer is asynchronous.
*/
authorizeAsync?: false

/**
* You can pass your own authorizer function, to check the credentials however you want.
* It will be called with a username and password and is expected to return true or false to indicate that the
* credentials were approved or not:
*
* Example:
* app.use(basicAuth({ authorizer }))
*
* function myAuthorizer(username: string, password: string) {
* return username.startsWith('A') && password.startsWith('secret');
* }
*
* This will authorize all requests with credentials where the username begins with 'A' and the password begins
* with 'secret'. In an actual application you would likely look up some data instead ;-)
*/
authorizer: Authorizer
}

interface IAsyncAuthorizerOptions extends IBaseOptions {
/**
* Set it to true to use a asynchronous authorizer.
*/
authorizeAsync: true

/**
* You can pass an asynchronous authorizer. It will be passed a callback as the third parameter, which is
* expected to be called by standard node convention with an error and a boolean to indicate if the credentials
* have been approved or not.
*
* Example:
* app.use(basicAuth({ authorizer, authorizeAsync: true }));
*
* function authorizer(username, password, authorize) {
* if(username.startsWith('A') && password.startsWith('secret'))
* return authorize(null, true)
*
* return authorize(null, false)
* }
*/
authorizer: AsyncAuthorizer
}
}

export = expressBasicAuth
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"version": "1.0.2",
"description": "Plug & play basic auth middleware for express",
"main": "index.js",
"types": "express-basic-auth.d.ts",
"scripts": {
"test": "mocha test.js"
},
Expand All @@ -25,6 +26,7 @@
},
"homepage": "https://github.com/LionC/express-basic-auth#readme",
"dependencies": {
"@types/express": ">=4.0.0",
"basic-auth": "^1.0.4"
},
"devDependencies": {
Expand Down

0 comments on commit 2310f82

Please sign in to comment.