Skip to content

Commit

Permalink
fix obs search geoprivacy spec; make sure v2 appOrUserJwtRequired che…
Browse files Browse the repository at this point in the history
…cks application jwt
  • Loading branch information
pleary committed Nov 24, 2020
1 parent 821800d commit 8eaa07e
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 39 deletions.
4 changes: 3 additions & 1 deletion config.js.travis
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,7 @@ module.exports = {
uploadsDir: "/tmp/",
tensorappURL: "http://localhost:6006"
},
debug: true
debug: true,
jwtSecret: "jwtSecret",
jwtApplicationSecret: "jwtApplicationSecret"
}
92 changes: 55 additions & 37 deletions lib/inaturalist_api_v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,42 @@ const InaturalistAPIV2 = class InaturalistAPIV2 {
next( );
};

static userJwtValidate = ( req, required ) => {
if ( !req.headers.authorization ) {
if ( required === "required" ) {
throw jwtMissingError;
}
return Promise.resolve( true );
}
const token = _.last( req.headers.authorization.split( /\s+/ ) );
return jwt.verify( token, config.jwtSecret || "secret",
{ algorithms: ["HS512"] }, ( err, payload ) => {
if ( required === "required" && err ) throw jwtInvalidError;
if ( payload && payload.user_id ) {
req.userSession = payload;
}
return Promise.resolve( true );
} );
}

static applicationJwtValidate = ( req, required ) => {
if ( !req.headers.authorization ) {
if ( required === "required" ) {
throw jwtMissingError;
}
return Promise.resolve( true );
}
const token = _.last( req.headers.authorization.split( /\s+/ ) );
return jwt.verify( token, config.jwtApplicationSecret || "secret",
{ algorithms: ["HS512"] }, ( err, payload ) => {
if ( required === "required" && err ) throw jwtInvalidError;
if ( payload && payload.application ) {
req.applicationSession = payload;
}
return Promise.resolve( true );
} );
}

static initializeOpenapi = inaturalistAPIExpressApp => {
// method override middleware must be defined before initializing openapi
InaturalistAPIV2.applyMethodOverrideMiddleware( inaturalistAPIExpressApp );
Expand All @@ -413,49 +449,31 @@ const InaturalistAPIV2 = class InaturalistAPIV2 {
paths: "./openapi/paths/v2",
promiseMode: true,
securityHandlers: {
userJwtRequired: req => {
if ( !req.headers.authorization ) throw jwtMissingError;
const token = _.last( req.headers.authorization.split( /\s+/ ) );
return jwt.verify( token, config.jwtSecret || "secret", { algorithms: ["HS512"] }, ( err, payload ) => {
if ( err ) throw jwtInvalidError;
if ( payload.user_id ) {
req.userSession = payload;
return Promise.resolve( true );
}
const jwtMissingUserError = new Error( "JWT does not specify a user" );
jwtMissingUserError.status = 401;
throw jwtMissingUserError;
} );
},
userJwtOptional: req => {
if ( !req.headers.authorization ) return Promise.resolve( true );
const token = _.last( req.headers.authorization.split( /\s+/ ) );
return jwt.verify( token, config.jwtSecret || "secret", { algorithms: ["HS512"] }, ( err, payload ) => {
if ( err ) throw jwtInvalidError;
if ( payload.user_id ) {
req.userSession = payload;
}
userJwtRequired: req => InaturalistAPIV2.userJwtValidate( req, "required" ).then( ( ) => {
if ( req.userSession ) {
return Promise.resolve( true );
} );
},
appOrUserJwtRequired: req => {
if ( !req.headers.authorization ) throw jwtMissingError;
const token = _.last( req.headers.authorization.split( /\s+/ ) );
return jwt.verify( token, config.jwtSecret || "secret", { algorithms: ["HS512"] }, ( err, payload ) => {
if ( err ) throw jwtInvalidError;
if ( payload.application ) {
req.applicationSession = payload;
return Promise.resolve( true );
}
if ( payload.user_id ) {
req.userSession = payload;
}
const jwtMissingUserError = new Error( "JWT does not specify a user" );
jwtMissingUserError.status = 401;
throw jwtMissingUserError;
} ).catch( err => {
throw err;
} ),
userJwtOptional: req => InaturalistAPIV2.userJwtValidate( req ).catch( err => {
throw err;
} ),
appOrUserJwtRequired: req => InaturalistAPIV2.userJwtValidate( req )
.then( ( ) => InaturalistAPIV2.applicationJwtValidate( req ) )
.then( ( ) => {
if ( req.userSession || req.applicationSession ) {
return Promise.resolve( true );
}
const jwtMissingContextError = new Error( "JWT does not specify an application or a user" );
jwtMissingContextError.status = 401;
throw jwtMissingContextError;
} );
}
} ).catch( err => {
throw err;
} )
},
consumesMiddleware: {
// TODO: custom coercion for JSON bodies?
Expand Down
10 changes: 9 additions & 1 deletion test/controllers/v1/observations_controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ const moment = require( "moment" );
const _ = require( "lodash" );
const { observations } = require( "inaturalistjs" );
const testHelper = require( "../../../lib/test_helper" );
const util = require( "../../../lib/util" );
const Observation = require( "../../../lib/models/observation" );
const Project = require( "../../../lib/models/project" );
const List = require( "../../../lib/models/list" );
Expand Down Expand Up @@ -667,7 +668,14 @@ describe( "ObservationsController", ( ) => {

it( "filters by geoprivacy open", async ( ) => {
const q = await Q( { geoprivacy: "open" } );
expect( q.inverse_filters ).to.eql( [{ exists: { field: "geoprivacy" } }] );
expect( q.filters ).to.eql( [{
bool: {
should: [
{ terms: { geoprivacy: ["open"] } },
{ bool: { must_not: { exists: { field: "geoprivacy" } } } }
]
}
}] );
} );

it( "filters by geoprivacy obscured_private", async ( ) => {
Expand Down

0 comments on commit 8eaa07e

Please sign in to comment.