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

Generate a default admin user #2443

Merged
merged 13 commits into from
Jul 28, 2023
1 change: 1 addition & 0 deletions docs/install/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Option | Description
`base_url` | The url to access the platform. This defaults to `http://localhost:3000` which means a number of internally generated URLs will only work when browsing on the same device as is running the platform. To be able to access the platform remotely, replace `localhost` with the ip address of the device running FlowForge.
`domain` | The domain that instance names will be pre-pended to on Docker & Kubernetes platforms to create a hostname to access the instance. A wildcard DNS A record should point be configured to point to the FlowForge entry IP Address.
`support_contact` | a URL or string with contact details for the administrator e.g `mailto:support@example.com` or `https://support.example.com` . Defaults to the email address of the first admin user or `the administrator` if no email address set.
`create_admin` | If set to `true` will create a default admin user on first run, the username/password is written to the logs .Default: `false`
hardillb marked this conversation as resolved.
Show resolved Hide resolved


NOTE: Changing the `base_url` and `domain` after Node-RED instances have been created is possible, but the original hostname and domain must remain active in order to access the instances and for an them to be able to access the FlowForge resources.
Expand Down
25 changes: 25 additions & 0 deletions forge/routes/setup/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@
* @namespace setup
* @memberof forge.routes
*/
const crypto = require('crypto')
const fs = require('fs/promises')
const path = require('path')

const setupApp = path.join(__dirname, '../../../frontend/dist-setup/setup.html')

const generatePassword = () => {
const charList = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!@-#$'
return Array.from(crypto.randomFillSync(new Uint32Array(8))).map(x => charList[x % charList.length]).join('')
}

module.exports = async function (app) {
let cachedSetupFile

Expand All @@ -19,6 +25,25 @@ module.exports = async function (app) {
reply.redirect('/')
return
}

if (app.config.create_admin) {
if (await app.db.models.User.count() === 0) {
const password = generatePassword()
await app.db.models.User.create({
username: 'admin',
hardillb marked this conversation as resolved.
Show resolved Hide resolved
name: 'Default Admin',
email: 'admin@example.com',
email_verified: true,
password,
admin: true,
password_expired: true
})
app.log.info('[SETUP] Created default Admin User')
app.log.info('[SETUP] username: admin')
hardillb marked this conversation as resolved.
Show resolved Hide resolved
app.log.info(`[SETUP] password ${password}`)
hardillb marked this conversation as resolved.
Show resolved Hide resolved
}
}

const csrfToken = await reply.generateCsrf()
if (!cachedSetupFile) {
cachedSetupFile = await fs.readFile(setupApp, 'utf-8')
Expand Down
62 changes: 62 additions & 0 deletions test/system/002-setup-admin_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const should = require('should') // eslint-disable-line
const FF_UTIL = require('flowforge-test-utils')
const { LocalTransport } = require('flowforge-test-utils/forge/postoffice/localTransport.js')

describe('Default Admin User', function () {
// forge - this will be the running FF application we are testing
let forge
// inbox - a local transport we can use to capture email without an SMTP server
const inbox = new LocalTransport()

before(async function () {
// Create the FF application with a suitable test configuration
forge = await FF_UTIL.setupApp({
telemetry: { enabled: false },
driver: {
type: 'stub'
},
db: {
type: 'sqlite',
storage: ':memory:'
},
email: {
enabled: true,
transport: inbox
},
create_admin: true
})
})

after(function () {
return forge.close()
})

let CSRF_TOKEN
const CSRF_COOKIE = {}

it('should create admin user on start of wizard', async function () {
const response = await forge.inject({
method: 'GET',
url: '/setup'
})

const match = /SETUP_CSRF_TOKEN = "(.*?)"/.exec(response.payload)
CSRF_TOKEN = match[1]
should.exist(CSRF_TOKEN)

const cookies = response.cookies
cookies.should.have.lengthOf(1)
cookies[0].should.have.property('name', '_csrf')
CSRF_COOKIE[cookies[0].name] = cookies[0].value

const admin = await forge.db.models.User.findOne({
where: {
admin: true
}
})

admin.should.have.property('username', 'admin')
hardillb marked this conversation as resolved.
Show resolved Hide resolved
admin.should.have.property('email', 'admin@example.com')
admin.should.have.property('password_expired', true)
})
})