Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Quick Connect device to the core #3388

Merged
merged 18 commits into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/device-agent/images/add_device.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed docs/device-agent/images/config_yml.png
Binary file not shown.
Binary file added docs/device-agent/images/config_yml1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/device-agent/images/config_yml2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 12 additions & 12 deletions docs/device-agent/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,29 +9,29 @@ navOrder: 2

### Install Device Agent Software

1. Open your Command Prompt (CMD) as an administrator.
1. Open a Terminal/Command Prompt as an root/administrative user
2. Run the following commands in sequence:

Windows:
```bash
mkdir c:\opt\flowfuse-device
cd c:\opt\flowfuse-device
npm install -g @flowfuse/device-agent
flowfuse-device-agent -w --ui-user admin --ui-pass password --ui-port 8081
flowfuse-device-agent -ui --ui-user admin --ui-pass password --ui-port 8081
Steve-Mcl marked this conversation as resolved.
Show resolved Hide resolved
Steve-Mcl marked this conversation as resolved.
Show resolved Hide resolved
```
Linux:
Linux/MacOS:
```bash
mkdir /opt/flowfuse-device
cd /opt/flowfuse-device
npm install -g @flowfuse/device-agent
flowfuse-device-agent -w --ui-user admin --ui-pass password --ui-port 8081
flowfuse-device-agent -ui --ui-user admin --ui-pass password --ui-port 8081
knolleary marked this conversation as resolved.
Show resolved Hide resolved
```

Note: The flags used in the command above have the following meanings:
- -w: Run the device agent in web user interface mode.
- --ui-user: Specify the username for the web user interface.
- --ui-pass: Specify the password for the web user interface.
- --ui-port: Specify the port for the web user interface.
- `-ui`: Start the device agents web-based user interface.
knolleary marked this conversation as resolved.
Show resolved Hide resolved
- `--ui-user`: Specify the username for the web user interface.
- `--ui-pass`: Specify the password for the web user interface.
- `--ui-port`: Specify the port for the web user interface.
- You can find more details about these flags [here](https://flowfuse.com/docs/device-agent/running/).

### Add Device in FlowFuse
Expand All @@ -43,9 +43,9 @@ Note: The flags used in the command above have the following meanings:
<img src="images/add_device.png" width=500 />

### Configure Device Agent
1. Click **Copy to Clipboard** to copy the device configuration.
1. Expand the Manual setup section, click the **Copy** button below the **Device Configuration**.

<img src="images/config_yml.png" width=500 />
<img src="images/config_yml2.png" width=500 />

2. Open a new browser tab and navigate to the IP address of the device using the port defined when starting up the device agent. **\<\<ip of device>>:\<\<port>>**, e.g., localhost:8081
3. Paste the copied device configuration into the **Agent Configuration** field. This configuration contains vital information instructing the device on how to communicate with FlowFuse. It's crucial to keep this information secure and not share it with unauthorized individuals.
Expand Down Expand Up @@ -101,9 +101,9 @@ Note: The flags used in the command above have the following meanings:
5. Click **Create.**

### Restore Device from Snapshot
1. Naviage to Devices Menu.
1. Navigate to Devices Menu.
2. Select the Device needing to be restored.
3. Navigate to Shapshots menu.
3. Navigate to Snapshots menu.

<img src="images/deploySnapshot.png" width=500 />

Expand Down
70 changes: 47 additions & 23 deletions docs/device-agent/register.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ navOrder: 4

# Register your Device

To connect a device to FlowFuse, it needs a set of credentials.
To connect a device to FlowFuse, it needs a set of credentials and configuration information.

There are two types of configurations to choose from:

Expand All @@ -16,16 +16,22 @@ There are two types of configurations to choose from:
_for a single device_

1. Go to your teams's **Devices** page.
2. Click the **Register Device** button.
3. You will be prompted to give the device a **name**, an optional **type** and to chose which **Application**, if any, the device should be assigned to.
* The **type** field can be used to record additional meta information about the device.
* If you do not wish to assign the device to an application at this time, you can do so later.
4. Click **Register**

Once the device has been registered, you will be shown the **Device Configuration**
dialog. This is the only time the platform will show you this information without
resetting it. Make sure to take a copy or use the **Download** button to save
the configuration file locally.
2. Click the **Add Device** button.
3. You will be prompted to give the device a **Name**, an optional **Type** and to chose which **Application**, if any, the device should be assigned to.
* <img src="images/add_device.png" width=500 />
* The **Type** field can be used to record additional meta information about the device.
* If you do not wish to assign the device to an **Application** at this time, you can do so later.
4. Click **Add**

Once the device has been registered, you will be shown the **Device Configuration** dialog which
contains all the information needed to connect the device to the platform.

By default, you are offered the [Setup command](#setup-command-method) method that was introduced in FlowFuse V2.1.
<img src="images/config_yml1.png" width=500 />

For older versions of the device agent, you can expand the the **Manual Setup** section
and use the configuration data with the [Copy](#copy-method) or the [Download](#download-method) methods instead.
<img src="images/config_yml2.png" width=500 />

Repeat these steps for each device you want to connect to the platform.

Expand All @@ -51,15 +57,39 @@ the configuration file locally.

Before you can connect a device to the platform, the device must have
a **Device Configuration** file or a **Device Provisioning Configuration**
file present in its working directory. There are two ways to do this:
1. Copy the configuration file into the device's
file present in its working directory. There are three ways to do this:
1. Copy the Setup command and run it in a terminal window on the device.
2. Copy the configuration file to the device using its built in Web UI.
* _The Device Agent must be running and the command line flag for the Web UI must be enabled._
* _See [Command Line Options](./running.md#device-agent-command-line-options) for more information._
3. Download the configuration file into the device's
[Working Directory](./install.md#working-directory).
2. Download the configuration file to the device using its built in Web UI.
NOTE: The Device Agent must be running and the command line flag for the Web UI must be enabled.
See [Command Line Options](./running.md#device-agent-command-line-options) for more information.


### Setup command method

The Setup command method was introduced in FlowFuse V2.1. This is the fastest way to connect a device to the platform.

1. Copy the **Setup** command to the clipboard.
2. Open a terminal window to the device and paste or type in the command.
3. The device agent connect to the platform, retrieve the device configuration.

Upon completion, the terminal window will report that the device has connected to the platform and
will output a new `command` for you to use to start the device agent with the new configuration.

NOTES
* The Setup command is only valid for 24h. If you do not use it within this time, you will need to [regenerate](#regenerating-configurations) it.
* The 3 word One-Time-Code (OTC) contained in the Setup command is single use and is deleted immediately upon use.

### Copy method

If the Device Agent is running with the Web UI enabled, you can download the
configuration file to the device using the Web UI. This is useful if you don't
have direct access to the device's file system. Once the configuration file is
downloaded, the device agent will automatically restart and load the configuration.

### Download method

Place the **Device Configuration** or **Device Provisioning Configuration** file on the device
in the [Working Directory](./install.md#working-directory)

Expand All @@ -74,12 +104,6 @@ flowfuse-device-agent
You will see the device start and perform a 'call-home' where it connects back
to the platform to check what it should be running.

### Download method

If the Device Agent is running with the Web UI enabled, you can download the
configuration file to the device using the Web UI. This is useful if you don't
have direct access to the device's file system. Once the configuration file is
downloaded, the device agent will automatically restart and load the configuration.

#### Additional Information

Expand Down Expand Up @@ -150,7 +174,7 @@ To regenerate device configurations:
running. Click **Regenerate Configuration** to continue.

You will then be shown the **Device Configuration** dialog again with a new
configuration to copy or download.
setup command and the manual configuration to copy or download.

## Deleting a device

Expand Down
15 changes: 10 additions & 5 deletions docs/device-agent/running.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,24 +39,29 @@ The following command line options are available:
Options

-c, --config file Device configuration file. Default: device.yml
-d, --dir dir Where the agent should store its state. Default: /opt/flowforge-device
-d, --dir dir Where the agent should store its state. Default: /opt/flowfuse-device
-i, --interval secs
-p, --port number
-m, --moduleCache Use local npm module cache rather than install

Web UI Options

-w, --ui Start the Web UI Server (optional, does not run by default)
--ui-host string Web UI server host. Default: (0.0.0.0) (listen on all interfaces)
-w, --ui Start the Web UI Server (optional, does not run by default)
--ui-host string Web UI server host. Default: (0.0.0.0) (listen on all interfaces)
--ui-port number Web UI server port. Default: 1879
--ui-user string Web UI username. Required if --ui is specified
--ui-pass string Web UI password. Required if --ui is specified
--ui-runtime mins Time the Web UI server is permitted to run. Default: 10

Setup command

-o, --otc string Setup device using a one time code
-u, --ff-url url URL of FlowFuse. Required for setup

Global Options

-h, --help print out helpful usage information
--version print out version information
-h, --help print out helpful usage information
--version print out version information
-v, --verbose turn on debugging output
```

Expand Down
28 changes: 27 additions & 1 deletion forge/db/controllers/AccessToken.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
const { Op } = require('sequelize')

const { generateToken, sha256 } = require('../utils')
const { generateToken, sha256, randomPhrase } = require('../utils')

const DEFAULT_TOKEN_SESSION_EXPIRY = 1000 * 60 * 30 // 30 mins session - with refresh token support

const DEFAULT_DEVICE_OTC_EXPIRY = 1000 * 60 * 60 * 24 // 24 hours

module.exports = {
/**
* Create an AccessToken for the given project.
Expand Down Expand Up @@ -78,6 +80,30 @@ module.exports = {
return { token }
},

createDeviceOTC: async function (app, device) {
const existing = await app.db.models.AccessToken.findOne({
where: {
ownerId: '' + device.id,
ownerType: 'device',
scope: 'device:otc'
}
})
if (existing) {
await existing.destroy()
}
const otc = randomPhrase(3, 2, 15, '-') // 3 words, min 2 chars, max 15 chars, separated by '-'
const token = Buffer.from(otc).toString('base64')
const data = {
token,
expiresAt: Date.now() + DEFAULT_DEVICE_OTC_EXPIRY,
scope: 'device:otc',
ownerId: '' + device.id,
ownerType: 'device'
}
await app.db.models.AccessToken.create(data)
return { otc }
},

/**
* Create an AccessToken for the editor.
*/
Expand Down
8 changes: 6 additions & 2 deletions forge/db/models/Device.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ module.exports = {
finders: function (M) {
return {
instance: {
async refreshAuthTokens () {
async refreshAuthTokens ({ refreshOTC = false } = {}) {
const accessToken = await Controllers.AccessToken.createTokenForDevice(this)
const credentialSecret = crypto.randomBytes(32).toString('hex')
this.credentialSecret = credentialSecret
Expand All @@ -114,6 +114,10 @@ module.exports = {
token: accessToken.token,
credentialSecret
}
if (refreshOTC) {
const oneTimeCode = await Controllers.AccessToken.createDeviceOTC(this)
result.otc = oneTimeCode.otc
}
const broker = await Controllers.BrokerClient.createClientForDevice(this)
if (broker) {
result.broker = broker
Expand All @@ -122,7 +126,7 @@ module.exports = {
},
async getAccessToken () {
return M.AccessToken.findOne({
where: { ownerId: '' + this.id, ownerType: 'device' }
where: { ownerId: '' + this.id, ownerType: 'device', scope: 'device' }
})
},
async updateSettingsHash (settings) {
Expand Down
42 changes: 41 additions & 1 deletion forge/db/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,13 @@ const sha256 = value => crypto.createHash('sha256').update(value).digest().toStr

let app

/** @type {typeof import('random-words')} */
let randomWords

(async () => {
randomWords = (await import('random-words'))
})()

/**
* Generate a properly formed where-object for sequelize findAll, that applies
* the required pagination, search and filter logic
Expand Down Expand Up @@ -115,6 +122,37 @@ function getCanonicalEmail (email, options = { removeDotsForDomains: ['gmail.',
return `${local}@${domain}`
}

/**
* Generate a random 1 or more random strings
* @param {Number} [count=1] - Number of strings to generate
* @param {Number} [minLength=2] - Minimum length of each string
* @param {Number} [maxLength=15] - Maximum length of each string
* @param {Number} [wordsPerString=1] - Number of words in each string
* @returns {Array<String>} - Array of random strings
*/
function randomStrings (count = 1, minLength = 2, maxLength = 15, wordsPerString = 1) {
const words = randomWords.generate({
exactly: count || 1,
minLength: minLength || 2,
maxLength: maxLength || 15,
wordsPerString: wordsPerString || 1
})
words.sort(() => Math.random() - 0.5) // a bit of extra randomness additional to the default behaviour of `random-words` lib method
return words
}

/**
* Generate a random phrase
* @param {Number} [wordCount=3] - Number of words in the phrase
* @param {Number} [minLength=2] - Minimum length of each word
* @param {Number} [maxLength=15] - Maximum length of each word
* @param {String} [separator='-'] - Separator between words
* @returns {String} - Random phrase
*/
function randomPhrase (wordCount = 3, minLength = 2, maxLength = 15, separator = '-') {
return randomStrings(wordCount, minLength, maxLength).join(separator)
}

module.exports = {
init: _app => { app = _app },
generateToken: (length, prefix) => (prefix ? prefix + '_' : '') + base64URLEncode(crypto.randomBytes(length || 32)),
Expand Down Expand Up @@ -143,5 +181,7 @@ module.exports = {
return hashids[type]
},
buildPaginationSearchClause,
getCanonicalEmail
getCanonicalEmail,
randomStrings,
randomPhrase
}
Loading
Loading