diff --git a/.versions b/.versions
index 736c8ee..cb93d71 100644
--- a/.versions
+++ b/.versions
@@ -1,70 +1,65 @@
-accounts-base@2.2.10
-accounts-password@2.4.0
-allow-deny@1.1.1
-babel-compiler@7.10.5
-babel-runtime@1.5.1
-base64@1.0.12
-binary-heap@1.0.11
-boilerplate-generator@1.7.2
-caching-compiler@1.2.2
-callback-hook@1.5.1
-check@1.3.2
-coffeescript@1.0.17
-dburles:mongo-collection-instances@0.1.3
-ddp@1.4.1
-ddp-client@2.6.1
-ddp-common@1.4.0
-ddp-rate-limiter@1.2.1
-ddp-server@2.7.0
-diff-sequence@1.1.2
-dynamic-import@0.7.3
-ecmascript@0.16.8
-ecmascript-runtime@0.8.1
-ecmascript-runtime-client@0.12.1
-ecmascript-runtime-server@0.11.0
-ejson@1.1.3
-email@2.2.5
-fetch@0.1.4
-geojson-utils@1.0.11
-http@1.4.4
-id-map@1.1.1
-inter-process-messaging@0.1.1
+accounts-base@3.0.4
+accounts-password@3.0.3
+allow-deny@2.1.0
+babel-compiler@7.11.3
+babel-runtime@1.5.2
+base64@1.0.13
+binary-heap@1.0.12
+boilerplate-generator@2.0.0
+callback-hook@1.6.0
+check@1.4.4
+core-runtime@1.0.0
+dburles:mongo-collection-instances@1.0.0
+ddp@1.4.2
+ddp-client@3.1.0
+ddp-common@1.4.4
+ddp-rate-limiter@1.2.2
+ddp-server@3.1.0
+diff-sequence@1.1.3
+dynamic-import@0.7.4
+ecmascript@0.16.10
+ecmascript-runtime@0.8.3
+ecmascript-runtime-client@0.12.2
+ecmascript-runtime-server@0.11.1
+ejson@1.1.4
+email@3.1.2
+facts-base@1.0.2
+fetch@0.1.5
+geojson-utils@1.0.12
+id-map@1.2.0
+inter-process-messaging@0.1.2
jkuester:http@2.1.0
-leaonline:oauth2-server@5.1.0
-lmieulet:meteor-coverage@3.2.0
-lmieulet:meteor-legacy-coverage@0.1.0
-lmieulet:meteor-packages-coverage@0.1.0
-local-test:leaonline:oauth2-server@5.1.0
-localstorage@1.2.0
-logging@1.3.3
-meteor@1.11.5
-meteortesting:browser-tests@1.3.5
-meteortesting:mocha@2.0.3
-meteortesting:mocha-core@8.0.1
-minimongo@1.9.3
-modern-browsers@0.1.10
-modules@0.20.0
-modules-runtime@0.13.1
-mongo@1.16.8
-mongo-decimal@0.1.3
-mongo-dev-server@1.1.0
-mongo-id@1.0.8
-npm-mongo@4.17.2
-ordered-dict@1.1.0
-practicalmeteor:chai@1.9.2_3
-promise@0.12.2
-random@1.2.1
-rate-limit@1.1.1
-react-fast-refresh@0.2.8
-reactive-var@1.0.12
-reload@1.3.1
-retry@1.1.0
-routepolicy@1.1.1
-sha@1.0.9
-socket-stream-client@0.5.2
-tracker@1.3.3
-typescript@4.9.5
-underscore@1.6.0
-url@1.3.2
-webapp@1.13.8
-webapp-hashing@1.1.1
+lai:collection-extensions@1.0.0
+leaonline:oauth2-server@6.0.0
+local-test:leaonline:oauth2-server@6.0.0
+localstorage@1.2.1
+logging@1.3.5
+meteor@2.1.0
+meteortesting:browser-tests@1.7.0
+meteortesting:mocha@3.2.0
+meteortesting:mocha-core@8.2.0
+minimongo@2.0.2
+modern-browsers@0.2.0
+modules@0.20.3
+modules-runtime@0.13.2
+mongo@2.1.0
+mongo-decimal@0.2.0
+mongo-dev-server@1.1.1
+mongo-id@1.0.9
+npm-mongo@6.10.2
+ordered-dict@1.2.0
+promise@1.0.0
+random@1.2.2
+rate-limit@1.1.2
+react-fast-refresh@0.2.9
+reactive-var@1.0.13
+reload@1.3.2
+retry@1.1.1
+routepolicy@1.1.2
+sha@1.0.10
+socket-stream-client@0.6.0
+tracker@1.3.4
+typescript@5.6.3
+url@1.3.5
+webapp@2.0.5
+webapp-hashing@1.1.2
diff --git a/API.md b/API.md
index 46ee425..7252973 100644
--- a/API.md
+++ b/API.md
@@ -46,12 +46,16 @@ Uses the following values to check:
'saveRefreshToken',
'saveToken',
'getAccessToken'
+'revokeToken'
UserValidation
Used to register handlers for different instances that validate users.
This allows you to validate user access on a client-based level.
+validateParams ⇒ boolean
+Abstraction that checks given query/body params against a given schema
+
app : Object
Wrapped WebApp
with express-style get/post and default use routes.
@@ -76,6 +80,8 @@ Implements the OAuth2Server model with Meteor-Mongo bindings.
* [.saveRefreshToken(token, clientId, expires, user)](#OAuthMeteorModel+saveRefreshToken) ⇒ Promise.<\*>
* [.getRefreshToken()](#OAuthMeteorModel+getRefreshToken)
* [.grantTypeAllowed(clientId, grantType)](#OAuthMeteorModel+grantTypeAllowed) ⇒ boolean
+ * [.verifyScope(accessToken, scope)](#OAuthMeteorModel+verifyScope) ⇒ Promise.<boolean>
+ * [.revokeToken()](#OAuthMeteorModel+revokeToken)
@@ -199,6 +205,24 @@ getRefreshToken(token) should return an object with:
| clientId |
| grantType |
+
+
+### oAuthMeteorModel.verifyScope(accessToken, scope) ⇒ Promise.<boolean>
+Compares expected scope from token with actual scope from request
+
+**Kind**: instance method of [OAuthMeteorModel
](#OAuthMeteorModel)
+
+| Param |
+| --- |
+| accessToken |
+| scope |
+
+
+
+### oAuthMeteorModel.revokeToken()
+revokeToken(refreshToken) is required and should return true
+
+**Kind**: instance method of [OAuthMeteorModel
](#OAuthMeteorModel)
## OAuth2ServerDefaults : Object
@@ -250,6 +274,7 @@ Defaults to a 500 response, unless further details were added.
| res | | |
| options | Object
| options with error information |
| options.error | String
| Error name |
+| options.logError | boolean
| optional flag to log the erroe to the console |
| options.description | String
| Error description |
| options.uri | String
| Optional uri to redirect to when error occurs |
| options.status | Number
| Optional statuscode, defaults to 500 |
@@ -274,6 +299,7 @@ Uses the following values to check:
- 'saveRefreshToken',
- 'saveToken',
- 'getAccessToken'
+- 'revokeToken'
**Kind**: global constant
**Returns**: boolean
- true if valid, otherwise false
@@ -289,6 +315,24 @@ Used to register handlers for different instances that validate users.
This allows you to validate user access on a client-based level.
**Kind**: global constant
+
+* [UserValidation](#UserValidation)
+ * [.register(instance, validationHandler)](#UserValidation.register)
+ * [.isValid(instance, handlerArgs)](#UserValidation.isValid) ⇒ \*
+
+
+
+### UserValidation.register(instance, validationHandler)
+Registers a validation method that allows
+to validate users on custom logic.
+
+**Kind**: static method of [UserValidation
](#UserValidation)
+
+| Param | Type | Description |
+| --- | --- | --- |
+| instance | [OAuth2Server
](#OAuth2Server) | |
+| validationHandler | function
| sync or async function that performs the validation |
+
### UserValidation.isValid(instance, handlerArgs) ⇒ \*
@@ -302,53 +346,23 @@ Delegates `handlerArgs` to the registered validation handler.
| instance | [OAuth2Server
](#OAuth2Server) |
| handlerArgs | \*
|
-
+
-## app : Object
-Wrapped `WebApp` with express-style get/post and default use routes.
+## validateParams ⇒ boolean
+Abstraction that checks given query/body params against a given schema
**Kind**: global constant
-**See**: https://docs.meteor.com/packages/webapp.html
-
-* [app](#app) : Object
- * [.get(url, handler)](#app.get)
- * [.post(url, handler)](#app.post)
- * [.use(args)](#app.use)
-
-
-
-### app.get(url, handler)
-Creates a get route for a given handler
-
-**Kind**: static method of [app
](#app)
-
-| Param | Type |
-| --- | --- |
-| url | string
|
-| handler | function
|
-
-
-
-### app.post(url, handler)
-Creates a post route for a given handler.
-If headers' content-type does not equal to `application/x-www-form-urlencoded`
-then it will be transformed accordingly.
-
-**Kind**: static method of [app
](#app)
-
-| Param | Type |
-| --- | --- |
-| url | string
|
-| handler | function
|
-
-
-
-### app.use(args)
-Default wrapper around `WebApp.use`
-
-**Kind**: static method of [app
](#app)
| Param |
| --- |
-| args |
+| actualParams |
+| requiredParams |
+| debug |
+
+
+## app : Object
+Wrapped `WebApp` with express-style get/post and default use routes.
+
+**Kind**: global constant
+**See**: https://docs.meteor.com/packages/webapp.html
diff --git a/HISTORY.md b/HISTORY.md
index 0b00e19..02ca912 100644
--- a/HISTORY.md
+++ b/HISTORY.md
@@ -1,5 +1,15 @@
# History
+### 6.0.0
+- Meteor 3 / Express compatibility
+- added scope verification in authenticated routes
+- improved internal logging
+- fix bug in validation for custom models
+- fix support for explicit `client.id` field
+
+## 5.0.0
+- sync support for @node-oauth/oauth2-server 5.x by
+
## 4.2.1
- this is a patch release, fixing a syntax error
(that never got picked up, due to wrong linter config)
diff --git a/lib/middleware/getDebugMiddleware.js b/lib/middleware/getDebugMiddleware.js
index b1f70ae..63b2be4 100644
--- a/lib/middleware/getDebugMiddleware.js
+++ b/lib/middleware/getDebugMiddleware.js
@@ -4,12 +4,29 @@ import { debug } from '../utils/console'
* Creates a middleware to debug routes on an instance level
* @private
* @param instance
- * @return {function(*, *, *): *}
+ * @param options {object?} optional options
+ * @param options.description {string?} optional way to descrive the next handler
+ * @param options.data {boolean?} optional flag to log body/query
*/
-export const getDebugMiddleWare = instance => (req, res, next) => {
- if (instance.debug === true) {
+export const getDebugMiddleWare = (instance, options = {}) => {
+ if (!instance.debug) {
+ return function (req, res, next) { next() }
+ }
+
+ return function (req, res, next) {
const baseUrl = req.originalUrl.split('?')[0]
- debug(req.method, baseUrl, req.query || req.body)
+ let message = `${req.method} ${baseUrl}`
+
+ if (options.description) {
+ message = `${message} (${options.description})`
+ }
+
+ if (options.data) {
+ const data = { query: req.query, body: req.body }
+ message = `${message} data: ${data}`
+ }
+
+ debug(message)
+ next()
}
- return next()
}
diff --git a/lib/middleware/secureHandler.js b/lib/middleware/secureHandler.js
index 5adaad9..6f4ce57 100644
--- a/lib/middleware/secureHandler.js
+++ b/lib/middleware/secureHandler.js
@@ -8,11 +8,10 @@ import { bind } from '../utils/bind'
* @param handler
* @return {Function}
*/
-export const secureHandler = (self, handler) => bind(function (req, res, next) {
+export const secureHandler = (self, handler) => bind(async function (req, res, next) {
const that = this
-
try {
- handler.call(that, req, res, next)
+ return handler.call(that, req, res, next)
} catch (anyError) {
// to avoid server-crashes we wrap all request handlers and
// catch the error here, creating a default 500 response
diff --git a/lib/model/meteor-model.js b/lib/model/meteor-model.js
index 3cdadd9..ae43fe4 100644
--- a/lib/model/meteor-model.js
+++ b/lib/model/meteor-model.js
@@ -1,68 +1,76 @@
import { Random } from 'meteor/random'
-import { bind } from '../utils/bind'
export const collections = {
+ /** @type {Mongo.Collection} */
AccessTokens: undefined,
+ /** @type {Mongo.Collection} */
RefreshTokens: undefined,
+ /** @type {Mongo.Collection} */
Clients: undefined,
+ /** @type {Mongo.Collection} */
AuthCodes: undefined
}
/**
- * @private used by OAuthMeteorModel.prototype.getAccessToken
+ * used by OAuthMeteorModel.prototype.getAccessToken
+ * @private
*/
-export const getAccessToken = bind(function (bearerToken) {
- return collections.AccessTokens.findOne({ accessToken: bearerToken })
-})
+export const getAccessToken = async (accessToken) => {
+ return collections.AccessTokens.findOneAsync({ accessToken })
+}
/**
- * @private used by OAuthMeteorModel.prototype.createClient
+ * used by OAuthMeteorModel.prototype.createClient
+ * @private
*/
-export const createClient = bind(function ({ title, homepage, description, privacyLink, redirectUris, grants, clientId, secret }) {
- const existingClient = collections.Clients.findOne({ title })
+export const createClient = async ({ title, homepage, description, privacyLink, redirectUris, grants, clientId, secret }) => {
+ const existingClient = await collections.Clients.findOneAsync({ title })
if (existingClient) {
const updateValues = { description, privacyLink, redirectUris, grants }
if (clientId) updateValues.clientId = clientId
if (secret) updateValues.secret = secret
- return collections.Clients.update(existingClient._id, {
+ return collections.Clients.updateAsync(existingClient._id, {
$set: updateValues
})
}
-
- const clientDocId = collections.Clients.insert({
+ const id = clientId ?? Random.id(16)
+ const clientDocId = await collections.Clients.insertAsync({
title,
homepage,
description,
privacyLink,
redirectUris,
- clientId: clientId || Random.id(16),
+ clientId: id,
+ id, // required by oauth-2-server which
secret: secret || Random.id(32),
grants
})
- return collections.Clients.findOne(clientDocId)
-})
+ return collections.Clients.findOneAsync(clientDocId)
+}
/**
- * @private used by OAuthMeteorModel.prototype.getClient
+ * used by OAuthMeteorModel.prototype.getClient
+ * @private
*/
-export const getClient = bind(function (clientId, secret) {
- const clientDoc = collections.Clients.findOne({
+export const getClient = async (clientId, secret) => {
+ const clientDoc = await collections.Clients.findOneAsync({
clientId,
secret: secret || undefined // secret can be undefined or null but should act as the same
})
return clientDoc || false
-})
+}
/**
- * @private used by OAuthMeteorModel.prototype.saveToken
+ * used by OAuthMeteorModel.prototype.saveToken
+ * @private
*/
-export const saveToken = bind(function (tokenDoc, clientDoc, userDoc) {
- const tokenDocId = collections.AccessTokens.insert({
+export const saveToken = async (tokenDoc, clientDoc, userDoc) => {
+ const tokenDocId = await collections.AccessTokens.insertAsync({
accessToken: tokenDoc.accessToken,
accessTokenExpiresAt: tokenDoc.accessTokenExpiresAt,
refreshToken: tokenDoc.refreshToken,
@@ -75,78 +83,81 @@ export const saveToken = bind(function (tokenDoc, clientDoc, userDoc) {
id: userDoc.id
}
})
- return collections.AccessTokens.findOne(tokenDocId)
-})
+ return collections.AccessTokens.findOneAsync(tokenDocId)
+}
/**
- * @private used by OAuthMeteorModel.prototype.getAuthorizationCode
+ * used by OAuthMeteorModel.prototype.getAuthorizationCode
+ * @private
*/
-export const getAuthorizationCode = bind(function (authorizationCode) {
- return collections.AuthCodes.findOne({ authorizationCode })
-})
+export const getAuthorizationCode = async (authorizationCode) => {
+ return collections.AuthCodes.findOneAsync({ authorizationCode })
+}
/**
- * @private used by OAuthMeteorModel.prototype.saveAuthorizationCode
+ * used by OAuthMeteorModel.prototype.saveAuthorizationCode
+ * @private
*/
-export const saveAuthorizationCode = bind(function saveAuthCode (code, client, user) {
+export const saveAuthorizationCode = async (code, client, user) => {
const { authorizationCode } = code
const { expiresAt } = code
const { redirectUri } = code
- collections.AuthCodes.upsert({ authorizationCode }, {
+ await collections.AuthCodes.upsertAsync({ authorizationCode }, {
authorizationCode,
expiresAt,
redirectUri,
scope: code.scope,
client: {
- id: client.clientId
+ // xxx: fix for newer oauth2-server versions
+ id: client.id ?? client.clientId
},
user: {
id: user.id
}
})
- return collections.AuthCodes.findOne({ authorizationCode })
-})
+ return collections.AuthCodes.findOneAsync({ authorizationCode })
+}
/**
- * @private used by OAuthMeteorModel.prototype.revokeAuthorizationCode
+ * used by OAuthMeteorModel.prototype.revokeAuthorizationCode
+ * @private
*/
-export const revokeAuthorizationCode = bind(function revokeAuthorizationCode ({ authorizationCode }) {
- const docCount = collections.AuthCodes.find({ authorizationCode }).count()
+export const revokeAuthorizationCode = async ({ authorizationCode }) => {
+ const docCount = await collections.AuthCodes.countDocuments({ authorizationCode })
if (docCount === 0) {
return true
}
- return collections.AuthCodes.remove({ authorizationCode }) === docCount
-})
+ const removeCount = await collections.AuthCodes.removeAsync({ authorizationCode })
+ return removeCount === docCount
+}
/**
- * @private used by OAuthMeteorModel.prototype.saveRefreshToken
+ * used by OAuthMeteorModel.prototype.saveRefreshToken
+ * @private
*/
-export const saveRefreshToken = bind(function (token, clientId, expires, user) {
- return collections.RefreshTokens.insert({
+export const saveRefreshToken = async (token, clientId, expires, user) => {
+ return collections.RefreshTokens.insertAsync({
refreshToken: token,
clientId,
userId: user.id,
expires
})
-})
+}
/**
- * @private used by OAuthMeteorModel.prototype.getRefreshToken
+ * used by OAuthMeteorModel.prototype.getRefreshToken
+ * @private
*/
-export const getRefreshToken = bind(function (refreshToken) {
- return collections.AccessTokens.findOne({ refreshToken })
-})
-
-export const revokeToken = bind(function (token) {
- const docCount = collections.AccessTokens.find({ refreshToken: token.refreshToken }).count()
- if (docCount === 0) {
- return true
- }
+export const getRefreshToken = async (refreshToken) => {
+ return collections.RefreshTokens.findOneAsync({ refreshToken })
+}
- return collections.AccessTokens.remove({ refreshToken: token.refreshToken }) === docCount
-})
+export const revokeToken = async (token) => {
+ const result = await collections.AccessTokens.removeAsync({ refreshToken: token.refreshToken })
+ return !!result
+}
diff --git a/lib/model/model.js b/lib/model/model.js
index 8231e22..ff205d9 100644
--- a/lib/model/model.js
+++ b/lib/model/model.js
@@ -20,9 +20,7 @@ import {
class OAuthMeteorModel {
constructor (config = {}) {
const modelConfig = { ...DefaultModelConfig, ...config }
-
this.debug = modelConfig.debug
-
collections.AccessTokens = createCollection(modelConfig.accessTokensCollection, modelConfig.accessTokensCollectionName)
collections.RefreshTokens = createCollection(modelConfig.refreshTokensCollection, modelConfig.refreshTokensCollectionName)
collections.AuthCodes = createCollection(modelConfig.authCodesCollection, modelConfig.authCodesCollectionName)
@@ -36,7 +34,7 @@ class OAuthMeteorModel {
log (...args) {
if (this.debug === true) {
- console.log('[OAuth2Server][model]:', ...args)
+ console.debug('[OAuth2Server][model]:', ...args)
}
}
@@ -49,7 +47,7 @@ class OAuthMeteorModel {
user (Object)
*/
async getAccessToken (bearerToken) {
- this.log('getAccessToken (bearerToken:', bearerToken, ')')
+ this.log(`getAccessToken (bearerToken: '${bearerToken}')`)
return getAccessToken(bearerToken)
}
@@ -69,6 +67,7 @@ class OAuthMeteorModel {
async createClient ({ title, homepage, description, privacyLink, redirectUris, grants, clientId, secret }) {
this.log(`createClient (${redirectUris})`)
return createClient({
+ id: clientId, // xxx: fix for newer oauth2-server versions that explicitly check for .id presence
title,
homepage,
description,
@@ -87,7 +86,16 @@ class OAuthMeteorModel {
*/
async getClient (clientId, secret) {
this.log(`getClient (clientId: ${clientId}) (secret: ${secret})`)
- return getClient(clientId, secret)
+ const clientDoc = await getClient(clientId, secret)
+ if (!clientDoc) return clientDoc
+
+ // xxx: fixes compatibility with newer versions of oauth2-server
+ // which checks for the client.id value, instead of client.clientId
+ if (!clientDoc.id) {
+ clientDoc.id = clientDoc.clientId
+ }
+
+ return clientDoc
}
/**
@@ -176,6 +184,16 @@ class OAuthMeteorModel {
return ['authorization_code', 'refresh_token'].includes(grantType)
}
+ /**
+ * Compares expected scope from token with actual scope from request
+ * @param accessToken
+ * @param scope
+ * @return {Promise}
+ */
+ async verifyScope (accessToken, scope) {
+ return accessToken.scope.sort().join(',') === scope.sort().join(',')
+ }
+
/**
* revokeToken(refreshToken) is required and should return true
*/
diff --git a/lib/oauth.js b/lib/oauth.js
index e5e7930..28c49cf 100644
--- a/lib/oauth.js
+++ b/lib/oauth.js
@@ -2,9 +2,9 @@
import { Meteor } from 'meteor/meteor'
import { check } from 'meteor/check'
import { Accounts } from 'meteor/accounts-base'
+import * as Log from './utils/console'
// utils
-import { bind } from './utils/bind'
import { getDebugMiddleWare } from './middleware/getDebugMiddleware'
// model
@@ -41,7 +41,10 @@ const { Request, Response } = OAuthserver
* @return {function():Mongo.Cursor}
* @private
*/
-const publishAuthorizedClients = (pubName) => {
+const publishAuthorizedClients = (pubName, debug) => {
+ if (debug) {
+ Log.debug('publish authorized clients as', pubName)
+ }
return Meteor.publish(pubName, function () {
if (!this.userId) {
return this.ready()
@@ -61,13 +64,14 @@ export class OAuth2Server {
* @param model
* @param routes
* @param debug
+ * @param logError
* @return {OAuth2Server}
*/
- constructor ({ serverOptions = {}, model, routes, debug } = {}) {
+ constructor ({ serverOptions = {}, model, routes, debug, logError } = {}) {
check(serverOptions, OptionsSchema.serverOptions)
if (debug) {
- console.debug('[OAuth2Server]: create new instance')
- console.debug('[OAuth2Server]: serveroptions', serverOptions)
+ Log.debug('create new instance')
+ Log.debug('serveroptions', serverOptions)
}
this.instanceId = Random.id()
this.config = {
@@ -87,12 +91,13 @@ export class OAuth2Server {
this.app = app
this.debug = debug
+ this.logError = logError
const oauthOptions = Object.assign({ model: this.model }, serverOptions)
this.oauth = new OAuthserver(oauthOptions)
const authorizedPubName = (serverOptions && serverOptions.authorizedPublicationName) || 'authorizedOAuth'
- publishAuthorizedClients(authorizedPubName)
+ publishAuthorizedClients(authorizedPubName, this.debug)
initRoutes(this, routes)
return this
}
@@ -121,7 +126,7 @@ export class OAuth2Server {
* @param grants
* @param clientId
* @param secret
- * @returns {}
+ * @returns {object}
*/
async registerClient ({ title, homepage, description, privacyLink, redirectUris, grants, clientId, secret }) {
return this.model.createClient({
@@ -136,6 +141,9 @@ export class OAuth2Server {
})
}
+ /**
+ * @private
+ */
authorizeHandler (options) {
const self = this
return async function (req, res, next) {
@@ -144,15 +152,18 @@ export class OAuth2Server {
try {
const code = await self.oauth.authorize(request, response, options)
+ Log.debug('authorization code', code)
res.locals.oauth = { code: code }
next()
} catch (err) {
- res.writeHead(500)
- res.end(err)
+ res.status(500).json(err)
}
}
}
+ /**
+ * @private
+ */
authenticateHandler (options) {
const self = this
return async function (req, res, next) {
@@ -168,7 +179,8 @@ export class OAuth2Server {
status: err.status,
error: err.name,
description: err.message,
- debug: self.debug
+ debug: self.debug,
+ logError: self.logError
})
}
}
@@ -177,28 +189,36 @@ export class OAuth2Server {
/**
* Allows to create `get` or `post` routes, that are only
* accessible to authenticated users.
+ * @param options {object?} optional options
+ * @param options.scope {string} optional scope to check. Model must implement {verifyScope} if used!
* @return {{get:function, post:function}}
*/
- authenticatedRoute () {
+ authenticatedRoute (options = {}) {
const self = this
- const debugMiddleware = getDebugMiddleWare(self)
- const authHandler = self.authenticateHandler()
+ let authOptions
+ if (options.scope) {
+ authOptions = {
+ addAcceptedScopesHeader: true,
+ addAuthorizedScopesHeader: true,
+ scope: options.scope
+ }
+ }
+ const authHandler = self.authenticateHandler(authOptions)
return {
get (route, fn) {
- app.get(route, debugMiddleware)
- app.get(route, authHandler)
- app.get(route, secureHandler(self, fn))
+ app.get(route, authHandler, secureHandler(self, fn))
},
post (route, fn) {
- return app.post(route, debugMiddleware, authHandler, fn)
+ app.post(route, authHandler, secureHandler(self, fn))
}
}
}
}
-const initRoutes = (self, { accessTokenUrl = '/oauth/token', authorizeUrl = '/oauth/authorize', errorUrl = '/oauth/error', fallbackUrl = '/oauth/*' } = {}) => {
- const debugMiddleware = getDebugMiddleWare(self)
-
+const initRoutes = (self, {
+ accessTokenUrl = '/oauth/token',
+ authorizeUrl = '/oauth/authorize'
+} = {}) => {
const validateResponseType = (req, res) => {
const responseType = req.method.toLowerCase() === 'get'
? req.query.response_type
@@ -249,19 +269,11 @@ const initRoutes = (self, { accessTokenUrl = '/oauth/token', authorizeUrl = '/oa
return redirectUri
}
- const route = (method, url, handler) => {
- const targetFn = self.app[method]
- if (self.debug) {
- targetFn.call(self.app, url, debugMiddleware)
- }
-
- // we automatically bound any route
- // to ensure a functional fiber running
- // and to support Meteor and Mongo features
- targetFn.call(self.app, url, bind(function (req, res, next) {
+ const route = ({ method, url, description, handler }) => {
+ const wrapper = async function (req, res, next) {
const that = this
try {
- handler.call(that, req, res, next)
+ return handler.call(that, req, res, next)
} catch (unknownException) {
const state = req && req.query && req.query.state
errorHandler(res, {
@@ -273,7 +285,23 @@ const initRoutes = (self, { accessTokenUrl = '/oauth/token', authorizeUrl = '/oa
originalError: unknownException
})
}
- }))
+ }
+
+ const handlers = []
+
+ if (self.debug) {
+ const debugMiddleware = getDebugMiddleWare(self, { description })
+ handlers.push(debugMiddleware)
+ Log.debug('Create route', method, url)
+ }
+
+ handlers.push(wrapper)
+
+ switch (method) {
+ case 'get': return app.get(url, ...handlers)
+ case 'post': return app.post(url, ...handlers)
+ default: return app.use(url, ...handlers)
+ }
}
// STEP 1: VALIDATE CLIENT REQUEST
@@ -281,85 +309,95 @@ const initRoutes = (self, { accessTokenUrl = '/oauth/token', authorizeUrl = '/oa
// If there is something wrong with the syntax of the request, such as the redirect_uri or client_id is invalid,
// then it’s important not to redirect the user and instead you should show the error message directly.
// This is to avoid letting your authorization server be used as an open redirector.
- route('get', authorizeUrl, async function (req, res, next) {
- if (!validateParams(req.query, requiredAuthorizeGetParams, self.debug)) {
- return errorHandler(res, {
- status: 400,
- error: 'invalid_request',
- description: 'One or more request parameters are invalid',
- state: req.query.state,
- debug: self.debug
- })
- }
+ route({
+ method: 'get',
+ url: authorizeUrl,
+ description: 'step 1 - validate initial request',
+ handler: async function (req, res, next) {
+ if (!validateParams(req.query, requiredAuthorizeGetParams, self.debug)) {
+ return errorHandler(res, {
+ status: 400,
+ error: 'invalid_request',
+ description: 'One or more request parameters are invalid',
+ state: req.query.state,
+ debug: self.debug
+ })
+ }
- const validResponseType = validateResponseType(req, res)
- if (!validResponseType) return
+ const validResponseType = validateResponseType(req, res)
+ if (!validResponseType) return res.end()
- const client = await getValidatedClient(req, res)
- if (!client) return
+ const client = await getValidatedClient(req, res)
+ if (!client) return res.end()
- const redirectUri = getValidatedRedirectUri(req, res, client)
- if (!redirectUri) return
+ const redirectUri = getValidatedRedirectUri(req, res, client)
+ if (!redirectUri) return res.end()
- return next()
+ next()
+ }
})
// STEP 2: ADD USER TO THE REQUEST
// validate all inputs again, since all inputs
- // could have been manipulated within form
- route('post', authorizeUrl, async function (req, res, next) {
- if (!validateParams(req.body, requiredAuthorizePostParams, self.debug)) {
- return errorHandler(res, {
- error: 'invalid_request',
- description: 'One or more request parameters are invalid',
- state: req.body.state,
- debug: self.debug,
- status: 400
- })
- }
+ // could have been manipulated within the form
+ route({
+ method: 'post',
+ url: authorizeUrl,
+ description: 'step 2 - add user to request',
+ handler: async function (req, res, next) {
+ if (!validateParams(req.body, requiredAuthorizePostParams, self.debug)) {
+ return errorHandler(res, {
+ error: 'invalid_request',
+ description: 'One or more request parameters are invalid',
+ state: req.body.state,
+ debug: self.debug,
+ status: 400
+ })
+ }
- const client = await getValidatedClient(req, res)
- if (!client) return
+ const client = await getValidatedClient(req, res)
+ if (!client) return
- const validRedirectUri = getValidatedRedirectUri(req, res, client)
- if (!validRedirectUri) return
+ const validRedirectUri = getValidatedRedirectUri(req, res, client)
+ if (!validRedirectUri) return
- // token refers here to the Meteor.loginToken,
- // which is assigned, once the user has been validly logged-in
- // only valid tokens can be used to find a user
- // in the Meteor.users collection
- const user = Meteor.users.findOne({
- 'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(req.body.token)
- })
+ // token refers here to the Meteor.loginToken,
+ // which is assigned, once the user has been validly logged-in
+ // only valid tokens can be used to find a user
+ // in the Meteor.users collection
+ const user = await Meteor.users.findOneAsync({
+ 'services.resume.loginTokens.hashedToken': Accounts._hashLoginToken(req.body.token)
+ })
- // we fail already here if no user has been found
- // since the oauth-node sever would repsond with a
- // 503 error, while it should be a 400
- const validateUserCredentials = { user, client }
+ // we fail already here if no user has been found
+ // since the oauth-node sever would repsond with a
+ // 503 error, while it should be a 400
+ const validateUserCredentials = { user, client }
- if (!user || !UserValidation.isValid(self, validateUserCredentials)) {
- return errorHandler(res, {
- status: 400,
- error: 'access_denied',
- description: 'You are no valid user',
- state: req.body.state,
- debug: self.debug
- })
- }
+ if (!user || !(await UserValidation.isValid(self, validateUserCredentials))) {
+ return errorHandler(res, {
+ status: 400,
+ error: 'access_denied',
+ description: 'You are no valid user',
+ state: req.body.state,
+ debug: self.debug
+ })
+ }
- const id = user._id
- req.user = { id } // TODO add fields from scope
+ const id = user._id
+ req.user = { id } // TODO add fields from scope
- if (req.body.allowed === 'false') {
- Meteor.users.update(id, { $pull: { 'oauth.authorizedClients': client.clientId } })
- } else {
- Meteor.users.update(id, { $addToSet: { 'oauth.authorizedClients': client.clientId } })
- }
+ const updateDoc = req.body.allowed === 'false'
+ ? { $pull: { 'oauth.authorizedClients': client.clientId } }
+ : { $addToSet: { 'oauth.authorizedClients': client.clientId } }
- // make this work on a post route
- req.query.allowed = req.body.allowed
+ await Meteor.users.updateAsync(id, updateDoc)
- return next()
+ // make this work on a post route
+ req.query.allowed = req.body.allowed
+
+ return next()
+ }
})
// STEP 3: GENERATE AUTHORIZATION CODE RESPONSE
@@ -367,39 +405,41 @@ const initRoutes = (self, { accessTokenUrl = '/oauth/token', authorizeUrl = '/oa
// - on allow, assign the client_id to the user's authorized clients
// - on deny, ...?
// - construct the redirect query and redirect to the redirect_uri
- route('post', authorizeUrl, async function (req, res /*, next */) {
- const request = new Request(req)
- const response = new Response(res)
- const authorizeOptions = {
- authenticateHandler: {
- handle: function (request, response) {
- return request.user
+ route({
+ method: 'post',
+ url: authorizeUrl,
+ description: 'step 3 - authorization code response',
+ handler: async function (req, res /*, next */) {
+ const request = new Request(req)
+ const response = new Response(res)
+ const authorizeOptions = {
+ authenticateHandler: {
+ handle: function (request, response) {
+ return request.user
+ }
}
}
- }
- try {
- const code = await self.oauth.authorize(request, response, authorizeOptions)
- const query = new URLSearchParams({
- code: code.authorizationCode,
- user: req.user.id,
- state: req.body.state
- })
+ try {
+ const code = await self.oauth.authorize(request, response, authorizeOptions)
+ const query = new URLSearchParams({
+ code: code.authorizationCode,
+ user: req.user.id,
+ state: req.body.state
+ })
- const finalRedirectUri = `${req.body.redirect_uri}?${query}`
-
- res.statusCode = 302
- res.setHeader('Location', finalRedirectUri)
- res.end()
- } catch (err) {
- errorHandler(res, {
- originalError: err,
- error: err.name,
- description: err.message,
- status: err.statusCode,
- state: req.body.state,
- debug: self.debug
- })
+ const finalRedirectUri = `${req.body.redirect_uri}?${query}`
+ res.redirect(302, finalRedirectUri)
+ } catch (err) {
+ errorHandler(res, {
+ originalError: err,
+ error: err.name,
+ description: err.message,
+ status: err.statusCode,
+ state: req.body.state,
+ debug: self.debug
+ })
+ }
}
})
@@ -407,50 +447,51 @@ const initRoutes = (self, { accessTokenUrl = '/oauth/token', authorizeUrl = '/oa
// - validate params
// - validate authorization code
// - issue accessToken and refreshToken
- route('post', accessTokenUrl, async function (req, res /*, next */) {
- if (!validateParams(req.body, req.body?.refresh_token ? requiredRefreshTokenPostParams : requiredAccessTokenPostParams, self.debug)) {
- return errorHandler(res, {
- status: 400,
- error: 'invalid_request',
- description: 'One or more request parameters are invalid',
- state: req.body.state,
- debug: self.debug
- })
- }
+ route({
+ method: 'post',
+ url: accessTokenUrl,
+ description: 'step 4 - generate access token response',
+ handler: async function (req, res /*, next */) {
+ if (!validateParams(req.body, req.body?.refresh_token ? requiredRefreshTokenPostParams : requiredAccessTokenPostParams, self.debug)) {
+ return errorHandler(res, {
+ status: 400,
+ error: 'invalid_request',
+ description: 'One or more request parameters are invalid',
+ state: req.body.state,
+ debug: self.debug
+ })
+ }
- const request = new Request(req)
- const response = new Response(res)
+ // XXX: conformity for the token endpoint
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
- try {
- const token = await self.oauth.token(request, response)
- res.writeHead(200, {
- 'Content-Type': 'application/json',
- 'Cache-Control': 'no-store',
- Pragma: 'no-cache'
- })
- const body = JSON.stringify({
- access_token: token.accessToken,
- token_type: 'bearer',
- expires_in: token.accessTokenExpiresAt,
- refresh_token: token.refreshToken
- })
- res.end(body)
- } catch (err) {
- return errorHandler(res, {
- error: 'unauthorized_client',
- description: err.message,
- state: req.body.state,
- debug: self.debug,
- status: err.statusCode
- })
- }
- })
+ const request = new Request(req)
+ const response = new Response(res)
- route('use', fallbackUrl, function (req, res, next) {
- return errorHandler(res, {
- error: 'route not found',
- status: 404,
- debug: self.debug
- })
+ try {
+ const token = await self.oauth.token(request, response)
+ res
+ .set({
+ 'Content-Type': 'application/json',
+ 'Cache-Control': 'no-store',
+ Pragma: 'no-cache'
+ })
+ .status(200)
+ .json({
+ access_token: token.accessToken,
+ token_type: 'bearer',
+ expires_in: token.accessTokenExpiresAt,
+ refresh_token: token.refreshToken
+ })
+ } catch (err) {
+ return errorHandler(res, {
+ error: 'unauthorized_client',
+ description: err.message,
+ state: req.body.state,
+ debug: self.debug,
+ status: err.statusCode
+ })
+ }
+ }
})
}
diff --git a/lib/utils/error.js b/lib/utils/error.js
index dd5d6da..4062bbc 100644
--- a/lib/utils/error.js
+++ b/lib/utils/error.js
@@ -6,6 +6,7 @@ import { error } from './console'
* @param res
* @param options {Object} options with error information
* @param options.error {String} Error name
+ * @param options.logError {boolean} optional flag to log the erroe to the console
* @param options.description {String} Error description
* @param options.uri {String?} Optional uri to redirect to when error occurs
* @param options.status {Number?} Optional statuscode, defaults to 500
@@ -17,22 +18,23 @@ import { error } from './console'
export const errorHandler = function (res, options) {
// { error, description, uri, status, state, debug, originalError }
const errCode = options.status || 500
- res.writeHead(errCode, { 'Content-Type': 'application/json' })
+ res.status(errCode)
+ res.set({ 'Content-Type': 'application/json' })
// by default we log the error that will be used as response
- error(`[error] ${errCode} - ${options.error} - ${options.description}`)
+ if (options.logError) {
+ error(`[error] ${errCode} - ${options.error} - ${options.description}`)
+ }
if (options.debug && options.originalError) {
error('[original error]:')
error(options.originalError)
}
- const body = JSON.stringify({
+ res.json({
error: options.error,
error_description: options.description,
error_uri: options.uri,
state: options.state
- }, null, 2)
-
- res.end(body)
+ })
}
diff --git a/lib/utils/isModelInterface.js b/lib/utils/isModelInterface.js
index e963e2f..728f57d 100644
--- a/lib/utils/isModelInterface.js
+++ b/lib/utils/isModelInterface.js
@@ -34,7 +34,7 @@ const modelNames = [
* @return {boolean} true if valid, otherwise false
*/
export const isModelInterface = model => {
- return model && Object.keys(model).some(property => {
- return modelNames.includes(property) && typeof model[property] === 'function'
+ return model && modelNames.every(name => {
+ return typeof model[name] === 'function'
})
}
diff --git a/lib/validation/UserValidation.js b/lib/validation/UserValidation.js
index 580b743..202a4ae 100644
--- a/lib/validation/UserValidation.js
+++ b/lib/validation/UserValidation.js
@@ -7,9 +7,16 @@ import { warn } from '../utils/console'
*/
export const UserValidation = {}
+/** @private */
const validationHandlers = new WeakMap()
-UserValidation.register = function (instance = {}, validationHandler) {
+/**
+ * Registers a validation method that allows
+ * to validate users on custom logic.
+ * @param instance {OAuth2Server}
+ * @param validationHandler {function} sync or async function that performs the validation
+ */
+UserValidation.register = function (instance, validationHandler) {
const instanceCheck = { instanceId: instance.instanceId }
check(instanceCheck, Match.ObjectIncluding({
instanceId: String
@@ -25,7 +32,7 @@ UserValidation.register = function (instance = {}, validationHandler) {
* @param handlerArgs {*}
* @return {*} should return truthy/falsy value
*/
-UserValidation.isValid = function (instance = {}, handlerArgs) {
+UserValidation.isValid = async function (instance, handlerArgs) {
// we assume, that if there is no validation handler registered
// then the developers intended to do so. However, we will print an info.
if (!validationHandlers.has(instance)) {
diff --git a/lib/validation/validateParams.js b/lib/validation/validateParams.js
index 36ad52c..c9ca4e5 100644
--- a/lib/validation/validateParams.js
+++ b/lib/validation/validateParams.js
@@ -1,5 +1,13 @@
import { check } from 'meteor/check'
+import { error } from '../utils/console'
+/**
+ * Abstraction that checks given query/body params against a given schema
+ * @param actualParams
+ * @param requiredParams
+ * @param debug
+ * @return {boolean}
+ */
export const validateParams = (actualParams, requiredParams, debug) => {
if (!actualParams || !requiredParams) {
return false
@@ -12,7 +20,7 @@ export const validateParams = (actualParams, requiredParams, debug) => {
return true
} catch (e) {
if (debug) {
- console.error(`[validation error]: key <${requiredParamKey}> => expected <${expected}>, got <${actual}>`)
+ error(`[validation error]: key <${requiredParamKey}> => expected <${expected}>, got <${actual}>`)
}
return false
}
diff --git a/lib/webapp.js b/lib/webapp.js
index 55da2fd..dbc1940 100644
--- a/lib/webapp.js
+++ b/lib/webapp.js
@@ -1,65 +1,12 @@
import { WebApp } from 'meteor/webapp'
-import { info } from './utils/console'
import bodyParser from 'body-parser'
-/**
- * @private
- */
-const server = WebApp.connectHandlers
-server.use(bodyParser.urlencoded({ extended: false }))
-
/**
* Wrapped `WebApp` with express-style get/post and default use routes.
* @see https://docs.meteor.com/packages/webapp.html
* @type {{get: get, post: post, use: use}}
*/
-const app = {
- /**
- * Creates a get route for a given handler
- * @param url {string}
- * @param handler {function}
- */
- get (url, handler) {
- server.use(url, function (req, res, next) {
- if (req.method.toLowerCase() === 'get') {
- handler.call(this, req, res, next)
- } else {
- next()
- }
- })
- },
- /**
- * Creates a post route for a given handler.
- * If headers' content-type does not equal to `application/x-www-form-urlencoded`
- * then it will be transformed accordingly.
- *
- * @param url {string}
- * @param handler {function}
- */
- post (url, handler) {
- server.use(url, function (req, res, next) {
- if (req.method.toLowerCase() === 'post') {
- if (req.headers['content-type'] !== 'application/x-www-form-urlencoded') {
- // Transforms requests which are POST and aren't "x-www-form-urlencoded" content type
- // and they pass the required information as query strings
- info('Transforming a request to form-urlencoded with the query going to the body.')
- req.headers['content-type'] = 'application/x-www-form-urlencoded'
- req.body = Object.assign({}, req.body, req.query)
- }
- handler.call(this, req, res, next)
- } else {
- next()
- }
- })
- },
-
- /**
- * Default wrapper around `WebApp.use`
- * @param args
- */
- use (...args) {
- server.use(...args)
- }
-}
+export const app = WebApp.handlers
-export { app }
+app.use(bodyParser.json())
+app.use(bodyParser.urlencoded({ extended: true }))
diff --git a/package.js b/package.js
index 7b217a6..eb0345f 100644
--- a/package.js
+++ b/package.js
@@ -1,37 +1,35 @@
/* eslint-env meteor */
Package.describe({
name: 'leaonline:oauth2-server',
- version: '5.1.0',
+ version: '6.0.0',
summary: 'Node OAuth2 Server (v4) with Meteor bindings',
git: 'https://github.com/leaonline/oauth2-server.git'
})
Package.onUse(function (api) {
- api.versionsFrom(['1.6', '2.3'])
- api.use('ecmascript@0.12.7')
+ api.versionsFrom(['3.0'])
+ api.use('ecmascript')
api.mainModule('lib/oauth.js', 'server')
})
Npm.depends({
- '@node-oauth/oauth2-server': '5.1.0',
- 'body-parser': '1.20.0'
+ '@node-oauth/oauth2-server': '5.2.0',
+ 'body-parser': '1.20.3'
})
Package.onTest(function (api) {
api.use([
- 'lmieulet:meteor-legacy-coverage',
- 'lmieulet:meteor-coverage@3.2.0',
- 'lmieulet:meteor-packages-coverage',
- 'meteortesting:mocha@2.0.0'
+ // FIXME: include, once we have a working coverage for Meteor 3
+ // 'lmieulet:meteor-legacy-coverage@0.4.0',
+ // 'lmieulet:meteor-coverage@4.3.0',
+ 'meteortesting:mocha@3.2.0'
])
api.use('ecmascript')
api.use('mongo')
api.use('jkuester:http@2.1.0')
- api.use('dburles:mongo-collection-instances')
- api.use('accounts-base@2.0.0')
- api.use('accounts-password@2.0.0')
- api.use('practicalmeteor:chai')
- // api.mainModule('oauth-tests.js', 'server')
+ api.use('dburles:mongo-collection-instances@1.0.0')
+ api.use('accounts-base')
+ api.use('accounts-password')
api.addFiles([
'tests/error-tests.js',
diff --git a/test-proxy/.meteor/packages b/test-proxy/.meteor/packages
index 166dd6f..c6915a7 100644
--- a/test-proxy/.meteor/packages
+++ b/test-proxy/.meteor/packages
@@ -4,16 +4,16 @@
# 'meteor add' and 'meteor remove' will edit this file for you,
# but you can also edit it by hand.
-meteor-base@1.5.1 # Packages every Meteor app needs to have
-mobile-experience@1.1.1 # Packages for a great mobile UX
-mongo@1.16.8 # The database Meteor supports right now
-static-html@1.3.2 # Define static page content in .html files
-reactive-var@1.0.12 # Reactive variable for tracker
-tracker@1.3.3 # Meteor's client-side reactive programming library
+meteor-base@1.5.2 # Packages every Meteor app needs to have
+mobile-experience@1.1.2 # Packages for a great mobile UX
+mongo@2.0.0 # The database Meteor supports right now
+static-html@1.3.3 # Define static page content in .html files
+reactive-var@1.0.13 # Reactive variable for tracker
+tracker@1.3.4 # Meteor's client-side reactive programming library
-standard-minifier-css@1.9.2 # CSS minifier run for production mode
-standard-minifier-js@2.8.1 # JS minifier run for production mode
-es5-shim@4.8.0 # ECMAScript 5 compatibility for older browsers
-ecmascript@0.16.8 # Enable ECMAScript2015+ syntax in app code
-typescript@4.9.5 # Enable TypeScript syntax in .ts and .tsx modules
-shell-server@0.5.0 # Server-side component of the `meteor shell` command
+standard-minifier-css@1.9.3 # CSS minifier run for production mode
+standard-minifier-js@3.0.0 # JS minifier run for production mode
+es5-shim@4.8.1 # ECMAScript 5 compatibility for older browsers
+ecmascript@0.16.9 # Enable ECMAScript2015+ syntax in app code
+typescript@5.4.3 # Enable TypeScript syntax in .ts and .tsx modules
+shell-server@0.6.0 # Server-side component of the `meteor shell` command
diff --git a/test-proxy/.meteor/release b/test-proxy/.meteor/release
index 966586c..508bc95 100644
--- a/test-proxy/.meteor/release
+++ b/test-proxy/.meteor/release
@@ -1 +1 @@
-METEOR@2.15
+METEOR@3.0.1
diff --git a/test-proxy/.meteor/versions b/test-proxy/.meteor/versions
index 304b1e5..9ffc0b4 100644
--- a/test-proxy/.meteor/versions
+++ b/test-proxy/.meteor/versions
@@ -1,68 +1,70 @@
-allow-deny@1.1.1
-autoupdate@1.8.0
-babel-compiler@7.10.5
-babel-runtime@1.5.1
-base64@1.0.12
-binary-heap@1.0.11
-blaze-tools@1.1.4
-boilerplate-generator@1.7.2
-caching-compiler@1.2.2
-caching-html-compiler@1.2.2
-callback-hook@1.5.1
-check@1.3.2
-ddp@1.4.1
-ddp-client@2.6.1
-ddp-common@1.4.0
-ddp-server@2.7.0
-diff-sequence@1.1.2
-dynamic-import@0.7.3
-ecmascript@0.16.8
-ecmascript-runtime@0.8.1
-ecmascript-runtime-client@0.12.1
-ecmascript-runtime-server@0.11.0
-ejson@1.1.3
-es5-shim@4.8.0
-fetch@0.1.4
-geojson-utils@1.0.11
-hot-code-push@1.0.4
-html-tools@1.1.4
-htmljs@1.2.0
-id-map@1.1.1
-inter-process-messaging@0.1.1
-launch-screen@2.0.0
-logging@1.3.3
-meteor@1.11.5
-meteor-base@1.5.1
-minifier-css@1.6.4
-minifier-js@2.7.5
-minimongo@1.9.3
-mobile-experience@1.1.1
-mobile-status-bar@1.1.0
-modern-browsers@0.1.10
-modules@0.20.0
-modules-runtime@0.13.1
-mongo@1.16.9
-mongo-decimal@0.1.3
-mongo-dev-server@1.1.0
-mongo-id@1.0.8
-npm-mongo@4.17.2
-ordered-dict@1.1.0
-promise@0.12.2
-random@1.2.1
-react-fast-refresh@0.2.8
-reactive-var@1.0.12
-reload@1.3.1
-retry@1.1.0
-routepolicy@1.1.1
-shell-server@0.5.0
-socket-stream-client@0.5.2
-spacebars-compiler@1.3.2
-standard-minifier-css@1.9.2
-standard-minifier-js@2.8.1
-static-html@1.3.2
-templating-tools@1.2.3
-tracker@1.3.3
-typescript@4.9.5
-underscore@1.6.1
-webapp@1.13.8
-webapp-hashing@1.1.1
+allow-deny@2.0.0
+autoupdate@2.0.0
+babel-compiler@7.11.0
+babel-runtime@1.5.2
+base64@1.0.13
+binary-heap@1.0.12
+blaze-tools@2.0.0
+boilerplate-generator@2.0.0
+caching-compiler@2.0.0
+caching-html-compiler@2.0.0
+callback-hook@1.6.0
+check@1.4.2
+core-runtime@1.0.0
+ddp@1.4.2
+ddp-client@3.0.0
+ddp-common@1.4.3
+ddp-server@3.0.0
+diff-sequence@1.1.3
+dynamic-import@0.7.4
+ecmascript@0.16.9
+ecmascript-runtime@0.8.2
+ecmascript-runtime-client@0.12.2
+ecmascript-runtime-server@0.11.1
+ejson@1.1.4
+es5-shim@4.8.1
+facts-base@1.0.2
+fetch@0.1.5
+geojson-utils@1.0.12
+hot-code-push@1.0.5
+html-tools@2.0.0
+htmljs@2.0.1
+id-map@1.2.0
+inter-process-messaging@0.1.2
+launch-screen@2.0.1
+logging@1.3.5
+meteor@2.0.0
+meteor-base@1.5.2
+minifier-css@2.0.0
+minifier-js@3.0.0
+minimongo@2.0.0
+mobile-experience@1.1.2
+mobile-status-bar@1.1.1
+modern-browsers@0.1.11
+modules@0.20.1
+modules-runtime@0.13.2
+mongo@2.0.0
+mongo-decimal@0.1.4-beta300.7
+mongo-dev-server@1.1.1
+mongo-id@1.0.9
+npm-mongo@4.17.3
+ordered-dict@1.2.0
+promise@1.0.0
+random@1.2.2
+react-fast-refresh@0.2.9
+reactive-var@1.0.13
+reload@1.3.2
+retry@1.1.1
+routepolicy@1.1.2
+shell-server@0.6.0
+socket-stream-client@0.5.3
+spacebars-compiler@2.0.0
+standard-minifier-css@1.9.3
+standard-minifier-js@3.0.0
+static-html@1.3.3
+templating-tools@2.0.0
+tracker@1.3.4
+typescript@5.4.3
+underscore@1.6.4
+webapp@2.0.0
+webapp-hashing@1.1.2
diff --git a/tests/error-tests.js b/tests/error-tests.js
index 054cd11..f168e95 100644
--- a/tests/error-tests.js
+++ b/tests/error-tests.js
@@ -1,17 +1,24 @@
/* eslint-env mocha */
import { Random } from 'meteor/random'
-import { assert } from 'meteor/practicalmeteor:chai'
+import { assert } from 'chai'
import { errorHandler } from '../lib/utils/error'
class Res {
- writeHead (httpStatus, options) {
+ status (httpStatus) {
this.httpStatus = httpStatus
+ }
+
+ set (options) {
this.options = options
}
- end (body) {
+ send (body) {
this.body = body
}
+
+ json (body) {
+ this.body = JSON.stringify(body)
+ }
}
describe('errorHandler', function () {
diff --git a/tests/model-tests.js b/tests/model-tests.js
index 4026c28..8ecd194 100644
--- a/tests/model-tests.js
+++ b/tests/model-tests.js
@@ -17,7 +17,7 @@ const GrantTypes = {
const assertCollection = name => {
const collection = Mongo.Collection.get(name)
assert.isDefined(collection)
- assert.equal(collection.constructor.name, 'Collection')
+ assert.instanceOf(collection, Mongo.Collection)
}
describe('model', function () {
@@ -33,11 +33,11 @@ describe('model', function () {
randomClientsName = Random.id()
})
- afterEach(function () {
- Mongo.Collection.get(DefaultModelConfig.clientsCollectionName).remove({})
- Mongo.Collection.get(DefaultModelConfig.accessTokensCollectionName).remove({})
- Mongo.Collection.get(DefaultModelConfig.refreshTokensCollectionName).remove({})
- Mongo.Collection.get(DefaultModelConfig.authCodesCollectionName).remove({})
+ afterEach(async () => {
+ await Mongo.Collection.get(DefaultModelConfig.clientsCollectionName).removeAsync({})
+ await Mongo.Collection.get(DefaultModelConfig.accessTokensCollectionName).removeAsync({})
+ await Mongo.Collection.get(DefaultModelConfig.refreshTokensCollectionName).removeAsync({})
+ await Mongo.Collection.get(DefaultModelConfig.authCodesCollectionName).removeAsync({})
})
describe('constructor', function () {
@@ -81,13 +81,13 @@ describe('model', function () {
})
describe('createClient', function () {
- it('creates a client with minimum required credentials', function () {
+ it('creates a client with minimum required credentials', async () => {
const model = new OAuthMeteorModel()
const title = Random.id()
const redirectUris = [Meteor.absoluteUrl(`/${Random.id()}`)]
const grants = [GrantTypes.authorization_code]
- const clientDocId = Promise.await(model.createClient({ title, redirectUris, grants }))
- const clientDoc = Mongo.Collection.get(DefaultModelConfig.clientsCollectionName).findOne(clientDocId)
+ const clientDocId = await (model.createClient({ title, redirectUris, grants }))
+ const clientDoc = await Mongo.Collection.get(DefaultModelConfig.clientsCollectionName).findOneAsync(clientDocId)
assert.isDefined(clientDoc)
assert.isDefined(clientDoc.clientId)
@@ -97,15 +97,15 @@ describe('model', function () {
assert.deepEqual(clientDoc.grants, grants)
})
- it('creates a client with an already given clientId and secret', function () {
+ it('creates a client with an already given clientId and secret', async () => {
const model = new OAuthMeteorModel()
const title = Random.id()
const clientId = Random.id(16)
const secret = Random.id(32)
const redirectUris = [Meteor.absoluteUrl(`/${Random.id()}`)]
const grants = [GrantTypes.authorization_code]
- const clientDocId = Promise.await(model.createClient({ title, redirectUris, grants, clientId, secret }))
- const clientDoc = Mongo.Collection.get(DefaultModelConfig.clientsCollectionName).findOne(clientDocId)
+ const clientDocId = await (model.createClient({ title, redirectUris, grants, clientId, secret }))
+ const clientDoc = await Mongo.Collection.get(DefaultModelConfig.clientsCollectionName).findOneAsync(clientDocId)
assert.isDefined(clientDoc)
assert.equal(clientDoc.clientId, clientId)
@@ -120,42 +120,42 @@ describe('model', function () {
let model
let clientDoc
- beforeEach(function () {
+ beforeEach(async () => {
model = new OAuthMeteorModel()
const title = Random.id()
const redirectUris = [Meteor.absoluteUrl(`/${Random.id()}`)]
const grants = [GrantTypes.authorization_code]
- const clientDocId = Promise.await(model.createClient({ title, redirectUris, grants }))
- clientDoc = Mongo.Collection.get(DefaultModelConfig.clientsCollectionName).findOne(clientDocId)
+ const clientDocId = await (model.createClient({ title, redirectUris, grants }))
+ clientDoc = await Mongo.Collection.get(DefaultModelConfig.clientsCollectionName).findOneAsync(clientDocId)
})
- it('returns a client by clientId', function () {
+ it('returns a client by clientId', async () => {
const { clientId } = clientDoc
- const actualClientDoc = Promise.await(model.getClient(clientId))
+ const actualClientDoc = await (model.getClient(clientId))
assert.deepEqual(actualClientDoc, clientDoc)
})
- it('returns a client on null secret', function () {
+ it('returns a client on null secret', async () => {
const { clientId } = clientDoc
- const actualClientDoc = Promise.await(model.getClient(clientId, null))
+ const actualClientDoc = await (model.getClient(clientId, null))
assert.deepEqual(actualClientDoc, clientDoc)
})
- it('returns false if no client is found', function () {
- const falsey = Promise.await(model.getClient(Random.id()))
+ it('returns false if no client is found', async () => {
+ const falsey = await (model.getClient(Random.id()))
assert.isFalse(falsey)
})
- it('returns a client by clientId and clientSecret', function () {
+ it('returns a client by clientId and clientSecret', async () => {
const { clientId } = clientDoc
const { secret } = clientDoc
- const actualClientDoc = Promise.await(model.getClient(clientId, secret))
+ const actualClientDoc = await (model.getClient(clientId, secret))
assert.deepEqual(actualClientDoc, clientDoc)
})
- it('returns false if clientSecret is incorrect', function () {
+ it('returns false if clientSecret is incorrect', async () => {
const { clientId } = clientDoc
- const falsey = Promise.await(model.getClient(clientId, Random.id()))
+ const falsey = await (model.getClient(clientId, Random.id()))
assert.isFalse(falsey)
})
})
@@ -196,7 +196,7 @@ describe('model', function () {
it('returns a saved token', async () => {
const collection = Mongo.Collection.get(DefaultModelConfig.accessTokensCollectionName)
const accessToken = Random.id()
- const docId = collection.insert({ accessToken })
+ const docId = await collection.insertAsync({ accessToken })
const tokenDoc = await model.getAccessToken(accessToken)
expect(tokenDoc).to.deep.equal({
_id: docId,
@@ -205,6 +205,44 @@ describe('model', function () {
})
})
+ describe('verifyScope', () => {
+ let model
+
+ beforeEach(function () {
+ model = new OAuthMeteorModel()
+ })
+
+ it('returns true if the access token scope meets the expected scope', async () => {
+ expect(await model.verifyScope({ scope: ['foo'] }, ['foo'])).to.equal(true)
+ expect(await model.verifyScope({ scope: ['foo'] }, ['foo', 'bar'])).to.equal(false)
+ expect(await model.verifyScope({ scope: ['foo'] }, [])).to.equal(false)
+ expect(await model.verifyScope({ scope: [] }, ['foo'])).to.equal(false)
+ expect(await model.verifyScope({ scope: ['foo', 'bar'] }, ['foo'])).to.equal(false)
+ })
+ })
+
+ describe('revokeRefreshToken', () => {
+ let model
+
+ beforeEach(function () {
+ model = new OAuthMeteorModel()
+ })
+
+ it('returns true if the refresh token was revoked', async () => {
+ const collection = Mongo.Collection.get(DefaultModelConfig.accessTokensCollectionName)
+ const refreshToken = Random.id()
+ await collection.insertAsync({ refreshToken })
+ const tokenDoc = await model.revokeToken({ refreshToken })
+ assert.isTrue(tokenDoc)
+ })
+
+ it('returns false if the refresh token was not found', async () => {
+ const refreshToken = Random.id()
+ const tokenDoc = await model.revokeToken({ refreshToken })
+ assert.isFalse(tokenDoc)
+ })
+ })
+
describe('saveAuthorizationCode', function () {
it('is not yet implemented')
})
diff --git a/tests/oauth-tests.js b/tests/oauth-tests.js
index c50a902..3cd7e23 100644
--- a/tests/oauth-tests.js
+++ b/tests/oauth-tests.js
@@ -1,7 +1,7 @@
/* eslint-env mocha */
import { Meteor } from 'meteor/meteor'
import { Mongo } from 'meteor/mongo'
-import { assert } from 'meteor/practicalmeteor:chai'
+import { assert } from 'chai'
import { Random } from 'meteor/random'
import { Accounts } from 'meteor/accounts-base'
import { HTTP } from 'meteor/jkuester:http'
@@ -47,13 +47,18 @@ describe('constructor', function () {
it('can be created with a custom model', function () {
const model = {
- getAccessToken: function () {
- return new Promise('works!')
- }
+ getAccessToken: async () => true,
+ getAuthorizationCode: async () => true,
+ getClient: async () => true,
+ getRefreshToken: async () => true,
+ revokeAuthorizationCode: async () => true,
+ saveAuthorizationCode: async () => true,
+ saveRefreshToken: async () => true,
+ saveToken: async () => true,
+ revokeToken: async () => true
}
const server = new OAuth2Server({ model })
assert.isDefined(server)
- console.debug(server.model)
assert.deepEqual(server.model, model)
})
@@ -79,31 +84,31 @@ describe('integration tests of OAuth2 workflows', function () {
const logErrors = false
const authCodeServer = new OAuth2Server({ debug, model: { debug }, routes })
- const get = (url, params, done, cb) => {
+ const get = (url, params, cb) => new Promise((resolve, reject) => {
const fullUrl = Meteor.absoluteUrl(url)
HTTP.get(fullUrl, params, (err, res) => {
- if (err && logErrors) console.error(err)
+ if (err && logErrors) return reject(err)
try {
cb(res)
- done()
+ resolve()
} catch (e) {
- done(e)
+ reject(e)
}
})
- }
+ })
- const post = (url, params, done, cb) => {
+ const post = (url, params, cb) => new Promise((resolve, reject) => {
const fullUrl = Meteor.absoluteUrl(url)
HTTP.post(fullUrl, params, (err, res) => {
- if (err && logErrors) console.error(err)
+ if (err && logErrors) return reject(err)
try {
cb(res)
- done()
+ resolve()
} catch (e) {
- done(e)
+ reject(e)
}
})
- }
+ })
let ClientCollection
let clientDoc
@@ -116,76 +121,76 @@ describe('integration tests of OAuth2 workflows', function () {
redirectUris: [Meteor.absoluteUrl(`/${Random.id()}`)],
grants: ['authorization_code']
})
- clientDoc = ClientCollection.findOne(clientDocId)
+ clientDoc = await ClientCollection.findOneAsync(clientDocId)
assert.isDefined(clientDoc)
// for the user we are faking the
// login token to simulare a user, that is
// currently logged in
- const userId = Accounts.createUser({ username: Random.id(), password: Random.id() })
+ const userId = await Accounts.createUserAsync({ username: Random.id(), password: Random.id() })
const token = Random.id()
const hashedToken = Accounts._hashLoginToken(token)
- Meteor.users.update(userId, {
+ await Meteor.users.updateAsync(userId, {
$set: {
token: token,
'services.resume.loginTokens.hashedToken': hashedToken
}
})
- user = Meteor.users.findOne(userId)
+ user = await Meteor.users.findOneAsync(userId)
})
describe('Authorization Request', function () {
- it('returns a valid response for a valid request', function (done) {
+ it('returns a valid response for a valid request', async () => {
const params = {
client_id: clientDoc.clientId,
response_type: 'code',
redirect_uri: clientDoc.redirectUris[0],
state: Random.id()
}
- get(routes.authorizeUrl, { params }, done, res => {
+ await get(routes.authorizeUrl, { params }, res => {
assert.equal(res.statusCode, 200)
assert.equal(res.data, null)
})
})
- it('returns an invalid_request error for invalid formed requests', function (done) {
+ it('returns an invalid_request error for invalid formed requests', async () => {
const params = { state: Random.id() }
- get(routes.authorizeUrl, { params }, done, (res) => {
+ await get(routes.authorizeUrl, { params }, (res) => {
assert.equal(res.statusCode, 400)
assert.equal(res.data.error, 'invalid_request')
assert.equal(res.data.state, params.state)
})
})
- it('returns unsupported_response_type if the response method is not supported by the server', function (done) {
+ it('returns unsupported_response_type if the response method is not supported by the server', async () => {
const params = {
client_id: clientDoc.clientId,
response_type: Random.id(),
redirect_uri: clientDoc.redirectUris[0],
state: Random.id()
}
- get(routes.authorizeUrl, { params }, done, res => {
+ await get(routes.authorizeUrl, { params }, res => {
assert.equal(res.statusCode, 415)
assert.equal(res.data.error, 'unsupported_response_type')
assert.equal(res.data.state, params.state)
})
})
- it('returns an unauthorized_client error for invalid clients', function (done) {
+ it('returns an unauthorized_client error for invalid clients', async () => {
const params = {
client_id: Random.id(),
response_type: 'code',
redirect_uri: clientDoc.redirectUris[0],
state: Random.id()
}
- get(routes.authorizeUrl, { params }, done, (res) => {
+ await get(routes.authorizeUrl, { params }, (res) => {
assert.equal(res.statusCode, 401)
assert.equal(res.data.error, 'unauthorized_client')
assert.equal(res.data.state, params.state)
})
})
- it('returns an invalid_request on invalid redirect_uri', function (done) {
+ it('returns an invalid_request on invalid redirect_uri', async () => {
const invalidRedirectUri = Meteor.absoluteUrl(`/${Random.id()}`)
const params = {
client_id: clientDoc.clientId,
@@ -193,7 +198,7 @@ describe('integration tests of OAuth2 workflows', function () {
redirect_uri: invalidRedirectUri,
state: Random.id()
}
- get(routes.authorizeUrl, { params }, done, (res) => {
+ await get(routes.authorizeUrl, { params }, (res) => {
assert.equal(res.statusCode, 400)
assert.equal(res.data.error, 'invalid_request')
assert.equal(res.data.error_description, `Invalid redirection uri ${invalidRedirectUri}`)
@@ -204,7 +209,7 @@ describe('integration tests of OAuth2 workflows', function () {
describe('Authorization Response', function () {
[true, false].forEach(followRedirects => {
- it(`issues an authorization code and delivers it to the client via redirect follow=${followRedirects}`, function (done) {
+ it(`issues an authorization code and delivers it to the client via redirect follow=${followRedirects}`, async () => {
const params = {
client_id: clientDoc.clientId,
response_type: 'code',
@@ -218,7 +223,7 @@ describe('integration tests of OAuth2 workflows', function () {
// redirect and expect a 200 repsonse or, if we don't follow,
// we expect a 302 response with location header, which can be used
// by the client to manually follow
- post(routes.authorizeUrl, { params, followRedirects }, done, res => {
+ await post(routes.authorizeUrl, { params, followRedirects }, res => {
if (followRedirects) {
assert.equal(res.statusCode, 200)
assert.equal(res.headers.location, undefined)
@@ -235,7 +240,7 @@ describe('integration tests of OAuth2 workflows', function () {
})
})
- it('returns an access_denied error when no user exists for the given token', function (done) {
+ it('returns an access_denied error when no user exists for the given token', async () => {
const params = {
client_id: clientDoc.clientId,
response_type: 'code',
@@ -244,14 +249,14 @@ describe('integration tests of OAuth2 workflows', function () {
token: Random.id(),
allowed: undefined
}
- post(routes.authorizeUrl, { params }, done, res => {
+ await post(routes.authorizeUrl, { params }, res => {
assert.equal(res.statusCode, 400)
assert.equal(res.data.error, 'access_denied')
assert.equal(res.data.state, params.state)
})
})
- it('returns an access_denied error when the user denied the request', function (done) {
+ it('returns an access_denied error when the user denied the request', async () => {
const params = {
client_id: clientDoc.clientId,
response_type: 'code',
@@ -260,7 +265,7 @@ describe('integration tests of OAuth2 workflows', function () {
token: user.token,
allowed: 'false'
}
- post(routes.authorizeUrl, { params }, done, res => {
+ await post(routes.authorizeUrl, { params }, res => {
assert.equal(res.statusCode, 400)
assert.equal(res.data.error, 'access_denied')
assert.equal(res.data.state, params.state)
@@ -269,25 +274,25 @@ describe('integration tests of OAuth2 workflows', function () {
})
describe('Access Token Request', function () {
- it('returns an invalid_request error on missing credentials', function (done) {
+ it('returns an invalid_request error on missing credentials', async () => {
const params = {
state: Random.id()
}
- post(routes.accessTokenUrl, { params }, done, res => {
+ await post(routes.accessTokenUrl, { params }, res => {
assert.equal(res.statusCode, 400)
assert.equal(res.data.error, 'invalid_request')
assert.equal(res.data.state, params.state)
})
})
- it('returns an invalid_request error if the redirect uri is not correct', function (done) {
+ it('returns an invalid_request error if the redirect uri is not correct', async function () {
const authorizationCode = Random.id()
const expiresAt = new Date(new Date().getTime() + 30000)
- authCodeServer.model.saveAuthorizationCode({
+ await authCodeServer.model.saveAuthorizationCode({
authorizationCode,
expiresAt,
redirectUri: clientDoc.redirectUris[0]
- }, { client_id: clientDoc.clientId }, { id: user._id })
+ }, clientDoc, { id: user._id })
const params = {
code: authorizationCode,
@@ -298,21 +303,21 @@ describe('integration tests of OAuth2 workflows', function () {
grant_type: 'authorization_code'
}
- post(routes.accessTokenUrl, { params }, done, res => {
+ await post(routes.accessTokenUrl, { params }, res => {
assert.equal(res.statusCode, 400)
assert.equal(res.data.error, 'unauthorized_client')
assert.equal(res.data.state, params.state)
})
})
- it('returns an unauthorized_client error when the client does not provide a secret', function (done) {
+ it('returns an unauthorized_client error when the client does not provide a secret', async () => {
const authorizationCode = Random.id()
const expiresAt = new Date(new Date().getTime() + 30000)
- authCodeServer.model.saveAuthorizationCode({
+ await authCodeServer.model.saveAuthorizationCode({
authorizationCode,
expiresAt,
redirectUri: clientDoc.redirectUris[0]
- }, {}, { id: user._id })
+ }, clientDoc, { id: user._id })
const params = {
code: authorizationCode,
@@ -323,7 +328,7 @@ describe('integration tests of OAuth2 workflows', function () {
grant_type: 'authorization_code'
}
- post(routes.accessTokenUrl, { params }, done, res => {
+ await post(routes.accessTokenUrl, { params }, res => {
assert.equal(res.statusCode, 400)
assert.equal(res.data.error, 'unauthorized_client')
assert.equal(res.data.error_description, 'Invalid client: client is invalid')
@@ -331,7 +336,7 @@ describe('integration tests of OAuth2 workflows', function () {
})
})
- it('returns an unauthorized_client error when the given code is not found', function (done) {
+ it('returns an unauthorized_client error when the given code is not found', async () => {
const params = {
code: Random.id(),
client_id: clientDoc.clientId,
@@ -341,7 +346,7 @@ describe('integration tests of OAuth2 workflows', function () {
grant_type: 'authorization_code'
}
- post(routes.accessTokenUrl, { params }, done, res => {
+ await post(routes.accessTokenUrl, { params }, res => {
assert.equal(res.statusCode, 400)
assert.equal(res.data.error, 'unauthorized_client')
assert.equal(res.data.error_description, 'Invalid grant: authorization code is invalid')
@@ -351,14 +356,16 @@ describe('integration tests of OAuth2 workflows', function () {
})
describe('Access Token Response', function () {
- it('issues an access token for a valid request', function (done) {
+ it('issues an access token for a valid request', async () => {
const authorizationCode = Random.id()
const expiresAt = new Date(new Date().getTime() + 30000)
- authCodeServer.model.saveAuthorizationCode({
+ const code = {
authorizationCode,
expiresAt,
redirectUri: clientDoc.redirectUris[0]
- }, {}, { id: user._id })
+ }
+
+ await authCodeServer.model.saveAuthorizationCode(code, clientDoc, { id: user._id })
const params = {
code: authorizationCode,
@@ -369,11 +376,11 @@ describe('integration tests of OAuth2 workflows', function () {
grant_type: 'authorization_code'
}
- post(routes.accessTokenUrl, { params }, done, res => {
+ await post(routes.accessTokenUrl, { params }, res => {
assert.equal(res.statusCode, 200)
const headers = res.headers
- assert.equal(headers['content-type'], 'application/json')
+ assert.equal(headers['content-type'].includes('application/json'), true)
assert.equal(headers['cache-control'], 'no-store')
assert.equal(headers.pragma, 'no-cache')
diff --git a/tests/test-helpers.tests.js b/tests/test-helpers.tests.js
index 3d6215f..be25f62 100644
--- a/tests/test-helpers.tests.js
+++ b/tests/test-helpers.tests.js
@@ -1,8 +1,8 @@
import { Mongo } from 'meteor/mongo'
-import { assert } from 'meteor/practicalmeteor:chai'
+import { assert } from 'chai'
export const assertCollection = name => {
const collection = Mongo.Collection.get(name)
assert.isDefined(collection)
- assert.equal(collection.constructor.name, 'Collection')
+ assert.instanceOf(collection, Mongo.Collection)
}
diff --git a/tests/validation-tests.js b/tests/validation-tests.js
index 2704c1f..feac2a8 100644
--- a/tests/validation-tests.js
+++ b/tests/validation-tests.js
@@ -35,7 +35,6 @@ describe('validation', function () {
})
describe(UserValidation.register.name, function () {
it('throws if key is not an instance with instanceId', function () {
- expect(() => UserValidation.register()).to.throw('Match error: Expected string, got undefined in field instanceId')
expect(() => UserValidation.register({})).to.throw('Match error: Expected string, got undefined in field instanceId')
})
it('throws if fct ist not a function', function () {
@@ -43,22 +42,22 @@ describe('validation', function () {
})
})
describe(UserValidation.isValid.name, function () {
- it('returns true if not registered (skips)', function () {
- const instance = { instanceId, debug: true }
- expect(UserValidation.isValid()).to.equal(true)
- expect(UserValidation.isValid(instance)).to.equal(true)
+ it('returns true if not registered (skips)', async function () {
+ const instance = { instanceId }
+ expect(await UserValidation.isValid({})).to.equal(true)
+ expect(await UserValidation.isValid(instance)).to.equal(true)
})
- it('returns true if registered and handler passes', function () {
- const instance = { instanceId, debug: true }
+ it('returns true if registered and handler passes', async function () {
+ const instance = { instanceId }
const handler = () => true
UserValidation.register(instance, handler)
- expect(UserValidation.isValid(instance)).to.equal(true)
+ expect(await UserValidation.isValid(instance)).to.equal(true)
})
- it('returns false if registered and handler denies', function () {
- const instance = { instanceId, debug: true }
+ it('returns false if registered and handler denies', async function () {
+ const instance = { instanceId }
const handler = () => false
UserValidation.register(instance, handler)
- expect(UserValidation.isValid(instance)).to.equal(false)
+ expect(await UserValidation.isValid(instance)).to.equal(false)
})
})
})
diff --git a/tests/webapp-tests.js b/tests/webapp-tests.js
index 72a83aa..b7a70d7 100644
--- a/tests/webapp-tests.js
+++ b/tests/webapp-tests.js
@@ -2,7 +2,7 @@
import { Meteor } from 'meteor/meteor'
import { HTTP } from 'meteor/jkuester:http'
import { Random } from 'meteor/random'
-import { assert } from 'meteor/practicalmeteor:chai'
+import { assert } from 'chai'
import { app } from '../lib/webapp'
const toUrl = path => Meteor.absoluteUrl(path)
@@ -70,22 +70,6 @@ describe('webapp', function () {
})
})
- it('transforms any request to application/x-www-form-urlencoded', function (done) {
- const route = Random.id()
- const url = toUrl(route)
-
- app.post(`/${route}`, function (req, res, next) {
- try {
- assert.equal(req.headers['content-type'], 'application/x-www-form-urlencoded')
- finish(res, done)
- } catch (e) {
- finish(res, done, e)
- }
- })
-
- HTTP.post(url)
- })
-
it('creates a POST route which is not reachable via GET request', function (done) {
const route = Random.id()
const url = toUrl(route)