diff --git a/app/assets/app/dash/NavBar.component.js b/app/assets/app/dash/NavBar.component.js
index e5eb349..1729c5c 100644
--- a/app/assets/app/dash/NavBar.component.js
+++ b/app/assets/app/dash/NavBar.component.js
@@ -38,6 +38,7 @@ const template = `
My Profile
API Tokens
+ System Announcements
Sign-Out of {{ app_name }}
@@ -64,6 +65,7 @@ export default class NavBarComponent extends Component {
async vue_on_create() {
this.can.api_tokens = await session.check_permissions('v1:reflect:tokens:list')
+ this.can.messages = await session.check_permissions('v1:message:banners:create')
this.$forceUpdate()
}
diff --git a/app/assets/app/resource/system/Announcement.resource.js b/app/assets/app/resource/system/Announcement.resource.js
new file mode 100644
index 0000000..869b315
--- /dev/null
+++ b/app/assets/app/resource/system/Announcement.resource.js
@@ -0,0 +1,98 @@
+import CRUDBase from '../CRUDBase.js'
+
+class AnnouncementResource extends CRUDBase {
+ endpoint = '/api/v1/system/announcements'
+ required_fields = ['user_ids', 'group_ids', 'title', 'message', 'type']
+ permission_base = 'v1:system:announcements'
+
+ item = 'System Announcement'
+ plural = 'System Announcements'
+
+ listing_definition = {
+ display: `
+ System announcements are administrative messages that you want all or some of your users to see. These messages can be delivered via e-mail, as a message after login, or as a system banner announcement.
+ `,
+ columns: [
+ {
+ name: 'Title',
+ field: 'title',
+ },
+ {
+ name: 'Message',
+ field: 'message',
+ renderer: (message) => String(message).slice(0, 150),
+ },
+ ],
+ actions: [
+ {
+ type: 'resource',
+ position: 'main',
+ action: 'insert',
+ text: 'Create New',
+ color: 'success',
+ },
+ {
+ type: 'resource',
+ position: 'row',
+ action: 'delete',
+ icon: 'fa fa-times',
+ color: 'danger',
+ confirm: true,
+ },
+ ],
+ }
+
+ form_definition = {
+ fields: [
+ {
+ name: 'Title',
+ field: 'title',
+ type: 'text',
+ },
+ {
+ name: 'Message',
+ field: 'message',
+ type: 'textarea',
+ },
+ {
+ name: 'Users',
+ field: 'user_ids',
+ type: 'select.dynamic.multiple',
+ options: {
+ resource: 'auth/User',
+ display: (user) => `${user.last_name}, ${user.first_name} (${user.uid})`,
+ value: 'id',
+ },
+ },
+ {
+ name: 'Groups',
+ field: 'group_ids',
+ type: 'select.dynamic.multiple',
+ options: {
+ resource: 'auth/Group',
+ display: (group) => `${group.name}`,
+ value: 'id',
+ },
+ },
+ {
+ name: 'Type',
+ field: 'type',
+ type: 'select',
+ options: [
+ { display: 'Login Intercept', value: 'login' },
+ { display: 'E-Mail', value: 'email' },
+ { display: 'System Banner', value: 'banner' },
+ ],
+ },
+ ],
+ handlers: {
+ insert: {
+ action: 'redirect',
+ next: '/dash/c/listing/system/Announcement',
+ },
+ }
+ }
+}
+
+const system_announcement = new AnnouncementResource()
+export { system_announcement }
diff --git a/app/controllers/api/v1/Message.controller.js b/app/controllers/api/v1/Message.controller.js
index 20cc2ec..7f665ba 100644
--- a/app/controllers/api/v1/Message.controller.js
+++ b/app/controllers/api/v1/Message.controller.js
@@ -40,6 +40,26 @@ class MessageController extends Controller {
await message.dismiss()
return res.api()
}
+
+ async create_banner(req, res, next) {
+ // expires, display_type = info, message, user_id?
+ const expires = req.body.expires
+ const display_type = req.body.display_type || 'info'
+ const message = req.body.message
+ const user_ids = req.body.user_ids
+
+ if ( !expires )
+ return res.status(400)
+ .message(`${req.T('api.missing_field')} expires`)
+ .api()
+
+ if ( !message )
+ return res.status(400)
+ .message(`${req.T('api.missing_field')} message`)
+ .api()
+
+
+ }
}
module.exports = exports = MessageController
diff --git a/app/controllers/api/v1/System.controller.js b/app/controllers/api/v1/System.controller.js
new file mode 100644
index 0000000..699f448
--- /dev/null
+++ b/app/controllers/api/v1/System.controller.js
@@ -0,0 +1,84 @@
+const { Controller } = require('libflitter')
+
+class ReflectController extends Controller {
+ static get services() {
+ return [...super.services, 'routers', 'models', 'activity']
+ }
+
+ async get_announcements(req, res, next) {
+ const Announcement = this.models.get('system:Announcement')
+ const announcements = await Announcement.find()
+
+ const data = []
+ for ( const announcement of announcements ) {
+ data.push(await announcement.to_api())
+ }
+
+ return res.api(data)
+ }
+
+ async get_announcement(req, res, next) {
+ const Announcement = this.models.get('system:Announcement')
+ const announcement = await Announcement.findById(req.params.id)
+
+ if ( !announcement )
+ return res.status(404)
+ .message(req.T('api.announcement_not_found'))
+ .api()
+
+ return res.api(await announcement.to_api())
+ }
+
+ async create_announcement(req, res, next) {
+ const Announcement = this.models.get('system:Announcement')
+
+ const required_fields = ['title', 'message', 'user_ids', 'group_ids', 'type']
+ for ( const field of required_fields ) {
+ if ( !req.body[field] )
+ return res.status(400)
+ .message(`${req.T('api.missing_field')} ${field}`)
+ .api()
+ }
+
+ if ( !Array.isArray(req.body.user_ids) )
+ return res.status(400)
+ .message(`${req.T('api.improper_field')} user_ids`)
+ .api()
+
+ if ( !Array.isArray(req.body.group_ids) )
+ return res.status(400)
+ .message(`${req.T('api.improper_field')} group_ids`)
+ .api()
+
+ if ( !['email', 'login', 'banner'].includes(req.body.type) )
+ return res.status(400)
+ .message(`${req.T('api.improper_field')} type`)
+ .api()
+
+ const announcement = new Announcement({
+ title: req.body.title,
+ message: req.body.message,
+ user_ids: req.body.user_ids,
+ group_ids: req.body.group_ids,
+ type: req.body.type,
+ })
+
+ await announcement.save()
+ return res.api(await announcement.to_api())
+ }
+
+ async delete_announcement(req, res, next) {
+ const Announcement = this.models.get('system:Announcement')
+ const announcement = await Announcement.findById(req.params.id)
+
+ if ( !announcement )
+ return res.status(404)
+ .message(req.T('api.announcement_not_found'))
+ .api()
+
+ await announcement.delete()
+ return res.api()
+ }
+}
+
+module.exports = exports = ReflectController
diff --git a/app/models/oauth/Client.model.js b/app/models/oauth/Client.model.js
index 9aa8ee7..df4b48b 100644
--- a/app/models/oauth/Client.model.js
+++ b/app/models/oauth/Client.model.js
@@ -24,10 +24,6 @@ class ClientModel extends Model {
}
}
- can(scope) {
- return this.api_scopes.includes()
- }
-
async application() {
const Application = this.models.get('Application')
return Application.findOne({ active: true, oauth_client_ids: this.id })
diff --git a/app/models/system/Announcement.model.js b/app/models/system/Announcement.model.js
new file mode 100644
index 0000000..abceb38
--- /dev/null
+++ b/app/models/system/Announcement.model.js
@@ -0,0 +1,26 @@
+const { Model } = require('flitter-orm')
+
+class AnnouncementModel extends Model {
+ static get schema() {
+ return {
+ title: String,
+ message: String,
+ user_ids: [String],
+ group_ids: [String],
+ type: String, // login | email | banner
+ }
+ }
+
+ async to_api() {
+ return {
+ id: this.id,
+ title: this.title,
+ message: this.message,
+ user_ids: this.user_ids,
+ group_ids: this.group_ids,
+ type: this.type,
+ }
+ }
+}
+
+module.exports = exports = AnnouncementModel
diff --git a/app/routing/routers/api/v1/message.routes.js b/app/routing/routers/api/v1/message.routes.js
index bb2f674..e2d9b79 100644
--- a/app/routing/routers/api/v1/message.routes.js
+++ b/app/routing/routers/api/v1/message.routes.js
@@ -17,6 +17,10 @@ const message_routes = {
['middleware::api:Permission', { check: 'v1:message:banners:update' }],
'controller::api:v1:Message.read_banner',
],
+ '/banners': [
+ ['middleware::api:Permission', { check: 'v1:message:banners:create' }],
+ 'controller::api:v1:Message.create_banner',
+ ],
},
}
diff --git a/app/routing/routers/api/v1/system.routes.js b/app/routing/routers/api/v1/system.routes.js
new file mode 100644
index 0000000..cbbd2ae
--- /dev/null
+++ b/app/routing/routers/api/v1/system.routes.js
@@ -0,0 +1,34 @@
+const system_routes = {
+ prefix: '/api/v1/system',
+
+ middleware: [
+ 'auth:APIRoute'
+ ],
+
+ get: {
+ '/announcements': [
+ ['middleware::api:Permission', { check: 'v1:system:announcements:list' }],
+ 'controller::api:v1:System.get_announcements',
+ ],
+ '/announcements/:id': [
+ ['middleware::api:Permission', { check: 'v1:system:announcements:get' }],
+ 'controller::api:v1:System.get_announcement',
+ ],
+ },
+
+ post: {
+ '/announcements': [
+ ['middleware::api:Permission', { check: 'v1:system:announcements:create'}],
+ 'controller::api:v1:System.create_announcement',
+ ],
+ },
+
+ delete: {
+ '/announcements/:id': [
+ ['middleware::api:Permission', { check: 'v1:system:announcements:delete' }],
+ 'controller::api:v1:System.delete_announcement',
+ ],
+ },
+}
+
+module.exports = exports = system_routes
diff --git a/locale/en_US/api.locale.js b/locale/en_US/api.locale.js
index 7e8732e..3e68299 100644
--- a/locale/en_US/api.locale.js
+++ b/locale/en_US/api.locale.js
@@ -21,6 +21,8 @@ module.exports = exports = {
app_pw_not_found: 'App password not found with that UUID.',
+ announcement_not_found: 'Announcement not found with that ID.',
+
invalid_ldap_client_id: 'Invalid ldap_client_id:',
invalid_oauth_client_id: 'Invalid oauth_client_id:',
invalid_saml_service_provider_id: 'Invalid saml_service_provider_id:',