diff --git a/.gitignore b/.gitignore index 5efd54d..099f639 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,4 @@ jspm_packages .vscode +package-lock.json diff --git a/config-default.json b/config-default.json index e1536fc..098041d 100644 --- a/config-default.json +++ b/config-default.json @@ -15,5 +15,6 @@ "cleanupTasksAfter": 3, "test": false, "testSkipOrthophotos": false, - "testSkipDems": false + "testSkipDems": false, + "token": "" } \ No newline at end of file diff --git a/config.js b/config.js index 27416c7..a56b3b6 100644 --- a/config.js +++ b/config.js @@ -36,7 +36,8 @@ Options: --test Enable test mode. In test mode, no commands are sent to OpenDroneMap. This can be useful during development or testing (default: false) --test_skip_orthophotos If test mode is enabled, skip orthophoto results when generating assets. (default: false) --test_skip_dems If test mode is enabled, skip dems results when generating assets. (default: false) - --powercycle When set, the application exits immediately after powering up. Useful for testing launch and compilation issues. + --powercycle When set, the application exits immediately after powering up. Useful for testing launch and compilation issues. + --token Sets a token that needs to be passed for every request. This can be used to limit access to the node only to token holders. (default: none) Log Levels: error | debug | info | verbose | debug | silly `); @@ -84,5 +85,6 @@ config.test = argv.test || fromConfigFile("test", false); config.testSkipOrthophotos = argv.test_skip_orthophotos || fromConfigFile("testSkipOrthophotos", false); config.testSkipDems = argv.test_skip_dems || fromConfigFile("testSkipDems", false); config.powercycle = argv.powercycle || fromConfigFile("powercycle", false); +config.token = argv.token || fromConfigFile("token", ""); module.exports = config; diff --git a/docs/index.adoc b/docs/index.adoc index cd2413d..8387b87 100644 --- a/docs/index.adoc +++ b/docs/index.adoc @@ -8,7 +8,7 @@ REST API to access OpenDroneMap === Version information [%hardbreaks] -_Version_ : 1.0.4 +_Version_ : 1.1.0 === Contact information @@ -127,6 +127,8 @@ Cancels a task (stops its execution, or prevents it from being executed) [options="header", cols=".^2,.^3,.^9,.^4,.^2"] |=== |Type|Name|Description|Schema|Default +|*Query*|*token* + +_optional_|Token required for authentication (when authentication is required).|string| |*Body*|*uuid* + _required_|UUID of the task|string| |=== @@ -153,6 +155,8 @@ Creates a new task and places it at the end of the processing queue [options="header", cols=".^2,.^3,.^9,.^4,.^2"] |=== |Type|Name|Description|Schema|Default +|*Query*|*token* + +_optional_|Token required for authentication (when authentication is required).|string| |*FormData*|*images* + _optional_|Images to process, plus an optional GPC file. If included, the GPC file should have .txt extension|file| |*FormData*|*name* + @@ -206,6 +210,8 @@ Removes a task and deletes all of its assets [options="header", cols=".^2,.^3,.^9,.^4,.^2"] |=== |Type|Name|Description|Schema|Default +|*Query*|*token* + +_optional_|Token required for authentication (when authentication is required).|string| |*Body*|*uuid* + _required_|UUID of the task|string| |=== @@ -232,6 +238,8 @@ Restarts a task that was previously canceled, that had failed to process or that [options="header", cols=".^2,.^3,.^9,.^4,.^2"] |=== |Type|Name|Description|Schema|Default +|*Query*|*token* + +_optional_|Token required for authentication (when authentication is required).|string| |*Body*|*options* + _optional_|Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, …]. For example, [{"name":"cmvs-maxImages","value":"500"},{"name":"time","value":true}]. For a list of all options, call /options. Overrides the previous options set for this task.|string| |*Body*|*uuid* + @@ -264,6 +272,8 @@ Retrieves an asset (the output of OpenDroneMap's processing) associated with a t _required_|Type of asset to download. Use "all.zip" for zip file containing all assets.|enum (all.zip, orthophoto.tif)| |*Path*|*uuid* + _required_|UUID of the task|string| +|*Query*|*token* + +_optional_|Token required for authentication (when authentication is required).|string| |=== @@ -301,6 +311,10 @@ Gets information about this task, such as name, creation date, processing time, |Type|Name|Description|Schema|Default |*Path*|*uuid* + _required_|UUID of the task|string| +|*Query*|*token* + +_optional_|Token required for authentication (when authentication is required).|string| +|*FormData*|*options* + +_optional_|Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, …]. For example, [{"name":"cmvs-maxImages","value":"500"},{"name":"time","value":true}]. For a list of all options, call /options|string| |=== @@ -369,6 +383,8 @@ Retrieves the console output of the OpenDroneMap's process. Useful for monitorin _required_|UUID of the task|string| |*Query*|*line* + _optional_|Optional line number that the console output should be truncated from. For example, passing a value of 100 will retrieve the console output starting from line 100. Defaults to 0 (retrieve all console output).|integer|`"0"` +|*Query*|*token* + +_optional_|Token required for authentication (when authentication is required).|string| |=== diff --git a/docs/swagger.json b/docs/swagger.json index 4074542..d61f7a6 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -1 +1 @@ -{"info":{"title":"node-opendronemap","version":"1.0.3","description":"REST API to access OpenDroneMap","license":{"name":"GPL-3.0"},"contact":{"name":"Piero Toffanin"}},"consumes":["application/json"],"produces":["application/json","application/zip"],"basePath":"/","schemes":["http"],"swagger":"2.0","paths":{"/task/new":{"post":{"description":"Creates a new task and places it at the end of the processing queue","tags":["task"],"consumes":["multipart/form-data"],"parameters":[{"name":"images","in":"formData","description":"Images to process, plus an optional GPC file. If included, the GPC file should have .txt extension","required":false,"type":"file"},{"name":"zipurl","in":"formData","description":"URL of the zip file containing the images to process, plus an optional GPC file. If included, the GPC file should have .txt extension","required":false,"type":"string"},{"name":"name","in":"formData","description":"An optional name to be associated with the task","required":false,"type":"string"},{"name":"options","in":"formData","description":"Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, ...]. For example, [{\"name\":\"cmvs-maxImages\",\"value\":\"500\"},{\"name\":\"time\",\"value\":true}]. For a list of all options, call /options","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"object","required":["uuid"],"properties":{"uuid":{"type":"string","description":"UUID of the newly created task"}}}},"default":{"description":"Error","schema":{"$ref":"#/definitions/Error"}}}}},"/task/{uuid}/info":{"get":{"description":"Gets information about this task, such as name, creation date, processing time, status, command line options and number of images being processed. See schema definition for a full list.","tags":["task"],"parameters":[{"name":"uuid","in":"path","description":"UUID of the task","required":true,"type":"string"}],"responses":{"200":{"description":"Task Information","schema":{"title":"TaskInfo","type":"object","required":["uuid","name","dateCreated","processingTime","status","options","imagesCount"],"properties":{"uuid":{"type":"string","description":"UUID"},"name":{"type":"string","description":"Name"},"dateCreated":{"type":"integer","description":"Timestamp"},"processingTime":{"type":"integer","description":"Milliseconds that have elapsed since the task started being processed."},"status":{"type":"integer","description":"Status code (10 = QUEUED, 20 = RUNNING, 30 = FAILED, 40 = COMPLETED, 50 = CANCELED)","enum":[10,20,30,40,50]},"options":{"type":"array","description":"List of options used to process this task","items":{"type":"object","required":["name","value"],"properties":{"name":{"type":"string","description":"Option name (example: \"odm_meshing-octreeDepth\")"},"value":{"type":"string","description":"Value (example: 9)"}}}},"imagesCount":{"type":"integer","description":"Number of images"}}}},"default":{"description":"Error","schema":{"$ref":"#/definitions/Error"}}}}},"/task/{uuid}/output":{"get":{"description":"Retrieves the console output of the OpenDroneMap's process. Useful for monitoring execution and to provide updates to the user.","tags":["task"],"parameters":[{"name":"uuid","in":"path","description":"UUID of the task","required":true,"type":"string"},{"name":"line","in":"query","description":"Optional line number that the console output should be truncated from. For example, passing a value of 100 will retrieve the console output starting from line 100. Defaults to 0 (retrieve all console output).","default":0,"required":false,"type":"integer"}],"responses":{"200":{"description":"Console Output","schema":{"type":"string"}},"default":{"description":"Error","schema":{"$ref":"#/definitions/Error"}}}}},"/task/{uuid}/download/{asset}":{"get":{"description":"Retrieves an asset (the output of OpenDroneMap's processing) associated with a task","tags":["task"],"produces":["application/zip"],"parameters":[{"name":"uuid","in":"path","type":"string","description":"UUID of the task","required":true},{"name":"asset","in":"path","type":"string","description":"Type of asset to download. Use \"all.zip\" for zip file containing all assets.","required":true,"enum":["all.zip","orthophoto.tif"]}],"responses":{"200":{"description":"Asset File","schema":{"type":"file"}},"default":{"description":"Error message","schema":{"$ref":"#/definitions/Error"}}}}},"/task/cancel":{"post":{"description":"Cancels a task (stops its execution, or prevents it from being executed)","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/task/remove":{"post":{"description":"Removes a task and deletes all of its assets","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/task/restart":{"post":{"description":"Restarts a task that was previously canceled, that had failed to process or that successfully completed","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}},{"name":"options","in":"body","description":"Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, ...]. For example, [{\"name\":\"cmvs-maxImages\",\"value\":\"500\"},{\"name\":\"time\",\"value\":true}]. For a list of all options, call /options. Overrides the previous options set for this task.","required":false,"schema":{"type":"string"}}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/options":{"get":{"description":"Retrieves the command line options that can be passed to process a task","tags":["server"],"responses":{"200":{"description":"Options","schema":{"type":"array","items":{"title":"Option","type":"object","required":["name","type","value","domain","help"],"properties":{"name":{"type":"string","description":"Command line option (exactly as it is passed to the OpenDroneMap process, minus the leading '--')"},"type":{"type":"string","description":"Datatype of the value of this option","enum":["int","float","string","bool"]},"value":{"type":"string","description":"Default value of this option"},"domain":{"type":"string","description":"Valid range of values (for example, \"positive integer\" or \"float > 0.0\")"},"help":{"type":"string","description":"Description of what this option does"}}}}}}}},"/info":{"get":{"description":"Retrieves information about this node","tags":["server"],"responses":{"200":{"description":"Info","schema":{"type":"object","required":["version","taskQueueCount"],"properties":{"version":{"type":"string","description":"Current version"},"taskQueueCount":{"type":"integer","description":"Number of tasks currently being processed or waiting to be processed"}}}}}}}},"definitions":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Description of the error"}}},"Response":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"true if the command succeeded, false otherwise"},"error":{"type":"string","description":"Error message if an error occured"}}}},"responses":{},"parameters":{},"securityDefinitions":{},"tags":[]} \ No newline at end of file +{"info":{"title":"node-opendronemap","version":"1.1.0","description":"REST API to access OpenDroneMap","license":{"name":"GPL-3.0"},"contact":{"name":"Piero Toffanin"}},"consumes":["application/json"],"produces":["application/json","application/zip"],"basePath":"/","schemes":["http"],"swagger":"2.0","paths":{"/task/new":{"post":{"description":"Creates a new task and places it at the end of the processing queue","tags":["task"],"consumes":["multipart/form-data"],"parameters":[{"name":"images","in":"formData","description":"Images to process, plus an optional GPC file. If included, the GPC file should have .txt extension","required":false,"type":"file"},{"name":"zipurl","in":"formData","description":"URL of the zip file containing the images to process, plus an optional GPC file. If included, the GPC file should have .txt extension","required":false,"type":"string"},{"name":"name","in":"formData","description":"An optional name to be associated with the task","required":false,"type":"string"},{"name":"options","in":"formData","description":"Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, ...]. For example, [{\"name\":\"cmvs-maxImages\",\"value\":\"500\"},{\"name\":\"time\",\"value\":true}]. For a list of all options, call /options","required":false,"type":"string"},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Success","schema":{"type":"object","required":["uuid"],"properties":{"uuid":{"type":"string","description":"UUID of the newly created task"}}}},"default":{"description":"Error","schema":{"$ref":"#/definitions/Error"}}}}},"/task/{uuid}/info":{"get":{"description":"Gets information about this task, such as name, creation date, processing time, status, command line options and number of images being processed. See schema definition for a full list.","tags":["task"],"parameters":[{"name":"uuid","in":"path","description":"UUID of the task","required":true,"type":"string"},{"name":"options","in":"formData","description":"Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, ...]. For example, [{\"name\":\"cmvs-maxImages\",\"value\":\"500\"},{\"name\":\"time\",\"value\":true}]. For a list of all options, call /options","required":false,"type":"string"},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Task Information","schema":{"title":"TaskInfo","type":"object","required":["uuid","name","dateCreated","processingTime","status","options","imagesCount"],"properties":{"uuid":{"type":"string","description":"UUID"},"name":{"type":"string","description":"Name"},"dateCreated":{"type":"integer","description":"Timestamp"},"processingTime":{"type":"integer","description":"Milliseconds that have elapsed since the task started being processed."},"status":{"type":"integer","description":"Status code (10 = QUEUED, 20 = RUNNING, 30 = FAILED, 40 = COMPLETED, 50 = CANCELED)","enum":[10,20,30,40,50]},"options":{"type":"array","description":"List of options used to process this task","items":{"type":"object","required":["name","value"],"properties":{"name":{"type":"string","description":"Option name (example: \"odm_meshing-octreeDepth\")"},"value":{"type":"string","description":"Value (example: 9)"}}}},"imagesCount":{"type":"integer","description":"Number of images"}}}},"default":{"description":"Error","schema":{"$ref":"#/definitions/Error"}}}}},"/task/{uuid}/output":{"get":{"description":"Retrieves the console output of the OpenDroneMap's process. Useful for monitoring execution and to provide updates to the user.","tags":["task"],"parameters":[{"name":"uuid","in":"path","description":"UUID of the task","required":true,"type":"string"},{"name":"line","in":"query","description":"Optional line number that the console output should be truncated from. For example, passing a value of 100 will retrieve the console output starting from line 100. Defaults to 0 (retrieve all console output).","default":0,"required":false,"type":"integer"},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Console Output","schema":{"type":"string"}},"default":{"description":"Error","schema":{"$ref":"#/definitions/Error"}}}}},"/task/{uuid}/download/{asset}":{"get":{"description":"Retrieves an asset (the output of OpenDroneMap's processing) associated with a task","tags":["task"],"produces":["application/zip"],"parameters":[{"name":"uuid","in":"path","type":"string","description":"UUID of the task","required":true},{"name":"asset","in":"path","type":"string","description":"Type of asset to download. Use \"all.zip\" for zip file containing all assets.","required":true,"enum":["all.zip","orthophoto.tif"]},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Asset File","schema":{"type":"file"}},"default":{"description":"Error message","schema":{"$ref":"#/definitions/Error"}}}}},"/task/cancel":{"post":{"description":"Cancels a task (stops its execution, or prevents it from being executed)","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/task/remove":{"post":{"description":"Removes a task and deletes all of its assets","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/task/restart":{"post":{"description":"Restarts a task that was previously canceled, that had failed to process or that successfully completed","parameters":[{"name":"uuid","in":"body","description":"UUID of the task","required":true,"schema":{"type":"string"}},{"name":"options","in":"body","description":"Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, ...]. For example, [{\"name\":\"cmvs-maxImages\",\"value\":\"500\"},{\"name\":\"time\",\"value\":true}]. For a list of all options, call /options. Overrides the previous options set for this task.","required":false,"schema":{"type":"string"}},{"name":"token","in":"query","description":"Token required for authentication (when authentication is required).","required":false,"type":"string"}],"responses":{"200":{"description":"Command Received","schema":{"$ref":"#/definitions/Response"}}}}},"/options":{"get":{"description":"Retrieves the command line options that can be passed to process a task","tags":["server"],"responses":{"200":{"description":"Options","schema":{"type":"array","items":{"title":"Option","type":"object","required":["name","type","value","domain","help"],"properties":{"name":{"type":"string","description":"Command line option (exactly as it is passed to the OpenDroneMap process, minus the leading '--')"},"type":{"type":"string","description":"Datatype of the value of this option","enum":["int","float","string","bool"]},"value":{"type":"string","description":"Default value of this option"},"domain":{"type":"string","description":"Valid range of values (for example, \"positive integer\" or \"float > 0.0\")"},"help":{"type":"string","description":"Description of what this option does"}}}}}}}},"/info":{"get":{"description":"Retrieves information about this node","tags":["server"],"responses":{"200":{"description":"Info","schema":{"type":"object","required":["version","taskQueueCount"],"properties":{"version":{"type":"string","description":"Current version"},"taskQueueCount":{"type":"integer","description":"Number of tasks currently being processed or waiting to be processed"}}}}}}}},"definitions":{"Error":{"type":"object","required":["error"],"properties":{"error":{"type":"string","description":"Description of the error"}}},"Response":{"type":"object","required":["success"],"properties":{"success":{"type":"boolean","description":"true if the command succeeded, false otherwise"},"error":{"type":"string","description":"Error message if an error occured"}}}},"responses":{},"parameters":{},"securityDefinitions":{},"tags":[]} \ No newline at end of file diff --git a/index.js b/index.js index ea0a27c..477ea88 100644 --- a/index.js +++ b/index.js @@ -40,6 +40,9 @@ let odmOptions = require('./libs/odmOptions'); let Directories = require('./libs/Directories'); let unzip = require('node-unzip-2'); +let auth = require('./libs/auth/factory').fromConfig(config); +const authCheck = auth.getMiddleware(); + // zip files let request = require('request'); @@ -107,18 +110,24 @@ let server; * description: URL of the zip file containing the images to process, plus an optional GPC file. If included, the GPC file should have .txt extension * required: false * type: string - * - + * - * name: name * in: formData * description: An optional name to be associated with the task * required: false * type: string - * - + * - * name: options * in: formData * description: 'Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, ...]. For example, [{"name":"cmvs-maxImages","value":"500"},{"name":"time","value":true}]. For a list of all options, call /options' * required: false * type: string + * - + * name: token + * in: query + * description: 'Token required for authentication (when authentication is required).' + * required: false + * type: string * responses: * 200: * description: Success @@ -134,7 +143,7 @@ let server; * schema: * $ref: '#/definitions/Error' */ -app.post('/task/new', addRequestId, upload.array('images'), (req, res) => { +app.post('/task/new', authCheck, addRequestId, upload.array('images'), (req, res) => { if ((!req.files || req.files.length === 0) && !req.body.zipurl) res.json({ error: "Need at least 1 file or a zip file url." }); @@ -270,6 +279,18 @@ let getTaskFromUuid = (req, res, next) => { * description: UUID of the task * required: true * type: string + * - + * name: options + * in: formData + * description: 'Serialized JSON string of the options to use for processing, as an array of the format: [{name: option1, value: value1}, {name: option2, value: value2}, ...]. For example, [{"name":"cmvs-maxImages","value":"500"},{"name":"time","value":true}]. For a list of all options, call /options' + * required: false + * type: string + * - + * name: token + * in: query + * description: 'Token required for authentication (when authentication is required).' + * required: false + * type: string * responses: * 200: * description: Task Information @@ -315,7 +336,7 @@ let getTaskFromUuid = (req, res, next) => { * schema: * $ref: '#/definitions/Error' */ -app.get('/task/:uuid/info', getTaskFromUuid, (req, res) => { +app.get('/task/:uuid/info', authCheck, getTaskFromUuid, (req, res) => { res.json(req.task.getInfo()); }); @@ -338,6 +359,12 @@ app.get('/task/:uuid/info', getTaskFromUuid, (req, res) => { * default: 0 * required: false * type: integer + * - + * name: token + * in: query + * description: 'Token required for authentication (when authentication is required).' + * required: false + * type: string * responses: * 200: * description: Console Output @@ -348,7 +375,7 @@ app.get('/task/:uuid/info', getTaskFromUuid, (req, res) => { * schema: * $ref: '#/definitions/Error' */ -app.get('/task/:uuid/output', getTaskFromUuid, (req, res) => { +app.get('/task/:uuid/output', authCheck, getTaskFromUuid, (req, res) => { res.json(req.task.getOutput(req.query.line)); }); @@ -372,6 +399,12 @@ app.get('/task/:uuid/output', getTaskFromUuid, (req, res) => { * enum: * - all.zip * - orthophoto.tif + * - + * name: token + * in: query + * description: 'Token required for authentication (when authentication is required).' + * required: false + * type: string * responses: * 200: * description: Asset File @@ -382,7 +415,7 @@ app.get('/task/:uuid/output', getTaskFromUuid, (req, res) => { * schema: * $ref: '#/definitions/Error' */ -app.get('/task/:uuid/download/:asset', getTaskFromUuid, (req, res) => { +app.get('/task/:uuid/download/:asset', authCheck, getTaskFromUuid, (req, res) => { let asset = req.params.asset !== undefined ? req.params.asset : "all.zip"; let filePath = req.task.getAssetsArchivePath(asset); if (filePath) { @@ -447,13 +480,19 @@ let successHandler = res => { * required: true * schema: * type: string + * - + * name: token + * in: query + * description: 'Token required for authentication (when authentication is required).' + * required: false + * type: string * responses: * 200: * description: Command Received * schema: * $ref: "#/definitions/Response" */ -app.post('/task/cancel', uuidCheck, (req, res) => { +app.post('/task/cancel', authCheck, uuidCheck, (req, res) => { taskManager.cancel(req.body.uuid, successHandler(res)); }); @@ -469,13 +508,19 @@ app.post('/task/cancel', uuidCheck, (req, res) => { * required: true * schema: * type: string + * - + * name: token + * in: query + * description: 'Token required for authentication (when authentication is required).' + * required: false + * type: string * responses: * 200: * description: Command Received * schema: * $ref: "#/definitions/Response" */ -app.post('/task/remove', uuidCheck, (req, res) => { +app.post('/task/remove', authCheck, uuidCheck, (req, res) => { taskManager.remove(req.body.uuid, successHandler(res)); }); @@ -498,13 +543,19 @@ app.post('/task/remove', uuidCheck, (req, res) => { * required: false * schema: * type: string + * - + * name: token + * in: query + * description: 'Token required for authentication (when authentication is required).' + * required: false + * type: string * responses: * 200: * description: Command Received * schema: * $ref: "#/definitions/Response" */ -app.post('/task/restart', uuidCheck, (req, res, next) => { +app.post('/task/restart', authCheck, uuidCheck, (req, res, next) => { if (req.body.options){ odmOptions.filterOptions(req.body.options, (err, options) => { if (err) res.json({ error: err.message }); @@ -590,6 +641,7 @@ app.get('/info', (req, res) => { let gracefulShutdown = done => { async.series([ cb => taskManager.dumpTaskList(cb), + cb => auth.cleanup(cb), cb => { logger.info("Closing server"); server.close(); @@ -610,6 +662,7 @@ if (config.test) logger.info("Running in test mode"); let commands = [ cb => odmOptions.initialize(cb), + cb => auth.initialize(cb), cb => { taskManager = new TaskManager(cb); }, cb => { server = app.listen(config.port, err => { diff --git a/libs/auth/NoTokenRequiredAuth.js b/libs/auth/NoTokenRequiredAuth.js new file mode 100644 index 0000000..09d7fd6 --- /dev/null +++ b/libs/auth/NoTokenRequiredAuth.js @@ -0,0 +1,23 @@ +/* +Node-OpenDroneMap Node.js App and REST API to access OpenDroneMap. +Copyright (C) 2018 Node-OpenDroneMap Contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +const TokenAuthBase = require('./TokenAuthBase'); + +module.exports = class NoTokenRequiredAuth extends TokenAuthBase{ + // Always return valid + validateToken(token, cb){ cb(null, true); } +}; \ No newline at end of file diff --git a/libs/auth/SimpleTokenAuth.js b/libs/auth/SimpleTokenAuth.js new file mode 100644 index 0000000..5022c5f --- /dev/null +++ b/libs/auth/SimpleTokenAuth.js @@ -0,0 +1,35 @@ +/* +Node-OpenDroneMap Node.js App and REST API to access OpenDroneMap. +Copyright (C) 2018 Node-OpenDroneMap Contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +const TokenAuthBase = require('./TokenAuthBase'); + +module.exports = class SimpleTokenAuth extends TokenAuthBase{ + // @param token {String} token to use for authentication + constructor(token){ + super(token); + + this.token = token; + } + + validateToken(token, cb){ + if (this.token === token){ + return cb(null, true); + }else{ + cb(new Error("token does not match."), false); + } + } +}; \ No newline at end of file diff --git a/libs/auth/TokenAuthBase.js b/libs/auth/TokenAuthBase.js new file mode 100644 index 0000000..d6065c3 --- /dev/null +++ b/libs/auth/TokenAuthBase.js @@ -0,0 +1,42 @@ +/* +Node-OpenDroneMap Node.js App and REST API to access OpenDroneMap. +Copyright (C) 2018 Node-OpenDroneMap Contributors + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . +*/ +let logger = require('../logger'); + +module.exports = /*abstract */ class TokenAuthBase{ + initialize(cb){ + logger.info(`Authentication using ${this.constructor.name.replace(/Auth$/, "")}`); + cb(); + } + + cleanup(cb){ + cb(); + } + + validateToken(token, cb){ cb(new Error("Not implemented"), false); } + + getMiddleware(){ + return (req, res, next) => { + this.validateToken(req.query.token, (err, valid) => { + if (valid) next(); + else{ + res.json({ error: "Invalid authentication token: " + err.message }); + } + }); + }; + } +}; \ No newline at end of file diff --git a/libs/auth/factory.js b/libs/auth/factory.js new file mode 100644 index 0000000..1153fb4 --- /dev/null +++ b/libs/auth/factory.js @@ -0,0 +1,12 @@ +const NoTokenRequiredAuth = require('./NoTokenRequiredAuth'); +const SimpleTokenAuth = require('./SimpleTokenAuth'); + +module.exports = { + fromConfig: function(config){ + if (config.token){ + return new SimpleTokenAuth(config.token); + }else{ + return new NoTokenRequiredAuth(); + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index a6ba26e..dc1243f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "node-opendronemap", - "version": "1.0.4", + "version": "1.1.0", "description": "REST API to access OpenDroneMap", "main": "index.js", "scripts": {