Skip to content

Commit 2e9cd32

Browse files
feat: add package for getting the full URL from a request (#16)
1 parent a5e518a commit 2e9cd32

File tree

13 files changed

+242
-138
lines changed

13 files changed

+242
-138
lines changed

docs/request-url.md

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
---
2+
title: Get Request URL
3+
---
4+
5+
A small utility to get the original URL that a request was made with. e.g. if your user types `http://example.com/foo/bar` into the address bar, and it loads the web page on your server, this should return `http://example.com/foo/bar`.
6+
7+
## Installation
8+
9+
To install, run the following command in your terminal:
10+
11+
```
12+
yarn add @authentication/request-url
13+
```
14+
15+
## Usage
16+
17+
Calling `getRequestURL` with either an express request or koa context returns the `URL` object representing the full URL that the visitor navigated to in order to load the page.
18+
19+
```typescript
20+
import getRequestURL from '@authentication/cloudflare-ip';
21+
import express from 'express';
22+
23+
const app = express();
24+
25+
app.use((req, res) => {
26+
res.send(`The URL you requested is: ${getRequestURL(req).href}`);
27+
});
28+
29+
app.listen(process.env.PORT || 3000);
30+
```
31+
32+
```javascript
33+
const getRequestURL = require('@authentication/cloudflare-ip');
34+
const express = require('express');
35+
36+
const app = express();
37+
38+
app.use((req, res) => {
39+
res.send(`The URL you requested is: ${getRequestURL(req).href}`);
40+
});
41+
42+
app.listen(process.env.PORT || 3000);
43+
```
44+
45+
### Trust Proxy
46+
47+
If you run your app behind a proxy in production, you will also need to pass `{trustProxy: true}`. `trustProxy` is enabled by default if `NODE_ENV=development` to make it easier to use `getRequestURL` with setups like webpack-dev-server.
48+
49+
**N.B.** If a malicious user finds a way to bypass the proxy, or if the proxy does not overwrite the `x-forwarded-host` and `x-forwarded-proto` headers, it may be possible for a malicous attacker to force this function to return a URL for a server you do not control.
50+
51+
### Base URL
52+
53+
Instead of setting `trustProxy`, if you know the host name you expect your app to be running on, you should set the `baseURL` option. This is much more secure. You can use an environment variable to support different values between development, staging and production:
54+
55+
```typescript
56+
import getRequestURL from '@authentication/cloudflare-ip';
57+
import express from 'express';
58+
59+
const app = express();
60+
61+
app.use((req, res) => {
62+
res.send(`The URL you requested is: ${getRequestURL(req, {
63+
// set this variable to something like: https://www.example.com
64+
baseURL: process.env.BASE_URL,
65+
}).href}`);
66+
});
67+
68+
app.listen(process.env.PORT || 3000);
69+
```
70+
71+
```javascript
72+
const getRequestURL = require('@authentication/cloudflare-ip');
73+
const express = require('express');
74+
75+
const app = express();
76+
77+
app.use((req, res) => {
78+
res.send(`The URL you requested is: ${getRequestURL(req, {
79+
// set this variable to something like: https://www.example.com
80+
baseURL: process.env.BASE_URL,
81+
}).href}`);
82+
});
83+
84+
app.listen(process.env.PORT || 3000);
85+
```

packages/oauth1/src/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import Cookie from '@authentication/cookie';
44
import {Mixed} from '@authentication/types';
55
import AuthorizationError from './errors/AuthorizationError';
66
import StateVerificationFailure from './errors/StateVerificationFailure';
7-
import originalURL from './originalURL';
7+
import getRequestURL from '@authentication/request-url';
88
const OAuth1Base = require('oauth').OAuth;
99

1010
function parseURL(name: string, input: URL | string, base?: string | URL) {
@@ -239,7 +239,14 @@ export default class OAuth1Authentication<State = Mixed> {
239239
typeof callbackURLInitial === 'string'
240240
? new URL(
241241
callbackURLInitial,
242-
originalURL(req, {trustProxy: this._trustProxy}),
242+
getRequestURL(req, {
243+
trustProxy:
244+
this._trustProxy === undefined
245+
? req.app.get('trust proxy') ||
246+
process.env.NODE_ENV === 'development'
247+
: this._trustProxy,
248+
baseURL: process.env.BASE_URL || process.env.BASE_URI,
249+
}),
243250
)
244251
: callbackURLInitial;
245252
if (callbackURL) {
@@ -261,7 +268,7 @@ export default class OAuth1Authentication<State = Mixed> {
261268
}
262269
const userAuthorizationParams = options.userAuthorizationParams;
263270
if (userAuthorizationParams) {
264-
Object.keys(userAuthorizationParams).forEach(key => {
271+
Object.keys(userAuthorizationParams).forEach((key) => {
265272
userAuthorizationURL.searchParams.set(
266273
key,
267274
userAuthorizationParams[key],

packages/oauth1/src/originalURL.ts

Lines changed: 0 additions & 36 deletions
This file was deleted.

packages/oauth2/src/index.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import StateVerificationFailure from './errors/StateVerificationFailure';
88
import TokenError from './errors/TokenError';
99
import InternalOAuthError from './errors/InternalOAuthError';
1010
import getUID from './getUID';
11-
import originalURL from './originalURL';
11+
import getRequestURL from '@authentication/request-url';
1212
const OAuth2Base = require('oauth').OAuth2;
1313

1414
// This type used to be exported from http but has gone missing
@@ -261,7 +261,14 @@ export default class OAuth2Authentication<State = Mixed, Results = Mixed> {
261261
return typeof this._callbackURL === 'string'
262262
? new URL(
263263
this._callbackURL,
264-
originalURL(req, {trustProxy: this._trustProxy}),
264+
getRequestURL(req, {
265+
trustProxy:
266+
this._trustProxy === undefined
267+
? req.app.get('trust proxy') ||
268+
process.env.NODE_ENV === 'development'
269+
: this._trustProxy,
270+
baseURL: process.env.BASE_URL || process.env.BASE_URI,
271+
}),
265272
)
266273
: this._callbackURL;
267274
}
@@ -279,7 +286,7 @@ export default class OAuth2Authentication<State = Mixed, Results = Mixed> {
279286
const authorizeUrl = new URL(this._authorizeURL.href);
280287
const p = options.params;
281288
if (p) {
282-
Object.keys(p).forEach(key => {
289+
Object.keys(p).forEach((key) => {
283290
authorizeUrl.searchParams.set(key, p[key]);
284291
});
285292
}

packages/oauth2/src/originalURL.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

packages/passwordless/src/index.ts

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import Store, {
2424
StoreTransaction,
2525
} from './Store';
2626
import Token from './Token';
27-
import originalURL from './originalURL';
27+
import getRequestURL from '@authentication/request-url';
2828

2929
import {
3030
CreateTokenStatusKind,
@@ -173,8 +173,8 @@ export default class PasswordlessAuthentication<State> {
173173
options.maxAge === undefined
174174
? ms('1 hour')
175175
: typeof options.maxAge === 'number'
176-
? options.maxAge
177-
: ms(options.maxAge);
176+
? options.maxAge
177+
: ms(options.maxAge);
178178
if (
179179
typeof this._maxAge !== 'number' ||
180180
isNaN(this._maxAge) ||
@@ -210,7 +210,7 @@ export default class PasswordlessAuthentication<State> {
210210
// from behind a single router, but over the long run, we want to keep
211211
// this pretty low or someone could be quite abusive.
212212
this._createTokenByIpRateLimit = new BucketRateLimit(
213-
this._getStore(ip => 'create_ip_' + ip),
213+
this._getStore((ip) => 'create_ip_' + ip),
214214
{
215215
interval: '10 minutes',
216216
maxSize: 20,
@@ -223,7 +223,7 @@ export default class PasswordlessAuthentication<State> {
223223
// a few attempts. It is possible a user might get spammed with a
224224
// few token e-mails, but this will quickly stem the tide.
225225
this._createTokenByUserRateLimit = new ExponentialRateLimit(
226-
this._getStore(userID => 'user_' + userID),
226+
this._getStore((userID) => 'user_' + userID),
227227
{
228228
baseDelay: '5 minutes',
229229
factor: 2,
@@ -235,7 +235,7 @@ export default class PasswordlessAuthentication<State> {
235235
// We don't use an exponential backoff because resetting it when a
236236
// correct token attempt happens would defeat the point.
237237
this._validatePassCodeByIpRateLimit = new BucketRateLimit(
238-
this._getStore(ip => 'validate_ip_' + ip),
238+
this._getStore((ip) => 'validate_ip_' + ip),
239239
{
240240
interval: '10 minutes',
241241
maxSize: 20,
@@ -253,7 +253,14 @@ export default class PasswordlessAuthentication<State> {
253253
return typeof this._callbackURL === 'string'
254254
? new URL(
255255
this._callbackURL,
256-
originalURL(req, {trustProxy: this._trustProxy}),
256+
getRequestURL(req, {
257+
trustProxy:
258+
this._trustProxy === undefined
259+
? req.app.get('trust proxy') ||
260+
process.env.NODE_ENV === 'development'
261+
: this._trustProxy,
262+
baseURL: process.env.BASE_URL || process.env.BASE_URI,
263+
}),
257264
)
258265
: new URL(this._callbackURL.href);
259266
}
@@ -265,8 +272,8 @@ export default class PasswordlessAuthentication<State> {
265272
}
266273
private _getStore<T>(idToString: (id: T) => string): RateLimitStoreAPI<T> {
267274
return {
268-
tx: fn =>
269-
this._store.tx(store =>
275+
tx: (fn) =>
276+
this._store.tx((store) =>
270277
fn({
271278
save(id, state, oldState) {
272279
return store.saveRateLimit(idToString(id), state, oldState);
@@ -323,7 +330,7 @@ export default class PasswordlessAuthentication<State> {
323330
]);
324331
const passCodeHash = await hash(passCode);
325332
// store the token
326-
const tokenID = await this._store.tx(store =>
333+
const tokenID = await this._store.tx((store) =>
327334
store.saveToken({
328335
userID,
329336
dos: dosCode,
@@ -446,7 +453,7 @@ export default class PasswordlessAuthentication<State> {
446453
(await verify(
447454
passCode,
448455
token.passCodeHash,
449-
async updatedPassCodeHash => {
456+
async (updatedPassCodeHash) => {
450457
// we're about to delete the token anyway,
451458
// so no need to update it
452459
},

packages/passwordless/src/originalURL.ts

Lines changed: 0 additions & 39 deletions
This file was deleted.

packages/passwordless/types.d.ts

Lines changed: 0 additions & 3 deletions
This file was deleted.

packages/passwordless/types.js

Lines changed: 0 additions & 3 deletions
This file was deleted.

packages/request-url/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @authentication/request-url
2+
3+
For documentation, see https://www.atauthentication.com/docs/request-url.html

0 commit comments

Comments
 (0)