-
Notifications
You must be signed in to change notification settings - Fork 85
Commit
This is a follow-up of PR #32. General: - Refactor tests: - Namespace all tests into their separate packages (for better organization of the output), including a subspace for middleware - Rename `expect` function to `waitFor` in all calls to testAsyncMulti(), to better reflect its behavior - Copy .jshintrc into new packages json-routes: - Export RestMiddleware object for middleware packages to have a namespace to declare themselves within - Add a `JsonRoutes.middleware` to eventually replace `JsonRoutes.middleWare` (since 'middleware' is one word) - Update change log - Remove 100 character line limit in README rest-accounts-bearer-token: - Split rest-accounts-bearer-token middleware package into a middleware package that parses a standard bearer token (rest-bearer-token-parser) and one that validates an auth token and sets the request.userId to the authorized Meteor.user's ID (authenticate-meteor-user-by-token) - Attach middleware to RestMiddleware namespace, instead of adding it to every route. This allows middleware to be made available by simply adding its package. It can later be added to the middleware stack with JsonRoutes.middleware.use('path', RestMiddleware.<middlewareFunction>). - Move tests to their appropriate package simple-rest: - Add token and auth middleware in tests to pass auth tests - This could be a sign that auth tests may be better suited in the corresponding middleware packages rest-accounts-password: - Use the latest version of json-routes (1.0.3) - Add token parsing and auth middleware to the stack (currently added to all routes, which is bad - needs fix)
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#simple:authenticate-meteor-user-by-token | ||
|
||
SimpleRest middleware for validating a Meteor.user's login token | ||
|
||
## Middleware Name | ||
|
||
This middleware can be accessed as: | ||
|
||
**`RestMiddleware.authenticateMeteorUserByToken`** | ||
|
||
### Request Properties Required | ||
|
||
- `request.authToken` | ||
- _String_ | ||
- A valid login token for a `Meteor.user` account (requires `accounts-base`) | ||
|
||
### Request Properties Modified | ||
|
||
- `request.userId` | ||
- _String_ | ||
- If the `request.authToken` is found in a user account, sets this to the ID of the authenticated user. Otherwise, `null`. | ||
|
||
## Usage | ||
|
||
Simply add this layer of middleware after any token parsing middleware, and voila! | ||
This comment has been minimized.
Sorry, something went wrong. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
/* global JsonRoutes:false - from simple:json-routes package */ | ||
|
||
/** | ||
* SimpleRest middleware for validating a Meteor.user's login token | ||
* | ||
* This middleware must be processed after the request.token has been set to a | ||
* valid login token for a Meteor.user account (from a separate layer of | ||
* middleware). If authentication is successful, the request.userId will be set | ||
* to the ID of the authenticated user. | ||
* | ||
* @middleware | ||
*/ | ||
RestMiddleware.authenticateMeteorUserByToken = function (req, res, next) { | ||
var userId = getUserIdFromAuthToken(req); | ||
if (userId) { | ||
req.userId = userId; | ||
} | ||
next(); | ||
}; | ||
|
||
// Validate the req.authToken and set the req.userId of the authorized Meteor.user | ||
This comment has been minimized.
Sorry, something went wrong.
stubailo
Owner
|
||
function getUserIdFromAuthToken (req) { | ||
if (! _.has(Package, "accounts-base")) { | ||
return null; | ||
} | ||
|
||
var token = req.authToken; | ||
if (! token) { | ||
return null; | ||
} | ||
|
||
// TODO: Check token expiration? | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
kahmali
Author
Collaborator
|
||
//var tokenExpires = | ||
// Package["accounts-base"].Accounts._tokenExpiration(token.when); | ||
//if (new Date() >= tokenExpires) { | ||
// throw new Meteor.Error("token-expired", "Your login token has expired. " + | ||
// "Please log in again."); | ||
//} | ||
|
||
var user = Meteor.users.findOne({ | ||
"services.resume.loginTokens.hashedToken": hashToken(token) | ||
}); | ||
|
||
if (user) { | ||
return user._id; | ||
} | ||
else { | ||
return null; | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong. |
||
} | ||
} | ||
|
||
function hashToken (unhashedToken) { | ||
check(unhashedToken, String); | ||
|
||
// The Accounts._hashStampedToken function has a questionable API where | ||
// it actually takes an object of which it only uses one property, so don't | ||
// give it any more properties than it needs. | ||
var hashStampedTokenArg = { token: unhashedToken }; | ||
var hashStampedTokenReturn = Package["accounts-base"] | ||
.Accounts | ||
._hashStampedToken(hashStampedTokenArg); | ||
check(hashStampedTokenReturn, { | ||
hashedToken: String | ||
}); | ||
|
||
// Accounts._hashStampedToken also returns an object, get rid of it and just | ||
// get the one property we want. | ||
return hashStampedTokenReturn.hashedToken; | ||
} | ||
This comment has been minimized.
Sorry, something went wrong.
aldeed
Collaborator
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
if (Meteor.isServer) { | ||
JsonRoutes.add("get", "accounts-auth-user", function (req, res) { | ||
JsonRoutes.sendResult(res, 200, req.userId); | ||
}); | ||
} | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
kahmali
Author
Collaborator
|
||
else { // Meteor.isClient | ||
var token; | ||
var userId; | ||
|
||
// TODO: Do all of this work in some sort of setup/teardown (maybe use munit?) | ||
This comment has been minimized.
Sorry, something went wrong.
stubailo
Owner
|
||
testAsyncMulti ("Middleware - Authenticate User By Token - set req.userId", [ | ||
function (test, waitFor) { | ||
Meteor.call ("clearUsers", waitFor (function () { | ||
})); | ||
}, | ||
function (test, waitFor) { | ||
HTTP.post ("/users/register", { | ||
This comment has been minimized.
Sorry, something went wrong.
stubailo
Owner
|
||
data: { | ||
username: "test", | ||
email: "test@test.com", | ||
password: "test" | ||
} | ||
}, waitFor (function (err, res) { | ||
test.equal (err, null); | ||
test.isTrue (Match.test (res.data, { | ||
id: String, | ||
token: String, | ||
tokenExpires: String | ||
})); | ||
|
||
token = res.data.token; | ||
userId = res.data.id; | ||
})); | ||
}, | ||
function (test, waitFor) { | ||
HTTP.get ("/accounts-auth-user", { | ||
headers: {Authorization: "Bearer " + token} | ||
}, waitFor (function (err, res) { | ||
test.equal (err, null); | ||
test.equal (res.data, userId); | ||
})); | ||
} | ||
]); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
Package.describe({ | ||
name: 'simple:authenticate-meteor-user-by-token', | ||
This comment has been minimized.
Sorry, something went wrong.
stubailo
Owner
|
||
version: '0.0.1', | ||
// Brief, one-line summary of the package. | ||
summary: 'Authorize user via auth token', | ||
This comment has been minimized.
Sorry, something went wrong. |
||
// URL to the Git repository containing the source code for this package. | ||
git: '', | ||
// By default, Meteor will default to using README.md for documentation. | ||
// To avoid submitting documentation, set this field to null. | ||
documentation: 'README.md' | ||
}); | ||
|
||
Package.onUse(function(api) { | ||
api.versionsFrom('1.0'); | ||
api.use('simple:json-routes@1.0.3'); | ||
api.addFiles('auth.js', 'server'); | ||
}); | ||
|
||
Package.onTest(function(api) { | ||
api.use('tinytest'); | ||
api.use('test-helpers'); | ||
api.use('simple:authenticate-meteor-user-by-token'); | ||
api.use('simple:json-routes@1.0.3'); | ||
api.addFiles('auth_tests.js'); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,8 +2,7 @@ | |
|
||
<https://atmospherejs.com/simple/json-routes> | ||
|
||
The simplest bare-bones way to define server-side JSON API endpoints, without | ||
any extra functionality. Based on [connect-route]. | ||
The simplest bare-bones way to define server-side JSON API endpoints, without any extra functionality. Based on [connect-route]. | ||
This comment has been minimized.
Sorry, something went wrong.
stubailo
Owner
|
||
|
||
## Example | ||
|
||
|
@@ -21,25 +20,17 @@ JsonRoutes.add("get", "/posts/:id", function (req, res, next) { | |
|
||
Add a server-side route that returns JSON. | ||
|
||
- `method` - The HTTP method that this route should accept: `"get"`, `"post"`, | ||
etc. See the full list [here][connect-route L4]. The method name is | ||
case-insensitive, so `'get'` and `'GET'` are both acceptable. | ||
- `path` - The path, possibly with parameters prefixed with a `:`. See the | ||
example. | ||
- `handler(request, response, next)` - A handler function for this route. | ||
`request` is a Node request object, `response` is a Node response object, | ||
`next` is a callback to call to let the next middleware handle this route. You | ||
don't need to use this normally. | ||
- `method` - The HTTP method that this route should accept: `"get"`, `"post"`, etc. See the full list [here][connect-route L4]. The method name is case-insensitive, so `'get'` and `'GET'` are both acceptable. | ||
- `path` - The path, possibly with parameters prefixed with a `:`. See the example. | ||
- `handler(request, response, next)` - A handler function for this route. `request` is a Node request object, `response` is a Node response object, `next` is a callback to call to let the next middleware handle this route. You don't need to use this normally. | ||
|
||
### JsonRoutes.sendResult(response, code, data) | ||
|
||
Return data fom a route. | ||
|
||
- `response` - the Node response object you got as an argument to your handler | ||
function. | ||
- `response` - the Node response object you got as an argument to your handler function. | ||
- `code` - the status code to send. `200` for OK, `500` for internal error, etc. | ||
- `data` - the data you want to send back, will be sent as serialized JSON with | ||
content type `application/json`. | ||
- `data` - the data you want to send back, will be sent as serialized JSON with content type `application/json`. | ||
|
||
### JsonRoutes.setResponseHeaders(headerObj) | ||
|
||
|
@@ -52,10 +43,9 @@ Set the headers returned by `JsonRoutes.sendResult`. Default value is: | |
} | ||
``` | ||
|
||
## Adding middlewares | ||
## Adding middleware | ||
|
||
If you want to insert connect middleware and ensure that it runs before your | ||
REST route is hit, use `JsonRoutes.middleWare`. | ||
If you want to insert connect middleware and ensure that it runs before your REST route is hit, use `JsonRoutes.middleWare`. | ||
|
||
```js | ||
JsonRoutes.middleWare.use(function (req, res, next) { | ||
|
@@ -68,13 +58,13 @@ JsonRoutes.middleWare.use(function (req, res, next) { | |
|
||
#### Unreleased | ||
|
||
Allow case-insensitive method names to be passed as the first param to | ||
`JsonRoutes.add()` (e.g., `JsonRoutes.add('get',...)` and | ||
`JsonRoutes.add('GET',...)` are both acceptable) | ||
- Add `JsonRoutes.middleware` to eventually replace `JsonRoutes.middleWare` (since 'middleware' is one word) | ||
- Export `RestMiddleware` to provide a namespace for all middleware packages to add their middleware functions (only available on the server) | ||
- Allow case-insensitive method names to be passed as the first param to `JsonRoutes.add()` (e.g., `JsonRoutes.add('get',...)` and `JsonRoutes.add('GET',...)` are both acceptable) | ||
|
||
#### 1.0.3 | ||
|
||
Add `JsonRoutes.middleWare` | ||
Add `JsonRoutes.middleWare` for adding middleware to the stack | ||
|
||
[connect-route]: https://github.com/baryshev/connect-route | ||
[connect-route L4]: https://github.com/baryshev/connect-route/blob/06f92e07dc8e4690f7f788df39b37b5db4b06f90/lib/connect-route.js#L4 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/** | ||
* The namespace for all REST middleware | ||
* | ||
* Each middleware package should define a single middleware function and add it | ||
* to this namespace. | ||
* | ||
* Usage (in a separate package): | ||
* | ||
* RestMiddleware.coolMiddleware = function (req, res, next) { | ||
* // Do interesting middleware stuff here | ||
* }; | ||
* | ||
* TODO: Consider having sub-namespaces for each lifecycle method (e.g., Middleware.pre, Middleware.auth, etc) | ||
*/ | ||
RestMiddleware = {}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,19 +11,21 @@ Package.describe({ | |
}); | ||
|
||
Npm.depends({ | ||
connect: "2.11.0", | ||
"connect-route": "0.1.5" | ||
connect: '2.11.0', | ||
'connect-route': '0.1.5' | ||
}); | ||
|
||
Package.onUse(function(api) { | ||
api.export("JsonRoutes"); | ||
api.versionsFrom('1.0'); | ||
api.addFiles('json-routes.js', "server"); | ||
|
||
api.use([ | ||
"webapp", | ||
"underscore" | ||
], "server"); | ||
'webapp', | ||
'underscore' | ||
], 'server'); | ||
api.addFiles('json-routes.js', 'server'); | ||
api.addFiles('middleware.js', 'server'); | ||
|
||
api.export('JsonRoutes', 'server'); | ||
api.export('RestMiddleware'); | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
kahmali
Author
Collaborator
|
||
}); | ||
|
||
Package.onTest(function(api) { | ||
|
This file was deleted.
This file was deleted.
Should include usage example here.