Browse Source

Merged core back into main project

pull/73/head
NGPixel 8 years ago
parent
commit
1ced194cd2
34 changed files with 9453 additions and 45 deletions
  1. 1
      .eslintrc.json
  2. 14
      agent.js
  3. 53
      client/scss/app.scss
  4. 107
      client/scss/base/base.scss
  5. 327
      client/scss/base/colors.scss
  6. 2027
      client/scss/base/fonts.scss
  7. 142
      client/scss/base/mixins.scss
  8. 138
      client/scss/base/reset.scss
  9. 25
      client/scss/base/variables.scss
  10. 114
      client/scss/components/alert.scss
  11. 122
      client/scss/components/button.scss
  12. 26
      client/scss/components/footer.scss
  13. 213
      client/scss/components/form.scss
  14. 501
      client/scss/components/grid.scss
  15. 68
      client/scss/components/hero.scss
  16. 62
      client/scss/components/list.scss
  17. 355
      client/scss/components/markdown-content.scss
  18. 356
      client/scss/components/modal.scss
  19. 182
      client/scss/components/nav.scss
  20. 112
      client/scss/components/panel.scss
  21. 57
      client/scss/components/search.scss
  22. 83
      client/scss/components/sidebar.scss
  23. 90
      client/scss/components/table.scss
  24. 16
      client/scss/components/typography.scss
  25. 3486
      client/scss/libs/animate.scss
  26. 52
      client/scss/pages/_error.scss
  27. 306
      client/scss/pages/_login.scss
  28. 243
      libs/auth.js
  29. 58
      libs/config.js
  30. 120
      libs/rights.js
  31. 19
      middlewares/auth.js
  32. 2
      middlewares/security.js
  33. 12
      package.json
  34. 9
      server.js

1
.eslintrc.json

@ -26,7 +26,6 @@
"winston": true,
"ws": true,
"Mongoose": true,
"CORE_PATH": true,
"ROOTPATH": true,
"IS_DEBUG": true,
"PROCNAME": true

14
agent.js

@ -7,17 +7,16 @@
global.PROCNAME = 'AGENT'
global.ROOTPATH = __dirname
global.IS_DEBUG = process.env.NODE_ENV === 'development'
if (IS_DEBUG) {
global.CORE_PATH = ROOTPATH + '/../core/'
} else {
global.CORE_PATH = ROOTPATH + '/node_modules/requarks-core/'
}
let appconf = require('./libs/config')()
global.appconfig = appconf.config
global.appdata = appconf.data
// ----------------------------------------
// Load Winston
// ----------------------------------------
global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG)
global.winston = require('./libs/logger')(IS_DEBUG)
// ----------------------------------------
// Load global modules
@ -25,9 +24,6 @@ global.winston = require(CORE_PATH + 'core-libs/winston')(IS_DEBUG)
winston.info('[AGENT] Background Agent is initializing...')
let appconf = require(CORE_PATH + 'core-libs/config')()
global.appconfig = appconf.config
global.appdata = appconf.data
global.db = require('./libs/db').init()
global.upl = require('./libs/uploads-agent').init()
global.git = require('./libs/git').init()

53
client/scss/app.scss

@ -1,31 +1,38 @@
@charset "utf-8";
$primary: 'indigo';
@import 'core-client/scss/core';
@import 'core-client/scss/components/alert';
@import 'core-client/scss/components/button';
@import 'core-client/scss/components/footer';
@import 'core-client/scss/components/form';
@import 'core-client/scss/components/grid';
@import 'core-client/scss/components/hero';
@import 'core-client/scss/components/markdown-content';
@import 'core-client/scss/components/modal';
@import 'core-client/scss/components/nav';
@import 'core-client/scss/components/panel';
@import 'core-client/scss/components/search';
@import 'core-client/scss/components/sidebar';
@import 'core-client/scss/components/table';
@import 'core-client/scss/components/typography';
@import './libs/twemoji-awesome';
@import './libs/jquery-contextmenu';
@import "base/variables";
@import "base/colors";
@import "base/reset";
@import "base/mixins";
@import "base/fonts";
@import "base/base";
@import "libs/animate";
@import 'components/alert';
@import 'components/button';
@import 'components/footer';
@import 'components/form';
@import 'components/grid';
@import 'components/hero';
@import 'components/markdown-content';
@import 'components/modal';
@import 'components/nav';
@import 'components/panel';
@import 'components/search';
@import 'components/sidebar';
@import 'components/table';
@import 'components/typography';
@import 'libs/twemoji-awesome';
@import 'libs/jquery-contextmenu';
@import 'node_modules/highlight.js/styles/tomorrow';
@import 'node_modules/simplemde/dist/simplemde.min';
@import './components/_editor';
@import 'components/_editor';
@import './layout/_header';
//@import './layout/_content';
@import 'layout/_header';
//@import './pages/_account';
@import './pages/_welcome';
@import 'pages/_welcome';

107
client/scss/base/base.scss

@ -0,0 +1,107 @@
html {
box-sizing: border-box;
font-family: $core-font-standard;
}
*, *:before, *:after {
box-sizing: inherit;
}
[v-cloak], .is-hidden {
display: none;
}
body {
background-color: mc('blue-grey','100');
}
main {
background-color: #FFF;
}
a {
color: mc('indigo', '600');
text-decoration: none;
&:hover {
color: mc('indigo', '700');
text-decoration: underline;
}
}
// Container
.has-stickynav {
padding-top: 50px;
}
.container {
position: relative;
@include desktop {
margin: 0 auto;
max-width: 960px;
// Modifiers
&.is-fluid {
margin: 0;
max-width: none;
}
}
@include widescreen {
max-width: 1200px;
}
}
.content {
padding: 20px;
}
// Visibility
.is-hidden {
display: none !important;
}
.is-hidden-mobile {
@include mobile {
display: none !important;
}
}
.is-hidden-tablet {
@include tablet {
display: none !important;
}
}
.is-hidden-tablet-only {
@include tablet-only {
display: none !important;
}
}
.is-hidden-touch {
@include touch {
display: none !important;
}
}
.is-hidden-desktop {
@include desktop {
display: none !important;
}
}
.is-hidden-desktop-only {
@include desktop-only {
display: none !important;
}
}
.is-hidden-widescreen {
@include widescreen {
display: none !important;
}
}

327
client/scss/base/colors.scss

@ -0,0 +1,327 @@
$material-colors: (
'red': (
'50': #ffebee,
'100': #ffcdd2,
'200': #ef9a9a,
'300': #e57373,
'400': #ef5350,
'500': #f44336,
'600': #e53935,
'700': #d32f2f,
'800': #c62828,
'900': #b71c1c,
'a100': #ff8a80,
'a200': #ff5252,
'a400': #ff1744,
'a700': #d50000
),
'pink': (
'50': #fce4ec,
'100': #f8bbd0,
'200': #f48fb1,
'300': #f06292,
'400': #ec407a,
'500': #e91e63,
'600': #d81b60,
'700': #c2185b,
'800': #ad1457,
'900': #880e4f,
'a100': #ff80ab,
'a200': #ff4081,
'a400': #f50057,
'a700': #c51162
),
'purple': (
'50': #f3e5f5,
'100': #e1bee7,
'200': #ce93d8,
'300': #ba68c8,
'400': #ab47bc,
'500': #9c27b0,
'600': #8e24aa,
'700': #7b1fa2,
'800': #6a1b9a,
'900': #4a148c,
'a100': #ea80fc,
'a200': #e040fb,
'a400': #d500f9,
'a700': #aa00ff
),
'deep-purple': (
'50': #ede7f6,
'100': #d1c4e9,
'200': #b39ddb,
'300': #9575cd,
'400': #7e57c2,
'500': #673ab7,
'600': #5e35b1,
'700': #512da8,
'800': #4527a0,
'900': #311b92,
'a100': #b388ff,
'a200': #7c4dff,
'a400': #651fff,
'a700': #6200ea
),
'indigo': (
'50': #e8eaf6,
'100': #c5cae9,
'200': #9fa8da,
'300': #7986cb,
'400': #5c6bc0,
'500': #3f51b5,
'600': #3949ab,
'700': #303f9f,
'800': #283593,
'900': #1a237e,
'a100': #8c9eff,
'a200': #536dfe,
'a400': #3d5afe,
'a700': #304ffe
),
'blue': (
'50': #e3f2fd,
'100': #bbdefb,
'200': #90caf9,
'300': #64b5f6,
'400': #42a5f5,
'500': #2196f3,
'600': #1e88e5,
'700': #1976d2,
'800': #1565c0,
'900': #0d47a1,
'a100': #82b1ff,
'a200': #448aff,
'a400': #2979ff,
'a700': #2962ff
),
'light-blue': (
'50': #e1f5fe,
'100': #b3e5fc,
'200': #81d4fa,
'300': #4fc3f7,
'400': #29b6f6,
'500': #03a9f4,
'600': #039be5,
'700': #0288d1,
'800': #0277bd,
'900': #01579b,
'a100': #80d8ff,
'a200': #40c4ff,
'a400': #00b0ff,
'a700': #0091ea
),
'cyan': (
'50': #e0f7fa,
'100': #b2ebf2,
'200': #80deea,
'300': #4dd0e1,
'400': #26c6da,
'500': #00bcd4,
'600': #00acc1,
'700': #0097a7,
'800': #00838f,
'900': #006064,
'a100': #84ffff,
'a200': #18ffff,
'a400': #00e5ff,
'a700': #00b8d4
),
'teal': (
'50': #e0f2f1,
'100': #b2dfdb,
'200': #80cbc4,
'300': #4db6ac,
'400': #26a69a,
'500': #009688,
'600': #00897b,
'700': #00796b,
'800': #00695c,
'900': #004d40,
'a100': #a7ffeb,
'a200': #64ffda,
'a400': #1de9b6,
'a700': #00bfa5
),
'green': (
'50': #e8f5e9,
'100': #c8e6c9,
'200': #a5d6a7,
'300': #81c784,
'400': #66bb6a,
'500': #4caf50,
'600': #43a047,
'700': #388e3c,
'800': #2e7d32,
'900': #1b5e20,
'a100': #b9f6ca,
'a200': #69f0ae,
'a400': #00e676,
'a700': #00c853
),
'light-green': (
'50': #f1f8e9,
'100': #dcedc8,
'200': #c5e1a5,
'300': #aed581,
'400': #9ccc65,
'500': #8bc34a,
'600': #7cb342,
'700': #689f38,
'800': #558b2f,
'900': #33691e,
'a100': #ccff90,
'a200': #b2ff59,
'a400': #76ff03,
'a700': #64dd17
),
'lime': (
'50': #f9fbe7,
'100': #f0f4c3,
'200': #e6ee9c,
'300': #dce775,
'400': #d4e157,
'500': #cddc39,
'600': #c0ca33,
'700': #afb42b,
'800': #9e9d24,
'900': #827717,
'a100': #f4ff81,
'a200': #eeff41,
'a400': #c6ff00,
'a700': #aeea00
),
'yellow': (
'50': #fffde7,
'100': #fff9c4,
'200': #fff59d,
'300': #fff176,
'400': #ffee58,
'500': #ffeb3b,
'600': #fdd835,
'700': #fbc02d,
'800': #f9a825,
'900': #f57f17,
'a100': #ffff8d,
'a200': #ffff00,
'a400': #ffea00,
'a700': #ffd600
),
'amber': (
'50': #fff8e1,
'100': #ffecb3,
'200': #ffe082,
'300': #ffd54f,
'400': #ffca28,
'500': #ffc107,
'600': #ffb300,
'700': #ffa000,
'800': #ff8f00,
'900': #ff6f00,
'a100': #ffe57f,
'a200': #ffd740,
'a400': #ffc400,
'a700': #ffab00
),
'orange': (
'50': #fff3e0,
'100': #ffe0b2,
'200': #ffcc80,
'300': #ffb74d,
'400': #ffa726,
'500': #ff9800,
'600': #fb8c00,
'700': #f57c00,
'800': #ef6c00,
'900': #e65100,
'a100': #ffd180,
'a200': #ffab40,
'a400': #ff9100,
'a700': #ff6d00
),
'deep-orange': (
'50': #fbe9e7,
'100': #ffccbc,
'200': #ffab91,
'300': #ff8a65,
'400': #ff7043,
'500': #ff5722,
'600': #f4511e,
'700': #e64a19,
'800': #d84315,
'900': #bf360c,
'a100': #ff9e80,
'a200': #ff6e40,
'a400': #ff3d00,
'a700': #dd2c00
),
'brown': (
'50': #efebe9,
'100': #d7ccc8,
'200': #bcaaa4,
'300': #a1887f,
'400': #8d6e63,
'500': #795548,
'600': #6d4c41,
'700': #5d4037,
'800': #4e342e,
'900': #3e2723
),
'grey': (
'50': #fafafa,
'100': #f5f5f5,
'200': #eeeeee,
'300': #e0e0e0,
'400': #bdbdbd,
'500': #9e9e9e,
'600': #757575,
'700': #616161,
'800': #424242,
'900': #212121
),
'blue-grey': (
'50': #eceff1,
'100': #cfd8dc,
'200': #b0bec5,
'300': #90a4ae,
'400': #78909c,
'500': #607d8b,
'600': #546e7a,
'700': #455a64,
'800': #37474f,
'900': #263238,
'1000': #11171a
)
);
@function material-color($color-name, $color-variant: '500') {
$color: map-get(map-get($material-colors, $color-name),$color-variant);
@if $color {
@return $color;
} @else {
// Libsass still doesn't seem to support @error
@warn "=> ERROR: COLOR NOT FOUND! <= | Your $color-name, $color-variant combination did not match any of the values in the $material-colors map.";
}
}
@function mc($color-name, $color-variant: '500') {
@return material-color($color-name, $color-variant);
}

2027
client/scss/base/fonts.scss
File diff suppressed because it is too large
View File

142
client/scss/base/mixins.scss

@ -0,0 +1,142 @@
/**
* Clearfix
*
* @return {string} Clearfix attribute
*/
@mixin clearfix {
&:after {
content: "";
display: table;
clear: both;
}
}
/**
* Placeholder attribute for inputs
*
* @return {string} Placeholder attributes
*/
@mixin placeholder {
&::-webkit-input-placeholder {@content};
&::-moz-placeholder {@content}
&:-ms-input-placeholder {@content}
&:placeholder-shown {@content};
}
/**
* Spinner element
*
* @param {string} $color - Color
* @param {string} $dur - Animation Duration
* @param {int} $width - Width
* @param {int} $height [$width] - height
*
* @return {string} Spinner element
*/
@mixin spinner($color,$dur,$width,$height:$width) {
width: $width;
height: $height;
border-radius: 50%;
box-shadow:0 0 0 1px rgba(0,0,0,0.1), 2px 1px 0 $color;
@include prefix(animation, spin $dur linear infinite);
@include keyframes(spin) {
100%{
@include prefix(transform, rotate(360deg));
}
};
}
/**
* Prefixes for keyframes
*
* @param {string} $animation-name - The animation name
*
* @return {string} Prefixed keyframes attributes
*/
@mixin keyframes($animation-name) {
@-webkit-keyframes #{$animation-name} {
@content;
}
@-moz-keyframes #{$animation-name} {
@content;
}
@-o-keyframes #{$animation-name} {
@content;
}
@keyframes #{$animation-name} {
@content;
}
}
/**
* Prefix function for browser compatibility
*
* @param {string} $property - Property name
* @param {any} $value - Property value
*
* @return {string} Prefixed attributes
*/
@mixin prefix($property, $value) {
-webkit-#{$property}: #{$value};
-moz-#{$property}: #{$value};
-ms-#{$property}: #{$value};
-o-#{$property}: #{$value};
#{$property}: #{$value};
}
/**
* Layout Mixins
*/
@mixin from($device) {
@media screen and (min-width: $device) {
@content;
}
}
@mixin until($device) {
@media screen and (max-width: $device - 1px) {
@content;
}
}
@mixin mobile {
@media screen and (max-width: $tablet - 1px) {
@content;
}
}
@mixin tablet {
@media screen and (min-width: $tablet) {
@content;
}
}
@mixin tablet-only {
@media screen and (min-width: $tablet) and (max-width: $desktop - 1px) {
@content;
}
}
@mixin touch {
@media screen and (max-width: $desktop - 1px) {
@content;
}
}
@mixin desktop {
@media screen and (min-width: $desktop) {
@content;
}
}
@mixin desktop-only {
@media screen and (min-width: $desktop) and (max-width: $widescreen - 1px) {
@content;
}
}
@mixin widescreen {
@media screen and (min-width: $widescreen) {
@content;
}
}

138
client/scss/base/reset.scss

@ -0,0 +1,138 @@
/*
HTML5 Reset :: style.css
----------------------------------------------------------
We have learned much from/been inspired by/taken code where offered from:
Eric Meyer :: http://meyerweb.com
HTML5 Doctor :: http://html5doctor.com
and the HTML5 Boilerplate :: http://html5boilerplate.com
-------------------------------------------------------------------------------*/
/* Let's default this puppy out
-------------------------------------------------------------------------------*/
html, body, body div, span, object, iframe, h1, h2, h3, h4, h5, h6, p, blockquote, pre, abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp, small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li, fieldset, form, label, legend, table, caption, tbody, tfoot, thead, tr, th, td, article, aside, figure, footer, header, menu, nav, section, time, mark, audio, video, details, summary {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font-weight: normal;
vertical-align: baseline;
background: transparent;
}
main, article, aside, figure, footer, header, nav, section, details, summary {display: block;}
/* Handle box-sizing while better addressing child elements:
http://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ */
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
/* consider resetting the default cursor: https://gist.github.com/murtaugh/5247154 */
/* Responsive images and other embedded objects */
/* if you don't have full control over `img` tags (if you have to overcome attributes), consider adding height: auto */
img,
object,
embed {max-width: 100%;}
/*
Note: keeping IMG here will cause problems if you're using foreground images as sprites.
In fact, it *will* cause problems with Google Maps' controls at small size.
If this is the case for you, try uncommenting the following:
#map img {
max-width: none;
}
*/
/* force a vertical scrollbar to prevent a jumpy page */
html {overflow-y: scroll;}
/* we use a lot of ULs that aren't bulleted.
you'll have to restore the bullets within content,
which is fine because they're probably customized anyway */
ul {list-style: none;}
blockquote, q {quotes: none;}
blockquote:before,
blockquote:after,
q:before,
q:after {content: ''; content: none;}
a {margin: 0; padding: 0; font-size: 100%; vertical-align: baseline; background: transparent;}
del {text-decoration: line-through;}
abbr[title], dfn[title] {border-bottom: 1px dotted #000; cursor: help;}
/* tables still need cellspacing="0" in the markup */
table {border-collapse: separate; border-spacing: 0;}
th {font-weight: bold; vertical-align: bottom;}
td {font-weight: normal; vertical-align: top;}
hr {display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0;}
input, select {vertical-align: middle;}
pre {
white-space: pre; /* CSS2 */
white-space: pre-wrap; /* CSS 2.1 */
white-space: pre-line; /* CSS 3 (and 2.1 as well, actually) */
word-wrap: break-word; /* IE */
}
input[type="radio"] {vertical-align: text-bottom;}
input[type="checkbox"] {vertical-align: bottom;}
.ie7 input[type="checkbox"] {vertical-align: baseline;}
.ie6 input {vertical-align: text-bottom;}
select, input, textarea {font: 99% sans-serif;}
table {font-size: inherit; font: 100%;}
small {font-size: 85%;}
strong {font-weight: bold;}
td, td img {vertical-align: top;}
/* Make sure sup and sub don't mess with your line-heights http://gist.github.com/413930 */
sub, sup {font-size: 75%; line-height: 0; position: relative;}
sup {top: -0.5em;}
sub {bottom: -0.25em;}
/* standardize any monospaced elements */
pre, code, kbd, samp {font-family: monospace, sans-serif;}
/* hand cursor on clickable elements */
.clickable,
label,
input[type=button],
input[type=submit],
input[type=file],
button {cursor: pointer;}
/* Webkit browsers add a 2px margin outside the chrome of form elements */
button, input, select, textarea {margin: 0;}
/* make buttons play nice in IE */
button,
input[type=button] {width: auto; overflow: visible;}
/* scale images in IE7 more attractively */
.ie7 img {-ms-interpolation-mode: bicubic;}
/* prevent BG image flicker upon hover
(commented out as usage is rare, and the filter syntax messes with some pre-processors)
.ie6 html {filter: expression(document.execCommand("BackgroundImageCache", false, true));}
*/
/* let's clear some floats */
.clearfix:after { content: " "; display: block; clear: both; }

25
client/scss/base/variables.scss

@ -0,0 +1,25 @@
// --------------------------------------
// FONTS
// --------------------------------------
$core-font-standard: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
$core-font-monospace: Consolas, "Liberation Mono", Menlo, Courier, monospace;
// --------------------------------------
// LAYOUT
// --------------------------------------
$tablet: 769px !default;
$desktop: 980px !default;
$widescreen: 1180px !default;
// --------------------------------------
// COLORS
// --------------------------------------
$color-text: mc('grey', '800');
$color-link: mc('blue', '500');
$color-link-hover: mc('blue', '700');
$color-link-active: mc('purple', '500');
$color-bg: #F4F5F9;

114
client/scss/components/alert.scss

@ -0,0 +1,114 @@
/*#alerts {
position: fixed;
top: 60px;
right: 10px;
width: 350px;
z-index: 10;
text-shadow: 1px 1px 0 rgba(0,0,0,0.1);
.notification {
animation: 0.5s ease slideInRight;
margin-top: 5px;
&.exit {
animation: 0.5s ease fadeOutRight;
}
}
h3 {
font-size: 16px;
font-size: 500;
}
}*/
#alerts {
position: fixed;
top: 55px;
right: 10px;
width: 350px;
z-index: 100;
> ul {
margin: 0;
padding: 0;
list-style-type: none;
> li {
background-color: material-color('blue-grey', '800');
box-shadow: 5px 5px 0 transparentize(material-color('blue-grey', '900'), 0.7);
border: 1px solid material-color('blue-grey', '500');
border-left-width: 5px;
margin-top: 5px;
padding: 8px 12px;
animation-name: slideFromRight;
animation-duration: 1s;
cursor: pointer;
position: relative;
&:hover {
background-color: material-color('blue-grey', '900');
}
&.exit {
animation-name: zoomOut;
animation-duration: 1s;
transform-origin: top center;
}
> button {
background-color: transparent;
border: none;
color: #FFF;
width: 15px;
height: 15px;
padding: 0;
position: absolute;
top: 10px;
right: 10px;
&:before {
content: 'X';
}
}
> strong {
display: block;
font-size: 13px;
font-weight: 500;
color: #FFF;
> i {
margin-right: 5px;
}
}
> span {
font-size: 12px;
font-weight: 500;
color: material-color('blue-grey', '100');
}
&.error {
border-color: material-color('red', '400');
background-color: material-color('red', '600');
> span {
color: material-color('red', '50');
}
}
&.success {
border-color: material-color('green', '400');
background-color: material-color('green', '700');
> span {
color: material-color('green', '50');
}
}
}
}
}

122
client/scss/components/button.scss

@ -0,0 +1,122 @@
.button {
background-color: mc('orange','600');
color: #FFF;
border: 1px solid mc('orange','700');
border-radius: 3px;
display: inline-flex;
height: 30px;
align-items: center;
padding: 0 15px;
font-size: 13px;
font-weight: 600;
font-family: $core-font-standard;
margin: 0;
transition: all .4s ease;
cursor: pointer;
text-decoration: none;
text-transform: uppercase;
span {
font-weight: 600;
display: inline-flex;
align-items: center;
line-height: 14px;
height: 14px;
}
i {
margin-right: 8px;
font-size: 14px;
line-height: 14px;
height: 14px;
}
&:focus {
outline: none;
border-color: #FFF;
}
&:hover {
background-color: mc('orange','800');
text-decoration: none;
}
@each $color, $colorvalue in $material-colors {
&.is-#{$color} {
background-color: mc($color, '600');
border-color: mc($color,'700');
color: #FFF;
&.is-outlined {
background-color: #FFF;
color: mc($color,'700');
}
&.is-inverted {
background-color: rgba(mc($color, '800'), 0);
border-color: mc($color, '500');
}
&:hover {
background-color: mc($color,'800');
color: #FFF;
animation: none;
}
}
}
&.is-icon-only {
i {
margin-right: 0;
}
}
&.is-featured {
animation: btnInvertedPulse .6s ease alternate infinite;
}
&.is-disabled, &:disabled {
background-color: mc('grey', '300');
border: 1px solid mc('grey','400');
color: mc('grey', '500');
cursor: default;
transition: none;
&:hover {
background-color: mc('grey', '300') !important;
color: mc('grey', '500') !important;
}
}
}
.button-group {
.button {
border-radius: 0;
margin-left: 1px;
&:first-child {
margin-left: 0;
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
&:last-child {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
}
}
@include keyframes(btnInvertedPulse) {
0% {
background-color: rgba(mc('grey', '500'), 0);
}
100% {
background-color: rgba(mc('grey', '500'), 0.25);
}
}

26
client/scss/components/footer.scss

@ -0,0 +1,26 @@
.footer {
background-color: mc('blue-grey','50');
border-bottom: 5px solid mc('blue-grey','100');
display: flex;
justify-content: space-between;
align-items: center;
padding: 25px;
font-size: 13px;
font-weight: 500;
color: mc('blue-grey','500');
ul {
padding: 0;
margin: 0;
list-style-type: none;
display: flex;
justify-content: center;
align-items: center;
li {
padding: 0 15px;
}
}
}

213
client/scss/components/form.scss

@ -0,0 +1,213 @@
.control {
& + .control {
margin-top: 15px;
}
// ===============================================================
// TEXTBOX
// ===============================================================
input[type=text], input[type=password] {
background-color: #FFF;
display: flex;
height: 30px;
align-items: center;
padding: 0 12px;
border: 1px solid mc('grey', '400');
border-radius: 3px;
font-family: $core-font-standard;
font-size: 14px;
color: mc('grey', '700');
transition: all .4s ease;
box-shadow: inset 0 0 5px 0 rgba(0,0,0,0.1);
&:focus {
outline: none;
border-color: mc('light-blue', '500');
box-shadow: inset 0 0 5px 0 rgba(mc('light-blue', '500'), 0.3);
}
&:disabled {
background-color: mc('grey', '100');
}
&.is-dirty.is-invalid {
border-color: mc('red', '500');
box-shadow: inset 0 0 5px 0 mc('red', '100');
}
}
&.is-fullwidth {
input[type=text], input[type=password], select, textarea {
width: 100%;
}
}
// ===============================================================
// DROPDOWN
// ===============================================================
select {
background-color: #FFF;
display: flex;
height: 30px;
align-items: center;
padding: 0 12px;
border: 1px solid mc('grey', '400');
border-radius: 3px;
font-family: $core-font-standard;
font-size: 14px;
color: mc('grey', '700');
transition: all .4s ease;
box-shadow: inset 0 0 5px 0 rgba(0,0,0,0.1);
cursor: pointer;
&:focus {
outline: none;
border-color: mc('light-blue', '500');
box-shadow: inset 0 0 5px 0 rgba(mc('light-blue', '500'), 0.3);
}
&:disabled {
background-color: mc('grey', '100');
}
}
// ===============================================================
// CHECKBOX / RADIO BUTTONS
// ===============================================================
input[type=radio], input[type=checkbox] {
position: absolute;
left: -9999px;
opacity: 0;
& + label {
position: relative;
padding: 0 15px 0 25px;
cursor: pointer;
display: inline-block;
height: 25px;
line-height: 25px;
font-size: 14px;
transition: .28s ease;
@include prefix('user-select', none);
&:before, &:after {
content: '';
position: absolute;
left: 0;
top: 0;
margin: 4px;
border: 2px solid mc($primary, '600');
margin: 4px;
width: 16px;
height: 16px;
border-radius: 50%;
z-index: 0;
transition: .28s ease;
}
}
&:checked + label {
&:before, &:after {
border-color: mc($primary, '600');
}
&:after {
@include prefix('transform', scale(0.5));
background-color: mc($primary, '600');
}
}
}
input[type=checkbox] + label {
&:before, &:after {
border-radius: 0;
}
}
.help {
font-size: 12px;
&.is-red {
color: mc('red','600');
}
}
& + label {
margin-top: 20px;
}
> i:first-child {
margin-right: 8px;
}
}
.label {
margin-bottom: 5px;
font-size: 14px;
font-weight: 500;
display: block;
}
.form-sections {
section {
border-top: 1px solid mc('grey', '200');
padding: 20px;
@include prefix(animation-duration, .6s);
&:first-child {
border-top: none;
}
.button + .button {
margin-left: 10px;
}
.desc {
display: inline-block;
padding: 10px 0 0 0px;
font-size: 12px;
color: mc('grey', '500');
}
.section-block {
padding-left: 20px;
font-size: 14px;
color: mc('blue-grey', '800');
h6 {
font-size: 14px;
font-weight: 500;
color: mc('blue-grey', '600');
margin-top: 15px;
border-bottom: 1px dotted mc('blue-grey', '200');
}
p {
padding: 5px 0;
&.is-small {
font-size: 13px;
}
}
}
}
}

501
client/scss/components/grid.scss

@ -0,0 +1,501 @@
.column {
flex-basis: 0;
flex-grow: 1;
flex-shrink: 1;
padding: 10px;
.columns.is-mobile > &.is-narrow {
flex: none;
}
.columns.is-mobile > &.is-full {
flex: none;
width: 100%;
}
.columns.is-mobile > &.is-three-quarters {
flex: none;
width: 75%;
}
.columns.is-mobile > &.is-two-thirds {
flex: none;
width: 66.6666%;
}
.columns.is-mobile > &.is-half {
flex: none;
width: 50%;
}
.columns.is-mobile > &.is-one-third {
flex: none;
width: 33.3333%;
}
.columns.is-mobile > &.is-one-quarter {
flex: none;
width: 25%;
}
.columns.is-mobile > &.is-offset-three-quarters {
margin-left: 75%;
}
.columns.is-mobile > &.is-offset-two-thirds {
margin-left: 66.6666%;
}
.columns.is-mobile > &.is-offset-half {
margin-left: 50%;
}
.columns.is-mobile > &.is-offset-one-third {
margin-left: 33.3333%;
}
.columns.is-mobile > &.is-offset-one-quarter {
margin-left: 25%;
}
@for $i from 1 through 12 {
.columns.is-mobile > &.is-#{$i} {
flex: none;
width: $i / 12 * 100%;
}
.columns.is-mobile > &.is-offset-#{$i} {
margin-left: $i / 12 * 100%;
}
}
@include mobile {
&.is-narrow-mobile {
flex: none;
}
&.is-full-mobile {
flex: none;
width: 100%;
}
&.is-three-quarters-mobile {
flex: none;
width: 75%;
}
&.is-two-thirds-mobile {
flex: none;
width: 66.6666%;
}
&.is-half-mobile {
flex: none;
width: 50%;
}
&.is-one-third-mobile {
flex: none;
width: 33.3333%;
}
&.is-one-quarter-mobile {
flex: none;
width: 25%;
}
&.is-offset-three-quarters-mobile {
margin-left: 75%;
}
&.is-offset-two-thirds-mobile {
margin-left: 66.6666%;
}
&.is-offset-half-mobile {
margin-left: 50%;
}
&.is-offset-one-third-mobile {
margin-left: 33.3333%;
}
&.is-offset-one-quarter-mobile {
margin-left: 25%;
}
@for $i from 1 through 12 {
&.is-#{$i}-mobile {
flex: none;
width: $i / 12 * 100%;
}
&.is-offset-#{$i}-mobile {
margin-left: $i / 12 * 100%;
}
}
}
@include tablet {
&.is-narrow,
&.is-narrow-tablet {
flex: none;
}
&.is-full,
&.is-full-tablet {
flex: none;
width: 100%;
}
&.is-three-quarters,
&.is-three-quarters-tablet {
flex: none;
width: 75%;
}
&.is-two-thirds,
&.is-two-thirds-tablet {
flex: none;
width: 66.6666%;
}
&.is-half,
&.is-half-tablet {
flex: none;
width: 50%;
}
&.is-one-third,
&.is-one-third-tablet {
flex: none;
width: 33.3333%;
}
&.is-one-quarter,
&.is-one-quarter-tablet {
flex: none;
width: 25%;
}
&.is-offset-three-quarters,
&.is-offset-three-quarters-tablet {
margin-left: 75%;
}
&.is-offset-two-thirds,
&.is-offset-two-thirds-tablet {
margin-left: 66.6666%;
}
&.is-offset-half,
&.is-offset-half-tablet {
margin-left: 50%;
}
&.is-offset-one-third,
&.is-offset-one-third-tablet {
margin-left: 33.3333%;
}
&.is-offset-one-quarter,
&.is-offset-one-quarter-tablet {
margin-left: 25%;
}
@for $i from 1 through 12 {
&.is-#{$i},
&.is-#{$i}-tablet {
flex: none;
width: $i / 12 * 100%;
}
&.is-offset-#{$i},
&.is-offset-#{$i}-tablet {
margin-left: $i / 12 * 100%;
}
}
}
@include desktop {
&.is-narrow-desktop {
flex: none;
}
&.is-full-desktop {
flex: none;
width: 100%;
}
&.is-three-quarters-desktop {
flex: none;
width: 75%;
}
&.is-two-thirds-desktop {
flex: none;
width: 66.6666%;
}
&.is-half-desktop {
flex: none;
width: 50%;
}
&.is-one-third-desktop {
flex: none;
width: 33.3333%;
}
&.is-one-quarter-desktop {
flex: none;
width: 25%;
}
&.is-offset-three-quarters-desktop {
margin-left: 75%;
}
&.is-offset-two-thirds-desktop {
margin-left: 66.6666%;
}
&.is-offset-half-desktop {
margin-left: 50%;
}
&.is-offset-one-third-desktop {
margin-left: 33.3333%;
}
&.is-offset-one-quarter-desktop {
margin-left: 25%;
}
@for $i from 1 through 12 {
&.is-#{$i}-desktop {
flex: none;
width: $i / 12 * 100%;
}
&.is-offset-#{$i}-desktop {
margin-left: $i / 12 * 100%;
}
}
}
@include widescreen {
&.is-narrow-widescreen {
flex: none;
}
&.is-full-widescreen {
flex: none;
width: 100%;
}
&.is-three-quarters-widescreen {
flex: none;
width: 75%;
}
&.is-two-thirds-widescreen {
flex: none;
width: 66.6666%;
}
&.is-half-widescreen {
flex: none;
width: 50%;
}
&.is-one-third-widescreen {
flex: none;
width: 33.3333%;
}
&.is-one-quarter-widescreen {
flex: none;
width: 25%;
}
&.is-offset-three-quarters-widescreen {
margin-left: 75%;
}
&.is-offset-two-thirds-widescreen {
margin-left: 66.6666%;
}
&.is-offset-half-widescreen {
margin-left: 50%;
}
&.is-offset-one-third-widescreen {
margin-left: 33.3333%;
}
&.is-offset-one-quarter-widescreen {
margin-left: 25%;
}
@for $i from 1 through 12 {
&.is-#{$i}-widescreen {
flex: none;
width: $i / 12 * 100%;
}
&.is-offset-#{$i}-widescreen {
margin-left: $i / 12 * 100%;
}
}
}
}
.columns {
margin-left: -10px;
margin-right: -10px;
margin-top: -10px;
&:last-child {
margin-bottom: -10px;
}
&:not(:last-child) {
margin-bottom: 10px;
}
// Modifiers
&.is-centered {
justify-content: center;
}
&.is-gapless {
margin-left: 0;
margin-right: 0;
margin-top: 0;
&:last-child {
margin-bottom: 0;
}
&:not(:last-child) {
margin-bottom: 20px;
}
& > .column {
margin: 0;
padding: 0;
}
}
&.is-stretched {
flex-grow: 1;
align-items: stretch;
align-self: stretch;
}
&.is-grid {
// Responsiveness
@include tablet {
flex-wrap: wrap;
& > .column {
max-width: 33.3333%;
padding: 10px;
width: 33.3333%;
& + .column {
margin-left: 0;
}
}
}
}
&.is-mobile {
display: flex;
}
&.is-multiline {
flex-wrap: wrap;
}
&.is-vcentered {
align-items: center;
}
// Responsiveness
@include tablet {
&:not(.is-desktop) {
display: flex;
}
}
@include desktop {
// Modifiers
&.is-desktop {
display: flex;
}
}
}
.tile {
align-items: stretch;
flex-basis: auto;
flex-grow: 1;
flex-shrink: 1;
min-height: min-content;
// Modifiers
&.is-ancestor {
margin-left: -10px;
margin-right: -10px;
margin-top: -10px;
&:last-child {
margin-bottom: -10px;
}
&:not(:last-child) {
margin-bottom: 10px;
}
}
&.is-child {
margin: 0 !important;
}
&.is-parent {
padding: 10px;
}
&.is-vertical {
flex-direction: column;
& > .tile.is-child:not(:last-child) {
margin-bottom: 20px !important;
}
}
// Responsiveness
@include tablet {
&:not(.is-child) {
display: flex;
}
@for $i from 1 through 12 {
&.is-#{$i} {
flex: none;
width: $i / 12 * 100%;
}
}
}
}
.column.is-white {
background-color: #FFF;
}

68
client/scss/components/hero.scss

@ -0,0 +1,68 @@
.hero {
padding: 20px;
background-color: mc('grey', '50');
border-bottom: 1px solid mc('grey', '200');
position: relative;
h1 {
font-size: 28px;
color: mc($primary, '500');
font-weight: 300;
}
h2 {
font-size: 18px;
color: mc('grey', '500');
font-weight: 400;
}
.hero-menu {
position: absolute;
right: 20px;
bottom: -1px;
z-index: 1;
display: flex;
li {
display: flex;
margin-left: 1px;
a, button {
background-color: mc('light-blue', '500');
color: #FFF;
display: inline-flex;
align-items: center;
justify-items: center;
padding: 0 15px;
height: 32px;
border: 1px solid mc('light-blue', '600');
font-family: $core-font-standard;
font-size: 13px;
transition: all 0.4s ease;
cursor: pointer;
text-decoration: none;
text-transform: uppercase;
i {
margin-right: 10px;
}
@each $color, $colorvalue in $material-colors {
&.is-#{$color} {
background-color: mc($color, '600');
border-color: mc($color, '600');
&:hover {
background-color: mc($color, '800');
}
}
}
}
}
}
}

62
client/scss/components/list.scss

@ -0,0 +1,62 @@
.list {
background-color: #FFF;
min-height: 25px;
.list-header {
background-color: mc('grey','100');
height: 30px;
display: flex;
align-items: center;
padding: 0 20px;
text-transform: uppercase;
font-size: 13px;
color: mc($primary,'500');
text-shadow: 1px 1px 0 #FFF;
span {
font-weight: 500;
}
i {
margin-right: 10px;
}
}
.list-row {
border-top: 1px solid mc('grey','100');
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 20px;
&:first-child {
border-top: none;
}
}
.list-item {
display: flex;
justify-content: flex-start;
align-items: center;
}
.list-icon {
margin-right: 15px;
color: mc('grey','500');
}
.list-content {
display: flex;
flex-direction: column;
strong {
color: mc('grey','700');
}
span {
color: mc('grey','600');
}
}
}

355
client/scss/components/markdown-content.scss

@ -0,0 +1,355 @@
.mkcontent {
font-size: 14px;
color: mc('grey', '700');
padding: 0 0 20px 0;
h1, h2, h3 {
font-weight: 400;
margin: 10px 0 0;
padding: 7px 20px;
font-weight: 500;
}
h1 {
background-color: mc('indigo', '50');
border-bottom: 2px solid mc('indigo', '100');
font-size: 18px;
color: mc('indigo', '500');
&:first-child {
margin-top: 1px;
}
/*& + h2 {
margin-top: 1px;
border-top: none;
}*/
& + p {
padding-top: 20px;
}
}
h2 {
background-color: lighten(mc('teal', '50'), 5%);
border: 1px solid mc('teal', '100');
border-right-width: 5px;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
font-size: 16px;
color: mc('teal', '900');
margin-left: 20px;
}
.indent-h2 {
border-right: 5px solid mc('teal', '100');
margin-left: 20px;
padding-top: 1px;
padding-bottom: 20px;
overflow: hidden;
& + h1, & + h2 {
margin-top: 1px;
}
&:last-child {
padding-bottom: 5px;
}
h3:first-child {
margin-top: 0;
border-top: none;
}
}
h3 {
background-color: lighten(mc('blue', '50'), 3%);
border: 1px solid mc('blue', '100');
border-right-width: 5px;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
font-size: 14px;
color: mc('blue', '700');
margin-left: 20px;
margin-right: 1px;
padding: 5px 20px;
}
.indent-h3 {
border-right: 5px solid mc('teal', '100');
margin-left: 20px;
margin-right: 1px;
padding-bottom: 10px;
& + h1, & + h2, & + h3 {
margin-top: 1px;
}
&:last-child {
padding-bottom: 0;
}
}
a {
text-decoration: underline;
font-weight: 400;
&:hover {
color: mc('blue', '700');
}
&.toc-anchor {
font-size: 80%;
color: mc('indigo', '300');
border-bottom: none;
text-decoration: none;
&:visited {
color: mc('indigo', '300') !important;
}
}
&.external-link {
position: relative;
padding-left: 5px;
//display: inline-flex;
//align-items: center;
&:before {
content: $icon-open;
display: inline-block;
font-family: 'core-icons';
font-style: normal;
font-weight: normal;
text-decoration: none;
color: mc('grey', '500');
font-size: 14px;
margin-right: 5px;
}
&:hover:before {
text-decoration: none;
}
}
}
ul {
padding: 10px 0 10px 40px;
list-style-type: square;
li {
padding: 1px 0;
> ul {
padding: 5px 0 5px 15px;
list-style-type: disc;
}
p {
padding: 0;
&:first-child {
padding: 0;
}
}
}
}
ol {
padding: 10px 40px;
list-style-type: decimal;
li {
padding: 1px 0;
}
}
p {
padding: 10px 20px;
&:first-child {
padding-top: 20px;
}
&.is-gapless {
padding: 0 20px;
& + p {
padding-top: 20px;
}
& + h1 {
margin-top: 1px;
}
}
}
table {
width: auto;
border-collapse: collapse;
margin: 10px 20px;
font-size: 14px;
th {
background-color: mc('blue', '500');
color: #FFF;
border: 1px solid mc('blue', '500');
padding: 5px 15px;
&:first-child {
border-left-color: mc('blue', '500');
}
&:last-child {
border-right-color: mc('blue', '500');
}
}
td {
border: 1px solid mc('grey', '500');
padding: 5px 15px;
}
tr:nth-child(even) {
background-color: mc('grey', '100');
}
}
code {
font-weight: 500;
color: mc('purple', '500');
background-color: lighten(mc('purple', '50'), 5%);
padding: 0 5px;
border-radius: 4px;
}
pre {
background-color: mc('grey', '50');
border-top: 1px solid mc('grey', '100');
box-shadow: inset 0 0 5px 0 rgba(mc('grey', '500'), 0.3);
padding: 20px;
font-family: $core-font-monospace;
white-space: pre;
> code {
box-shadow: inset 0 0 5px 0 mc('grey', '100');
border-radius: 5px;
font-weight: 400;
background-color: none;
color: mc('grey', '700');
padding: 0;
}
& + p {
padding-top: 1em;
}
& + h1, & + h2, & + h3 {
margin-top: 1px;
}
}
.align-right {
float:right;
margin: 0 0 10px 10px;
max-width: 30vw;
}
.align-center {
text-align: center;
}
img.pagelogo {
position: absolute;
right: 20px;
top: 20px;
max-width: 200px;
max-height: 100px;
z-index: 3;
}
strong {
color: mc('grey', '700');
}
.twa {
font-size: 120%;
}
hr {
margin: 20px;
border-top: 1px dotted mc('grey', '500');
}
blockquote {
background-color: mc('teal', '50');
border: 1px solid mc('teal', '100');
border-bottom-width: 2px;
box-shadow: inset 0px 0px 0px 1px rgba(255,255,255,1);
border-radius: 5px;
padding: 0 10px;
margin: 10px 20px;
p {
padding: 10px 0;
color: mc('teal', '800');
&:first-child {
padding: 10px 0;
}
strong {
color: inherit;
}
}
&.is-danger {
background-color: mc('red', '100');
border-color: mc('red', '200');
p {
color: mc('red', '900');
}
}
&.is-warning {
background-color: mc('amber', '50');
border-color: mc('amber', '200');
p {
color: darken(mc('amber', '900'), 10%);
}
}
&.is-success {
background-color: mc('green', '50');
border-color: mc('green', '200');
p {
color: darken(mc('green', '900'), 10%);
}
}
&.is-info {
background-color: mc('blue', '50');
border-color: mc('blue', '200');
p {
color: darken(mc('blue', '900'), 10%);
}
}
}
}

356
client/scss/components/modal.scss

@ -0,0 +1,356 @@
.modal {
align-items: flex-start;
display: none;
&.is-active {
display: block;
}
&.is-superimposed {
.modal-background {
z-index: 20;
}
.modal-container {
z-index: 21;
}
}
}
.modal-background {
top: 0;
left: 0;
width: 100vw;
height: 100vh;
position: fixed;
background-color: rgba(0,0,0,0.85);
animation: .4s ease fadeIn;
z-index: 10;
}
.modal-container {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 11;
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
animation: .3s ease zoomIn;
width: 600px;
background-color: #FFF;
&.is-expanded {
align-self: stretch;
width: 100%;
margin: 20px;
display: flex;
flex-direction: column;
> section {
flex-grow: 1;
}
}
header {
background-color: mc('teal', '600');
color: #FFF;
display: flex;
flex-shrink: 0;
height: 40px;
align-items: center;
font-weight: 400;
font-size: 16px;
padding: 0 20px;
position: relative;
@each $color, $colorvalue in $material-colors {
&.is-#{$color} {
background-color: mc($color, '600');
}
}
.modal-notify {
position: absolute;
display: none;
align-items: center;
height: 40px;
right: 20px;
top: 0;
&.is-active {
display: flex;
}
span {
font-size: 12px;
letter-spacing: 1px;
text-transform: uppercase;
}
i {
margin-left: 15px;
display: inline-block;
@include spinner(#FFF, .5s, 20px);
}
}
}
section {
padding: 20px;
border-top: 1px dotted mc('grey', '300');
&:first-of-type {
border-top: none;
padding-top: 20px;
}
&:last-of-type {
padding-bottom: 20px;
}
&.is-gapless {
padding: 10px;
display: flex;
}
&.modal-loading {
display: flex;
flex-direction: column;
align-items: center;
> i {
display: block;
@include spinner(mc('blue','500'), .4s, 32px);
margin-bottom: 10px;
}
> span {
color: mc('grey', '600');
}
> em {
font-size: 12px;
color: mc('grey', '500');
font-style: normal;
}
}
&.modal-instructions {
display: flex;
flex-direction: column;
align-items: center;
color: mc('grey', '800');
img {
height: 100px;
& + * {
margin-top: 10px;
}
}
i.is-huge {
font-size: 72px;
margin-bottom: 10px;
}
> span {
color: mc('grey', '800');
}
> em {
font-size: 12px;
color: mc('grey', '600');
font-style: normal;
margin-top: 10px;
display: block;
}
}
.bullets {
list-style-type: square;
padding: 5px 0 0 30px;
font-size: 14px;
color: mc('grey', '800');
}
.note {
display: block;
margin-top: 10px;
font-size: 14px;
color: mc('grey', '800');
&:first-child {
margin-top: 0;
}
ul {
color: mc('grey', '800');
padding-left: 10px;
li {
margin-top: 5px;
display: flex;
align-items: center;
> i {
margin-right: 8px;
font-size: 18px;
}
}
}
}
}
footer {
padding: 20px;
text-align: right;
.button {
margin-left: 10px;
}
}
}
.modal-toolbar {
background-color: mc('teal', '700');
padding: 7px 20px;
display: flex;
flex-shrink: 0;
justify-content: center;
@each $color, $colorvalue in $material-colors {
&.is-#{$color} {
background-color: mc($color, '700');
.button {
border-color: mc($color, '900');
background-color: mc($color, '900');
&:hover {
border-color: mc($color, '900');
background-color: mc($color, '800');
}
}
}
}
// BUTTONS
.button {
border: 1px solid mc('teal', '900');
background-color: mc('teal', '900');
transition: all .4s ease;
color: #FFF;
border-radius: 0;
&:first-child {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
&:last-child {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
&:hover {
border-color: mc('teal', '900');
background-color: mc('teal', '800');
color: #FFF;
}
}
.button + .button {
margin-left: 1px;
}
}
.modal-sidebar {
background-color: mc('teal', '50');
padding: 0;
//padding: 7px 20px;
@each $color, $colorvalue in $material-colors {
&.is-#{$color} {
background-color: mc($color, '50');
.model-sidebar-header {
background-color: mc($color, '100');
color: mc($color, '800');
}
.model-sidebar-list > li a {
&:hover {
background-color: mc($color, '200');
}
&.is-active {
background-color: mc($color, '500');
}
}
}
}
.model-sidebar-header {
padding: 7px 20px;
}
.model-sidebar-content {
padding: 7px 20px;
}
.model-sidebar-list {
> li {
padding: 0;
a {
display: flex;
align-items: center;
height: 34px;
padding: 0 20px;
cursor: pointer;
color: mc('grey', '800');
&:hover {
background-color: mc('teal', '200');
}
&.is-active {
color: #FFF;
}
i {
margin-right: 7px;
}
}
}
}
}
.modal-content .card-footer-item.featured {
animation: flash 4s ease 0 infinite;
}

182
client/scss/components/nav.scss

@ -0,0 +1,182 @@
.nav {
align-items: stretch;
background-color: mc($primary, '500');
display: flex;
min-height: 50px;
position: relative;
text-align: center;
box-shadow: 0 2px 3px rgba(mc($primary, '500'), 0.2);
z-index: 2;
color: #FFF;
@each $color, $colorvalue in $material-colors {
&.is-#{$color} {
background-color: mc($color, '500');
box-shadow: 0 2px 3px rgba(mc($color, '500'), 0.2);
.nav-item {
.button {
border: 1px solid mc($color, '900');
background-color: mc($color, '800');
&.is-outlined {
background-color: mc($color, '600');
border-color: mc($color, '800');
color: mc($color, '100');
}
&:hover {
border-color: mc($color, '900');
background-color: mc($color, '900');
}
}
}
}
}
}
.nav-left {
align-items: stretch;
display: flex;
flex-basis: 0;
flex-grow: 1;
justify-content: flex-start;
overflow: hidden;
overflow-x: auto;
white-space: nowrap;
}
.nav-center {
align-items: stretch;
display: flex;
justify-content: center;
margin-left: auto;
margin-right: auto;
}
.nav-right {
@include tablet {
align-items: stretch;
display: flex;
flex-basis: 0;
flex-grow: 1;
justify-content: flex-end;
}
}
.nav-item {
align-items: center;
display: flex;
justify-content: center;
padding: 0 10px;
// LINKS
@at-root .nav-item a, a.nav-item {
color: mc($primary, '50');
transition: color .4s ease;
cursor: pointer;
&:hover {
color: mc($primary, '200');
text-decoration: none;
}
}
// LOGO
img {
max-height: 34px;
}
// HEADERS
h1 {
font-size: 16px;
font-weight: 400;
letter-spacing: 0.5px;
text-transform: uppercase;
transition: color .4s ease;
color: #FFF;
padding-left: 10px;
i {
margin-right: 8px;
}
&:hover {
color: mc('indigo', '100');
}
}
@at-root h2.nav-item, .nav-item h2 {
color: mc($primary, '50');
}
// BUTTONS
.button {
border: 1px solid mc($primary, '900');
background-color: mc($primary, '800');
transition: all .4s ease;
color: #FFF;
border-radius: 0;
&:first-child {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
&:last-child {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
&.is-outlined {
background-color: mc($primary, '600');
border-color: mc($primary, '800');
color: mc($primary, '100');
}
&:hover {
border-color: mc($primary, '900');
background-color: mc($primary, '900');
color: #FFF;
}
}
.button + .button {
margin-left: 1px;
}
// INPUTS
.control {
input[type=text] {
background-color: mc($primary, '800');
border-color: mc($primary, '400');
color: mc($primary, '50');
&:focus {
border-color: mc($primary, '200');
box-shadow: inset 0 0 5px 0 rgba(mc($primary, '900'), 0.5);
}
@include placeholder {
color: mc($primary, '200');
}
}
}
}

112
client/scss/components/panel.scss

@ -0,0 +1,112 @@
.panel-aside {
background-color: mc('blue-grey', '800');
border: 1px solid mc('blue-grey', '800');
border-bottom-left-radius: 8px;
padding: 20px;
color: mc('blue-grey','100');
label {
color: #FFF;
}
}
.panel {
background-color: #FFF;
box-shadow: 0 0 12px 0 rgba(mc('grey','800'), .10), 1px 6px 8px 0 rgba(mc('grey','800'), .10);
padding: 0 0 1px 0;
border-radius: 4px;
.panel-title {
border-bottom: 1px solid darken($color-bg, 5%);
padding: 0 15px;
color: $color-text;
font-size: 16px;
font-weight: 500;
display: flex;
align-items: center;
justify-content: space-between;
height: 40px;
&.is-featured {
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background-color: mc('indigo', '300');
border-bottom-color: mc('indigo', '400');
color: #FFF;
> i::before {
@include spinner(#FFF, 0.4s, 18px);
}
}
> span {
font-weight: 500;
}
> i {
display: flex;
width: 18px;
align-items: center;
&::before {
content: " ";
@include spinner(mc($primary,'500'), 0.4s, 18px);
}
}
}
.panel-content {
padding: 0 15px;
&.is-text {
padding: 25px;
p + p, p + h3 {
margin-top: 25px;
}
h3 {
margin-bottom: 15px;
font-weight: 500;
}
ul li {
color: mc('grey', '700');
}
strong {
font-weight: 500;
color: mc($primary,'800');
}
}
}
.panel-footer {
display: flex;
align-items: center;
justify-content: flex-end;
height: 50px;
background-color: $color-bg;
padding: 0 15px;
margin: 0 1px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
position: relative;
.button + .button {
margin-left: 10px;
}
}
+ .panel {
margin-top: 25px;
}
}

57
client/scss/components/search.scss

@ -0,0 +1,57 @@
.searchresults {
position: fixed;
top: 45px;
left: 0;
right: 0;
margin: 0 auto;
width: 500px;
z-index: 1;
background-color: mc($primary, '700');
border-bottom: 5px solid mc($primary, '800');
box-shadow: 0 0 5px mc($primary, '500');
color: #FFF;
&.slideInDown {
@include prefix(animation-duration, .6s);
}
.searchresults-label {
color: mc($primary, '200');
padding: 15px 10px 10px;
font-size: 13px;
text-transform: uppercase;
border-bottom: 1px dotted mc($primary, '400');
}
.searchresults-list {
> li {
display: flex;
font-size: 14px;
transition: background-color .3s linear;
&:nth-child(odd) {
background-color: mc($primary, '600');
}
&.is-active, &:hover {
background-color: mc($primary, '400');
color: #FFF;
border-left: 5px solid mc($primary, '200');
}
a {
color: mc($primary, '50');
display: flex;
align-items: center;
height: 30px;
padding: 0 20px;
width: 100%;
cursor: pointer;
}
}
}
}

83
client/scss/components/sidebar.scss

@ -0,0 +1,83 @@
.sidebar {
background-color: mc('blue-grey', '900');
color: mc('blue-grey', '50');
width: 250px;
max-width: 250px;
min-height: 80vh;
aside {
&:last-child {
padding-bottom: 20px;
}
.sidebar-label {
padding: 0 0 5px 0;
color: mc('blue-grey', '400');
font-size: 13px;
letter-spacing: 1px;
text-transform: uppercase;
border-bottom: 1px solid mc('blue-grey', '700');
margin: 25px 10px 15px 10px;
i {
margin-right: 5px;
}
}
.sidebar-menu {
li {
display: block;
a {
display: flex;
min-height: 30px;
align-items: center;
padding: 5px 20px;
color: mc('blue-grey', '50');
font-size: 14px;
transition: all .4s ease;
line-height: 14px;
i {
margin-right: 7px;
color: mc('blue-grey', '300');
}
&:hover {
color: mc('blue-grey', '400');
text-decoration: none;
}
}
> ul {
border-top: 1px solid lighten(mc('blue-grey', '900'), 3%);
border-bottom: 1px solid lighten(mc('blue-grey', '900'), 2%);
background-color: darken(mc('blue-grey', '900'), 2%);
margin-bottom: 10px;
padding: 10px 0;
li {
padding-left: 10px;
//border-left: 5px solid mc('blue-grey', '800');
a {
min-height: 24px;
color: mc('blue-grey', '100');
}
}
}
}
}
}
}

90
client/scss/components/table.scss

@ -0,0 +1,90 @@
.table {
border-spacing: collapse;
padding: 1px;
width: 100%;
font-size: 14px;
thead {
background-color: mc('blue-grey', '500');
color: #FFF;
th {
padding: 5px 10px;
font-weight: 500;
text-align: center;
border-left: 1px solid mc('blue-grey', '200');
&:first-child {
border-left: none;
}
}
@each $color, $colorvalue in $material-colors {
&.is-#{$color} {
background-color: mc($color, '500');
th {
border-left-color: mc($color, '200');
}
}
}
}
tbody {
tr {
background-color: mc('blue-grey', '100');
&:nth-child(odd) {
background-color: mc('blue-grey', '50');
}
td {
padding: 5px 10px;
border-left: 1px solid #FFF;
vertical-align: middle;
&:first-child {
border-left: none;
}
}
}
}
.is-centered {
text-align: center;
}
.has-icons i {
margin-right: 8px;
}
.is-icon {
font-size: 14px;
width: 20px;
}
.has-action-icons {
i {
cursor: pointer;
font-size: 20px;
}
}
}
.table-actions {
text-align: right;
.button {
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}

16
client/scss/components/typography.scss

@ -0,0 +1,16 @@
h1 {
font-size: 28px;
}
h2 {
font-size: 18px;
}
h3 {
font-size: 16px;
}
@each $color, $colorvalue in $material-colors {
i.is-#{$color} {
color: mc($color, '600');
}
}

3486
client/scss/libs/animate.scss
File diff suppressed because it is too large
View File

52
client/scss/pages/_error.scss

@ -0,0 +1,52 @@
body {
display: flex;
align-items: center;
min-height: 100vh;
width: 100vw;
padding: 25px 0;
margin: 0;
color: #FFF;
&.is-notexist {
background-color: mc('blue-grey', '900');
}
&.is-forbidden {
background-color: darken(mc('blue-grey', '900'), 5%);
}
&.is-error {
background-color: darken(mc('blue-grey', '900'), 10%);
}
}
.container {
text-align: center;
h1 {
margin-top: 30px;
}
h2 {
margin-bottom: 50px;
}
a.button {
margin: 0 5px;
}
h3 {
text-align: left;
margin-top: 50px;
}
pre {
margin-top: 10px;
text-align: left;
color: mc('blue-grey', '200');
font-size: 12px;
}
}

306
client/scss/pages/_login.scss

@ -0,0 +1,306 @@
body {
padding: 0;
margin: 0;
font-family: $core-font-standard;
font-size: 14px;
}
a {
color: #FFF;
transition: color 0.4s ease;
text-decoration: none;
&:hover {
color: mc('orange','600');
text-decoration: underline;
}
}
#bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
background-color: #000;
> div {
background-size: cover;
background-position: center center;
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
opacity: 0;
visibility: hidden;
transition: opacity 3s ease, visibility 3s;
animation: bg 30s linear infinite;
&:nth-child(1) {
animation-delay: 10s;
}
&:nth-child(2) {
animation-delay: 20s;
}
}
}
#root {
position: fixed;
top: 15vh;
left: 10vw;
z-index: 2;
color: #FFF;
display: flex;
flex-direction: column;
h1 {
font-size: 4rem;
font-weight: bold;
color: #FFF;
padding: 0;
margin: 0;
animation: headerIntro 3s ease;
}
h2 {
font-size: 1.5rem;
font-weight: normal;
color: rgba(255,255,255,0.7);
padding: 0;
margin: 0 0 25px 0;
animation: headerIntro 3s ease;
}
h3 {
font-size: 1.25rem;
font-weight: normal;
color: #FB8C00;
padding: 0;
margin: 0;
animation: shake 1s ease;
> .fa {
margin-right: 7px;
}
}
h4 {
font-size: 0.8rem;
font-weight: normal;
color: rgba(255,255,255,0.7);
padding: 0;
margin: 0 0 15px 0;
animation: fadeIn 3s ease;
}
form {
display: flex;
flex-direction: column;
}
input[type=text], input[type=password] {
width: 350px;
max-width: 80vw;
border: 1px solid rgba(255,255,255,0.3);
border-radius: 3px;
background-color: rgba(0,0,0,0.2);
padding: 0 15px;
height: 40px;
margin: 0 0 10px 0;
color: #FFF;
font-weight: bold;
font-size: 14px;
transition: all 0.4s ease;
&:focus {
outline: none;
border-color: mc('orange','600');
}
}
button {
background-color: mc('orange','600');
color: #FFF;
border: 1px solid lighten(mc('orange','600'), 10%);
border-radius: 3px;
height: 40px;
width: 125px;
padding: 0;
font-weight: bold;
margin: 15px 0 0 0;
transition: all 0.4s ease;
cursor: pointer;
span {
font-weight: bold;
}
&:focus {
outline: none;
border-color: #FFF;
}
&:hover {
background-color: darken(mc('orange','600'), 10%);
}
}
#social {
margin-top: 25px;
> span {
display: block;
font-weight: bold;
color: rgba(255,255,255,0.7);
}
button {
margin-right: 5px;
width: auto;
padding: 0 15px;
> i {
margin-right: 10px;
font-size: 16px;
}
&.ms {
background-color: mc('blue','600');
border-color: lighten(mc('blue','600'), 10%);
&:focus {
border-color: #FFF;
}
&:hover {
background-color: darken(mc('blue','600'), 10%);
}
}
&.google {
background-color: mc('light-blue','600');
border-color: lighten(mc('light-blue','600'), 10%);
&:focus {
border-color: #FFF;
}
&:hover {
background-color: darken(mc('light-blue','600'), 10%);
}
}
&.facebook {
background-color: mc('indigo','600');
border-color: lighten(mc('indigo','600'), 10%);
&:focus {
border-color: #FFF;
}
&:hover {
background-color: darken(mc('indigo','600'), 10%);
}
}
&.github {
background-color: mc('blue-grey','700');
border-color: lighten(mc('blue-grey','700'), 10%);
&:focus {
border-color: #FFF;
}
&:hover {
background-color: darken(mc('blue-grey','700'), 10%);
}
}
&.slack {
background-color: mc('purple','700');
border-color: lighten(mc('purple','700'), 10%);
&:focus {
border-color: #FFF;
}
&:hover {
background-color: darken(mc('purple','700'), 10%);
}
}
}
}
}
#copyright {
display: flex;
align-items: center;
justify-content: flex-start;
position: absolute;
left: 10vw;
bottom: 10vh;
z-index: 2;
color: rgba(255,255,255,0.5);
font-weight: bold;
.icon {
font-size: 1.2rem;
margin: 0 8px;
}
a {
opacity: 0.75;
}
}
@include keyframes(bg) {
0% {
@include prefix(transform, scale(1,1));
visibility: visible;
opacity: 0;
},
5% {
opacity: 0.5;
},
33% {
opacity: 0.5;
},
38% {
@include prefix(transform, scale(1.2, 1.2));
opacity: 0;
},
39% {
visibility: hidden;
}
100% {
visibility: hidden;
opacity: 0;
}
}
@include keyframes(headerIntro) {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}

243
libs/auth.js

@ -0,0 +1,243 @@
'use strict'
/* global appconfig, appdata, db, winston */
const LocalStrategy = require('passport-local').Strategy
const GoogleStrategy = require('passport-google-oauth20').Strategy
const WindowsLiveStrategy = require('passport-windowslive').Strategy
const FacebookStrategy = require('passport-facebook').Strategy
const GitHubStrategy = require('passport-github2').Strategy
const SlackStrategy = require('passport-slack').Strategy
const LdapStrategy = require('passport-ldapauth').Strategy
const fs = require('fs')
module.exports = function (passport) {
// Serialization user methods
passport.serializeUser(function (user, done) {
done(null, user._id)
})
passport.deserializeUser(function (id, done) {
db.User.findById(id).then((user) => {
if (user) {
done(null, user)
} else {
done(new Error('User not found.'), null)
}
return true
}).catch((err) => {
done(err, null)
})
})
// Local Account
if (!appdata.capabilities.manyAuthProviders || (appconfig.auth.local && appconfig.auth.local.enabled)) {
passport.use('local',
new LocalStrategy({
usernameField: 'email',
passwordField: 'password'
},
(uEmail, uPassword, done) => {
db.User.findOne({ email: uEmail, provider: 'local' }).then((user) => {
if (user) {
return user.validatePassword(uPassword).then(() => {
return done(null, user) || true
}).catch((err) => {
return done(err, null)
})
} else {
return done(new Error('INVALID_LOGIN'), null)
}
}).catch((err) => {
done(err, null)
})
}
))
}
// Google ID
if (appdata.capabilities.manyAuthProviders && appconfig.auth.google && appconfig.auth.google.enabled) {
passport.use('google',
new GoogleStrategy({
clientID: appconfig.auth.google.clientId,
clientSecret: appconfig.auth.google.clientSecret,
callbackURL: appconfig.host + '/login/google/callback'
},
(accessToken, refreshToken, profile, cb) => {
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// Microsoft Accounts
if (appdata.capabilities.manyAuthProviders && appconfig.auth.microsoft && appconfig.auth.microsoft.enabled) {
passport.use('windowslive',
new WindowsLiveStrategy({
clientID: appconfig.auth.microsoft.clientId,
clientSecret: appconfig.auth.microsoft.clientSecret,
callbackURL: appconfig.host + '/login/ms/callback'
},
function (accessToken, refreshToken, profile, cb) {
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// Facebook
if (appdata.capabilities.manyAuthProviders && appconfig.auth.facebook && appconfig.auth.facebook.enabled) {
passport.use('facebook',
new FacebookStrategy({
clientID: appconfig.auth.facebook.clientId,
clientSecret: appconfig.auth.facebook.clientSecret,
callbackURL: appconfig.host + '/login/facebook/callback',
profileFields: ['id', 'displayName', 'email']
},
function (accessToken, refreshToken, profile, cb) {
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// GitHub
if (appdata.capabilities.manyAuthProviders && appconfig.auth.github && appconfig.auth.github.enabled) {
passport.use('github',
new GitHubStrategy({
clientID: appconfig.auth.github.clientId,
clientSecret: appconfig.auth.github.clientSecret,
callbackURL: appconfig.host + '/login/github/callback',
scope: [ 'user:email' ]
},
(accessToken, refreshToken, profile, cb) => {
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// Slack
if (appdata.capabilities.manyAuthProviders && appconfig.auth.slack && appconfig.auth.slack.enabled) {
passport.use('slack',
new SlackStrategy({
clientID: appconfig.auth.slack.clientId,
clientSecret: appconfig.auth.slack.clientSecret,
callbackURL: appconfig.host + '/login/slack/callback'
},
(accessToken, refreshToken, profile, cb) => {
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// LDAP
if (appdata.capabilities.manyAuthProviders && appconfig.auth.ldap && appconfig.auth.ldap.enabled) {
passport.use('ldapauth',
new LdapStrategy({
server: {
url: appconfig.auth.ldap.url,
bindDn: appconfig.auth.ldap.bindDn,
bindCredentials: appconfig.auth.ldap.bindCredentials,
searchBase: appconfig.auth.ldap.searchBase,
searchFilter: appconfig.auth.ldap.searchFilter,
searchAttributes: ['displayName', 'name', 'cn', 'mail'],
tlsOptions: (appconfig.auth.ldap.tlsEnabled) ? {
ca: [
fs.readFileSync(appconfig.auth.ldap.tlsCertPath)
]
} : {}
},
usernameField: 'email',
passReqToCallback: false
},
(profile, cb) => {
profile.provider = 'ldap'
profile.id = profile.dn
db.User.processProfile(profile).then((user) => {
return cb(null, user) || true
}).catch((err) => {
return cb(err, null) || true
})
}
))
}
// Create users for first-time
db.onReady.then(() => {
db.User.findOne({ provider: 'local', email: 'guest' }).then((c) => {
if (c < 1) {
// Create root admin account
winston.info('[AUTH] No administrator account found. Creating a new one...')
db.User.hashPassword('admin123').then((pwd) => {
return db.User.create({
provider: 'local',
email: appconfig.admin,
name: 'Administrator',
password: pwd,
rights: [{
role: 'admin',
path: '/',
exact: false,
deny: false
}]
})
}).then(() => {
winston.info('[AUTH] Administrator account created successfully!')
}).then(() => {
if (appdata.capabilities.guest) {
// Create guest account
return db.User.create({
provider: 'local',
email: 'guest',
name: 'Guest',
password: '',
rights: [{
role: 'read',
path: '/',
exact: false,
deny: !appconfig.public
}]
}).then(() => {
winston.info('[AUTH] Guest account created successfully!')
})
} else {
return true
}
}).catch((err) => {
winston.error('[AUTH] An error occured while creating administrator/guest account:')
winston.error(err)
})
}
})
return true
})
}

58
libs/config.js

@ -0,0 +1,58 @@
'use strict'
/* global winston */
const fs = require('fs')
const yaml = require('js-yaml')
const _ = require('lodash')
/**
* Load Application Configuration
*
* @param {Object} confPaths Path to the configuration files
* @return {Object} Application Configuration
*/
module.exports = (confPaths) => {
confPaths = _.defaults(confPaths, {
config: './config.yml',
data: './app/data.yml'
})
let appconfig = {}
let appdata = {}
try {
appconfig = yaml.safeLoad(fs.readFileSync(confPaths.config, 'utf8'))
appdata = yaml.safeLoad(fs.readFileSync(confPaths.data, 'utf8'))
} catch (ex) {
winston.error(ex)
process.exit(1)
}
// Merge with defaults
appconfig = _.defaultsDeep(appconfig, appdata.defaults.config)
// List authentication strategies
if (appdata.capabilities.manyAuthProviders) {
appconfig.authStrategies = {
list: _.filter(appconfig.auth, ['enabled', true]),
socialEnabled: (_.chain(appconfig.auth).omit('local').reject({ enabled: false }).value().length > 0)
}
if (appconfig.authStrategies.list.length < 1) {
winston.error(new Error('You must enable at least 1 authentication strategy!'))
process.exit(1)
}
} else {
appconfig.authStrategies = {
list: { local: { enabled: true } },
socialEnabled: false
}
}
return {
config: appconfig,
data: appdata
}
}

120
libs/rights.js

@ -0,0 +1,120 @@
'use strict'
/* global db */
const _ = require('lodash')
/**
* Rights
*/
module.exports = {
guest: {
provider: 'local',
email: 'guest',
name: 'Guest',
password: '',
rights: [
{
role: 'read',
path: '/',
deny: false,
exact: false
}
]
},
/**
* Initialize Rights module
*
* @return {void} Void
*/
init () {
let self = this
db.onReady.then(() => {
db.User.findOne({ provider: 'local', email: 'guest' }).then((u) => {
if (u) {
self.guest = u
}
})
})
},
/**
* Check user permissions for this request
*
* @param {object} req The request object
* @return {object} List of permissions for this request
*/
check (req) {
let self = this
let perm = {
read: false,
write: false,
manage: false
}
let rt = []
let p = _.chain(req.originalUrl).toLower().trim().value()
// Load User Rights
if (_.isArray(req.user.rights)) {
rt = req.user.rights
}
// Is admin?
if (_.find(rt, { role: 'admin' })) {
perm.read = true
perm.write = true
perm.manage = true
} else if (self.checkRole(p, rt, 'write')) {
perm.read = true
perm.write = true
} else if (self.checkRole(p, rt, 'read')) {
perm.read = true
}
return perm
},
/**
* Check for a specific role based on list of user rights
*
* @param {String} p Base path
* @param {array<object>} rt The user rights
* @param {string} role The minimum role required
* @return {boolean} True if authorized
*/
checkRole (p, rt, role) {
// Check specific role on path
let filteredRights = _.filter(rt, (r) => {
if (r.role === role || (r.role === 'write' && role === 'read')) {
if ((!r.exact && _.startsWith(p, r.path)) || (r.exact && p === r.path)) {
return true
}
}
return false
})
// Check for deny scenario
let isValid = false
if (filteredRights.length > 1) {
isValid = !_.chain(filteredRights).sortBy((r) => {
return r.path.length + ((r.deny) ? 0.5 : 0)
}).last().get('deny').value()
} else if (filteredRights.length === 1 && filteredRights[0].deny === false) {
isValid = true
}
// Deny by default
return isValid
}
}

19
middlewares/auth.js

@ -1,5 +1,7 @@
'use strict'
/* global appdata, rights */
const moment = require('moment-timezone')
/**
@ -14,13 +16,24 @@ module.exports = (req, res, next) => {
// Is user authenticated ?
if (!req.isAuthenticated()) {
return res.redirect('/login')
if (!appdata.capabilities.guest || req.app.locals.appconfig.public !== true) {
return res.redirect('/login')
} else {
req.user = rights.guest
res.locals.isGuest = true
}
} else if (appdata.capabilities.guest) {
res.locals.isGuest = false
}
// Check permissions
if (!rights.check(req, 'read')) {
return res.render('error-forbidden')
if (appdata.capabilities.rights) {
res.locals.rights = rights.check(req)
if (!res.locals.rights.read) {
return res.render('error-forbidden')
}
}
// Set i18n locale

2
middlewares/security.js

@ -1,5 +1,7 @@
'use strict'
/* global app */
/**
* Security Middleware
*

12
package.json

@ -73,7 +73,7 @@
"i18next-node-fs-backend": "^0.1.3",
"image-size": "^0.5.1",
"jimp": "github:ngpixel/jimp",
"js-yaml": "^3.8.1",
"js-yaml": "^3.8.2",
"klaw": "^1.3.1",
"levelup": "^1.3.5",
"lodash": "^4.17.3",
@ -95,13 +95,18 @@
"multer": "^1.2.1",
"ora": "^1.2.0",
"passport": "^0.3.2",
"passport-facebook": "^2.1.1",
"passport-github2": "^0.1.10",
"passport-google-oauth20": "^1.0.0",
"passport-ldapauth": "^1.0.0",
"passport-local": "^1.0.0",
"passport-slack": "0.0.7",
"passport-windowslive": "^1.0.2",
"passport.socketio": "^3.7.0",
"pm2": "^2.4.3",
"pug": "^2.0.0-beta11",
"read-chunk": "^2.0.0",
"remove-markdown": "^0.1.0",
"requarks-core": "^0.2.2",
"request": "^2.81.0",
"search-index-adder": "github:ngpixel/search-index-adder",
"search-index-searcher": "github:ngpixel/search-index-searcher",
@ -116,7 +121,7 @@
"through2": "^2.0.3",
"validator": "^7.0.0",
"validator-as-promised": "^1.0.2",
"winston": "^2.3.0"
"winston": "^2.3.1"
},
"devDependencies": {
"babel-cli": "latest",
@ -165,7 +170,6 @@
"winston",
"ws",
"Mongoose",
"CORE_PATH",
"ROOTPATH",
"IS_DEBUG",
"PROCNAME",

9
server.js

@ -9,7 +9,6 @@
global.PROCNAME = 'SERVER'
global.ROOTPATH = __dirname
global.IS_DEBUG = process.env.NODE_ENV === 'development'
global.CORE_PATH = (IS_DEBUG) ? ROOTPATH + '/../core/' : ROOTPATH + '/node_modules/requarks-core/'
if (IS_DEBUG) {
try { require('newrelic') } catch (err) {}
@ -17,7 +16,7 @@ if (IS_DEBUG) {
process.env.VIPS_WARNING = false
let appconf = require(CORE_PATH + 'core-libs/config')()
let appconf = require('./libs/config')()
global.appconfig = appconf.config
global.appdata = appconf.data
@ -63,7 +62,7 @@ const session = require('express-session')
const SessionMongoStore = require('connect-mongo')(session)
const socketio = require('socket.io')
var mw = autoload(CORE_PATH + '/core-middlewares')
var mw = autoload(path.join(ROOTPATH, '/middlewares'))
var ctrl = autoload(path.join(ROOTPATH, '/controllers'))
// ----------------------------------------
@ -90,8 +89,8 @@ app.use(express.static(path.join(ROOTPATH, 'assets')))
// Passport Authentication
// ----------------------------------------
require(CORE_PATH + 'core-libs/auth')(passport)
global.rights = require(CORE_PATH + 'core-libs/rights')
require('./libs/auth')(passport)
global.rights = require('./libs/rights')
rights.init()
var sessionStore = new SessionMongoStore({

Loading…
Cancel
Save