10 Commits

14 changed files with 297 additions and 33 deletions

View File

@@ -0,0 +1,202 @@
<?php
/*
Plugin Name: Spacedeck
Plugin URI: https://spacedeck.com
description: Embed Spacedeck Whiteboards in Wordpress Posts
Version: 1.0
Author: MNT Research GmbH
Author URI: https://mntre.com
License: GPLv3+
*/
add_option("spacedeck_settings");
function spacedeck_apicall($method, $path, $data) {
$spacedeck_api_base_uri = get_option("spacedeck_settings")[spacedeck_api_base_uri];
$spacedeck_api_key = get_option("spacedeck_settings")[spacedeck_api_key];
$data_string = json_encode($data);
$url = $spacedeck_api_base_uri . $path;
$headers = array(
'Content-Type' => 'application/json',
'X-Spacedeck-API-Token' => $spacedeck_api_key
);
$payload = array(
'method' => $method,
'timeout' => 10,
'blocking' => true,
'headers' => $headers,
'body' => $data_string
);
// echo("<p>payload:</p><pre>");
// print_r($payload);
// echo("</pre>");
$result = wp_remote_post($url, $payload);
if (is_wp_error($result)) {
return $result;
}
$result = json_decode($result[body], true);
// echo("<p>decoded:</p><pre>");
// print_r($result);
// echo("</pre>");
return $result;
}
function spacedeck_embed_space($slug, $width = '90%', $height = '800', $parent_space_id = null) {
$spacedeck_frontend_base_uri = get_option("spacedeck_settings")[spacedeck_frontend_base_uri];
// try to find the space identified by slug
$space = spacedeck_apicall("GET", "/spaces/" . $slug, array());
if (is_wp_error($space)) {
$error = $response->get_error_message();
return("<p><b>Spacedeck: WP Error looking up Space: $error</b></p>");
} else if ($space[error] && $space[error]!="space_not_found") {
return("<p><b>Spacedeck: Error looking up Space: $space[error]</b></p>");
}
// if it doesn't exist, create it:
if ($space[error]=="space_not_found") {
$data = array(
"name" => $slug,
"edit_slug" => $slug
);
if ($parent_space_id) {
$data[parent_space_id] = $parent_space_id;
}
$space = spacedeck_apicall("POST", "/spaces", $data);
if (is_wp_error($space)) {
$error = $response->get_error_message();
return("<p><b>Spacedeck: WP Error creating Space: $error</b></p>");
} else if ($space[error]) {
return("<p><b>Spacedeck: Error creating Space: $space[error]</b></p>");
}
}
if (is_wp_error($space)) {
$error = $response->get_error_message();
return("<p><b>Spacedeck: WP Error embedding Space: $error</b></p>");
} else if (!$space || $space[error]) {
return("<p><b>Spacedeck: Error embedding Space. Is your API key set up correctly?</b></p>");
}
$space_auth = $space[edit_hash];
// return a piece of html (iframe) embedding the space
$uri = $spacedeck_frontend_base_uri . '/spaces/' . $slug . '?embedded=1&spaceAuth=' . $space_auth;
$html = "<iframe src='$uri' class='spacedeck' width='$width' height='$height' style='max-width:100%' frameborder='0' allowFullScreen='true'></iframe>";
return $html;
}
function spacedeck_shortcode($attrs) {
extract(shortcode_atts(array(
'id' => 'none',
'parent_space_id' => null,
'width' => '100%',
'height' => '800'
), $attrs));
$w = $attrs[width];
$h = $attrs[height];
if (!$w) $w = '100%';
if (!$h) $h = 800;
return spacedeck_embed_space($attrs[id],$w,$h,$attrs[parent_space_id]);
}
add_shortcode('spacedeck_space', 'spacedeck_shortcode');
add_action('admin_menu', 'spacedeck_add_admin_menu');
add_action('admin_init', 'spacedeck_settings_init');
function spacedeck_add_admin_menu() {
add_options_page('spacedeck', 'Spacedeck', 'manage_options', 'spacedeck', 'spacedeck_options_page');
}
function spacedeck_settings_init() {
register_setting('pluginPage', 'spacedeck_settings');
add_settings_section(
'spacedeck_pluginPage_section',
'Spacedeck Settings',
'spacedeck_settings_section_callback',
'pluginPage'
);
add_settings_field(
'spacedeck_text_field_0',
'API key',
'spacedeck_text_field_0_render',
'pluginPage',
'spacedeck_pluginPage_section'
);
add_settings_field(
'spacedeck_text_field_1',
'API base URL',
'spacedeck_text_field_1_render',
'pluginPage',
'spacedeck_pluginPage_section'
);
add_settings_field(
'spacedeck_text_field_2',
'Frontend base URL',
'spacedeck_text_field_2_render',
'pluginPage',
'spacedeck_pluginPage_section'
);
}
function spacedeck_text_field_0_render() {
$opts = get_option('spacedeck_settings');
?>
<input type='text' name='spacedeck_settings[spacedeck_api_key]' value='<?php echo $opts[spacedeck_api_key]; ?>'>
<?php
}
function spacedeck_text_field_1_render() {
$opts = get_option('spacedeck_settings');
?>
<input type='text' name='spacedeck_settings[spacedeck_api_base_uri]' value='<?php echo $opts[spacedeck_api_base_uri]; ?>'>
<?php
}
function spacedeck_text_field_2_render() {
$opts = get_option('spacedeck_settings');
?>
<input type='text' name='spacedeck_settings[spacedeck_frontend_base_uri]' value='<?php echo $opts[spacedeck_frontend_base_uri]; ?>'>
<?php
}
function spacedeck_settings_section_callback() {
echo '';
}
function spacedeck_options_page() {
?>
<form action='options.php' method='post'>
<?php
settings_fields('pluginPage');
do_settings_sections('pluginPage');
submit_button();
?>
</form>
<?php
}
?>

View File

@@ -4,8 +4,27 @@ const db = require('../models/db');
var config = require('config'); var config = require('config');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
// authentication via API token
const api_token = req.headers["x-spacedeck-api-token"];
if (api_token && api_token.length>7) {
db.User.findOne({where: {api_token: api_token}}).then(user => {
req.user = user;
next();
}).error(err => {
res.status(403).json({
"error": "invalid_api-token"
});
next();
});
return;
}
// authentication via session/cookie
const token = req.cookies["sdsession"]; const token = req.cookies["sdsession"];
if (token && token != "null" && token != null) { if (token && token != "null" && token != null) {
db.Session.findOne({where: {token: token}}) db.Session.findOne({where: {token: token}})
.then(session => { .then(session => {
@@ -28,7 +47,7 @@ module.exports = (req, res, next) => {
} else { } else {
res.send("Please clear your cookies and try again."); res.send("Please clear your cookies and try again.");
} }
} else { } else {
req["token"] = token; req["token"] = token;
req["user"] = user; req["user"] = user;
@@ -44,4 +63,3 @@ module.exports = (req, res, next) => {
next(); next();
} }
} }

View File

@@ -1,6 +1,7 @@
'use strict'; 'use strict';
const db = require('../models/db'); const db = require('../models/db');
const { Op } = require("sequelize");
var config = require('config'); var config = require('config');
module.exports = (req, res, next) => { module.exports = (req, res, next) => {
@@ -53,8 +54,12 @@ module.exports = (req, res, next) => {
'email': 1 'email': 1
}; };
// find space by id or slug
db.Space.findOne({where: { db.Space.findOne({where: {
"_id": spaceId [Op.or]: [
{"_id": spaceId},
{"edit_slug": spaceId}
]
}}).then(function(space) { }}).then(function(space) {
if (space) { if (space) {

View File

@@ -42,6 +42,7 @@ module.exports = {
avatar_thumb_uri: Sequelize.STRING, avatar_thumb_uri: Sequelize.STRING,
confirmation_token: Sequelize.STRING, confirmation_token: Sequelize.STRING,
password_reset_token: Sequelize.STRING, password_reset_token: Sequelize.STRING,
api_token: Sequelize.STRING,
home_folder_id: Sequelize.STRING, home_folder_id: Sequelize.STRING,
prefs_language: Sequelize.STRING, prefs_language: Sequelize.STRING,
prefs_email_notifications: Sequelize.STRING, prefs_email_notifications: Sequelize.STRING,
@@ -50,6 +51,17 @@ module.exports = {
updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW} updated_at: {type: Sequelize.DATE, defaultValue: Sequelize.NOW}
}), }),
CreatorSafeInclude: function(db) {
return {
model: this.User,
as: 'creator',
attributes: ['_id','email','nickname',
'avatar_original_uri',
'avatar_thumb_uri',
'created_at','updated_at']
};
},
Session: sequelize.define('session', { Session: sequelize.define('session', {
token: {type: Sequelize.STRING, primaryKey: true}, token: {type: Sequelize.STRING, primaryKey: true},
user_id: Sequelize.STRING, user_id: Sequelize.STRING,

View File

@@ -0,0 +1,19 @@
'use strict';
module.exports = {
up: function(migration, DataTypes) {
return Promise.all([
migration.addColumn('users', 'api_token',
{
type: DataTypes.STRING
}
)
])
},
down: function(migration, DataTypes) {
return Promise.all([
migration.removeColumn('users', 'api_token')
])
}
}

View File

@@ -100,13 +100,13 @@ var SpacedeckSpaces = {
}, },
load_space: function(space_id, on_success, on_error) { load_space: function(space_id, on_success, on_error) {
console.log("load space: ", space_id);
this.folder_spaces_filter=""; this.folder_spaces_filter="";
this.folder_spaces_search=""; this.folder_spaces_search="";
space_auth = get_query_param("spaceAuth"); space_auth = get_query_param("spaceAuth");
this.embedded = !!(get_query_param("embedded"));
var userReady = function() { var userReady = function() {
this.close_dropdown(); this.close_dropdown();
@@ -649,6 +649,13 @@ var SpacedeckSpaces = {
this.present_mode = !this.present_mode; this.present_mode = !this.present_mode;
if (this.present_mode) { if (this.present_mode) {
//this.go_to_first_zone(); //this.go_to_first_zone();
if (this.embedded) {
document.documentElement.requestFullscreen();
}
} else {
if (this.embedded) {
document.exitFullscreen();
}
} }
}, },

View File

@@ -17,7 +17,6 @@ SpacedeckUsers = {
loading_user: false, loading_user: false,
password_reset_confirm_error: "", password_reset_confirm_error: "",
password_reset_error: "", password_reset_error: "",
}, },
methods:{ methods:{
load_user: function(on_success, on_error) { load_user: function(on_success, on_error) {

View File

@@ -14,6 +14,7 @@ function boot_spacedeck() {
account: "profile", account: "profile",
logged_in: false, logged_in: false,
guest_nickname: null, guest_nickname: null,
embedded: false,
user: {}, user: {},
active_profile: null, active_profile: null,

View File

@@ -14548,9 +14548,6 @@ button.close {
position: relative; position: relative;
overflow: scroll; } overflow: scroll; }
.board .wrapper { .board .wrapper {
border: 4px solid black;
transition-duration: 0.25s;
transition-property: width, height, background-color;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: cover; } background-size: cover; }

View File

@@ -71,7 +71,7 @@ router.get('/', function(req, res, next) {
{"_id": {[Op.in]: spaceIds}}, {"_id": {[Op.in]: spaceIds}},
{"parent_space_id": {[Op.in]: spaceIds}}], {"parent_space_id": {[Op.in]: spaceIds}}],
name: {[Op.like]: "%"+req.query.search+"%"} name: {[Op.like]: "%"+req.query.search+"%"}
}, include: ['creator']}; }, include: [db.CreatorSafeInclude(db)]};
db.Space db.Space
.findAll(q) .findAll(q)
@@ -87,7 +87,6 @@ router.get('/', function(req, res, next) {
.findOne({where: { .findOne({where: {
_id: req.query.parent_space_id _id: req.query.parent_space_id
}}) }})
//.populate('creator', userMapping)
.then(function(space) { .then(function(space) {
if (space) { if (space) {
db.getUserRoleInSpace(space, req.user, function(role) { db.getUserRoleInSpace(space, req.user, function(role) {
@@ -101,7 +100,7 @@ router.get('/', function(req, res, next) {
db.Space db.Space
.findAll({where:{ .findAll({where:{
parent_space_id: req.query.parent_space_id parent_space_id: req.query.parent_space_id
}, include:['creator']}) }, include:[db.CreatorSafeInclude(db)]})
.then(function(spaces) { .then(function(spaces) {
res.status(200).json(spaces); res.status(200).json(spaces);
}); });
@@ -147,7 +146,7 @@ router.get('/', function(req, res, next) {
}; };
db.Space db.Space
.findAll({where: q, include: ['creator']}) .findAll({where: q, include: [db.CreatorSafeInclude(db)]})
.then(function(spaces) { .then(function(spaces) {
var updatedSpaces = spaces.map(function(s) { var updatedSpaces = spaces.map(function(s) {
var spaceObj = db.spaceToObject(s); var spaceObj = db.spaceToObject(s);
@@ -169,7 +168,7 @@ router.post('/', function(req, res, next) {
attrs._id = uuidv4(); attrs._id = uuidv4();
attrs.creator_id = req.user._id; attrs.creator_id = req.user._id;
attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7); attrs.edit_hash = crypto.randomBytes(64).toString('hex').substring(0, 7);
attrs.edit_slug = slug(attrs.name); attrs.edit_slug = attrs.edit_slug || slug(attrs.name);
attrs.access_mode = "private"; attrs.access_mode = "private";
db.Space.create(attrs).then(createdSpace => { db.Space.create(attrs).then(createdSpace => {
@@ -211,6 +210,7 @@ router.post('/', function(req, res, next) {
} }
}); });
} else { } else {
attrs.parent_space_id = req.user.home_folder_id;
createSpace(); createSpace();
} }

View File

@@ -71,18 +71,18 @@ app.use(bodyParser.urlencoded({
})); }));
app.use(cookieParser()); app.use(cookieParser());
app.use(helmet.frameguard()) //app.use(helmet.frameguard())
app.use(helmet.xssFilter()) //app.use(helmet.xssFilter())
app.use(helmet.hsts({ /*app.use(helmet.hsts({
maxAge: 7776000000, maxAge: 7776000000,
includeSubDomains: true includeSubDomains: true
})) }))*/
app.disable('x-powered-by'); app.disable('x-powered-by');
app.use(helmet.noSniff()) //app.use(helmet.noSniff())
//app.use(require("./middlewares/error_helpers")); //app.use(require("./middlewares/error_helpers"));
app.use(require("./middlewares/session"));
//app.use(require("./middlewares/cors")); //app.use(require("./middlewares/cors"));
app.use(require("./middlewares/session"));
app.use(require("./middlewares/i18n")); app.use(require("./middlewares/i18n"));
app.use("/api", require("./middlewares/api_helpers")); app.use("/api", require("./middlewares/api_helpers"));
app.use('/api/spaces/:id', require("./middlewares/space_helpers")); app.use('/api/spaces/:id', require("./middlewares/space_helpers"));

View File

@@ -118,10 +118,6 @@
padding: 0 !important; padding: 0 !important;
.wrapper { .wrapper {
border: 4px solid black;
transition-duration: 0.25s;
transition-property: width, height, background-color;
background-repeat: no-repeat; background-repeat: no-repeat;
background-size: cover; background-size: cover;
} }

View File

@@ -55,6 +55,15 @@
</div> </div>
<div> <div>
<div class="form-group">
<label class="label">API Token</label>
<input
type="text"
id="api-token"
class="input input-white no-b"
v-model="user.api_token"
placeholder="secret key">
</div>
<div class="form-group"> <div class="form-group">
<label class="label" >[[__("profile_name")]]</label> <label class="label" >[[__("profile_name")]]</label>
@@ -67,18 +76,17 @@
<div class="form-group"> <div class="form-group">
<label class="label">[[__("profile_email")]]</label> <label class="label">[[__("profile_email")]]</label>
<input <input
id="new-email"
v-bind:class="{disabled: user.account_type=='google'}"
v-bind:disabled="user.account_type=='google'"
class="input input-white no-b"
type="email" type="email"
id="new-email"
class="input input-white no-b"
v-model="user.email" v-model="user.email"
v-on:change="user.email_changed=true" v-on:change="user.email_changed=true"
placeholder="mail@example.com"> placeholder="mail@example.com">
</div>
<button class="btn btn-md btn-dark" v-on:click=" save_user()" style="margin-top:20px">Save</button> <div class="form-group">
<button class="btn btn-md btn-dark" v-on:click="save_user()">Save</button>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -4,14 +4,14 @@
<a class="btn btn-icon btn-transparent" <a class="btn btn-icon btn-transparent"
title="[[__("home")]]" href="/spaces" title="[[__("home")]]" href="/spaces"
v-if="(!active_space.parent_space_id && !guest_nickname)"> v-if="(!active_space.parent_space_id && !guest_nickname && !embedded)">
<span class="icon icon-folder"></span> <span class="icon icon-folder"></span>
</a> </a>
<a class="btn btn-icon btn-dark" <a class="btn btn-icon btn-dark"
title="Parent Folder" title="Parent Folder"
href="/folders/{{active_space.parent_space_id}}" href="/folders/{{active_space.parent_space_id}}"
v-if="(active_space.parent_space_id && !guest_nickname)"> v-if="(active_space.parent_space_id && !guest_nickname && !embedded)">
<span class="icon icon-sd6 icon-svg"></span> <span class="icon icon-sd6 icon-svg"></span>
</a> </a>