Skip to content

Commit 845cec1

Browse files
authored
Merge branch 'main' into ldap-group-support
2 parents dbe5dbe + 8c97fec commit 845cec1

24 files changed

Lines changed: 721 additions & 51 deletions

File tree

forge/auditLog/platform.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,9 @@ module.exports = {
5656
['users', 'teams', 'projects', 'devices'].includes(resource) || (resource = resource || 'unknown')
5757
const info = { resource, count, limit }
5858
await log('platform.license.overage', actionedBy, generateBody({ error, info }))
59+
},
60+
async expired (actionedBy, error, license) {
61+
await log('platform.license.expired', actionedBy, generateBody({ error, license }))
5962
}
6063
},
6164
team: {

forge/comms/authRoutes.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ module.exports = async function (app) {
2323
}
2424
}
2525
}, async (request, response) => {
26+
if (app.license.active() && app.license.status().expired) {
27+
response.status(401).send()
28+
return
29+
}
2630
const isValid = await app.db.controllers.BrokerClient.authenticateCredentials(
2731
request.body.username,
2832
request.body.password
@@ -51,6 +55,10 @@ module.exports = async function (app) {
5155
}
5256
}
5357
}, async (request, response) => {
58+
if (app.license.active() && app.license.status().expired) {
59+
response.status(401).send()
60+
return
61+
}
5462
const allowed = await app.comms.aclManager.verify(request.body.username, request.body.topic, request.body.acc)
5563
// ↓ Useful for debugging ↓
5664
// console.warn(`${allowed ? 'ALLOWED' : 'FORBIDDEN'}! ACL check: '${request.body.topic}' for user ${request.body.username} (${request.body.acc === 2 ? 'PUB' : 'SUB'})`)

forge/containers/wrapper.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ module.exports = {
6767
* @returns {Promise} Resolves when the start request has been *accepted*.
6868
*/
6969
start: async (project) => {
70+
if (this._app.license.active() && this._app.license.status().expired) {
71+
this._app.log.error({
72+
code: 'license_expired',
73+
error: `Failed to start project ${project.id}: License expired`
74+
})
75+
project.state = 'suspended'
76+
await project.save()
77+
throw new Error('License Expired')
78+
}
79+
7080
if (this._isBillingEnabled()) {
7181
await this._subscriptionHandler.addProject(project)
7282
}

forge/db/models/Device.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ module.exports = {
7878
if (devices.count >= devices.limit) {
7979
throw new Error('license limit reached')
8080
}
81+
} else {
82+
if (app.license.status().expired) {
83+
throw new Error('license expired')
84+
}
8185
}
8286
},
8387
afterCreate: async (device, options) => {

forge/db/models/Notification.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ module.exports = {
5353
where: {
5454
reference,
5555
UserId: user.id
56-
}
56+
},
57+
order: [['id', 'DESC']]
5758
})
5859
},
5960
forUser: async (user, pagination = {}) => {

forge/db/models/Team.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,23 @@ module.exports = {
274274
// In this case the findAll above will return an array that includes null, this needs to be guarded against
275275
return owners.filter((owner) => owner !== null)
276276
},
277+
/**
278+
* Get all members of the team optionally filtered by `role` array
279+
* @param {Array<Number> | null} roleFilter - Array of roles to filter by
280+
* @example
281+
* // Get all members of the team
282+
* const members = await team.getTeamMembers()
283+
* @example
284+
* // Get viewers only
285+
* const viewers = await team.getTeamMembers([Roles.Viewer])
286+
*/
287+
getTeamMembers: async function (roleFilter = null) {
288+
const where = { TeamId: this.id }
289+
if (roleFilter && Array.isArray(roleFilter)) {
290+
where.role = roleFilter
291+
}
292+
return (await M.TeamMember.findAll({ where, include: M.User })).filter(tm => tm && tm.User).map(tm => tm.User)
293+
},
277294
memberCount: async function (role) {
278295
const where = {
279296
TeamId: this.id

forge/ee/emailTemplates/LicenseExpired.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ module.exports = {
99
1010
Your FlowFuse License has now expired.
1111
12+
All running instances have been suspended and can not be restarted until
13+
a new license is applied.
14+
1215
We hope you will get in touch to renew your license and continue to enjoy the
1316
FlowFuse experience.
1417
@@ -24,6 +27,9 @@ Your friendly FlowFuse Team
2427
2528
<p>Your FlowFuse License has now expired.</p>
2629
30+
<p>All running instances have been suspended and can not be restarted until
31+
a new license is applied.</p>
32+
2733
<p>We hope you will get in touch to renew your license and continue to enjoy the
2834
FlowFuse experience.</p>
2935

forge/ee/emailTemplates/LicenseReminder.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ This is a friendly reminder that your FlowFuse License will expire in
1414
You can check the status of your license by logging into your account at
1515
{{{forgeURL}}}.
1616
17+
When your license expires all running instances will be suspended and can
18+
not be restarted until a new license is applied.
19+
1720
We hope you will get in touch to renew your license and continue to enjoy the
1821
FlowFuse experience.
1922
@@ -30,6 +33,9 @@ Your friendly FlowFuse Team
3033
<p>You can check the status of your license by logging into your account at
3134
{{{forgeURL}}}.</p>
3235
36+
<p>When your license expires all running instances will be suspended and can
37+
not be restarted until a new license is applied.</p>
38+
3339
<p>We hope you will get in touch to renew your license and continue to enjoy the
3440
FlowFuse experience.</p>
3541

forge/housekeeper/tasks/licenseCheck.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
const { Op } = require('sequelize')
12
module.exports = {
23
name: 'licenseCheck',
34
startup: false,
@@ -26,6 +27,31 @@ module.exports = {
2627
if (isExpiryDate || isSunday(today)) {
2728
await emailAdmins(app, 'LicenseExpired', {})
2829
}
30+
// Log everyday the license is expired
31+
app.auditLog.Platform.platform.license.expired('system', null, app.license.get())
32+
33+
// get list of running Instances
34+
const projectList = await app.db.models.Project.findAll({
35+
attributes: [
36+
'id',
37+
'state',
38+
'ProjectStackId',
39+
'TeamId'
40+
],
41+
where: {
42+
state: {
43+
[Op.eq]: 'running'
44+
}
45+
}
46+
})
47+
// Shut down all running projects
48+
projectList.forEach(async (project) => {
49+
try {
50+
await app.containers.stop(project)
51+
} catch (err) {
52+
// do we need to log a failure?
53+
}
54+
})
2955
}
3056
}
3157
}

forge/notifications/index.js

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,36 @@ module.exports = fp(async function (app, _opts) {
77
* @param {string} type the type of the notification
88
* @param {Object} data meta data for the notification - specific to the type
99
* @param {string} reference a key that can be used to lookup this notification, for example: `invite:HASHID`
10-
*
10+
* @param {Object} [options]
11+
* @param {boolean} [options.upsert] if true, updates the existing notification with the same reference & adds/increments `data.meta.counter`
12+
* @param {boolean} [options.supersede] if true, marks existing notification (with the same reference) as read & adds a new one
1113
*/
12-
async function send (user, type, data, reference = null) {
14+
async function send (user, type, data, reference = null, options = null) {
15+
if (reference && options && typeof options === 'object') {
16+
if (options.upsert) {
17+
const existing = await app.db.models.Notification.byReference(reference, user)
18+
if (existing && !existing.read) {
19+
const updatedData = Object.assign({}, existing.data, data)
20+
if (!updatedData.meta || typeof updatedData.meta !== 'object') {
21+
updatedData.meta = {}
22+
}
23+
if (typeof updatedData.meta.counter === 'number') {
24+
updatedData.meta.counter += 1
25+
} else {
26+
updatedData.meta.counter = 2 // if notification already exists, then this is the 2nd occurrence!
27+
}
28+
await existing.update({ data: updatedData })
29+
return existing
30+
}
31+
} else if (options.supersede) {
32+
const existing = await app.db.models.Notification.byReference(reference, user)
33+
if (existing && !existing.read) {
34+
existing.read = true
35+
await existing.save()
36+
}
37+
}
38+
}
39+
1340
return app.db.models.Notification.create({
1441
UserId: user.id,
1542
type,

0 commit comments

Comments
 (0)