diff --git a/README.md b/README.md
index c160cd4..b49aaff 100644
--- a/README.md
+++ b/README.md
@@ -32,17 +32,22 @@ For HOOBS or Homebridge without a configuration UI, you can use the [authenticat
### Manual configuration
An example configuration can be found in the [config.example.json](config.example.json) file.
-| Property | Type | Details |
-| -------------- | -------- | ------------------------------------------------ |
-| `platform` | `string` | **Required**
Must always be `Bold`. |
-| `accessToken` | `string` | **Required**
Access token for the Bold API. |
-| `refreshToken` | `string` | **Required**
Refresh token for the Bold API. |
+| Property | Type | Details |
+| ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------- |
+| `platform` | `string` | **Required**
Must always be `Bold`. |
+| `accessToken` | `string` | **Required**
Access token for the Bold API. |
+| `refreshToken` | `string` | **Required**
Refresh token for the Bold API. |
+| `refreshURL` | `string` | **Optional**
Custom refresh URL for token refreshing. Use this only if you authenticated with a custom backend. |
+| `legacyAuthentication` | `boolean` | **Required**
Switch between default and legacy authentication. This settings will impact token refreshing. |
+| | | |
## Backend
-The `backend/` folder contains the source code for the backend that is used while authenticating using the Bold app (default authentication). I host this myself on AWS. While your password is never available to this server, you can choose to self host this backend if you obtain a client id and secret from Bold. Specify a custom backend by clicking the settings icon on the login page.
+The `backend/` folder contains the source code for the backend that is used while authenticating using the Bold app (default authentication). I host this myself on AWS, with a client id and secret that was provided to me. While your password is never available to this server, you can choose to self host this backend if you obtain a client id and secret from Bold. Specify a custom backend by clicking the settings icon on the login page. Also specify a custom refresh URL in the config, as a custom client id and secret require refreshing with the same client id and secret.
-Alternatively you can also choose to use Legacy Authentication using username/password if you prefer not to use either of these options. This will log out your Bold app as only one username/password session can be active at the same time.
+Alternatively you can also choose to use legacy authentication using username/password if you prefer not to use either of these options. This will log out your Bold app as only one username/password based session can be active at the same time.
+
+*Note:* While default authentication is (semi-)supported by Bold, legacy authentication is not supported at all and may break at any time.
## Credits
diff --git a/backend/package-lock.json b/backend/package-lock.json
index ece5405..96b2b1e 100644
--- a/backend/package-lock.json
+++ b/backend/package-lock.json
@@ -24,7 +24,7 @@
"serverless": "^3.16.0",
"serverless-domain-manager": "^6.0.3",
"serverless-dynamodb-local": "^0.2.40",
- "serverless-offline": "^8.7.0",
+ "serverless-offline": "^8.8.0",
"serverless-scriptable-plugin": "^1.2.2",
"typescript": "^4.6.4"
}
@@ -2124,9 +2124,9 @@
}
},
"node_modules/aws-sdk": {
- "version": "2.1125.0",
- "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1125.0.tgz",
- "integrity": "sha512-2syNkKDqDcDmB/chc61a5xx+KYzaarLs1/KshE0b1Opp2oSq2FARyUBbk59HgwKaDUB61uPF33ZG9sHiIVx2hQ==",
+ "version": "2.1149.0",
+ "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1149.0.tgz",
+ "integrity": "sha512-wNb3YMLhXoK4UkjXhGAWMjRdrXT/Zhv3KdgPmd7VWlr3nXMViLwVJEEYdVmALUdkzCefdzY1JUTRLMgCxtn9EA==",
"dev": true,
"dependencies": {
"buffer": "4.9.2",
@@ -2136,7 +2136,7 @@
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
- "uuid": "3.3.2",
+ "uuid": "8.0.0",
"xml2js": "0.4.19"
},
"engines": {
@@ -2154,13 +2154,12 @@
}
},
"node_modules/aws-sdk/node_modules/uuid": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
- "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
- "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
+ "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==",
"dev": true,
"bin": {
- "uuid": "bin/uuid"
+ "uuid": "dist/bin/uuid"
}
},
"node_modules/axios": {
@@ -2974,18 +2973,27 @@
}
},
"node_modules/cron-parser": {
- "version": "2.18.0",
- "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.18.0.tgz",
- "integrity": "sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==",
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz",
+ "integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==",
"dev": true,
"dependencies": {
- "is-nan": "^1.3.0",
- "moment-timezone": "^0.5.31"
+ "is-nan": "^1.3.2",
+ "luxon": "^1.26.0"
},
"engines": {
"node": ">=0.8"
}
},
+ "node_modules/cron-parser/node_modules/luxon": {
+ "version": "1.28.0",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
+ "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -5950,7 +5958,7 @@
"node_modules/long-timeout": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
- "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=",
+ "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==",
"dev": true
},
"node_modules/loupe": {
@@ -5993,12 +6001,12 @@
}
},
"node_modules/luxon": {
- "version": "1.28.0",
- "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
- "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.4.0.tgz",
+ "integrity": "sha512-w+NAwWOUL5hO0SgwOHsMBAmZ15SoknmQXhSO0hIbJCAmPKSsGeK8MlmhYh2w6Iib38IxN2M+/ooXWLbeis7GuA==",
"dev": true,
"engines": {
- "node": "*"
+ "node": ">=12"
}
},
"node_modules/make-dir": {
@@ -6406,27 +6414,6 @@
"node": ">=6"
}
},
- "node_modules/moment": {
- "version": "2.29.3",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
- "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==",
- "dev": true,
- "engines": {
- "node": "*"
- }
- },
- "node_modules/moment-timezone": {
- "version": "0.5.34",
- "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz",
- "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==",
- "dev": true,
- "dependencies": {
- "moment": ">= 2.9.0"
- },
- "engines": {
- "node": "*"
- }
- },
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -6531,14 +6518,17 @@
}
},
"node_modules/node-schedule": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.3.tgz",
- "integrity": "sha512-uF9Ubn6luOPrcAYKfsXWimcJ1tPFtQ8I85wb4T3NgJQrXazEzojcFZVk46ZlLHby3eEJChgkV/0T689IsXh2Gw==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.0.tgz",
+ "integrity": "sha512-nl4JTiZ7ZQDc97MmpTq9BQjYhq7gOtoh7SiPH069gBFBj0PzD8HI7zyFs6rzqL8Y5tTiEEYLxgtbx034YPrbyQ==",
"dev": true,
"dependencies": {
- "cron-parser": "^2.18.0",
+ "cron-parser": "^3.5.0",
"long-timeout": "0.1.1",
"sorted-array-functions": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=6"
}
},
"node_modules/normalize-path": {
@@ -6652,23 +6642,6 @@
"node": ">= 0.4"
}
},
- "node_modules/object.fromentries": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz",
- "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==",
- "dev": true,
- "dependencies": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "es-abstract": "^1.19.1"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
"node_modules/object.getownpropertydescriptors": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz",
@@ -7280,15 +7253,6 @@
"node": ">=0.10.0"
}
},
- "node_modules/please-upgrade-node": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
- "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
- "dev": true,
- "dependencies": {
- "semver-compare": "^1.0.0"
- }
- },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -7764,12 +7728,6 @@
"node": ">=10"
}
},
- "node_modules/semver-compare": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
- "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
- "dev": true
- },
"node_modules/semver-diff": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz",
@@ -7891,36 +7849,33 @@
}
},
"node_modules/serverless-offline": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/serverless-offline/-/serverless-offline-8.7.0.tgz",
- "integrity": "sha512-OqBfSFk4iuhBx6oXxLLRY7QKNVzBIZBnhgeG/4GmJ+dG93AGYts0BD6Myi5EZALCD8T6WYQiNwHQRPDyGIgz2w==",
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/serverless-offline/-/serverless-offline-8.8.0.tgz",
+ "integrity": "sha512-FLS5AZb6uGxalDfwB9/M6jl3IOA4zwIEIgJY8QANuNncbwbcoZfilD1Ajl87hLvUnRvTzncv3gz8QYssVAvq8Q==",
"dev": true,
"dependencies": {
"@hapi/boom": "^9.1.4",
"@hapi/h2o2": "^9.1.0",
- "@hapi/hapi": "^20.2.1",
- "aws-sdk": "^2.1097.0",
+ "@hapi/hapi": "^20.2.2",
+ "aws-sdk": "^2.1136.0",
"boxen": "^5.1.2",
"chalk": "^4.1.2",
"cuid": "^2.1.8",
"execa": "^5.1.1",
- "extend": "^3.0.2",
- "fs-extra": "^9.1.0",
+ "fs-extra": "^10.1.0",
"java-invoke-local": "0.0.6",
"js-string-escape": "^1.0.1",
"jsonpath-plus": "^5.1.0",
"jsonschema": "^1.4.0",
"jsonwebtoken": "^8.5.1",
- "jszip": "^3.7.1",
- "luxon": "^1.28.0",
+ "jszip": "^3.9.1",
+ "luxon": "^2.4.0",
"node-fetch": "^2.6.7",
- "node-schedule": "^1.3.3",
- "object.fromentries": "^2.0.5",
+ "node-schedule": "^2.1.0",
"p-memoize": "^4.0.4",
"p-queue": "^6.6.2",
- "p-retry": "^4.6.1",
- "please-upgrade-node": "^3.2.0",
- "semver": "^7.3.5",
+ "p-retry": "^4.6.2",
+ "semver": "^7.3.7",
"update-notifier": "^5.1.0",
"velocityjs": "^2.0.6",
"ws": "^7.5.7"
@@ -7932,6 +7887,20 @@
"serverless": "^1.60.0 || 2 || 3"
}
},
+ "node_modules/serverless-offline/node_modules/fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "dependencies": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/serverless-scriptable-plugin": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/serverless-scriptable-plugin/-/serverless-scriptable-plugin-1.2.2.tgz",
@@ -10913,9 +10882,9 @@
"dev": true
},
"aws-sdk": {
- "version": "2.1125.0",
- "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1125.0.tgz",
- "integrity": "sha512-2syNkKDqDcDmB/chc61a5xx+KYzaarLs1/KshE0b1Opp2oSq2FARyUBbk59HgwKaDUB61uPF33ZG9sHiIVx2hQ==",
+ "version": "2.1149.0",
+ "resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1149.0.tgz",
+ "integrity": "sha512-wNb3YMLhXoK4UkjXhGAWMjRdrXT/Zhv3KdgPmd7VWlr3nXMViLwVJEEYdVmALUdkzCefdzY1JUTRLMgCxtn9EA==",
"dev": true,
"requires": {
"buffer": "4.9.2",
@@ -10925,7 +10894,7 @@
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
- "uuid": "3.3.2",
+ "uuid": "8.0.0",
"xml2js": "0.4.19"
},
"dependencies": {
@@ -10936,9 +10905,9 @@
"dev": true
},
"uuid": {
- "version": "3.3.2",
- "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
- "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.0.0.tgz",
+ "integrity": "sha512-jOXGuXZAWdsTH7eZLtyXMqUb9EcWMGZNbL9YcGBJl4MH4nrxHmZJhEHvyLFrkxo+28uLb/NYRcStH48fnD0Vzw==",
"dev": true
}
}
@@ -11573,13 +11542,21 @@
}
},
"cron-parser": {
- "version": "2.18.0",
- "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.18.0.tgz",
- "integrity": "sha512-s4odpheTyydAbTBQepsqd2rNWGa2iV3cyo8g7zbI2QQYGLVsfbhmwukayS1XHppe02Oy1fg7mg6xoaraVJeEcg==",
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-3.5.0.tgz",
+ "integrity": "sha512-wyVZtbRs6qDfFd8ap457w3XVntdvqcwBGxBoTvJQH9KGVKL/fB+h2k3C8AqiVxvUQKN1Ps/Ns46CNViOpVDhfQ==",
"dev": true,
"requires": {
- "is-nan": "^1.3.0",
- "moment-timezone": "^0.5.31"
+ "is-nan": "^1.3.2",
+ "luxon": "^1.26.0"
+ },
+ "dependencies": {
+ "luxon": {
+ "version": "1.28.0",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
+ "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
+ "dev": true
+ }
}
},
"cross-spawn": {
@@ -13879,7 +13856,7 @@
"long-timeout": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
- "integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ=",
+ "integrity": "sha512-BFRuQUqc7x2NWxfJBCyUrN8iYUYznzL9JROmRz1gZ6KlOIgmoD+njPVbb+VNn2nGMKggMsK79iUNErillsrx7w==",
"dev": true
},
"loupe": {
@@ -13916,9 +13893,9 @@
}
},
"luxon": {
- "version": "1.28.0",
- "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.28.0.tgz",
- "integrity": "sha512-TfTiyvZhwBYM/7QdAVDh+7dBTBA29v4ik0Ce9zda3Mnf8on1S5KJI8P2jKFZ8+5C0jhmr0KwJEO/Wdpm0VeWJQ==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.4.0.tgz",
+ "integrity": "sha512-w+NAwWOUL5hO0SgwOHsMBAmZ15SoknmQXhSO0hIbJCAmPKSsGeK8MlmhYh2w6Iib38IxN2M+/ooXWLbeis7GuA==",
"dev": true
},
"make-dir": {
@@ -14242,21 +14219,6 @@
}
}
},
- "moment": {
- "version": "2.29.3",
- "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.3.tgz",
- "integrity": "sha512-c6YRvhEo//6T2Jz/vVtYzqBzwvPT95JBQ+smCytzf7c50oMZRsR/a4w88aD34I+/QVSfnoAnSBFPJHItlOMJVw==",
- "dev": true
- },
- "moment-timezone": {
- "version": "0.5.34",
- "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.34.tgz",
- "integrity": "sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==",
- "dev": true,
- "requires": {
- "moment": ">= 2.9.0"
- }
- },
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
@@ -14346,12 +14308,12 @@
}
},
"node-schedule": {
- "version": "1.3.3",
- "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.3.tgz",
- "integrity": "sha512-uF9Ubn6luOPrcAYKfsXWimcJ1tPFtQ8I85wb4T3NgJQrXazEzojcFZVk46ZlLHby3eEJChgkV/0T689IsXh2Gw==",
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-2.1.0.tgz",
+ "integrity": "sha512-nl4JTiZ7ZQDc97MmpTq9BQjYhq7gOtoh7SiPH069gBFBj0PzD8HI7zyFs6rzqL8Y5tTiEEYLxgtbx034YPrbyQ==",
"dev": true,
"requires": {
- "cron-parser": "^2.18.0",
+ "cron-parser": "^3.5.0",
"long-timeout": "0.1.1",
"sorted-array-functions": "^1.3.0"
}
@@ -14436,17 +14398,6 @@
"object-keys": "^1.0.11"
}
},
- "object.fromentries": {
- "version": "2.0.5",
- "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.5.tgz",
- "integrity": "sha512-CAyG5mWQRRiBU57Re4FKoTBjXfDoNwdFVH2Y1tS9PqCsfUTymAohOkEMSG3aRNKmv4lV3O7p1et7c187q6bynw==",
- "dev": true,
- "requires": {
- "call-bind": "^1.0.2",
- "define-properties": "^1.1.3",
- "es-abstract": "^1.19.1"
- }
- },
"object.getownpropertydescriptors": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.3.tgz",
@@ -14899,15 +14850,6 @@
"pinkie": "^2.0.0"
}
},
- "please-upgrade-node": {
- "version": "3.2.0",
- "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz",
- "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==",
- "dev": true,
- "requires": {
- "semver-compare": "^1.0.0"
- }
- },
"prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -15231,12 +15173,6 @@
"lru-cache": "^6.0.0"
}
},
- "semver-compare": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz",
- "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=",
- "dev": true
- },
"semver-diff": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-3.1.1.tgz",
@@ -15337,39 +15273,49 @@
}
},
"serverless-offline": {
- "version": "8.7.0",
- "resolved": "https://registry.npmjs.org/serverless-offline/-/serverless-offline-8.7.0.tgz",
- "integrity": "sha512-OqBfSFk4iuhBx6oXxLLRY7QKNVzBIZBnhgeG/4GmJ+dG93AGYts0BD6Myi5EZALCD8T6WYQiNwHQRPDyGIgz2w==",
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/serverless-offline/-/serverless-offline-8.8.0.tgz",
+ "integrity": "sha512-FLS5AZb6uGxalDfwB9/M6jl3IOA4zwIEIgJY8QANuNncbwbcoZfilD1Ajl87hLvUnRvTzncv3gz8QYssVAvq8Q==",
"dev": true,
"requires": {
"@hapi/boom": "^9.1.4",
"@hapi/h2o2": "^9.1.0",
- "@hapi/hapi": "^20.2.1",
- "aws-sdk": "^2.1097.0",
+ "@hapi/hapi": "^20.2.2",
+ "aws-sdk": "^2.1136.0",
"boxen": "^5.1.2",
"chalk": "^4.1.2",
"cuid": "^2.1.8",
"execa": "^5.1.1",
- "extend": "^3.0.2",
- "fs-extra": "^9.1.0",
+ "fs-extra": "^10.1.0",
"java-invoke-local": "0.0.6",
"js-string-escape": "^1.0.1",
"jsonpath-plus": "^5.1.0",
"jsonschema": "^1.4.0",
"jsonwebtoken": "^8.5.1",
- "jszip": "^3.7.1",
- "luxon": "^1.28.0",
+ "jszip": "^3.9.1",
+ "luxon": "^2.4.0",
"node-fetch": "^2.6.7",
- "node-schedule": "^1.3.3",
- "object.fromentries": "^2.0.5",
+ "node-schedule": "^2.1.0",
"p-memoize": "^4.0.4",
"p-queue": "^6.6.2",
- "p-retry": "^4.6.1",
- "please-upgrade-node": "^3.2.0",
- "semver": "^7.3.5",
+ "p-retry": "^4.6.2",
+ "semver": "^7.3.7",
"update-notifier": "^5.1.0",
"velocityjs": "^2.0.6",
"ws": "^7.5.7"
+ },
+ "dependencies": {
+ "fs-extra": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
+ "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ }
+ }
}
},
"serverless-scriptable-plugin": {
diff --git a/backend/package.json b/backend/package.json
index c06fac2..515975e 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -6,13 +6,14 @@
"scripts": {
"dev": "serverless offline start --stage dev",
"build": "tsc",
- "package": "npm run prepackage && npm run package:websocket-manager && npm run package:oauth-begin && npm run package:oauth-callback && npm run postpackage",
"db:migrate": "serverless dynamodb migrate --stage dev",
"clean": "rm -rf build/ dist/",
"prepackage": "npm run clean && mkdir -p build dist && npm run build && cp package*.json build/ && cd build && npm install --production",
+ "package": "npm run prepackage && npm run package:websocket-manager && npm run package:oauth-begin && npm run package:oauth-callback && npm run package:oauth-refresh && npm run postpackage",
"package:websocket-manager": "cd build && zip -r ../dist/websocket-manager.zip websocket-manager/ common/ node_modules/ package*.json",
"package:oauth-begin": "cd build && zip -r ../dist/oauth-begin.zip oauth-begin.js common/ node_modules/ package*.json",
"package:oauth-callback": "cd build && zip -r ../dist/oauth-callback.zip oauth-callback.js common/ node_modules/ package*.json",
+ "package:oauth-refresh": "cd build && zip -r ../dist/oauth-refresh.zip oauth-refresh.js common/ node_modules/ package*.json",
"postpackage": "rm -rf build/"
},
"keywords": [
@@ -30,7 +31,7 @@
"serverless": "^3.16.0",
"serverless-domain-manager": "^6.0.3",
"serverless-dynamodb-local": "^0.2.40",
- "serverless-offline": "^8.7.0",
+ "serverless-offline": "^8.8.0",
"serverless-scriptable-plugin": "^1.2.2",
"typescript": "^4.6.4"
},
diff --git a/backend/serverless.yaml b/backend/serverless.yaml
index 57cccd8..6ece5e9 100644
--- a/backend/serverless.yaml
+++ b/backend/serverless.yaml
@@ -89,6 +89,20 @@ functions:
method: GET
path: /oauth/callback
+ oauthRefresh:
+ handler: oauth-refresh.handler
+ name: ${self:service}-${opt:stage}-oauth-refresh
+
+ environment: ${self:custom.stageOptions.environment.${opt:stage}, self:custom.stageOptions.environment.default}
+
+ package:
+ artifact: dist/oauth-refresh.zip
+
+ events:
+ - httpApi:
+ method: POST
+ path: /oauth/refresh
+
resources:
Resources:
ConnectionsTable:
diff --git a/backend/src/common/respones.ts b/backend/src/common/respones.ts
index d196cd8..77ff626 100644
--- a/backend/src/common/respones.ts
+++ b/backend/src/common/respones.ts
@@ -1,15 +1,19 @@
import { APIGatewayProxyResult } from 'aws-lambda';
-export function respone(statusCode: number, data?: Record): APIGatewayProxyResult {
+export function response(statusCode: number, string?: string): APIGatewayProxyResult
+export function response(statusCode: number, data?: Record): APIGatewayProxyResult
+
+export function response(statusCode: number, stringOrData?: string | Record): APIGatewayProxyResult {
return {
statusCode,
headers: {
'Content-Type': 'application/json'
},
- body: data ? JSON.stringify({
- success: true,
- data
- }) : ''
+ body: typeof stringOrData == 'string' ? stringOrData :
+ stringOrData != null ? JSON.stringify({
+ success: true,
+ data: stringOrData
+ }) : ''
};
}
diff --git a/backend/src/oauth-begin.ts b/backend/src/oauth-begin.ts
index 767c523..350440a 100644
--- a/backend/src/oauth-begin.ts
+++ b/backend/src/oauth-begin.ts
@@ -5,7 +5,7 @@ import { v4 as uuidv4 } from 'uuid';
import { dynamoClient, websocketClient } from './common/clients';
import { environment } from './common/environment';
-import { errorResponse, internalErrorResponse, respone } from './common/respones';
+import { errorResponse, internalErrorResponse, response } from './common/respones';
export const handler: APIGatewayProxyHandler = async (event): Promise => {
try {
@@ -70,7 +70,7 @@ export const handler: APIGatewayProxyHandler = async (event): Promise => {
try {
@@ -124,7 +124,7 @@ export const handler: APIGatewayProxyHandlerV2 = async (event): Promise => {
+ try {
+ let { body: bodyString } = event;
+
+ let { refreshToken } = JSON.parse(bodyString || '{}');
+
+ if (refreshToken == null) {
+ return errorResponse(400, 'Missing refresh token');
+ }
+
+ let accessToken, newRefreshToken;
+
+ try {
+ let form = new URLSearchParams();
+
+ form.append('grant_type', 'refresh_token');
+ form.append('refresh_token', refreshToken);
+ form.append('client_id', environment.BOLD_CLIENT_ID || '');
+ form.append('client_secret', environment.BOLD_CLIENT_SECRET || '');
+
+ let authResponse = await axios.post('https://api.boldsmartlock.com/v2/oauth/token', form.toString(), {
+ headers: {
+ 'Content-Type': 'application/x-www-form-urlencoded'
+ }
+ });
+
+ accessToken = authResponse.data.access_token;
+ newRefreshToken = authResponse.data.refresh_token;
+ } catch (error) {
+ console.error(`Error while refreshing access token: ${error}`);
+
+ if (axios.isAxiosError(error)) {
+ console.error(error.response?.data);
+
+ return errorResponse(error.response?.status ?? 500, `Bold Error: ${JSON.stringify(error.response?.data ?? '"Unknown error"')}`);
+ }
+
+ return internalErrorResponse(`Error while refreshing access token: ${error}`);
+ }
+
+ if (!accessToken || !newRefreshToken) {
+ console.error('Missing access or new refresh token');
+ return internalErrorResponse('Missing access or new refresh token');
+ }
+
+ return response(200, { accessToken, refreshToken: newRefreshToken });
+ } catch (error) {
+ console.error(`Unhandled exception: ${error}`);
+ return internalErrorResponse(`Unhandled exception: ${error}`);
+ }
+};
\ No newline at end of file
diff --git a/backend/src/websocket-manager/routes.ts b/backend/src/websocket-manager/routes.ts
index 9184c6e..0f904c7 100644
--- a/backend/src/websocket-manager/routes.ts
+++ b/backend/src/websocket-manager/routes.ts
@@ -2,7 +2,7 @@ import { DeleteItemCommand, PutItemCommand } from '@aws-sdk/client-dynamodb';
import type { APIGatewayProxyResult } from 'aws-lambda';
import { dynamoClient } from '../common/clients';
-import { internalErrorResponse, respone } from '../common/respones';
+import { internalErrorResponse, response } from '../common/respones';
import { environment } from '../common/environment';
export async function connect(connectionId: string): Promise {
@@ -19,7 +19,7 @@ export async function connect(connectionId: string): Promise {
@@ -36,5 +36,5 @@ export async function disconnect(connectionId: string): Promise",
- "refreshToken": ""
+ "refreshToken": "",
+ "legacyAuthentication": "false"
}
]
}
\ No newline at end of file
diff --git a/plugin/config.schema.json b/plugin/config.schema.json
index e75f367..8e95885 100644
--- a/plugin/config.schema.json
+++ b/plugin/config.schema.json
@@ -5,6 +5,7 @@
"customUi": true,
"schema": {
"type": "object",
+ "required": ["accessToken", "refreshToken", "legacyAuthentication"],
"properties": {
"accessToken": {
"title": "Access token",
@@ -17,6 +18,16 @@
"type": "string",
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$",
"description": "This token will be used to refresh the access token."
+ },
+ "refreshURL": {
+ "title": "Custom refresh URL",
+ "type": "string",
+ "description": "Custom refresh URL for token refreshing. Use this only if you authenticated with a custom backend."
+ },
+ "legacyAuthentication": {
+ "title": "Use legacy authentication",
+ "type": "boolean",
+ "description": "Switch between default and legacy authentication. This settings will impact token refreshing."
}
}
}
diff --git a/plugin/homebridge-ui/src/components/App/App.tsx b/plugin/homebridge-ui/src/components/App/App.tsx
index 867ad6c..10138c3 100644
--- a/plugin/homebridge-ui/src/components/App/App.tsx
+++ b/plugin/homebridge-ui/src/components/App/App.tsx
@@ -1,6 +1,7 @@
import { useEffect, useState } from 'react';
import QRCode from 'react-qr-code';
import AuthManager from '../../auth-manager';
+import { Config } from '../../types';
import LegacyAuth from '../LegacyAuth';
import Page from '../Page';
@@ -13,7 +14,7 @@ function App(): JSX.Element {
let [isShowingQRCode, setShowingQRCode] = useState(false);
let [oauthURL, setOAuthURL] = useState();
- let [result, setResult] = useState<{ accessToken: string, refreshToken: string }>();
+ let [result, setResult] = useState();
let [isConfigured, setConfigured] = useState();
let [isInSettings, setInSettings] = useState(false);
@@ -40,7 +41,7 @@ function App(): JSX.Element {
setOAuthURL(`https://boldsmartlock.com/app/authorize?response_type=code&client_id=HomeBridge&redirect_uri=${encodeURI(`${AuthManager.shared.callbackURL}`)}&state=${encodeURI(callbackId)}`);
AuthManager.shared.once('oauthCallback', (result) => {
- setResult(result);
+ setResult({ ...result, legacyAuthentication: false });
AuthManager.shared.close();
});
}
@@ -71,6 +72,7 @@ function App(): JSX.Element {
config[0].accessToken = result.accessToken;
config[0].refreshToken = result.refreshToken;
+ config[0].legacyAuthentication = result.legacyAuthentication;
await homebridge.updatePluginConfig(config);
homebridge.showSchemaForm();
@@ -104,8 +106,8 @@ function App(): JSX.Element {
-
-
+
+
);
} else if (!isAuthenticating) {
diff --git a/plugin/homebridge-ui/src/components/LegacyAuth/LegacyAuth.tsx b/plugin/homebridge-ui/src/components/LegacyAuth/LegacyAuth.tsx
index c823157..8273ae7 100644
--- a/plugin/homebridge-ui/src/components/LegacyAuth/LegacyAuth.tsx
+++ b/plugin/homebridge-ui/src/components/LegacyAuth/LegacyAuth.tsx
@@ -1,9 +1,9 @@
import React, { useState } from 'react';
-import { LegacyAuthPage } from '../../types';
+import { Config, LegacyAuthPage } from '../../types';
import Page from '../Page';
interface LegacyAuthProps {
- setResult: (result: { accessToken: string, refreshToken: string }) => void;
+ setResult: (result: Config) => void;
}
function LegacyAuth(props: LegacyAuthProps): JSX.Element {
@@ -150,7 +150,7 @@ function LegacyAuth(props: LegacyAuthProps): JSX.Element {
let { access_token, refresh_token } = body;
- props.setResult({ accessToken: access_token, refreshToken: refresh_token });
+ props.setResult({ accessToken: access_token, refreshToken: refresh_token, legacyAuthentication: true });
} catch (error) {
console.error(`Error while authenticating: ${error}`);
setError((error as any).toString());
diff --git a/plugin/homebridge-ui/src/types/config.ts b/plugin/homebridge-ui/src/types/config.ts
new file mode 100644
index 0000000..291d45e
--- /dev/null
+++ b/plugin/homebridge-ui/src/types/config.ts
@@ -0,0 +1,5 @@
+export interface Config {
+ accessToken: string;
+ refreshToken: string;
+ legacyAuthentication: boolean;
+}
\ No newline at end of file
diff --git a/plugin/homebridge-ui/src/types/index.ts b/plugin/homebridge-ui/src/types/index.ts
index eae448a..5aac783 100644
--- a/plugin/homebridge-ui/src/types/index.ts
+++ b/plugin/homebridge-ui/src/types/index.ts
@@ -1 +1,2 @@
-export * from './page';
\ No newline at end of file
+export * from './page';
+export * from './config';
\ No newline at end of file
diff --git a/plugin/package-lock.json b/plugin/package-lock.json
index 180a928..0b8e92c 100644
--- a/plugin/package-lock.json
+++ b/plugin/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "homebridge-bold",
- "version": "2.0.0",
+ "version": "2.1.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "homebridge-bold",
- "version": "2.0.0",
+ "version": "2.1.0",
"funding": [
{
"type": "paypal",
diff --git a/plugin/package.json b/plugin/package.json
index 96bd38e..2f96658 100644
--- a/plugin/package.json
+++ b/plugin/package.json
@@ -1,7 +1,7 @@
{
"name": "homebridge-bold",
"displayName": "Homebridge Bold",
- "version": "2.0.0",
+ "version": "2.1.0",
"description": "HomeKit support for the Bold Smart Locks.",
"main": "build/index.js",
"engines": {
diff --git a/plugin/src/bold.ts b/plugin/src/bold.ts
index c8b3df3..2781cad 100644
--- a/plugin/src/bold.ts
+++ b/plugin/src/bold.ts
@@ -1,7 +1,8 @@
import axios, { AxiosError, Method } from 'axios';
import FormData from 'form-data';
import { Logger } from 'homebridge';
-import { Device } from './types';
+import { REFRESH_URL, LEGACY_CLIENT_ID, LEGACY_CLIENT_SECRET } from './const';
+import { Config, Device } from './types';
interface APISuccess {
success: true;
@@ -21,8 +22,7 @@ type APIResponse = APISuccess | APIError;
export class BoldAPI {
constructor(
- private accessToken: string,
- private refreshToken: string,
+ private config: Config,
private log: Logger
) {}
@@ -32,7 +32,7 @@ export class BoldAPI {
method: method,
url: `https://api.sesamtechnology.com${endpoint}`,
headers: {
- 'Authorization': `Bearer ${this.accessToken}`,
+ 'Authorization': `Bearer ${this.config.accessToken}`,
...(!Object.keys(headers || {}).some((header) => header.toLowerCase() == 'content-type') && { 'Content-Type': 'application/json' }),
...headers
},
@@ -117,11 +117,40 @@ export class BoldAPI {
async refresh(): Promise<{ accessToken: string, refreshToken: string } | undefined> {
this.log.debug('Refreshing access token');
+ if (this.config.legacyAuthentication) {
+ return await this.refreshLegacy();
+ }
+
+ try {
+ let response = await axios.post(this.config.refreshURL || REFRESH_URL, { refreshToken: this.config.refreshToken });
+
+ let { accessToken, refreshToken } = response.data.data;
+
+ if (!accessToken || !refreshToken) {
+ this.log.error(`Missing access or refresh token: ${JSON.stringify(response.data)}`);
+ return;
+ }
+
+ this.log.debug('Successfully refreshed access token');
+
+ return { accessToken, refreshToken };
+ } catch (error) {
+ if (axios.isAxiosError(error)) {
+ let axiosError = error as AxiosError;
+
+ this.log.error(`Error (${error.response?.status}) while refreshing token: ${axiosError.response?.data?.error?.message || error}`);
+ } else {
+ this.log.error(`Error while refreshing access token: ${error}`);
+ }
+ }
+ }
+
+ async refreshLegacy(): Promise<{ accessToken: string, refreshToken: string } | undefined> {
let formData = new FormData();
- formData.append('client_id', 'BoldApp');
- formData.append('client_secret', 'pgJFgnGB87f9ednFiiHygCbf');
- formData.append('refresh_token', this.refreshToken);
+ formData.append('client_id', LEGACY_CLIENT_ID);
+ formData.append('client_secret', LEGACY_CLIENT_SECRET);
+ formData.append('refresh_token', this.config.refreshToken);
formData.append('grant_type', 'refresh_token');
let response = await this.request('POST', '/v2/oauth/token', formData, formData.getHeaders());
@@ -134,11 +163,11 @@ export class BoldAPI {
let data = response.data as any;
- this.accessToken = data.access_token;
- this.refreshToken = data.refresh_token;
+ this.config.accessToken = data.access_token;
+ this.config.refreshToken = data.refresh_token;
this.log.debug('Successfully refreshed access token');
- return { accessToken: this.accessToken, refreshToken: this.refreshToken };
+ return { accessToken: this.config.accessToken, refreshToken: this.config.refreshToken };
}
}
\ No newline at end of file
diff --git a/plugin/src/const.ts b/plugin/src/const.ts
index f2efa22..986bb84 100644
--- a/plugin/src/const.ts
+++ b/plugin/src/const.ts
@@ -1,2 +1,6 @@
export const PLUGIN_NAME = 'homebridge-bold';
-export const PLATFORM_NAME = 'Bold';
\ No newline at end of file
+export const PLATFORM_NAME = 'Bold';
+
+export const REFRESH_URL = 'https://bold.nienhuisdevelopment.com/oauth/refresh';
+export const LEGACY_CLIENT_ID = 'BoldApp';
+export const LEGACY_CLIENT_SECRET = 'pgJFgnGB87f9ednFiiHygCbf';
\ No newline at end of file
diff --git a/plugin/src/index.ts b/plugin/src/index.ts
index b26d1e1..a26c866 100644
--- a/plugin/src/index.ts
+++ b/plugin/src/index.ts
@@ -28,7 +28,7 @@ class BoldPlatform implements DynamicPlatformPlugin {
) {
this.hap = api.hap;
this.config = config as Config;
- this.bold = new BoldAPI(this.config.accessToken, this.config.refreshToken, this.log);
+ this.bold = new BoldAPI({ ...this.config }, this.log);
api.on(APIEvent.DID_FINISH_LAUNCHING, async () => {
await this.refreshAccessToken();
diff --git a/plugin/src/types/config.ts b/plugin/src/types/config.ts
index af89854..2398e39 100644
--- a/plugin/src/types/config.ts
+++ b/plugin/src/types/config.ts
@@ -3,4 +3,6 @@ import { PlatformConfig } from 'homebridge';
export interface Config extends PlatformConfig {
accessToken: string;
refreshToken: string;
+ refreshURL?: string;
+ legacyAuthentication: boolean;
}
\ No newline at end of file