-
-
Notifications
You must be signed in to change notification settings - Fork 60
Security: explicit string requirement for request parameters (client id, tokens etc.) #399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
a4a14f4
c76e4c0
11de06a
f3d77ad
50adf11
e51fc27
802546a
f9a05dd
aa88382
fd8f367
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,9 @@ const tokenUtil = require('../utils/token-util'); | |
| const url = require('url'); | ||
| const pkce = require('../pkce/pkce'); | ||
| const { parseScope } = require('../utils/scope-util'); | ||
| const { isDefined, isInTypes } = require('../utils/param-util'); | ||
| const isString = isInTypes('string'); | ||
| const isStringOrNumber = isInTypes('string', 'number'); | ||
|
|
||
| /** | ||
| * Response types. | ||
|
|
@@ -160,17 +163,18 @@ class AuthorizeHandler { | |
| const self = this; | ||
| const clientId = request.body.client_id || request.query.client_id; | ||
|
|
||
| if (!clientId) { | ||
| if (!isDefined(clientId)) { | ||
| throw new InvalidRequestError('Missing parameter: `client_id`'); | ||
| } | ||
|
|
||
| if (!isFormat.vschar(clientId)) { | ||
| if (!isStringOrNumber(clientId) || !isFormat.vschar(clientId)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the one number-accepting site that's missing the Reproduced: a numeric // mirror token-handler:
if (!isStringOrNumber(clientId) || !isFormat.vschar(toString(clientId))) {We should also add a numeric- |
||
| throw new InvalidRequestError('Invalid parameter: `client_id`'); | ||
| } | ||
|
|
||
| const redirectUri = request.body.redirect_uri || request.query.redirect_uri; | ||
|
|
||
| if (redirectUri && !isFormat.uri(redirectUri)) { | ||
| // XXX: why is there no 'Missing parameter: `redirect_uri`' error? | ||
| if (isDefined(redirectUri) && (!isString(redirectUri) || !isFormat.uri(redirectUri))) { | ||
| throw new InvalidRequestError('Invalid request: `redirect_uri` is not a valid URI'); | ||
| } | ||
|
|
||
|
|
@@ -236,7 +240,7 @@ class AuthorizeHandler { | |
|
|
||
| getState (request) { | ||
| const state = request.body.state || request.query.state; | ||
| const stateExists = state && state.length > 0; | ||
| const stateExists = isString(state) && state.length > 0; | ||
| const stateIsValid = stateExists | ||
| ? isFormat.vschar(state) | ||
| : this.allowEmptyState; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| 'use strict'; | ||
|
|
||
| /** | ||
| * @module ParamUtil | ||
| */ | ||
|
|
||
| /** | ||
| * Create a function to check, whether a value is one of the given types. | ||
| * @param types {...string[]} | ||
| * @return {function(*): boolean} | ||
| */ | ||
| function isInTypes (...types) { | ||
| return function (value) { | ||
| return types.includes(typeof value); | ||
| }; | ||
| } | ||
|
|
||
| /** | ||
| * Check if a value is defined (not missing) | ||
| * @param value | ||
| * @return {boolean} | ||
| */ | ||
| function isDefined (value) { | ||
| // TODO: in future versions, consider changing this to `value !== undefined && value !== null && value !== ''`, | ||
| // which might be a breaking changes for some users | ||
| return !!value; | ||
| } | ||
|
|
||
| /** | ||
| * Safely converts a value to a string in the following order: | ||
| * - If the value is already a string, return it. | ||
| * - If the value has a `toString` method, call it and return the result. | ||
| * - If the value is `null` or `undefined`, return an empty string. | ||
| * - Otherwise, use the global `String` function to convert the value. | ||
| * @param value {*} | ||
| * @return {string} | ||
| */ | ||
| function toString(value) { | ||
|
jankapunkt marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two small things on this helper:
(Minor: |
||
| const type = typeof value; | ||
| if (type === 'string') { | ||
| return value; | ||
| } | ||
|
|
||
| if (type === 'undefined' || value === null) { | ||
| throw new TypeError(`Cannot convert ${value} to a string`); | ||
| } | ||
|
|
||
| if (type === 'number' || type === 'bigint') { | ||
| const val = String(value); | ||
| if (val === 'NaN' || val === 'Infinity' || val === '-Infinity') { | ||
| throw new TypeError(`Invalid numeric value ${value}, cannot be converted to a string (${val})`); | ||
| } | ||
| return val; | ||
| } | ||
|
|
||
|
|
||
| throw new TypeError(`Cannot convert value ${value} of type ${type} to a string`); | ||
| } | ||
|
|
||
| module.exports = { isInTypes, isDefined, toString }; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Guarding the request-side
redirect_urihere is good — but the bump to@node-oauth/formats@1.1.0also changes behaviour at theisFormat.*call sites this PR didn't touch, because 1.1.0 now throws on non-strings instead of returningfalse.The one that matters is line 121:
if (code.redirectUri && !isFormat.uri(code.redirectUri)), wherecode.redirectUricomes from the model. A loosely-typed model returning a non-string truthy value now gets a rawTypeError→ 503 instead of the previous cleanInvalidGrantError(400). Worth guarding that one withisStringtoo.More generally, adopting 1.1.0 means we should audit every remaining
isFormatcaller (e.g.token-handlernchar(grant_type)/uri(grant_type)), not just the request-param sites retrofitted here.