diff --git a/community_server/Dockerfile b/community_server/Dockerfile
index cb4f67d27..8129d581d 100644
--- a/community_server/Dockerfile
+++ b/community_server/Dockerfile
@@ -1,17 +1,17 @@
-FROM phpdockerio/php74-fpm
-
-# install php fpm
-RUN apt-get update \
- && apt-get -y --no-install-recommends install curl unzip php7.4-curl php7.4-fpm php7.4-mbstring php7.4-intl php7.4-xml php7.4-pdo php7.4-mysql \
- && apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
-
-WORKDIR /var/www/cakephp
-RUN mkdir logs && mkdir tmp && chmod 777 logs && chmod 777 tmp
-COPY ./community_server/ .
-COPY ./configs/community_server/app.php ./config/
-
-RUN composer update
-RUN composer dump-autoload
-
-
-
+FROM phpdockerio/php74-fpm
+
+# install php fpm
+RUN apt-get update \
+ && apt-get -y --no-install-recommends install curl unzip php7.4-curl php7.4-fpm php7.4-mbstring php7.4-intl php7.4-xml php7.4-pdo php7.4-mysql \
+ && apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
+
+WORKDIR /var/www/cakephp
+RUN mkdir logs && mkdir tmp && chmod 777 logs && chmod 777 tmp
+COPY ./community_server/ .
+COPY ./configs/community_server/app.php ./config/
+
+RUN composer update
+RUN composer dump-autoload
+
+
+
diff --git a/community_server/config/nginx/fastcgi.conf b/community_server/config/nginx/fastcgi.conf
index c2976fe91..238f7869f 100644
--- a/community_server/config/nginx/fastcgi.conf
+++ b/community_server/config/nginx/fastcgi.conf
@@ -1,25 +1,25 @@
-fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-fastcgi_param QUERY_STRING $query_string;
-fastcgi_param REQUEST_METHOD $request_method;
-fastcgi_param CONTENT_TYPE $content_type;
-fastcgi_param CONTENT_LENGTH $content_length;
-
-fastcgi_param SCRIPT_NAME $fastcgi_script_name;
-fastcgi_param REQUEST_URI $request_uri;
-fastcgi_param DOCUMENT_URI $document_uri;
-fastcgi_param DOCUMENT_ROOT $document_root;
-fastcgi_param SERVER_PROTOCOL $server_protocol;
-fastcgi_param REQUEST_SCHEME $scheme;
-fastcgi_param HTTPS $https if_not_empty;
-
-fastcgi_param GATEWAY_INTERFACE CGI/1.1;
-fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
-
-fastcgi_param REMOTE_ADDR $remote_addr;
-fastcgi_param REMOTE_PORT $remote_port;
-fastcgi_param SERVER_ADDR $server_addr;
-fastcgi_param SERVER_PORT $server_port;
-fastcgi_param SERVER_NAME $server_name;
-
-# PHP only, required if PHP was built with --enable-force-cgi-redirect
+fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+fastcgi_param QUERY_STRING $query_string;
+fastcgi_param REQUEST_METHOD $request_method;
+fastcgi_param CONTENT_TYPE $content_type;
+fastcgi_param CONTENT_LENGTH $content_length;
+
+fastcgi_param SCRIPT_NAME $fastcgi_script_name;
+fastcgi_param REQUEST_URI $request_uri;
+fastcgi_param DOCUMENT_URI $document_uri;
+fastcgi_param DOCUMENT_ROOT $document_root;
+fastcgi_param SERVER_PROTOCOL $server_protocol;
+fastcgi_param REQUEST_SCHEME $scheme;
+fastcgi_param HTTPS $https if_not_empty;
+
+fastcgi_param GATEWAY_INTERFACE CGI/1.1;
+fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
+
+fastcgi_param REMOTE_ADDR $remote_addr;
+fastcgi_param REMOTE_PORT $remote_port;
+fastcgi_param SERVER_ADDR $server_addr;
+fastcgi_param SERVER_PORT $server_port;
+fastcgi_param SERVER_NAME $server_name;
+
+# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
\ No newline at end of file
diff --git a/community_server/config/nginx/mime.types b/community_server/config/nginx/mime.types
index 84c644fc7..cd3d700ea 100644
--- a/community_server/config/nginx/mime.types
+++ b/community_server/config/nginx/mime.types
@@ -1,88 +1,88 @@
-types {
- text/html html htm shtml;
- text/css css;
- text/xml xml;
- image/gif gif;
- image/jpeg jpeg jpg;
- application/javascript js;
- application/atom+xml atom;
- application/rss+xml rss;
-
- text/mathml mml;
- text/plain txt;
- text/vnd.sun.j2me.app-descriptor jad;
- text/vnd.wap.wml wml;
- text/x-component htc;
-
- image/png png;
- image/tiff tif tiff;
- image/vnd.wap.wbmp wbmp;
- image/x-icon ico;
- image/x-jng jng;
- image/x-ms-bmp bmp;
- image/svg+xml svg svgz;
- image/webp webp;
-
- application/font-woff woff;
- application/java-archive jar war ear;
- application/json json;
- application/mac-binhex40 hqx;
- application/msword doc;
- application/pdf pdf;
- application/postscript ps eps ai;
- application/rtf rtf;
- application/vnd.apple.mpegurl m3u8;
- application/vnd.ms-excel xls;
- application/vnd.ms-fontobject eot;
- application/vnd.ms-powerpoint ppt;
- application/vnd.wap.wmlc wmlc;
- application/vnd.google-earth.kml+xml kml;
- application/vnd.google-earth.kmz kmz;
- application/x-7z-compressed 7z;
- application/x-cocoa cco;
- application/x-java-archive-diff jardiff;
- application/x-java-jnlp-file jnlp;
- application/x-makeself run;
- application/x-perl pl pm;
- application/x-pilot prc pdb;
- application/x-rar-compressed rar;
- application/x-redhat-package-manager rpm;
- application/x-sea sea;
- application/x-shockwave-flash swf;
- application/x-stuffit sit;
- application/x-tcl tcl tk;
- application/x-x509-ca-cert der pem crt;
- application/x-xpinstall xpi;
- application/xhtml+xml xhtml;
- application/xspf+xml xspf;
- application/zip zip;
-
- application/octet-stream bin exe dll;
- application/octet-stream deb;
- application/octet-stream dmg;
- application/octet-stream iso img;
- application/octet-stream msi msp msm;
-
- application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
- application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
-
- audio/midi mid midi kar;
- audio/mpeg mp3;
- audio/ogg ogg;
- audio/x-m4a m4a;
- audio/x-realaudio ra;
-
- video/3gpp 3gpp 3gp;
- video/mp2t ts;
- video/mp4 mp4;
- video/mpeg mpeg mpg;
- video/quicktime mov;
- video/webm webm;
- video/x-flv flv;
- video/x-m4v m4v;
- video/x-mng mng;
- video/x-ms-asf asx asf;
- video/x-ms-wmv wmv;
- video/x-msvideo avi;
-}
+types {
+ text/html html htm shtml;
+ text/css css;
+ text/xml xml;
+ image/gif gif;
+ image/jpeg jpeg jpg;
+ application/javascript js;
+ application/atom+xml atom;
+ application/rss+xml rss;
+
+ text/mathml mml;
+ text/plain txt;
+ text/vnd.sun.j2me.app-descriptor jad;
+ text/vnd.wap.wml wml;
+ text/x-component htc;
+
+ image/png png;
+ image/tiff tif tiff;
+ image/vnd.wap.wbmp wbmp;
+ image/x-icon ico;
+ image/x-jng jng;
+ image/x-ms-bmp bmp;
+ image/svg+xml svg svgz;
+ image/webp webp;
+
+ application/font-woff woff;
+ application/java-archive jar war ear;
+ application/json json;
+ application/mac-binhex40 hqx;
+ application/msword doc;
+ application/pdf pdf;
+ application/postscript ps eps ai;
+ application/rtf rtf;
+ application/vnd.apple.mpegurl m3u8;
+ application/vnd.ms-excel xls;
+ application/vnd.ms-fontobject eot;
+ application/vnd.ms-powerpoint ppt;
+ application/vnd.wap.wmlc wmlc;
+ application/vnd.google-earth.kml+xml kml;
+ application/vnd.google-earth.kmz kmz;
+ application/x-7z-compressed 7z;
+ application/x-cocoa cco;
+ application/x-java-archive-diff jardiff;
+ application/x-java-jnlp-file jnlp;
+ application/x-makeself run;
+ application/x-perl pl pm;
+ application/x-pilot prc pdb;
+ application/x-rar-compressed rar;
+ application/x-redhat-package-manager rpm;
+ application/x-sea sea;
+ application/x-shockwave-flash swf;
+ application/x-stuffit sit;
+ application/x-tcl tcl tk;
+ application/x-x509-ca-cert der pem crt;
+ application/x-xpinstall xpi;
+ application/xhtml+xml xhtml;
+ application/xspf+xml xspf;
+ application/zip zip;
+
+ application/octet-stream bin exe dll;
+ application/octet-stream deb;
+ application/octet-stream dmg;
+ application/octet-stream iso img;
+ application/octet-stream msi msp msm;
+
+ application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
+ application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
+ application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
+
+ audio/midi mid midi kar;
+ audio/mpeg mp3;
+ audio/ogg ogg;
+ audio/x-m4a m4a;
+ audio/x-realaudio ra;
+
+ video/3gpp 3gpp 3gp;
+ video/mp2t ts;
+ video/mp4 mp4;
+ video/mpeg mpeg mpg;
+ video/quicktime mov;
+ video/webm webm;
+ video/x-flv flv;
+ video/x-m4v m4v;
+ video/x-mng mng;
+ video/x-ms-asf asx asf;
+ video/x-ms-wmv wmv;
+ video/x-msvideo avi;
+}
diff --git a/community_server/config/nginx/nginx.conf b/community_server/config/nginx/nginx.conf
index 5aa5c3095..197c96167 100644
--- a/community_server/config/nginx/nginx.conf
+++ b/community_server/config/nginx/nginx.conf
@@ -1,86 +1,86 @@
-
-server {
-
- listen 80 ;
- listen [::]:80;
- server_name 0.0.0.0;
-
- #include /etc/nginx/common/protect.conf;
- #include /etc/nginx/common/protect_add_header.conf;
- #include /etc/nginx/common/ssl.conf;
-
-
- root /usr/share/nginx/html/webroot;
- index index.php;
-
- location ~* \.(png|jpg|ico|webp)\$ {
- expires 30d;
- }
-
- location ~* \.(js|css) {
- # expires 1d;
- expires 1d;
- }
-
- location ~ \.php\$ {
- # regex to split $uri to $fastcgi_script_name and $fastcgi_path
- fastcgi_split_path_info ^(.+\.php)(/.+)$;
-
- # Check that the PHP script exists before passing it
- try_files $fastcgi_script_name =404;
-
- # Bypass the fact that try_files resets $fastcgi_path_info
- # see: http://trac.nginx.org/nginx/ticket/321
- set $path_info $fastcgi_path_info;
- fastcgi_param PATH_INFO $path_info;
-
- fastcgi_index index.php;
- include fastcgi.conf;
-
- #fastcgi_pass unix:/run/php/php7.3-fpm.sock;
- fastcgi_pass 127.0.0.1:9000;
-
- }
-
- location ~ /\.ht {
- deny all;
- }
-
- location /account {
- proxy_http_version 1.1;
- proxy_set_header Upgrade \$http_upgrade;
- proxy_set_header Connection 'upgrade';
- proxy_cache_bypass \$http_upgrade;
- proxy_set_header X-Real-IP \$remote_addr;
- proxy_set_header X-Forwarded-For \$remote_addr;
- proxy_set_header Host \$host;
- rewrite /account/(.*) /\$1 break;
-
- #proxy_next_upstream error timeout invalid_header http_502 non_idempotent;
- proxy_pass http://login-server:1200;
- proxy_redirect off;
-
-
- }
-
- location /login_api {
- proxy_http_version 1.1;
- proxy_set_header Upgrade \$http_upgrade;
- proxy_set_header Connection 'upgrade';
- proxy_cache_bypass \$http_upgrade;
- proxy_set_header X-Real-IP \$remote_addr;
- proxy_set_header X-Forwarded-For \$remote_addr;
- proxy_set_header Host \$host;
- rewrite /login_api/(.*) /\$1 break;
-
- proxy_pass http://login-server:1201;
- proxy_redirect off;
- }
-
- location / {
- try_files \$uri \$uri/ /index.php?\$args;
- }
-
-# access_log /var/log/nginx/access.log main;
-
+
+server {
+
+ listen 80 ;
+ listen [::]:80;
+ server_name 0.0.0.0;
+
+ #include /etc/nginx/common/protect.conf;
+ #include /etc/nginx/common/protect_add_header.conf;
+ #include /etc/nginx/common/ssl.conf;
+
+
+ root /usr/share/nginx/html/webroot;
+ index index.php;
+
+ location ~* \.(png|jpg|ico|webp)\$ {
+ expires 30d;
+ }
+
+ location ~* \.(js|css) {
+ # expires 1d;
+ expires 1d;
+ }
+
+ location ~ \.php\$ {
+ # regex to split $uri to $fastcgi_script_name and $fastcgi_path
+ fastcgi_split_path_info ^(.+\.php)(/.+)$;
+
+ # Check that the PHP script exists before passing it
+ try_files $fastcgi_script_name =404;
+
+ # Bypass the fact that try_files resets $fastcgi_path_info
+ # see: http://trac.nginx.org/nginx/ticket/321
+ set $path_info $fastcgi_path_info;
+ fastcgi_param PATH_INFO $path_info;
+
+ fastcgi_index index.php;
+ include fastcgi.conf;
+
+ #fastcgi_pass unix:/run/php/php7.3-fpm.sock;
+ fastcgi_pass 127.0.0.1:9000;
+
+ }
+
+ location ~ /\.ht {
+ deny all;
+ }
+
+ location /account {
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade \$http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_cache_bypass \$http_upgrade;
+ proxy_set_header X-Real-IP \$remote_addr;
+ proxy_set_header X-Forwarded-For \$remote_addr;
+ proxy_set_header Host \$host;
+ rewrite /account/(.*) /\$1 break;
+
+ #proxy_next_upstream error timeout invalid_header http_502 non_idempotent;
+ proxy_pass http://login-server:1200;
+ proxy_redirect off;
+
+
+ }
+
+ location /login_api {
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade \$http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_cache_bypass \$http_upgrade;
+ proxy_set_header X-Real-IP \$remote_addr;
+ proxy_set_header X-Forwarded-For \$remote_addr;
+ proxy_set_header Host \$host;
+ rewrite /login_api/(.*) /\$1 break;
+
+ proxy_pass http://login-server:1201;
+ proxy_redirect off;
+ }
+
+ location / {
+ try_files \$uri \$uri/ /index.php?\$args;
+ }
+
+# access_log /var/log/nginx/access.log main;
+
}
\ No newline at end of file
diff --git a/community_server/skeema/gradido_community/community_profiles.sql b/community_server/skeema/gradido_community/community_profiles.sql
index ea857bebf..22926b126 100644
--- a/community_server/skeema/gradido_community/community_profiles.sql
+++ b/community_server/skeema/gradido_community/community_profiles.sql
@@ -1,8 +1,8 @@
-CREATE TABLE `community_profiles` (
- `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
- `state_user_id` int(10) unsigned NOT NULL,
- `profile_img` longblob,
- `profile_desc` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
- PRIMARY KEY (`id`),
- KEY `state_user_id` (`state_user_id`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
+CREATE TABLE `community_profiles` (
+ `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
+ `state_user_id` int(10) unsigned NOT NULL,
+ `profile_img` longblob,
+ `profile_desc` varchar(2000) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
+ PRIMARY KEY (`id`),
+ KEY `state_user_id` (`state_user_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
diff --git a/community_server/src/Controller/Component/JsonRpcRequestClientComponent.php b/community_server/src/Controller/Component/JsonRpcRequestClientComponent.php
index d5ca72808..5ccf6d898 100644
--- a/community_server/src/Controller/Component/JsonRpcRequestClientComponent.php
+++ b/community_server/src/Controller/Component/JsonRpcRequestClientComponent.php
@@ -1,71 +1,71 @@
-rpcClient = new JsonRpcClient();
- }
-
- // @param id: if id = 0 call rand for it
- public function request($method, $params = [], $id = 0)
- {
-
- if(0 == $id) {
- $id = random_int(1, 12000);
- }
- $this->rpcClient->query($id, $method, $params);
-
- $message = $this->rpcClient->encode();
- return $this->sendRequest($message);
- // message: {"jsonrpc":"2.0","method":"add","params":[1,2],"id":1}
- }
-
- public function sendRequest($message) {
- $http = new Client();
-
- $response = $http->post($this->getGradidoNodeUrl(), $message, ['type' => 'json']);
- $responseStatus = $response->getStatusCode();
- if($responseStatus != 200) {
- return ['state' => 'error', 'type' => 'request error', 'msg' => 'server response status code isn\'t 200', 'details' => $responseStatus];
- }
- //$responseType = $response->getType();
- //if($responseType != 'application/json') {
-// return ['state' => 'error', 'type' => 'request error', 'msg' => 'server response isn\'t json', 'details' => $responseType];
-// }
- $json = $response->getJson();
- if($json == null) {
- //$responseType = $response->getType();
- return ['state' => 'error', 'type' => 'request error', 'msg' => 'server response isn\'t valid json'];
- }
- return $json;
- //return ['state' => 'success', 'data' => $json];
- }
-
- static public function getGradidoNodeUrl()
- {
- $gradidoNode = Configure::read('GradidoNode');
- return $gradidoNode['host'] . ':' . $gradidoNode['port'];
- }
-
-
-}
-
-
+rpcClient = new JsonRpcClient();
+ }
+
+ // @param id: if id = 0 call rand for it
+ public function request($method, $params = [], $id = 0)
+ {
+
+ if(0 == $id) {
+ $id = random_int(1, 12000);
+ }
+ $this->rpcClient->query($id, $method, $params);
+
+ $message = $this->rpcClient->encode();
+ return $this->sendRequest($message);
+ // message: {"jsonrpc":"2.0","method":"add","params":[1,2],"id":1}
+ }
+
+ public function sendRequest($message) {
+ $http = new Client();
+
+ $response = $http->post($this->getGradidoNodeUrl(), $message, ['type' => 'json']);
+ $responseStatus = $response->getStatusCode();
+ if($responseStatus != 200) {
+ return ['state' => 'error', 'type' => 'request error', 'msg' => 'server response status code isn\'t 200', 'details' => $responseStatus];
+ }
+ //$responseType = $response->getType();
+ //if($responseType != 'application/json') {
+// return ['state' => 'error', 'type' => 'request error', 'msg' => 'server response isn\'t json', 'details' => $responseType];
+// }
+ $json = $response->getJson();
+ if($json == null) {
+ //$responseType = $response->getType();
+ return ['state' => 'error', 'type' => 'request error', 'msg' => 'server response isn\'t valid json'];
+ }
+ return $json;
+ //return ['state' => 'success', 'data' => $json];
+ }
+
+ static public function getGradidoNodeUrl()
+ {
+ $gradidoNode = Configure::read('GradidoNode');
+ return $gradidoNode['host'] . ':' . $gradidoNode['port'];
+ }
+
+
+}
+
+
diff --git a/community_server/src/Model/Messages/Gradido/Key.php b/community_server/src/Model/Messages/Gradido/Key.php
index ace440729..b6190304c 100644
--- a/community_server/src/Model/Messages/Gradido/Key.php
+++ b/community_server/src/Model/Messages/Gradido/Key.php
@@ -22,9 +22,9 @@ class Key extends \Google\Protobuf\Internal\Message
* Optional. Data for populating the Message object.
*
* @type string $ed25519
- * ed25519 signature (libsodium default)
+ * ed25519 signature (libsodium default)
* @type string $ed25519_ref10
- * ed25519 ref10 signature
+ * ed25519 ref10 signature
* }
*/
public function __construct($data = NULL) {
@@ -33,7 +33,7 @@ class Key extends \Google\Protobuf\Internal\Message
}
/**
- * ed25519 signature (libsodium default)
+ * ed25519 signature (libsodium default)
*
* Generated from protobuf field bytes ed25519 = 2;
* @return string
@@ -44,7 +44,7 @@ class Key extends \Google\Protobuf\Internal\Message
}
/**
- * ed25519 signature (libsodium default)
+ * ed25519 signature (libsodium default)
*
* Generated from protobuf field bytes ed25519 = 2;
* @param string $var
@@ -59,7 +59,7 @@ class Key extends \Google\Protobuf\Internal\Message
}
/**
- * ed25519 ref10 signature
+ * ed25519 ref10 signature
*
* Generated from protobuf field bytes ed25519_ref10 = 3;
* @return string
@@ -70,7 +70,7 @@ class Key extends \Google\Protobuf\Internal\Message
}
/**
- * ed25519 ref10 signature
+ * ed25519 ref10 signature
*
* Generated from protobuf field bytes ed25519_ref10 = 3;
* @param string $var
diff --git a/community_server/src/Model/Messages/Gradido/SenderAmount.php b/community_server/src/Model/Messages/Gradido/SenderAmount.php
index 9b423947e..52a41f71f 100644
--- a/community_server/src/Model/Messages/Gradido/SenderAmount.php
+++ b/community_server/src/Model/Messages/Gradido/SenderAmount.php
@@ -22,7 +22,7 @@ class SenderAmount extends \Google\Protobuf\Internal\Message
*/
private $amount = 0;
/**
- * sender balance after transaction, including perishability
+ * sender balance after transaction, including perishability
*
* Generated from protobuf field sint64 senderFinalBalance = 3;
*/
@@ -37,7 +37,7 @@ class SenderAmount extends \Google\Protobuf\Internal\Message
* @type string $ed25519_sender_pubkey
* @type int|string $amount
* @type int|string $senderFinalBalance
- * sender balance after transaction, including perishability
+ * sender balance after transaction, including perishability
* }
*/
public function __construct($data = NULL) {
@@ -90,7 +90,7 @@ class SenderAmount extends \Google\Protobuf\Internal\Message
}
/**
- * sender balance after transaction, including perishability
+ * sender balance after transaction, including perishability
*
* Generated from protobuf field sint64 senderFinalBalance = 3;
* @return int|string
@@ -101,7 +101,7 @@ class SenderAmount extends \Google\Protobuf\Internal\Message
}
/**
- * sender balance after transaction, including perishability
+ * sender balance after transaction, including perishability
*
* Generated from protobuf field sint64 senderFinalBalance = 3;
* @param int|string $var
diff --git a/community_server/src/Model/Messages/Gradido/SignatureMap.php b/community_server/src/Model/Messages/Gradido/SignatureMap.php
index ea6f16150..228282747 100644
--- a/community_server/src/Model/Messages/Gradido/SignatureMap.php
+++ b/community_server/src/Model/Messages/Gradido/SignatureMap.php
@@ -14,7 +14,7 @@ use Google\Protobuf\Internal\GPBUtil;
class SignatureMap extends \Google\Protobuf\Internal\Message
{
/**
- * Each signature pair corresponds to a unique Key required to sign the transaction.
+ * Each signature pair corresponds to a unique Key required to sign the transaction.
*
* Generated from protobuf field repeated .model.messages.gradido.SignaturePair sigPair = 1;
*/
@@ -27,7 +27,7 @@ class SignatureMap extends \Google\Protobuf\Internal\Message
* Optional. Data for populating the Message object.
*
* @type \Model\Messages\Gradido\SignaturePair[]|\Google\Protobuf\Internal\RepeatedField $sigPair
- * Each signature pair corresponds to a unique Key required to sign the transaction.
+ * Each signature pair corresponds to a unique Key required to sign the transaction.
* }
*/
public function __construct($data = NULL) {
@@ -36,7 +36,7 @@ class SignatureMap extends \Google\Protobuf\Internal\Message
}
/**
- * Each signature pair corresponds to a unique Key required to sign the transaction.
+ * Each signature pair corresponds to a unique Key required to sign the transaction.
*
* Generated from protobuf field repeated .model.messages.gradido.SignaturePair sigPair = 1;
* @return \Google\Protobuf\Internal\RepeatedField
@@ -47,7 +47,7 @@ class SignatureMap extends \Google\Protobuf\Internal\Message
}
/**
- * Each signature pair corresponds to a unique Key required to sign the transaction.
+ * Each signature pair corresponds to a unique Key required to sign the transaction.
*
* Generated from protobuf field repeated .model.messages.gradido.SignaturePair sigPair = 1;
* @param \Model\Messages\Gradido\SignaturePair[]|\Google\Protobuf\Internal\RepeatedField $var
diff --git a/community_server/src/Model/Messages/Gradido/SignaturePair.php b/community_server/src/Model/Messages/Gradido/SignaturePair.php
index 4575a7d38..203eb5677 100644
--- a/community_server/src/Model/Messages/Gradido/SignaturePair.php
+++ b/community_server/src/Model/Messages/Gradido/SignaturePair.php
@@ -27,9 +27,9 @@ class SignaturePair extends \Google\Protobuf\Internal\Message
*
* @type string $pubKey
* @type string $ed25519
- * ed25519 signature (libsodium default)
+ * ed25519 signature (libsodium default)
* @type string $ed25519_ref10
- * ed25519 ref10 signature
+ * ed25519 ref10 signature
* }
*/
public function __construct($data = NULL) {
@@ -60,7 +60,7 @@ class SignaturePair extends \Google\Protobuf\Internal\Message
}
/**
- * ed25519 signature (libsodium default)
+ * ed25519 signature (libsodium default)
*
* Generated from protobuf field bytes ed25519 = 2;
* @return string
@@ -71,7 +71,7 @@ class SignaturePair extends \Google\Protobuf\Internal\Message
}
/**
- * ed25519 signature (libsodium default)
+ * ed25519 signature (libsodium default)
*
* Generated from protobuf field bytes ed25519 = 2;
* @param string $var
@@ -86,7 +86,7 @@ class SignaturePair extends \Google\Protobuf\Internal\Message
}
/**
- * ed25519 ref10 signature
+ * ed25519 ref10 signature
*
* Generated from protobuf field bytes ed25519_ref10 = 3;
* @return string
@@ -97,7 +97,7 @@ class SignaturePair extends \Google\Protobuf\Internal\Message
}
/**
- * ed25519 ref10 signature
+ * ed25519 ref10 signature
*
* Generated from protobuf field bytes ed25519_ref10 = 3;
* @param string $var
diff --git a/community_server/src/Model/Messages/Gradido/StateCreateGroup.php b/community_server/src/Model/Messages/Gradido/StateCreateGroup.php
index dad246745..4273ef93d 100644
--- a/community_server/src/Model/Messages/Gradido/StateCreateGroup.php
+++ b/community_server/src/Model/Messages/Gradido/StateCreateGroup.php
@@ -9,7 +9,7 @@ use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
- * need signature from this group and from parent (if it isn't zero)
+ * need signature from this group and from parent (if it isn't zero)
*
* Generated from protobuf message model.messages.gradido.StateCreateGroup
*/
diff --git a/community_server/src/Model/Messages/Gradido/StateGroupChangeParent.php b/community_server/src/Model/Messages/Gradido/StateGroupChangeParent.php
index 9cd15175a..c5371d76c 100644
--- a/community_server/src/Model/Messages/Gradido/StateGroupChangeParent.php
+++ b/community_server/src/Model/Messages/Gradido/StateGroupChangeParent.php
@@ -9,7 +9,7 @@ use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
- * need signature from this group and from both parents (if it isn't zero)
+ * need signature from this group and from both parents (if it isn't zero)
*
* Generated from protobuf message model.messages.gradido.StateGroupChangeParent
*/
diff --git a/community_server/src/Model/Messages/Gradido/Timestamp.php b/community_server/src/Model/Messages/Gradido/Timestamp.php
index 7b2316720..19721729b 100644
--- a/community_server/src/Model/Messages/Gradido/Timestamp.php
+++ b/community_server/src/Model/Messages/Gradido/Timestamp.php
@@ -16,13 +16,13 @@ use Google\Protobuf\Internal\GPBUtil;
class Timestamp extends \Google\Protobuf\Internal\Message
{
/**
- * Number of complete seconds since the start of the epoch
+ * Number of complete seconds since the start of the epoch
*
* Generated from protobuf field int64 seconds = 1;
*/
private $seconds = 0;
/**
- * Number of nanoseconds since the start of the last second
+ * Number of nanoseconds since the start of the last second
*
* Generated from protobuf field int32 nanos = 2;
*/
@@ -35,9 +35,9 @@ class Timestamp extends \Google\Protobuf\Internal\Message
* Optional. Data for populating the Message object.
*
* @type int|string $seconds
- * Number of complete seconds since the start of the epoch
+ * Number of complete seconds since the start of the epoch
* @type int $nanos
- * Number of nanoseconds since the start of the last second
+ * Number of nanoseconds since the start of the last second
* }
*/
public function __construct($data = NULL) {
@@ -46,7 +46,7 @@ class Timestamp extends \Google\Protobuf\Internal\Message
}
/**
- * Number of complete seconds since the start of the epoch
+ * Number of complete seconds since the start of the epoch
*
* Generated from protobuf field int64 seconds = 1;
* @return int|string
@@ -57,7 +57,7 @@ class Timestamp extends \Google\Protobuf\Internal\Message
}
/**
- * Number of complete seconds since the start of the epoch
+ * Number of complete seconds since the start of the epoch
*
* Generated from protobuf field int64 seconds = 1;
* @param int|string $var
@@ -72,7 +72,7 @@ class Timestamp extends \Google\Protobuf\Internal\Message
}
/**
- * Number of nanoseconds since the start of the last second
+ * Number of nanoseconds since the start of the last second
*
* Generated from protobuf field int32 nanos = 2;
* @return int
@@ -83,7 +83,7 @@ class Timestamp extends \Google\Protobuf\Internal\Message
}
/**
- * Number of nanoseconds since the start of the last second
+ * Number of nanoseconds since the start of the last second
*
* Generated from protobuf field int32 nanos = 2;
* @param int $var
diff --git a/community_server/src/Model/Messages/Gradido/TimestampSeconds.php b/community_server/src/Model/Messages/Gradido/TimestampSeconds.php
index 2646a06c9..2a588a312 100644
--- a/community_server/src/Model/Messages/Gradido/TimestampSeconds.php
+++ b/community_server/src/Model/Messages/Gradido/TimestampSeconds.php
@@ -16,7 +16,7 @@ use Google\Protobuf\Internal\GPBUtil;
class TimestampSeconds extends \Google\Protobuf\Internal\Message
{
/**
- * Number of complete seconds since the start of the epoch
+ * Number of complete seconds since the start of the epoch
*
* Generated from protobuf field int64 seconds = 1;
*/
@@ -29,7 +29,7 @@ class TimestampSeconds extends \Google\Protobuf\Internal\Message
* Optional. Data for populating the Message object.
*
* @type int|string $seconds
- * Number of complete seconds since the start of the epoch
+ * Number of complete seconds since the start of the epoch
* }
*/
public function __construct($data = NULL) {
@@ -38,7 +38,7 @@ class TimestampSeconds extends \Google\Protobuf\Internal\Message
}
/**
- * Number of complete seconds since the start of the epoch
+ * Number of complete seconds since the start of the epoch
*
* Generated from protobuf field int64 seconds = 1;
* @return int|string
@@ -49,7 +49,7 @@ class TimestampSeconds extends \Google\Protobuf\Internal\Message
}
/**
- * Number of complete seconds since the start of the epoch
+ * Number of complete seconds since the start of the epoch
*
* Generated from protobuf field int64 seconds = 1;
* @param int|string $var
diff --git a/community_server/src/Model/Messages/Gradido/TransactionCreation.php b/community_server/src/Model/Messages/Gradido/TransactionCreation.php
index 1c8f33dba..ae5f86e07 100644
--- a/community_server/src/Model/Messages/Gradido/TransactionCreation.php
+++ b/community_server/src/Model/Messages/Gradido/TransactionCreation.php
@@ -9,27 +9,27 @@ use Google\Protobuf\Internal\RepeatedField;
use Google\Protobuf\Internal\GPBUtil;
/**
- * need signature from group admin or
- * percent of group users another than the receiver
+ * need signature from group admin or
+ * percent of group users another than the receiver
*
* Generated from protobuf message model.messages.gradido.TransactionCreation
*/
class TransactionCreation extends \Google\Protobuf\Internal\Message
{
/**
- * 40 Byte
+ * 40 Byte
*
* Generated from protobuf field .model.messages.gradido.ReceiverAmount receiverAmount = 1;
*/
private $receiverAmount = null;
/**
- * 4 Byte
+ * 4 Byte
*
* Generated from protobuf field sint32 ident_hash = 2;
*/
private $ident_hash = 0;
/**
- * 8 Byte
+ * 8 Byte
*
* Generated from protobuf field .model.messages.gradido.TimestampSeconds target_date = 3;
*/
@@ -42,11 +42,11 @@ class TransactionCreation extends \Google\Protobuf\Internal\Message
* Optional. Data for populating the Message object.
*
* @type \Model\Messages\Gradido\ReceiverAmount $receiverAmount
- * 40 Byte
+ * 40 Byte
* @type int $ident_hash
- * 4 Byte
+ * 4 Byte
* @type \Model\Messages\Gradido\TimestampSeconds $target_date
- * 8 Byte
+ * 8 Byte
* }
*/
public function __construct($data = NULL) {
@@ -55,7 +55,7 @@ class TransactionCreation extends \Google\Protobuf\Internal\Message
}
/**
- * 40 Byte
+ * 40 Byte
*
* Generated from protobuf field .model.messages.gradido.ReceiverAmount receiverAmount = 1;
* @return \Model\Messages\Gradido\ReceiverAmount
@@ -66,7 +66,7 @@ class TransactionCreation extends \Google\Protobuf\Internal\Message
}
/**
- * 40 Byte
+ * 40 Byte
*
* Generated from protobuf field .model.messages.gradido.ReceiverAmount receiverAmount = 1;
* @param \Model\Messages\Gradido\ReceiverAmount $var
@@ -81,7 +81,7 @@ class TransactionCreation extends \Google\Protobuf\Internal\Message
}
/**
- * 4 Byte
+ * 4 Byte
*
* Generated from protobuf field sint32 ident_hash = 2;
* @return int
@@ -92,7 +92,7 @@ class TransactionCreation extends \Google\Protobuf\Internal\Message
}
/**
- * 4 Byte
+ * 4 Byte
*
* Generated from protobuf field sint32 ident_hash = 2;
* @param int $var
@@ -107,7 +107,7 @@ class TransactionCreation extends \Google\Protobuf\Internal\Message
}
/**
- * 8 Byte
+ * 8 Byte
*
* Generated from protobuf field .model.messages.gradido.TimestampSeconds target_date = 3;
* @return \Model\Messages\Gradido\TimestampSeconds
@@ -118,7 +118,7 @@ class TransactionCreation extends \Google\Protobuf\Internal\Message
}
/**
- * 8 Byte
+ * 8 Byte
*
* Generated from protobuf field .model.messages.gradido.TimestampSeconds target_date = 3;
* @param \Model\Messages\Gradido\TimestampSeconds $var
diff --git a/community_server/src/Model/Transactions/Transaction.php b/community_server/src/Model/Transactions/Transaction.php
index 76353595a..99bd5fae7 100644
--- a/community_server/src/Model/Transactions/Transaction.php
+++ b/community_server/src/Model/Transactions/Transaction.php
@@ -1,269 +1,269 @@
-mProtoTransaction = $base64Data;
- $this->mTransactionBody = new TransactionBody($this->mProtoTransaction->getBodyBytes());
- return;
- }
-
- try {
- $transactionBin = sodium_base642bin($base64Data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
- } catch(\SodiumException $e) {
- //$this->addError('Transaction', $e->getMessage());// . ' ' . $base64Data);
- //return;
- $transactionBin = base64_decode($base64Data, true);
- if($transactionBin == false) {
- $this->addError('Transaction', $e->getMessage());// . ' ' . $base64Data);
- return;
- }
- }
- //*/}
-
- if($transactionBin == false) {
- //$this->addError('base64 decode failed');
- $this->addError('Transaction', 'base64 decode error: ' . $base64Data);
- } else {
- //var_dump($transactionBin);
- $this->mProtoTransaction = new \Model\Messages\Gradido\Transaction();
- try {
- $this->mProtoTransaction->mergeFromString($transactionBin);
- //var_dump($this->mProtoTransaction);
- // cannot catch Exception with cakePHP, I don't know why
- } catch(\Google\Protobuf\Internal\GPBDecodeException $e) {
- //var_dump($e);
- $this->addError('Transaction', $e->getMessage());
- return;
- }//*/
-
- //echo 'serialize to json:
';
- //echo $this->mProtoTransaction->serializeToJsonString();
- //echo "body bytes:
";
- //var_dump($this->mProtoTransaction->getBodyBytes());
- //echo "
end body bytes
";
- $this->mTransactionBody = new TransactionBody($this->mProtoTransaction->getBodyBytes());
- }
- }
-
- static public function build(\Model\Messages\Gradido\TransactionBody $transactionBody, $senderKeyPair)
- {
- $protoTransaction = new \Model\Messages\Gradido\Transaction();
-
- $recevied = new \Model\Messages\Gradido\TimestampSeconds();
- $recevied->setSeconds(time());
- $protoTransaction->setReceived($recevied);
-
- $bodyBytes = $transactionBody->serializeToString();
-
- $sigMap = SignatureMap::build($bodyBytes, [$senderKeyPair]);
- $protoTransaction->setSigMap($sigMap->getProto());
-
- $protoTransaction->setBodyBytes($bodyBytes);
-
- return $protoTransaction;
-
- }
-
- public function getTransactionBody() {
- return $this->mTransactionBody;
- }
-
- public function getFirstPublic() {
- $sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair();
- return $sigPairs[0]->getPubKey();
- }
-
- public function getId() {
- return $this->mProtoTransaction->getId();
- }
-
- public function validate() {
- $sigMap = $this->mProtoTransaction->getSigMap();
- if(!$sigMap) {
- $this->addError('Transaction', 'signature map is zero');
- return false;
- }
- //var_dump($sigMap);
- //die();
- $sigPairs = $sigMap->getSigPair();
- $bodyBytes = $this->mProtoTransaction->getBodyBytes();
-
-
- if(!$sigPairs || count($sigPairs) < 1) {
- $this->addError('Transaction::validate', 'no signature found');
- return false;
- }
-
- // check signature(s)
- foreach($sigPairs as $sigPair) {
- //echo 'sig Pair: '; var_dump($sigPair); echo "
";
- $pubkey = $sigPair->getPubKey();
- $signature = $sigPair->getEd25519();
- //echo "verify bodybytes:
" . bin2hex($bodyBytes) . '
';
- if (!\Sodium\crypto_sign_verify_detached($signature, $bodyBytes, $pubkey)) {
- $this->addError('Transaction::validate', 'signature for key ' . bin2hex($pubkey) . ' isn\'t valid ' );
- return false;
- }
- }
-
- if(!$this->mTransactionBody->validate($sigPairs)) {
- $this->addErrors($this->mTransactionBody->getErrors());
- return false;
- }
-
- return true;
- }
-
- public function save()
- {
- $connection = ConnectionManager::get('default');
- $connection->begin();
- //id transaction_id signature pubkey
-
- if (!$this->mTransactionBody->save($this->getFirstPublic(), $this->mProtoTransaction->getSigMap())) {
- $this->addErrors($this->mTransactionBody->getErrors());
- $connection->rollback();
- return false;
- }
-
- // save transaction signatures
- $transactionsSignaturesTable = TableRegistry::getTableLocator()->get('transaction_signatures');
- $transactionId = $this->mTransactionBody->getTransactionID();
- //signature pubkey
-
- $sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair();
- //echo "sigPairs: "; var_dump($sigPairs);
- $signatureEntitys = [];
- foreach($sigPairs as $sigPair) {
- $signatureEntity = $transactionsSignaturesTable->newEntity();
- $signatureEntity->transaction_id = $transactionId;
- $signatureEntity->signature = $sigPair->getEd25519();
- $signatureEntity->pubkey = $sigPair->getPubKey();
- array_push($signatureEntitys, $signatureEntity);
- }
- //debug($signatureEntitys);
- if(!$transactionsSignaturesTable->saveMany($signatureEntitys)) {
- foreach($signatureEntitys as $entity) {
- $errors = $entity->getErrors();
- if(!$errors && count($errors) > 0) {
- $pubkeyHex = bin2hex($entity->pubkey);
- $this->addError('Transaction::save', 'error saving signature for pubkey: ' . $pubkeyHex . ', with errors: ' . json_encode($errors) );
- }
- }
- $connection->rollback();
- return false;
- }
-
- $connection->commit();
-
- $this->mTransactionBody->getSpecificTransaction()->sendNotificationEmail($this->mTransactionBody->getMemo());
-
- return true;
- }
-
- static public function fromTable($id)
- {
- $transactionsTable = TableRegistry::getTableLocator()->get('transactions');
- $transactionEntry = $transactionsTable
- ->find('all')
- ->where(['id' => $id])
- ->contain([
- 'TransactionCreations',
- 'TransactionSendCoins',
- 'TransactionSignatures'])
- ->first();
- //var_dump($transactionEntry->toArray());
- $protoTransaction = new \Model\Messages\Gradido\Transaction();
-
-
-
- $protoTransaction->setId($transactionEntry->id);
-
-
- $recevied = new \Model\Messages\Gradido\TimestampSeconds();
- $recevied->setSeconds($transactionEntry->received->getTimestamp());
- $protoTransaction->setReceived($recevied);
-
-
- $sigMap = SignatureMap::fromEntity($transactionEntry->transaction_signatures);
- $protoTransaction->setSigMap($sigMap->getProto());
-
- //echo "sig map: check
";
- $protoTransaction->setTxHash(stream_get_contents($transactionEntry->tx_hash));
-
- $body = TransactionBody::fromEntity($transactionEntry->memo, $transactionEntry);
- if(is_array($body)) {
- return ['state' => 'error', 'msg' => 'error creating body transaction', 'details' => $body];
- }
-
- // validate signatures
- $sigPairs = $sigMap->getProto()->getSigPair();
-
- if(!$sigPairs || count($sigPairs) < 1) {
- return ['state' => 'error', 'msg' => 'error no signatures found'];
- }
-
- //echo "verify bodybytes:
" . bin2hex($bodyBytes) . '
';
- $created = new \Model\Messages\Gradido\TimestampSeconds();
- $created->setSeconds($recevied->getSeconds());
- $body->setCreated($created);
- $bodyBytes = $body->serializeToString();
- $createTrys = 0;
- $createRight = false;
- // check signature(s) and
- // try to get created field of TransactionBody right, because it wasn't saved
- foreach($sigPairs as $sigPair) {
- //echo 'sig Pair: '; var_dump($sigPair); echo "
";
- $pubkey = $sigPair->getPubKey();
- $signature = $sigPair->getEd25519();
- if(!$createRight) {
- while($createTrys < 500) {
- if(\Sodium\crypto_sign_verify_detached($signature, $bodyBytes, $pubkey)) {
- $createRight = true;
- break;
- } else {
- $createTrys++;
- $created->setSeconds($created->getSeconds() - 1);
- //$body->setCreated($created);
- $bodyBytes = $body->serializeToString();
- }
- }
- }
-
- if (!\Sodium\crypto_sign_verify_detached($signature, $bodyBytes, $pubkey)) {
- return ['state' => 'error', 'msg' => 'signature for key ' . bin2hex($pubkey) . ' isn\'t valid '];
- }
- }
-
- $protoTransaction->setBodyBytes($bodyBytes);
-
-
-
- return $protoTransaction;
- }
-
+mProtoTransaction = $base64Data;
+ $this->mTransactionBody = new TransactionBody($this->mProtoTransaction->getBodyBytes());
+ return;
+ }
+
+ try {
+ $transactionBin = sodium_base642bin($base64Data, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
+ } catch(\SodiumException $e) {
+ //$this->addError('Transaction', $e->getMessage());// . ' ' . $base64Data);
+ //return;
+ $transactionBin = base64_decode($base64Data, true);
+ if($transactionBin == false) {
+ $this->addError('Transaction', $e->getMessage());// . ' ' . $base64Data);
+ return;
+ }
+ }
+ //*/}
+
+ if($transactionBin == false) {
+ //$this->addError('base64 decode failed');
+ $this->addError('Transaction', 'base64 decode error: ' . $base64Data);
+ } else {
+ //var_dump($transactionBin);
+ $this->mProtoTransaction = new \Model\Messages\Gradido\Transaction();
+ try {
+ $this->mProtoTransaction->mergeFromString($transactionBin);
+ //var_dump($this->mProtoTransaction);
+ // cannot catch Exception with cakePHP, I don't know why
+ } catch(\Google\Protobuf\Internal\GPBDecodeException $e) {
+ //var_dump($e);
+ $this->addError('Transaction', $e->getMessage());
+ return;
+ }//*/
+
+ //echo 'serialize to json:
';
+ //echo $this->mProtoTransaction->serializeToJsonString();
+ //echo "body bytes:
";
+ //var_dump($this->mProtoTransaction->getBodyBytes());
+ //echo "
end body bytes
";
+ $this->mTransactionBody = new TransactionBody($this->mProtoTransaction->getBodyBytes());
+ }
+ }
+
+ static public function build(\Model\Messages\Gradido\TransactionBody $transactionBody, $senderKeyPair)
+ {
+ $protoTransaction = new \Model\Messages\Gradido\Transaction();
+
+ $recevied = new \Model\Messages\Gradido\TimestampSeconds();
+ $recevied->setSeconds(time());
+ $protoTransaction->setReceived($recevied);
+
+ $bodyBytes = $transactionBody->serializeToString();
+
+ $sigMap = SignatureMap::build($bodyBytes, [$senderKeyPair]);
+ $protoTransaction->setSigMap($sigMap->getProto());
+
+ $protoTransaction->setBodyBytes($bodyBytes);
+
+ return $protoTransaction;
+
+ }
+
+ public function getTransactionBody() {
+ return $this->mTransactionBody;
+ }
+
+ public function getFirstPublic() {
+ $sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair();
+ return $sigPairs[0]->getPubKey();
+ }
+
+ public function getId() {
+ return $this->mProtoTransaction->getId();
+ }
+
+ public function validate() {
+ $sigMap = $this->mProtoTransaction->getSigMap();
+ if(!$sigMap) {
+ $this->addError('Transaction', 'signature map is zero');
+ return false;
+ }
+ //var_dump($sigMap);
+ //die();
+ $sigPairs = $sigMap->getSigPair();
+ $bodyBytes = $this->mProtoTransaction->getBodyBytes();
+
+
+ if(!$sigPairs || count($sigPairs) < 1) {
+ $this->addError('Transaction::validate', 'no signature found');
+ return false;
+ }
+
+ // check signature(s)
+ foreach($sigPairs as $sigPair) {
+ //echo 'sig Pair: '; var_dump($sigPair); echo "
";
+ $pubkey = $sigPair->getPubKey();
+ $signature = $sigPair->getEd25519();
+ //echo "verify bodybytes:
" . bin2hex($bodyBytes) . '
';
+ if (!\Sodium\crypto_sign_verify_detached($signature, $bodyBytes, $pubkey)) {
+ $this->addError('Transaction::validate', 'signature for key ' . bin2hex($pubkey) . ' isn\'t valid ' );
+ return false;
+ }
+ }
+
+ if(!$this->mTransactionBody->validate($sigPairs)) {
+ $this->addErrors($this->mTransactionBody->getErrors());
+ return false;
+ }
+
+ return true;
+ }
+
+ public function save()
+ {
+ $connection = ConnectionManager::get('default');
+ $connection->begin();
+ //id transaction_id signature pubkey
+
+ if (!$this->mTransactionBody->save($this->getFirstPublic(), $this->mProtoTransaction->getSigMap())) {
+ $this->addErrors($this->mTransactionBody->getErrors());
+ $connection->rollback();
+ return false;
+ }
+
+ // save transaction signatures
+ $transactionsSignaturesTable = TableRegistry::getTableLocator()->get('transaction_signatures');
+ $transactionId = $this->mTransactionBody->getTransactionID();
+ //signature pubkey
+
+ $sigPairs = $this->mProtoTransaction->getSigMap()->getSigPair();
+ //echo "sigPairs: "; var_dump($sigPairs);
+ $signatureEntitys = [];
+ foreach($sigPairs as $sigPair) {
+ $signatureEntity = $transactionsSignaturesTable->newEntity();
+ $signatureEntity->transaction_id = $transactionId;
+ $signatureEntity->signature = $sigPair->getEd25519();
+ $signatureEntity->pubkey = $sigPair->getPubKey();
+ array_push($signatureEntitys, $signatureEntity);
+ }
+ //debug($signatureEntitys);
+ if(!$transactionsSignaturesTable->saveMany($signatureEntitys)) {
+ foreach($signatureEntitys as $entity) {
+ $errors = $entity->getErrors();
+ if(!$errors && count($errors) > 0) {
+ $pubkeyHex = bin2hex($entity->pubkey);
+ $this->addError('Transaction::save', 'error saving signature for pubkey: ' . $pubkeyHex . ', with errors: ' . json_encode($errors) );
+ }
+ }
+ $connection->rollback();
+ return false;
+ }
+
+ $connection->commit();
+
+ $this->mTransactionBody->getSpecificTransaction()->sendNotificationEmail($this->mTransactionBody->getMemo());
+
+ return true;
+ }
+
+ static public function fromTable($id)
+ {
+ $transactionsTable = TableRegistry::getTableLocator()->get('transactions');
+ $transactionEntry = $transactionsTable
+ ->find('all')
+ ->where(['id' => $id])
+ ->contain([
+ 'TransactionCreations',
+ 'TransactionSendCoins',
+ 'TransactionSignatures'])
+ ->first();
+ //var_dump($transactionEntry->toArray());
+ $protoTransaction = new \Model\Messages\Gradido\Transaction();
+
+
+
+ $protoTransaction->setId($transactionEntry->id);
+
+
+ $recevied = new \Model\Messages\Gradido\TimestampSeconds();
+ $recevied->setSeconds($transactionEntry->received->getTimestamp());
+ $protoTransaction->setReceived($recevied);
+
+
+ $sigMap = SignatureMap::fromEntity($transactionEntry->transaction_signatures);
+ $protoTransaction->setSigMap($sigMap->getProto());
+
+ //echo "sig map: check
";
+ $protoTransaction->setTxHash(stream_get_contents($transactionEntry->tx_hash));
+
+ $body = TransactionBody::fromEntity($transactionEntry->memo, $transactionEntry);
+ if(is_array($body)) {
+ return ['state' => 'error', 'msg' => 'error creating body transaction', 'details' => $body];
+ }
+
+ // validate signatures
+ $sigPairs = $sigMap->getProto()->getSigPair();
+
+ if(!$sigPairs || count($sigPairs) < 1) {
+ return ['state' => 'error', 'msg' => 'error no signatures found'];
+ }
+
+ //echo "verify bodybytes:
" . bin2hex($bodyBytes) . '
';
+ $created = new \Model\Messages\Gradido\TimestampSeconds();
+ $created->setSeconds($recevied->getSeconds());
+ $body->setCreated($created);
+ $bodyBytes = $body->serializeToString();
+ $createTrys = 0;
+ $createRight = false;
+ // check signature(s) and
+ // try to get created field of TransactionBody right, because it wasn't saved
+ foreach($sigPairs as $sigPair) {
+ //echo 'sig Pair: '; var_dump($sigPair); echo "
";
+ $pubkey = $sigPair->getPubKey();
+ $signature = $sigPair->getEd25519();
+ if(!$createRight) {
+ while($createTrys < 500) {
+ if(\Sodium\crypto_sign_verify_detached($signature, $bodyBytes, $pubkey)) {
+ $createRight = true;
+ break;
+ } else {
+ $createTrys++;
+ $created->setSeconds($created->getSeconds() - 1);
+ //$body->setCreated($created);
+ $bodyBytes = $body->serializeToString();
+ }
+ }
+ }
+
+ if (!\Sodium\crypto_sign_verify_detached($signature, $bodyBytes, $pubkey)) {
+ return ['state' => 'error', 'msg' => 'signature for key ' . bin2hex($pubkey) . ' isn\'t valid '];
+ }
+ }
+
+ $protoTransaction->setBodyBytes($bodyBytes);
+
+
+
+ return $protoTransaction;
+ }
+
}
\ No newline at end of file
diff --git a/community_server/src/Model/Transactions/TransactionBase.php b/community_server/src/Model/Transactions/TransactionBase.php
index 80ce8ef5d..31a26f2ee 100644
--- a/community_server/src/Model/Transactions/TransactionBase.php
+++ b/community_server/src/Model/Transactions/TransactionBase.php
@@ -1,122 +1,122 @@
-errors;
- }
-
- public function addError($functionName, $errorName) {
- array_push($this->errors, [$functionName => $errorName]);
- }
-
- public function addErrors($errors) {
- $this->errors = array_merge($this->errors, $errors);
- }
-
- public function hasErrors() {
- return count($this->errors) > 0;
- }
-
- public static function getTable($tableName) {
- if(!isset(self::$tables[$tableName])) {
- self::$tables[$tableName] = TableRegistry::getTableLocator()->get($tableName);
- }
- return self::$tables[$tableName];
- }
-
-
- protected function getStateUserId($publicKey) {
-
- $stateUsersTable = self::getTable('state_users');
- $stateUser = $stateUsersTable->find('all')->select(['id'])->where(['public_key' => $publicKey])->first();
- if($stateUser) {
- return $stateUser->id;
- }
- // create new entry
- $stateUserEntity = $stateUsersTable->newEntity();
- $stateUserEntity->public_key = $publicKey;
- if($stateUsersTable->save($stateUserEntity)) {
- return $stateUserEntity->id;
- } else {
- $this->addError('TransactionBase::getStateUserId', 'error saving new state user with error: ' . json_encode($stateUserEntity->getErrors()));
- }
-
- return NULL;
- }
-
- protected function getStateUser($id) {
- $stateUsersTable = self::getTable('state_users');
- $stateUser = $stateUsersTable->get($id);
- if($stateUser) {
- return $stateUser;
- }
-
- return NULL;
- }
-
-
- protected function updateStateBalance($stateUserId, $addAmountCent, $recordDate) {
- $finalBalance = 0;
- $stateBalancesTable = self::getTable('stateBalances');
- $stateBalanceQuery = $stateBalancesTable
- ->find('all')
- ->select(['amount', 'id'])
- ->contain(false)
- ->where(['state_user_id' => $stateUserId]);//->first();
- //debug($stateBalanceQuery);
-
- if($stateBalanceQuery->count() > 0) {
- $stateBalanceEntry = $stateBalanceQuery->first();
- $stateBalanceEntry->amount = $stateBalanceEntry->partDecay($recordDate) + $addAmountCent;
- $stateBalanceEntry->amount += $addAmountCent;
- } else {
- $stateBalanceEntry = $stateBalancesTable->newEntity();
- $stateBalanceEntry->state_user_id = $stateUserId;
- $stateBalanceEntry->amount = $addAmountCent;
- }
- $stateBalanceEntry->record_date = $recordDate;
- $finalBalance = $stateBalanceEntry->amount;
- //echo "\ntry to save: "; var_dump($stateBalanceEntry); echo "\n";
- if(!$stateBalancesTable->save($stateBalanceEntry)) {
- $errors = $stateBalanceEntry->getErrors();
- $this->addError('TransactionBase::updateStateBalance', 'error saving state balance with: ' . json_encode($errors));
- return false;
- }
- return $finalBalance;
- }
-
- protected function addStateUserTransaction($stateUserId, $transactionId, $transactionTypeId, $balance) {
- $stateUserTransactionTable = self::getTable('state_user_transactions');
- $stateUserTransactions = $stateUserTransactionTable
- ->find('all')
- ->where(['state_user_id' => $stateUserId])
- ->order(['transaction_id DESC']);
-
- if($stateUserTransactions->count() > 0) {
- $stateBalanceTable = self::getTable('state_balances');
- $balance_entity = $stateBalanceTable->newEntity();
- $balance_entity->amount = $stateUserTransactions->first()->balance;
- $balance_entity->record_date = $stateUserTransactions->first()->balance_date;
- $balance = $balance_entity->decay + $balance;
- }
- $entity = $stateUserTransactionTable->newEntity();
- $entity->state_user_id = $stateUserId;
- $entity->transaction_id = $transactionId;
- $entity->transaction_type_id = $transactionTypeId;
- $entity->balance = $balance;
-
- if(!$stateUserTransactionTable->save($entity)) {
- $errors = $entity->getErrors();
- $this->addError('TransactionBase::addStateUserTransaction', 'error saving state user balance with: ' . json_encode($errors));
- return false;
- }
- return true;
- }
+errors;
+ }
+
+ public function addError($functionName, $errorName) {
+ array_push($this->errors, [$functionName => $errorName]);
+ }
+
+ public function addErrors($errors) {
+ $this->errors = array_merge($this->errors, $errors);
+ }
+
+ public function hasErrors() {
+ return count($this->errors) > 0;
+ }
+
+ public static function getTable($tableName) {
+ if(!isset(self::$tables[$tableName])) {
+ self::$tables[$tableName] = TableRegistry::getTableLocator()->get($tableName);
+ }
+ return self::$tables[$tableName];
+ }
+
+
+ protected function getStateUserId($publicKey) {
+
+ $stateUsersTable = self::getTable('state_users');
+ $stateUser = $stateUsersTable->find('all')->select(['id'])->where(['public_key' => $publicKey])->first();
+ if($stateUser) {
+ return $stateUser->id;
+ }
+ // create new entry
+ $stateUserEntity = $stateUsersTable->newEntity();
+ $stateUserEntity->public_key = $publicKey;
+ if($stateUsersTable->save($stateUserEntity)) {
+ return $stateUserEntity->id;
+ } else {
+ $this->addError('TransactionBase::getStateUserId', 'error saving new state user with error: ' . json_encode($stateUserEntity->getErrors()));
+ }
+
+ return NULL;
+ }
+
+ protected function getStateUser($id) {
+ $stateUsersTable = self::getTable('state_users');
+ $stateUser = $stateUsersTable->get($id);
+ if($stateUser) {
+ return $stateUser;
+ }
+
+ return NULL;
+ }
+
+
+ protected function updateStateBalance($stateUserId, $addAmountCent, $recordDate) {
+ $finalBalance = 0;
+ $stateBalancesTable = self::getTable('stateBalances');
+ $stateBalanceQuery = $stateBalancesTable
+ ->find('all')
+ ->select(['amount', 'id'])
+ ->contain(false)
+ ->where(['state_user_id' => $stateUserId]);//->first();
+ //debug($stateBalanceQuery);
+
+ if($stateBalanceQuery->count() > 0) {
+ $stateBalanceEntry = $stateBalanceQuery->first();
+ $stateBalanceEntry->amount = $stateBalanceEntry->partDecay($recordDate) + $addAmountCent;
+ $stateBalanceEntry->amount += $addAmountCent;
+ } else {
+ $stateBalanceEntry = $stateBalancesTable->newEntity();
+ $stateBalanceEntry->state_user_id = $stateUserId;
+ $stateBalanceEntry->amount = $addAmountCent;
+ }
+ $stateBalanceEntry->record_date = $recordDate;
+ $finalBalance = $stateBalanceEntry->amount;
+ //echo "\ntry to save: "; var_dump($stateBalanceEntry); echo "\n";
+ if(!$stateBalancesTable->save($stateBalanceEntry)) {
+ $errors = $stateBalanceEntry->getErrors();
+ $this->addError('TransactionBase::updateStateBalance', 'error saving state balance with: ' . json_encode($errors));
+ return false;
+ }
+ return $finalBalance;
+ }
+
+ protected function addStateUserTransaction($stateUserId, $transactionId, $transactionTypeId, $balance) {
+ $stateUserTransactionTable = self::getTable('state_user_transactions');
+ $stateUserTransactions = $stateUserTransactionTable
+ ->find('all')
+ ->where(['state_user_id' => $stateUserId])
+ ->order(['transaction_id DESC']);
+
+ if($stateUserTransactions->count() > 0) {
+ $stateBalanceTable = self::getTable('state_balances');
+ $balance_entity = $stateBalanceTable->newEntity();
+ $balance_entity->amount = $stateUserTransactions->first()->balance;
+ $balance_entity->record_date = $stateUserTransactions->first()->balance_date;
+ $balance = $balance_entity->decay + $balance;
+ }
+ $entity = $stateUserTransactionTable->newEntity();
+ $entity->state_user_id = $stateUserId;
+ $entity->transaction_id = $transactionId;
+ $entity->transaction_type_id = $transactionTypeId;
+ $entity->balance = $balance;
+
+ if(!$stateUserTransactionTable->save($entity)) {
+ $errors = $entity->getErrors();
+ $this->addError('TransactionBase::addStateUserTransaction', 'error saving state user balance with: ' . json_encode($errors));
+ return false;
+ }
+ return true;
+ }
}
\ No newline at end of file
diff --git a/community_server/src/Template/StateBalances/overview.ctp b/community_server/src/Template/StateBalances/overview.ctp
index e55d0aa7b..82208fa84 100644
--- a/community_server/src/Template/StateBalances/overview.ctp
+++ b/community_server/src/Template/StateBalances/overview.ctp
@@ -1,149 +1,149 @@
-assign('title', __('Kontoübersicht'));
-
-$header = '
' . __('Aktueller Kontostand: ') . '
' .
- '' . $this->element('printGradido', ['number' => $balance]) . '
';
-if($gdtSum > 0) {
- $header .= ''.$this->Html->link(
- $this->element('printGDT', ['number' => $gdtSum]),
- ['action' => 'overview_gdt'],
- ['escape' => false]
- ).'
';
-}
-$this->assign('header', $header);
-//var_dump($transactions);
-?>
-
- 0) : ?>
-
-
-
-
-
-
Überweisungen
-
-
-
-
-
-
-
-
- 30) {
- $memoShort = substr($memoShort, 0, 30) . '...';
- }
- $cellColorClass = 'success-color';
- if($send) {
- $balance = -$balance;
- $cellColorClass = 'alert-color';
- } else if($transaction['type'] == 'creation') {
- $cellColorClass = 'orange-color';
- }
- ?>
-
-
- = $this->Html->image('50x50.png', ['class' => 'profile-img', 'alt' => 'profile image']) ?>
-
-
-
- = $transaction['name'] ?>
-
-
-
= $transaction['name'] ?>
-
-
-
-
- redeem
-
- = __('Geschöpft')?>
-
- arrow_back
- = __('Gesendet') ?>
-
- arrow_forward
- = __('Empfangen') ?>
-
-
-
-
-
- 30): ?>
- = substr($memoShort, 0, 30) . '...' ?>
-
- = $transaction['memo'] ?>
-
-
-
= $transaction['date']->nice() ?>
-
= $this->element('printGradido', ['number' => $balance]) ?>
-
- = $transaction['transaction_id'] ?>
-
-
-
-
-
-
-
- 0) : ?>
-
+assign('title', __('Kontoübersicht'));
+
+$header = '' . __('Aktueller Kontostand: ') . '
' .
+ '' . $this->element('printGradido', ['number' => $balance]) . '
';
+if($gdtSum > 0) {
+ $header .= ''.$this->Html->link(
+ $this->element('printGDT', ['number' => $gdtSum]),
+ ['action' => 'overview_gdt'],
+ ['escape' => false]
+ ).'
';
+}
+$this->assign('header', $header);
+//var_dump($transactions);
+?>
+
+ 0) : ?>
+
+
+
+
+
+
Überweisungen
+
+
+
+
+
+
+
+
+ 30) {
+ $memoShort = substr($memoShort, 0, 30) . '...';
+ }
+ $cellColorClass = 'success-color';
+ if($send) {
+ $balance = -$balance;
+ $cellColorClass = 'alert-color';
+ } else if($transaction['type'] == 'creation') {
+ $cellColorClass = 'orange-color';
+ }
+ ?>
+
+
+ = $this->Html->image('50x50.png', ['class' => 'profile-img', 'alt' => 'profile image']) ?>
+
+
+
+ = $transaction['name'] ?>
+
+
+
= $transaction['name'] ?>
+
+
+
+
+ redeem
+
+ = __('Geschöpft')?>
+
+ arrow_back
+ = __('Gesendet') ?>
+
+ arrow_forward
+ = __('Empfangen') ?>
+
+
+
+
+
+ 30): ?>
+ = substr($memoShort, 0, 30) . '...' ?>
+
+ = $transaction['memo'] ?>
+
+
+
= $transaction['date']->nice() ?>
+
= $this->element('printGradido', ['number' => $balance]) ?>
+
+ = $transaction['transaction_id'] ?>
+
+
+
+
+
+
+
+ 0) : ?>
+
\ No newline at end of file
diff --git a/community_server/src/Template/StateUsers/list_ident_hashes.ctp b/community_server/src/Template/StateUsers/list_ident_hashes.ctp
index 7f50910b1..777a32c99 100644
--- a/community_server/src/Template/StateUsers/list_ident_hashes.ctp
+++ b/community_server/src/Template/StateUsers/list_ident_hashes.ctp
@@ -1,29 +1,29 @@
-
-
-
-
-
- | first name | last name | email | identHash | Public key hex
- |
-
-
-
-
-
- | = $user->first_name ?> |
- = $user->last_name ?> |
- = $user->email ?> |
- = $user->identHash ?> |
- = bin2hex(stream_get_contents($user->public_key)) ?> |
-
-
-
-
-
+
+
+
+
+
+ | first name | last name | email | identHash | Public key hex
+ |
+
+
+
+
+
+ | = $user->first_name ?> |
+ = $user->last_name ?> |
+ = $user->email ?> |
+ = $user->identHash ?> |
+ = bin2hex(stream_get_contents($user->public_key)) ?> |
+
+
+
+
+
diff --git a/community_server/src/Template/TransactionCreations/create.ctp b/community_server/src/Template/TransactionCreations/create.ctp
index a7e7c4afc..d5c9ee2c5 100644
--- a/community_server/src/Template/TransactionCreations/create.ctp
+++ b/community_server/src/Template/TransactionCreations/create.ctp
@@ -1,31 +1,31 @@
- $receiver) {
- //var_dump($receiver);
- array_push($address_options, [
- 'text' => $receiver['name'],
- 'value' => $i+1,
- 'title' => $receiver['key']
- ]);
-}
-$this->assign('title', __('Schöpfungstransaktion'));
-?>
-
-
- = $this->Form->create($creationForm) ?>
-
- = $this->Form->button(__('Transaktion(en) abschließen'), ['name' => 'next', 'class' => 'grd-form-bn grd-form-bn-succeed grd_clickable grd-width-200']) ?>
- = $this->Form->button(__('Weitere Transaktion erstellen'), ['name' => 'add', 'class' => 'grd-form-bn grd_clickable grd-width-200']) ?>
- = $this->Form->end() ?>
-
+ $receiver) {
+ //var_dump($receiver);
+ array_push($address_options, [
+ 'text' => $receiver['name'],
+ 'value' => $i+1,
+ 'title' => $receiver['key']
+ ]);
+}
+$this->assign('title', __('Schöpfungstransaktion'));
+?>
+
+
+ = $this->Form->create($creationForm) ?>
+
+ = $this->Form->button(__('Transaktion(en) abschließen'), ['name' => 'next', 'class' => 'grd-form-bn grd-form-bn-succeed grd_clickable grd-width-200']) ?>
+ = $this->Form->button(__('Weitere Transaktion erstellen'), ['name' => 'add', 'class' => 'grd-form-bn grd_clickable grd-width-200']) ?>
+ = $this->Form->end() ?>
+
diff --git a/community_server/src/Template/Transactions/synchronize_with_state_user_transactions.ctp b/community_server/src/Template/Transactions/synchronize_with_state_user_transactions.ctp
index a4b48cb14..8ac1ecb2d 100644
--- a/community_server/src/Template/Transactions/synchronize_with_state_user_transactions.ctp
+++ b/community_server/src/Template/Transactions/synchronize_with_state_user_transactions.ctp
@@ -1,46 +1,46 @@
-
-
-
Synchronize state_user_transactions with transactions
-
transactions count: = $count1 ?>
-
state_user_transaction count: = $count2 ?>
-
Missing count: = count($missing_transactions); ?>
-
First 10 Missing ids:
-
$id) {
- if($i > 10) break;
- if($i > 0) echo ', ';
- echo $id['id'];
-} ?>
-
-
-
Synchronize errors:
-
- $result) :
- if(false != $result) {
- $succeed++;
- continue;
- }
- ?>
- - Error saving entity: = json_encode($entities[$i]) ?> with error: = json_encode($entities[$i]->getErrors()) ?>
-
-
- - Succeed: = $succeed ?>
-
-
-
- = $this->Form->create() ?>
- = $this->Form->button(__('Synchronize')) ?>
- = $this->Form->end() ?>
-
+
+
+
Synchronize state_user_transactions with transactions
+
transactions count: = $count1 ?>
+
state_user_transaction count: = $count2 ?>
+
Missing count: = count($missing_transactions); ?>
+
First 10 Missing ids:
+
$id) {
+ if($i > 10) break;
+ if($i > 0) echo ', ';
+ echo $id['id'];
+} ?>
+
+
+
Synchronize errors:
+
+ $result) :
+ if(false != $result) {
+ $succeed++;
+ continue;
+ }
+ ?>
+ - Error saving entity: = json_encode($entities[$i]) ?> with error: = json_encode($entities[$i]->getErrors()) ?>
+
+
+ - Succeed: = $succeed ?>
+
+
+
+ = $this->Form->create() ?>
+ = $this->Form->button(__('Synchronize')) ?>
+ = $this->Form->end() ?>
+
diff --git a/community_server/tests/TestCase/Controller/AppControllerTest.php b/community_server/tests/TestCase/Controller/AppControllerTest.php
index 79294cbe0..502ed46da 100644
--- a/community_server/tests/TestCase/Controller/AppControllerTest.php
+++ b/community_server/tests/TestCase/Controller/AppControllerTest.php
@@ -1,46 +1,46 @@
-session(['StateUser.id' => 1]);
- $this->get('/');
- $this->assertSession(1200, 'StateUser.balance');
- //$this->markTestIncomplete('Not implemented yet.');
- }
-
-
-}
+session(['StateUser.id' => 1]);
+ $this->get('/');
+ $this->assertSession(1200, 'StateUser.balance');
+ //$this->markTestIncomplete('Not implemented yet.');
+ }
+
+
+}
diff --git a/community_server/webroot/css/materialdesignicons.min.css b/community_server/webroot/css/materialdesignicons.min.css
index 07688156b..bbdcbc816 100644
--- a/community_server/webroot/css/materialdesignicons.min.css
+++ b/community_server/webroot/css/materialdesignicons.min.css
@@ -1,2 +1,2 @@
-/* MaterialDesignIcons.com */@font-face{font-family:"Material Design Icons";src:url("../fonts/materialdesignicons-webfont.eot?v=3.5.95");src:url("../fonts/materialdesignicons-webfont.eot?#iefix&v=3.5.95") format("embedded-opentype"),url("../fonts/materialdesignicons-webfont.woff2?v=3.5.95") format("woff2"),url("../fonts/materialdesignicons-webfont.woff?v=3.5.95") format("woff"),url("../fonts/materialdesignicons-webfont.ttf?v=3.5.95") format("truetype"),url("../fonts/materialdesignicons-webfont.svg?v=3.5.95#materialdesigniconsregular") format("svg");font-weight:normal;font-style:normal}.mdi:before,.mdi-set{display:inline-block;font:normal normal normal 24px/1 "Material Design Icons";font-size:inherit;text-rendering:auto;line-height:inherit;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.mdi-access-point:before{content:"\F002"}.mdi-access-point-network:before{content:"\F003"}.mdi-access-point-network-off:before{content:"\FBBD"}.mdi-account:before{content:"\F004"}.mdi-account-alert:before{content:"\F005"}.mdi-account-alert-outline:before{content:"\FB2C"}.mdi-account-arrow-left:before{content:"\FB2D"}.mdi-account-arrow-left-outline:before{content:"\FB2E"}.mdi-account-arrow-right:before{content:"\FB2F"}.mdi-account-arrow-right-outline:before{content:"\FB30"}.mdi-account-badge:before{content:"\FD83"}.mdi-account-badge-alert:before{content:"\FD84"}.mdi-account-badge-alert-outline:before{content:"\FD85"}.mdi-account-badge-outline:before{content:"\FD86"}.mdi-account-box:before{content:"\F006"}.mdi-account-box-multiple:before{content:"\F933"}.mdi-account-box-outline:before{content:"\F007"}.mdi-account-card-details:before{content:"\F5D2"}.mdi-account-card-details-outline:before{content:"\FD87"}.mdi-account-check:before{content:"\F008"}.mdi-account-check-outline:before{content:"\FBBE"}.mdi-account-child:before{content:"\FA88"}.mdi-account-child-circle:before{content:"\FA89"}.mdi-account-circle:before{content:"\F009"}.mdi-account-circle-outline:before{content:"\FB31"}.mdi-account-clock:before{content:"\FB32"}.mdi-account-clock-outline:before{content:"\FB33"}.mdi-account-convert:before{content:"\F00A"}.mdi-account-details:before{content:"\F631"}.mdi-account-edit:before{content:"\F6BB"}.mdi-account-group:before{content:"\F848"}.mdi-account-group-outline:before{content:"\FB34"}.mdi-account-heart:before{content:"\F898"}.mdi-account-heart-outline:before{content:"\FBBF"}.mdi-account-key:before{content:"\F00B"}.mdi-account-key-outline:before{content:"\FBC0"}.mdi-account-minus:before{content:"\F00D"}.mdi-account-minus-outline:before{content:"\FAEB"}.mdi-account-multiple:before{content:"\F00E"}.mdi-account-multiple-check:before{content:"\F8C4"}.mdi-account-multiple-minus:before{content:"\F5D3"}.mdi-account-multiple-minus-outline:before{content:"\FBC1"}.mdi-account-multiple-outline:before{content:"\F00F"}.mdi-account-multiple-plus:before{content:"\F010"}.mdi-account-multiple-plus-outline:before{content:"\F7FF"}.mdi-account-network:before{content:"\F011"}.mdi-account-network-outline:before{content:"\FBC2"}.mdi-account-off:before{content:"\F012"}.mdi-account-off-outline:before{content:"\FBC3"}.mdi-account-outline:before{content:"\F013"}.mdi-account-plus:before{content:"\F014"}.mdi-account-plus-outline:before{content:"\F800"}.mdi-account-question:before{content:"\FB35"}.mdi-account-question-outline:before{content:"\FB36"}.mdi-account-remove:before{content:"\F015"}.mdi-account-remove-outline:before{content:"\FAEC"}.mdi-account-search:before{content:"\F016"}.mdi-account-search-outline:before{content:"\F934"}.mdi-account-settings:before{content:"\F630"}.mdi-account-star:before{content:"\F017"}.mdi-account-star-outline:before{content:"\FBC4"}.mdi-account-supervisor:before{content:"\FA8A"}.mdi-account-supervisor-circle:before{content:"\FA8B"}.mdi-account-switch:before{content:"\F019"}.mdi-account-tie:before{content:"\FCBF"}.mdi-accusoft:before{content:"\F849"}.mdi-adchoices:before{content:"\FD1E"}.mdi-adjust:before{content:"\F01A"}.mdi-adobe:before{content:"\F935"}.mdi-air-conditioner:before{content:"\F01B"}.mdi-air-filter:before{content:"\FD1F"}.mdi-air-horn:before{content:"\FD88"}.mdi-air-purifier:before{content:"\FD20"}.mdi-airbag:before{content:"\FBC5"}.mdi-airballoon:before{content:"\F01C"}.mdi-airplane:before{content:"\F01D"}.mdi-airplane-landing:before{content:"\F5D4"}.mdi-airplane-off:before{content:"\F01E"}.mdi-airplane-takeoff:before{content:"\F5D5"}.mdi-airplay:before{content:"\F01F"}.mdi-airport:before{content:"\F84A"}.mdi-alarm:before{content:"\F020"}.mdi-alarm-bell:before{content:"\F78D"}.mdi-alarm-check:before{content:"\F021"}.mdi-alarm-light:before{content:"\F78E"}.mdi-alarm-light-outline:before{content:"\FBC6"}.mdi-alarm-multiple:before{content:"\F022"}.mdi-alarm-off:before{content:"\F023"}.mdi-alarm-plus:before{content:"\F024"}.mdi-alarm-snooze:before{content:"\F68D"}.mdi-album:before{content:"\F025"}.mdi-alert:before{content:"\F026"}.mdi-alert-box:before{content:"\F027"}.mdi-alert-box-outline:before{content:"\FCC0"}.mdi-alert-circle:before{content:"\F028"}.mdi-alert-circle-outline:before{content:"\F5D6"}.mdi-alert-decagram:before{content:"\F6BC"}.mdi-alert-decagram-outline:before{content:"\FCC1"}.mdi-alert-octagon:before{content:"\F029"}.mdi-alert-octagon-outline:before{content:"\FCC2"}.mdi-alert-octagram:before{content:"\F766"}.mdi-alert-octagram-outline:before{content:"\FCC3"}.mdi-alert-outline:before{content:"\F02A"}.mdi-alien:before{content:"\F899"}.mdi-all-inclusive:before{content:"\F6BD"}.mdi-alpha:before{content:"\F02B"}.mdi-alpha-a:before{content:"\41"}.mdi-alpha-a-box:before{content:"\FAED"}.mdi-alpha-a-box-outline:before{content:"\FBC7"}.mdi-alpha-a-circle:before{content:"\FBC8"}.mdi-alpha-a-circle-outline:before{content:"\FBC9"}.mdi-alpha-b:before{content:"\42"}.mdi-alpha-b-box:before{content:"\FAEE"}.mdi-alpha-b-box-outline:before{content:"\FBCA"}.mdi-alpha-b-circle:before{content:"\FBCB"}.mdi-alpha-b-circle-outline:before{content:"\FBCC"}.mdi-alpha-c:before{content:"\43"}.mdi-alpha-c-box:before{content:"\FAEF"}.mdi-alpha-c-box-outline:before{content:"\FBCD"}.mdi-alpha-c-circle:before{content:"\FBCE"}.mdi-alpha-c-circle-outline:before{content:"\FBCF"}.mdi-alpha-d:before{content:"\44"}.mdi-alpha-d-box:before{content:"\FAF0"}.mdi-alpha-d-box-outline:before{content:"\FBD0"}.mdi-alpha-d-circle:before{content:"\FBD1"}.mdi-alpha-d-circle-outline:before{content:"\FBD2"}.mdi-alpha-e:before{content:"\45"}.mdi-alpha-e-box:before{content:"\FAF1"}.mdi-alpha-e-box-outline:before{content:"\FBD3"}.mdi-alpha-e-circle:before{content:"\FBD4"}.mdi-alpha-e-circle-outline:before{content:"\FBD5"}.mdi-alpha-f:before{content:"\46"}.mdi-alpha-f-box:before{content:"\FAF2"}.mdi-alpha-f-box-outline:before{content:"\FBD6"}.mdi-alpha-f-circle:before{content:"\FBD7"}.mdi-alpha-f-circle-outline:before{content:"\FBD8"}.mdi-alpha-g:before{content:"\47"}.mdi-alpha-g-box:before{content:"\FAF3"}.mdi-alpha-g-box-outline:before{content:"\FBD9"}.mdi-alpha-g-circle:before{content:"\FBDA"}.mdi-alpha-g-circle-outline:before{content:"\FBDB"}.mdi-alpha-h:before{content:"\48"}.mdi-alpha-h-box:before{content:"\FAF4"}.mdi-alpha-h-box-outline:before{content:"\FBDC"}.mdi-alpha-h-circle:before{content:"\FBDD"}.mdi-alpha-h-circle-outline:before{content:"\FBDE"}.mdi-alpha-i:before{content:"\49"}.mdi-alpha-i-box:before{content:"\FAF5"}.mdi-alpha-i-box-outline:before{content:"\FBDF"}.mdi-alpha-i-circle:before{content:"\FBE0"}.mdi-alpha-i-circle-outline:before{content:"\FBE1"}.mdi-alpha-j:before{content:"\4A"}.mdi-alpha-j-box:before{content:"\FAF6"}.mdi-alpha-j-box-outline:before{content:"\FBE2"}.mdi-alpha-j-circle:before{content:"\FBE3"}.mdi-alpha-j-circle-outline:before{content:"\FBE4"}.mdi-alpha-k:before{content:"\4B"}.mdi-alpha-k-box:before{content:"\FAF7"}.mdi-alpha-k-box-outline:before{content:"\FBE5"}.mdi-alpha-k-circle:before{content:"\FBE6"}.mdi-alpha-k-circle-outline:before{content:"\FBE7"}.mdi-alpha-l:before{content:"\4C"}.mdi-alpha-l-box:before{content:"\FAF8"}.mdi-alpha-l-box-outline:before{content:"\FBE8"}.mdi-alpha-l-circle:before{content:"\FBE9"}.mdi-alpha-l-circle-outline:before{content:"\FBEA"}.mdi-alpha-m:before{content:"\4D"}.mdi-alpha-m-box:before{content:"\FAF9"}.mdi-alpha-m-box-outline:before{content:"\FBEB"}.mdi-alpha-m-circle:before{content:"\FBEC"}.mdi-alpha-m-circle-outline:before{content:"\FBED"}.mdi-alpha-n:before{content:"\4E"}.mdi-alpha-n-box:before{content:"\FAFA"}.mdi-alpha-n-box-outline:before{content:"\FBEE"}.mdi-alpha-n-circle:before{content:"\FBEF"}.mdi-alpha-n-circle-outline:before{content:"\FBF0"}.mdi-alpha-o:before{content:"\4F"}.mdi-alpha-o-box:before{content:"\FAFB"}.mdi-alpha-o-box-outline:before{content:"\FBF1"}.mdi-alpha-o-circle:before{content:"\FBF2"}.mdi-alpha-o-circle-outline:before{content:"\FBF3"}.mdi-alpha-p:before{content:"\50"}.mdi-alpha-p-box:before{content:"\FAFC"}.mdi-alpha-p-box-outline:before{content:"\FBF4"}.mdi-alpha-p-circle:before{content:"\FBF5"}.mdi-alpha-p-circle-outline:before{content:"\FBF6"}.mdi-alpha-q:before{content:"\51"}.mdi-alpha-q-box:before{content:"\FAFD"}.mdi-alpha-q-box-outline:before{content:"\FBF7"}.mdi-alpha-q-circle:before{content:"\FBF8"}.mdi-alpha-q-circle-outline:before{content:"\FBF9"}.mdi-alpha-r:before{content:"\52"}.mdi-alpha-r-box:before{content:"\FAFE"}.mdi-alpha-r-box-outline:before{content:"\FBFA"}.mdi-alpha-r-circle:before{content:"\FBFB"}.mdi-alpha-r-circle-outline:before{content:"\FBFC"}.mdi-alpha-s:before{content:"\53"}.mdi-alpha-s-box:before{content:"\FAFF"}.mdi-alpha-s-box-outline:before{content:"\FBFD"}.mdi-alpha-s-circle:before{content:"\FBFE"}.mdi-alpha-s-circle-outline:before{content:"\FBFF"}.mdi-alpha-t:before{content:"\54"}.mdi-alpha-t-box:before{content:"\FB00"}.mdi-alpha-t-box-outline:before{content:"\FC00"}.mdi-alpha-t-circle:before{content:"\FC01"}.mdi-alpha-t-circle-outline:before{content:"\FC02"}.mdi-alpha-u:before{content:"\55"}.mdi-alpha-u-box:before{content:"\FB01"}.mdi-alpha-u-box-outline:before{content:"\FC03"}.mdi-alpha-u-circle:before{content:"\FC04"}.mdi-alpha-u-circle-outline:before{content:"\FC05"}.mdi-alpha-v:before{content:"\56"}.mdi-alpha-v-box:before{content:"\FB02"}.mdi-alpha-v-box-outline:before{content:"\FC06"}.mdi-alpha-v-circle:before{content:"\FC07"}.mdi-alpha-v-circle-outline:before{content:"\FC08"}.mdi-alpha-w:before{content:"\57"}.mdi-alpha-w-box:before{content:"\FB03"}.mdi-alpha-w-box-outline:before{content:"\FC09"}.mdi-alpha-w-circle:before{content:"\FC0A"}.mdi-alpha-w-circle-outline:before{content:"\FC0B"}.mdi-alpha-x:before{content:"\58"}.mdi-alpha-x-box:before{content:"\FB04"}.mdi-alpha-x-box-outline:before{content:"\FC0C"}.mdi-alpha-x-circle:before{content:"\FC0D"}.mdi-alpha-x-circle-outline:before{content:"\FC0E"}.mdi-alpha-y:before{content:"\59"}.mdi-alpha-y-box:before{content:"\FB05"}.mdi-alpha-y-box-outline:before{content:"\FC0F"}.mdi-alpha-y-circle:before{content:"\FC10"}.mdi-alpha-y-circle-outline:before{content:"\FC11"}.mdi-alpha-z:before{content:"\5A"}.mdi-alpha-z-box:before{content:"\FB06"}.mdi-alpha-z-box-outline:before{content:"\FC12"}.mdi-alpha-z-circle:before{content:"\FC13"}.mdi-alpha-z-circle-outline:before{content:"\FC14"}.mdi-alphabetical:before{content:"\F02C"}.mdi-altimeter:before{content:"\F5D7"}.mdi-amazon:before{content:"\F02D"}.mdi-amazon-alexa:before{content:"\F8C5"}.mdi-amazon-drive:before{content:"\F02E"}.mdi-ambulance:before{content:"\F02F"}.mdi-ammunition:before{content:"\FCC4"}.mdi-ampersand:before{content:"\FA8C"}.mdi-amplifier:before{content:"\F030"}.mdi-anchor:before{content:"\F031"}.mdi-android:before{content:"\F032"}.mdi-android-auto:before{content:"\FA8D"}.mdi-android-debug-bridge:before{content:"\F033"}.mdi-android-head:before{content:"\F78F"}.mdi-android-messages:before{content:"\FD21"}.mdi-android-studio:before{content:"\F034"}.mdi-angle-acute:before{content:"\F936"}.mdi-angle-obtuse:before{content:"\F937"}.mdi-angle-right:before{content:"\F938"}.mdi-angular:before{content:"\F6B1"}.mdi-angularjs:before{content:"\F6BE"}.mdi-animation:before{content:"\F5D8"}.mdi-animation-outline:before{content:"\FA8E"}.mdi-animation-play:before{content:"\F939"}.mdi-animation-play-outline:before{content:"\FA8F"}.mdi-anvil:before{content:"\F89A"}.mdi-apple:before{content:"\F035"}.mdi-apple-finder:before{content:"\F036"}.mdi-apple-icloud:before{content:"\F038"}.mdi-apple-ios:before{content:"\F037"}.mdi-apple-keyboard-caps:before{content:"\F632"}.mdi-apple-keyboard-command:before{content:"\F633"}.mdi-apple-keyboard-control:before{content:"\F634"}.mdi-apple-keyboard-option:before{content:"\F635"}.mdi-apple-keyboard-shift:before{content:"\F636"}.mdi-apple-safari:before{content:"\F039"}.mdi-application:before{content:"\F614"}.mdi-application-export:before{content:"\FD89"}.mdi-application-import:before{content:"\FD8A"}.mdi-apps:before{content:"\F03B"}.mdi-apps-box:before{content:"\FD22"}.mdi-arch:before{content:"\F8C6"}.mdi-archive:before{content:"\F03C"}.mdi-arrange-bring-forward:before{content:"\F03D"}.mdi-arrange-bring-to-front:before{content:"\F03E"}.mdi-arrange-send-backward:before{content:"\F03F"}.mdi-arrange-send-to-back:before{content:"\F040"}.mdi-arrow-all:before{content:"\F041"}.mdi-arrow-bottom-left:before{content:"\F042"}.mdi-arrow-bottom-left-bold-outline:before{content:"\F9B6"}.mdi-arrow-bottom-left-thick:before{content:"\F9B7"}.mdi-arrow-bottom-right:before{content:"\F043"}.mdi-arrow-bottom-right-bold-outline:before{content:"\F9B8"}.mdi-arrow-bottom-right-thick:before{content:"\F9B9"}.mdi-arrow-collapse:before{content:"\F615"}.mdi-arrow-collapse-all:before{content:"\F044"}.mdi-arrow-collapse-down:before{content:"\F791"}.mdi-arrow-collapse-horizontal:before{content:"\F84B"}.mdi-arrow-collapse-left:before{content:"\F792"}.mdi-arrow-collapse-right:before{content:"\F793"}.mdi-arrow-collapse-up:before{content:"\F794"}.mdi-arrow-collapse-vertical:before{content:"\F84C"}.mdi-arrow-decision:before{content:"\F9BA"}.mdi-arrow-decision-auto:before{content:"\F9BB"}.mdi-arrow-decision-auto-outline:before{content:"\F9BC"}.mdi-arrow-decision-outline:before{content:"\F9BD"}.mdi-arrow-down:before{content:"\F045"}.mdi-arrow-down-bold:before{content:"\F72D"}.mdi-arrow-down-bold-box:before{content:"\F72E"}.mdi-arrow-down-bold-box-outline:before{content:"\F72F"}.mdi-arrow-down-bold-circle:before{content:"\F047"}.mdi-arrow-down-bold-circle-outline:before{content:"\F048"}.mdi-arrow-down-bold-hexagon-outline:before{content:"\F049"}.mdi-arrow-down-bold-outline:before{content:"\F9BE"}.mdi-arrow-down-box:before{content:"\F6BF"}.mdi-arrow-down-circle:before{content:"\FCB7"}.mdi-arrow-down-circle-outline:before{content:"\FCB8"}.mdi-arrow-down-drop-circle:before{content:"\F04A"}.mdi-arrow-down-drop-circle-outline:before{content:"\F04B"}.mdi-arrow-down-thick:before{content:"\F046"}.mdi-arrow-expand:before{content:"\F616"}.mdi-arrow-expand-all:before{content:"\F04C"}.mdi-arrow-expand-down:before{content:"\F795"}.mdi-arrow-expand-horizontal:before{content:"\F84D"}.mdi-arrow-expand-left:before{content:"\F796"}.mdi-arrow-expand-right:before{content:"\F797"}.mdi-arrow-expand-up:before{content:"\F798"}.mdi-arrow-expand-vertical:before{content:"\F84E"}.mdi-arrow-left:before{content:"\F04D"}.mdi-arrow-left-bold:before{content:"\F730"}.mdi-arrow-left-bold-box:before{content:"\F731"}.mdi-arrow-left-bold-box-outline:before{content:"\F732"}.mdi-arrow-left-bold-circle:before{content:"\F04F"}.mdi-arrow-left-bold-circle-outline:before{content:"\F050"}.mdi-arrow-left-bold-hexagon-outline:before{content:"\F051"}.mdi-arrow-left-bold-outline:before{content:"\F9BF"}.mdi-arrow-left-box:before{content:"\F6C0"}.mdi-arrow-left-circle:before{content:"\FCB9"}.mdi-arrow-left-circle-outline:before{content:"\FCBA"}.mdi-arrow-left-drop-circle:before{content:"\F052"}.mdi-arrow-left-drop-circle-outline:before{content:"\F053"}.mdi-arrow-left-right-bold-outline:before{content:"\F9C0"}.mdi-arrow-left-thick:before{content:"\F04E"}.mdi-arrow-right:before{content:"\F054"}.mdi-arrow-right-bold:before{content:"\F733"}.mdi-arrow-right-bold-box:before{content:"\F734"}.mdi-arrow-right-bold-box-outline:before{content:"\F735"}.mdi-arrow-right-bold-circle:before{content:"\F056"}.mdi-arrow-right-bold-circle-outline:before{content:"\F057"}.mdi-arrow-right-bold-hexagon-outline:before{content:"\F058"}.mdi-arrow-right-bold-outline:before{content:"\F9C1"}.mdi-arrow-right-box:before{content:"\F6C1"}.mdi-arrow-right-circle:before{content:"\FCBB"}.mdi-arrow-right-circle-outline:before{content:"\FCBC"}.mdi-arrow-right-drop-circle:before{content:"\F059"}.mdi-arrow-right-drop-circle-outline:before{content:"\F05A"}.mdi-arrow-right-thick:before{content:"\F055"}.mdi-arrow-split-horizontal:before{content:"\F93A"}.mdi-arrow-split-vertical:before{content:"\F93B"}.mdi-arrow-top-left:before{content:"\F05B"}.mdi-arrow-top-left-bold-outline:before{content:"\F9C2"}.mdi-arrow-top-left-thick:before{content:"\F9C3"}.mdi-arrow-top-right:before{content:"\F05C"}.mdi-arrow-top-right-bold-outline:before{content:"\F9C4"}.mdi-arrow-top-right-thick:before{content:"\F9C5"}.mdi-arrow-up:before{content:"\F05D"}.mdi-arrow-up-bold:before{content:"\F736"}.mdi-arrow-up-bold-box:before{content:"\F737"}.mdi-arrow-up-bold-box-outline:before{content:"\F738"}.mdi-arrow-up-bold-circle:before{content:"\F05F"}.mdi-arrow-up-bold-circle-outline:before{content:"\F060"}.mdi-arrow-up-bold-hexagon-outline:before{content:"\F061"}.mdi-arrow-up-bold-outline:before{content:"\F9C6"}.mdi-arrow-up-box:before{content:"\F6C2"}.mdi-arrow-up-circle:before{content:"\FCBD"}.mdi-arrow-up-circle-outline:before{content:"\FCBE"}.mdi-arrow-up-down-bold-outline:before{content:"\F9C7"}.mdi-arrow-up-drop-circle:before{content:"\F062"}.mdi-arrow-up-drop-circle-outline:before{content:"\F063"}.mdi-arrow-up-thick:before{content:"\F05E"}.mdi-artist:before{content:"\F802"}.mdi-artist-outline:before{content:"\FCC5"}.mdi-artstation:before{content:"\FB37"}.mdi-aspect-ratio:before{content:"\FA23"}.mdi-assistant:before{content:"\F064"}.mdi-asterisk:before{content:"\F6C3"}.mdi-at:before{content:"\F065"}.mdi-atlassian:before{content:"\F803"}.mdi-atm:before{content:"\FD23"}.mdi-atom:before{content:"\F767"}.mdi-attachment:before{content:"\F066"}.mdi-audio-video:before{content:"\F93C"}.mdi-audiobook:before{content:"\F067"}.mdi-augmented-reality:before{content:"\F84F"}.mdi-auto-fix:before{content:"\F068"}.mdi-auto-upload:before{content:"\F069"}.mdi-autorenew:before{content:"\F06A"}.mdi-av-timer:before{content:"\F06B"}.mdi-axe:before{content:"\F8C7"}.mdi-axis:before{content:"\FD24"}.mdi-axis-arrow:before{content:"\FD25"}.mdi-axis-arrow-lock:before{content:"\FD26"}.mdi-axis-lock:before{content:"\FD27"}.mdi-axis-x-arrow:before{content:"\FD28"}.mdi-axis-x-arrow-lock:before{content:"\FD29"}.mdi-axis-x-rotate-clockwise:before{content:"\FD2A"}.mdi-axis-x-rotate-counterclockwise:before{content:"\FD2B"}.mdi-axis-x-y-arrow-lock:before{content:"\FD2C"}.mdi-axis-y-arrow:before{content:"\FD2D"}.mdi-axis-y-arrow-lock:before{content:"\FD2E"}.mdi-axis-y-rotate-clockwise:before{content:"\FD2F"}.mdi-axis-y-rotate-counterclockwise:before{content:"\FD30"}.mdi-axis-z-arrow:before{content:"\FD31"}.mdi-axis-z-arrow-lock:before{content:"\FD32"}.mdi-axis-z-rotate-clockwise:before{content:"\FD33"}.mdi-axis-z-rotate-counterclockwise:before{content:"\FD34"}.mdi-azure:before{content:"\F804"}.mdi-babel:before{content:"\FA24"}.mdi-baby:before{content:"\F06C"}.mdi-baby-buggy:before{content:"\F68E"}.mdi-backburger:before{content:"\F06D"}.mdi-backspace:before{content:"\F06E"}.mdi-backspace-outline:before{content:"\FB38"}.mdi-backup-restore:before{content:"\F06F"}.mdi-badminton:before{content:"\F850"}.mdi-balloon:before{content:"\FA25"}.mdi-ballot:before{content:"\F9C8"}.mdi-ballot-outline:before{content:"\F9C9"}.mdi-ballot-recount:before{content:"\FC15"}.mdi-ballot-recount-outline:before{content:"\FC16"}.mdi-bandage:before{content:"\FD8B"}.mdi-bandcamp:before{content:"\F674"}.mdi-bank:before{content:"\F070"}.mdi-bank-minus:before{content:"\FD8C"}.mdi-bank-plus:before{content:"\FD8D"}.mdi-bank-remove:before{content:"\FD8E"}.mdi-bank-transfer:before{content:"\FA26"}.mdi-bank-transfer-in:before{content:"\FA27"}.mdi-bank-transfer-out:before{content:"\FA28"}.mdi-barcode:before{content:"\F071"}.mdi-barcode-scan:before{content:"\F072"}.mdi-barley:before{content:"\F073"}.mdi-barley-off:before{content:"\FB39"}.mdi-barn:before{content:"\FB3A"}.mdi-barrel:before{content:"\F074"}.mdi-baseball:before{content:"\F851"}.mdi-baseball-bat:before{content:"\F852"}.mdi-basecamp:before{content:"\F075"}.mdi-basket:before{content:"\F076"}.mdi-basket-fill:before{content:"\F077"}.mdi-basket-unfill:before{content:"\F078"}.mdi-basketball:before{content:"\F805"}.mdi-basketball-hoop:before{content:"\FC17"}.mdi-basketball-hoop-outline:before{content:"\FC18"}.mdi-bat:before{content:"\FB3B"}.mdi-battery:before{content:"\F079"}.mdi-battery-10:before{content:"\F07A"}.mdi-battery-10-bluetooth:before{content:"\F93D"}.mdi-battery-20:before{content:"\F07B"}.mdi-battery-20-bluetooth:before{content:"\F93E"}.mdi-battery-30:before{content:"\F07C"}.mdi-battery-30-bluetooth:before{content:"\F93F"}.mdi-battery-40:before{content:"\F07D"}.mdi-battery-40-bluetooth:before{content:"\F940"}.mdi-battery-50:before{content:"\F07E"}.mdi-battery-50-bluetooth:before{content:"\F941"}.mdi-battery-60:before{content:"\F07F"}.mdi-battery-60-bluetooth:before{content:"\F942"}.mdi-battery-70:before{content:"\F080"}.mdi-battery-70-bluetooth:before{content:"\F943"}.mdi-battery-80:before{content:"\F081"}.mdi-battery-80-bluetooth:before{content:"\F944"}.mdi-battery-90:before{content:"\F082"}.mdi-battery-90-bluetooth:before{content:"\F945"}.mdi-battery-alert:before{content:"\F083"}.mdi-battery-alert-bluetooth:before{content:"\F946"}.mdi-battery-bluetooth:before{content:"\F947"}.mdi-battery-bluetooth-variant:before{content:"\F948"}.mdi-battery-charging:before{content:"\F084"}.mdi-battery-charging-10:before{content:"\F89B"}.mdi-battery-charging-100:before{content:"\F085"}.mdi-battery-charging-20:before{content:"\F086"}.mdi-battery-charging-30:before{content:"\F087"}.mdi-battery-charging-40:before{content:"\F088"}.mdi-battery-charging-50:before{content:"\F89C"}.mdi-battery-charging-60:before{content:"\F089"}.mdi-battery-charging-70:before{content:"\F89D"}.mdi-battery-charging-80:before{content:"\F08A"}.mdi-battery-charging-90:before{content:"\F08B"}.mdi-battery-charging-outline:before{content:"\F89E"}.mdi-battery-charging-wireless:before{content:"\F806"}.mdi-battery-charging-wireless-10:before{content:"\F807"}.mdi-battery-charging-wireless-20:before{content:"\F808"}.mdi-battery-charging-wireless-30:before{content:"\F809"}.mdi-battery-charging-wireless-40:before{content:"\F80A"}.mdi-battery-charging-wireless-50:before{content:"\F80B"}.mdi-battery-charging-wireless-60:before{content:"\F80C"}.mdi-battery-charging-wireless-70:before{content:"\F80D"}.mdi-battery-charging-wireless-80:before{content:"\F80E"}.mdi-battery-charging-wireless-90:before{content:"\F80F"}.mdi-battery-charging-wireless-alert:before{content:"\F810"}.mdi-battery-charging-wireless-outline:before{content:"\F811"}.mdi-battery-minus:before{content:"\F08C"}.mdi-battery-negative:before{content:"\F08D"}.mdi-battery-outline:before{content:"\F08E"}.mdi-battery-plus:before{content:"\F08F"}.mdi-battery-positive:before{content:"\F090"}.mdi-battery-unknown:before{content:"\F091"}.mdi-battery-unknown-bluetooth:before{content:"\F949"}.mdi-battlenet:before{content:"\FB3C"}.mdi-beach:before{content:"\F092"}.mdi-beaker:before{content:"\FCC6"}.mdi-beaker-outline:before{content:"\F68F"}.mdi-beats:before{content:"\F097"}.mdi-bed-empty:before{content:"\F89F"}.mdi-beer:before{content:"\F098"}.mdi-behance:before{content:"\F099"}.mdi-bell:before{content:"\F09A"}.mdi-bell-alert:before{content:"\FD35"}.mdi-bell-circle:before{content:"\FD36"}.mdi-bell-circle-outline:before{content:"\FD37"}.mdi-bell-off:before{content:"\F09B"}.mdi-bell-off-outline:before{content:"\FA90"}.mdi-bell-outline:before{content:"\F09C"}.mdi-bell-plus:before{content:"\F09D"}.mdi-bell-plus-outline:before{content:"\FA91"}.mdi-bell-ring:before{content:"\F09E"}.mdi-bell-ring-outline:before{content:"\F09F"}.mdi-bell-sleep:before{content:"\F0A0"}.mdi-bell-sleep-outline:before{content:"\FA92"}.mdi-beta:before{content:"\F0A1"}.mdi-betamax:before{content:"\F9CA"}.mdi-bible:before{content:"\F0A2"}.mdi-bike:before{content:"\F0A3"}.mdi-billiards:before{content:"\FB3D"}.mdi-billiards-rack:before{content:"\FB3E"}.mdi-bing:before{content:"\F0A4"}.mdi-binoculars:before{content:"\F0A5"}.mdi-bio:before{content:"\F0A6"}.mdi-biohazard:before{content:"\F0A7"}.mdi-bitbucket:before{content:"\F0A8"}.mdi-bitcoin:before{content:"\F812"}.mdi-black-mesa:before{content:"\F0A9"}.mdi-blackberry:before{content:"\F0AA"}.mdi-blender:before{content:"\FCC7"}.mdi-blender-software:before{content:"\F0AB"}.mdi-blinds:before{content:"\F0AC"}.mdi-block-helper:before{content:"\F0AD"}.mdi-blogger:before{content:"\F0AE"}.mdi-blood-bag:before{content:"\FCC8"}.mdi-bluetooth:before{content:"\F0AF"}.mdi-bluetooth-audio:before{content:"\F0B0"}.mdi-bluetooth-connect:before{content:"\F0B1"}.mdi-bluetooth-off:before{content:"\F0B2"}.mdi-bluetooth-settings:before{content:"\F0B3"}.mdi-bluetooth-transfer:before{content:"\F0B4"}.mdi-blur:before{content:"\F0B5"}.mdi-blur-linear:before{content:"\F0B6"}.mdi-blur-off:before{content:"\F0B7"}.mdi-blur-radial:before{content:"\F0B8"}.mdi-bolnisi-cross:before{content:"\FCC9"}.mdi-bolt:before{content:"\FD8F"}.mdi-bomb:before{content:"\F690"}.mdi-bomb-off:before{content:"\F6C4"}.mdi-bone:before{content:"\F0B9"}.mdi-book:before{content:"\F0BA"}.mdi-book-lock:before{content:"\F799"}.mdi-book-lock-open:before{content:"\F79A"}.mdi-book-minus:before{content:"\F5D9"}.mdi-book-multiple:before{content:"\F0BB"}.mdi-book-multiple-minus:before{content:"\FA93"}.mdi-book-multiple-plus:before{content:"\FA94"}.mdi-book-multiple-remove:before{content:"\FA95"}.mdi-book-multiple-variant:before{content:"\F0BC"}.mdi-book-open:before{content:"\F0BD"}.mdi-book-open-outline:before{content:"\FB3F"}.mdi-book-open-page-variant:before{content:"\F5DA"}.mdi-book-open-variant:before{content:"\F0BE"}.mdi-book-outline:before{content:"\FB40"}.mdi-book-plus:before{content:"\F5DB"}.mdi-book-remove:before{content:"\FA96"}.mdi-book-variant:before{content:"\F0BF"}.mdi-bookmark:before{content:"\F0C0"}.mdi-bookmark-check:before{content:"\F0C1"}.mdi-bookmark-minus:before{content:"\F9CB"}.mdi-bookmark-minus-outline:before{content:"\F9CC"}.mdi-bookmark-music:before{content:"\F0C2"}.mdi-bookmark-off:before{content:"\F9CD"}.mdi-bookmark-off-outline:before{content:"\F9CE"}.mdi-bookmark-outline:before{content:"\F0C3"}.mdi-bookmark-plus:before{content:"\F0C5"}.mdi-bookmark-plus-outline:before{content:"\F0C4"}.mdi-bookmark-remove:before{content:"\F0C6"}.mdi-boombox:before{content:"\F5DC"}.mdi-bootstrap:before{content:"\F6C5"}.mdi-border-all:before{content:"\F0C7"}.mdi-border-all-variant:before{content:"\F8A0"}.mdi-border-bottom:before{content:"\F0C8"}.mdi-border-bottom-variant:before{content:"\F8A1"}.mdi-border-color:before{content:"\F0C9"}.mdi-border-horizontal:before{content:"\F0CA"}.mdi-border-inside:before{content:"\F0CB"}.mdi-border-left:before{content:"\F0CC"}.mdi-border-left-variant:before{content:"\F8A2"}.mdi-border-none:before{content:"\F0CD"}.mdi-border-none-variant:before{content:"\F8A3"}.mdi-border-outside:before{content:"\F0CE"}.mdi-border-right:before{content:"\F0CF"}.mdi-border-right-variant:before{content:"\F8A4"}.mdi-border-style:before{content:"\F0D0"}.mdi-border-top:before{content:"\F0D1"}.mdi-border-top-variant:before{content:"\F8A5"}.mdi-border-vertical:before{content:"\F0D2"}.mdi-bottle-wine:before{content:"\F853"}.mdi-bow-tie:before{content:"\F677"}.mdi-bowl:before{content:"\F617"}.mdi-bowling:before{content:"\F0D3"}.mdi-box:before{content:"\F0D4"}.mdi-box-cutter:before{content:"\F0D5"}.mdi-box-shadow:before{content:"\F637"}.mdi-boxing-glove:before{content:"\FB41"}.mdi-braille:before{content:"\F9CF"}.mdi-brain:before{content:"\F9D0"}.mdi-bread-slice:before{content:"\FCCA"}.mdi-bread-slice-outline:before{content:"\FCCB"}.mdi-bridge:before{content:"\F618"}.mdi-briefcase:before{content:"\F0D6"}.mdi-briefcase-account:before{content:"\FCCC"}.mdi-briefcase-account-outline:before{content:"\FCCD"}.mdi-briefcase-check:before{content:"\F0D7"}.mdi-briefcase-download:before{content:"\F0D8"}.mdi-briefcase-download-outline:before{content:"\FC19"}.mdi-briefcase-edit:before{content:"\FA97"}.mdi-briefcase-edit-outline:before{content:"\FC1A"}.mdi-briefcase-minus:before{content:"\FA29"}.mdi-briefcase-minus-outline:before{content:"\FC1B"}.mdi-briefcase-outline:before{content:"\F813"}.mdi-briefcase-plus:before{content:"\FA2A"}.mdi-briefcase-plus-outline:before{content:"\FC1C"}.mdi-briefcase-remove:before{content:"\FA2B"}.mdi-briefcase-remove-outline:before{content:"\FC1D"}.mdi-briefcase-search:before{content:"\FA2C"}.mdi-briefcase-search-outline:before{content:"\FC1E"}.mdi-briefcase-upload:before{content:"\F0D9"}.mdi-briefcase-upload-outline:before{content:"\FC1F"}.mdi-brightness-1:before{content:"\F0DA"}.mdi-brightness-2:before{content:"\F0DB"}.mdi-brightness-3:before{content:"\F0DC"}.mdi-brightness-4:before{content:"\F0DD"}.mdi-brightness-5:before{content:"\F0DE"}.mdi-brightness-6:before{content:"\F0DF"}.mdi-brightness-7:before{content:"\F0E0"}.mdi-brightness-auto:before{content:"\F0E1"}.mdi-brightness-percent:before{content:"\FCCE"}.mdi-broom:before{content:"\F0E2"}.mdi-brush:before{content:"\F0E3"}.mdi-buddhism:before{content:"\F94A"}.mdi-buffer:before{content:"\F619"}.mdi-bug:before{content:"\F0E4"}.mdi-bug-check:before{content:"\FA2D"}.mdi-bug-check-outline:before{content:"\FA2E"}.mdi-bug-outline:before{content:"\FA2F"}.mdi-bugle:before{content:"\FD90"}.mdi-bulldozer:before{content:"\FB07"}.mdi-bullet:before{content:"\FCCF"}.mdi-bulletin-board:before{content:"\F0E5"}.mdi-bullhorn:before{content:"\F0E6"}.mdi-bullhorn-outline:before{content:"\FB08"}.mdi-bullseye:before{content:"\F5DD"}.mdi-bullseye-arrow:before{content:"\F8C8"}.mdi-bus:before{content:"\F0E7"}.mdi-bus-alert:before{content:"\FA98"}.mdi-bus-articulated-end:before{content:"\F79B"}.mdi-bus-articulated-front:before{content:"\F79C"}.mdi-bus-clock:before{content:"\F8C9"}.mdi-bus-double-decker:before{content:"\F79D"}.mdi-bus-school:before{content:"\F79E"}.mdi-bus-side:before{content:"\F79F"}.mdi-cached:before{content:"\F0E8"}.mdi-cactus:before{content:"\FD91"}.mdi-cake:before{content:"\F0E9"}.mdi-cake-layered:before{content:"\F0EA"}.mdi-cake-variant:before{content:"\F0EB"}.mdi-calculator:before{content:"\F0EC"}.mdi-calculator-variant:before{content:"\FA99"}.mdi-calendar:before{content:"\F0ED"}.mdi-calendar-alert:before{content:"\FA30"}.mdi-calendar-blank:before{content:"\F0EE"}.mdi-calendar-blank-outline:before{content:"\FB42"}.mdi-calendar-check:before{content:"\F0EF"}.mdi-calendar-check-outline:before{content:"\FC20"}.mdi-calendar-clock:before{content:"\F0F0"}.mdi-calendar-edit:before{content:"\F8A6"}.mdi-calendar-export:before{content:"\FB09"}.mdi-calendar-heart:before{content:"\F9D1"}.mdi-calendar-import:before{content:"\FB0A"}.mdi-calendar-minus:before{content:"\FD38"}.mdi-calendar-multiple:before{content:"\F0F1"}.mdi-calendar-multiple-check:before{content:"\F0F2"}.mdi-calendar-multiselect:before{content:"\FA31"}.mdi-calendar-outline:before{content:"\FB43"}.mdi-calendar-plus:before{content:"\F0F3"}.mdi-calendar-question:before{content:"\F691"}.mdi-calendar-range:before{content:"\F678"}.mdi-calendar-range-outline:before{content:"\FB44"}.mdi-calendar-remove:before{content:"\F0F4"}.mdi-calendar-remove-outline:before{content:"\FC21"}.mdi-calendar-search:before{content:"\F94B"}.mdi-calendar-star:before{content:"\F9D2"}.mdi-calendar-text:before{content:"\F0F5"}.mdi-calendar-text-outline:before{content:"\FC22"}.mdi-calendar-today:before{content:"\F0F6"}.mdi-calendar-week:before{content:"\FA32"}.mdi-calendar-week-begin:before{content:"\FA33"}.mdi-call-made:before{content:"\F0F7"}.mdi-call-merge:before{content:"\F0F8"}.mdi-call-missed:before{content:"\F0F9"}.mdi-call-received:before{content:"\F0FA"}.mdi-call-split:before{content:"\F0FB"}.mdi-camcorder:before{content:"\F0FC"}.mdi-camcorder-box:before{content:"\F0FD"}.mdi-camcorder-box-off:before{content:"\F0FE"}.mdi-camcorder-off:before{content:"\F0FF"}.mdi-camera:before{content:"\F100"}.mdi-camera-account:before{content:"\F8CA"}.mdi-camera-burst:before{content:"\F692"}.mdi-camera-control:before{content:"\FB45"}.mdi-camera-enhance:before{content:"\F101"}.mdi-camera-enhance-outline:before{content:"\FB46"}.mdi-camera-front:before{content:"\F102"}.mdi-camera-front-variant:before{content:"\F103"}.mdi-camera-gopro:before{content:"\F7A0"}.mdi-camera-image:before{content:"\F8CB"}.mdi-camera-iris:before{content:"\F104"}.mdi-camera-metering-center:before{content:"\F7A1"}.mdi-camera-metering-matrix:before{content:"\F7A2"}.mdi-camera-metering-partial:before{content:"\F7A3"}.mdi-camera-metering-spot:before{content:"\F7A4"}.mdi-camera-off:before{content:"\F5DF"}.mdi-camera-outline:before{content:"\FD39"}.mdi-camera-party-mode:before{content:"\F105"}.mdi-camera-rear:before{content:"\F106"}.mdi-camera-rear-variant:before{content:"\F107"}.mdi-camera-switch:before{content:"\F108"}.mdi-camera-timer:before{content:"\F109"}.mdi-camera-wireless:before{content:"\FD92"}.mdi-camera-wireless-outline:before{content:"\FD93"}.mdi-cancel:before{content:"\F739"}.mdi-candle:before{content:"\F5E2"}.mdi-candycane:before{content:"\F10A"}.mdi-cannabis:before{content:"\F7A5"}.mdi-caps-lock:before{content:"\FA9A"}.mdi-car:before{content:"\F10B"}.mdi-car-battery:before{content:"\F10C"}.mdi-car-brake-abs:before{content:"\FC23"}.mdi-car-brake-alert:before{content:"\FC24"}.mdi-car-brake-hold:before{content:"\FD3A"}.mdi-car-brake-parking:before{content:"\FD3B"}.mdi-car-connected:before{content:"\F10D"}.mdi-car-convertible:before{content:"\F7A6"}.mdi-car-cruise-control:before{content:"\FD3C"}.mdi-car-defrost-front:before{content:"\FD3D"}.mdi-car-defrost-rear:before{content:"\FD3E"}.mdi-car-door:before{content:"\FB47"}.mdi-car-electric:before{content:"\FB48"}.mdi-car-esp:before{content:"\FC25"}.mdi-car-estate:before{content:"\F7A7"}.mdi-car-hatchback:before{content:"\F7A8"}.mdi-car-key:before{content:"\FB49"}.mdi-car-light-dimmed:before{content:"\FC26"}.mdi-car-light-fog:before{content:"\FC27"}.mdi-car-light-high:before{content:"\FC28"}.mdi-car-limousine:before{content:"\F8CC"}.mdi-car-multiple:before{content:"\FB4A"}.mdi-car-parking-lights:before{content:"\FD3F"}.mdi-car-pickup:before{content:"\F7A9"}.mdi-car-side:before{content:"\F7AA"}.mdi-car-sports:before{content:"\F7AB"}.mdi-car-tire-alert:before{content:"\FC29"}.mdi-car-traction-control:before{content:"\FD40"}.mdi-car-wash:before{content:"\F10E"}.mdi-caravan:before{content:"\F7AC"}.mdi-card:before{content:"\FB4B"}.mdi-card-bulleted:before{content:"\FB4C"}.mdi-card-bulleted-off:before{content:"\FB4D"}.mdi-card-bulleted-off-outline:before{content:"\FB4E"}.mdi-card-bulleted-outline:before{content:"\FB4F"}.mdi-card-bulleted-settings:before{content:"\FB50"}.mdi-card-bulleted-settings-outline:before{content:"\FB51"}.mdi-card-outline:before{content:"\FB52"}.mdi-card-text:before{content:"\FB53"}.mdi-card-text-outline:before{content:"\FB54"}.mdi-cards:before{content:"\F638"}.mdi-cards-club:before{content:"\F8CD"}.mdi-cards-diamond:before{content:"\F8CE"}.mdi-cards-heart:before{content:"\F8CF"}.mdi-cards-outline:before{content:"\F639"}.mdi-cards-playing-outline:before{content:"\F63A"}.mdi-cards-spade:before{content:"\F8D0"}.mdi-cards-variant:before{content:"\F6C6"}.mdi-carrot:before{content:"\F10F"}.mdi-carry-on-bag-check:before{content:"\FD41"}.mdi-cart:before{content:"\F110"}.mdi-cart-arrow-down:before{content:"\FD42"}.mdi-cart-arrow-right:before{content:"\FC2A"}.mdi-cart-arrow-up:before{content:"\FD43"}.mdi-cart-minus:before{content:"\FD44"}.mdi-cart-off:before{content:"\F66B"}.mdi-cart-outline:before{content:"\F111"}.mdi-cart-plus:before{content:"\F112"}.mdi-cart-remove:before{content:"\FD45"}.mdi-case-sensitive-alt:before{content:"\F113"}.mdi-cash:before{content:"\F114"}.mdi-cash-100:before{content:"\F115"}.mdi-cash-marker:before{content:"\FD94"}.mdi-cash-multiple:before{content:"\F116"}.mdi-cash-refund:before{content:"\FA9B"}.mdi-cash-register:before{content:"\FCD0"}.mdi-cash-usd:before{content:"\F117"}.mdi-cassette:before{content:"\F9D3"}.mdi-cast:before{content:"\F118"}.mdi-cast-connected:before{content:"\F119"}.mdi-cast-off:before{content:"\F789"}.mdi-castle:before{content:"\F11A"}.mdi-cat:before{content:"\F11B"}.mdi-cctv:before{content:"\F7AD"}.mdi-ceiling-light:before{content:"\F768"}.mdi-cellphone:before{content:"\F11C"}.mdi-cellphone-android:before{content:"\F11D"}.mdi-cellphone-arrow-down:before{content:"\F9D4"}.mdi-cellphone-basic:before{content:"\F11E"}.mdi-cellphone-dock:before{content:"\F11F"}.mdi-cellphone-erase:before{content:"\F94C"}.mdi-cellphone-iphone:before{content:"\F120"}.mdi-cellphone-key:before{content:"\F94D"}.mdi-cellphone-link:before{content:"\F121"}.mdi-cellphone-link-off:before{content:"\F122"}.mdi-cellphone-lock:before{content:"\F94E"}.mdi-cellphone-message:before{content:"\F8D2"}.mdi-cellphone-off:before{content:"\F94F"}.mdi-cellphone-screenshot:before{content:"\FA34"}.mdi-cellphone-settings:before{content:"\F123"}.mdi-cellphone-settings-variant:before{content:"\F950"}.mdi-cellphone-sound:before{content:"\F951"}.mdi-cellphone-text:before{content:"\F8D1"}.mdi-cellphone-wireless:before{content:"\F814"}.mdi-celtic-cross:before{content:"\FCD1"}.mdi-certificate:before{content:"\F124"}.mdi-chair-school:before{content:"\F125"}.mdi-charity:before{content:"\FC2B"}.mdi-chart-arc:before{content:"\F126"}.mdi-chart-areaspline:before{content:"\F127"}.mdi-chart-bar:before{content:"\F128"}.mdi-chart-bar-stacked:before{content:"\F769"}.mdi-chart-bell-curve:before{content:"\FC2C"}.mdi-chart-bubble:before{content:"\F5E3"}.mdi-chart-donut:before{content:"\F7AE"}.mdi-chart-donut-variant:before{content:"\F7AF"}.mdi-chart-gantt:before{content:"\F66C"}.mdi-chart-histogram:before{content:"\F129"}.mdi-chart-line:before{content:"\F12A"}.mdi-chart-line-stacked:before{content:"\F76A"}.mdi-chart-line-variant:before{content:"\F7B0"}.mdi-chart-multiline:before{content:"\F8D3"}.mdi-chart-pie:before{content:"\F12B"}.mdi-chart-scatterplot-hexbin:before{content:"\F66D"}.mdi-chart-timeline:before{content:"\F66E"}.mdi-chat:before{content:"\FB55"}.mdi-chat-alert:before{content:"\FB56"}.mdi-chat-processing:before{content:"\FB57"}.mdi-check:before{content:"\F12C"}.mdi-check-all:before{content:"\F12D"}.mdi-check-box-multiple-outline:before{content:"\FC2D"}.mdi-check-box-outline:before{content:"\FC2E"}.mdi-check-circle:before{content:"\F5E0"}.mdi-check-circle-outline:before{content:"\F5E1"}.mdi-check-decagram:before{content:"\F790"}.mdi-check-network:before{content:"\FC2F"}.mdi-check-network-outline:before{content:"\FC30"}.mdi-check-outline:before{content:"\F854"}.mdi-checkbook:before{content:"\FA9C"}.mdi-checkbox-blank:before{content:"\F12E"}.mdi-checkbox-blank-circle:before{content:"\F12F"}.mdi-checkbox-blank-circle-outline:before{content:"\F130"}.mdi-checkbox-blank-outline:before{content:"\F131"}.mdi-checkbox-intermediate:before{content:"\F855"}.mdi-checkbox-marked:before{content:"\F132"}.mdi-checkbox-marked-circle:before{content:"\F133"}.mdi-checkbox-marked-circle-outline:before{content:"\F134"}.mdi-checkbox-marked-outline:before{content:"\F135"}.mdi-checkbox-multiple-blank:before{content:"\F136"}.mdi-checkbox-multiple-blank-circle:before{content:"\F63B"}.mdi-checkbox-multiple-blank-circle-outline:before{content:"\F63C"}.mdi-checkbox-multiple-blank-outline:before{content:"\F137"}.mdi-checkbox-multiple-marked:before{content:"\F138"}.mdi-checkbox-multiple-marked-circle:before{content:"\F63D"}.mdi-checkbox-multiple-marked-circle-outline:before{content:"\F63E"}.mdi-checkbox-multiple-marked-outline:before{content:"\F139"}.mdi-checkerboard:before{content:"\F13A"}.mdi-chef-hat:before{content:"\FB58"}.mdi-chemical-weapon:before{content:"\F13B"}.mdi-chess-bishop:before{content:"\F85B"}.mdi-chess-king:before{content:"\F856"}.mdi-chess-knight:before{content:"\F857"}.mdi-chess-pawn:before{content:"\F858"}.mdi-chess-queen:before{content:"\F859"}.mdi-chess-rook:before{content:"\F85A"}.mdi-chevron-double-down:before{content:"\F13C"}.mdi-chevron-double-left:before{content:"\F13D"}.mdi-chevron-double-right:before{content:"\F13E"}.mdi-chevron-double-up:before{content:"\F13F"}.mdi-chevron-down:before{content:"\F140"}.mdi-chevron-down-box:before{content:"\F9D5"}.mdi-chevron-down-box-outline:before{content:"\F9D6"}.mdi-chevron-down-circle:before{content:"\FB0B"}.mdi-chevron-down-circle-outline:before{content:"\FB0C"}.mdi-chevron-left:before{content:"\F141"}.mdi-chevron-left-box:before{content:"\F9D7"}.mdi-chevron-left-box-outline:before{content:"\F9D8"}.mdi-chevron-left-circle:before{content:"\FB0D"}.mdi-chevron-left-circle-outline:before{content:"\FB0E"}.mdi-chevron-right:before{content:"\F142"}.mdi-chevron-right-box:before{content:"\F9D9"}.mdi-chevron-right-box-outline:before{content:"\F9DA"}.mdi-chevron-right-circle:before{content:"\FB0F"}.mdi-chevron-right-circle-outline:before{content:"\FB10"}.mdi-chevron-triple-down:before{content:"\FD95"}.mdi-chevron-triple-left:before{content:"\FD96"}.mdi-chevron-triple-right:before{content:"\FD97"}.mdi-chevron-triple-up:before{content:"\FD98"}.mdi-chevron-up:before{content:"\F143"}.mdi-chevron-up-box:before{content:"\F9DB"}.mdi-chevron-up-box-outline:before{content:"\F9DC"}.mdi-chevron-up-circle:before{content:"\FB11"}.mdi-chevron-up-circle-outline:before{content:"\FB12"}.mdi-chili-hot:before{content:"\F7B1"}.mdi-chili-medium:before{content:"\F7B2"}.mdi-chili-mild:before{content:"\F7B3"}.mdi-chip:before{content:"\F61A"}.mdi-christianity:before{content:"\F952"}.mdi-christianity-outline:before{content:"\FCD2"}.mdi-church:before{content:"\F144"}.mdi-circle:before{content:"\F764"}.mdi-circle-edit-outline:before{content:"\F8D4"}.mdi-circle-medium:before{content:"\F9DD"}.mdi-circle-outline:before{content:"\F765"}.mdi-circle-slice-1:before{content:"\FA9D"}.mdi-circle-slice-2:before{content:"\FA9E"}.mdi-circle-slice-3:before{content:"\FA9F"}.mdi-circle-slice-4:before{content:"\FAA0"}.mdi-circle-slice-5:before{content:"\FAA1"}.mdi-circle-slice-6:before{content:"\FAA2"}.mdi-circle-slice-7:before{content:"\FAA3"}.mdi-circle-slice-8:before{content:"\FAA4"}.mdi-circle-small:before{content:"\F9DE"}.mdi-cisco-webex:before{content:"\F145"}.mdi-city:before{content:"\F146"}.mdi-city-variant:before{content:"\FA35"}.mdi-city-variant-outline:before{content:"\FA36"}.mdi-clipboard:before{content:"\F147"}.mdi-clipboard-account:before{content:"\F148"}.mdi-clipboard-account-outline:before{content:"\FC31"}.mdi-clipboard-alert:before{content:"\F149"}.mdi-clipboard-alert-outline:before{content:"\FCD3"}.mdi-clipboard-arrow-down:before{content:"\F14A"}.mdi-clipboard-arrow-down-outline:before{content:"\FC32"}.mdi-clipboard-arrow-left:before{content:"\F14B"}.mdi-clipboard-arrow-left-outline:before{content:"\FCD4"}.mdi-clipboard-arrow-right:before{content:"\FCD5"}.mdi-clipboard-arrow-right-outline:before{content:"\FCD6"}.mdi-clipboard-arrow-up:before{content:"\FC33"}.mdi-clipboard-arrow-up-outline:before{content:"\FC34"}.mdi-clipboard-check:before{content:"\F14C"}.mdi-clipboard-check-outline:before{content:"\F8A7"}.mdi-clipboard-flow:before{content:"\F6C7"}.mdi-clipboard-outline:before{content:"\F14D"}.mdi-clipboard-play:before{content:"\FC35"}.mdi-clipboard-play-outline:before{content:"\FC36"}.mdi-clipboard-plus:before{content:"\F750"}.mdi-clipboard-pulse:before{content:"\F85C"}.mdi-clipboard-pulse-outline:before{content:"\F85D"}.mdi-clipboard-text:before{content:"\F14E"}.mdi-clipboard-text-outline:before{content:"\FA37"}.mdi-clipboard-text-play:before{content:"\FC37"}.mdi-clipboard-text-play-outline:before{content:"\FC38"}.mdi-clippy:before{content:"\F14F"}.mdi-clock:before{content:"\F953"}.mdi-clock-alert:before{content:"\F954"}.mdi-clock-alert-outline:before{content:"\F5CE"}.mdi-clock-end:before{content:"\F151"}.mdi-clock-fast:before{content:"\F152"}.mdi-clock-in:before{content:"\F153"}.mdi-clock-out:before{content:"\F154"}.mdi-clock-outline:before{content:"\F150"}.mdi-clock-start:before{content:"\F155"}.mdi-close:before{content:"\F156"}.mdi-close-box:before{content:"\F157"}.mdi-close-box-multiple:before{content:"\FC39"}.mdi-close-box-multiple-outline:before{content:"\FC3A"}.mdi-close-box-outline:before{content:"\F158"}.mdi-close-circle:before{content:"\F159"}.mdi-close-circle-outline:before{content:"\F15A"}.mdi-close-network:before{content:"\F15B"}.mdi-close-network-outline:before{content:"\FC3B"}.mdi-close-octagon:before{content:"\F15C"}.mdi-close-octagon-outline:before{content:"\F15D"}.mdi-close-outline:before{content:"\F6C8"}.mdi-closed-caption:before{content:"\F15E"}.mdi-closed-caption-outline:before{content:"\FD99"}.mdi-cloud:before{content:"\F15F"}.mdi-cloud-alert:before{content:"\F9DF"}.mdi-cloud-braces:before{content:"\F7B4"}.mdi-cloud-check:before{content:"\F160"}.mdi-cloud-circle:before{content:"\F161"}.mdi-cloud-download:before{content:"\F162"}.mdi-cloud-download-outline:before{content:"\FB59"}.mdi-cloud-off-outline:before{content:"\F164"}.mdi-cloud-outline:before{content:"\F163"}.mdi-cloud-print:before{content:"\F165"}.mdi-cloud-print-outline:before{content:"\F166"}.mdi-cloud-question:before{content:"\FA38"}.mdi-cloud-search:before{content:"\F955"}.mdi-cloud-search-outline:before{content:"\F956"}.mdi-cloud-sync:before{content:"\F63F"}.mdi-cloud-tags:before{content:"\F7B5"}.mdi-cloud-upload:before{content:"\F167"}.mdi-cloud-upload-outline:before{content:"\FB5A"}.mdi-clover:before{content:"\F815"}.mdi-code-array:before{content:"\F168"}.mdi-code-braces:before{content:"\F169"}.mdi-code-brackets:before{content:"\F16A"}.mdi-code-equal:before{content:"\F16B"}.mdi-code-greater-than:before{content:"\F16C"}.mdi-code-greater-than-or-equal:before{content:"\F16D"}.mdi-code-less-than:before{content:"\F16E"}.mdi-code-less-than-or-equal:before{content:"\F16F"}.mdi-code-not-equal:before{content:"\F170"}.mdi-code-not-equal-variant:before{content:"\F171"}.mdi-code-parentheses:before{content:"\F172"}.mdi-code-string:before{content:"\F173"}.mdi-code-tags:before{content:"\F174"}.mdi-code-tags-check:before{content:"\F693"}.mdi-codepen:before{content:"\F175"}.mdi-coffee:before{content:"\F176"}.mdi-coffee-outline:before{content:"\F6C9"}.mdi-coffee-to-go:before{content:"\F177"}.mdi-coffin:before{content:"\FB5B"}.mdi-cogs:before{content:"\F8D5"}.mdi-coin:before{content:"\F178"}.mdi-coins:before{content:"\F694"}.mdi-collage:before{content:"\F640"}.mdi-collapse-all:before{content:"\FAA5"}.mdi-collapse-all-outline:before{content:"\FAA6"}.mdi-color-helper:before{content:"\F179"}.mdi-comment:before{content:"\F17A"}.mdi-comment-account:before{content:"\F17B"}.mdi-comment-account-outline:before{content:"\F17C"}.mdi-comment-alert:before{content:"\F17D"}.mdi-comment-alert-outline:before{content:"\F17E"}.mdi-comment-arrow-left:before{content:"\F9E0"}.mdi-comment-arrow-left-outline:before{content:"\F9E1"}.mdi-comment-arrow-right:before{content:"\F9E2"}.mdi-comment-arrow-right-outline:before{content:"\F9E3"}.mdi-comment-check:before{content:"\F17F"}.mdi-comment-check-outline:before{content:"\F180"}.mdi-comment-eye:before{content:"\FA39"}.mdi-comment-eye-outline:before{content:"\FA3A"}.mdi-comment-multiple:before{content:"\F85E"}.mdi-comment-multiple-outline:before{content:"\F181"}.mdi-comment-outline:before{content:"\F182"}.mdi-comment-plus:before{content:"\F9E4"}.mdi-comment-plus-outline:before{content:"\F183"}.mdi-comment-processing:before{content:"\F184"}.mdi-comment-processing-outline:before{content:"\F185"}.mdi-comment-question:before{content:"\F816"}.mdi-comment-question-outline:before{content:"\F186"}.mdi-comment-remove:before{content:"\F5DE"}.mdi-comment-remove-outline:before{content:"\F187"}.mdi-comment-search:before{content:"\FA3B"}.mdi-comment-search-outline:before{content:"\FA3C"}.mdi-comment-text:before{content:"\F188"}.mdi-comment-text-multiple:before{content:"\F85F"}.mdi-comment-text-multiple-outline:before{content:"\F860"}.mdi-comment-text-outline:before{content:"\F189"}.mdi-compare:before{content:"\F18A"}.mdi-compass:before{content:"\F18B"}.mdi-compass-off:before{content:"\FB5C"}.mdi-compass-off-outline:before{content:"\FB5D"}.mdi-compass-outline:before{content:"\F18C"}.mdi-console:before{content:"\F18D"}.mdi-console-line:before{content:"\F7B6"}.mdi-console-network:before{content:"\F8A8"}.mdi-console-network-outline:before{content:"\FC3C"}.mdi-contact-mail:before{content:"\F18E"}.mdi-contactless-payment:before{content:"\FD46"}.mdi-contacts:before{content:"\F6CA"}.mdi-contain:before{content:"\FA3D"}.mdi-contain-end:before{content:"\FA3E"}.mdi-contain-start:before{content:"\FA3F"}.mdi-content-copy:before{content:"\F18F"}.mdi-content-cut:before{content:"\F190"}.mdi-content-duplicate:before{content:"\F191"}.mdi-content-paste:before{content:"\F192"}.mdi-content-save:before{content:"\F193"}.mdi-content-save-all:before{content:"\F194"}.mdi-content-save-edit:before{content:"\FCD7"}.mdi-content-save-edit-outline:before{content:"\FCD8"}.mdi-content-save-outline:before{content:"\F817"}.mdi-content-save-settings:before{content:"\F61B"}.mdi-content-save-settings-outline:before{content:"\FB13"}.mdi-contrast:before{content:"\F195"}.mdi-contrast-box:before{content:"\F196"}.mdi-contrast-circle:before{content:"\F197"}.mdi-controller-classic:before{content:"\FB5E"}.mdi-controller-classic-outline:before{content:"\FB5F"}.mdi-cookie:before{content:"\F198"}.mdi-copyright:before{content:"\F5E6"}.mdi-cordova:before{content:"\F957"}.mdi-corn:before{content:"\F7B7"}.mdi-counter:before{content:"\F199"}.mdi-cow:before{content:"\F19A"}.mdi-crane:before{content:"\F861"}.mdi-creation:before{content:"\F1C9"}.mdi-creative-commons:before{content:"\FD47"}.mdi-credit-card:before{content:"\F19B"}.mdi-credit-card-marker:before{content:"\FD9A"}.mdi-credit-card-multiple:before{content:"\F19C"}.mdi-credit-card-off:before{content:"\F5E4"}.mdi-credit-card-plus:before{content:"\F675"}.mdi-credit-card-refund:before{content:"\FAA7"}.mdi-credit-card-scan:before{content:"\F19D"}.mdi-credit-card-settings:before{content:"\F8D6"}.mdi-credit-card-wireless:before{content:"\FD48"}.mdi-cricket:before{content:"\FD49"}.mdi-crop:before{content:"\F19E"}.mdi-crop-free:before{content:"\F19F"}.mdi-crop-landscape:before{content:"\F1A0"}.mdi-crop-portrait:before{content:"\F1A1"}.mdi-crop-rotate:before{content:"\F695"}.mdi-crop-square:before{content:"\F1A2"}.mdi-crosshairs:before{content:"\F1A3"}.mdi-crosshairs-gps:before{content:"\F1A4"}.mdi-crown:before{content:"\F1A5"}.mdi-cryengine:before{content:"\F958"}.mdi-crystal-ball:before{content:"\FB14"}.mdi-cube:before{content:"\F1A6"}.mdi-cube-outline:before{content:"\F1A7"}.mdi-cube-scan:before{content:"\FB60"}.mdi-cube-send:before{content:"\F1A8"}.mdi-cube-unfolded:before{content:"\F1A9"}.mdi-cup:before{content:"\F1AA"}.mdi-cup-off:before{content:"\F5E5"}.mdi-cup-water:before{content:"\F1AB"}.mdi-cupcake:before{content:"\F959"}.mdi-curling:before{content:"\F862"}.mdi-currency-bdt:before{content:"\F863"}.mdi-currency-brl:before{content:"\FB61"}.mdi-currency-btc:before{content:"\F1AC"}.mdi-currency-chf:before{content:"\F7B8"}.mdi-currency-cny:before{content:"\F7B9"}.mdi-currency-eth:before{content:"\F7BA"}.mdi-currency-eur:before{content:"\F1AD"}.mdi-currency-gbp:before{content:"\F1AE"}.mdi-currency-ils:before{content:"\FC3D"}.mdi-currency-inr:before{content:"\F1AF"}.mdi-currency-jpy:before{content:"\F7BB"}.mdi-currency-krw:before{content:"\F7BC"}.mdi-currency-kzt:before{content:"\F864"}.mdi-currency-ngn:before{content:"\F1B0"}.mdi-currency-php:before{content:"\F9E5"}.mdi-currency-rub:before{content:"\F1B1"}.mdi-currency-sign:before{content:"\F7BD"}.mdi-currency-try:before{content:"\F1B2"}.mdi-currency-twd:before{content:"\F7BE"}.mdi-currency-usd:before{content:"\F1B3"}.mdi-currency-usd-off:before{content:"\F679"}.mdi-current-ac:before{content:"\F95A"}.mdi-current-dc:before{content:"\F95B"}.mdi-cursor-default:before{content:"\F1B4"}.mdi-cursor-default-click:before{content:"\FCD9"}.mdi-cursor-default-click-outline:before{content:"\FCDA"}.mdi-cursor-default-outline:before{content:"\F1B5"}.mdi-cursor-move:before{content:"\F1B6"}.mdi-cursor-pointer:before{content:"\F1B7"}.mdi-cursor-text:before{content:"\F5E7"}.mdi-database:before{content:"\F1B8"}.mdi-database-check:before{content:"\FAA8"}.mdi-database-edit:before{content:"\FB62"}.mdi-database-export:before{content:"\F95D"}.mdi-database-import:before{content:"\F95C"}.mdi-database-lock:before{content:"\FAA9"}.mdi-database-minus:before{content:"\F1B9"}.mdi-database-plus:before{content:"\F1BA"}.mdi-database-refresh:before{content:"\FCDB"}.mdi-database-remove:before{content:"\FCDC"}.mdi-database-search:before{content:"\F865"}.mdi-database-settings:before{content:"\FCDD"}.mdi-death-star:before{content:"\F8D7"}.mdi-death-star-variant:before{content:"\F8D8"}.mdi-deathly-hallows:before{content:"\FB63"}.mdi-debian:before{content:"\F8D9"}.mdi-debug-step-into:before{content:"\F1BB"}.mdi-debug-step-out:before{content:"\F1BC"}.mdi-debug-step-over:before{content:"\F1BD"}.mdi-decagram:before{content:"\F76B"}.mdi-decagram-outline:before{content:"\F76C"}.mdi-decimal-decrease:before{content:"\F1BE"}.mdi-decimal-increase:before{content:"\F1BF"}.mdi-delete:before{content:"\F1C0"}.mdi-delete-circle:before{content:"\F682"}.mdi-delete-circle-outline:before{content:"\FB64"}.mdi-delete-empty:before{content:"\F6CB"}.mdi-delete-forever:before{content:"\F5E8"}.mdi-delete-forever-outline:before{content:"\FB65"}.mdi-delete-outline:before{content:"\F9E6"}.mdi-delete-restore:before{content:"\F818"}.mdi-delete-sweep:before{content:"\F5E9"}.mdi-delete-sweep-outline:before{content:"\FC3E"}.mdi-delete-variant:before{content:"\F1C1"}.mdi-delta:before{content:"\F1C2"}.mdi-desk-lamp:before{content:"\F95E"}.mdi-deskphone:before{content:"\F1C3"}.mdi-desktop-classic:before{content:"\F7BF"}.mdi-desktop-mac:before{content:"\F1C4"}.mdi-desktop-mac-dashboard:before{content:"\F9E7"}.mdi-desktop-tower:before{content:"\F1C5"}.mdi-desktop-tower-monitor:before{content:"\FAAA"}.mdi-details:before{content:"\F1C6"}.mdi-dev-to:before{content:"\FD4A"}.mdi-developer-board:before{content:"\F696"}.mdi-deviantart:before{content:"\F1C7"}.mdi-dialpad:before{content:"\F61C"}.mdi-diameter:before{content:"\FC3F"}.mdi-diameter-outline:before{content:"\FC40"}.mdi-diameter-variant:before{content:"\FC41"}.mdi-diamond:before{content:"\FB66"}.mdi-diamond-outline:before{content:"\FB67"}.mdi-diamond-stone:before{content:"\F1C8"}.mdi-dice-1:before{content:"\F1CA"}.mdi-dice-2:before{content:"\F1CB"}.mdi-dice-3:before{content:"\F1CC"}.mdi-dice-4:before{content:"\F1CD"}.mdi-dice-5:before{content:"\F1CE"}.mdi-dice-6:before{content:"\F1CF"}.mdi-dice-d10:before{content:"\F76E"}.mdi-dice-d12:before{content:"\F866"}.mdi-dice-d20:before{content:"\F5EA"}.mdi-dice-d4:before{content:"\F5EB"}.mdi-dice-d6:before{content:"\F5EC"}.mdi-dice-d8:before{content:"\F5ED"}.mdi-dice-multiple:before{content:"\F76D"}.mdi-dictionary:before{content:"\F61D"}.mdi-dip-switch:before{content:"\F7C0"}.mdi-directions:before{content:"\F1D0"}.mdi-directions-fork:before{content:"\F641"}.mdi-disc:before{content:"\F5EE"}.mdi-disc-alert:before{content:"\F1D1"}.mdi-disc-player:before{content:"\F95F"}.mdi-discord:before{content:"\F66F"}.mdi-dishwasher:before{content:"\FAAB"}.mdi-disqus:before{content:"\F1D2"}.mdi-disqus-outline:before{content:"\F1D3"}.mdi-diving-flippers:before{content:"\FD9B"}.mdi-diving-helmet:before{content:"\FD9C"}.mdi-diving-scuba:before{content:"\FD9D"}.mdi-diving-scuba-flag:before{content:"\FD9E"}.mdi-diving-scuba-tank:before{content:"\FD9F"}.mdi-diving-scuba-tank-multiple:before{content:"\FDA0"}.mdi-diving-snorkel:before{content:"\FDA1"}.mdi-division:before{content:"\F1D4"}.mdi-division-box:before{content:"\F1D5"}.mdi-dlna:before{content:"\FA40"}.mdi-dna:before{content:"\F683"}.mdi-dns:before{content:"\F1D6"}.mdi-dns-outline:before{content:"\FB68"}.mdi-do-not-disturb:before{content:"\F697"}.mdi-do-not-disturb-off:before{content:"\F698"}.mdi-docker:before{content:"\F867"}.mdi-doctor:before{content:"\FA41"}.mdi-dog:before{content:"\FA42"}.mdi-dog-service:before{content:"\FAAC"}.mdi-dog-side:before{content:"\FA43"}.mdi-dolby:before{content:"\F6B2"}.mdi-domain:before{content:"\F1D7"}.mdi-domain-off:before{content:"\FD4B"}.mdi-donkey:before{content:"\F7C1"}.mdi-door:before{content:"\F819"}.mdi-door-closed:before{content:"\F81A"}.mdi-door-open:before{content:"\F81B"}.mdi-doorbell-video:before{content:"\F868"}.mdi-dot-net:before{content:"\FAAD"}.mdi-dots-horizontal:before{content:"\F1D8"}.mdi-dots-horizontal-circle:before{content:"\F7C2"}.mdi-dots-horizontal-circle-outline:before{content:"\FB69"}.mdi-dots-vertical:before{content:"\F1D9"}.mdi-dots-vertical-circle:before{content:"\F7C3"}.mdi-dots-vertical-circle-outline:before{content:"\FB6A"}.mdi-douban:before{content:"\F699"}.mdi-download:before{content:"\F1DA"}.mdi-download-multiple:before{content:"\F9E8"}.mdi-download-network:before{content:"\F6F3"}.mdi-download-network-outline:before{content:"\FC42"}.mdi-download-outline:before{content:"\FB6B"}.mdi-drag:before{content:"\F1DB"}.mdi-drag-horizontal:before{content:"\F1DC"}.mdi-drag-variant:before{content:"\FB6C"}.mdi-drag-vertical:before{content:"\F1DD"}.mdi-drama-masks:before{content:"\FCDE"}.mdi-drawing:before{content:"\F1DE"}.mdi-drawing-box:before{content:"\F1DF"}.mdi-dribbble:before{content:"\F1E0"}.mdi-dribbble-box:before{content:"\F1E1"}.mdi-drone:before{content:"\F1E2"}.mdi-dropbox:before{content:"\F1E3"}.mdi-drupal:before{content:"\F1E4"}.mdi-duck:before{content:"\F1E5"}.mdi-dumbbell:before{content:"\F1E6"}.mdi-dump-truck:before{content:"\FC43"}.mdi-ear-hearing:before{content:"\F7C4"}.mdi-ear-hearing-off:before{content:"\FA44"}.mdi-earth:before{content:"\F1E7"}.mdi-earth-box:before{content:"\F6CC"}.mdi-earth-box-off:before{content:"\F6CD"}.mdi-earth-off:before{content:"\F1E8"}.mdi-edge:before{content:"\F1E9"}.mdi-egg:before{content:"\FAAE"}.mdi-egg-easter:before{content:"\FAAF"}.mdi-eight-track:before{content:"\F9E9"}.mdi-eject:before{content:"\F1EA"}.mdi-eject-outline:before{content:"\FB6D"}.mdi-elephant:before{content:"\F7C5"}.mdi-elevation-decline:before{content:"\F1EB"}.mdi-elevation-rise:before{content:"\F1EC"}.mdi-elevator:before{content:"\F1ED"}.mdi-email:before{content:"\F1EE"}.mdi-email-alert:before{content:"\F6CE"}.mdi-email-box:before{content:"\FCDF"}.mdi-email-check:before{content:"\FAB0"}.mdi-email-check-outline:before{content:"\FAB1"}.mdi-email-lock:before{content:"\F1F1"}.mdi-email-mark-as-unread:before{content:"\FB6E"}.mdi-email-open:before{content:"\F1EF"}.mdi-email-open-outline:before{content:"\F5EF"}.mdi-email-outline:before{content:"\F1F0"}.mdi-email-plus:before{content:"\F9EA"}.mdi-email-plus-outline:before{content:"\F9EB"}.mdi-email-search:before{content:"\F960"}.mdi-email-search-outline:before{content:"\F961"}.mdi-email-variant:before{content:"\F5F0"}.mdi-ember:before{content:"\FB15"}.mdi-emby:before{content:"\F6B3"}.mdi-emoticon:before{content:"\FC44"}.mdi-emoticon-angry:before{content:"\FC45"}.mdi-emoticon-angry-outline:before{content:"\FC46"}.mdi-emoticon-cool:before{content:"\FC47"}.mdi-emoticon-cool-outline:before{content:"\F1F3"}.mdi-emoticon-cry:before{content:"\FC48"}.mdi-emoticon-cry-outline:before{content:"\FC49"}.mdi-emoticon-dead:before{content:"\FC4A"}.mdi-emoticon-dead-outline:before{content:"\F69A"}.mdi-emoticon-devil:before{content:"\FC4B"}.mdi-emoticon-devil-outline:before{content:"\F1F4"}.mdi-emoticon-excited:before{content:"\FC4C"}.mdi-emoticon-excited-outline:before{content:"\F69B"}.mdi-emoticon-happy:before{content:"\FC4D"}.mdi-emoticon-happy-outline:before{content:"\F1F5"}.mdi-emoticon-kiss:before{content:"\FC4E"}.mdi-emoticon-kiss-outline:before{content:"\FC4F"}.mdi-emoticon-neutral:before{content:"\FC50"}.mdi-emoticon-neutral-outline:before{content:"\F1F6"}.mdi-emoticon-outline:before{content:"\F1F2"}.mdi-emoticon-poop:before{content:"\F1F7"}.mdi-emoticon-poop-outline:before{content:"\FC51"}.mdi-emoticon-sad:before{content:"\FC52"}.mdi-emoticon-sad-outline:before{content:"\F1F8"}.mdi-emoticon-tongue:before{content:"\F1F9"}.mdi-emoticon-tongue-outline:before{content:"\FC53"}.mdi-emoticon-wink:before{content:"\FC54"}.mdi-emoticon-wink-outline:before{content:"\FC55"}.mdi-engine:before{content:"\F1FA"}.mdi-engine-off:before{content:"\FA45"}.mdi-engine-off-outline:before{content:"\FA46"}.mdi-engine-outline:before{content:"\F1FB"}.mdi-equal:before{content:"\F1FC"}.mdi-equal-box:before{content:"\F1FD"}.mdi-eraser:before{content:"\F1FE"}.mdi-eraser-variant:before{content:"\F642"}.mdi-escalator:before{content:"\F1FF"}.mdi-eslint:before{content:"\FC56"}.mdi-et:before{content:"\FAB2"}.mdi-ethereum:before{content:"\F869"}.mdi-ethernet:before{content:"\F200"}.mdi-ethernet-cable:before{content:"\F201"}.mdi-ethernet-cable-off:before{content:"\F202"}.mdi-etsy:before{content:"\F203"}.mdi-ev-station:before{content:"\F5F1"}.mdi-eventbrite:before{content:"\F7C6"}.mdi-evernote:before{content:"\F204"}.mdi-exclamation:before{content:"\F205"}.mdi-exit-run:before{content:"\FA47"}.mdi-exit-to-app:before{content:"\F206"}.mdi-expand-all:before{content:"\FAB3"}.mdi-expand-all-outline:before{content:"\FAB4"}.mdi-exponent:before{content:"\F962"}.mdi-exponent-box:before{content:"\F963"}.mdi-export:before{content:"\F207"}.mdi-export-variant:before{content:"\FB6F"}.mdi-eye:before{content:"\F208"}.mdi-eye-check:before{content:"\FCE0"}.mdi-eye-check-outline:before{content:"\FCE1"}.mdi-eye-circle:before{content:"\FB70"}.mdi-eye-circle-outline:before{content:"\FB71"}.mdi-eye-off:before{content:"\F209"}.mdi-eye-off-outline:before{content:"\F6D0"}.mdi-eye-outline:before{content:"\F6CF"}.mdi-eye-plus:before{content:"\F86A"}.mdi-eye-plus-outline:before{content:"\F86B"}.mdi-eye-settings:before{content:"\F86C"}.mdi-eye-settings-outline:before{content:"\F86D"}.mdi-eyedropper:before{content:"\F20A"}.mdi-eyedropper-variant:before{content:"\F20B"}.mdi-face:before{content:"\F643"}.mdi-face-agent:before{content:"\FD4C"}.mdi-face-outline:before{content:"\FB72"}.mdi-face-profile:before{content:"\F644"}.mdi-face-recognition:before{content:"\FC57"}.mdi-facebook:before{content:"\F20C"}.mdi-facebook-box:before{content:"\F20D"}.mdi-facebook-messenger:before{content:"\F20E"}.mdi-facebook-workplace:before{content:"\FB16"}.mdi-factory:before{content:"\F20F"}.mdi-fan:before{content:"\F210"}.mdi-fan-off:before{content:"\F81C"}.mdi-fast-forward:before{content:"\F211"}.mdi-fast-forward-10:before{content:"\FD4D"}.mdi-fast-forward-30:before{content:"\FCE2"}.mdi-fast-forward-outline:before{content:"\F6D1"}.mdi-fax:before{content:"\F212"}.mdi-feather:before{content:"\F6D2"}.mdi-feature-search:before{content:"\FA48"}.mdi-feature-search-outline:before{content:"\FA49"}.mdi-fedora:before{content:"\F8DA"}.mdi-ferry:before{content:"\F213"}.mdi-file:before{content:"\F214"}.mdi-file-account:before{content:"\F73A"}.mdi-file-alert:before{content:"\FA4A"}.mdi-file-alert-outline:before{content:"\FA4B"}.mdi-file-cabinet:before{content:"\FAB5"}.mdi-file-cancel:before{content:"\FDA2"}.mdi-file-cancel-outline:before{content:"\FDA3"}.mdi-file-chart:before{content:"\F215"}.mdi-file-check:before{content:"\F216"}.mdi-file-cloud:before{content:"\F217"}.mdi-file-compare:before{content:"\F8A9"}.mdi-file-delimited:before{content:"\F218"}.mdi-file-document:before{content:"\F219"}.mdi-file-document-box:before{content:"\F21A"}.mdi-file-document-box-multiple:before{content:"\FAB6"}.mdi-file-document-box-multiple-outline:before{content:"\FAB7"}.mdi-file-document-box-outline:before{content:"\F9EC"}.mdi-file-document-edit:before{content:"\FDA4"}.mdi-file-document-edit-outline:before{content:"\FDA5"}.mdi-file-document-outline:before{content:"\F9ED"}.mdi-file-download:before{content:"\F964"}.mdi-file-download-outline:before{content:"\F965"}.mdi-file-excel:before{content:"\F21B"}.mdi-file-excel-box:before{content:"\F21C"}.mdi-file-export:before{content:"\F21D"}.mdi-file-eye:before{content:"\FDA6"}.mdi-file-eye-outline:before{content:"\FDA7"}.mdi-file-find:before{content:"\F21E"}.mdi-file-find-outline:before{content:"\FB73"}.mdi-file-hidden:before{content:"\F613"}.mdi-file-image:before{content:"\F21F"}.mdi-file-import:before{content:"\F220"}.mdi-file-lock:before{content:"\F221"}.mdi-file-move:before{content:"\FAB8"}.mdi-file-multiple:before{content:"\F222"}.mdi-file-music:before{content:"\F223"}.mdi-file-outline:before{content:"\F224"}.mdi-file-pdf:before{content:"\F225"}.mdi-file-pdf-box:before{content:"\F226"}.mdi-file-percent:before{content:"\F81D"}.mdi-file-plus:before{content:"\F751"}.mdi-file-powerpoint:before{content:"\F227"}.mdi-file-powerpoint-box:before{content:"\F228"}.mdi-file-presentation-box:before{content:"\F229"}.mdi-file-question:before{content:"\F86E"}.mdi-file-remove:before{content:"\FB74"}.mdi-file-replace:before{content:"\FB17"}.mdi-file-replace-outline:before{content:"\FB18"}.mdi-file-restore:before{content:"\F670"}.mdi-file-search:before{content:"\FC58"}.mdi-file-search-outline:before{content:"\FC59"}.mdi-file-send:before{content:"\F22A"}.mdi-file-table:before{content:"\FC5A"}.mdi-file-table-outline:before{content:"\FC5B"}.mdi-file-tree:before{content:"\F645"}.mdi-file-undo:before{content:"\F8DB"}.mdi-file-upload:before{content:"\FA4C"}.mdi-file-upload-outline:before{content:"\FA4D"}.mdi-file-video:before{content:"\F22B"}.mdi-file-word:before{content:"\F22C"}.mdi-file-word-box:before{content:"\F22D"}.mdi-file-xml:before{content:"\F22E"}.mdi-film:before{content:"\F22F"}.mdi-filmstrip:before{content:"\F230"}.mdi-filmstrip-off:before{content:"\F231"}.mdi-filter:before{content:"\F232"}.mdi-filter-outline:before{content:"\F233"}.mdi-filter-remove:before{content:"\F234"}.mdi-filter-remove-outline:before{content:"\F235"}.mdi-filter-variant:before{content:"\F236"}.mdi-finance:before{content:"\F81E"}.mdi-find-replace:before{content:"\F6D3"}.mdi-fingerprint:before{content:"\F237"}.mdi-fire:before{content:"\F238"}.mdi-fire-truck:before{content:"\F8AA"}.mdi-firebase:before{content:"\F966"}.mdi-firefox:before{content:"\F239"}.mdi-fish:before{content:"\F23A"}.mdi-flag:before{content:"\F23B"}.mdi-flag-checkered:before{content:"\F23C"}.mdi-flag-minus:before{content:"\FB75"}.mdi-flag-outline:before{content:"\F23D"}.mdi-flag-plus:before{content:"\FB76"}.mdi-flag-remove:before{content:"\FB77"}.mdi-flag-triangle:before{content:"\F23F"}.mdi-flag-variant:before{content:"\F240"}.mdi-flag-variant-outline:before{content:"\F23E"}.mdi-flare:before{content:"\FD4E"}.mdi-flash:before{content:"\F241"}.mdi-flash-auto:before{content:"\F242"}.mdi-flash-circle:before{content:"\F81F"}.mdi-flash-off:before{content:"\F243"}.mdi-flash-outline:before{content:"\F6D4"}.mdi-flash-red-eye:before{content:"\F67A"}.mdi-flashlight:before{content:"\F244"}.mdi-flashlight-off:before{content:"\F245"}.mdi-flask:before{content:"\F093"}.mdi-flask-empty:before{content:"\F094"}.mdi-flask-empty-outline:before{content:"\F095"}.mdi-flask-outline:before{content:"\F096"}.mdi-flattr:before{content:"\F246"}.mdi-flickr:before{content:"\FCE3"}.mdi-flip-to-back:before{content:"\F247"}.mdi-flip-to-front:before{content:"\F248"}.mdi-floor-lamp:before{content:"\F8DC"}.mdi-floor-plan:before{content:"\F820"}.mdi-floppy:before{content:"\F249"}.mdi-floppy-variant:before{content:"\F9EE"}.mdi-flower:before{content:"\F24A"}.mdi-flower-outline:before{content:"\F9EF"}.mdi-flower-poppy:before{content:"\FCE4"}.mdi-flower-tulip:before{content:"\F9F0"}.mdi-flower-tulip-outline:before{content:"\F9F1"}.mdi-folder:before{content:"\F24B"}.mdi-folder-account:before{content:"\F24C"}.mdi-folder-account-outline:before{content:"\FB78"}.mdi-folder-alert:before{content:"\FDA8"}.mdi-folder-alert-outline:before{content:"\FDA9"}.mdi-folder-clock:before{content:"\FAB9"}.mdi-folder-clock-outline:before{content:"\FABA"}.mdi-folder-download:before{content:"\F24D"}.mdi-folder-edit:before{content:"\F8DD"}.mdi-folder-edit-outline:before{content:"\FDAA"}.mdi-folder-google-drive:before{content:"\F24E"}.mdi-folder-image:before{content:"\F24F"}.mdi-folder-key:before{content:"\F8AB"}.mdi-folder-key-network:before{content:"\F8AC"}.mdi-folder-key-network-outline:before{content:"\FC5C"}.mdi-folder-lock:before{content:"\F250"}.mdi-folder-lock-open:before{content:"\F251"}.mdi-folder-move:before{content:"\F252"}.mdi-folder-multiple:before{content:"\F253"}.mdi-folder-multiple-image:before{content:"\F254"}.mdi-folder-multiple-outline:before{content:"\F255"}.mdi-folder-network:before{content:"\F86F"}.mdi-folder-network-outline:before{content:"\FC5D"}.mdi-folder-open:before{content:"\F76F"}.mdi-folder-open-outline:before{content:"\FDAB"}.mdi-folder-outline:before{content:"\F256"}.mdi-folder-plus:before{content:"\F257"}.mdi-folder-plus-outline:before{content:"\FB79"}.mdi-folder-pound:before{content:"\FCE5"}.mdi-folder-pound-outline:before{content:"\FCE6"}.mdi-folder-remove:before{content:"\F258"}.mdi-folder-remove-outline:before{content:"\FB7A"}.mdi-folder-search:before{content:"\F967"}.mdi-folder-search-outline:before{content:"\F968"}.mdi-folder-star:before{content:"\F69C"}.mdi-folder-star-outline:before{content:"\FB7B"}.mdi-folder-sync:before{content:"\FCE7"}.mdi-folder-sync-outline:before{content:"\FCE8"}.mdi-folder-text:before{content:"\FC5E"}.mdi-folder-text-outline:before{content:"\FC5F"}.mdi-folder-upload:before{content:"\F259"}.mdi-font-awesome:before{content:"\F03A"}.mdi-food:before{content:"\F25A"}.mdi-food-apple:before{content:"\F25B"}.mdi-food-apple-outline:before{content:"\FC60"}.mdi-food-croissant:before{content:"\F7C7"}.mdi-food-fork-drink:before{content:"\F5F2"}.mdi-food-off:before{content:"\F5F3"}.mdi-food-variant:before{content:"\F25C"}.mdi-football:before{content:"\F25D"}.mdi-football-australian:before{content:"\F25E"}.mdi-football-helmet:before{content:"\F25F"}.mdi-forklift:before{content:"\F7C8"}.mdi-format-align-bottom:before{content:"\F752"}.mdi-format-align-center:before{content:"\F260"}.mdi-format-align-justify:before{content:"\F261"}.mdi-format-align-left:before{content:"\F262"}.mdi-format-align-middle:before{content:"\F753"}.mdi-format-align-right:before{content:"\F263"}.mdi-format-align-top:before{content:"\F754"}.mdi-format-annotation-minus:before{content:"\FABB"}.mdi-format-annotation-plus:before{content:"\F646"}.mdi-format-bold:before{content:"\F264"}.mdi-format-clear:before{content:"\F265"}.mdi-format-color-fill:before{content:"\F266"}.mdi-format-color-text:before{content:"\F69D"}.mdi-format-columns:before{content:"\F8DE"}.mdi-format-float-center:before{content:"\F267"}.mdi-format-float-left:before{content:"\F268"}.mdi-format-float-none:before{content:"\F269"}.mdi-format-float-right:before{content:"\F26A"}.mdi-format-font:before{content:"\F6D5"}.mdi-format-font-size-decrease:before{content:"\F9F2"}.mdi-format-font-size-increase:before{content:"\F9F3"}.mdi-format-header-1:before{content:"\F26B"}.mdi-format-header-2:before{content:"\F26C"}.mdi-format-header-3:before{content:"\F26D"}.mdi-format-header-4:before{content:"\F26E"}.mdi-format-header-5:before{content:"\F26F"}.mdi-format-header-6:before{content:"\F270"}.mdi-format-header-decrease:before{content:"\F271"}.mdi-format-header-equal:before{content:"\F272"}.mdi-format-header-increase:before{content:"\F273"}.mdi-format-header-pound:before{content:"\F274"}.mdi-format-horizontal-align-center:before{content:"\F61E"}.mdi-format-horizontal-align-left:before{content:"\F61F"}.mdi-format-horizontal-align-right:before{content:"\F620"}.mdi-format-indent-decrease:before{content:"\F275"}.mdi-format-indent-increase:before{content:"\F276"}.mdi-format-italic:before{content:"\F277"}.mdi-format-letter-case:before{content:"\FB19"}.mdi-format-letter-case-lower:before{content:"\FB1A"}.mdi-format-letter-case-upper:before{content:"\FB1B"}.mdi-format-line-spacing:before{content:"\F278"}.mdi-format-line-style:before{content:"\F5C8"}.mdi-format-line-weight:before{content:"\F5C9"}.mdi-format-list-bulleted:before{content:"\F279"}.mdi-format-list-bulleted-square:before{content:"\FDAC"}.mdi-format-list-bulleted-type:before{content:"\F27A"}.mdi-format-list-checkbox:before{content:"\F969"}.mdi-format-list-checks:before{content:"\F755"}.mdi-format-list-numbered:before{content:"\F27B"}.mdi-format-list-numbered-rtl:before{content:"\FCE9"}.mdi-format-page-break:before{content:"\F6D6"}.mdi-format-paint:before{content:"\F27C"}.mdi-format-paragraph:before{content:"\F27D"}.mdi-format-pilcrow:before{content:"\F6D7"}.mdi-format-quote-close:before{content:"\F27E"}.mdi-format-quote-open:before{content:"\F756"}.mdi-format-rotate-90:before{content:"\F6A9"}.mdi-format-section:before{content:"\F69E"}.mdi-format-size:before{content:"\F27F"}.mdi-format-strikethrough:before{content:"\F280"}.mdi-format-strikethrough-variant:before{content:"\F281"}.mdi-format-subscript:before{content:"\F282"}.mdi-format-superscript:before{content:"\F283"}.mdi-format-text:before{content:"\F284"}.mdi-format-text-rotation-down:before{content:"\FD4F"}.mdi-format-text-rotation-none:before{content:"\FD50"}.mdi-format-text-wrapping-clip:before{content:"\FCEA"}.mdi-format-text-wrapping-overflow:before{content:"\FCEB"}.mdi-format-text-wrapping-wrap:before{content:"\FCEC"}.mdi-format-textbox:before{content:"\FCED"}.mdi-format-textdirection-l-to-r:before{content:"\F285"}.mdi-format-textdirection-r-to-l:before{content:"\F286"}.mdi-format-title:before{content:"\F5F4"}.mdi-format-underline:before{content:"\F287"}.mdi-format-vertical-align-bottom:before{content:"\F621"}.mdi-format-vertical-align-center:before{content:"\F622"}.mdi-format-vertical-align-top:before{content:"\F623"}.mdi-format-wrap-inline:before{content:"\F288"}.mdi-format-wrap-square:before{content:"\F289"}.mdi-format-wrap-tight:before{content:"\F28A"}.mdi-format-wrap-top-bottom:before{content:"\F28B"}.mdi-forum:before{content:"\F28C"}.mdi-forum-outline:before{content:"\F821"}.mdi-forward:before{content:"\F28D"}.mdi-forwardburger:before{content:"\FD51"}.mdi-fountain:before{content:"\F96A"}.mdi-fountain-pen:before{content:"\FCEE"}.mdi-fountain-pen-tip:before{content:"\FCEF"}.mdi-foursquare:before{content:"\F28E"}.mdi-freebsd:before{content:"\F8DF"}.mdi-fridge:before{content:"\F290"}.mdi-fridge-bottom:before{content:"\F292"}.mdi-fridge-outline:before{content:"\F28F"}.mdi-fridge-top:before{content:"\F291"}.mdi-fuel:before{content:"\F7C9"}.mdi-fullscreen:before{content:"\F293"}.mdi-fullscreen-exit:before{content:"\F294"}.mdi-function:before{content:"\F295"}.mdi-function-variant:before{content:"\F870"}.mdi-fuse:before{content:"\FC61"}.mdi-fuse-blade:before{content:"\FC62"}.mdi-gamepad:before{content:"\F296"}.mdi-gamepad-variant:before{content:"\F297"}.mdi-gantry-crane:before{content:"\FDAD"}.mdi-garage:before{content:"\F6D8"}.mdi-garage-alert:before{content:"\F871"}.mdi-garage-open:before{content:"\F6D9"}.mdi-gas-cylinder:before{content:"\F647"}.mdi-gas-station:before{content:"\F298"}.mdi-gate:before{content:"\F299"}.mdi-gate-and:before{content:"\F8E0"}.mdi-gate-nand:before{content:"\F8E1"}.mdi-gate-nor:before{content:"\F8E2"}.mdi-gate-not:before{content:"\F8E3"}.mdi-gate-or:before{content:"\F8E4"}.mdi-gate-xnor:before{content:"\F8E5"}.mdi-gate-xor:before{content:"\F8E6"}.mdi-gauge:before{content:"\F29A"}.mdi-gauge-empty:before{content:"\F872"}.mdi-gauge-full:before{content:"\F873"}.mdi-gauge-low:before{content:"\F874"}.mdi-gavel:before{content:"\F29B"}.mdi-gender-female:before{content:"\F29C"}.mdi-gender-male:before{content:"\F29D"}.mdi-gender-male-female:before{content:"\F29E"}.mdi-gender-transgender:before{content:"\F29F"}.mdi-gentoo:before{content:"\F8E7"}.mdi-gesture:before{content:"\F7CA"}.mdi-gesture-double-tap:before{content:"\F73B"}.mdi-gesture-pinch:before{content:"\FABC"}.mdi-gesture-spread:before{content:"\FABD"}.mdi-gesture-swipe:before{content:"\FD52"}.mdi-gesture-swipe-down:before{content:"\F73C"}.mdi-gesture-swipe-horizontal:before{content:"\FABE"}.mdi-gesture-swipe-left:before{content:"\F73D"}.mdi-gesture-swipe-right:before{content:"\F73E"}.mdi-gesture-swipe-up:before{content:"\F73F"}.mdi-gesture-swipe-vertical:before{content:"\FABF"}.mdi-gesture-tap:before{content:"\F740"}.mdi-gesture-tap-hold:before{content:"\FD53"}.mdi-gesture-two-double-tap:before{content:"\F741"}.mdi-gesture-two-tap:before{content:"\F742"}.mdi-ghost:before{content:"\F2A0"}.mdi-ghost-off:before{content:"\F9F4"}.mdi-gif:before{content:"\FD54"}.mdi-gift:before{content:"\F2A1"}.mdi-git:before{content:"\F2A2"}.mdi-github-box:before{content:"\F2A3"}.mdi-github-circle:before{content:"\F2A4"}.mdi-github-face:before{content:"\F6DA"}.mdi-gitlab:before{content:"\FB7C"}.mdi-glass-cocktail:before{content:"\F356"}.mdi-glass-flute:before{content:"\F2A5"}.mdi-glass-mug:before{content:"\F2A6"}.mdi-glass-stange:before{content:"\F2A7"}.mdi-glass-tulip:before{content:"\F2A8"}.mdi-glass-wine:before{content:"\F875"}.mdi-glassdoor:before{content:"\F2A9"}.mdi-glasses:before{content:"\F2AA"}.mdi-globe-model:before{content:"\F8E8"}.mdi-gmail:before{content:"\F2AB"}.mdi-gnome:before{content:"\F2AC"}.mdi-go-kart:before{content:"\FD55"}.mdi-go-kart-track:before{content:"\FD56"}.mdi-gog:before{content:"\FB7D"}.mdi-golf:before{content:"\F822"}.mdi-gondola:before{content:"\F685"}.mdi-goodreads:before{content:"\FD57"}.mdi-google:before{content:"\F2AD"}.mdi-google-adwords:before{content:"\FC63"}.mdi-google-allo:before{content:"\F801"}.mdi-google-analytics:before{content:"\F7CB"}.mdi-google-assistant:before{content:"\F7CC"}.mdi-google-cardboard:before{content:"\F2AE"}.mdi-google-chrome:before{content:"\F2AF"}.mdi-google-circles:before{content:"\F2B0"}.mdi-google-circles-communities:before{content:"\F2B1"}.mdi-google-circles-extended:before{content:"\F2B2"}.mdi-google-circles-group:before{content:"\F2B3"}.mdi-google-classroom:before{content:"\F2C0"}.mdi-google-controller:before{content:"\F2B4"}.mdi-google-controller-off:before{content:"\F2B5"}.mdi-google-drive:before{content:"\F2B6"}.mdi-google-earth:before{content:"\F2B7"}.mdi-google-fit:before{content:"\F96B"}.mdi-google-glass:before{content:"\F2B8"}.mdi-google-hangouts:before{content:"\F2C9"}.mdi-google-home:before{content:"\F823"}.mdi-google-keep:before{content:"\F6DB"}.mdi-google-lens:before{content:"\F9F5"}.mdi-google-maps:before{content:"\F5F5"}.mdi-google-nearby:before{content:"\F2B9"}.mdi-google-pages:before{content:"\F2BA"}.mdi-google-photos:before{content:"\F6DC"}.mdi-google-physical-web:before{content:"\F2BB"}.mdi-google-play:before{content:"\F2BC"}.mdi-google-plus:before{content:"\F2BD"}.mdi-google-plus-box:before{content:"\F2BE"}.mdi-google-spreadsheet:before{content:"\F9F6"}.mdi-google-street-view:before{content:"\FC64"}.mdi-google-translate:before{content:"\F2BF"}.mdi-gpu:before{content:"\F8AD"}.mdi-gradient:before{content:"\F69F"}.mdi-grain:before{content:"\FD58"}.mdi-graphql:before{content:"\F876"}.mdi-grave-stone:before{content:"\FB7E"}.mdi-grease-pencil:before{content:"\F648"}.mdi-greater-than:before{content:"\F96C"}.mdi-greater-than-or-equal:before{content:"\F96D"}.mdi-grid:before{content:"\F2C1"}.mdi-grid-large:before{content:"\F757"}.mdi-grid-off:before{content:"\F2C2"}.mdi-group:before{content:"\F2C3"}.mdi-guitar-acoustic:before{content:"\F770"}.mdi-guitar-electric:before{content:"\F2C4"}.mdi-guitar-pick:before{content:"\F2C5"}.mdi-guitar-pick-outline:before{content:"\F2C6"}.mdi-guy-fawkes-mask:before{content:"\F824"}.mdi-hackernews:before{content:"\F624"}.mdi-hail:before{content:"\FAC0"}.mdi-halloween:before{content:"\FB7F"}.mdi-hamburger:before{content:"\F684"}.mdi-hammer:before{content:"\F8E9"}.mdi-hand:before{content:"\FA4E"}.mdi-hand-okay:before{content:"\FA4F"}.mdi-hand-peace:before{content:"\FA50"}.mdi-hand-peace-variant:before{content:"\FA51"}.mdi-hand-pointing-down:before{content:"\FA52"}.mdi-hand-pointing-left:before{content:"\FA53"}.mdi-hand-pointing-right:before{content:"\F2C7"}.mdi-hand-pointing-up:before{content:"\FA54"}.mdi-hanger:before{content:"\F2C8"}.mdi-hard-hat:before{content:"\F96E"}.mdi-harddisk:before{content:"\F2CA"}.mdi-hat-fedora:before{content:"\FB80"}.mdi-hazard-lights:before{content:"\FC65"}.mdi-hdr:before{content:"\FD59"}.mdi-hdr-off:before{content:"\FD5A"}.mdi-headphones:before{content:"\F2CB"}.mdi-headphones-bluetooth:before{content:"\F96F"}.mdi-headphones-box:before{content:"\F2CC"}.mdi-headphones-off:before{content:"\F7CD"}.mdi-headphones-settings:before{content:"\F2CD"}.mdi-headset:before{content:"\F2CE"}.mdi-headset-dock:before{content:"\F2CF"}.mdi-headset-off:before{content:"\F2D0"}.mdi-heart:before{content:"\F2D1"}.mdi-heart-box:before{content:"\F2D2"}.mdi-heart-box-outline:before{content:"\F2D3"}.mdi-heart-broken:before{content:"\F2D4"}.mdi-heart-broken-outline:before{content:"\FCF0"}.mdi-heart-circle:before{content:"\F970"}.mdi-heart-circle-outline:before{content:"\F971"}.mdi-heart-half:before{content:"\F6DE"}.mdi-heart-half-full:before{content:"\F6DD"}.mdi-heart-half-outline:before{content:"\F6DF"}.mdi-heart-multiple:before{content:"\FA55"}.mdi-heart-multiple-outline:before{content:"\FA56"}.mdi-heart-off:before{content:"\F758"}.mdi-heart-outline:before{content:"\F2D5"}.mdi-heart-pulse:before{content:"\F5F6"}.mdi-helicopter:before{content:"\FAC1"}.mdi-help:before{content:"\F2D6"}.mdi-help-box:before{content:"\F78A"}.mdi-help-circle:before{content:"\F2D7"}.mdi-help-circle-outline:before{content:"\F625"}.mdi-help-network:before{content:"\F6F4"}.mdi-help-network-outline:before{content:"\FC66"}.mdi-help-rhombus:before{content:"\FB81"}.mdi-help-rhombus-outline:before{content:"\FB82"}.mdi-hexagon:before{content:"\F2D8"}.mdi-hexagon-multiple:before{content:"\F6E0"}.mdi-hexagon-outline:before{content:"\F2D9"}.mdi-hexagon-slice-1:before{content:"\FAC2"}.mdi-hexagon-slice-2:before{content:"\FAC3"}.mdi-hexagon-slice-3:before{content:"\FAC4"}.mdi-hexagon-slice-4:before{content:"\FAC5"}.mdi-hexagon-slice-5:before{content:"\FAC6"}.mdi-hexagon-slice-6:before{content:"\FAC7"}.mdi-hexagram:before{content:"\FAC8"}.mdi-hexagram-outline:before{content:"\FAC9"}.mdi-high-definition:before{content:"\F7CE"}.mdi-high-definition-box:before{content:"\F877"}.mdi-highway:before{content:"\F5F7"}.mdi-hiking:before{content:"\FD5B"}.mdi-hinduism:before{content:"\F972"}.mdi-history:before{content:"\F2DA"}.mdi-hockey-puck:before{content:"\F878"}.mdi-hockey-sticks:before{content:"\F879"}.mdi-hololens:before{content:"\F2DB"}.mdi-home:before{content:"\F2DC"}.mdi-home-account:before{content:"\F825"}.mdi-home-alert:before{content:"\F87A"}.mdi-home-assistant:before{content:"\F7CF"}.mdi-home-automation:before{content:"\F7D0"}.mdi-home-circle:before{content:"\F7D1"}.mdi-home-city:before{content:"\FCF1"}.mdi-home-city-outline:before{content:"\FCF2"}.mdi-home-currency-usd:before{content:"\F8AE"}.mdi-home-floor-0:before{content:"\FDAE"}.mdi-home-floor-1:before{content:"\FD5C"}.mdi-home-floor-2:before{content:"\FD5D"}.mdi-home-floor-3:before{content:"\FD5E"}.mdi-home-floor-a:before{content:"\FD5F"}.mdi-home-floor-b:before{content:"\FD60"}.mdi-home-floor-g:before{content:"\FD61"}.mdi-home-floor-l:before{content:"\FD62"}.mdi-home-floor-negative-1:before{content:"\FDAF"}.mdi-home-group:before{content:"\FDB0"}.mdi-home-heart:before{content:"\F826"}.mdi-home-lock:before{content:"\F8EA"}.mdi-home-lock-open:before{content:"\F8EB"}.mdi-home-map-marker:before{content:"\F5F8"}.mdi-home-minus:before{content:"\F973"}.mdi-home-modern:before{content:"\F2DD"}.mdi-home-outline:before{content:"\F6A0"}.mdi-home-plus:before{content:"\F974"}.mdi-home-variant:before{content:"\F2DE"}.mdi-home-variant-outline:before{content:"\FB83"}.mdi-hook:before{content:"\F6E1"}.mdi-hook-off:before{content:"\F6E2"}.mdi-hops:before{content:"\F2DF"}.mdi-horseshoe:before{content:"\FA57"}.mdi-hospital:before{content:"\F2E0"}.mdi-hospital-building:before{content:"\F2E1"}.mdi-hospital-marker:before{content:"\F2E2"}.mdi-hot-tub:before{content:"\F827"}.mdi-hotel:before{content:"\F2E3"}.mdi-houzz:before{content:"\F2E4"}.mdi-houzz-box:before{content:"\F2E5"}.mdi-hubspot:before{content:"\FCF3"}.mdi-hulu:before{content:"\F828"}.mdi-human:before{content:"\F2E6"}.mdi-human-child:before{content:"\F2E7"}.mdi-human-female:before{content:"\F649"}.mdi-human-female-boy:before{content:"\FA58"}.mdi-human-female-female:before{content:"\FA59"}.mdi-human-female-girl:before{content:"\FA5A"}.mdi-human-greeting:before{content:"\F64A"}.mdi-human-handsdown:before{content:"\F64B"}.mdi-human-handsup:before{content:"\F64C"}.mdi-human-male:before{content:"\F64D"}.mdi-human-male-boy:before{content:"\FA5B"}.mdi-human-male-female:before{content:"\F2E8"}.mdi-human-male-girl:before{content:"\FA5C"}.mdi-human-male-male:before{content:"\FA5D"}.mdi-human-pregnant:before{content:"\F5CF"}.mdi-humble-bundle:before{content:"\F743"}.mdi-ice-cream:before{content:"\F829"}.mdi-iframe:before{content:"\FC67"}.mdi-iframe-outline:before{content:"\FC68"}.mdi-image:before{content:"\F2E9"}.mdi-image-album:before{content:"\F2EA"}.mdi-image-area:before{content:"\F2EB"}.mdi-image-area-close:before{content:"\F2EC"}.mdi-image-broken:before{content:"\F2ED"}.mdi-image-broken-variant:before{content:"\F2EE"}.mdi-image-filter:before{content:"\F2EF"}.mdi-image-filter-black-white:before{content:"\F2F0"}.mdi-image-filter-center-focus:before{content:"\F2F1"}.mdi-image-filter-center-focus-weak:before{content:"\F2F2"}.mdi-image-filter-drama:before{content:"\F2F3"}.mdi-image-filter-frames:before{content:"\F2F4"}.mdi-image-filter-hdr:before{content:"\F2F5"}.mdi-image-filter-none:before{content:"\F2F6"}.mdi-image-filter-tilt-shift:before{content:"\F2F7"}.mdi-image-filter-vintage:before{content:"\F2F8"}.mdi-image-move:before{content:"\F9F7"}.mdi-image-multiple:before{content:"\F2F9"}.mdi-image-off:before{content:"\F82A"}.mdi-image-outline:before{content:"\F975"}.mdi-image-plus:before{content:"\F87B"}.mdi-image-search:before{content:"\F976"}.mdi-image-search-outline:before{content:"\F977"}.mdi-image-size-select-actual:before{content:"\FC69"}.mdi-image-size-select-large:before{content:"\FC6A"}.mdi-image-size-select-small:before{content:"\FC6B"}.mdi-import:before{content:"\F2FA"}.mdi-inbox:before{content:"\F686"}.mdi-inbox-arrow-down:before{content:"\F2FB"}.mdi-inbox-arrow-up:before{content:"\F3D1"}.mdi-inbox-multiple:before{content:"\F8AF"}.mdi-inbox-multiple-outline:before{content:"\FB84"}.mdi-incognito:before{content:"\F5F9"}.mdi-infinity:before{content:"\F6E3"}.mdi-information:before{content:"\F2FC"}.mdi-information-outline:before{content:"\F2FD"}.mdi-information-variant:before{content:"\F64E"}.mdi-instagram:before{content:"\F2FE"}.mdi-instapaper:before{content:"\F2FF"}.mdi-internet-explorer:before{content:"\F300"}.mdi-invert-colors:before{content:"\F301"}.mdi-ip:before{content:"\FA5E"}.mdi-ip-network:before{content:"\FA5F"}.mdi-ip-network-outline:before{content:"\FC6C"}.mdi-ipod:before{content:"\FC6D"}.mdi-islam:before{content:"\F978"}.mdi-itunes:before{content:"\F676"}.mdi-jabber:before{content:"\FDB1"}.mdi-jeepney:before{content:"\F302"}.mdi-jira:before{content:"\F303"}.mdi-jquery:before{content:"\F87C"}.mdi-jsfiddle:before{content:"\F304"}.mdi-json:before{content:"\F626"}.mdi-judaism:before{content:"\F979"}.mdi-kabaddi:before{content:"\FD63"}.mdi-karate:before{content:"\F82B"}.mdi-keg:before{content:"\F305"}.mdi-kettle:before{content:"\F5FA"}.mdi-key:before{content:"\F306"}.mdi-key-change:before{content:"\F307"}.mdi-key-minus:before{content:"\F308"}.mdi-key-outline:before{content:"\FDB2"}.mdi-key-plus:before{content:"\F309"}.mdi-key-remove:before{content:"\F30A"}.mdi-key-variant:before{content:"\F30B"}.mdi-keyboard:before{content:"\F30C"}.mdi-keyboard-backspace:before{content:"\F30D"}.mdi-keyboard-caps:before{content:"\F30E"}.mdi-keyboard-close:before{content:"\F30F"}.mdi-keyboard-off:before{content:"\F310"}.mdi-keyboard-outline:before{content:"\F97A"}.mdi-keyboard-return:before{content:"\F311"}.mdi-keyboard-settings:before{content:"\F9F8"}.mdi-keyboard-settings-outline:before{content:"\F9F9"}.mdi-keyboard-tab:before{content:"\F312"}.mdi-keyboard-variant:before{content:"\F313"}.mdi-kickstarter:before{content:"\F744"}.mdi-knife:before{content:"\F9FA"}.mdi-knife-military:before{content:"\F9FB"}.mdi-kodi:before{content:"\F314"}.mdi-label:before{content:"\F315"}.mdi-label-off:before{content:"\FACA"}.mdi-label-off-outline:before{content:"\FACB"}.mdi-label-outline:before{content:"\F316"}.mdi-label-variant:before{content:"\FACC"}.mdi-label-variant-outline:before{content:"\FACD"}.mdi-ladybug:before{content:"\F82C"}.mdi-lambda:before{content:"\F627"}.mdi-lamp:before{content:"\F6B4"}.mdi-lan:before{content:"\F317"}.mdi-lan-connect:before{content:"\F318"}.mdi-lan-disconnect:before{content:"\F319"}.mdi-lan-pending:before{content:"\F31A"}.mdi-language-c:before{content:"\F671"}.mdi-language-cpp:before{content:"\F672"}.mdi-language-csharp:before{content:"\F31B"}.mdi-language-css3:before{content:"\F31C"}.mdi-language-go:before{content:"\F7D2"}.mdi-language-haskell:before{content:"\FC6E"}.mdi-language-html5:before{content:"\F31D"}.mdi-language-java:before{content:"\FB1C"}.mdi-language-javascript:before{content:"\F31E"}.mdi-language-lua:before{content:"\F8B0"}.mdi-language-php:before{content:"\F31F"}.mdi-language-python:before{content:"\F320"}.mdi-language-python-text:before{content:"\F321"}.mdi-language-r:before{content:"\F7D3"}.mdi-language-ruby-on-rails:before{content:"\FACE"}.mdi-language-swift:before{content:"\F6E4"}.mdi-language-typescript:before{content:"\F6E5"}.mdi-laptop:before{content:"\F322"}.mdi-laptop-chromebook:before{content:"\F323"}.mdi-laptop-mac:before{content:"\F324"}.mdi-laptop-off:before{content:"\F6E6"}.mdi-laptop-windows:before{content:"\F325"}.mdi-laravel:before{content:"\FACF"}.mdi-lastfm:before{content:"\F326"}.mdi-lastpass:before{content:"\F446"}.mdi-launch:before{content:"\F327"}.mdi-lava-lamp:before{content:"\F7D4"}.mdi-layers:before{content:"\F328"}.mdi-layers-off:before{content:"\F329"}.mdi-layers-off-outline:before{content:"\F9FC"}.mdi-layers-outline:before{content:"\F9FD"}.mdi-lead-pencil:before{content:"\F64F"}.mdi-leaf:before{content:"\F32A"}.mdi-leaf-maple:before{content:"\FC6F"}.mdi-leak:before{content:"\FDB3"}.mdi-leak-off:before{content:"\FDB4"}.mdi-led-off:before{content:"\F32B"}.mdi-led-on:before{content:"\F32C"}.mdi-led-outline:before{content:"\F32D"}.mdi-led-strip:before{content:"\F7D5"}.mdi-led-variant-off:before{content:"\F32E"}.mdi-led-variant-on:before{content:"\F32F"}.mdi-led-variant-outline:before{content:"\F330"}.mdi-less-than:before{content:"\F97B"}.mdi-less-than-or-equal:before{content:"\F97C"}.mdi-library:before{content:"\F331"}.mdi-library-books:before{content:"\F332"}.mdi-library-movie:before{content:"\FCF4"}.mdi-library-music:before{content:"\F333"}.mdi-library-plus:before{content:"\F334"}.mdi-library-shelves:before{content:"\FB85"}.mdi-library-video:before{content:"\FCF5"}.mdi-lifebuoy:before{content:"\F87D"}.mdi-light-switch:before{content:"\F97D"}.mdi-lightbulb:before{content:"\F335"}.mdi-lightbulb-on:before{content:"\F6E7"}.mdi-lightbulb-on-outline:before{content:"\F6E8"}.mdi-lightbulb-outline:before{content:"\F336"}.mdi-lighthouse:before{content:"\F9FE"}.mdi-lighthouse-on:before{content:"\F9FF"}.mdi-link:before{content:"\F337"}.mdi-link-box:before{content:"\FCF6"}.mdi-link-box-outline:before{content:"\FCF7"}.mdi-link-box-variant:before{content:"\FCF8"}.mdi-link-box-variant-outline:before{content:"\FCF9"}.mdi-link-off:before{content:"\F338"}.mdi-link-plus:before{content:"\FC70"}.mdi-link-variant:before{content:"\F339"}.mdi-link-variant-off:before{content:"\F33A"}.mdi-linkedin:before{content:"\F33B"}.mdi-linkedin-box:before{content:"\F33C"}.mdi-linux:before{content:"\F33D"}.mdi-linux-mint:before{content:"\F8EC"}.mdi-litecoin:before{content:"\FA60"}.mdi-loading:before{content:"\F771"}.mdi-lock:before{content:"\F33E"}.mdi-lock-alert:before{content:"\F8ED"}.mdi-lock-clock:before{content:"\F97E"}.mdi-lock-open:before{content:"\F33F"}.mdi-lock-open-outline:before{content:"\F340"}.mdi-lock-outline:before{content:"\F341"}.mdi-lock-pattern:before{content:"\F6E9"}.mdi-lock-plus:before{content:"\F5FB"}.mdi-lock-question:before{content:"\F8EE"}.mdi-lock-reset:before{content:"\F772"}.mdi-lock-smart:before{content:"\F8B1"}.mdi-locker:before{content:"\F7D6"}.mdi-locker-multiple:before{content:"\F7D7"}.mdi-login:before{content:"\F342"}.mdi-login-variant:before{content:"\F5FC"}.mdi-logout:before{content:"\F343"}.mdi-logout-variant:before{content:"\F5FD"}.mdi-looks:before{content:"\F344"}.mdi-loop:before{content:"\F6EA"}.mdi-loupe:before{content:"\F345"}.mdi-lumx:before{content:"\F346"}.mdi-lyft:before{content:"\FB1D"}.mdi-magnet:before{content:"\F347"}.mdi-magnet-on:before{content:"\F348"}.mdi-magnify:before{content:"\F349"}.mdi-magnify-close:before{content:"\F97F"}.mdi-magnify-minus:before{content:"\F34A"}.mdi-magnify-minus-cursor:before{content:"\FA61"}.mdi-magnify-minus-outline:before{content:"\F6EB"}.mdi-magnify-plus:before{content:"\F34B"}.mdi-magnify-plus-cursor:before{content:"\FA62"}.mdi-magnify-plus-outline:before{content:"\F6EC"}.mdi-mail-ru:before{content:"\F34C"}.mdi-mailbox:before{content:"\F6ED"}.mdi-mailbox-open:before{content:"\FD64"}.mdi-mailbox-open-outline:before{content:"\FD65"}.mdi-mailbox-open-up:before{content:"\FD66"}.mdi-mailbox-open-up-outline:before{content:"\FD67"}.mdi-mailbox-outline:before{content:"\FD68"}.mdi-mailbox-up:before{content:"\FD69"}.mdi-mailbox-up-outline:before{content:"\FD6A"}.mdi-map:before{content:"\F34D"}.mdi-map-clock:before{content:"\FCFA"}.mdi-map-clock-outline:before{content:"\FCFB"}.mdi-map-legend:before{content:"\FA00"}.mdi-map-marker:before{content:"\F34E"}.mdi-map-marker-check:before{content:"\FC71"}.mdi-map-marker-circle:before{content:"\F34F"}.mdi-map-marker-distance:before{content:"\F8EF"}.mdi-map-marker-minus:before{content:"\F650"}.mdi-map-marker-multiple:before{content:"\F350"}.mdi-map-marker-off:before{content:"\F351"}.mdi-map-marker-outline:before{content:"\F7D8"}.mdi-map-marker-path:before{content:"\FCFC"}.mdi-map-marker-plus:before{content:"\F651"}.mdi-map-marker-radius:before{content:"\F352"}.mdi-map-minus:before{content:"\F980"}.mdi-map-outline:before{content:"\F981"}.mdi-map-plus:before{content:"\F982"}.mdi-map-search:before{content:"\F983"}.mdi-map-search-outline:before{content:"\F984"}.mdi-mapbox:before{content:"\FB86"}.mdi-margin:before{content:"\F353"}.mdi-markdown:before{content:"\F354"}.mdi-marker:before{content:"\F652"}.mdi-marker-cancel:before{content:"\FDB5"}.mdi-marker-check:before{content:"\F355"}.mdi-mastodon:before{content:"\FAD0"}.mdi-mastodon-variant:before{content:"\FAD1"}.mdi-material-design:before{content:"\F985"}.mdi-material-ui:before{content:"\F357"}.mdi-math-compass:before{content:"\F358"}.mdi-math-cos:before{content:"\FC72"}.mdi-math-sin:before{content:"\FC73"}.mdi-math-tan:before{content:"\FC74"}.mdi-matrix:before{content:"\F628"}.mdi-maxcdn:before{content:"\F359"}.mdi-medal:before{content:"\F986"}.mdi-medical-bag:before{content:"\F6EE"}.mdi-medium:before{content:"\F35A"}.mdi-meetup:before{content:"\FAD2"}.mdi-memory:before{content:"\F35B"}.mdi-menu:before{content:"\F35C"}.mdi-menu-down:before{content:"\F35D"}.mdi-menu-down-outline:before{content:"\F6B5"}.mdi-menu-left:before{content:"\F35E"}.mdi-menu-left-outline:before{content:"\FA01"}.mdi-menu-open:before{content:"\FB87"}.mdi-menu-right:before{content:"\F35F"}.mdi-menu-right-outline:before{content:"\FA02"}.mdi-menu-swap:before{content:"\FA63"}.mdi-menu-swap-outline:before{content:"\FA64"}.mdi-menu-up:before{content:"\F360"}.mdi-menu-up-outline:before{content:"\F6B6"}.mdi-message:before{content:"\F361"}.mdi-message-alert:before{content:"\F362"}.mdi-message-alert-outline:before{content:"\FA03"}.mdi-message-bulleted:before{content:"\F6A1"}.mdi-message-bulleted-off:before{content:"\F6A2"}.mdi-message-draw:before{content:"\F363"}.mdi-message-image:before{content:"\F364"}.mdi-message-outline:before{content:"\F365"}.mdi-message-plus:before{content:"\F653"}.mdi-message-processing:before{content:"\F366"}.mdi-message-reply:before{content:"\F367"}.mdi-message-reply-text:before{content:"\F368"}.mdi-message-settings:before{content:"\F6EF"}.mdi-message-settings-variant:before{content:"\F6F0"}.mdi-message-text:before{content:"\F369"}.mdi-message-text-outline:before{content:"\F36A"}.mdi-message-video:before{content:"\F36B"}.mdi-meteor:before{content:"\F629"}.mdi-metronome:before{content:"\F7D9"}.mdi-metronome-tick:before{content:"\F7DA"}.mdi-micro-sd:before{content:"\F7DB"}.mdi-microphone:before{content:"\F36C"}.mdi-microphone-minus:before{content:"\F8B2"}.mdi-microphone-off:before{content:"\F36D"}.mdi-microphone-outline:before{content:"\F36E"}.mdi-microphone-plus:before{content:"\F8B3"}.mdi-microphone-settings:before{content:"\F36F"}.mdi-microphone-variant:before{content:"\F370"}.mdi-microphone-variant-off:before{content:"\F371"}.mdi-microscope:before{content:"\F654"}.mdi-microsoft:before{content:"\F372"}.mdi-microsoft-dynamics:before{content:"\F987"}.mdi-microwave:before{content:"\FC75"}.mdi-midi:before{content:"\F8F0"}.mdi-midi-port:before{content:"\F8F1"}.mdi-mine:before{content:"\FDB6"}.mdi-minecraft:before{content:"\F373"}.mdi-mini-sd:before{content:"\FA04"}.mdi-minidisc:before{content:"\FA05"}.mdi-minus:before{content:"\F374"}.mdi-minus-box:before{content:"\F375"}.mdi-minus-box-outline:before{content:"\F6F1"}.mdi-minus-circle:before{content:"\F376"}.mdi-minus-circle-outline:before{content:"\F377"}.mdi-minus-network:before{content:"\F378"}.mdi-minus-network-outline:before{content:"\FC76"}.mdi-mixcloud:before{content:"\F62A"}.mdi-mixed-martial-arts:before{content:"\FD6B"}.mdi-mixed-reality:before{content:"\F87E"}.mdi-mixer:before{content:"\F7DC"}.mdi-molecule:before{content:"\FB88"}.mdi-monitor:before{content:"\F379"}.mdi-monitor-cellphone:before{content:"\F988"}.mdi-monitor-cellphone-star:before{content:"\F989"}.mdi-monitor-dashboard:before{content:"\FA06"}.mdi-monitor-lock:before{content:"\FDB7"}.mdi-monitor-multiple:before{content:"\F37A"}.mdi-monitor-off:before{content:"\FD6C"}.mdi-monitor-star:before{content:"\FDB8"}.mdi-more:before{content:"\F37B"}.mdi-mother-nurse:before{content:"\FCFD"}.mdi-motion-sensor:before{content:"\FD6D"}.mdi-motorbike:before{content:"\F37C"}.mdi-mouse:before{content:"\F37D"}.mdi-mouse-bluetooth:before{content:"\F98A"}.mdi-mouse-off:before{content:"\F37E"}.mdi-mouse-variant:before{content:"\F37F"}.mdi-mouse-variant-off:before{content:"\F380"}.mdi-move-resize:before{content:"\F655"}.mdi-move-resize-variant:before{content:"\F656"}.mdi-movie:before{content:"\F381"}.mdi-movie-outline:before{content:"\FDB9"}.mdi-movie-roll:before{content:"\F7DD"}.mdi-muffin:before{content:"\F98B"}.mdi-multiplication:before{content:"\F382"}.mdi-multiplication-box:before{content:"\F383"}.mdi-mushroom:before{content:"\F7DE"}.mdi-mushroom-outline:before{content:"\F7DF"}.mdi-music:before{content:"\F759"}.mdi-music-box:before{content:"\F384"}.mdi-music-box-outline:before{content:"\F385"}.mdi-music-circle:before{content:"\F386"}.mdi-music-circle-outline:before{content:"\FAD3"}.mdi-music-note:before{content:"\F387"}.mdi-music-note-bluetooth:before{content:"\F5FE"}.mdi-music-note-bluetooth-off:before{content:"\F5FF"}.mdi-music-note-eighth:before{content:"\F388"}.mdi-music-note-half:before{content:"\F389"}.mdi-music-note-off:before{content:"\F38A"}.mdi-music-note-plus:before{content:"\FDBA"}.mdi-music-note-quarter:before{content:"\F38B"}.mdi-music-note-sixteenth:before{content:"\F38C"}.mdi-music-note-whole:before{content:"\F38D"}.mdi-music-off:before{content:"\F75A"}.mdi-nail:before{content:"\FDBB"}.mdi-nas:before{content:"\F8F2"}.mdi-nativescript:before{content:"\F87F"}.mdi-nature:before{content:"\F38E"}.mdi-nature-people:before{content:"\F38F"}.mdi-navigation:before{content:"\F390"}.mdi-near-me:before{content:"\F5CD"}.mdi-needle:before{content:"\F391"}.mdi-netflix:before{content:"\F745"}.mdi-network:before{content:"\F6F2"}.mdi-network-off:before{content:"\FC77"}.mdi-network-off-outline:before{content:"\FC78"}.mdi-network-outline:before{content:"\FC79"}.mdi-network-strength-1:before{content:"\F8F3"}.mdi-network-strength-1-alert:before{content:"\F8F4"}.mdi-network-strength-2:before{content:"\F8F5"}.mdi-network-strength-2-alert:before{content:"\F8F6"}.mdi-network-strength-3:before{content:"\F8F7"}.mdi-network-strength-3-alert:before{content:"\F8F8"}.mdi-network-strength-4:before{content:"\F8F9"}.mdi-network-strength-4-alert:before{content:"\F8FA"}.mdi-network-strength-off:before{content:"\F8FB"}.mdi-network-strength-off-outline:before{content:"\F8FC"}.mdi-network-strength-outline:before{content:"\F8FD"}.mdi-new-box:before{content:"\F394"}.mdi-newspaper:before{content:"\F395"}.mdi-nfc:before{content:"\F396"}.mdi-nfc-tap:before{content:"\F397"}.mdi-nfc-variant:before{content:"\F398"}.mdi-ninja:before{content:"\F773"}.mdi-nintendo-switch:before{content:"\F7E0"}.mdi-nodejs:before{content:"\F399"}.mdi-not-equal:before{content:"\F98C"}.mdi-not-equal-variant:before{content:"\F98D"}.mdi-note:before{content:"\F39A"}.mdi-note-multiple:before{content:"\F6B7"}.mdi-note-multiple-outline:before{content:"\F6B8"}.mdi-note-outline:before{content:"\F39B"}.mdi-note-plus:before{content:"\F39C"}.mdi-note-plus-outline:before{content:"\F39D"}.mdi-note-text:before{content:"\F39E"}.mdi-notebook:before{content:"\F82D"}.mdi-notification-clear-all:before{content:"\F39F"}.mdi-npm:before{content:"\F6F6"}.mdi-npm-variant:before{content:"\F98E"}.mdi-npm-variant-outline:before{content:"\F98F"}.mdi-nuke:before{content:"\F6A3"}.mdi-null:before{content:"\F7E1"}.mdi-numeric:before{content:"\F3A0"}.mdi-numeric-0:before{content:"\30"}.mdi-numeric-0-box:before{content:"\F3A1"}.mdi-numeric-0-box-multiple-outline:before{content:"\F3A2"}.mdi-numeric-0-box-outline:before{content:"\F3A3"}.mdi-numeric-0-circle:before{content:"\FC7A"}.mdi-numeric-0-circle-outline:before{content:"\FC7B"}.mdi-numeric-1:before{content:"\31"}.mdi-numeric-1-box:before{content:"\F3A4"}.mdi-numeric-1-box-multiple-outline:before{content:"\F3A5"}.mdi-numeric-1-box-outline:before{content:"\F3A6"}.mdi-numeric-1-circle:before{content:"\FC7C"}.mdi-numeric-1-circle-outline:before{content:"\FC7D"}.mdi-numeric-2:before{content:"\32"}.mdi-numeric-2-box:before{content:"\F3A7"}.mdi-numeric-2-box-multiple-outline:before{content:"\F3A8"}.mdi-numeric-2-box-outline:before{content:"\F3A9"}.mdi-numeric-2-circle:before{content:"\FC7E"}.mdi-numeric-2-circle-outline:before{content:"\FC7F"}.mdi-numeric-3:before{content:"\33"}.mdi-numeric-3-box:before{content:"\F3AA"}.mdi-numeric-3-box-multiple-outline:before{content:"\F3AB"}.mdi-numeric-3-box-outline:before{content:"\F3AC"}.mdi-numeric-3-circle:before{content:"\FC80"}.mdi-numeric-3-circle-outline:before{content:"\FC81"}.mdi-numeric-4:before{content:"\34"}.mdi-numeric-4-box:before{content:"\F3AD"}.mdi-numeric-4-box-multiple-outline:before{content:"\F3AE"}.mdi-numeric-4-box-outline:before{content:"\F3AF"}.mdi-numeric-4-circle:before{content:"\FC82"}.mdi-numeric-4-circle-outline:before{content:"\FC83"}.mdi-numeric-5:before{content:"\35"}.mdi-numeric-5-box:before{content:"\F3B0"}.mdi-numeric-5-box-multiple-outline:before{content:"\F3B1"}.mdi-numeric-5-box-outline:before{content:"\F3B2"}.mdi-numeric-5-circle:before{content:"\FC84"}.mdi-numeric-5-circle-outline:before{content:"\FC85"}.mdi-numeric-6:before{content:"\36"}.mdi-numeric-6-box:before{content:"\F3B3"}.mdi-numeric-6-box-multiple-outline:before{content:"\F3B4"}.mdi-numeric-6-box-outline:before{content:"\F3B5"}.mdi-numeric-6-circle:before{content:"\FC86"}.mdi-numeric-6-circle-outline:before{content:"\FC87"}.mdi-numeric-7:before{content:"\37"}.mdi-numeric-7-box:before{content:"\F3B6"}.mdi-numeric-7-box-multiple-outline:before{content:"\F3B7"}.mdi-numeric-7-box-outline:before{content:"\F3B8"}.mdi-numeric-7-circle:before{content:"\FC88"}.mdi-numeric-7-circle-outline:before{content:"\FC89"}.mdi-numeric-8:before{content:"\38"}.mdi-numeric-8-box:before{content:"\F3B9"}.mdi-numeric-8-box-multiple-outline:before{content:"\F3BA"}.mdi-numeric-8-box-outline:before{content:"\F3BB"}.mdi-numeric-8-circle:before{content:"\FC8A"}.mdi-numeric-8-circle-outline:before{content:"\FC8B"}.mdi-numeric-9:before{content:"\39"}.mdi-numeric-9-box:before{content:"\F3BC"}.mdi-numeric-9-box-multiple-outline:before{content:"\F3BD"}.mdi-numeric-9-box-outline:before{content:"\F3BE"}.mdi-numeric-9-circle:before{content:"\FC8C"}.mdi-numeric-9-circle-outline:before{content:"\FC8D"}.mdi-numeric-9-plus-box:before{content:"\F3BF"}.mdi-numeric-9-plus-box-multiple-outline:before{content:"\F3C0"}.mdi-numeric-9-plus-box-outline:before{content:"\F3C1"}.mdi-numeric-9-plus-circle:before{content:"\FC8E"}.mdi-numeric-9-plus-circle-outline:before{content:"\FC8F"}.mdi-nut:before{content:"\F6F7"}.mdi-nutrition:before{content:"\F3C2"}.mdi-oar:before{content:"\F67B"}.mdi-ocarina:before{content:"\FDBC"}.mdi-octagon:before{content:"\F3C3"}.mdi-octagon-outline:before{content:"\F3C4"}.mdi-octagram:before{content:"\F6F8"}.mdi-octagram-outline:before{content:"\F774"}.mdi-odnoklassniki:before{content:"\F3C5"}.mdi-office:before{content:"\F3C6"}.mdi-office-building:before{content:"\F990"}.mdi-oil:before{content:"\F3C7"}.mdi-oil-temperature:before{content:"\F3C8"}.mdi-omega:before{content:"\F3C9"}.mdi-one-up:before{content:"\FB89"}.mdi-onedrive:before{content:"\F3CA"}.mdi-onenote:before{content:"\F746"}.mdi-onepassword:before{content:"\F880"}.mdi-opacity:before{content:"\F5CC"}.mdi-open-in-app:before{content:"\F3CB"}.mdi-open-in-new:before{content:"\F3CC"}.mdi-open-source-initiative:before{content:"\FB8A"}.mdi-openid:before{content:"\F3CD"}.mdi-opera:before{content:"\F3CE"}.mdi-orbit:before{content:"\F018"}.mdi-origin:before{content:"\FB2B"}.mdi-ornament:before{content:"\F3CF"}.mdi-ornament-variant:before{content:"\F3D0"}.mdi-outlook:before{content:"\FCFE"}.mdi-owl:before{content:"\F3D2"}.mdi-pac-man:before{content:"\FB8B"}.mdi-package:before{content:"\F3D3"}.mdi-package-down:before{content:"\F3D4"}.mdi-package-up:before{content:"\F3D5"}.mdi-package-variant:before{content:"\F3D6"}.mdi-package-variant-closed:before{content:"\F3D7"}.mdi-page-first:before{content:"\F600"}.mdi-page-last:before{content:"\F601"}.mdi-page-layout-body:before{content:"\F6F9"}.mdi-page-layout-footer:before{content:"\F6FA"}.mdi-page-layout-header:before{content:"\F6FB"}.mdi-page-layout-sidebar-left:before{content:"\F6FC"}.mdi-page-layout-sidebar-right:before{content:"\F6FD"}.mdi-page-next:before{content:"\FB8C"}.mdi-page-next-outline:before{content:"\FB8D"}.mdi-page-previous:before{content:"\FB8E"}.mdi-page-previous-outline:before{content:"\FB8F"}.mdi-palette:before{content:"\F3D8"}.mdi-palette-advanced:before{content:"\F3D9"}.mdi-palette-outline:before{content:"\FDE8"}.mdi-palette-swatch:before{content:"\F8B4"}.mdi-pan:before{content:"\FB90"}.mdi-pan-bottom-left:before{content:"\FB91"}.mdi-pan-bottom-right:before{content:"\FB92"}.mdi-pan-down:before{content:"\FB93"}.mdi-pan-horizontal:before{content:"\FB94"}.mdi-pan-left:before{content:"\FB95"}.mdi-pan-right:before{content:"\FB96"}.mdi-pan-top-left:before{content:"\FB97"}.mdi-pan-top-right:before{content:"\FB98"}.mdi-pan-up:before{content:"\FB99"}.mdi-pan-vertical:before{content:"\FB9A"}.mdi-panda:before{content:"\F3DA"}.mdi-pandora:before{content:"\F3DB"}.mdi-panorama:before{content:"\F3DC"}.mdi-panorama-fisheye:before{content:"\F3DD"}.mdi-panorama-horizontal:before{content:"\F3DE"}.mdi-panorama-vertical:before{content:"\F3DF"}.mdi-panorama-wide-angle:before{content:"\F3E0"}.mdi-paper-cut-vertical:before{content:"\F3E1"}.mdi-paperclip:before{content:"\F3E2"}.mdi-parachute:before{content:"\FC90"}.mdi-parachute-outline:before{content:"\FC91"}.mdi-parking:before{content:"\F3E3"}.mdi-passport:before{content:"\F7E2"}.mdi-passport-biometric:before{content:"\FDBD"}.mdi-patreon:before{content:"\F881"}.mdi-pause:before{content:"\F3E4"}.mdi-pause-circle:before{content:"\F3E5"}.mdi-pause-circle-outline:before{content:"\F3E6"}.mdi-pause-octagon:before{content:"\F3E7"}.mdi-pause-octagon-outline:before{content:"\F3E8"}.mdi-paw:before{content:"\F3E9"}.mdi-paw-off:before{content:"\F657"}.mdi-paypal:before{content:"\F882"}.mdi-peace:before{content:"\F883"}.mdi-pen:before{content:"\F3EA"}.mdi-pen-lock:before{content:"\FDBE"}.mdi-pen-minus:before{content:"\FDBF"}.mdi-pen-off:before{content:"\FDC0"}.mdi-pen-plus:before{content:"\FDC1"}.mdi-pen-remove:before{content:"\FDC2"}.mdi-pencil:before{content:"\F3EB"}.mdi-pencil-box:before{content:"\F3EC"}.mdi-pencil-box-outline:before{content:"\F3ED"}.mdi-pencil-circle:before{content:"\F6FE"}.mdi-pencil-circle-outline:before{content:"\F775"}.mdi-pencil-lock:before{content:"\F3EE"}.mdi-pencil-lock-outline:before{content:"\FDC3"}.mdi-pencil-minus:before{content:"\FDC4"}.mdi-pencil-minus-outline:before{content:"\FDC5"}.mdi-pencil-off:before{content:"\F3EF"}.mdi-pencil-off-outline:before{content:"\FDC6"}.mdi-pencil-outline:before{content:"\FC92"}.mdi-pencil-plus:before{content:"\FDC7"}.mdi-pencil-plus-outline:before{content:"\FDC8"}.mdi-pencil-remove:before{content:"\FDC9"}.mdi-pencil-remove-outline:before{content:"\FDCA"}.mdi-pentagon:before{content:"\F6FF"}.mdi-pentagon-outline:before{content:"\F700"}.mdi-percent:before{content:"\F3F0"}.mdi-periodic-table:before{content:"\F8B5"}.mdi-periodic-table-co2:before{content:"\F7E3"}.mdi-periscope:before{content:"\F747"}.mdi-perspective-less:before{content:"\FCFF"}.mdi-perspective-more:before{content:"\FD00"}.mdi-pharmacy:before{content:"\F3F1"}.mdi-phone:before{content:"\F3F2"}.mdi-phone-bluetooth:before{content:"\F3F3"}.mdi-phone-classic:before{content:"\F602"}.mdi-phone-forward:before{content:"\F3F4"}.mdi-phone-hangup:before{content:"\F3F5"}.mdi-phone-in-talk:before{content:"\F3F6"}.mdi-phone-incoming:before{content:"\F3F7"}.mdi-phone-lock:before{content:"\F3F8"}.mdi-phone-log:before{content:"\F3F9"}.mdi-phone-minus:before{content:"\F658"}.mdi-phone-missed:before{content:"\F3FA"}.mdi-phone-off:before{content:"\FDCB"}.mdi-phone-outgoing:before{content:"\F3FB"}.mdi-phone-outline:before{content:"\FDCC"}.mdi-phone-paused:before{content:"\F3FC"}.mdi-phone-plus:before{content:"\F659"}.mdi-phone-return:before{content:"\F82E"}.mdi-phone-rotate-landscape:before{content:"\F884"}.mdi-phone-rotate-portrait:before{content:"\F885"}.mdi-phone-settings:before{content:"\F3FD"}.mdi-phone-voip:before{content:"\F3FE"}.mdi-pi:before{content:"\F3FF"}.mdi-pi-box:before{content:"\F400"}.mdi-pi-hole:before{content:"\FDCD"}.mdi-piano:before{content:"\F67C"}.mdi-pickaxe:before{content:"\F8B6"}.mdi-pier:before{content:"\F886"}.mdi-pier-crane:before{content:"\F887"}.mdi-pig:before{content:"\F401"}.mdi-pill:before{content:"\F402"}.mdi-pillar:before{content:"\F701"}.mdi-pin:before{content:"\F403"}.mdi-pin-off:before{content:"\F404"}.mdi-pin-off-outline:before{content:"\F92F"}.mdi-pin-outline:before{content:"\F930"}.mdi-pine-tree:before{content:"\F405"}.mdi-pine-tree-box:before{content:"\F406"}.mdi-pinterest:before{content:"\F407"}.mdi-pinterest-box:before{content:"\F408"}.mdi-pinwheel:before{content:"\FAD4"}.mdi-pinwheel-outline:before{content:"\FAD5"}.mdi-pipe:before{content:"\F7E4"}.mdi-pipe-disconnected:before{content:"\F7E5"}.mdi-pipe-leak:before{content:"\F888"}.mdi-pirate:before{content:"\FA07"}.mdi-pistol:before{content:"\F702"}.mdi-piston:before{content:"\F889"}.mdi-pizza:before{content:"\F409"}.mdi-play:before{content:"\F40A"}.mdi-play-box-outline:before{content:"\F40B"}.mdi-play-circle:before{content:"\F40C"}.mdi-play-circle-outline:before{content:"\F40D"}.mdi-play-network:before{content:"\F88A"}.mdi-play-network-outline:before{content:"\FC93"}.mdi-play-pause:before{content:"\F40E"}.mdi-play-protected-content:before{content:"\F40F"}.mdi-play-speed:before{content:"\F8FE"}.mdi-playlist-check:before{content:"\F5C7"}.mdi-playlist-edit:before{content:"\F8FF"}.mdi-playlist-minus:before{content:"\F410"}.mdi-playlist-music:before{content:"\FC94"}.mdi-playlist-music-outline:before{content:"\FC95"}.mdi-playlist-play:before{content:"\F411"}.mdi-playlist-plus:before{content:"\F412"}.mdi-playlist-remove:before{content:"\F413"}.mdi-playlist-star:before{content:"\FDCE"}.mdi-playstation:before{content:"\F414"}.mdi-plex:before{content:"\F6B9"}.mdi-plus:before{content:"\F415"}.mdi-plus-box:before{content:"\F416"}.mdi-plus-box-outline:before{content:"\F703"}.mdi-plus-circle:before{content:"\F417"}.mdi-plus-circle-multiple-outline:before{content:"\F418"}.mdi-plus-circle-outline:before{content:"\F419"}.mdi-plus-minus:before{content:"\F991"}.mdi-plus-minus-box:before{content:"\F992"}.mdi-plus-network:before{content:"\F41A"}.mdi-plus-network-outline:before{content:"\FC96"}.mdi-plus-one:before{content:"\F41B"}.mdi-plus-outline:before{content:"\F704"}.mdi-pocket:before{content:"\F41C"}.mdi-podcast:before{content:"\F993"}.mdi-podium:before{content:"\FD01"}.mdi-podium-bronze:before{content:"\FD02"}.mdi-podium-gold:before{content:"\FD03"}.mdi-podium-silver:before{content:"\FD04"}.mdi-point-of-sale:before{content:"\FD6E"}.mdi-pokeball:before{content:"\F41D"}.mdi-pokemon-go:before{content:"\FA08"}.mdi-poker-chip:before{content:"\F82F"}.mdi-polaroid:before{content:"\F41E"}.mdi-poll:before{content:"\F41F"}.mdi-poll-box:before{content:"\F420"}.mdi-polymer:before{content:"\F421"}.mdi-pool:before{content:"\F606"}.mdi-popcorn:before{content:"\F422"}.mdi-postage-stamp:before{content:"\FC97"}.mdi-pot:before{content:"\F65A"}.mdi-pot-mix:before{content:"\F65B"}.mdi-pound:before{content:"\F423"}.mdi-pound-box:before{content:"\F424"}.mdi-power:before{content:"\F425"}.mdi-power-cycle:before{content:"\F900"}.mdi-power-off:before{content:"\F901"}.mdi-power-on:before{content:"\F902"}.mdi-power-plug:before{content:"\F6A4"}.mdi-power-plug-off:before{content:"\F6A5"}.mdi-power-settings:before{content:"\F426"}.mdi-power-sleep:before{content:"\F903"}.mdi-power-socket:before{content:"\F427"}.mdi-power-socket-au:before{content:"\F904"}.mdi-power-socket-eu:before{content:"\F7E6"}.mdi-power-socket-uk:before{content:"\F7E7"}.mdi-power-socket-us:before{content:"\F7E8"}.mdi-power-standby:before{content:"\F905"}.mdi-powershell:before{content:"\FA09"}.mdi-prescription:before{content:"\F705"}.mdi-presentation:before{content:"\F428"}.mdi-presentation-play:before{content:"\F429"}.mdi-printer:before{content:"\F42A"}.mdi-printer-3d:before{content:"\F42B"}.mdi-printer-alert:before{content:"\F42C"}.mdi-printer-settings:before{content:"\F706"}.mdi-printer-wireless:before{content:"\FA0A"}.mdi-priority-high:before{content:"\F603"}.mdi-priority-low:before{content:"\F604"}.mdi-professional-hexagon:before{content:"\F42D"}.mdi-progress-alert:before{content:"\FC98"}.mdi-progress-check:before{content:"\F994"}.mdi-progress-clock:before{content:"\F995"}.mdi-progress-download:before{content:"\F996"}.mdi-progress-upload:before{content:"\F997"}.mdi-progress-wrench:before{content:"\FC99"}.mdi-projector:before{content:"\F42E"}.mdi-projector-screen:before{content:"\F42F"}.mdi-publish:before{content:"\F6A6"}.mdi-pulse:before{content:"\F430"}.mdi-pumpkin:before{content:"\FB9B"}.mdi-puzzle:before{content:"\F431"}.mdi-puzzle-outline:before{content:"\FA65"}.mdi-qi:before{content:"\F998"}.mdi-qqchat:before{content:"\F605"}.mdi-qrcode:before{content:"\F432"}.mdi-qrcode-edit:before{content:"\F8B7"}.mdi-qrcode-scan:before{content:"\F433"}.mdi-quadcopter:before{content:"\F434"}.mdi-quality-high:before{content:"\F435"}.mdi-quality-low:before{content:"\FA0B"}.mdi-quality-medium:before{content:"\FA0C"}.mdi-quicktime:before{content:"\F436"}.mdi-quora:before{content:"\FD05"}.mdi-rabbit:before{content:"\F906"}.mdi-racing-helmet:before{content:"\FD6F"}.mdi-racquetball:before{content:"\FD70"}.mdi-radar:before{content:"\F437"}.mdi-radiator:before{content:"\F438"}.mdi-radiator-disabled:before{content:"\FAD6"}.mdi-radiator-off:before{content:"\FAD7"}.mdi-radio:before{content:"\F439"}.mdi-radio-am:before{content:"\FC9A"}.mdi-radio-fm:before{content:"\FC9B"}.mdi-radio-handheld:before{content:"\F43A"}.mdi-radio-tower:before{content:"\F43B"}.mdi-radioactive:before{content:"\F43C"}.mdi-radiobox-blank:before{content:"\F43D"}.mdi-radiobox-marked:before{content:"\F43E"}.mdi-radius:before{content:"\FC9C"}.mdi-radius-outline:before{content:"\FC9D"}.mdi-raspberry-pi:before{content:"\F43F"}.mdi-ray-end:before{content:"\F440"}.mdi-ray-end-arrow:before{content:"\F441"}.mdi-ray-start:before{content:"\F442"}.mdi-ray-start-arrow:before{content:"\F443"}.mdi-ray-start-end:before{content:"\F444"}.mdi-ray-vertex:before{content:"\F445"}.mdi-react:before{content:"\F707"}.mdi-read:before{content:"\F447"}.mdi-receipt:before{content:"\F449"}.mdi-record:before{content:"\F44A"}.mdi-record-player:before{content:"\F999"}.mdi-record-rec:before{content:"\F44B"}.mdi-recycle:before{content:"\F44C"}.mdi-reddit:before{content:"\F44D"}.mdi-redo:before{content:"\F44E"}.mdi-redo-variant:before{content:"\F44F"}.mdi-reflect-horizontal:before{content:"\FA0D"}.mdi-reflect-vertical:before{content:"\FA0E"}.mdi-refresh:before{content:"\F450"}.mdi-regex:before{content:"\F451"}.mdi-registered-trademark:before{content:"\FA66"}.mdi-relative-scale:before{content:"\F452"}.mdi-reload:before{content:"\F453"}.mdi-reminder:before{content:"\F88B"}.mdi-remote:before{content:"\F454"}.mdi-remote-desktop:before{content:"\F8B8"}.mdi-rename-box:before{content:"\F455"}.mdi-reorder-horizontal:before{content:"\F687"}.mdi-reorder-vertical:before{content:"\F688"}.mdi-repeat:before{content:"\F456"}.mdi-repeat-off:before{content:"\F457"}.mdi-repeat-once:before{content:"\F458"}.mdi-replay:before{content:"\F459"}.mdi-reply:before{content:"\F45A"}.mdi-reply-all:before{content:"\F45B"}.mdi-reproduction:before{content:"\F45C"}.mdi-resistor:before{content:"\FB1F"}.mdi-resistor-nodes:before{content:"\FB20"}.mdi-resize:before{content:"\FA67"}.mdi-resize-bottom-right:before{content:"\F45D"}.mdi-responsive:before{content:"\F45E"}.mdi-restart:before{content:"\F708"}.mdi-restart-off:before{content:"\FD71"}.mdi-restore:before{content:"\F99A"}.mdi-restore-clock:before{content:"\F6A7"}.mdi-rewind:before{content:"\F45F"}.mdi-rewind-10:before{content:"\FD06"}.mdi-rewind-30:before{content:"\FD72"}.mdi-rewind-outline:before{content:"\F709"}.mdi-rhombus:before{content:"\F70A"}.mdi-rhombus-medium:before{content:"\FA0F"}.mdi-rhombus-outline:before{content:"\F70B"}.mdi-rhombus-split:before{content:"\FA10"}.mdi-ribbon:before{content:"\F460"}.mdi-rice:before{content:"\F7E9"}.mdi-ring:before{content:"\F7EA"}.mdi-road:before{content:"\F461"}.mdi-road-variant:before{content:"\F462"}.mdi-robot:before{content:"\F6A8"}.mdi-robot-industrial:before{content:"\FB21"}.mdi-robot-vacuum:before{content:"\F70C"}.mdi-robot-vacuum-variant:before{content:"\F907"}.mdi-rocket:before{content:"\F463"}.mdi-roller-skate:before{content:"\FD07"}.mdi-rollerblade:before{content:"\FD08"}.mdi-rollupjs:before{content:"\FB9C"}.mdi-room-service:before{content:"\F88C"}.mdi-room-service-outline:before{content:"\FD73"}.mdi-rotate-3d:before{content:"\F464"}.mdi-rotate-left:before{content:"\F465"}.mdi-rotate-left-variant:before{content:"\F466"}.mdi-rotate-orbit:before{content:"\FD74"}.mdi-rotate-right:before{content:"\F467"}.mdi-rotate-right-variant:before{content:"\F468"}.mdi-rounded-corner:before{content:"\F607"}.mdi-router-wireless:before{content:"\F469"}.mdi-router-wireless-settings:before{content:"\FA68"}.mdi-routes:before{content:"\F46A"}.mdi-rowing:before{content:"\F608"}.mdi-rss:before{content:"\F46B"}.mdi-rss-box:before{content:"\F46C"}.mdi-ruby:before{content:"\FD09"}.mdi-rugby:before{content:"\FD75"}.mdi-ruler:before{content:"\F46D"}.mdi-ruler-square:before{content:"\FC9E"}.mdi-run:before{content:"\F70D"}.mdi-run-fast:before{content:"\F46E"}.mdi-sack:before{content:"\FD0A"}.mdi-sack-percent:before{content:"\FD0B"}.mdi-safe:before{content:"\FA69"}.mdi-safety-goggles:before{content:"\FD0C"}.mdi-sale:before{content:"\F46F"}.mdi-salesforce:before{content:"\F88D"}.mdi-sass:before{content:"\F7EB"}.mdi-satellite:before{content:"\F470"}.mdi-satellite-uplink:before{content:"\F908"}.mdi-satellite-variant:before{content:"\F471"}.mdi-sausage:before{content:"\F8B9"}.mdi-saxophone:before{content:"\F609"}.mdi-scale:before{content:"\F472"}.mdi-scale-balance:before{content:"\F5D1"}.mdi-scale-bathroom:before{content:"\F473"}.mdi-scanner:before{content:"\F6AA"}.mdi-scanner-off:before{content:"\F909"}.mdi-school:before{content:"\F474"}.mdi-scissors-cutting:before{content:"\FA6A"}.mdi-screen-rotation:before{content:"\F475"}.mdi-screen-rotation-lock:before{content:"\F476"}.mdi-screw-flat-top:before{content:"\FDCF"}.mdi-screw-lag:before{content:"\FDD0"}.mdi-screw-machine-flat-top:before{content:"\FDD1"}.mdi-screw-machine-round-top:before{content:"\FDD2"}.mdi-screw-round-top:before{content:"\FDD3"}.mdi-screwdriver:before{content:"\F477"}.mdi-script:before{content:"\FB9D"}.mdi-script-outline:before{content:"\F478"}.mdi-script-text:before{content:"\FB9E"}.mdi-script-text-outline:before{content:"\FB9F"}.mdi-sd:before{content:"\F479"}.mdi-seal:before{content:"\F47A"}.mdi-search-web:before{content:"\F70E"}.mdi-seat:before{content:"\FC9F"}.mdi-seat-flat:before{content:"\F47B"}.mdi-seat-flat-angled:before{content:"\F47C"}.mdi-seat-individual-suite:before{content:"\F47D"}.mdi-seat-legroom-extra:before{content:"\F47E"}.mdi-seat-legroom-normal:before{content:"\F47F"}.mdi-seat-legroom-reduced:before{content:"\F480"}.mdi-seat-outline:before{content:"\FCA0"}.mdi-seat-recline-extra:before{content:"\F481"}.mdi-seat-recline-normal:before{content:"\F482"}.mdi-seatbelt:before{content:"\FCA1"}.mdi-security:before{content:"\F483"}.mdi-security-network:before{content:"\F484"}.mdi-select:before{content:"\F485"}.mdi-select-all:before{content:"\F486"}.mdi-select-color:before{content:"\FD0D"}.mdi-select-compare:before{content:"\FAD8"}.mdi-select-drag:before{content:"\FA6B"}.mdi-select-inverse:before{content:"\F487"}.mdi-select-off:before{content:"\F488"}.mdi-selection:before{content:"\F489"}.mdi-selection-drag:before{content:"\FA6C"}.mdi-selection-ellipse:before{content:"\FD0E"}.mdi-selection-off:before{content:"\F776"}.mdi-send:before{content:"\F48A"}.mdi-send-circle:before{content:"\FDD4"}.mdi-send-circle-outline:before{content:"\FDD5"}.mdi-send-lock:before{content:"\F7EC"}.mdi-serial-port:before{content:"\F65C"}.mdi-server:before{content:"\F48B"}.mdi-server-minus:before{content:"\F48C"}.mdi-server-network:before{content:"\F48D"}.mdi-server-network-off:before{content:"\F48E"}.mdi-server-off:before{content:"\F48F"}.mdi-server-plus:before{content:"\F490"}.mdi-server-remove:before{content:"\F491"}.mdi-server-security:before{content:"\F492"}.mdi-set-all:before{content:"\F777"}.mdi-set-center:before{content:"\F778"}.mdi-set-center-right:before{content:"\F779"}.mdi-set-left:before{content:"\F77A"}.mdi-set-left-center:before{content:"\F77B"}.mdi-set-left-right:before{content:"\F77C"}.mdi-set-none:before{content:"\F77D"}.mdi-set-right:before{content:"\F77E"}.mdi-set-top-box:before{content:"\F99E"}.mdi-settings:before{content:"\F493"}.mdi-settings-box:before{content:"\F494"}.mdi-settings-helper:before{content:"\FA6D"}.mdi-settings-outline:before{content:"\F8BA"}.mdi-shape:before{content:"\F830"}.mdi-shape-circle-plus:before{content:"\F65D"}.mdi-shape-outline:before{content:"\F831"}.mdi-shape-plus:before{content:"\F495"}.mdi-shape-polygon-plus:before{content:"\F65E"}.mdi-shape-rectangle-plus:before{content:"\F65F"}.mdi-shape-square-plus:before{content:"\F660"}.mdi-share:before{content:"\F496"}.mdi-share-outline:before{content:"\F931"}.mdi-share-variant:before{content:"\F497"}.mdi-sheep:before{content:"\FCA2"}.mdi-shield:before{content:"\F498"}.mdi-shield-account:before{content:"\F88E"}.mdi-shield-account-outline:before{content:"\FA11"}.mdi-shield-airplane:before{content:"\F6BA"}.mdi-shield-airplane-outline:before{content:"\FCA3"}.mdi-shield-check:before{content:"\F565"}.mdi-shield-check-outline:before{content:"\FCA4"}.mdi-shield-cross:before{content:"\FCA5"}.mdi-shield-cross-outline:before{content:"\FCA6"}.mdi-shield-half-full:before{content:"\F77F"}.mdi-shield-home:before{content:"\F689"}.mdi-shield-home-outline:before{content:"\FCA7"}.mdi-shield-key:before{content:"\FBA0"}.mdi-shield-key-outline:before{content:"\FBA1"}.mdi-shield-link-variant:before{content:"\FD0F"}.mdi-shield-link-variant-outline:before{content:"\FD10"}.mdi-shield-lock:before{content:"\F99C"}.mdi-shield-lock-outline:before{content:"\FCA8"}.mdi-shield-off:before{content:"\F99D"}.mdi-shield-off-outline:before{content:"\F99B"}.mdi-shield-outline:before{content:"\F499"}.mdi-shield-plus:before{content:"\FAD9"}.mdi-shield-plus-outline:before{content:"\FADA"}.mdi-shield-remove:before{content:"\FADB"}.mdi-shield-remove-outline:before{content:"\FADC"}.mdi-shield-search:before{content:"\FD76"}.mdi-ship-wheel:before{content:"\F832"}.mdi-shoe-formal:before{content:"\FB22"}.mdi-shoe-heel:before{content:"\FB23"}.mdi-shoe-print:before{content:"\FDD6"}.mdi-shopify:before{content:"\FADD"}.mdi-shopping:before{content:"\F49A"}.mdi-shopping-music:before{content:"\F49B"}.mdi-shovel:before{content:"\F70F"}.mdi-shovel-off:before{content:"\F710"}.mdi-shower:before{content:"\F99F"}.mdi-shower-head:before{content:"\F9A0"}.mdi-shredder:before{content:"\F49C"}.mdi-shuffle:before{content:"\F49D"}.mdi-shuffle-disabled:before{content:"\F49E"}.mdi-shuffle-variant:before{content:"\F49F"}.mdi-sigma:before{content:"\F4A0"}.mdi-sigma-lower:before{content:"\F62B"}.mdi-sign-caution:before{content:"\F4A1"}.mdi-sign-direction:before{content:"\F780"}.mdi-sign-text:before{content:"\F781"}.mdi-signal:before{content:"\F4A2"}.mdi-signal-2g:before{content:"\F711"}.mdi-signal-3g:before{content:"\F712"}.mdi-signal-4g:before{content:"\F713"}.mdi-signal-5g:before{content:"\FA6E"}.mdi-signal-cellular-1:before{content:"\F8BB"}.mdi-signal-cellular-2:before{content:"\F8BC"}.mdi-signal-cellular-3:before{content:"\F8BD"}.mdi-signal-cellular-outline:before{content:"\F8BE"}.mdi-signal-hspa:before{content:"\F714"}.mdi-signal-hspa-plus:before{content:"\F715"}.mdi-signal-off:before{content:"\F782"}.mdi-signal-variant:before{content:"\F60A"}.mdi-signature:before{content:"\FDD7"}.mdi-signature-freehand:before{content:"\FDD8"}.mdi-signature-image:before{content:"\FDD9"}.mdi-signature-text:before{content:"\FDDA"}.mdi-silo:before{content:"\FB24"}.mdi-silverware:before{content:"\F4A3"}.mdi-silverware-fork:before{content:"\F4A4"}.mdi-silverware-fork-knife:before{content:"\FA6F"}.mdi-silverware-spoon:before{content:"\F4A5"}.mdi-silverware-variant:before{content:"\F4A6"}.mdi-sim:before{content:"\F4A7"}.mdi-sim-alert:before{content:"\F4A8"}.mdi-sim-off:before{content:"\F4A9"}.mdi-sina-weibo:before{content:"\FADE"}.mdi-sitemap:before{content:"\F4AA"}.mdi-skate:before{content:"\FD11"}.mdi-skew-less:before{content:"\FD12"}.mdi-skew-more:before{content:"\FD13"}.mdi-skip-backward:before{content:"\F4AB"}.mdi-skip-forward:before{content:"\F4AC"}.mdi-skip-next:before{content:"\F4AD"}.mdi-skip-next-circle:before{content:"\F661"}.mdi-skip-next-circle-outline:before{content:"\F662"}.mdi-skip-previous:before{content:"\F4AE"}.mdi-skip-previous-circle:before{content:"\F663"}.mdi-skip-previous-circle-outline:before{content:"\F664"}.mdi-skull:before{content:"\F68B"}.mdi-skull-crossbones:before{content:"\FBA2"}.mdi-skull-crossbones-outline:before{content:"\FBA3"}.mdi-skull-outline:before{content:"\FBA4"}.mdi-skype:before{content:"\F4AF"}.mdi-skype-business:before{content:"\F4B0"}.mdi-slack:before{content:"\F4B1"}.mdi-slackware:before{content:"\F90A"}.mdi-sleep:before{content:"\F4B2"}.mdi-sleep-off:before{content:"\F4B3"}.mdi-slope-downhill:before{content:"\FDDB"}.mdi-slope-uphill:before{content:"\FDDC"}.mdi-smog:before{content:"\FA70"}.mdi-smoke-detector:before{content:"\F392"}.mdi-smoking:before{content:"\F4B4"}.mdi-smoking-off:before{content:"\F4B5"}.mdi-snapchat:before{content:"\F4B6"}.mdi-snowflake:before{content:"\F716"}.mdi-snowman:before{content:"\F4B7"}.mdi-soccer:before{content:"\F4B8"}.mdi-soccer-field:before{content:"\F833"}.mdi-sofa:before{content:"\F4B9"}.mdi-solar-panel:before{content:"\FD77"}.mdi-solar-panel-large:before{content:"\FD78"}.mdi-solar-power:before{content:"\FA71"}.mdi-solid:before{content:"\F68C"}.mdi-sort:before{content:"\F4BA"}.mdi-sort-alphabetical:before{content:"\F4BB"}.mdi-sort-ascending:before{content:"\F4BC"}.mdi-sort-descending:before{content:"\F4BD"}.mdi-sort-numeric:before{content:"\F4BE"}.mdi-sort-variant:before{content:"\F4BF"}.mdi-sort-variant-lock:before{content:"\FCA9"}.mdi-sort-variant-lock-open:before{content:"\FCAA"}.mdi-soundcloud:before{content:"\F4C0"}.mdi-source-branch:before{content:"\F62C"}.mdi-source-commit:before{content:"\F717"}.mdi-source-commit-end:before{content:"\F718"}.mdi-source-commit-end-local:before{content:"\F719"}.mdi-source-commit-local:before{content:"\F71A"}.mdi-source-commit-next-local:before{content:"\F71B"}.mdi-source-commit-start:before{content:"\F71C"}.mdi-source-commit-start-next-local:before{content:"\F71D"}.mdi-source-fork:before{content:"\F4C1"}.mdi-source-merge:before{content:"\F62D"}.mdi-source-pull:before{content:"\F4C2"}.mdi-source-repository:before{content:"\FCAB"}.mdi-source-repository-multiple:before{content:"\FCAC"}.mdi-soy-sauce:before{content:"\F7ED"}.mdi-spa:before{content:"\FCAD"}.mdi-spa-outline:before{content:"\FCAE"}.mdi-space-invaders:before{content:"\FBA5"}.mdi-speaker:before{content:"\F4C3"}.mdi-speaker-bluetooth:before{content:"\F9A1"}.mdi-speaker-multiple:before{content:"\FD14"}.mdi-speaker-off:before{content:"\F4C4"}.mdi-speaker-wireless:before{content:"\F71E"}.mdi-speedometer:before{content:"\F4C5"}.mdi-spellcheck:before{content:"\F4C6"}.mdi-spider-web:before{content:"\FBA6"}.mdi-spotify:before{content:"\F4C7"}.mdi-spotlight:before{content:"\F4C8"}.mdi-spotlight-beam:before{content:"\F4C9"}.mdi-spray:before{content:"\F665"}.mdi-spray-bottle:before{content:"\FADF"}.mdi-square:before{content:"\F763"}.mdi-square-edit-outline:before{content:"\F90B"}.mdi-square-inc:before{content:"\F4CA"}.mdi-square-inc-cash:before{content:"\F4CB"}.mdi-square-medium:before{content:"\FA12"}.mdi-square-medium-outline:before{content:"\FA13"}.mdi-square-outline:before{content:"\F762"}.mdi-square-root:before{content:"\F783"}.mdi-square-root-box:before{content:"\F9A2"}.mdi-square-small:before{content:"\FA14"}.mdi-squeegee:before{content:"\FAE0"}.mdi-ssh:before{content:"\F8BF"}.mdi-stack-exchange:before{content:"\F60B"}.mdi-stack-overflow:before{content:"\F4CC"}.mdi-stadium:before{content:"\F71F"}.mdi-stairs:before{content:"\F4CD"}.mdi-stamper:before{content:"\FD15"}.mdi-standard-definition:before{content:"\F7EE"}.mdi-star:before{content:"\F4CE"}.mdi-star-box:before{content:"\FA72"}.mdi-star-box-outline:before{content:"\FA73"}.mdi-star-circle:before{content:"\F4CF"}.mdi-star-circle-outline:before{content:"\F9A3"}.mdi-star-face:before{content:"\F9A4"}.mdi-star-four-points:before{content:"\FAE1"}.mdi-star-four-points-outline:before{content:"\FAE2"}.mdi-star-half:before{content:"\F4D0"}.mdi-star-off:before{content:"\F4D1"}.mdi-star-outline:before{content:"\F4D2"}.mdi-star-three-points:before{content:"\FAE3"}.mdi-star-three-points-outline:before{content:"\FAE4"}.mdi-steam:before{content:"\F4D3"}.mdi-steam-box:before{content:"\F90C"}.mdi-steering:before{content:"\F4D4"}.mdi-steering-off:before{content:"\F90D"}.mdi-step-backward:before{content:"\F4D5"}.mdi-step-backward-2:before{content:"\F4D6"}.mdi-step-forward:before{content:"\F4D7"}.mdi-step-forward-2:before{content:"\F4D8"}.mdi-stethoscope:before{content:"\F4D9"}.mdi-sticker:before{content:"\F5D0"}.mdi-sticker-emoji:before{content:"\F784"}.mdi-stocking:before{content:"\F4DA"}.mdi-stop:before{content:"\F4DB"}.mdi-stop-circle:before{content:"\F666"}.mdi-stop-circle-outline:before{content:"\F667"}.mdi-store:before{content:"\F4DC"}.mdi-store-24-hour:before{content:"\F4DD"}.mdi-stove:before{content:"\F4DE"}.mdi-strava:before{content:"\FB25"}.mdi-subdirectory-arrow-left:before{content:"\F60C"}.mdi-subdirectory-arrow-right:before{content:"\F60D"}.mdi-subtitles:before{content:"\FA15"}.mdi-subtitles-outline:before{content:"\FA16"}.mdi-subway:before{content:"\F6AB"}.mdi-subway-alert-variant:before{content:"\FD79"}.mdi-subway-variant:before{content:"\F4DF"}.mdi-summit:before{content:"\F785"}.mdi-sunglasses:before{content:"\F4E0"}.mdi-surround-sound:before{content:"\F5C5"}.mdi-surround-sound-2-0:before{content:"\F7EF"}.mdi-surround-sound-3-1:before{content:"\F7F0"}.mdi-surround-sound-5-1:before{content:"\F7F1"}.mdi-surround-sound-7-1:before{content:"\F7F2"}.mdi-svg:before{content:"\F720"}.mdi-swap-horizontal:before{content:"\F4E1"}.mdi-swap-horizontal-bold:before{content:"\FBA9"}.mdi-swap-horizontal-variant:before{content:"\F8C0"}.mdi-swap-vertical:before{content:"\F4E2"}.mdi-swap-vertical-bold:before{content:"\FBAA"}.mdi-swap-vertical-variant:before{content:"\F8C1"}.mdi-swim:before{content:"\F4E3"}.mdi-switch:before{content:"\F4E4"}.mdi-sword:before{content:"\F4E5"}.mdi-sword-cross:before{content:"\F786"}.mdi-symfony:before{content:"\FAE5"}.mdi-sync:before{content:"\F4E6"}.mdi-sync-alert:before{content:"\F4E7"}.mdi-sync-off:before{content:"\F4E8"}.mdi-tab:before{content:"\F4E9"}.mdi-tab-minus:before{content:"\FB26"}.mdi-tab-plus:before{content:"\F75B"}.mdi-tab-remove:before{content:"\FB27"}.mdi-tab-unselected:before{content:"\F4EA"}.mdi-table:before{content:"\F4EB"}.mdi-table-border:before{content:"\FA17"}.mdi-table-column:before{content:"\F834"}.mdi-table-column-plus-after:before{content:"\F4EC"}.mdi-table-column-plus-before:before{content:"\F4ED"}.mdi-table-column-remove:before{content:"\F4EE"}.mdi-table-column-width:before{content:"\F4EF"}.mdi-table-edit:before{content:"\F4F0"}.mdi-table-large:before{content:"\F4F1"}.mdi-table-merge-cells:before{content:"\F9A5"}.mdi-table-of-contents:before{content:"\F835"}.mdi-table-plus:before{content:"\FA74"}.mdi-table-remove:before{content:"\FA75"}.mdi-table-row:before{content:"\F836"}.mdi-table-row-height:before{content:"\F4F2"}.mdi-table-row-plus-after:before{content:"\F4F3"}.mdi-table-row-plus-before:before{content:"\F4F4"}.mdi-table-row-remove:before{content:"\F4F5"}.mdi-table-search:before{content:"\F90E"}.mdi-table-settings:before{content:"\F837"}.mdi-tablet:before{content:"\F4F6"}.mdi-tablet-android:before{content:"\F4F7"}.mdi-tablet-cellphone:before{content:"\F9A6"}.mdi-tablet-ipad:before{content:"\F4F8"}.mdi-taco:before{content:"\F761"}.mdi-tag:before{content:"\F4F9"}.mdi-tag-faces:before{content:"\F4FA"}.mdi-tag-heart:before{content:"\F68A"}.mdi-tag-heart-outline:before{content:"\FBAB"}.mdi-tag-minus:before{content:"\F90F"}.mdi-tag-multiple:before{content:"\F4FB"}.mdi-tag-outline:before{content:"\F4FC"}.mdi-tag-plus:before{content:"\F721"}.mdi-tag-remove:before{content:"\F722"}.mdi-tag-text-outline:before{content:"\F4FD"}.mdi-tank:before{content:"\FD16"}.mdi-tape-measure:before{content:"\FB28"}.mdi-target:before{content:"\F4FE"}.mdi-target-account:before{content:"\FBAC"}.mdi-target-variant:before{content:"\FA76"}.mdi-taxi:before{content:"\F4FF"}.mdi-tea:before{content:"\FD7A"}.mdi-tea-outline:before{content:"\FD7B"}.mdi-teach:before{content:"\F88F"}.mdi-teamviewer:before{content:"\F500"}.mdi-telegram:before{content:"\F501"}.mdi-telescope:before{content:"\FB29"}.mdi-television:before{content:"\F502"}.mdi-television-box:before{content:"\F838"}.mdi-television-classic:before{content:"\F7F3"}.mdi-television-classic-off:before{content:"\F839"}.mdi-television-guide:before{content:"\F503"}.mdi-television-off:before{content:"\F83A"}.mdi-temperature-celsius:before{content:"\F504"}.mdi-temperature-fahrenheit:before{content:"\F505"}.mdi-temperature-kelvin:before{content:"\F506"}.mdi-tennis:before{content:"\FD7C"}.mdi-tennis-ball:before{content:"\F507"}.mdi-tent:before{content:"\F508"}.mdi-terrain:before{content:"\F509"}.mdi-test-tube:before{content:"\F668"}.mdi-test-tube-empty:before{content:"\F910"}.mdi-test-tube-off:before{content:"\F911"}.mdi-text:before{content:"\F9A7"}.mdi-text-shadow:before{content:"\F669"}.mdi-text-short:before{content:"\F9A8"}.mdi-text-subject:before{content:"\F9A9"}.mdi-text-to-speech:before{content:"\F50A"}.mdi-text-to-speech-off:before{content:"\F50B"}.mdi-textbox:before{content:"\F60E"}.mdi-textbox-password:before{content:"\F7F4"}.mdi-texture:before{content:"\F50C"}.mdi-theater:before{content:"\F50D"}.mdi-theme-light-dark:before{content:"\F50E"}.mdi-thermometer:before{content:"\F50F"}.mdi-thermometer-alert:before{content:"\FDDD"}.mdi-thermometer-chevron-down:before{content:"\FDDE"}.mdi-thermometer-chevron-up:before{content:"\FDDF"}.mdi-thermometer-lines:before{content:"\F510"}.mdi-thermometer-minus:before{content:"\FDE0"}.mdi-thermometer-plus:before{content:"\FDE1"}.mdi-thermostat:before{content:"\F393"}.mdi-thermostat-box:before{content:"\F890"}.mdi-thought-bubble:before{content:"\F7F5"}.mdi-thought-bubble-outline:before{content:"\F7F6"}.mdi-thumb-down:before{content:"\F511"}.mdi-thumb-down-outline:before{content:"\F512"}.mdi-thumb-up:before{content:"\F513"}.mdi-thumb-up-outline:before{content:"\F514"}.mdi-thumbs-up-down:before{content:"\F515"}.mdi-ticket:before{content:"\F516"}.mdi-ticket-account:before{content:"\F517"}.mdi-ticket-confirmation:before{content:"\F518"}.mdi-ticket-outline:before{content:"\F912"}.mdi-ticket-percent:before{content:"\F723"}.mdi-tie:before{content:"\F519"}.mdi-tilde:before{content:"\F724"}.mdi-timelapse:before{content:"\F51A"}.mdi-timeline:before{content:"\FBAD"}.mdi-timeline-outline:before{content:"\FBAE"}.mdi-timeline-text:before{content:"\FBAF"}.mdi-timeline-text-outline:before{content:"\FBB0"}.mdi-timer:before{content:"\F51B"}.mdi-timer-10:before{content:"\F51C"}.mdi-timer-3:before{content:"\F51D"}.mdi-timer-off:before{content:"\F51E"}.mdi-timer-sand:before{content:"\F51F"}.mdi-timer-sand-empty:before{content:"\F6AC"}.mdi-timer-sand-full:before{content:"\F78B"}.mdi-timetable:before{content:"\F520"}.mdi-toaster-oven:before{content:"\FCAF"}.mdi-toggle-switch:before{content:"\F521"}.mdi-toggle-switch-off:before{content:"\F522"}.mdi-toggle-switch-off-outline:before{content:"\FA18"}.mdi-toggle-switch-outline:before{content:"\FA19"}.mdi-toilet:before{content:"\F9AA"}.mdi-toolbox:before{content:"\F9AB"}.mdi-toolbox-outline:before{content:"\F9AC"}.mdi-tooltip:before{content:"\F523"}.mdi-tooltip-account:before{content:"\F00C"}.mdi-tooltip-edit:before{content:"\F524"}.mdi-tooltip-image:before{content:"\F525"}.mdi-tooltip-image-outline:before{content:"\FBB1"}.mdi-tooltip-outline:before{content:"\F526"}.mdi-tooltip-plus:before{content:"\FBB2"}.mdi-tooltip-plus-outline:before{content:"\F527"}.mdi-tooltip-text:before{content:"\F528"}.mdi-tooltip-text-outline:before{content:"\FBB3"}.mdi-tooth:before{content:"\F8C2"}.mdi-tooth-outline:before{content:"\F529"}.mdi-tor:before{content:"\F52A"}.mdi-tortoise:before{content:"\FD17"}.mdi-tournament:before{content:"\F9AD"}.mdi-tower-beach:before{content:"\F680"}.mdi-tower-fire:before{content:"\F681"}.mdi-towing:before{content:"\F83B"}.mdi-track-light:before{content:"\F913"}.mdi-trackpad:before{content:"\F7F7"}.mdi-trackpad-lock:before{content:"\F932"}.mdi-tractor:before{content:"\F891"}.mdi-trademark:before{content:"\FA77"}.mdi-traffic-light:before{content:"\F52B"}.mdi-train:before{content:"\F52C"}.mdi-train-car:before{content:"\FBB4"}.mdi-train-variant:before{content:"\F8C3"}.mdi-tram:before{content:"\F52D"}.mdi-transcribe:before{content:"\F52E"}.mdi-transcribe-close:before{content:"\F52F"}.mdi-transfer-down:before{content:"\FD7D"}.mdi-transfer-left:before{content:"\FD7E"}.mdi-transfer-right:before{content:"\F530"}.mdi-transfer-up:before{content:"\FD7F"}.mdi-transit-connection:before{content:"\FD18"}.mdi-transit-connection-variant:before{content:"\FD19"}.mdi-transit-transfer:before{content:"\F6AD"}.mdi-transition:before{content:"\F914"}.mdi-transition-masked:before{content:"\F915"}.mdi-translate:before{content:"\F5CA"}.mdi-translate-off:before{content:"\FDE2"}.mdi-transmission-tower:before{content:"\FD1A"}.mdi-trash-can:before{content:"\FA78"}.mdi-trash-can-outline:before{content:"\FA79"}.mdi-treasure-chest:before{content:"\F725"}.mdi-tree:before{content:"\F531"}.mdi-trello:before{content:"\F532"}.mdi-trending-down:before{content:"\F533"}.mdi-trending-neutral:before{content:"\F534"}.mdi-trending-up:before{content:"\F535"}.mdi-triangle:before{content:"\F536"}.mdi-triangle-outline:before{content:"\F537"}.mdi-triforce:before{content:"\FBB5"}.mdi-trophy:before{content:"\F538"}.mdi-trophy-award:before{content:"\F539"}.mdi-trophy-broken:before{content:"\FD80"}.mdi-trophy-outline:before{content:"\F53A"}.mdi-trophy-variant:before{content:"\F53B"}.mdi-trophy-variant-outline:before{content:"\F53C"}.mdi-truck:before{content:"\F53D"}.mdi-truck-check:before{content:"\FCB0"}.mdi-truck-delivery:before{content:"\F53E"}.mdi-truck-fast:before{content:"\F787"}.mdi-truck-trailer:before{content:"\F726"}.mdi-tshirt-crew:before{content:"\FA7A"}.mdi-tshirt-crew-outline:before{content:"\F53F"}.mdi-tshirt-v:before{content:"\FA7B"}.mdi-tshirt-v-outline:before{content:"\F540"}.mdi-tumble-dryer:before{content:"\F916"}.mdi-tumblr:before{content:"\F541"}.mdi-tumblr-box:before{content:"\F917"}.mdi-tumblr-reblog:before{content:"\F542"}.mdi-tune:before{content:"\F62E"}.mdi-tune-vertical:before{content:"\F66A"}.mdi-turnstile:before{content:"\FCB1"}.mdi-turnstile-outline:before{content:"\FCB2"}.mdi-turtle:before{content:"\FCB3"}.mdi-twitch:before{content:"\F543"}.mdi-twitter:before{content:"\F544"}.mdi-twitter-box:before{content:"\F545"}.mdi-twitter-circle:before{content:"\F546"}.mdi-twitter-retweet:before{content:"\F547"}.mdi-two-factor-authentication:before{content:"\F9AE"}.mdi-uber:before{content:"\F748"}.mdi-ubisoft:before{content:"\FBB6"}.mdi-ubuntu:before{content:"\F548"}.mdi-ultra-high-definition:before{content:"\F7F8"}.mdi-umbraco:before{content:"\F549"}.mdi-umbrella:before{content:"\F54A"}.mdi-umbrella-closed:before{content:"\F9AF"}.mdi-umbrella-outline:before{content:"\F54B"}.mdi-undo:before{content:"\F54C"}.mdi-undo-variant:before{content:"\F54D"}.mdi-unfold-less-horizontal:before{content:"\F54E"}.mdi-unfold-less-vertical:before{content:"\F75F"}.mdi-unfold-more-horizontal:before{content:"\F54F"}.mdi-unfold-more-vertical:before{content:"\F760"}.mdi-ungroup:before{content:"\F550"}.mdi-unity:before{content:"\F6AE"}.mdi-unreal:before{content:"\F9B0"}.mdi-untappd:before{content:"\F551"}.mdi-update:before{content:"\F6AF"}.mdi-upload:before{content:"\F552"}.mdi-upload-multiple:before{content:"\F83C"}.mdi-upload-network:before{content:"\F6F5"}.mdi-upload-network-outline:before{content:"\FCB4"}.mdi-upload-outline:before{content:"\FDE3"}.mdi-usb:before{content:"\F553"}.mdi-van-passenger:before{content:"\F7F9"}.mdi-van-utility:before{content:"\F7FA"}.mdi-vanish:before{content:"\F7FB"}.mdi-variable:before{content:"\FAE6"}.mdi-vector-arrange-above:before{content:"\F554"}.mdi-vector-arrange-below:before{content:"\F555"}.mdi-vector-bezier:before{content:"\FAE7"}.mdi-vector-circle:before{content:"\F556"}.mdi-vector-circle-variant:before{content:"\F557"}.mdi-vector-combine:before{content:"\F558"}.mdi-vector-curve:before{content:"\F559"}.mdi-vector-difference:before{content:"\F55A"}.mdi-vector-difference-ab:before{content:"\F55B"}.mdi-vector-difference-ba:before{content:"\F55C"}.mdi-vector-ellipse:before{content:"\F892"}.mdi-vector-intersection:before{content:"\F55D"}.mdi-vector-line:before{content:"\F55E"}.mdi-vector-point:before{content:"\F55F"}.mdi-vector-polygon:before{content:"\F560"}.mdi-vector-polyline:before{content:"\F561"}.mdi-vector-radius:before{content:"\F749"}.mdi-vector-rectangle:before{content:"\F5C6"}.mdi-vector-selection:before{content:"\F562"}.mdi-vector-square:before{content:"\F001"}.mdi-vector-triangle:before{content:"\F563"}.mdi-vector-union:before{content:"\F564"}.mdi-venmo:before{content:"\F578"}.mdi-vhs:before{content:"\FA1A"}.mdi-vibrate:before{content:"\F566"}.mdi-vibrate-off:before{content:"\FCB5"}.mdi-video:before{content:"\F567"}.mdi-video-3d:before{content:"\F7FC"}.mdi-video-4k-box:before{content:"\F83D"}.mdi-video-account:before{content:"\F918"}.mdi-video-image:before{content:"\F919"}.mdi-video-input-antenna:before{content:"\F83E"}.mdi-video-input-component:before{content:"\F83F"}.mdi-video-input-hdmi:before{content:"\F840"}.mdi-video-input-svideo:before{content:"\F841"}.mdi-video-minus:before{content:"\F9B1"}.mdi-video-off:before{content:"\F568"}.mdi-video-off-outline:before{content:"\FBB7"}.mdi-video-outline:before{content:"\FBB8"}.mdi-video-plus:before{content:"\F9B2"}.mdi-video-stabilization:before{content:"\F91A"}.mdi-video-switch:before{content:"\F569"}.mdi-video-vintage:before{content:"\FA1B"}.mdi-view-agenda:before{content:"\F56A"}.mdi-view-array:before{content:"\F56B"}.mdi-view-carousel:before{content:"\F56C"}.mdi-view-column:before{content:"\F56D"}.mdi-view-dashboard:before{content:"\F56E"}.mdi-view-dashboard-outline:before{content:"\FA1C"}.mdi-view-dashboard-variant:before{content:"\F842"}.mdi-view-day:before{content:"\F56F"}.mdi-view-grid:before{content:"\F570"}.mdi-view-headline:before{content:"\F571"}.mdi-view-list:before{content:"\F572"}.mdi-view-module:before{content:"\F573"}.mdi-view-parallel:before{content:"\F727"}.mdi-view-quilt:before{content:"\F574"}.mdi-view-sequential:before{content:"\F728"}.mdi-view-split-horizontal:before{content:"\FBA7"}.mdi-view-split-vertical:before{content:"\FBA8"}.mdi-view-stream:before{content:"\F575"}.mdi-view-week:before{content:"\F576"}.mdi-vimeo:before{content:"\F577"}.mdi-violin:before{content:"\F60F"}.mdi-virtual-reality:before{content:"\F893"}.mdi-visual-studio:before{content:"\F610"}.mdi-visual-studio-code:before{content:"\FA1D"}.mdi-vk:before{content:"\F579"}.mdi-vk-box:before{content:"\F57A"}.mdi-vk-circle:before{content:"\F57B"}.mdi-vlc:before{content:"\F57C"}.mdi-voice:before{content:"\F5CB"}.mdi-voicemail:before{content:"\F57D"}.mdi-volleyball:before{content:"\F9B3"}.mdi-volume-high:before{content:"\F57E"}.mdi-volume-low:before{content:"\F57F"}.mdi-volume-medium:before{content:"\F580"}.mdi-volume-minus:before{content:"\F75D"}.mdi-volume-mute:before{content:"\F75E"}.mdi-volume-off:before{content:"\F581"}.mdi-volume-plus:before{content:"\F75C"}.mdi-volume-variant-off:before{content:"\FDE4"}.mdi-vote:before{content:"\FA1E"}.mdi-vote-outline:before{content:"\FA1F"}.mdi-vpn:before{content:"\F582"}.mdi-vuejs:before{content:"\F843"}.mdi-walk:before{content:"\F583"}.mdi-wall:before{content:"\F7FD"}.mdi-wall-sconce:before{content:"\F91B"}.mdi-wall-sconce-flat:before{content:"\F91C"}.mdi-wall-sconce-variant:before{content:"\F91D"}.mdi-wallet:before{content:"\F584"}.mdi-wallet-giftcard:before{content:"\F585"}.mdi-wallet-membership:before{content:"\F586"}.mdi-wallet-outline:before{content:"\FBB9"}.mdi-wallet-travel:before{content:"\F587"}.mdi-wallpaper:before{content:"\FDE5"}.mdi-wan:before{content:"\F588"}.mdi-washing-machine:before{content:"\F729"}.mdi-watch:before{content:"\F589"}.mdi-watch-export:before{content:"\F58A"}.mdi-watch-export-variant:before{content:"\F894"}.mdi-watch-import:before{content:"\F58B"}.mdi-watch-import-variant:before{content:"\F895"}.mdi-watch-variant:before{content:"\F896"}.mdi-watch-vibrate:before{content:"\F6B0"}.mdi-watch-vibrate-off:before{content:"\FCB6"}.mdi-water:before{content:"\F58C"}.mdi-water-off:before{content:"\F58D"}.mdi-water-outline:before{content:"\FDE6"}.mdi-water-percent:before{content:"\F58E"}.mdi-water-pump:before{content:"\F58F"}.mdi-watermark:before{content:"\F612"}.mdi-waves:before{content:"\F78C"}.mdi-waze:before{content:"\FBBA"}.mdi-weather-cloudy:before{content:"\F590"}.mdi-weather-fog:before{content:"\F591"}.mdi-weather-hail:before{content:"\F592"}.mdi-weather-hurricane:before{content:"\F897"}.mdi-weather-lightning:before{content:"\F593"}.mdi-weather-lightning-rainy:before{content:"\F67D"}.mdi-weather-night:before{content:"\F594"}.mdi-weather-partlycloudy:before{content:"\F595"}.mdi-weather-pouring:before{content:"\F596"}.mdi-weather-rainy:before{content:"\F597"}.mdi-weather-snowy:before{content:"\F598"}.mdi-weather-snowy-rainy:before{content:"\F67E"}.mdi-weather-sunny:before{content:"\F599"}.mdi-weather-sunset:before{content:"\F59A"}.mdi-weather-sunset-down:before{content:"\F59B"}.mdi-weather-sunset-up:before{content:"\F59C"}.mdi-weather-windy:before{content:"\F59D"}.mdi-weather-windy-variant:before{content:"\F59E"}.mdi-web:before{content:"\F59F"}.mdi-webcam:before{content:"\F5A0"}.mdi-webhook:before{content:"\F62F"}.mdi-webpack:before{content:"\F72A"}.mdi-wechat:before{content:"\F611"}.mdi-weight:before{content:"\F5A1"}.mdi-weight-gram:before{content:"\FD1B"}.mdi-weight-kilogram:before{content:"\F5A2"}.mdi-weight-pound:before{content:"\F9B4"}.mdi-whatsapp:before{content:"\F5A3"}.mdi-wheelchair-accessibility:before{content:"\F5A4"}.mdi-whistle:before{content:"\F9B5"}.mdi-white-balance-auto:before{content:"\F5A5"}.mdi-white-balance-incandescent:before{content:"\F5A6"}.mdi-white-balance-iridescent:before{content:"\F5A7"}.mdi-white-balance-sunny:before{content:"\F5A8"}.mdi-widgets:before{content:"\F72B"}.mdi-wifi:before{content:"\F5A9"}.mdi-wifi-off:before{content:"\F5AA"}.mdi-wifi-star:before{content:"\FDE7"}.mdi-wifi-strength-1:before{content:"\F91E"}.mdi-wifi-strength-1-alert:before{content:"\F91F"}.mdi-wifi-strength-1-lock:before{content:"\F920"}.mdi-wifi-strength-2:before{content:"\F921"}.mdi-wifi-strength-2-alert:before{content:"\F922"}.mdi-wifi-strength-2-lock:before{content:"\F923"}.mdi-wifi-strength-3:before{content:"\F924"}.mdi-wifi-strength-3-alert:before{content:"\F925"}.mdi-wifi-strength-3-lock:before{content:"\F926"}.mdi-wifi-strength-4:before{content:"\F927"}.mdi-wifi-strength-4-alert:before{content:"\F928"}.mdi-wifi-strength-4-lock:before{content:"\F929"}.mdi-wifi-strength-alert-outline:before{content:"\F92A"}.mdi-wifi-strength-lock-outline:before{content:"\F92B"}.mdi-wifi-strength-off:before{content:"\F92C"}.mdi-wifi-strength-off-outline:before{content:"\F92D"}.mdi-wifi-strength-outline:before{content:"\F92E"}.mdi-wii:before{content:"\F5AB"}.mdi-wiiu:before{content:"\F72C"}.mdi-wikipedia:before{content:"\F5AC"}.mdi-wind-turbine:before{content:"\FD81"}.mdi-window-close:before{content:"\F5AD"}.mdi-window-closed:before{content:"\F5AE"}.mdi-window-maximize:before{content:"\F5AF"}.mdi-window-minimize:before{content:"\F5B0"}.mdi-window-open:before{content:"\F5B1"}.mdi-window-restore:before{content:"\F5B2"}.mdi-windows:before{content:"\F5B3"}.mdi-windows-classic:before{content:"\FA20"}.mdi-wiper:before{content:"\FAE8"}.mdi-wiper-wash:before{content:"\FD82"}.mdi-wordpress:before{content:"\F5B4"}.mdi-worker:before{content:"\F5B5"}.mdi-wrap:before{content:"\F5B6"}.mdi-wrap-disabled:before{content:"\FBBB"}.mdi-wrench:before{content:"\F5B7"}.mdi-wrench-outline:before{content:"\FBBC"}.mdi-wunderlist:before{content:"\F5B8"}.mdi-xamarin:before{content:"\F844"}.mdi-xamarin-outline:before{content:"\F845"}.mdi-xaml:before{content:"\F673"}.mdi-xbox:before{content:"\F5B9"}.mdi-xbox-controller:before{content:"\F5BA"}.mdi-xbox-controller-battery-alert:before{content:"\F74A"}.mdi-xbox-controller-battery-charging:before{content:"\FA21"}.mdi-xbox-controller-battery-empty:before{content:"\F74B"}.mdi-xbox-controller-battery-full:before{content:"\F74C"}.mdi-xbox-controller-battery-low:before{content:"\F74D"}.mdi-xbox-controller-battery-medium:before{content:"\F74E"}.mdi-xbox-controller-battery-unknown:before{content:"\F74F"}.mdi-xbox-controller-off:before{content:"\F5BB"}.mdi-xda:before{content:"\F5BC"}.mdi-xing:before{content:"\F5BD"}.mdi-xing-box:before{content:"\F5BE"}.mdi-xing-circle:before{content:"\F5BF"}.mdi-xml:before{content:"\F5C0"}.mdi-xmpp:before{content:"\F7FE"}.mdi-yahoo:before{content:"\FB2A"}.mdi-yammer:before{content:"\F788"}.mdi-yeast:before{content:"\F5C1"}.mdi-yelp:before{content:"\F5C2"}.mdi-yin-yang:before{content:"\F67F"}.mdi-youtube:before{content:"\F5C3"}.mdi-youtube-creator-studio:before{content:"\F846"}.mdi-youtube-gaming:before{content:"\F847"}.mdi-youtube-subscription:before{content:"\FD1C"}.mdi-youtube-tv:before{content:"\F448"}.mdi-z-wave:before{content:"\FAE9"}.mdi-zend:before{content:"\FAEA"}.mdi-zigbee:before{content:"\FD1D"}.mdi-zip-box:before{content:"\F5C4"}.mdi-zip-disk:before{content:"\FA22"}.mdi-zodiac-aquarius:before{content:"\FA7C"}.mdi-zodiac-aries:before{content:"\FA7D"}.mdi-zodiac-cancer:before{content:"\FA7E"}.mdi-zodiac-capricorn:before{content:"\FA7F"}.mdi-zodiac-gemini:before{content:"\FA80"}.mdi-zodiac-leo:before{content:"\FA81"}.mdi-zodiac-libra:before{content:"\FA82"}.mdi-zodiac-pisces:before{content:"\FA83"}.mdi-zodiac-sagittarius:before{content:"\FA84"}.mdi-zodiac-scorpio:before{content:"\FA85"}.mdi-zodiac-taurus:before{content:"\FA86"}.mdi-zodiac-virgo:before{content:"\FA87"}.mdi-blank:before{content:"\F68C";visibility:hidden}.mdi-18px.mdi-set,.mdi-18px.mdi:before{font-size:18px}.mdi-24px.mdi-set,.mdi-24px.mdi:before{font-size:24px}.mdi-36px.mdi-set,.mdi-36px.mdi:before{font-size:36px}.mdi-48px.mdi-set,.mdi-48px.mdi:before{font-size:48px}.mdi-dark:before{color:rgba(0,0,0,0.54)}.mdi-dark.mdi-inactive:before{color:rgba(0,0,0,0.26)}.mdi-light:before{color:#fff}.mdi-light.mdi-inactive:before{color:rgba(255,255,255,0.3)}.mdi-rotate-45:before{-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.mdi-rotate-90:before{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.mdi-rotate-135:before{-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg)}.mdi-rotate-180:before{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.mdi-rotate-225:before{-webkit-transform:rotate(225deg);-ms-transform:rotate(225deg);transform:rotate(225deg)}.mdi-rotate-270:before{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.mdi-rotate-315:before{-webkit-transform:rotate(315deg);-ms-transform:rotate(315deg);transform:rotate(315deg)}.mdi-flip-h:before{-webkit-transform:scaleX(-1);transform:scaleX(-1);filter:FlipH;-ms-filter:"FlipH"}.mdi-flip-v:before{-webkit-transform:scaleY(-1);transform:scaleY(-1);filter:FlipV;-ms-filter:"FlipV"}.mdi-spin:before{-webkit-animation:mdi-spin 2s infinite linear;animation:mdi-spin 2s infinite linear}@-webkit-keyframes mdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes mdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}
-/*# sourceMappingURL=materialdesignicons.min.css.map */
+/* MaterialDesignIcons.com */@font-face{font-family:"Material Design Icons";src:url("../fonts/materialdesignicons-webfont.eot?v=3.5.95");src:url("../fonts/materialdesignicons-webfont.eot?#iefix&v=3.5.95") format("embedded-opentype"),url("../fonts/materialdesignicons-webfont.woff2?v=3.5.95") format("woff2"),url("../fonts/materialdesignicons-webfont.woff?v=3.5.95") format("woff"),url("../fonts/materialdesignicons-webfont.ttf?v=3.5.95") format("truetype"),url("../fonts/materialdesignicons-webfont.svg?v=3.5.95#materialdesigniconsregular") format("svg");font-weight:normal;font-style:normal}.mdi:before,.mdi-set{display:inline-block;font:normal normal normal 24px/1 "Material Design Icons";font-size:inherit;text-rendering:auto;line-height:inherit;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.mdi-access-point:before{content:"\F002"}.mdi-access-point-network:before{content:"\F003"}.mdi-access-point-network-off:before{content:"\FBBD"}.mdi-account:before{content:"\F004"}.mdi-account-alert:before{content:"\F005"}.mdi-account-alert-outline:before{content:"\FB2C"}.mdi-account-arrow-left:before{content:"\FB2D"}.mdi-account-arrow-left-outline:before{content:"\FB2E"}.mdi-account-arrow-right:before{content:"\FB2F"}.mdi-account-arrow-right-outline:before{content:"\FB30"}.mdi-account-badge:before{content:"\FD83"}.mdi-account-badge-alert:before{content:"\FD84"}.mdi-account-badge-alert-outline:before{content:"\FD85"}.mdi-account-badge-outline:before{content:"\FD86"}.mdi-account-box:before{content:"\F006"}.mdi-account-box-multiple:before{content:"\F933"}.mdi-account-box-outline:before{content:"\F007"}.mdi-account-card-details:before{content:"\F5D2"}.mdi-account-card-details-outline:before{content:"\FD87"}.mdi-account-check:before{content:"\F008"}.mdi-account-check-outline:before{content:"\FBBE"}.mdi-account-child:before{content:"\FA88"}.mdi-account-child-circle:before{content:"\FA89"}.mdi-account-circle:before{content:"\F009"}.mdi-account-circle-outline:before{content:"\FB31"}.mdi-account-clock:before{content:"\FB32"}.mdi-account-clock-outline:before{content:"\FB33"}.mdi-account-convert:before{content:"\F00A"}.mdi-account-details:before{content:"\F631"}.mdi-account-edit:before{content:"\F6BB"}.mdi-account-group:before{content:"\F848"}.mdi-account-group-outline:before{content:"\FB34"}.mdi-account-heart:before{content:"\F898"}.mdi-account-heart-outline:before{content:"\FBBF"}.mdi-account-key:before{content:"\F00B"}.mdi-account-key-outline:before{content:"\FBC0"}.mdi-account-minus:before{content:"\F00D"}.mdi-account-minus-outline:before{content:"\FAEB"}.mdi-account-multiple:before{content:"\F00E"}.mdi-account-multiple-check:before{content:"\F8C4"}.mdi-account-multiple-minus:before{content:"\F5D3"}.mdi-account-multiple-minus-outline:before{content:"\FBC1"}.mdi-account-multiple-outline:before{content:"\F00F"}.mdi-account-multiple-plus:before{content:"\F010"}.mdi-account-multiple-plus-outline:before{content:"\F7FF"}.mdi-account-network:before{content:"\F011"}.mdi-account-network-outline:before{content:"\FBC2"}.mdi-account-off:before{content:"\F012"}.mdi-account-off-outline:before{content:"\FBC3"}.mdi-account-outline:before{content:"\F013"}.mdi-account-plus:before{content:"\F014"}.mdi-account-plus-outline:before{content:"\F800"}.mdi-account-question:before{content:"\FB35"}.mdi-account-question-outline:before{content:"\FB36"}.mdi-account-remove:before{content:"\F015"}.mdi-account-remove-outline:before{content:"\FAEC"}.mdi-account-search:before{content:"\F016"}.mdi-account-search-outline:before{content:"\F934"}.mdi-account-settings:before{content:"\F630"}.mdi-account-star:before{content:"\F017"}.mdi-account-star-outline:before{content:"\FBC4"}.mdi-account-supervisor:before{content:"\FA8A"}.mdi-account-supervisor-circle:before{content:"\FA8B"}.mdi-account-switch:before{content:"\F019"}.mdi-account-tie:before{content:"\FCBF"}.mdi-accusoft:before{content:"\F849"}.mdi-adchoices:before{content:"\FD1E"}.mdi-adjust:before{content:"\F01A"}.mdi-adobe:before{content:"\F935"}.mdi-air-conditioner:before{content:"\F01B"}.mdi-air-filter:before{content:"\FD1F"}.mdi-air-horn:before{content:"\FD88"}.mdi-air-purifier:before{content:"\FD20"}.mdi-airbag:before{content:"\FBC5"}.mdi-airballoon:before{content:"\F01C"}.mdi-airplane:before{content:"\F01D"}.mdi-airplane-landing:before{content:"\F5D4"}.mdi-airplane-off:before{content:"\F01E"}.mdi-airplane-takeoff:before{content:"\F5D5"}.mdi-airplay:before{content:"\F01F"}.mdi-airport:before{content:"\F84A"}.mdi-alarm:before{content:"\F020"}.mdi-alarm-bell:before{content:"\F78D"}.mdi-alarm-check:before{content:"\F021"}.mdi-alarm-light:before{content:"\F78E"}.mdi-alarm-light-outline:before{content:"\FBC6"}.mdi-alarm-multiple:before{content:"\F022"}.mdi-alarm-off:before{content:"\F023"}.mdi-alarm-plus:before{content:"\F024"}.mdi-alarm-snooze:before{content:"\F68D"}.mdi-album:before{content:"\F025"}.mdi-alert:before{content:"\F026"}.mdi-alert-box:before{content:"\F027"}.mdi-alert-box-outline:before{content:"\FCC0"}.mdi-alert-circle:before{content:"\F028"}.mdi-alert-circle-outline:before{content:"\F5D6"}.mdi-alert-decagram:before{content:"\F6BC"}.mdi-alert-decagram-outline:before{content:"\FCC1"}.mdi-alert-octagon:before{content:"\F029"}.mdi-alert-octagon-outline:before{content:"\FCC2"}.mdi-alert-octagram:before{content:"\F766"}.mdi-alert-octagram-outline:before{content:"\FCC3"}.mdi-alert-outline:before{content:"\F02A"}.mdi-alien:before{content:"\F899"}.mdi-all-inclusive:before{content:"\F6BD"}.mdi-alpha:before{content:"\F02B"}.mdi-alpha-a:before{content:"\41"}.mdi-alpha-a-box:before{content:"\FAED"}.mdi-alpha-a-box-outline:before{content:"\FBC7"}.mdi-alpha-a-circle:before{content:"\FBC8"}.mdi-alpha-a-circle-outline:before{content:"\FBC9"}.mdi-alpha-b:before{content:"\42"}.mdi-alpha-b-box:before{content:"\FAEE"}.mdi-alpha-b-box-outline:before{content:"\FBCA"}.mdi-alpha-b-circle:before{content:"\FBCB"}.mdi-alpha-b-circle-outline:before{content:"\FBCC"}.mdi-alpha-c:before{content:"\43"}.mdi-alpha-c-box:before{content:"\FAEF"}.mdi-alpha-c-box-outline:before{content:"\FBCD"}.mdi-alpha-c-circle:before{content:"\FBCE"}.mdi-alpha-c-circle-outline:before{content:"\FBCF"}.mdi-alpha-d:before{content:"\44"}.mdi-alpha-d-box:before{content:"\FAF0"}.mdi-alpha-d-box-outline:before{content:"\FBD0"}.mdi-alpha-d-circle:before{content:"\FBD1"}.mdi-alpha-d-circle-outline:before{content:"\FBD2"}.mdi-alpha-e:before{content:"\45"}.mdi-alpha-e-box:before{content:"\FAF1"}.mdi-alpha-e-box-outline:before{content:"\FBD3"}.mdi-alpha-e-circle:before{content:"\FBD4"}.mdi-alpha-e-circle-outline:before{content:"\FBD5"}.mdi-alpha-f:before{content:"\46"}.mdi-alpha-f-box:before{content:"\FAF2"}.mdi-alpha-f-box-outline:before{content:"\FBD6"}.mdi-alpha-f-circle:before{content:"\FBD7"}.mdi-alpha-f-circle-outline:before{content:"\FBD8"}.mdi-alpha-g:before{content:"\47"}.mdi-alpha-g-box:before{content:"\FAF3"}.mdi-alpha-g-box-outline:before{content:"\FBD9"}.mdi-alpha-g-circle:before{content:"\FBDA"}.mdi-alpha-g-circle-outline:before{content:"\FBDB"}.mdi-alpha-h:before{content:"\48"}.mdi-alpha-h-box:before{content:"\FAF4"}.mdi-alpha-h-box-outline:before{content:"\FBDC"}.mdi-alpha-h-circle:before{content:"\FBDD"}.mdi-alpha-h-circle-outline:before{content:"\FBDE"}.mdi-alpha-i:before{content:"\49"}.mdi-alpha-i-box:before{content:"\FAF5"}.mdi-alpha-i-box-outline:before{content:"\FBDF"}.mdi-alpha-i-circle:before{content:"\FBE0"}.mdi-alpha-i-circle-outline:before{content:"\FBE1"}.mdi-alpha-j:before{content:"\4A"}.mdi-alpha-j-box:before{content:"\FAF6"}.mdi-alpha-j-box-outline:before{content:"\FBE2"}.mdi-alpha-j-circle:before{content:"\FBE3"}.mdi-alpha-j-circle-outline:before{content:"\FBE4"}.mdi-alpha-k:before{content:"\4B"}.mdi-alpha-k-box:before{content:"\FAF7"}.mdi-alpha-k-box-outline:before{content:"\FBE5"}.mdi-alpha-k-circle:before{content:"\FBE6"}.mdi-alpha-k-circle-outline:before{content:"\FBE7"}.mdi-alpha-l:before{content:"\4C"}.mdi-alpha-l-box:before{content:"\FAF8"}.mdi-alpha-l-box-outline:before{content:"\FBE8"}.mdi-alpha-l-circle:before{content:"\FBE9"}.mdi-alpha-l-circle-outline:before{content:"\FBEA"}.mdi-alpha-m:before{content:"\4D"}.mdi-alpha-m-box:before{content:"\FAF9"}.mdi-alpha-m-box-outline:before{content:"\FBEB"}.mdi-alpha-m-circle:before{content:"\FBEC"}.mdi-alpha-m-circle-outline:before{content:"\FBED"}.mdi-alpha-n:before{content:"\4E"}.mdi-alpha-n-box:before{content:"\FAFA"}.mdi-alpha-n-box-outline:before{content:"\FBEE"}.mdi-alpha-n-circle:before{content:"\FBEF"}.mdi-alpha-n-circle-outline:before{content:"\FBF0"}.mdi-alpha-o:before{content:"\4F"}.mdi-alpha-o-box:before{content:"\FAFB"}.mdi-alpha-o-box-outline:before{content:"\FBF1"}.mdi-alpha-o-circle:before{content:"\FBF2"}.mdi-alpha-o-circle-outline:before{content:"\FBF3"}.mdi-alpha-p:before{content:"\50"}.mdi-alpha-p-box:before{content:"\FAFC"}.mdi-alpha-p-box-outline:before{content:"\FBF4"}.mdi-alpha-p-circle:before{content:"\FBF5"}.mdi-alpha-p-circle-outline:before{content:"\FBF6"}.mdi-alpha-q:before{content:"\51"}.mdi-alpha-q-box:before{content:"\FAFD"}.mdi-alpha-q-box-outline:before{content:"\FBF7"}.mdi-alpha-q-circle:before{content:"\FBF8"}.mdi-alpha-q-circle-outline:before{content:"\FBF9"}.mdi-alpha-r:before{content:"\52"}.mdi-alpha-r-box:before{content:"\FAFE"}.mdi-alpha-r-box-outline:before{content:"\FBFA"}.mdi-alpha-r-circle:before{content:"\FBFB"}.mdi-alpha-r-circle-outline:before{content:"\FBFC"}.mdi-alpha-s:before{content:"\53"}.mdi-alpha-s-box:before{content:"\FAFF"}.mdi-alpha-s-box-outline:before{content:"\FBFD"}.mdi-alpha-s-circle:before{content:"\FBFE"}.mdi-alpha-s-circle-outline:before{content:"\FBFF"}.mdi-alpha-t:before{content:"\54"}.mdi-alpha-t-box:before{content:"\FB00"}.mdi-alpha-t-box-outline:before{content:"\FC00"}.mdi-alpha-t-circle:before{content:"\FC01"}.mdi-alpha-t-circle-outline:before{content:"\FC02"}.mdi-alpha-u:before{content:"\55"}.mdi-alpha-u-box:before{content:"\FB01"}.mdi-alpha-u-box-outline:before{content:"\FC03"}.mdi-alpha-u-circle:before{content:"\FC04"}.mdi-alpha-u-circle-outline:before{content:"\FC05"}.mdi-alpha-v:before{content:"\56"}.mdi-alpha-v-box:before{content:"\FB02"}.mdi-alpha-v-box-outline:before{content:"\FC06"}.mdi-alpha-v-circle:before{content:"\FC07"}.mdi-alpha-v-circle-outline:before{content:"\FC08"}.mdi-alpha-w:before{content:"\57"}.mdi-alpha-w-box:before{content:"\FB03"}.mdi-alpha-w-box-outline:before{content:"\FC09"}.mdi-alpha-w-circle:before{content:"\FC0A"}.mdi-alpha-w-circle-outline:before{content:"\FC0B"}.mdi-alpha-x:before{content:"\58"}.mdi-alpha-x-box:before{content:"\FB04"}.mdi-alpha-x-box-outline:before{content:"\FC0C"}.mdi-alpha-x-circle:before{content:"\FC0D"}.mdi-alpha-x-circle-outline:before{content:"\FC0E"}.mdi-alpha-y:before{content:"\59"}.mdi-alpha-y-box:before{content:"\FB05"}.mdi-alpha-y-box-outline:before{content:"\FC0F"}.mdi-alpha-y-circle:before{content:"\FC10"}.mdi-alpha-y-circle-outline:before{content:"\FC11"}.mdi-alpha-z:before{content:"\5A"}.mdi-alpha-z-box:before{content:"\FB06"}.mdi-alpha-z-box-outline:before{content:"\FC12"}.mdi-alpha-z-circle:before{content:"\FC13"}.mdi-alpha-z-circle-outline:before{content:"\FC14"}.mdi-alphabetical:before{content:"\F02C"}.mdi-altimeter:before{content:"\F5D7"}.mdi-amazon:before{content:"\F02D"}.mdi-amazon-alexa:before{content:"\F8C5"}.mdi-amazon-drive:before{content:"\F02E"}.mdi-ambulance:before{content:"\F02F"}.mdi-ammunition:before{content:"\FCC4"}.mdi-ampersand:before{content:"\FA8C"}.mdi-amplifier:before{content:"\F030"}.mdi-anchor:before{content:"\F031"}.mdi-android:before{content:"\F032"}.mdi-android-auto:before{content:"\FA8D"}.mdi-android-debug-bridge:before{content:"\F033"}.mdi-android-head:before{content:"\F78F"}.mdi-android-messages:before{content:"\FD21"}.mdi-android-studio:before{content:"\F034"}.mdi-angle-acute:before{content:"\F936"}.mdi-angle-obtuse:before{content:"\F937"}.mdi-angle-right:before{content:"\F938"}.mdi-angular:before{content:"\F6B1"}.mdi-angularjs:before{content:"\F6BE"}.mdi-animation:before{content:"\F5D8"}.mdi-animation-outline:before{content:"\FA8E"}.mdi-animation-play:before{content:"\F939"}.mdi-animation-play-outline:before{content:"\FA8F"}.mdi-anvil:before{content:"\F89A"}.mdi-apple:before{content:"\F035"}.mdi-apple-finder:before{content:"\F036"}.mdi-apple-icloud:before{content:"\F038"}.mdi-apple-ios:before{content:"\F037"}.mdi-apple-keyboard-caps:before{content:"\F632"}.mdi-apple-keyboard-command:before{content:"\F633"}.mdi-apple-keyboard-control:before{content:"\F634"}.mdi-apple-keyboard-option:before{content:"\F635"}.mdi-apple-keyboard-shift:before{content:"\F636"}.mdi-apple-safari:before{content:"\F039"}.mdi-application:before{content:"\F614"}.mdi-application-export:before{content:"\FD89"}.mdi-application-import:before{content:"\FD8A"}.mdi-apps:before{content:"\F03B"}.mdi-apps-box:before{content:"\FD22"}.mdi-arch:before{content:"\F8C6"}.mdi-archive:before{content:"\F03C"}.mdi-arrange-bring-forward:before{content:"\F03D"}.mdi-arrange-bring-to-front:before{content:"\F03E"}.mdi-arrange-send-backward:before{content:"\F03F"}.mdi-arrange-send-to-back:before{content:"\F040"}.mdi-arrow-all:before{content:"\F041"}.mdi-arrow-bottom-left:before{content:"\F042"}.mdi-arrow-bottom-left-bold-outline:before{content:"\F9B6"}.mdi-arrow-bottom-left-thick:before{content:"\F9B7"}.mdi-arrow-bottom-right:before{content:"\F043"}.mdi-arrow-bottom-right-bold-outline:before{content:"\F9B8"}.mdi-arrow-bottom-right-thick:before{content:"\F9B9"}.mdi-arrow-collapse:before{content:"\F615"}.mdi-arrow-collapse-all:before{content:"\F044"}.mdi-arrow-collapse-down:before{content:"\F791"}.mdi-arrow-collapse-horizontal:before{content:"\F84B"}.mdi-arrow-collapse-left:before{content:"\F792"}.mdi-arrow-collapse-right:before{content:"\F793"}.mdi-arrow-collapse-up:before{content:"\F794"}.mdi-arrow-collapse-vertical:before{content:"\F84C"}.mdi-arrow-decision:before{content:"\F9BA"}.mdi-arrow-decision-auto:before{content:"\F9BB"}.mdi-arrow-decision-auto-outline:before{content:"\F9BC"}.mdi-arrow-decision-outline:before{content:"\F9BD"}.mdi-arrow-down:before{content:"\F045"}.mdi-arrow-down-bold:before{content:"\F72D"}.mdi-arrow-down-bold-box:before{content:"\F72E"}.mdi-arrow-down-bold-box-outline:before{content:"\F72F"}.mdi-arrow-down-bold-circle:before{content:"\F047"}.mdi-arrow-down-bold-circle-outline:before{content:"\F048"}.mdi-arrow-down-bold-hexagon-outline:before{content:"\F049"}.mdi-arrow-down-bold-outline:before{content:"\F9BE"}.mdi-arrow-down-box:before{content:"\F6BF"}.mdi-arrow-down-circle:before{content:"\FCB7"}.mdi-arrow-down-circle-outline:before{content:"\FCB8"}.mdi-arrow-down-drop-circle:before{content:"\F04A"}.mdi-arrow-down-drop-circle-outline:before{content:"\F04B"}.mdi-arrow-down-thick:before{content:"\F046"}.mdi-arrow-expand:before{content:"\F616"}.mdi-arrow-expand-all:before{content:"\F04C"}.mdi-arrow-expand-down:before{content:"\F795"}.mdi-arrow-expand-horizontal:before{content:"\F84D"}.mdi-arrow-expand-left:before{content:"\F796"}.mdi-arrow-expand-right:before{content:"\F797"}.mdi-arrow-expand-up:before{content:"\F798"}.mdi-arrow-expand-vertical:before{content:"\F84E"}.mdi-arrow-left:before{content:"\F04D"}.mdi-arrow-left-bold:before{content:"\F730"}.mdi-arrow-left-bold-box:before{content:"\F731"}.mdi-arrow-left-bold-box-outline:before{content:"\F732"}.mdi-arrow-left-bold-circle:before{content:"\F04F"}.mdi-arrow-left-bold-circle-outline:before{content:"\F050"}.mdi-arrow-left-bold-hexagon-outline:before{content:"\F051"}.mdi-arrow-left-bold-outline:before{content:"\F9BF"}.mdi-arrow-left-box:before{content:"\F6C0"}.mdi-arrow-left-circle:before{content:"\FCB9"}.mdi-arrow-left-circle-outline:before{content:"\FCBA"}.mdi-arrow-left-drop-circle:before{content:"\F052"}.mdi-arrow-left-drop-circle-outline:before{content:"\F053"}.mdi-arrow-left-right-bold-outline:before{content:"\F9C0"}.mdi-arrow-left-thick:before{content:"\F04E"}.mdi-arrow-right:before{content:"\F054"}.mdi-arrow-right-bold:before{content:"\F733"}.mdi-arrow-right-bold-box:before{content:"\F734"}.mdi-arrow-right-bold-box-outline:before{content:"\F735"}.mdi-arrow-right-bold-circle:before{content:"\F056"}.mdi-arrow-right-bold-circle-outline:before{content:"\F057"}.mdi-arrow-right-bold-hexagon-outline:before{content:"\F058"}.mdi-arrow-right-bold-outline:before{content:"\F9C1"}.mdi-arrow-right-box:before{content:"\F6C1"}.mdi-arrow-right-circle:before{content:"\FCBB"}.mdi-arrow-right-circle-outline:before{content:"\FCBC"}.mdi-arrow-right-drop-circle:before{content:"\F059"}.mdi-arrow-right-drop-circle-outline:before{content:"\F05A"}.mdi-arrow-right-thick:before{content:"\F055"}.mdi-arrow-split-horizontal:before{content:"\F93A"}.mdi-arrow-split-vertical:before{content:"\F93B"}.mdi-arrow-top-left:before{content:"\F05B"}.mdi-arrow-top-left-bold-outline:before{content:"\F9C2"}.mdi-arrow-top-left-thick:before{content:"\F9C3"}.mdi-arrow-top-right:before{content:"\F05C"}.mdi-arrow-top-right-bold-outline:before{content:"\F9C4"}.mdi-arrow-top-right-thick:before{content:"\F9C5"}.mdi-arrow-up:before{content:"\F05D"}.mdi-arrow-up-bold:before{content:"\F736"}.mdi-arrow-up-bold-box:before{content:"\F737"}.mdi-arrow-up-bold-box-outline:before{content:"\F738"}.mdi-arrow-up-bold-circle:before{content:"\F05F"}.mdi-arrow-up-bold-circle-outline:before{content:"\F060"}.mdi-arrow-up-bold-hexagon-outline:before{content:"\F061"}.mdi-arrow-up-bold-outline:before{content:"\F9C6"}.mdi-arrow-up-box:before{content:"\F6C2"}.mdi-arrow-up-circle:before{content:"\FCBD"}.mdi-arrow-up-circle-outline:before{content:"\FCBE"}.mdi-arrow-up-down-bold-outline:before{content:"\F9C7"}.mdi-arrow-up-drop-circle:before{content:"\F062"}.mdi-arrow-up-drop-circle-outline:before{content:"\F063"}.mdi-arrow-up-thick:before{content:"\F05E"}.mdi-artist:before{content:"\F802"}.mdi-artist-outline:before{content:"\FCC5"}.mdi-artstation:before{content:"\FB37"}.mdi-aspect-ratio:before{content:"\FA23"}.mdi-assistant:before{content:"\F064"}.mdi-asterisk:before{content:"\F6C3"}.mdi-at:before{content:"\F065"}.mdi-atlassian:before{content:"\F803"}.mdi-atm:before{content:"\FD23"}.mdi-atom:before{content:"\F767"}.mdi-attachment:before{content:"\F066"}.mdi-audio-video:before{content:"\F93C"}.mdi-audiobook:before{content:"\F067"}.mdi-augmented-reality:before{content:"\F84F"}.mdi-auto-fix:before{content:"\F068"}.mdi-auto-upload:before{content:"\F069"}.mdi-autorenew:before{content:"\F06A"}.mdi-av-timer:before{content:"\F06B"}.mdi-axe:before{content:"\F8C7"}.mdi-axis:before{content:"\FD24"}.mdi-axis-arrow:before{content:"\FD25"}.mdi-axis-arrow-lock:before{content:"\FD26"}.mdi-axis-lock:before{content:"\FD27"}.mdi-axis-x-arrow:before{content:"\FD28"}.mdi-axis-x-arrow-lock:before{content:"\FD29"}.mdi-axis-x-rotate-clockwise:before{content:"\FD2A"}.mdi-axis-x-rotate-counterclockwise:before{content:"\FD2B"}.mdi-axis-x-y-arrow-lock:before{content:"\FD2C"}.mdi-axis-y-arrow:before{content:"\FD2D"}.mdi-axis-y-arrow-lock:before{content:"\FD2E"}.mdi-axis-y-rotate-clockwise:before{content:"\FD2F"}.mdi-axis-y-rotate-counterclockwise:before{content:"\FD30"}.mdi-axis-z-arrow:before{content:"\FD31"}.mdi-axis-z-arrow-lock:before{content:"\FD32"}.mdi-axis-z-rotate-clockwise:before{content:"\FD33"}.mdi-axis-z-rotate-counterclockwise:before{content:"\FD34"}.mdi-azure:before{content:"\F804"}.mdi-babel:before{content:"\FA24"}.mdi-baby:before{content:"\F06C"}.mdi-baby-buggy:before{content:"\F68E"}.mdi-backburger:before{content:"\F06D"}.mdi-backspace:before{content:"\F06E"}.mdi-backspace-outline:before{content:"\FB38"}.mdi-backup-restore:before{content:"\F06F"}.mdi-badminton:before{content:"\F850"}.mdi-balloon:before{content:"\FA25"}.mdi-ballot:before{content:"\F9C8"}.mdi-ballot-outline:before{content:"\F9C9"}.mdi-ballot-recount:before{content:"\FC15"}.mdi-ballot-recount-outline:before{content:"\FC16"}.mdi-bandage:before{content:"\FD8B"}.mdi-bandcamp:before{content:"\F674"}.mdi-bank:before{content:"\F070"}.mdi-bank-minus:before{content:"\FD8C"}.mdi-bank-plus:before{content:"\FD8D"}.mdi-bank-remove:before{content:"\FD8E"}.mdi-bank-transfer:before{content:"\FA26"}.mdi-bank-transfer-in:before{content:"\FA27"}.mdi-bank-transfer-out:before{content:"\FA28"}.mdi-barcode:before{content:"\F071"}.mdi-barcode-scan:before{content:"\F072"}.mdi-barley:before{content:"\F073"}.mdi-barley-off:before{content:"\FB39"}.mdi-barn:before{content:"\FB3A"}.mdi-barrel:before{content:"\F074"}.mdi-baseball:before{content:"\F851"}.mdi-baseball-bat:before{content:"\F852"}.mdi-basecamp:before{content:"\F075"}.mdi-basket:before{content:"\F076"}.mdi-basket-fill:before{content:"\F077"}.mdi-basket-unfill:before{content:"\F078"}.mdi-basketball:before{content:"\F805"}.mdi-basketball-hoop:before{content:"\FC17"}.mdi-basketball-hoop-outline:before{content:"\FC18"}.mdi-bat:before{content:"\FB3B"}.mdi-battery:before{content:"\F079"}.mdi-battery-10:before{content:"\F07A"}.mdi-battery-10-bluetooth:before{content:"\F93D"}.mdi-battery-20:before{content:"\F07B"}.mdi-battery-20-bluetooth:before{content:"\F93E"}.mdi-battery-30:before{content:"\F07C"}.mdi-battery-30-bluetooth:before{content:"\F93F"}.mdi-battery-40:before{content:"\F07D"}.mdi-battery-40-bluetooth:before{content:"\F940"}.mdi-battery-50:before{content:"\F07E"}.mdi-battery-50-bluetooth:before{content:"\F941"}.mdi-battery-60:before{content:"\F07F"}.mdi-battery-60-bluetooth:before{content:"\F942"}.mdi-battery-70:before{content:"\F080"}.mdi-battery-70-bluetooth:before{content:"\F943"}.mdi-battery-80:before{content:"\F081"}.mdi-battery-80-bluetooth:before{content:"\F944"}.mdi-battery-90:before{content:"\F082"}.mdi-battery-90-bluetooth:before{content:"\F945"}.mdi-battery-alert:before{content:"\F083"}.mdi-battery-alert-bluetooth:before{content:"\F946"}.mdi-battery-bluetooth:before{content:"\F947"}.mdi-battery-bluetooth-variant:before{content:"\F948"}.mdi-battery-charging:before{content:"\F084"}.mdi-battery-charging-10:before{content:"\F89B"}.mdi-battery-charging-100:before{content:"\F085"}.mdi-battery-charging-20:before{content:"\F086"}.mdi-battery-charging-30:before{content:"\F087"}.mdi-battery-charging-40:before{content:"\F088"}.mdi-battery-charging-50:before{content:"\F89C"}.mdi-battery-charging-60:before{content:"\F089"}.mdi-battery-charging-70:before{content:"\F89D"}.mdi-battery-charging-80:before{content:"\F08A"}.mdi-battery-charging-90:before{content:"\F08B"}.mdi-battery-charging-outline:before{content:"\F89E"}.mdi-battery-charging-wireless:before{content:"\F806"}.mdi-battery-charging-wireless-10:before{content:"\F807"}.mdi-battery-charging-wireless-20:before{content:"\F808"}.mdi-battery-charging-wireless-30:before{content:"\F809"}.mdi-battery-charging-wireless-40:before{content:"\F80A"}.mdi-battery-charging-wireless-50:before{content:"\F80B"}.mdi-battery-charging-wireless-60:before{content:"\F80C"}.mdi-battery-charging-wireless-70:before{content:"\F80D"}.mdi-battery-charging-wireless-80:before{content:"\F80E"}.mdi-battery-charging-wireless-90:before{content:"\F80F"}.mdi-battery-charging-wireless-alert:before{content:"\F810"}.mdi-battery-charging-wireless-outline:before{content:"\F811"}.mdi-battery-minus:before{content:"\F08C"}.mdi-battery-negative:before{content:"\F08D"}.mdi-battery-outline:before{content:"\F08E"}.mdi-battery-plus:before{content:"\F08F"}.mdi-battery-positive:before{content:"\F090"}.mdi-battery-unknown:before{content:"\F091"}.mdi-battery-unknown-bluetooth:before{content:"\F949"}.mdi-battlenet:before{content:"\FB3C"}.mdi-beach:before{content:"\F092"}.mdi-beaker:before{content:"\FCC6"}.mdi-beaker-outline:before{content:"\F68F"}.mdi-beats:before{content:"\F097"}.mdi-bed-empty:before{content:"\F89F"}.mdi-beer:before{content:"\F098"}.mdi-behance:before{content:"\F099"}.mdi-bell:before{content:"\F09A"}.mdi-bell-alert:before{content:"\FD35"}.mdi-bell-circle:before{content:"\FD36"}.mdi-bell-circle-outline:before{content:"\FD37"}.mdi-bell-off:before{content:"\F09B"}.mdi-bell-off-outline:before{content:"\FA90"}.mdi-bell-outline:before{content:"\F09C"}.mdi-bell-plus:before{content:"\F09D"}.mdi-bell-plus-outline:before{content:"\FA91"}.mdi-bell-ring:before{content:"\F09E"}.mdi-bell-ring-outline:before{content:"\F09F"}.mdi-bell-sleep:before{content:"\F0A0"}.mdi-bell-sleep-outline:before{content:"\FA92"}.mdi-beta:before{content:"\F0A1"}.mdi-betamax:before{content:"\F9CA"}.mdi-bible:before{content:"\F0A2"}.mdi-bike:before{content:"\F0A3"}.mdi-billiards:before{content:"\FB3D"}.mdi-billiards-rack:before{content:"\FB3E"}.mdi-bing:before{content:"\F0A4"}.mdi-binoculars:before{content:"\F0A5"}.mdi-bio:before{content:"\F0A6"}.mdi-biohazard:before{content:"\F0A7"}.mdi-bitbucket:before{content:"\F0A8"}.mdi-bitcoin:before{content:"\F812"}.mdi-black-mesa:before{content:"\F0A9"}.mdi-blackberry:before{content:"\F0AA"}.mdi-blender:before{content:"\FCC7"}.mdi-blender-software:before{content:"\F0AB"}.mdi-blinds:before{content:"\F0AC"}.mdi-block-helper:before{content:"\F0AD"}.mdi-blogger:before{content:"\F0AE"}.mdi-blood-bag:before{content:"\FCC8"}.mdi-bluetooth:before{content:"\F0AF"}.mdi-bluetooth-audio:before{content:"\F0B0"}.mdi-bluetooth-connect:before{content:"\F0B1"}.mdi-bluetooth-off:before{content:"\F0B2"}.mdi-bluetooth-settings:before{content:"\F0B3"}.mdi-bluetooth-transfer:before{content:"\F0B4"}.mdi-blur:before{content:"\F0B5"}.mdi-blur-linear:before{content:"\F0B6"}.mdi-blur-off:before{content:"\F0B7"}.mdi-blur-radial:before{content:"\F0B8"}.mdi-bolnisi-cross:before{content:"\FCC9"}.mdi-bolt:before{content:"\FD8F"}.mdi-bomb:before{content:"\F690"}.mdi-bomb-off:before{content:"\F6C4"}.mdi-bone:before{content:"\F0B9"}.mdi-book:before{content:"\F0BA"}.mdi-book-lock:before{content:"\F799"}.mdi-book-lock-open:before{content:"\F79A"}.mdi-book-minus:before{content:"\F5D9"}.mdi-book-multiple:before{content:"\F0BB"}.mdi-book-multiple-minus:before{content:"\FA93"}.mdi-book-multiple-plus:before{content:"\FA94"}.mdi-book-multiple-remove:before{content:"\FA95"}.mdi-book-multiple-variant:before{content:"\F0BC"}.mdi-book-open:before{content:"\F0BD"}.mdi-book-open-outline:before{content:"\FB3F"}.mdi-book-open-page-variant:before{content:"\F5DA"}.mdi-book-open-variant:before{content:"\F0BE"}.mdi-book-outline:before{content:"\FB40"}.mdi-book-plus:before{content:"\F5DB"}.mdi-book-remove:before{content:"\FA96"}.mdi-book-variant:before{content:"\F0BF"}.mdi-bookmark:before{content:"\F0C0"}.mdi-bookmark-check:before{content:"\F0C1"}.mdi-bookmark-minus:before{content:"\F9CB"}.mdi-bookmark-minus-outline:before{content:"\F9CC"}.mdi-bookmark-music:before{content:"\F0C2"}.mdi-bookmark-off:before{content:"\F9CD"}.mdi-bookmark-off-outline:before{content:"\F9CE"}.mdi-bookmark-outline:before{content:"\F0C3"}.mdi-bookmark-plus:before{content:"\F0C5"}.mdi-bookmark-plus-outline:before{content:"\F0C4"}.mdi-bookmark-remove:before{content:"\F0C6"}.mdi-boombox:before{content:"\F5DC"}.mdi-bootstrap:before{content:"\F6C5"}.mdi-border-all:before{content:"\F0C7"}.mdi-border-all-variant:before{content:"\F8A0"}.mdi-border-bottom:before{content:"\F0C8"}.mdi-border-bottom-variant:before{content:"\F8A1"}.mdi-border-color:before{content:"\F0C9"}.mdi-border-horizontal:before{content:"\F0CA"}.mdi-border-inside:before{content:"\F0CB"}.mdi-border-left:before{content:"\F0CC"}.mdi-border-left-variant:before{content:"\F8A2"}.mdi-border-none:before{content:"\F0CD"}.mdi-border-none-variant:before{content:"\F8A3"}.mdi-border-outside:before{content:"\F0CE"}.mdi-border-right:before{content:"\F0CF"}.mdi-border-right-variant:before{content:"\F8A4"}.mdi-border-style:before{content:"\F0D0"}.mdi-border-top:before{content:"\F0D1"}.mdi-border-top-variant:before{content:"\F8A5"}.mdi-border-vertical:before{content:"\F0D2"}.mdi-bottle-wine:before{content:"\F853"}.mdi-bow-tie:before{content:"\F677"}.mdi-bowl:before{content:"\F617"}.mdi-bowling:before{content:"\F0D3"}.mdi-box:before{content:"\F0D4"}.mdi-box-cutter:before{content:"\F0D5"}.mdi-box-shadow:before{content:"\F637"}.mdi-boxing-glove:before{content:"\FB41"}.mdi-braille:before{content:"\F9CF"}.mdi-brain:before{content:"\F9D0"}.mdi-bread-slice:before{content:"\FCCA"}.mdi-bread-slice-outline:before{content:"\FCCB"}.mdi-bridge:before{content:"\F618"}.mdi-briefcase:before{content:"\F0D6"}.mdi-briefcase-account:before{content:"\FCCC"}.mdi-briefcase-account-outline:before{content:"\FCCD"}.mdi-briefcase-check:before{content:"\F0D7"}.mdi-briefcase-download:before{content:"\F0D8"}.mdi-briefcase-download-outline:before{content:"\FC19"}.mdi-briefcase-edit:before{content:"\FA97"}.mdi-briefcase-edit-outline:before{content:"\FC1A"}.mdi-briefcase-minus:before{content:"\FA29"}.mdi-briefcase-minus-outline:before{content:"\FC1B"}.mdi-briefcase-outline:before{content:"\F813"}.mdi-briefcase-plus:before{content:"\FA2A"}.mdi-briefcase-plus-outline:before{content:"\FC1C"}.mdi-briefcase-remove:before{content:"\FA2B"}.mdi-briefcase-remove-outline:before{content:"\FC1D"}.mdi-briefcase-search:before{content:"\FA2C"}.mdi-briefcase-search-outline:before{content:"\FC1E"}.mdi-briefcase-upload:before{content:"\F0D9"}.mdi-briefcase-upload-outline:before{content:"\FC1F"}.mdi-brightness-1:before{content:"\F0DA"}.mdi-brightness-2:before{content:"\F0DB"}.mdi-brightness-3:before{content:"\F0DC"}.mdi-brightness-4:before{content:"\F0DD"}.mdi-brightness-5:before{content:"\F0DE"}.mdi-brightness-6:before{content:"\F0DF"}.mdi-brightness-7:before{content:"\F0E0"}.mdi-brightness-auto:before{content:"\F0E1"}.mdi-brightness-percent:before{content:"\FCCE"}.mdi-broom:before{content:"\F0E2"}.mdi-brush:before{content:"\F0E3"}.mdi-buddhism:before{content:"\F94A"}.mdi-buffer:before{content:"\F619"}.mdi-bug:before{content:"\F0E4"}.mdi-bug-check:before{content:"\FA2D"}.mdi-bug-check-outline:before{content:"\FA2E"}.mdi-bug-outline:before{content:"\FA2F"}.mdi-bugle:before{content:"\FD90"}.mdi-bulldozer:before{content:"\FB07"}.mdi-bullet:before{content:"\FCCF"}.mdi-bulletin-board:before{content:"\F0E5"}.mdi-bullhorn:before{content:"\F0E6"}.mdi-bullhorn-outline:before{content:"\FB08"}.mdi-bullseye:before{content:"\F5DD"}.mdi-bullseye-arrow:before{content:"\F8C8"}.mdi-bus:before{content:"\F0E7"}.mdi-bus-alert:before{content:"\FA98"}.mdi-bus-articulated-end:before{content:"\F79B"}.mdi-bus-articulated-front:before{content:"\F79C"}.mdi-bus-clock:before{content:"\F8C9"}.mdi-bus-double-decker:before{content:"\F79D"}.mdi-bus-school:before{content:"\F79E"}.mdi-bus-side:before{content:"\F79F"}.mdi-cached:before{content:"\F0E8"}.mdi-cactus:before{content:"\FD91"}.mdi-cake:before{content:"\F0E9"}.mdi-cake-layered:before{content:"\F0EA"}.mdi-cake-variant:before{content:"\F0EB"}.mdi-calculator:before{content:"\F0EC"}.mdi-calculator-variant:before{content:"\FA99"}.mdi-calendar:before{content:"\F0ED"}.mdi-calendar-alert:before{content:"\FA30"}.mdi-calendar-blank:before{content:"\F0EE"}.mdi-calendar-blank-outline:before{content:"\FB42"}.mdi-calendar-check:before{content:"\F0EF"}.mdi-calendar-check-outline:before{content:"\FC20"}.mdi-calendar-clock:before{content:"\F0F0"}.mdi-calendar-edit:before{content:"\F8A6"}.mdi-calendar-export:before{content:"\FB09"}.mdi-calendar-heart:before{content:"\F9D1"}.mdi-calendar-import:before{content:"\FB0A"}.mdi-calendar-minus:before{content:"\FD38"}.mdi-calendar-multiple:before{content:"\F0F1"}.mdi-calendar-multiple-check:before{content:"\F0F2"}.mdi-calendar-multiselect:before{content:"\FA31"}.mdi-calendar-outline:before{content:"\FB43"}.mdi-calendar-plus:before{content:"\F0F3"}.mdi-calendar-question:before{content:"\F691"}.mdi-calendar-range:before{content:"\F678"}.mdi-calendar-range-outline:before{content:"\FB44"}.mdi-calendar-remove:before{content:"\F0F4"}.mdi-calendar-remove-outline:before{content:"\FC21"}.mdi-calendar-search:before{content:"\F94B"}.mdi-calendar-star:before{content:"\F9D2"}.mdi-calendar-text:before{content:"\F0F5"}.mdi-calendar-text-outline:before{content:"\FC22"}.mdi-calendar-today:before{content:"\F0F6"}.mdi-calendar-week:before{content:"\FA32"}.mdi-calendar-week-begin:before{content:"\FA33"}.mdi-call-made:before{content:"\F0F7"}.mdi-call-merge:before{content:"\F0F8"}.mdi-call-missed:before{content:"\F0F9"}.mdi-call-received:before{content:"\F0FA"}.mdi-call-split:before{content:"\F0FB"}.mdi-camcorder:before{content:"\F0FC"}.mdi-camcorder-box:before{content:"\F0FD"}.mdi-camcorder-box-off:before{content:"\F0FE"}.mdi-camcorder-off:before{content:"\F0FF"}.mdi-camera:before{content:"\F100"}.mdi-camera-account:before{content:"\F8CA"}.mdi-camera-burst:before{content:"\F692"}.mdi-camera-control:before{content:"\FB45"}.mdi-camera-enhance:before{content:"\F101"}.mdi-camera-enhance-outline:before{content:"\FB46"}.mdi-camera-front:before{content:"\F102"}.mdi-camera-front-variant:before{content:"\F103"}.mdi-camera-gopro:before{content:"\F7A0"}.mdi-camera-image:before{content:"\F8CB"}.mdi-camera-iris:before{content:"\F104"}.mdi-camera-metering-center:before{content:"\F7A1"}.mdi-camera-metering-matrix:before{content:"\F7A2"}.mdi-camera-metering-partial:before{content:"\F7A3"}.mdi-camera-metering-spot:before{content:"\F7A4"}.mdi-camera-off:before{content:"\F5DF"}.mdi-camera-outline:before{content:"\FD39"}.mdi-camera-party-mode:before{content:"\F105"}.mdi-camera-rear:before{content:"\F106"}.mdi-camera-rear-variant:before{content:"\F107"}.mdi-camera-switch:before{content:"\F108"}.mdi-camera-timer:before{content:"\F109"}.mdi-camera-wireless:before{content:"\FD92"}.mdi-camera-wireless-outline:before{content:"\FD93"}.mdi-cancel:before{content:"\F739"}.mdi-candle:before{content:"\F5E2"}.mdi-candycane:before{content:"\F10A"}.mdi-cannabis:before{content:"\F7A5"}.mdi-caps-lock:before{content:"\FA9A"}.mdi-car:before{content:"\F10B"}.mdi-car-battery:before{content:"\F10C"}.mdi-car-brake-abs:before{content:"\FC23"}.mdi-car-brake-alert:before{content:"\FC24"}.mdi-car-brake-hold:before{content:"\FD3A"}.mdi-car-brake-parking:before{content:"\FD3B"}.mdi-car-connected:before{content:"\F10D"}.mdi-car-convertible:before{content:"\F7A6"}.mdi-car-cruise-control:before{content:"\FD3C"}.mdi-car-defrost-front:before{content:"\FD3D"}.mdi-car-defrost-rear:before{content:"\FD3E"}.mdi-car-door:before{content:"\FB47"}.mdi-car-electric:before{content:"\FB48"}.mdi-car-esp:before{content:"\FC25"}.mdi-car-estate:before{content:"\F7A7"}.mdi-car-hatchback:before{content:"\F7A8"}.mdi-car-key:before{content:"\FB49"}.mdi-car-light-dimmed:before{content:"\FC26"}.mdi-car-light-fog:before{content:"\FC27"}.mdi-car-light-high:before{content:"\FC28"}.mdi-car-limousine:before{content:"\F8CC"}.mdi-car-multiple:before{content:"\FB4A"}.mdi-car-parking-lights:before{content:"\FD3F"}.mdi-car-pickup:before{content:"\F7A9"}.mdi-car-side:before{content:"\F7AA"}.mdi-car-sports:before{content:"\F7AB"}.mdi-car-tire-alert:before{content:"\FC29"}.mdi-car-traction-control:before{content:"\FD40"}.mdi-car-wash:before{content:"\F10E"}.mdi-caravan:before{content:"\F7AC"}.mdi-card:before{content:"\FB4B"}.mdi-card-bulleted:before{content:"\FB4C"}.mdi-card-bulleted-off:before{content:"\FB4D"}.mdi-card-bulleted-off-outline:before{content:"\FB4E"}.mdi-card-bulleted-outline:before{content:"\FB4F"}.mdi-card-bulleted-settings:before{content:"\FB50"}.mdi-card-bulleted-settings-outline:before{content:"\FB51"}.mdi-card-outline:before{content:"\FB52"}.mdi-card-text:before{content:"\FB53"}.mdi-card-text-outline:before{content:"\FB54"}.mdi-cards:before{content:"\F638"}.mdi-cards-club:before{content:"\F8CD"}.mdi-cards-diamond:before{content:"\F8CE"}.mdi-cards-heart:before{content:"\F8CF"}.mdi-cards-outline:before{content:"\F639"}.mdi-cards-playing-outline:before{content:"\F63A"}.mdi-cards-spade:before{content:"\F8D0"}.mdi-cards-variant:before{content:"\F6C6"}.mdi-carrot:before{content:"\F10F"}.mdi-carry-on-bag-check:before{content:"\FD41"}.mdi-cart:before{content:"\F110"}.mdi-cart-arrow-down:before{content:"\FD42"}.mdi-cart-arrow-right:before{content:"\FC2A"}.mdi-cart-arrow-up:before{content:"\FD43"}.mdi-cart-minus:before{content:"\FD44"}.mdi-cart-off:before{content:"\F66B"}.mdi-cart-outline:before{content:"\F111"}.mdi-cart-plus:before{content:"\F112"}.mdi-cart-remove:before{content:"\FD45"}.mdi-case-sensitive-alt:before{content:"\F113"}.mdi-cash:before{content:"\F114"}.mdi-cash-100:before{content:"\F115"}.mdi-cash-marker:before{content:"\FD94"}.mdi-cash-multiple:before{content:"\F116"}.mdi-cash-refund:before{content:"\FA9B"}.mdi-cash-register:before{content:"\FCD0"}.mdi-cash-usd:before{content:"\F117"}.mdi-cassette:before{content:"\F9D3"}.mdi-cast:before{content:"\F118"}.mdi-cast-connected:before{content:"\F119"}.mdi-cast-off:before{content:"\F789"}.mdi-castle:before{content:"\F11A"}.mdi-cat:before{content:"\F11B"}.mdi-cctv:before{content:"\F7AD"}.mdi-ceiling-light:before{content:"\F768"}.mdi-cellphone:before{content:"\F11C"}.mdi-cellphone-android:before{content:"\F11D"}.mdi-cellphone-arrow-down:before{content:"\F9D4"}.mdi-cellphone-basic:before{content:"\F11E"}.mdi-cellphone-dock:before{content:"\F11F"}.mdi-cellphone-erase:before{content:"\F94C"}.mdi-cellphone-iphone:before{content:"\F120"}.mdi-cellphone-key:before{content:"\F94D"}.mdi-cellphone-link:before{content:"\F121"}.mdi-cellphone-link-off:before{content:"\F122"}.mdi-cellphone-lock:before{content:"\F94E"}.mdi-cellphone-message:before{content:"\F8D2"}.mdi-cellphone-off:before{content:"\F94F"}.mdi-cellphone-screenshot:before{content:"\FA34"}.mdi-cellphone-settings:before{content:"\F123"}.mdi-cellphone-settings-variant:before{content:"\F950"}.mdi-cellphone-sound:before{content:"\F951"}.mdi-cellphone-text:before{content:"\F8D1"}.mdi-cellphone-wireless:before{content:"\F814"}.mdi-celtic-cross:before{content:"\FCD1"}.mdi-certificate:before{content:"\F124"}.mdi-chair-school:before{content:"\F125"}.mdi-charity:before{content:"\FC2B"}.mdi-chart-arc:before{content:"\F126"}.mdi-chart-areaspline:before{content:"\F127"}.mdi-chart-bar:before{content:"\F128"}.mdi-chart-bar-stacked:before{content:"\F769"}.mdi-chart-bell-curve:before{content:"\FC2C"}.mdi-chart-bubble:before{content:"\F5E3"}.mdi-chart-donut:before{content:"\F7AE"}.mdi-chart-donut-variant:before{content:"\F7AF"}.mdi-chart-gantt:before{content:"\F66C"}.mdi-chart-histogram:before{content:"\F129"}.mdi-chart-line:before{content:"\F12A"}.mdi-chart-line-stacked:before{content:"\F76A"}.mdi-chart-line-variant:before{content:"\F7B0"}.mdi-chart-multiline:before{content:"\F8D3"}.mdi-chart-pie:before{content:"\F12B"}.mdi-chart-scatterplot-hexbin:before{content:"\F66D"}.mdi-chart-timeline:before{content:"\F66E"}.mdi-chat:before{content:"\FB55"}.mdi-chat-alert:before{content:"\FB56"}.mdi-chat-processing:before{content:"\FB57"}.mdi-check:before{content:"\F12C"}.mdi-check-all:before{content:"\F12D"}.mdi-check-box-multiple-outline:before{content:"\FC2D"}.mdi-check-box-outline:before{content:"\FC2E"}.mdi-check-circle:before{content:"\F5E0"}.mdi-check-circle-outline:before{content:"\F5E1"}.mdi-check-decagram:before{content:"\F790"}.mdi-check-network:before{content:"\FC2F"}.mdi-check-network-outline:before{content:"\FC30"}.mdi-check-outline:before{content:"\F854"}.mdi-checkbook:before{content:"\FA9C"}.mdi-checkbox-blank:before{content:"\F12E"}.mdi-checkbox-blank-circle:before{content:"\F12F"}.mdi-checkbox-blank-circle-outline:before{content:"\F130"}.mdi-checkbox-blank-outline:before{content:"\F131"}.mdi-checkbox-intermediate:before{content:"\F855"}.mdi-checkbox-marked:before{content:"\F132"}.mdi-checkbox-marked-circle:before{content:"\F133"}.mdi-checkbox-marked-circle-outline:before{content:"\F134"}.mdi-checkbox-marked-outline:before{content:"\F135"}.mdi-checkbox-multiple-blank:before{content:"\F136"}.mdi-checkbox-multiple-blank-circle:before{content:"\F63B"}.mdi-checkbox-multiple-blank-circle-outline:before{content:"\F63C"}.mdi-checkbox-multiple-blank-outline:before{content:"\F137"}.mdi-checkbox-multiple-marked:before{content:"\F138"}.mdi-checkbox-multiple-marked-circle:before{content:"\F63D"}.mdi-checkbox-multiple-marked-circle-outline:before{content:"\F63E"}.mdi-checkbox-multiple-marked-outline:before{content:"\F139"}.mdi-checkerboard:before{content:"\F13A"}.mdi-chef-hat:before{content:"\FB58"}.mdi-chemical-weapon:before{content:"\F13B"}.mdi-chess-bishop:before{content:"\F85B"}.mdi-chess-king:before{content:"\F856"}.mdi-chess-knight:before{content:"\F857"}.mdi-chess-pawn:before{content:"\F858"}.mdi-chess-queen:before{content:"\F859"}.mdi-chess-rook:before{content:"\F85A"}.mdi-chevron-double-down:before{content:"\F13C"}.mdi-chevron-double-left:before{content:"\F13D"}.mdi-chevron-double-right:before{content:"\F13E"}.mdi-chevron-double-up:before{content:"\F13F"}.mdi-chevron-down:before{content:"\F140"}.mdi-chevron-down-box:before{content:"\F9D5"}.mdi-chevron-down-box-outline:before{content:"\F9D6"}.mdi-chevron-down-circle:before{content:"\FB0B"}.mdi-chevron-down-circle-outline:before{content:"\FB0C"}.mdi-chevron-left:before{content:"\F141"}.mdi-chevron-left-box:before{content:"\F9D7"}.mdi-chevron-left-box-outline:before{content:"\F9D8"}.mdi-chevron-left-circle:before{content:"\FB0D"}.mdi-chevron-left-circle-outline:before{content:"\FB0E"}.mdi-chevron-right:before{content:"\F142"}.mdi-chevron-right-box:before{content:"\F9D9"}.mdi-chevron-right-box-outline:before{content:"\F9DA"}.mdi-chevron-right-circle:before{content:"\FB0F"}.mdi-chevron-right-circle-outline:before{content:"\FB10"}.mdi-chevron-triple-down:before{content:"\FD95"}.mdi-chevron-triple-left:before{content:"\FD96"}.mdi-chevron-triple-right:before{content:"\FD97"}.mdi-chevron-triple-up:before{content:"\FD98"}.mdi-chevron-up:before{content:"\F143"}.mdi-chevron-up-box:before{content:"\F9DB"}.mdi-chevron-up-box-outline:before{content:"\F9DC"}.mdi-chevron-up-circle:before{content:"\FB11"}.mdi-chevron-up-circle-outline:before{content:"\FB12"}.mdi-chili-hot:before{content:"\F7B1"}.mdi-chili-medium:before{content:"\F7B2"}.mdi-chili-mild:before{content:"\F7B3"}.mdi-chip:before{content:"\F61A"}.mdi-christianity:before{content:"\F952"}.mdi-christianity-outline:before{content:"\FCD2"}.mdi-church:before{content:"\F144"}.mdi-circle:before{content:"\F764"}.mdi-circle-edit-outline:before{content:"\F8D4"}.mdi-circle-medium:before{content:"\F9DD"}.mdi-circle-outline:before{content:"\F765"}.mdi-circle-slice-1:before{content:"\FA9D"}.mdi-circle-slice-2:before{content:"\FA9E"}.mdi-circle-slice-3:before{content:"\FA9F"}.mdi-circle-slice-4:before{content:"\FAA0"}.mdi-circle-slice-5:before{content:"\FAA1"}.mdi-circle-slice-6:before{content:"\FAA2"}.mdi-circle-slice-7:before{content:"\FAA3"}.mdi-circle-slice-8:before{content:"\FAA4"}.mdi-circle-small:before{content:"\F9DE"}.mdi-cisco-webex:before{content:"\F145"}.mdi-city:before{content:"\F146"}.mdi-city-variant:before{content:"\FA35"}.mdi-city-variant-outline:before{content:"\FA36"}.mdi-clipboard:before{content:"\F147"}.mdi-clipboard-account:before{content:"\F148"}.mdi-clipboard-account-outline:before{content:"\FC31"}.mdi-clipboard-alert:before{content:"\F149"}.mdi-clipboard-alert-outline:before{content:"\FCD3"}.mdi-clipboard-arrow-down:before{content:"\F14A"}.mdi-clipboard-arrow-down-outline:before{content:"\FC32"}.mdi-clipboard-arrow-left:before{content:"\F14B"}.mdi-clipboard-arrow-left-outline:before{content:"\FCD4"}.mdi-clipboard-arrow-right:before{content:"\FCD5"}.mdi-clipboard-arrow-right-outline:before{content:"\FCD6"}.mdi-clipboard-arrow-up:before{content:"\FC33"}.mdi-clipboard-arrow-up-outline:before{content:"\FC34"}.mdi-clipboard-check:before{content:"\F14C"}.mdi-clipboard-check-outline:before{content:"\F8A7"}.mdi-clipboard-flow:before{content:"\F6C7"}.mdi-clipboard-outline:before{content:"\F14D"}.mdi-clipboard-play:before{content:"\FC35"}.mdi-clipboard-play-outline:before{content:"\FC36"}.mdi-clipboard-plus:before{content:"\F750"}.mdi-clipboard-pulse:before{content:"\F85C"}.mdi-clipboard-pulse-outline:before{content:"\F85D"}.mdi-clipboard-text:before{content:"\F14E"}.mdi-clipboard-text-outline:before{content:"\FA37"}.mdi-clipboard-text-play:before{content:"\FC37"}.mdi-clipboard-text-play-outline:before{content:"\FC38"}.mdi-clippy:before{content:"\F14F"}.mdi-clock:before{content:"\F953"}.mdi-clock-alert:before{content:"\F954"}.mdi-clock-alert-outline:before{content:"\F5CE"}.mdi-clock-end:before{content:"\F151"}.mdi-clock-fast:before{content:"\F152"}.mdi-clock-in:before{content:"\F153"}.mdi-clock-out:before{content:"\F154"}.mdi-clock-outline:before{content:"\F150"}.mdi-clock-start:before{content:"\F155"}.mdi-close:before{content:"\F156"}.mdi-close-box:before{content:"\F157"}.mdi-close-box-multiple:before{content:"\FC39"}.mdi-close-box-multiple-outline:before{content:"\FC3A"}.mdi-close-box-outline:before{content:"\F158"}.mdi-close-circle:before{content:"\F159"}.mdi-close-circle-outline:before{content:"\F15A"}.mdi-close-network:before{content:"\F15B"}.mdi-close-network-outline:before{content:"\FC3B"}.mdi-close-octagon:before{content:"\F15C"}.mdi-close-octagon-outline:before{content:"\F15D"}.mdi-close-outline:before{content:"\F6C8"}.mdi-closed-caption:before{content:"\F15E"}.mdi-closed-caption-outline:before{content:"\FD99"}.mdi-cloud:before{content:"\F15F"}.mdi-cloud-alert:before{content:"\F9DF"}.mdi-cloud-braces:before{content:"\F7B4"}.mdi-cloud-check:before{content:"\F160"}.mdi-cloud-circle:before{content:"\F161"}.mdi-cloud-download:before{content:"\F162"}.mdi-cloud-download-outline:before{content:"\FB59"}.mdi-cloud-off-outline:before{content:"\F164"}.mdi-cloud-outline:before{content:"\F163"}.mdi-cloud-print:before{content:"\F165"}.mdi-cloud-print-outline:before{content:"\F166"}.mdi-cloud-question:before{content:"\FA38"}.mdi-cloud-search:before{content:"\F955"}.mdi-cloud-search-outline:before{content:"\F956"}.mdi-cloud-sync:before{content:"\F63F"}.mdi-cloud-tags:before{content:"\F7B5"}.mdi-cloud-upload:before{content:"\F167"}.mdi-cloud-upload-outline:before{content:"\FB5A"}.mdi-clover:before{content:"\F815"}.mdi-code-array:before{content:"\F168"}.mdi-code-braces:before{content:"\F169"}.mdi-code-brackets:before{content:"\F16A"}.mdi-code-equal:before{content:"\F16B"}.mdi-code-greater-than:before{content:"\F16C"}.mdi-code-greater-than-or-equal:before{content:"\F16D"}.mdi-code-less-than:before{content:"\F16E"}.mdi-code-less-than-or-equal:before{content:"\F16F"}.mdi-code-not-equal:before{content:"\F170"}.mdi-code-not-equal-variant:before{content:"\F171"}.mdi-code-parentheses:before{content:"\F172"}.mdi-code-string:before{content:"\F173"}.mdi-code-tags:before{content:"\F174"}.mdi-code-tags-check:before{content:"\F693"}.mdi-codepen:before{content:"\F175"}.mdi-coffee:before{content:"\F176"}.mdi-coffee-outline:before{content:"\F6C9"}.mdi-coffee-to-go:before{content:"\F177"}.mdi-coffin:before{content:"\FB5B"}.mdi-cogs:before{content:"\F8D5"}.mdi-coin:before{content:"\F178"}.mdi-coins:before{content:"\F694"}.mdi-collage:before{content:"\F640"}.mdi-collapse-all:before{content:"\FAA5"}.mdi-collapse-all-outline:before{content:"\FAA6"}.mdi-color-helper:before{content:"\F179"}.mdi-comment:before{content:"\F17A"}.mdi-comment-account:before{content:"\F17B"}.mdi-comment-account-outline:before{content:"\F17C"}.mdi-comment-alert:before{content:"\F17D"}.mdi-comment-alert-outline:before{content:"\F17E"}.mdi-comment-arrow-left:before{content:"\F9E0"}.mdi-comment-arrow-left-outline:before{content:"\F9E1"}.mdi-comment-arrow-right:before{content:"\F9E2"}.mdi-comment-arrow-right-outline:before{content:"\F9E3"}.mdi-comment-check:before{content:"\F17F"}.mdi-comment-check-outline:before{content:"\F180"}.mdi-comment-eye:before{content:"\FA39"}.mdi-comment-eye-outline:before{content:"\FA3A"}.mdi-comment-multiple:before{content:"\F85E"}.mdi-comment-multiple-outline:before{content:"\F181"}.mdi-comment-outline:before{content:"\F182"}.mdi-comment-plus:before{content:"\F9E4"}.mdi-comment-plus-outline:before{content:"\F183"}.mdi-comment-processing:before{content:"\F184"}.mdi-comment-processing-outline:before{content:"\F185"}.mdi-comment-question:before{content:"\F816"}.mdi-comment-question-outline:before{content:"\F186"}.mdi-comment-remove:before{content:"\F5DE"}.mdi-comment-remove-outline:before{content:"\F187"}.mdi-comment-search:before{content:"\FA3B"}.mdi-comment-search-outline:before{content:"\FA3C"}.mdi-comment-text:before{content:"\F188"}.mdi-comment-text-multiple:before{content:"\F85F"}.mdi-comment-text-multiple-outline:before{content:"\F860"}.mdi-comment-text-outline:before{content:"\F189"}.mdi-compare:before{content:"\F18A"}.mdi-compass:before{content:"\F18B"}.mdi-compass-off:before{content:"\FB5C"}.mdi-compass-off-outline:before{content:"\FB5D"}.mdi-compass-outline:before{content:"\F18C"}.mdi-console:before{content:"\F18D"}.mdi-console-line:before{content:"\F7B6"}.mdi-console-network:before{content:"\F8A8"}.mdi-console-network-outline:before{content:"\FC3C"}.mdi-contact-mail:before{content:"\F18E"}.mdi-contactless-payment:before{content:"\FD46"}.mdi-contacts:before{content:"\F6CA"}.mdi-contain:before{content:"\FA3D"}.mdi-contain-end:before{content:"\FA3E"}.mdi-contain-start:before{content:"\FA3F"}.mdi-content-copy:before{content:"\F18F"}.mdi-content-cut:before{content:"\F190"}.mdi-content-duplicate:before{content:"\F191"}.mdi-content-paste:before{content:"\F192"}.mdi-content-save:before{content:"\F193"}.mdi-content-save-all:before{content:"\F194"}.mdi-content-save-edit:before{content:"\FCD7"}.mdi-content-save-edit-outline:before{content:"\FCD8"}.mdi-content-save-outline:before{content:"\F817"}.mdi-content-save-settings:before{content:"\F61B"}.mdi-content-save-settings-outline:before{content:"\FB13"}.mdi-contrast:before{content:"\F195"}.mdi-contrast-box:before{content:"\F196"}.mdi-contrast-circle:before{content:"\F197"}.mdi-controller-classic:before{content:"\FB5E"}.mdi-controller-classic-outline:before{content:"\FB5F"}.mdi-cookie:before{content:"\F198"}.mdi-copyright:before{content:"\F5E6"}.mdi-cordova:before{content:"\F957"}.mdi-corn:before{content:"\F7B7"}.mdi-counter:before{content:"\F199"}.mdi-cow:before{content:"\F19A"}.mdi-crane:before{content:"\F861"}.mdi-creation:before{content:"\F1C9"}.mdi-creative-commons:before{content:"\FD47"}.mdi-credit-card:before{content:"\F19B"}.mdi-credit-card-marker:before{content:"\FD9A"}.mdi-credit-card-multiple:before{content:"\F19C"}.mdi-credit-card-off:before{content:"\F5E4"}.mdi-credit-card-plus:before{content:"\F675"}.mdi-credit-card-refund:before{content:"\FAA7"}.mdi-credit-card-scan:before{content:"\F19D"}.mdi-credit-card-settings:before{content:"\F8D6"}.mdi-credit-card-wireless:before{content:"\FD48"}.mdi-cricket:before{content:"\FD49"}.mdi-crop:before{content:"\F19E"}.mdi-crop-free:before{content:"\F19F"}.mdi-crop-landscape:before{content:"\F1A0"}.mdi-crop-portrait:before{content:"\F1A1"}.mdi-crop-rotate:before{content:"\F695"}.mdi-crop-square:before{content:"\F1A2"}.mdi-crosshairs:before{content:"\F1A3"}.mdi-crosshairs-gps:before{content:"\F1A4"}.mdi-crown:before{content:"\F1A5"}.mdi-cryengine:before{content:"\F958"}.mdi-crystal-ball:before{content:"\FB14"}.mdi-cube:before{content:"\F1A6"}.mdi-cube-outline:before{content:"\F1A7"}.mdi-cube-scan:before{content:"\FB60"}.mdi-cube-send:before{content:"\F1A8"}.mdi-cube-unfolded:before{content:"\F1A9"}.mdi-cup:before{content:"\F1AA"}.mdi-cup-off:before{content:"\F5E5"}.mdi-cup-water:before{content:"\F1AB"}.mdi-cupcake:before{content:"\F959"}.mdi-curling:before{content:"\F862"}.mdi-currency-bdt:before{content:"\F863"}.mdi-currency-brl:before{content:"\FB61"}.mdi-currency-btc:before{content:"\F1AC"}.mdi-currency-chf:before{content:"\F7B8"}.mdi-currency-cny:before{content:"\F7B9"}.mdi-currency-eth:before{content:"\F7BA"}.mdi-currency-eur:before{content:"\F1AD"}.mdi-currency-gbp:before{content:"\F1AE"}.mdi-currency-ils:before{content:"\FC3D"}.mdi-currency-inr:before{content:"\F1AF"}.mdi-currency-jpy:before{content:"\F7BB"}.mdi-currency-krw:before{content:"\F7BC"}.mdi-currency-kzt:before{content:"\F864"}.mdi-currency-ngn:before{content:"\F1B0"}.mdi-currency-php:before{content:"\F9E5"}.mdi-currency-rub:before{content:"\F1B1"}.mdi-currency-sign:before{content:"\F7BD"}.mdi-currency-try:before{content:"\F1B2"}.mdi-currency-twd:before{content:"\F7BE"}.mdi-currency-usd:before{content:"\F1B3"}.mdi-currency-usd-off:before{content:"\F679"}.mdi-current-ac:before{content:"\F95A"}.mdi-current-dc:before{content:"\F95B"}.mdi-cursor-default:before{content:"\F1B4"}.mdi-cursor-default-click:before{content:"\FCD9"}.mdi-cursor-default-click-outline:before{content:"\FCDA"}.mdi-cursor-default-outline:before{content:"\F1B5"}.mdi-cursor-move:before{content:"\F1B6"}.mdi-cursor-pointer:before{content:"\F1B7"}.mdi-cursor-text:before{content:"\F5E7"}.mdi-database:before{content:"\F1B8"}.mdi-database-check:before{content:"\FAA8"}.mdi-database-edit:before{content:"\FB62"}.mdi-database-export:before{content:"\F95D"}.mdi-database-import:before{content:"\F95C"}.mdi-database-lock:before{content:"\FAA9"}.mdi-database-minus:before{content:"\F1B9"}.mdi-database-plus:before{content:"\F1BA"}.mdi-database-refresh:before{content:"\FCDB"}.mdi-database-remove:before{content:"\FCDC"}.mdi-database-search:before{content:"\F865"}.mdi-database-settings:before{content:"\FCDD"}.mdi-death-star:before{content:"\F8D7"}.mdi-death-star-variant:before{content:"\F8D8"}.mdi-deathly-hallows:before{content:"\FB63"}.mdi-debian:before{content:"\F8D9"}.mdi-debug-step-into:before{content:"\F1BB"}.mdi-debug-step-out:before{content:"\F1BC"}.mdi-debug-step-over:before{content:"\F1BD"}.mdi-decagram:before{content:"\F76B"}.mdi-decagram-outline:before{content:"\F76C"}.mdi-decimal-decrease:before{content:"\F1BE"}.mdi-decimal-increase:before{content:"\F1BF"}.mdi-delete:before{content:"\F1C0"}.mdi-delete-circle:before{content:"\F682"}.mdi-delete-circle-outline:before{content:"\FB64"}.mdi-delete-empty:before{content:"\F6CB"}.mdi-delete-forever:before{content:"\F5E8"}.mdi-delete-forever-outline:before{content:"\FB65"}.mdi-delete-outline:before{content:"\F9E6"}.mdi-delete-restore:before{content:"\F818"}.mdi-delete-sweep:before{content:"\F5E9"}.mdi-delete-sweep-outline:before{content:"\FC3E"}.mdi-delete-variant:before{content:"\F1C1"}.mdi-delta:before{content:"\F1C2"}.mdi-desk-lamp:before{content:"\F95E"}.mdi-deskphone:before{content:"\F1C3"}.mdi-desktop-classic:before{content:"\F7BF"}.mdi-desktop-mac:before{content:"\F1C4"}.mdi-desktop-mac-dashboard:before{content:"\F9E7"}.mdi-desktop-tower:before{content:"\F1C5"}.mdi-desktop-tower-monitor:before{content:"\FAAA"}.mdi-details:before{content:"\F1C6"}.mdi-dev-to:before{content:"\FD4A"}.mdi-developer-board:before{content:"\F696"}.mdi-deviantart:before{content:"\F1C7"}.mdi-dialpad:before{content:"\F61C"}.mdi-diameter:before{content:"\FC3F"}.mdi-diameter-outline:before{content:"\FC40"}.mdi-diameter-variant:before{content:"\FC41"}.mdi-diamond:before{content:"\FB66"}.mdi-diamond-outline:before{content:"\FB67"}.mdi-diamond-stone:before{content:"\F1C8"}.mdi-dice-1:before{content:"\F1CA"}.mdi-dice-2:before{content:"\F1CB"}.mdi-dice-3:before{content:"\F1CC"}.mdi-dice-4:before{content:"\F1CD"}.mdi-dice-5:before{content:"\F1CE"}.mdi-dice-6:before{content:"\F1CF"}.mdi-dice-d10:before{content:"\F76E"}.mdi-dice-d12:before{content:"\F866"}.mdi-dice-d20:before{content:"\F5EA"}.mdi-dice-d4:before{content:"\F5EB"}.mdi-dice-d6:before{content:"\F5EC"}.mdi-dice-d8:before{content:"\F5ED"}.mdi-dice-multiple:before{content:"\F76D"}.mdi-dictionary:before{content:"\F61D"}.mdi-dip-switch:before{content:"\F7C0"}.mdi-directions:before{content:"\F1D0"}.mdi-directions-fork:before{content:"\F641"}.mdi-disc:before{content:"\F5EE"}.mdi-disc-alert:before{content:"\F1D1"}.mdi-disc-player:before{content:"\F95F"}.mdi-discord:before{content:"\F66F"}.mdi-dishwasher:before{content:"\FAAB"}.mdi-disqus:before{content:"\F1D2"}.mdi-disqus-outline:before{content:"\F1D3"}.mdi-diving-flippers:before{content:"\FD9B"}.mdi-diving-helmet:before{content:"\FD9C"}.mdi-diving-scuba:before{content:"\FD9D"}.mdi-diving-scuba-flag:before{content:"\FD9E"}.mdi-diving-scuba-tank:before{content:"\FD9F"}.mdi-diving-scuba-tank-multiple:before{content:"\FDA0"}.mdi-diving-snorkel:before{content:"\FDA1"}.mdi-division:before{content:"\F1D4"}.mdi-division-box:before{content:"\F1D5"}.mdi-dlna:before{content:"\FA40"}.mdi-dna:before{content:"\F683"}.mdi-dns:before{content:"\F1D6"}.mdi-dns-outline:before{content:"\FB68"}.mdi-do-not-disturb:before{content:"\F697"}.mdi-do-not-disturb-off:before{content:"\F698"}.mdi-docker:before{content:"\F867"}.mdi-doctor:before{content:"\FA41"}.mdi-dog:before{content:"\FA42"}.mdi-dog-service:before{content:"\FAAC"}.mdi-dog-side:before{content:"\FA43"}.mdi-dolby:before{content:"\F6B2"}.mdi-domain:before{content:"\F1D7"}.mdi-domain-off:before{content:"\FD4B"}.mdi-donkey:before{content:"\F7C1"}.mdi-door:before{content:"\F819"}.mdi-door-closed:before{content:"\F81A"}.mdi-door-open:before{content:"\F81B"}.mdi-doorbell-video:before{content:"\F868"}.mdi-dot-net:before{content:"\FAAD"}.mdi-dots-horizontal:before{content:"\F1D8"}.mdi-dots-horizontal-circle:before{content:"\F7C2"}.mdi-dots-horizontal-circle-outline:before{content:"\FB69"}.mdi-dots-vertical:before{content:"\F1D9"}.mdi-dots-vertical-circle:before{content:"\F7C3"}.mdi-dots-vertical-circle-outline:before{content:"\FB6A"}.mdi-douban:before{content:"\F699"}.mdi-download:before{content:"\F1DA"}.mdi-download-multiple:before{content:"\F9E8"}.mdi-download-network:before{content:"\F6F3"}.mdi-download-network-outline:before{content:"\FC42"}.mdi-download-outline:before{content:"\FB6B"}.mdi-drag:before{content:"\F1DB"}.mdi-drag-horizontal:before{content:"\F1DC"}.mdi-drag-variant:before{content:"\FB6C"}.mdi-drag-vertical:before{content:"\F1DD"}.mdi-drama-masks:before{content:"\FCDE"}.mdi-drawing:before{content:"\F1DE"}.mdi-drawing-box:before{content:"\F1DF"}.mdi-dribbble:before{content:"\F1E0"}.mdi-dribbble-box:before{content:"\F1E1"}.mdi-drone:before{content:"\F1E2"}.mdi-dropbox:before{content:"\F1E3"}.mdi-drupal:before{content:"\F1E4"}.mdi-duck:before{content:"\F1E5"}.mdi-dumbbell:before{content:"\F1E6"}.mdi-dump-truck:before{content:"\FC43"}.mdi-ear-hearing:before{content:"\F7C4"}.mdi-ear-hearing-off:before{content:"\FA44"}.mdi-earth:before{content:"\F1E7"}.mdi-earth-box:before{content:"\F6CC"}.mdi-earth-box-off:before{content:"\F6CD"}.mdi-earth-off:before{content:"\F1E8"}.mdi-edge:before{content:"\F1E9"}.mdi-egg:before{content:"\FAAE"}.mdi-egg-easter:before{content:"\FAAF"}.mdi-eight-track:before{content:"\F9E9"}.mdi-eject:before{content:"\F1EA"}.mdi-eject-outline:before{content:"\FB6D"}.mdi-elephant:before{content:"\F7C5"}.mdi-elevation-decline:before{content:"\F1EB"}.mdi-elevation-rise:before{content:"\F1EC"}.mdi-elevator:before{content:"\F1ED"}.mdi-email:before{content:"\F1EE"}.mdi-email-alert:before{content:"\F6CE"}.mdi-email-box:before{content:"\FCDF"}.mdi-email-check:before{content:"\FAB0"}.mdi-email-check-outline:before{content:"\FAB1"}.mdi-email-lock:before{content:"\F1F1"}.mdi-email-mark-as-unread:before{content:"\FB6E"}.mdi-email-open:before{content:"\F1EF"}.mdi-email-open-outline:before{content:"\F5EF"}.mdi-email-outline:before{content:"\F1F0"}.mdi-email-plus:before{content:"\F9EA"}.mdi-email-plus-outline:before{content:"\F9EB"}.mdi-email-search:before{content:"\F960"}.mdi-email-search-outline:before{content:"\F961"}.mdi-email-variant:before{content:"\F5F0"}.mdi-ember:before{content:"\FB15"}.mdi-emby:before{content:"\F6B3"}.mdi-emoticon:before{content:"\FC44"}.mdi-emoticon-angry:before{content:"\FC45"}.mdi-emoticon-angry-outline:before{content:"\FC46"}.mdi-emoticon-cool:before{content:"\FC47"}.mdi-emoticon-cool-outline:before{content:"\F1F3"}.mdi-emoticon-cry:before{content:"\FC48"}.mdi-emoticon-cry-outline:before{content:"\FC49"}.mdi-emoticon-dead:before{content:"\FC4A"}.mdi-emoticon-dead-outline:before{content:"\F69A"}.mdi-emoticon-devil:before{content:"\FC4B"}.mdi-emoticon-devil-outline:before{content:"\F1F4"}.mdi-emoticon-excited:before{content:"\FC4C"}.mdi-emoticon-excited-outline:before{content:"\F69B"}.mdi-emoticon-happy:before{content:"\FC4D"}.mdi-emoticon-happy-outline:before{content:"\F1F5"}.mdi-emoticon-kiss:before{content:"\FC4E"}.mdi-emoticon-kiss-outline:before{content:"\FC4F"}.mdi-emoticon-neutral:before{content:"\FC50"}.mdi-emoticon-neutral-outline:before{content:"\F1F6"}.mdi-emoticon-outline:before{content:"\F1F2"}.mdi-emoticon-poop:before{content:"\F1F7"}.mdi-emoticon-poop-outline:before{content:"\FC51"}.mdi-emoticon-sad:before{content:"\FC52"}.mdi-emoticon-sad-outline:before{content:"\F1F8"}.mdi-emoticon-tongue:before{content:"\F1F9"}.mdi-emoticon-tongue-outline:before{content:"\FC53"}.mdi-emoticon-wink:before{content:"\FC54"}.mdi-emoticon-wink-outline:before{content:"\FC55"}.mdi-engine:before{content:"\F1FA"}.mdi-engine-off:before{content:"\FA45"}.mdi-engine-off-outline:before{content:"\FA46"}.mdi-engine-outline:before{content:"\F1FB"}.mdi-equal:before{content:"\F1FC"}.mdi-equal-box:before{content:"\F1FD"}.mdi-eraser:before{content:"\F1FE"}.mdi-eraser-variant:before{content:"\F642"}.mdi-escalator:before{content:"\F1FF"}.mdi-eslint:before{content:"\FC56"}.mdi-et:before{content:"\FAB2"}.mdi-ethereum:before{content:"\F869"}.mdi-ethernet:before{content:"\F200"}.mdi-ethernet-cable:before{content:"\F201"}.mdi-ethernet-cable-off:before{content:"\F202"}.mdi-etsy:before{content:"\F203"}.mdi-ev-station:before{content:"\F5F1"}.mdi-eventbrite:before{content:"\F7C6"}.mdi-evernote:before{content:"\F204"}.mdi-exclamation:before{content:"\F205"}.mdi-exit-run:before{content:"\FA47"}.mdi-exit-to-app:before{content:"\F206"}.mdi-expand-all:before{content:"\FAB3"}.mdi-expand-all-outline:before{content:"\FAB4"}.mdi-exponent:before{content:"\F962"}.mdi-exponent-box:before{content:"\F963"}.mdi-export:before{content:"\F207"}.mdi-export-variant:before{content:"\FB6F"}.mdi-eye:before{content:"\F208"}.mdi-eye-check:before{content:"\FCE0"}.mdi-eye-check-outline:before{content:"\FCE1"}.mdi-eye-circle:before{content:"\FB70"}.mdi-eye-circle-outline:before{content:"\FB71"}.mdi-eye-off:before{content:"\F209"}.mdi-eye-off-outline:before{content:"\F6D0"}.mdi-eye-outline:before{content:"\F6CF"}.mdi-eye-plus:before{content:"\F86A"}.mdi-eye-plus-outline:before{content:"\F86B"}.mdi-eye-settings:before{content:"\F86C"}.mdi-eye-settings-outline:before{content:"\F86D"}.mdi-eyedropper:before{content:"\F20A"}.mdi-eyedropper-variant:before{content:"\F20B"}.mdi-face:before{content:"\F643"}.mdi-face-agent:before{content:"\FD4C"}.mdi-face-outline:before{content:"\FB72"}.mdi-face-profile:before{content:"\F644"}.mdi-face-recognition:before{content:"\FC57"}.mdi-facebook:before{content:"\F20C"}.mdi-facebook-box:before{content:"\F20D"}.mdi-facebook-messenger:before{content:"\F20E"}.mdi-facebook-workplace:before{content:"\FB16"}.mdi-factory:before{content:"\F20F"}.mdi-fan:before{content:"\F210"}.mdi-fan-off:before{content:"\F81C"}.mdi-fast-forward:before{content:"\F211"}.mdi-fast-forward-10:before{content:"\FD4D"}.mdi-fast-forward-30:before{content:"\FCE2"}.mdi-fast-forward-outline:before{content:"\F6D1"}.mdi-fax:before{content:"\F212"}.mdi-feather:before{content:"\F6D2"}.mdi-feature-search:before{content:"\FA48"}.mdi-feature-search-outline:before{content:"\FA49"}.mdi-fedora:before{content:"\F8DA"}.mdi-ferry:before{content:"\F213"}.mdi-file:before{content:"\F214"}.mdi-file-account:before{content:"\F73A"}.mdi-file-alert:before{content:"\FA4A"}.mdi-file-alert-outline:before{content:"\FA4B"}.mdi-file-cabinet:before{content:"\FAB5"}.mdi-file-cancel:before{content:"\FDA2"}.mdi-file-cancel-outline:before{content:"\FDA3"}.mdi-file-chart:before{content:"\F215"}.mdi-file-check:before{content:"\F216"}.mdi-file-cloud:before{content:"\F217"}.mdi-file-compare:before{content:"\F8A9"}.mdi-file-delimited:before{content:"\F218"}.mdi-file-document:before{content:"\F219"}.mdi-file-document-box:before{content:"\F21A"}.mdi-file-document-box-multiple:before{content:"\FAB6"}.mdi-file-document-box-multiple-outline:before{content:"\FAB7"}.mdi-file-document-box-outline:before{content:"\F9EC"}.mdi-file-document-edit:before{content:"\FDA4"}.mdi-file-document-edit-outline:before{content:"\FDA5"}.mdi-file-document-outline:before{content:"\F9ED"}.mdi-file-download:before{content:"\F964"}.mdi-file-download-outline:before{content:"\F965"}.mdi-file-excel:before{content:"\F21B"}.mdi-file-excel-box:before{content:"\F21C"}.mdi-file-export:before{content:"\F21D"}.mdi-file-eye:before{content:"\FDA6"}.mdi-file-eye-outline:before{content:"\FDA7"}.mdi-file-find:before{content:"\F21E"}.mdi-file-find-outline:before{content:"\FB73"}.mdi-file-hidden:before{content:"\F613"}.mdi-file-image:before{content:"\F21F"}.mdi-file-import:before{content:"\F220"}.mdi-file-lock:before{content:"\F221"}.mdi-file-move:before{content:"\FAB8"}.mdi-file-multiple:before{content:"\F222"}.mdi-file-music:before{content:"\F223"}.mdi-file-outline:before{content:"\F224"}.mdi-file-pdf:before{content:"\F225"}.mdi-file-pdf-box:before{content:"\F226"}.mdi-file-percent:before{content:"\F81D"}.mdi-file-plus:before{content:"\F751"}.mdi-file-powerpoint:before{content:"\F227"}.mdi-file-powerpoint-box:before{content:"\F228"}.mdi-file-presentation-box:before{content:"\F229"}.mdi-file-question:before{content:"\F86E"}.mdi-file-remove:before{content:"\FB74"}.mdi-file-replace:before{content:"\FB17"}.mdi-file-replace-outline:before{content:"\FB18"}.mdi-file-restore:before{content:"\F670"}.mdi-file-search:before{content:"\FC58"}.mdi-file-search-outline:before{content:"\FC59"}.mdi-file-send:before{content:"\F22A"}.mdi-file-table:before{content:"\FC5A"}.mdi-file-table-outline:before{content:"\FC5B"}.mdi-file-tree:before{content:"\F645"}.mdi-file-undo:before{content:"\F8DB"}.mdi-file-upload:before{content:"\FA4C"}.mdi-file-upload-outline:before{content:"\FA4D"}.mdi-file-video:before{content:"\F22B"}.mdi-file-word:before{content:"\F22C"}.mdi-file-word-box:before{content:"\F22D"}.mdi-file-xml:before{content:"\F22E"}.mdi-film:before{content:"\F22F"}.mdi-filmstrip:before{content:"\F230"}.mdi-filmstrip-off:before{content:"\F231"}.mdi-filter:before{content:"\F232"}.mdi-filter-outline:before{content:"\F233"}.mdi-filter-remove:before{content:"\F234"}.mdi-filter-remove-outline:before{content:"\F235"}.mdi-filter-variant:before{content:"\F236"}.mdi-finance:before{content:"\F81E"}.mdi-find-replace:before{content:"\F6D3"}.mdi-fingerprint:before{content:"\F237"}.mdi-fire:before{content:"\F238"}.mdi-fire-truck:before{content:"\F8AA"}.mdi-firebase:before{content:"\F966"}.mdi-firefox:before{content:"\F239"}.mdi-fish:before{content:"\F23A"}.mdi-flag:before{content:"\F23B"}.mdi-flag-checkered:before{content:"\F23C"}.mdi-flag-minus:before{content:"\FB75"}.mdi-flag-outline:before{content:"\F23D"}.mdi-flag-plus:before{content:"\FB76"}.mdi-flag-remove:before{content:"\FB77"}.mdi-flag-triangle:before{content:"\F23F"}.mdi-flag-variant:before{content:"\F240"}.mdi-flag-variant-outline:before{content:"\F23E"}.mdi-flare:before{content:"\FD4E"}.mdi-flash:before{content:"\F241"}.mdi-flash-auto:before{content:"\F242"}.mdi-flash-circle:before{content:"\F81F"}.mdi-flash-off:before{content:"\F243"}.mdi-flash-outline:before{content:"\F6D4"}.mdi-flash-red-eye:before{content:"\F67A"}.mdi-flashlight:before{content:"\F244"}.mdi-flashlight-off:before{content:"\F245"}.mdi-flask:before{content:"\F093"}.mdi-flask-empty:before{content:"\F094"}.mdi-flask-empty-outline:before{content:"\F095"}.mdi-flask-outline:before{content:"\F096"}.mdi-flattr:before{content:"\F246"}.mdi-flickr:before{content:"\FCE3"}.mdi-flip-to-back:before{content:"\F247"}.mdi-flip-to-front:before{content:"\F248"}.mdi-floor-lamp:before{content:"\F8DC"}.mdi-floor-plan:before{content:"\F820"}.mdi-floppy:before{content:"\F249"}.mdi-floppy-variant:before{content:"\F9EE"}.mdi-flower:before{content:"\F24A"}.mdi-flower-outline:before{content:"\F9EF"}.mdi-flower-poppy:before{content:"\FCE4"}.mdi-flower-tulip:before{content:"\F9F0"}.mdi-flower-tulip-outline:before{content:"\F9F1"}.mdi-folder:before{content:"\F24B"}.mdi-folder-account:before{content:"\F24C"}.mdi-folder-account-outline:before{content:"\FB78"}.mdi-folder-alert:before{content:"\FDA8"}.mdi-folder-alert-outline:before{content:"\FDA9"}.mdi-folder-clock:before{content:"\FAB9"}.mdi-folder-clock-outline:before{content:"\FABA"}.mdi-folder-download:before{content:"\F24D"}.mdi-folder-edit:before{content:"\F8DD"}.mdi-folder-edit-outline:before{content:"\FDAA"}.mdi-folder-google-drive:before{content:"\F24E"}.mdi-folder-image:before{content:"\F24F"}.mdi-folder-key:before{content:"\F8AB"}.mdi-folder-key-network:before{content:"\F8AC"}.mdi-folder-key-network-outline:before{content:"\FC5C"}.mdi-folder-lock:before{content:"\F250"}.mdi-folder-lock-open:before{content:"\F251"}.mdi-folder-move:before{content:"\F252"}.mdi-folder-multiple:before{content:"\F253"}.mdi-folder-multiple-image:before{content:"\F254"}.mdi-folder-multiple-outline:before{content:"\F255"}.mdi-folder-network:before{content:"\F86F"}.mdi-folder-network-outline:before{content:"\FC5D"}.mdi-folder-open:before{content:"\F76F"}.mdi-folder-open-outline:before{content:"\FDAB"}.mdi-folder-outline:before{content:"\F256"}.mdi-folder-plus:before{content:"\F257"}.mdi-folder-plus-outline:before{content:"\FB79"}.mdi-folder-pound:before{content:"\FCE5"}.mdi-folder-pound-outline:before{content:"\FCE6"}.mdi-folder-remove:before{content:"\F258"}.mdi-folder-remove-outline:before{content:"\FB7A"}.mdi-folder-search:before{content:"\F967"}.mdi-folder-search-outline:before{content:"\F968"}.mdi-folder-star:before{content:"\F69C"}.mdi-folder-star-outline:before{content:"\FB7B"}.mdi-folder-sync:before{content:"\FCE7"}.mdi-folder-sync-outline:before{content:"\FCE8"}.mdi-folder-text:before{content:"\FC5E"}.mdi-folder-text-outline:before{content:"\FC5F"}.mdi-folder-upload:before{content:"\F259"}.mdi-font-awesome:before{content:"\F03A"}.mdi-food:before{content:"\F25A"}.mdi-food-apple:before{content:"\F25B"}.mdi-food-apple-outline:before{content:"\FC60"}.mdi-food-croissant:before{content:"\F7C7"}.mdi-food-fork-drink:before{content:"\F5F2"}.mdi-food-off:before{content:"\F5F3"}.mdi-food-variant:before{content:"\F25C"}.mdi-football:before{content:"\F25D"}.mdi-football-australian:before{content:"\F25E"}.mdi-football-helmet:before{content:"\F25F"}.mdi-forklift:before{content:"\F7C8"}.mdi-format-align-bottom:before{content:"\F752"}.mdi-format-align-center:before{content:"\F260"}.mdi-format-align-justify:before{content:"\F261"}.mdi-format-align-left:before{content:"\F262"}.mdi-format-align-middle:before{content:"\F753"}.mdi-format-align-right:before{content:"\F263"}.mdi-format-align-top:before{content:"\F754"}.mdi-format-annotation-minus:before{content:"\FABB"}.mdi-format-annotation-plus:before{content:"\F646"}.mdi-format-bold:before{content:"\F264"}.mdi-format-clear:before{content:"\F265"}.mdi-format-color-fill:before{content:"\F266"}.mdi-format-color-text:before{content:"\F69D"}.mdi-format-columns:before{content:"\F8DE"}.mdi-format-float-center:before{content:"\F267"}.mdi-format-float-left:before{content:"\F268"}.mdi-format-float-none:before{content:"\F269"}.mdi-format-float-right:before{content:"\F26A"}.mdi-format-font:before{content:"\F6D5"}.mdi-format-font-size-decrease:before{content:"\F9F2"}.mdi-format-font-size-increase:before{content:"\F9F3"}.mdi-format-header-1:before{content:"\F26B"}.mdi-format-header-2:before{content:"\F26C"}.mdi-format-header-3:before{content:"\F26D"}.mdi-format-header-4:before{content:"\F26E"}.mdi-format-header-5:before{content:"\F26F"}.mdi-format-header-6:before{content:"\F270"}.mdi-format-header-decrease:before{content:"\F271"}.mdi-format-header-equal:before{content:"\F272"}.mdi-format-header-increase:before{content:"\F273"}.mdi-format-header-pound:before{content:"\F274"}.mdi-format-horizontal-align-center:before{content:"\F61E"}.mdi-format-horizontal-align-left:before{content:"\F61F"}.mdi-format-horizontal-align-right:before{content:"\F620"}.mdi-format-indent-decrease:before{content:"\F275"}.mdi-format-indent-increase:before{content:"\F276"}.mdi-format-italic:before{content:"\F277"}.mdi-format-letter-case:before{content:"\FB19"}.mdi-format-letter-case-lower:before{content:"\FB1A"}.mdi-format-letter-case-upper:before{content:"\FB1B"}.mdi-format-line-spacing:before{content:"\F278"}.mdi-format-line-style:before{content:"\F5C8"}.mdi-format-line-weight:before{content:"\F5C9"}.mdi-format-list-bulleted:before{content:"\F279"}.mdi-format-list-bulleted-square:before{content:"\FDAC"}.mdi-format-list-bulleted-type:before{content:"\F27A"}.mdi-format-list-checkbox:before{content:"\F969"}.mdi-format-list-checks:before{content:"\F755"}.mdi-format-list-numbered:before{content:"\F27B"}.mdi-format-list-numbered-rtl:before{content:"\FCE9"}.mdi-format-page-break:before{content:"\F6D6"}.mdi-format-paint:before{content:"\F27C"}.mdi-format-paragraph:before{content:"\F27D"}.mdi-format-pilcrow:before{content:"\F6D7"}.mdi-format-quote-close:before{content:"\F27E"}.mdi-format-quote-open:before{content:"\F756"}.mdi-format-rotate-90:before{content:"\F6A9"}.mdi-format-section:before{content:"\F69E"}.mdi-format-size:before{content:"\F27F"}.mdi-format-strikethrough:before{content:"\F280"}.mdi-format-strikethrough-variant:before{content:"\F281"}.mdi-format-subscript:before{content:"\F282"}.mdi-format-superscript:before{content:"\F283"}.mdi-format-text:before{content:"\F284"}.mdi-format-text-rotation-down:before{content:"\FD4F"}.mdi-format-text-rotation-none:before{content:"\FD50"}.mdi-format-text-wrapping-clip:before{content:"\FCEA"}.mdi-format-text-wrapping-overflow:before{content:"\FCEB"}.mdi-format-text-wrapping-wrap:before{content:"\FCEC"}.mdi-format-textbox:before{content:"\FCED"}.mdi-format-textdirection-l-to-r:before{content:"\F285"}.mdi-format-textdirection-r-to-l:before{content:"\F286"}.mdi-format-title:before{content:"\F5F4"}.mdi-format-underline:before{content:"\F287"}.mdi-format-vertical-align-bottom:before{content:"\F621"}.mdi-format-vertical-align-center:before{content:"\F622"}.mdi-format-vertical-align-top:before{content:"\F623"}.mdi-format-wrap-inline:before{content:"\F288"}.mdi-format-wrap-square:before{content:"\F289"}.mdi-format-wrap-tight:before{content:"\F28A"}.mdi-format-wrap-top-bottom:before{content:"\F28B"}.mdi-forum:before{content:"\F28C"}.mdi-forum-outline:before{content:"\F821"}.mdi-forward:before{content:"\F28D"}.mdi-forwardburger:before{content:"\FD51"}.mdi-fountain:before{content:"\F96A"}.mdi-fountain-pen:before{content:"\FCEE"}.mdi-fountain-pen-tip:before{content:"\FCEF"}.mdi-foursquare:before{content:"\F28E"}.mdi-freebsd:before{content:"\F8DF"}.mdi-fridge:before{content:"\F290"}.mdi-fridge-bottom:before{content:"\F292"}.mdi-fridge-outline:before{content:"\F28F"}.mdi-fridge-top:before{content:"\F291"}.mdi-fuel:before{content:"\F7C9"}.mdi-fullscreen:before{content:"\F293"}.mdi-fullscreen-exit:before{content:"\F294"}.mdi-function:before{content:"\F295"}.mdi-function-variant:before{content:"\F870"}.mdi-fuse:before{content:"\FC61"}.mdi-fuse-blade:before{content:"\FC62"}.mdi-gamepad:before{content:"\F296"}.mdi-gamepad-variant:before{content:"\F297"}.mdi-gantry-crane:before{content:"\FDAD"}.mdi-garage:before{content:"\F6D8"}.mdi-garage-alert:before{content:"\F871"}.mdi-garage-open:before{content:"\F6D9"}.mdi-gas-cylinder:before{content:"\F647"}.mdi-gas-station:before{content:"\F298"}.mdi-gate:before{content:"\F299"}.mdi-gate-and:before{content:"\F8E0"}.mdi-gate-nand:before{content:"\F8E1"}.mdi-gate-nor:before{content:"\F8E2"}.mdi-gate-not:before{content:"\F8E3"}.mdi-gate-or:before{content:"\F8E4"}.mdi-gate-xnor:before{content:"\F8E5"}.mdi-gate-xor:before{content:"\F8E6"}.mdi-gauge:before{content:"\F29A"}.mdi-gauge-empty:before{content:"\F872"}.mdi-gauge-full:before{content:"\F873"}.mdi-gauge-low:before{content:"\F874"}.mdi-gavel:before{content:"\F29B"}.mdi-gender-female:before{content:"\F29C"}.mdi-gender-male:before{content:"\F29D"}.mdi-gender-male-female:before{content:"\F29E"}.mdi-gender-transgender:before{content:"\F29F"}.mdi-gentoo:before{content:"\F8E7"}.mdi-gesture:before{content:"\F7CA"}.mdi-gesture-double-tap:before{content:"\F73B"}.mdi-gesture-pinch:before{content:"\FABC"}.mdi-gesture-spread:before{content:"\FABD"}.mdi-gesture-swipe:before{content:"\FD52"}.mdi-gesture-swipe-down:before{content:"\F73C"}.mdi-gesture-swipe-horizontal:before{content:"\FABE"}.mdi-gesture-swipe-left:before{content:"\F73D"}.mdi-gesture-swipe-right:before{content:"\F73E"}.mdi-gesture-swipe-up:before{content:"\F73F"}.mdi-gesture-swipe-vertical:before{content:"\FABF"}.mdi-gesture-tap:before{content:"\F740"}.mdi-gesture-tap-hold:before{content:"\FD53"}.mdi-gesture-two-double-tap:before{content:"\F741"}.mdi-gesture-two-tap:before{content:"\F742"}.mdi-ghost:before{content:"\F2A0"}.mdi-ghost-off:before{content:"\F9F4"}.mdi-gif:before{content:"\FD54"}.mdi-gift:before{content:"\F2A1"}.mdi-git:before{content:"\F2A2"}.mdi-github-box:before{content:"\F2A3"}.mdi-github-circle:before{content:"\F2A4"}.mdi-github-face:before{content:"\F6DA"}.mdi-gitlab:before{content:"\FB7C"}.mdi-glass-cocktail:before{content:"\F356"}.mdi-glass-flute:before{content:"\F2A5"}.mdi-glass-mug:before{content:"\F2A6"}.mdi-glass-stange:before{content:"\F2A7"}.mdi-glass-tulip:before{content:"\F2A8"}.mdi-glass-wine:before{content:"\F875"}.mdi-glassdoor:before{content:"\F2A9"}.mdi-glasses:before{content:"\F2AA"}.mdi-globe-model:before{content:"\F8E8"}.mdi-gmail:before{content:"\F2AB"}.mdi-gnome:before{content:"\F2AC"}.mdi-go-kart:before{content:"\FD55"}.mdi-go-kart-track:before{content:"\FD56"}.mdi-gog:before{content:"\FB7D"}.mdi-golf:before{content:"\F822"}.mdi-gondola:before{content:"\F685"}.mdi-goodreads:before{content:"\FD57"}.mdi-google:before{content:"\F2AD"}.mdi-google-adwords:before{content:"\FC63"}.mdi-google-allo:before{content:"\F801"}.mdi-google-analytics:before{content:"\F7CB"}.mdi-google-assistant:before{content:"\F7CC"}.mdi-google-cardboard:before{content:"\F2AE"}.mdi-google-chrome:before{content:"\F2AF"}.mdi-google-circles:before{content:"\F2B0"}.mdi-google-circles-communities:before{content:"\F2B1"}.mdi-google-circles-extended:before{content:"\F2B2"}.mdi-google-circles-group:before{content:"\F2B3"}.mdi-google-classroom:before{content:"\F2C0"}.mdi-google-controller:before{content:"\F2B4"}.mdi-google-controller-off:before{content:"\F2B5"}.mdi-google-drive:before{content:"\F2B6"}.mdi-google-earth:before{content:"\F2B7"}.mdi-google-fit:before{content:"\F96B"}.mdi-google-glass:before{content:"\F2B8"}.mdi-google-hangouts:before{content:"\F2C9"}.mdi-google-home:before{content:"\F823"}.mdi-google-keep:before{content:"\F6DB"}.mdi-google-lens:before{content:"\F9F5"}.mdi-google-maps:before{content:"\F5F5"}.mdi-google-nearby:before{content:"\F2B9"}.mdi-google-pages:before{content:"\F2BA"}.mdi-google-photos:before{content:"\F6DC"}.mdi-google-physical-web:before{content:"\F2BB"}.mdi-google-play:before{content:"\F2BC"}.mdi-google-plus:before{content:"\F2BD"}.mdi-google-plus-box:before{content:"\F2BE"}.mdi-google-spreadsheet:before{content:"\F9F6"}.mdi-google-street-view:before{content:"\FC64"}.mdi-google-translate:before{content:"\F2BF"}.mdi-gpu:before{content:"\F8AD"}.mdi-gradient:before{content:"\F69F"}.mdi-grain:before{content:"\FD58"}.mdi-graphql:before{content:"\F876"}.mdi-grave-stone:before{content:"\FB7E"}.mdi-grease-pencil:before{content:"\F648"}.mdi-greater-than:before{content:"\F96C"}.mdi-greater-than-or-equal:before{content:"\F96D"}.mdi-grid:before{content:"\F2C1"}.mdi-grid-large:before{content:"\F757"}.mdi-grid-off:before{content:"\F2C2"}.mdi-group:before{content:"\F2C3"}.mdi-guitar-acoustic:before{content:"\F770"}.mdi-guitar-electric:before{content:"\F2C4"}.mdi-guitar-pick:before{content:"\F2C5"}.mdi-guitar-pick-outline:before{content:"\F2C6"}.mdi-guy-fawkes-mask:before{content:"\F824"}.mdi-hackernews:before{content:"\F624"}.mdi-hail:before{content:"\FAC0"}.mdi-halloween:before{content:"\FB7F"}.mdi-hamburger:before{content:"\F684"}.mdi-hammer:before{content:"\F8E9"}.mdi-hand:before{content:"\FA4E"}.mdi-hand-okay:before{content:"\FA4F"}.mdi-hand-peace:before{content:"\FA50"}.mdi-hand-peace-variant:before{content:"\FA51"}.mdi-hand-pointing-down:before{content:"\FA52"}.mdi-hand-pointing-left:before{content:"\FA53"}.mdi-hand-pointing-right:before{content:"\F2C7"}.mdi-hand-pointing-up:before{content:"\FA54"}.mdi-hanger:before{content:"\F2C8"}.mdi-hard-hat:before{content:"\F96E"}.mdi-harddisk:before{content:"\F2CA"}.mdi-hat-fedora:before{content:"\FB80"}.mdi-hazard-lights:before{content:"\FC65"}.mdi-hdr:before{content:"\FD59"}.mdi-hdr-off:before{content:"\FD5A"}.mdi-headphones:before{content:"\F2CB"}.mdi-headphones-bluetooth:before{content:"\F96F"}.mdi-headphones-box:before{content:"\F2CC"}.mdi-headphones-off:before{content:"\F7CD"}.mdi-headphones-settings:before{content:"\F2CD"}.mdi-headset:before{content:"\F2CE"}.mdi-headset-dock:before{content:"\F2CF"}.mdi-headset-off:before{content:"\F2D0"}.mdi-heart:before{content:"\F2D1"}.mdi-heart-box:before{content:"\F2D2"}.mdi-heart-box-outline:before{content:"\F2D3"}.mdi-heart-broken:before{content:"\F2D4"}.mdi-heart-broken-outline:before{content:"\FCF0"}.mdi-heart-circle:before{content:"\F970"}.mdi-heart-circle-outline:before{content:"\F971"}.mdi-heart-half:before{content:"\F6DE"}.mdi-heart-half-full:before{content:"\F6DD"}.mdi-heart-half-outline:before{content:"\F6DF"}.mdi-heart-multiple:before{content:"\FA55"}.mdi-heart-multiple-outline:before{content:"\FA56"}.mdi-heart-off:before{content:"\F758"}.mdi-heart-outline:before{content:"\F2D5"}.mdi-heart-pulse:before{content:"\F5F6"}.mdi-helicopter:before{content:"\FAC1"}.mdi-help:before{content:"\F2D6"}.mdi-help-box:before{content:"\F78A"}.mdi-help-circle:before{content:"\F2D7"}.mdi-help-circle-outline:before{content:"\F625"}.mdi-help-network:before{content:"\F6F4"}.mdi-help-network-outline:before{content:"\FC66"}.mdi-help-rhombus:before{content:"\FB81"}.mdi-help-rhombus-outline:before{content:"\FB82"}.mdi-hexagon:before{content:"\F2D8"}.mdi-hexagon-multiple:before{content:"\F6E0"}.mdi-hexagon-outline:before{content:"\F2D9"}.mdi-hexagon-slice-1:before{content:"\FAC2"}.mdi-hexagon-slice-2:before{content:"\FAC3"}.mdi-hexagon-slice-3:before{content:"\FAC4"}.mdi-hexagon-slice-4:before{content:"\FAC5"}.mdi-hexagon-slice-5:before{content:"\FAC6"}.mdi-hexagon-slice-6:before{content:"\FAC7"}.mdi-hexagram:before{content:"\FAC8"}.mdi-hexagram-outline:before{content:"\FAC9"}.mdi-high-definition:before{content:"\F7CE"}.mdi-high-definition-box:before{content:"\F877"}.mdi-highway:before{content:"\F5F7"}.mdi-hiking:before{content:"\FD5B"}.mdi-hinduism:before{content:"\F972"}.mdi-history:before{content:"\F2DA"}.mdi-hockey-puck:before{content:"\F878"}.mdi-hockey-sticks:before{content:"\F879"}.mdi-hololens:before{content:"\F2DB"}.mdi-home:before{content:"\F2DC"}.mdi-home-account:before{content:"\F825"}.mdi-home-alert:before{content:"\F87A"}.mdi-home-assistant:before{content:"\F7CF"}.mdi-home-automation:before{content:"\F7D0"}.mdi-home-circle:before{content:"\F7D1"}.mdi-home-city:before{content:"\FCF1"}.mdi-home-city-outline:before{content:"\FCF2"}.mdi-home-currency-usd:before{content:"\F8AE"}.mdi-home-floor-0:before{content:"\FDAE"}.mdi-home-floor-1:before{content:"\FD5C"}.mdi-home-floor-2:before{content:"\FD5D"}.mdi-home-floor-3:before{content:"\FD5E"}.mdi-home-floor-a:before{content:"\FD5F"}.mdi-home-floor-b:before{content:"\FD60"}.mdi-home-floor-g:before{content:"\FD61"}.mdi-home-floor-l:before{content:"\FD62"}.mdi-home-floor-negative-1:before{content:"\FDAF"}.mdi-home-group:before{content:"\FDB0"}.mdi-home-heart:before{content:"\F826"}.mdi-home-lock:before{content:"\F8EA"}.mdi-home-lock-open:before{content:"\F8EB"}.mdi-home-map-marker:before{content:"\F5F8"}.mdi-home-minus:before{content:"\F973"}.mdi-home-modern:before{content:"\F2DD"}.mdi-home-outline:before{content:"\F6A0"}.mdi-home-plus:before{content:"\F974"}.mdi-home-variant:before{content:"\F2DE"}.mdi-home-variant-outline:before{content:"\FB83"}.mdi-hook:before{content:"\F6E1"}.mdi-hook-off:before{content:"\F6E2"}.mdi-hops:before{content:"\F2DF"}.mdi-horseshoe:before{content:"\FA57"}.mdi-hospital:before{content:"\F2E0"}.mdi-hospital-building:before{content:"\F2E1"}.mdi-hospital-marker:before{content:"\F2E2"}.mdi-hot-tub:before{content:"\F827"}.mdi-hotel:before{content:"\F2E3"}.mdi-houzz:before{content:"\F2E4"}.mdi-houzz-box:before{content:"\F2E5"}.mdi-hubspot:before{content:"\FCF3"}.mdi-hulu:before{content:"\F828"}.mdi-human:before{content:"\F2E6"}.mdi-human-child:before{content:"\F2E7"}.mdi-human-female:before{content:"\F649"}.mdi-human-female-boy:before{content:"\FA58"}.mdi-human-female-female:before{content:"\FA59"}.mdi-human-female-girl:before{content:"\FA5A"}.mdi-human-greeting:before{content:"\F64A"}.mdi-human-handsdown:before{content:"\F64B"}.mdi-human-handsup:before{content:"\F64C"}.mdi-human-male:before{content:"\F64D"}.mdi-human-male-boy:before{content:"\FA5B"}.mdi-human-male-female:before{content:"\F2E8"}.mdi-human-male-girl:before{content:"\FA5C"}.mdi-human-male-male:before{content:"\FA5D"}.mdi-human-pregnant:before{content:"\F5CF"}.mdi-humble-bundle:before{content:"\F743"}.mdi-ice-cream:before{content:"\F829"}.mdi-iframe:before{content:"\FC67"}.mdi-iframe-outline:before{content:"\FC68"}.mdi-image:before{content:"\F2E9"}.mdi-image-album:before{content:"\F2EA"}.mdi-image-area:before{content:"\F2EB"}.mdi-image-area-close:before{content:"\F2EC"}.mdi-image-broken:before{content:"\F2ED"}.mdi-image-broken-variant:before{content:"\F2EE"}.mdi-image-filter:before{content:"\F2EF"}.mdi-image-filter-black-white:before{content:"\F2F0"}.mdi-image-filter-center-focus:before{content:"\F2F1"}.mdi-image-filter-center-focus-weak:before{content:"\F2F2"}.mdi-image-filter-drama:before{content:"\F2F3"}.mdi-image-filter-frames:before{content:"\F2F4"}.mdi-image-filter-hdr:before{content:"\F2F5"}.mdi-image-filter-none:before{content:"\F2F6"}.mdi-image-filter-tilt-shift:before{content:"\F2F7"}.mdi-image-filter-vintage:before{content:"\F2F8"}.mdi-image-move:before{content:"\F9F7"}.mdi-image-multiple:before{content:"\F2F9"}.mdi-image-off:before{content:"\F82A"}.mdi-image-outline:before{content:"\F975"}.mdi-image-plus:before{content:"\F87B"}.mdi-image-search:before{content:"\F976"}.mdi-image-search-outline:before{content:"\F977"}.mdi-image-size-select-actual:before{content:"\FC69"}.mdi-image-size-select-large:before{content:"\FC6A"}.mdi-image-size-select-small:before{content:"\FC6B"}.mdi-import:before{content:"\F2FA"}.mdi-inbox:before{content:"\F686"}.mdi-inbox-arrow-down:before{content:"\F2FB"}.mdi-inbox-arrow-up:before{content:"\F3D1"}.mdi-inbox-multiple:before{content:"\F8AF"}.mdi-inbox-multiple-outline:before{content:"\FB84"}.mdi-incognito:before{content:"\F5F9"}.mdi-infinity:before{content:"\F6E3"}.mdi-information:before{content:"\F2FC"}.mdi-information-outline:before{content:"\F2FD"}.mdi-information-variant:before{content:"\F64E"}.mdi-instagram:before{content:"\F2FE"}.mdi-instapaper:before{content:"\F2FF"}.mdi-internet-explorer:before{content:"\F300"}.mdi-invert-colors:before{content:"\F301"}.mdi-ip:before{content:"\FA5E"}.mdi-ip-network:before{content:"\FA5F"}.mdi-ip-network-outline:before{content:"\FC6C"}.mdi-ipod:before{content:"\FC6D"}.mdi-islam:before{content:"\F978"}.mdi-itunes:before{content:"\F676"}.mdi-jabber:before{content:"\FDB1"}.mdi-jeepney:before{content:"\F302"}.mdi-jira:before{content:"\F303"}.mdi-jquery:before{content:"\F87C"}.mdi-jsfiddle:before{content:"\F304"}.mdi-json:before{content:"\F626"}.mdi-judaism:before{content:"\F979"}.mdi-kabaddi:before{content:"\FD63"}.mdi-karate:before{content:"\F82B"}.mdi-keg:before{content:"\F305"}.mdi-kettle:before{content:"\F5FA"}.mdi-key:before{content:"\F306"}.mdi-key-change:before{content:"\F307"}.mdi-key-minus:before{content:"\F308"}.mdi-key-outline:before{content:"\FDB2"}.mdi-key-plus:before{content:"\F309"}.mdi-key-remove:before{content:"\F30A"}.mdi-key-variant:before{content:"\F30B"}.mdi-keyboard:before{content:"\F30C"}.mdi-keyboard-backspace:before{content:"\F30D"}.mdi-keyboard-caps:before{content:"\F30E"}.mdi-keyboard-close:before{content:"\F30F"}.mdi-keyboard-off:before{content:"\F310"}.mdi-keyboard-outline:before{content:"\F97A"}.mdi-keyboard-return:before{content:"\F311"}.mdi-keyboard-settings:before{content:"\F9F8"}.mdi-keyboard-settings-outline:before{content:"\F9F9"}.mdi-keyboard-tab:before{content:"\F312"}.mdi-keyboard-variant:before{content:"\F313"}.mdi-kickstarter:before{content:"\F744"}.mdi-knife:before{content:"\F9FA"}.mdi-knife-military:before{content:"\F9FB"}.mdi-kodi:before{content:"\F314"}.mdi-label:before{content:"\F315"}.mdi-label-off:before{content:"\FACA"}.mdi-label-off-outline:before{content:"\FACB"}.mdi-label-outline:before{content:"\F316"}.mdi-label-variant:before{content:"\FACC"}.mdi-label-variant-outline:before{content:"\FACD"}.mdi-ladybug:before{content:"\F82C"}.mdi-lambda:before{content:"\F627"}.mdi-lamp:before{content:"\F6B4"}.mdi-lan:before{content:"\F317"}.mdi-lan-connect:before{content:"\F318"}.mdi-lan-disconnect:before{content:"\F319"}.mdi-lan-pending:before{content:"\F31A"}.mdi-language-c:before{content:"\F671"}.mdi-language-cpp:before{content:"\F672"}.mdi-language-csharp:before{content:"\F31B"}.mdi-language-css3:before{content:"\F31C"}.mdi-language-go:before{content:"\F7D2"}.mdi-language-haskell:before{content:"\FC6E"}.mdi-language-html5:before{content:"\F31D"}.mdi-language-java:before{content:"\FB1C"}.mdi-language-javascript:before{content:"\F31E"}.mdi-language-lua:before{content:"\F8B0"}.mdi-language-php:before{content:"\F31F"}.mdi-language-python:before{content:"\F320"}.mdi-language-python-text:before{content:"\F321"}.mdi-language-r:before{content:"\F7D3"}.mdi-language-ruby-on-rails:before{content:"\FACE"}.mdi-language-swift:before{content:"\F6E4"}.mdi-language-typescript:before{content:"\F6E5"}.mdi-laptop:before{content:"\F322"}.mdi-laptop-chromebook:before{content:"\F323"}.mdi-laptop-mac:before{content:"\F324"}.mdi-laptop-off:before{content:"\F6E6"}.mdi-laptop-windows:before{content:"\F325"}.mdi-laravel:before{content:"\FACF"}.mdi-lastfm:before{content:"\F326"}.mdi-lastpass:before{content:"\F446"}.mdi-launch:before{content:"\F327"}.mdi-lava-lamp:before{content:"\F7D4"}.mdi-layers:before{content:"\F328"}.mdi-layers-off:before{content:"\F329"}.mdi-layers-off-outline:before{content:"\F9FC"}.mdi-layers-outline:before{content:"\F9FD"}.mdi-lead-pencil:before{content:"\F64F"}.mdi-leaf:before{content:"\F32A"}.mdi-leaf-maple:before{content:"\FC6F"}.mdi-leak:before{content:"\FDB3"}.mdi-leak-off:before{content:"\FDB4"}.mdi-led-off:before{content:"\F32B"}.mdi-led-on:before{content:"\F32C"}.mdi-led-outline:before{content:"\F32D"}.mdi-led-strip:before{content:"\F7D5"}.mdi-led-variant-off:before{content:"\F32E"}.mdi-led-variant-on:before{content:"\F32F"}.mdi-led-variant-outline:before{content:"\F330"}.mdi-less-than:before{content:"\F97B"}.mdi-less-than-or-equal:before{content:"\F97C"}.mdi-library:before{content:"\F331"}.mdi-library-books:before{content:"\F332"}.mdi-library-movie:before{content:"\FCF4"}.mdi-library-music:before{content:"\F333"}.mdi-library-plus:before{content:"\F334"}.mdi-library-shelves:before{content:"\FB85"}.mdi-library-video:before{content:"\FCF5"}.mdi-lifebuoy:before{content:"\F87D"}.mdi-light-switch:before{content:"\F97D"}.mdi-lightbulb:before{content:"\F335"}.mdi-lightbulb-on:before{content:"\F6E7"}.mdi-lightbulb-on-outline:before{content:"\F6E8"}.mdi-lightbulb-outline:before{content:"\F336"}.mdi-lighthouse:before{content:"\F9FE"}.mdi-lighthouse-on:before{content:"\F9FF"}.mdi-link:before{content:"\F337"}.mdi-link-box:before{content:"\FCF6"}.mdi-link-box-outline:before{content:"\FCF7"}.mdi-link-box-variant:before{content:"\FCF8"}.mdi-link-box-variant-outline:before{content:"\FCF9"}.mdi-link-off:before{content:"\F338"}.mdi-link-plus:before{content:"\FC70"}.mdi-link-variant:before{content:"\F339"}.mdi-link-variant-off:before{content:"\F33A"}.mdi-linkedin:before{content:"\F33B"}.mdi-linkedin-box:before{content:"\F33C"}.mdi-linux:before{content:"\F33D"}.mdi-linux-mint:before{content:"\F8EC"}.mdi-litecoin:before{content:"\FA60"}.mdi-loading:before{content:"\F771"}.mdi-lock:before{content:"\F33E"}.mdi-lock-alert:before{content:"\F8ED"}.mdi-lock-clock:before{content:"\F97E"}.mdi-lock-open:before{content:"\F33F"}.mdi-lock-open-outline:before{content:"\F340"}.mdi-lock-outline:before{content:"\F341"}.mdi-lock-pattern:before{content:"\F6E9"}.mdi-lock-plus:before{content:"\F5FB"}.mdi-lock-question:before{content:"\F8EE"}.mdi-lock-reset:before{content:"\F772"}.mdi-lock-smart:before{content:"\F8B1"}.mdi-locker:before{content:"\F7D6"}.mdi-locker-multiple:before{content:"\F7D7"}.mdi-login:before{content:"\F342"}.mdi-login-variant:before{content:"\F5FC"}.mdi-logout:before{content:"\F343"}.mdi-logout-variant:before{content:"\F5FD"}.mdi-looks:before{content:"\F344"}.mdi-loop:before{content:"\F6EA"}.mdi-loupe:before{content:"\F345"}.mdi-lumx:before{content:"\F346"}.mdi-lyft:before{content:"\FB1D"}.mdi-magnet:before{content:"\F347"}.mdi-magnet-on:before{content:"\F348"}.mdi-magnify:before{content:"\F349"}.mdi-magnify-close:before{content:"\F97F"}.mdi-magnify-minus:before{content:"\F34A"}.mdi-magnify-minus-cursor:before{content:"\FA61"}.mdi-magnify-minus-outline:before{content:"\F6EB"}.mdi-magnify-plus:before{content:"\F34B"}.mdi-magnify-plus-cursor:before{content:"\FA62"}.mdi-magnify-plus-outline:before{content:"\F6EC"}.mdi-mail-ru:before{content:"\F34C"}.mdi-mailbox:before{content:"\F6ED"}.mdi-mailbox-open:before{content:"\FD64"}.mdi-mailbox-open-outline:before{content:"\FD65"}.mdi-mailbox-open-up:before{content:"\FD66"}.mdi-mailbox-open-up-outline:before{content:"\FD67"}.mdi-mailbox-outline:before{content:"\FD68"}.mdi-mailbox-up:before{content:"\FD69"}.mdi-mailbox-up-outline:before{content:"\FD6A"}.mdi-map:before{content:"\F34D"}.mdi-map-clock:before{content:"\FCFA"}.mdi-map-clock-outline:before{content:"\FCFB"}.mdi-map-legend:before{content:"\FA00"}.mdi-map-marker:before{content:"\F34E"}.mdi-map-marker-check:before{content:"\FC71"}.mdi-map-marker-circle:before{content:"\F34F"}.mdi-map-marker-distance:before{content:"\F8EF"}.mdi-map-marker-minus:before{content:"\F650"}.mdi-map-marker-multiple:before{content:"\F350"}.mdi-map-marker-off:before{content:"\F351"}.mdi-map-marker-outline:before{content:"\F7D8"}.mdi-map-marker-path:before{content:"\FCFC"}.mdi-map-marker-plus:before{content:"\F651"}.mdi-map-marker-radius:before{content:"\F352"}.mdi-map-minus:before{content:"\F980"}.mdi-map-outline:before{content:"\F981"}.mdi-map-plus:before{content:"\F982"}.mdi-map-search:before{content:"\F983"}.mdi-map-search-outline:before{content:"\F984"}.mdi-mapbox:before{content:"\FB86"}.mdi-margin:before{content:"\F353"}.mdi-markdown:before{content:"\F354"}.mdi-marker:before{content:"\F652"}.mdi-marker-cancel:before{content:"\FDB5"}.mdi-marker-check:before{content:"\F355"}.mdi-mastodon:before{content:"\FAD0"}.mdi-mastodon-variant:before{content:"\FAD1"}.mdi-material-design:before{content:"\F985"}.mdi-material-ui:before{content:"\F357"}.mdi-math-compass:before{content:"\F358"}.mdi-math-cos:before{content:"\FC72"}.mdi-math-sin:before{content:"\FC73"}.mdi-math-tan:before{content:"\FC74"}.mdi-matrix:before{content:"\F628"}.mdi-maxcdn:before{content:"\F359"}.mdi-medal:before{content:"\F986"}.mdi-medical-bag:before{content:"\F6EE"}.mdi-medium:before{content:"\F35A"}.mdi-meetup:before{content:"\FAD2"}.mdi-memory:before{content:"\F35B"}.mdi-menu:before{content:"\F35C"}.mdi-menu-down:before{content:"\F35D"}.mdi-menu-down-outline:before{content:"\F6B5"}.mdi-menu-left:before{content:"\F35E"}.mdi-menu-left-outline:before{content:"\FA01"}.mdi-menu-open:before{content:"\FB87"}.mdi-menu-right:before{content:"\F35F"}.mdi-menu-right-outline:before{content:"\FA02"}.mdi-menu-swap:before{content:"\FA63"}.mdi-menu-swap-outline:before{content:"\FA64"}.mdi-menu-up:before{content:"\F360"}.mdi-menu-up-outline:before{content:"\F6B6"}.mdi-message:before{content:"\F361"}.mdi-message-alert:before{content:"\F362"}.mdi-message-alert-outline:before{content:"\FA03"}.mdi-message-bulleted:before{content:"\F6A1"}.mdi-message-bulleted-off:before{content:"\F6A2"}.mdi-message-draw:before{content:"\F363"}.mdi-message-image:before{content:"\F364"}.mdi-message-outline:before{content:"\F365"}.mdi-message-plus:before{content:"\F653"}.mdi-message-processing:before{content:"\F366"}.mdi-message-reply:before{content:"\F367"}.mdi-message-reply-text:before{content:"\F368"}.mdi-message-settings:before{content:"\F6EF"}.mdi-message-settings-variant:before{content:"\F6F0"}.mdi-message-text:before{content:"\F369"}.mdi-message-text-outline:before{content:"\F36A"}.mdi-message-video:before{content:"\F36B"}.mdi-meteor:before{content:"\F629"}.mdi-metronome:before{content:"\F7D9"}.mdi-metronome-tick:before{content:"\F7DA"}.mdi-micro-sd:before{content:"\F7DB"}.mdi-microphone:before{content:"\F36C"}.mdi-microphone-minus:before{content:"\F8B2"}.mdi-microphone-off:before{content:"\F36D"}.mdi-microphone-outline:before{content:"\F36E"}.mdi-microphone-plus:before{content:"\F8B3"}.mdi-microphone-settings:before{content:"\F36F"}.mdi-microphone-variant:before{content:"\F370"}.mdi-microphone-variant-off:before{content:"\F371"}.mdi-microscope:before{content:"\F654"}.mdi-microsoft:before{content:"\F372"}.mdi-microsoft-dynamics:before{content:"\F987"}.mdi-microwave:before{content:"\FC75"}.mdi-midi:before{content:"\F8F0"}.mdi-midi-port:before{content:"\F8F1"}.mdi-mine:before{content:"\FDB6"}.mdi-minecraft:before{content:"\F373"}.mdi-mini-sd:before{content:"\FA04"}.mdi-minidisc:before{content:"\FA05"}.mdi-minus:before{content:"\F374"}.mdi-minus-box:before{content:"\F375"}.mdi-minus-box-outline:before{content:"\F6F1"}.mdi-minus-circle:before{content:"\F376"}.mdi-minus-circle-outline:before{content:"\F377"}.mdi-minus-network:before{content:"\F378"}.mdi-minus-network-outline:before{content:"\FC76"}.mdi-mixcloud:before{content:"\F62A"}.mdi-mixed-martial-arts:before{content:"\FD6B"}.mdi-mixed-reality:before{content:"\F87E"}.mdi-mixer:before{content:"\F7DC"}.mdi-molecule:before{content:"\FB88"}.mdi-monitor:before{content:"\F379"}.mdi-monitor-cellphone:before{content:"\F988"}.mdi-monitor-cellphone-star:before{content:"\F989"}.mdi-monitor-dashboard:before{content:"\FA06"}.mdi-monitor-lock:before{content:"\FDB7"}.mdi-monitor-multiple:before{content:"\F37A"}.mdi-monitor-off:before{content:"\FD6C"}.mdi-monitor-star:before{content:"\FDB8"}.mdi-more:before{content:"\F37B"}.mdi-mother-nurse:before{content:"\FCFD"}.mdi-motion-sensor:before{content:"\FD6D"}.mdi-motorbike:before{content:"\F37C"}.mdi-mouse:before{content:"\F37D"}.mdi-mouse-bluetooth:before{content:"\F98A"}.mdi-mouse-off:before{content:"\F37E"}.mdi-mouse-variant:before{content:"\F37F"}.mdi-mouse-variant-off:before{content:"\F380"}.mdi-move-resize:before{content:"\F655"}.mdi-move-resize-variant:before{content:"\F656"}.mdi-movie:before{content:"\F381"}.mdi-movie-outline:before{content:"\FDB9"}.mdi-movie-roll:before{content:"\F7DD"}.mdi-muffin:before{content:"\F98B"}.mdi-multiplication:before{content:"\F382"}.mdi-multiplication-box:before{content:"\F383"}.mdi-mushroom:before{content:"\F7DE"}.mdi-mushroom-outline:before{content:"\F7DF"}.mdi-music:before{content:"\F759"}.mdi-music-box:before{content:"\F384"}.mdi-music-box-outline:before{content:"\F385"}.mdi-music-circle:before{content:"\F386"}.mdi-music-circle-outline:before{content:"\FAD3"}.mdi-music-note:before{content:"\F387"}.mdi-music-note-bluetooth:before{content:"\F5FE"}.mdi-music-note-bluetooth-off:before{content:"\F5FF"}.mdi-music-note-eighth:before{content:"\F388"}.mdi-music-note-half:before{content:"\F389"}.mdi-music-note-off:before{content:"\F38A"}.mdi-music-note-plus:before{content:"\FDBA"}.mdi-music-note-quarter:before{content:"\F38B"}.mdi-music-note-sixteenth:before{content:"\F38C"}.mdi-music-note-whole:before{content:"\F38D"}.mdi-music-off:before{content:"\F75A"}.mdi-nail:before{content:"\FDBB"}.mdi-nas:before{content:"\F8F2"}.mdi-nativescript:before{content:"\F87F"}.mdi-nature:before{content:"\F38E"}.mdi-nature-people:before{content:"\F38F"}.mdi-navigation:before{content:"\F390"}.mdi-near-me:before{content:"\F5CD"}.mdi-needle:before{content:"\F391"}.mdi-netflix:before{content:"\F745"}.mdi-network:before{content:"\F6F2"}.mdi-network-off:before{content:"\FC77"}.mdi-network-off-outline:before{content:"\FC78"}.mdi-network-outline:before{content:"\FC79"}.mdi-network-strength-1:before{content:"\F8F3"}.mdi-network-strength-1-alert:before{content:"\F8F4"}.mdi-network-strength-2:before{content:"\F8F5"}.mdi-network-strength-2-alert:before{content:"\F8F6"}.mdi-network-strength-3:before{content:"\F8F7"}.mdi-network-strength-3-alert:before{content:"\F8F8"}.mdi-network-strength-4:before{content:"\F8F9"}.mdi-network-strength-4-alert:before{content:"\F8FA"}.mdi-network-strength-off:before{content:"\F8FB"}.mdi-network-strength-off-outline:before{content:"\F8FC"}.mdi-network-strength-outline:before{content:"\F8FD"}.mdi-new-box:before{content:"\F394"}.mdi-newspaper:before{content:"\F395"}.mdi-nfc:before{content:"\F396"}.mdi-nfc-tap:before{content:"\F397"}.mdi-nfc-variant:before{content:"\F398"}.mdi-ninja:before{content:"\F773"}.mdi-nintendo-switch:before{content:"\F7E0"}.mdi-nodejs:before{content:"\F399"}.mdi-not-equal:before{content:"\F98C"}.mdi-not-equal-variant:before{content:"\F98D"}.mdi-note:before{content:"\F39A"}.mdi-note-multiple:before{content:"\F6B7"}.mdi-note-multiple-outline:before{content:"\F6B8"}.mdi-note-outline:before{content:"\F39B"}.mdi-note-plus:before{content:"\F39C"}.mdi-note-plus-outline:before{content:"\F39D"}.mdi-note-text:before{content:"\F39E"}.mdi-notebook:before{content:"\F82D"}.mdi-notification-clear-all:before{content:"\F39F"}.mdi-npm:before{content:"\F6F6"}.mdi-npm-variant:before{content:"\F98E"}.mdi-npm-variant-outline:before{content:"\F98F"}.mdi-nuke:before{content:"\F6A3"}.mdi-null:before{content:"\F7E1"}.mdi-numeric:before{content:"\F3A0"}.mdi-numeric-0:before{content:"\30"}.mdi-numeric-0-box:before{content:"\F3A1"}.mdi-numeric-0-box-multiple-outline:before{content:"\F3A2"}.mdi-numeric-0-box-outline:before{content:"\F3A3"}.mdi-numeric-0-circle:before{content:"\FC7A"}.mdi-numeric-0-circle-outline:before{content:"\FC7B"}.mdi-numeric-1:before{content:"\31"}.mdi-numeric-1-box:before{content:"\F3A4"}.mdi-numeric-1-box-multiple-outline:before{content:"\F3A5"}.mdi-numeric-1-box-outline:before{content:"\F3A6"}.mdi-numeric-1-circle:before{content:"\FC7C"}.mdi-numeric-1-circle-outline:before{content:"\FC7D"}.mdi-numeric-2:before{content:"\32"}.mdi-numeric-2-box:before{content:"\F3A7"}.mdi-numeric-2-box-multiple-outline:before{content:"\F3A8"}.mdi-numeric-2-box-outline:before{content:"\F3A9"}.mdi-numeric-2-circle:before{content:"\FC7E"}.mdi-numeric-2-circle-outline:before{content:"\FC7F"}.mdi-numeric-3:before{content:"\33"}.mdi-numeric-3-box:before{content:"\F3AA"}.mdi-numeric-3-box-multiple-outline:before{content:"\F3AB"}.mdi-numeric-3-box-outline:before{content:"\F3AC"}.mdi-numeric-3-circle:before{content:"\FC80"}.mdi-numeric-3-circle-outline:before{content:"\FC81"}.mdi-numeric-4:before{content:"\34"}.mdi-numeric-4-box:before{content:"\F3AD"}.mdi-numeric-4-box-multiple-outline:before{content:"\F3AE"}.mdi-numeric-4-box-outline:before{content:"\F3AF"}.mdi-numeric-4-circle:before{content:"\FC82"}.mdi-numeric-4-circle-outline:before{content:"\FC83"}.mdi-numeric-5:before{content:"\35"}.mdi-numeric-5-box:before{content:"\F3B0"}.mdi-numeric-5-box-multiple-outline:before{content:"\F3B1"}.mdi-numeric-5-box-outline:before{content:"\F3B2"}.mdi-numeric-5-circle:before{content:"\FC84"}.mdi-numeric-5-circle-outline:before{content:"\FC85"}.mdi-numeric-6:before{content:"\36"}.mdi-numeric-6-box:before{content:"\F3B3"}.mdi-numeric-6-box-multiple-outline:before{content:"\F3B4"}.mdi-numeric-6-box-outline:before{content:"\F3B5"}.mdi-numeric-6-circle:before{content:"\FC86"}.mdi-numeric-6-circle-outline:before{content:"\FC87"}.mdi-numeric-7:before{content:"\37"}.mdi-numeric-7-box:before{content:"\F3B6"}.mdi-numeric-7-box-multiple-outline:before{content:"\F3B7"}.mdi-numeric-7-box-outline:before{content:"\F3B8"}.mdi-numeric-7-circle:before{content:"\FC88"}.mdi-numeric-7-circle-outline:before{content:"\FC89"}.mdi-numeric-8:before{content:"\38"}.mdi-numeric-8-box:before{content:"\F3B9"}.mdi-numeric-8-box-multiple-outline:before{content:"\F3BA"}.mdi-numeric-8-box-outline:before{content:"\F3BB"}.mdi-numeric-8-circle:before{content:"\FC8A"}.mdi-numeric-8-circle-outline:before{content:"\FC8B"}.mdi-numeric-9:before{content:"\39"}.mdi-numeric-9-box:before{content:"\F3BC"}.mdi-numeric-9-box-multiple-outline:before{content:"\F3BD"}.mdi-numeric-9-box-outline:before{content:"\F3BE"}.mdi-numeric-9-circle:before{content:"\FC8C"}.mdi-numeric-9-circle-outline:before{content:"\FC8D"}.mdi-numeric-9-plus-box:before{content:"\F3BF"}.mdi-numeric-9-plus-box-multiple-outline:before{content:"\F3C0"}.mdi-numeric-9-plus-box-outline:before{content:"\F3C1"}.mdi-numeric-9-plus-circle:before{content:"\FC8E"}.mdi-numeric-9-plus-circle-outline:before{content:"\FC8F"}.mdi-nut:before{content:"\F6F7"}.mdi-nutrition:before{content:"\F3C2"}.mdi-oar:before{content:"\F67B"}.mdi-ocarina:before{content:"\FDBC"}.mdi-octagon:before{content:"\F3C3"}.mdi-octagon-outline:before{content:"\F3C4"}.mdi-octagram:before{content:"\F6F8"}.mdi-octagram-outline:before{content:"\F774"}.mdi-odnoklassniki:before{content:"\F3C5"}.mdi-office:before{content:"\F3C6"}.mdi-office-building:before{content:"\F990"}.mdi-oil:before{content:"\F3C7"}.mdi-oil-temperature:before{content:"\F3C8"}.mdi-omega:before{content:"\F3C9"}.mdi-one-up:before{content:"\FB89"}.mdi-onedrive:before{content:"\F3CA"}.mdi-onenote:before{content:"\F746"}.mdi-onepassword:before{content:"\F880"}.mdi-opacity:before{content:"\F5CC"}.mdi-open-in-app:before{content:"\F3CB"}.mdi-open-in-new:before{content:"\F3CC"}.mdi-open-source-initiative:before{content:"\FB8A"}.mdi-openid:before{content:"\F3CD"}.mdi-opera:before{content:"\F3CE"}.mdi-orbit:before{content:"\F018"}.mdi-origin:before{content:"\FB2B"}.mdi-ornament:before{content:"\F3CF"}.mdi-ornament-variant:before{content:"\F3D0"}.mdi-outlook:before{content:"\FCFE"}.mdi-owl:before{content:"\F3D2"}.mdi-pac-man:before{content:"\FB8B"}.mdi-package:before{content:"\F3D3"}.mdi-package-down:before{content:"\F3D4"}.mdi-package-up:before{content:"\F3D5"}.mdi-package-variant:before{content:"\F3D6"}.mdi-package-variant-closed:before{content:"\F3D7"}.mdi-page-first:before{content:"\F600"}.mdi-page-last:before{content:"\F601"}.mdi-page-layout-body:before{content:"\F6F9"}.mdi-page-layout-footer:before{content:"\F6FA"}.mdi-page-layout-header:before{content:"\F6FB"}.mdi-page-layout-sidebar-left:before{content:"\F6FC"}.mdi-page-layout-sidebar-right:before{content:"\F6FD"}.mdi-page-next:before{content:"\FB8C"}.mdi-page-next-outline:before{content:"\FB8D"}.mdi-page-previous:before{content:"\FB8E"}.mdi-page-previous-outline:before{content:"\FB8F"}.mdi-palette:before{content:"\F3D8"}.mdi-palette-advanced:before{content:"\F3D9"}.mdi-palette-outline:before{content:"\FDE8"}.mdi-palette-swatch:before{content:"\F8B4"}.mdi-pan:before{content:"\FB90"}.mdi-pan-bottom-left:before{content:"\FB91"}.mdi-pan-bottom-right:before{content:"\FB92"}.mdi-pan-down:before{content:"\FB93"}.mdi-pan-horizontal:before{content:"\FB94"}.mdi-pan-left:before{content:"\FB95"}.mdi-pan-right:before{content:"\FB96"}.mdi-pan-top-left:before{content:"\FB97"}.mdi-pan-top-right:before{content:"\FB98"}.mdi-pan-up:before{content:"\FB99"}.mdi-pan-vertical:before{content:"\FB9A"}.mdi-panda:before{content:"\F3DA"}.mdi-pandora:before{content:"\F3DB"}.mdi-panorama:before{content:"\F3DC"}.mdi-panorama-fisheye:before{content:"\F3DD"}.mdi-panorama-horizontal:before{content:"\F3DE"}.mdi-panorama-vertical:before{content:"\F3DF"}.mdi-panorama-wide-angle:before{content:"\F3E0"}.mdi-paper-cut-vertical:before{content:"\F3E1"}.mdi-paperclip:before{content:"\F3E2"}.mdi-parachute:before{content:"\FC90"}.mdi-parachute-outline:before{content:"\FC91"}.mdi-parking:before{content:"\F3E3"}.mdi-passport:before{content:"\F7E2"}.mdi-passport-biometric:before{content:"\FDBD"}.mdi-patreon:before{content:"\F881"}.mdi-pause:before{content:"\F3E4"}.mdi-pause-circle:before{content:"\F3E5"}.mdi-pause-circle-outline:before{content:"\F3E6"}.mdi-pause-octagon:before{content:"\F3E7"}.mdi-pause-octagon-outline:before{content:"\F3E8"}.mdi-paw:before{content:"\F3E9"}.mdi-paw-off:before{content:"\F657"}.mdi-paypal:before{content:"\F882"}.mdi-peace:before{content:"\F883"}.mdi-pen:before{content:"\F3EA"}.mdi-pen-lock:before{content:"\FDBE"}.mdi-pen-minus:before{content:"\FDBF"}.mdi-pen-off:before{content:"\FDC0"}.mdi-pen-plus:before{content:"\FDC1"}.mdi-pen-remove:before{content:"\FDC2"}.mdi-pencil:before{content:"\F3EB"}.mdi-pencil-box:before{content:"\F3EC"}.mdi-pencil-box-outline:before{content:"\F3ED"}.mdi-pencil-circle:before{content:"\F6FE"}.mdi-pencil-circle-outline:before{content:"\F775"}.mdi-pencil-lock:before{content:"\F3EE"}.mdi-pencil-lock-outline:before{content:"\FDC3"}.mdi-pencil-minus:before{content:"\FDC4"}.mdi-pencil-minus-outline:before{content:"\FDC5"}.mdi-pencil-off:before{content:"\F3EF"}.mdi-pencil-off-outline:before{content:"\FDC6"}.mdi-pencil-outline:before{content:"\FC92"}.mdi-pencil-plus:before{content:"\FDC7"}.mdi-pencil-plus-outline:before{content:"\FDC8"}.mdi-pencil-remove:before{content:"\FDC9"}.mdi-pencil-remove-outline:before{content:"\FDCA"}.mdi-pentagon:before{content:"\F6FF"}.mdi-pentagon-outline:before{content:"\F700"}.mdi-percent:before{content:"\F3F0"}.mdi-periodic-table:before{content:"\F8B5"}.mdi-periodic-table-co2:before{content:"\F7E3"}.mdi-periscope:before{content:"\F747"}.mdi-perspective-less:before{content:"\FCFF"}.mdi-perspective-more:before{content:"\FD00"}.mdi-pharmacy:before{content:"\F3F1"}.mdi-phone:before{content:"\F3F2"}.mdi-phone-bluetooth:before{content:"\F3F3"}.mdi-phone-classic:before{content:"\F602"}.mdi-phone-forward:before{content:"\F3F4"}.mdi-phone-hangup:before{content:"\F3F5"}.mdi-phone-in-talk:before{content:"\F3F6"}.mdi-phone-incoming:before{content:"\F3F7"}.mdi-phone-lock:before{content:"\F3F8"}.mdi-phone-log:before{content:"\F3F9"}.mdi-phone-minus:before{content:"\F658"}.mdi-phone-missed:before{content:"\F3FA"}.mdi-phone-off:before{content:"\FDCB"}.mdi-phone-outgoing:before{content:"\F3FB"}.mdi-phone-outline:before{content:"\FDCC"}.mdi-phone-paused:before{content:"\F3FC"}.mdi-phone-plus:before{content:"\F659"}.mdi-phone-return:before{content:"\F82E"}.mdi-phone-rotate-landscape:before{content:"\F884"}.mdi-phone-rotate-portrait:before{content:"\F885"}.mdi-phone-settings:before{content:"\F3FD"}.mdi-phone-voip:before{content:"\F3FE"}.mdi-pi:before{content:"\F3FF"}.mdi-pi-box:before{content:"\F400"}.mdi-pi-hole:before{content:"\FDCD"}.mdi-piano:before{content:"\F67C"}.mdi-pickaxe:before{content:"\F8B6"}.mdi-pier:before{content:"\F886"}.mdi-pier-crane:before{content:"\F887"}.mdi-pig:before{content:"\F401"}.mdi-pill:before{content:"\F402"}.mdi-pillar:before{content:"\F701"}.mdi-pin:before{content:"\F403"}.mdi-pin-off:before{content:"\F404"}.mdi-pin-off-outline:before{content:"\F92F"}.mdi-pin-outline:before{content:"\F930"}.mdi-pine-tree:before{content:"\F405"}.mdi-pine-tree-box:before{content:"\F406"}.mdi-pinterest:before{content:"\F407"}.mdi-pinterest-box:before{content:"\F408"}.mdi-pinwheel:before{content:"\FAD4"}.mdi-pinwheel-outline:before{content:"\FAD5"}.mdi-pipe:before{content:"\F7E4"}.mdi-pipe-disconnected:before{content:"\F7E5"}.mdi-pipe-leak:before{content:"\F888"}.mdi-pirate:before{content:"\FA07"}.mdi-pistol:before{content:"\F702"}.mdi-piston:before{content:"\F889"}.mdi-pizza:before{content:"\F409"}.mdi-play:before{content:"\F40A"}.mdi-play-box-outline:before{content:"\F40B"}.mdi-play-circle:before{content:"\F40C"}.mdi-play-circle-outline:before{content:"\F40D"}.mdi-play-network:before{content:"\F88A"}.mdi-play-network-outline:before{content:"\FC93"}.mdi-play-pause:before{content:"\F40E"}.mdi-play-protected-content:before{content:"\F40F"}.mdi-play-speed:before{content:"\F8FE"}.mdi-playlist-check:before{content:"\F5C7"}.mdi-playlist-edit:before{content:"\F8FF"}.mdi-playlist-minus:before{content:"\F410"}.mdi-playlist-music:before{content:"\FC94"}.mdi-playlist-music-outline:before{content:"\FC95"}.mdi-playlist-play:before{content:"\F411"}.mdi-playlist-plus:before{content:"\F412"}.mdi-playlist-remove:before{content:"\F413"}.mdi-playlist-star:before{content:"\FDCE"}.mdi-playstation:before{content:"\F414"}.mdi-plex:before{content:"\F6B9"}.mdi-plus:before{content:"\F415"}.mdi-plus-box:before{content:"\F416"}.mdi-plus-box-outline:before{content:"\F703"}.mdi-plus-circle:before{content:"\F417"}.mdi-plus-circle-multiple-outline:before{content:"\F418"}.mdi-plus-circle-outline:before{content:"\F419"}.mdi-plus-minus:before{content:"\F991"}.mdi-plus-minus-box:before{content:"\F992"}.mdi-plus-network:before{content:"\F41A"}.mdi-plus-network-outline:before{content:"\FC96"}.mdi-plus-one:before{content:"\F41B"}.mdi-plus-outline:before{content:"\F704"}.mdi-pocket:before{content:"\F41C"}.mdi-podcast:before{content:"\F993"}.mdi-podium:before{content:"\FD01"}.mdi-podium-bronze:before{content:"\FD02"}.mdi-podium-gold:before{content:"\FD03"}.mdi-podium-silver:before{content:"\FD04"}.mdi-point-of-sale:before{content:"\FD6E"}.mdi-pokeball:before{content:"\F41D"}.mdi-pokemon-go:before{content:"\FA08"}.mdi-poker-chip:before{content:"\F82F"}.mdi-polaroid:before{content:"\F41E"}.mdi-poll:before{content:"\F41F"}.mdi-poll-box:before{content:"\F420"}.mdi-polymer:before{content:"\F421"}.mdi-pool:before{content:"\F606"}.mdi-popcorn:before{content:"\F422"}.mdi-postage-stamp:before{content:"\FC97"}.mdi-pot:before{content:"\F65A"}.mdi-pot-mix:before{content:"\F65B"}.mdi-pound:before{content:"\F423"}.mdi-pound-box:before{content:"\F424"}.mdi-power:before{content:"\F425"}.mdi-power-cycle:before{content:"\F900"}.mdi-power-off:before{content:"\F901"}.mdi-power-on:before{content:"\F902"}.mdi-power-plug:before{content:"\F6A4"}.mdi-power-plug-off:before{content:"\F6A5"}.mdi-power-settings:before{content:"\F426"}.mdi-power-sleep:before{content:"\F903"}.mdi-power-socket:before{content:"\F427"}.mdi-power-socket-au:before{content:"\F904"}.mdi-power-socket-eu:before{content:"\F7E6"}.mdi-power-socket-uk:before{content:"\F7E7"}.mdi-power-socket-us:before{content:"\F7E8"}.mdi-power-standby:before{content:"\F905"}.mdi-powershell:before{content:"\FA09"}.mdi-prescription:before{content:"\F705"}.mdi-presentation:before{content:"\F428"}.mdi-presentation-play:before{content:"\F429"}.mdi-printer:before{content:"\F42A"}.mdi-printer-3d:before{content:"\F42B"}.mdi-printer-alert:before{content:"\F42C"}.mdi-printer-settings:before{content:"\F706"}.mdi-printer-wireless:before{content:"\FA0A"}.mdi-priority-high:before{content:"\F603"}.mdi-priority-low:before{content:"\F604"}.mdi-professional-hexagon:before{content:"\F42D"}.mdi-progress-alert:before{content:"\FC98"}.mdi-progress-check:before{content:"\F994"}.mdi-progress-clock:before{content:"\F995"}.mdi-progress-download:before{content:"\F996"}.mdi-progress-upload:before{content:"\F997"}.mdi-progress-wrench:before{content:"\FC99"}.mdi-projector:before{content:"\F42E"}.mdi-projector-screen:before{content:"\F42F"}.mdi-publish:before{content:"\F6A6"}.mdi-pulse:before{content:"\F430"}.mdi-pumpkin:before{content:"\FB9B"}.mdi-puzzle:before{content:"\F431"}.mdi-puzzle-outline:before{content:"\FA65"}.mdi-qi:before{content:"\F998"}.mdi-qqchat:before{content:"\F605"}.mdi-qrcode:before{content:"\F432"}.mdi-qrcode-edit:before{content:"\F8B7"}.mdi-qrcode-scan:before{content:"\F433"}.mdi-quadcopter:before{content:"\F434"}.mdi-quality-high:before{content:"\F435"}.mdi-quality-low:before{content:"\FA0B"}.mdi-quality-medium:before{content:"\FA0C"}.mdi-quicktime:before{content:"\F436"}.mdi-quora:before{content:"\FD05"}.mdi-rabbit:before{content:"\F906"}.mdi-racing-helmet:before{content:"\FD6F"}.mdi-racquetball:before{content:"\FD70"}.mdi-radar:before{content:"\F437"}.mdi-radiator:before{content:"\F438"}.mdi-radiator-disabled:before{content:"\FAD6"}.mdi-radiator-off:before{content:"\FAD7"}.mdi-radio:before{content:"\F439"}.mdi-radio-am:before{content:"\FC9A"}.mdi-radio-fm:before{content:"\FC9B"}.mdi-radio-handheld:before{content:"\F43A"}.mdi-radio-tower:before{content:"\F43B"}.mdi-radioactive:before{content:"\F43C"}.mdi-radiobox-blank:before{content:"\F43D"}.mdi-radiobox-marked:before{content:"\F43E"}.mdi-radius:before{content:"\FC9C"}.mdi-radius-outline:before{content:"\FC9D"}.mdi-raspberry-pi:before{content:"\F43F"}.mdi-ray-end:before{content:"\F440"}.mdi-ray-end-arrow:before{content:"\F441"}.mdi-ray-start:before{content:"\F442"}.mdi-ray-start-arrow:before{content:"\F443"}.mdi-ray-start-end:before{content:"\F444"}.mdi-ray-vertex:before{content:"\F445"}.mdi-react:before{content:"\F707"}.mdi-read:before{content:"\F447"}.mdi-receipt:before{content:"\F449"}.mdi-record:before{content:"\F44A"}.mdi-record-player:before{content:"\F999"}.mdi-record-rec:before{content:"\F44B"}.mdi-recycle:before{content:"\F44C"}.mdi-reddit:before{content:"\F44D"}.mdi-redo:before{content:"\F44E"}.mdi-redo-variant:before{content:"\F44F"}.mdi-reflect-horizontal:before{content:"\FA0D"}.mdi-reflect-vertical:before{content:"\FA0E"}.mdi-refresh:before{content:"\F450"}.mdi-regex:before{content:"\F451"}.mdi-registered-trademark:before{content:"\FA66"}.mdi-relative-scale:before{content:"\F452"}.mdi-reload:before{content:"\F453"}.mdi-reminder:before{content:"\F88B"}.mdi-remote:before{content:"\F454"}.mdi-remote-desktop:before{content:"\F8B8"}.mdi-rename-box:before{content:"\F455"}.mdi-reorder-horizontal:before{content:"\F687"}.mdi-reorder-vertical:before{content:"\F688"}.mdi-repeat:before{content:"\F456"}.mdi-repeat-off:before{content:"\F457"}.mdi-repeat-once:before{content:"\F458"}.mdi-replay:before{content:"\F459"}.mdi-reply:before{content:"\F45A"}.mdi-reply-all:before{content:"\F45B"}.mdi-reproduction:before{content:"\F45C"}.mdi-resistor:before{content:"\FB1F"}.mdi-resistor-nodes:before{content:"\FB20"}.mdi-resize:before{content:"\FA67"}.mdi-resize-bottom-right:before{content:"\F45D"}.mdi-responsive:before{content:"\F45E"}.mdi-restart:before{content:"\F708"}.mdi-restart-off:before{content:"\FD71"}.mdi-restore:before{content:"\F99A"}.mdi-restore-clock:before{content:"\F6A7"}.mdi-rewind:before{content:"\F45F"}.mdi-rewind-10:before{content:"\FD06"}.mdi-rewind-30:before{content:"\FD72"}.mdi-rewind-outline:before{content:"\F709"}.mdi-rhombus:before{content:"\F70A"}.mdi-rhombus-medium:before{content:"\FA0F"}.mdi-rhombus-outline:before{content:"\F70B"}.mdi-rhombus-split:before{content:"\FA10"}.mdi-ribbon:before{content:"\F460"}.mdi-rice:before{content:"\F7E9"}.mdi-ring:before{content:"\F7EA"}.mdi-road:before{content:"\F461"}.mdi-road-variant:before{content:"\F462"}.mdi-robot:before{content:"\F6A8"}.mdi-robot-industrial:before{content:"\FB21"}.mdi-robot-vacuum:before{content:"\F70C"}.mdi-robot-vacuum-variant:before{content:"\F907"}.mdi-rocket:before{content:"\F463"}.mdi-roller-skate:before{content:"\FD07"}.mdi-rollerblade:before{content:"\FD08"}.mdi-rollupjs:before{content:"\FB9C"}.mdi-room-service:before{content:"\F88C"}.mdi-room-service-outline:before{content:"\FD73"}.mdi-rotate-3d:before{content:"\F464"}.mdi-rotate-left:before{content:"\F465"}.mdi-rotate-left-variant:before{content:"\F466"}.mdi-rotate-orbit:before{content:"\FD74"}.mdi-rotate-right:before{content:"\F467"}.mdi-rotate-right-variant:before{content:"\F468"}.mdi-rounded-corner:before{content:"\F607"}.mdi-router-wireless:before{content:"\F469"}.mdi-router-wireless-settings:before{content:"\FA68"}.mdi-routes:before{content:"\F46A"}.mdi-rowing:before{content:"\F608"}.mdi-rss:before{content:"\F46B"}.mdi-rss-box:before{content:"\F46C"}.mdi-ruby:before{content:"\FD09"}.mdi-rugby:before{content:"\FD75"}.mdi-ruler:before{content:"\F46D"}.mdi-ruler-square:before{content:"\FC9E"}.mdi-run:before{content:"\F70D"}.mdi-run-fast:before{content:"\F46E"}.mdi-sack:before{content:"\FD0A"}.mdi-sack-percent:before{content:"\FD0B"}.mdi-safe:before{content:"\FA69"}.mdi-safety-goggles:before{content:"\FD0C"}.mdi-sale:before{content:"\F46F"}.mdi-salesforce:before{content:"\F88D"}.mdi-sass:before{content:"\F7EB"}.mdi-satellite:before{content:"\F470"}.mdi-satellite-uplink:before{content:"\F908"}.mdi-satellite-variant:before{content:"\F471"}.mdi-sausage:before{content:"\F8B9"}.mdi-saxophone:before{content:"\F609"}.mdi-scale:before{content:"\F472"}.mdi-scale-balance:before{content:"\F5D1"}.mdi-scale-bathroom:before{content:"\F473"}.mdi-scanner:before{content:"\F6AA"}.mdi-scanner-off:before{content:"\F909"}.mdi-school:before{content:"\F474"}.mdi-scissors-cutting:before{content:"\FA6A"}.mdi-screen-rotation:before{content:"\F475"}.mdi-screen-rotation-lock:before{content:"\F476"}.mdi-screw-flat-top:before{content:"\FDCF"}.mdi-screw-lag:before{content:"\FDD0"}.mdi-screw-machine-flat-top:before{content:"\FDD1"}.mdi-screw-machine-round-top:before{content:"\FDD2"}.mdi-screw-round-top:before{content:"\FDD3"}.mdi-screwdriver:before{content:"\F477"}.mdi-script:before{content:"\FB9D"}.mdi-script-outline:before{content:"\F478"}.mdi-script-text:before{content:"\FB9E"}.mdi-script-text-outline:before{content:"\FB9F"}.mdi-sd:before{content:"\F479"}.mdi-seal:before{content:"\F47A"}.mdi-search-web:before{content:"\F70E"}.mdi-seat:before{content:"\FC9F"}.mdi-seat-flat:before{content:"\F47B"}.mdi-seat-flat-angled:before{content:"\F47C"}.mdi-seat-individual-suite:before{content:"\F47D"}.mdi-seat-legroom-extra:before{content:"\F47E"}.mdi-seat-legroom-normal:before{content:"\F47F"}.mdi-seat-legroom-reduced:before{content:"\F480"}.mdi-seat-outline:before{content:"\FCA0"}.mdi-seat-recline-extra:before{content:"\F481"}.mdi-seat-recline-normal:before{content:"\F482"}.mdi-seatbelt:before{content:"\FCA1"}.mdi-security:before{content:"\F483"}.mdi-security-network:before{content:"\F484"}.mdi-select:before{content:"\F485"}.mdi-select-all:before{content:"\F486"}.mdi-select-color:before{content:"\FD0D"}.mdi-select-compare:before{content:"\FAD8"}.mdi-select-drag:before{content:"\FA6B"}.mdi-select-inverse:before{content:"\F487"}.mdi-select-off:before{content:"\F488"}.mdi-selection:before{content:"\F489"}.mdi-selection-drag:before{content:"\FA6C"}.mdi-selection-ellipse:before{content:"\FD0E"}.mdi-selection-off:before{content:"\F776"}.mdi-send:before{content:"\F48A"}.mdi-send-circle:before{content:"\FDD4"}.mdi-send-circle-outline:before{content:"\FDD5"}.mdi-send-lock:before{content:"\F7EC"}.mdi-serial-port:before{content:"\F65C"}.mdi-server:before{content:"\F48B"}.mdi-server-minus:before{content:"\F48C"}.mdi-server-network:before{content:"\F48D"}.mdi-server-network-off:before{content:"\F48E"}.mdi-server-off:before{content:"\F48F"}.mdi-server-plus:before{content:"\F490"}.mdi-server-remove:before{content:"\F491"}.mdi-server-security:before{content:"\F492"}.mdi-set-all:before{content:"\F777"}.mdi-set-center:before{content:"\F778"}.mdi-set-center-right:before{content:"\F779"}.mdi-set-left:before{content:"\F77A"}.mdi-set-left-center:before{content:"\F77B"}.mdi-set-left-right:before{content:"\F77C"}.mdi-set-none:before{content:"\F77D"}.mdi-set-right:before{content:"\F77E"}.mdi-set-top-box:before{content:"\F99E"}.mdi-settings:before{content:"\F493"}.mdi-settings-box:before{content:"\F494"}.mdi-settings-helper:before{content:"\FA6D"}.mdi-settings-outline:before{content:"\F8BA"}.mdi-shape:before{content:"\F830"}.mdi-shape-circle-plus:before{content:"\F65D"}.mdi-shape-outline:before{content:"\F831"}.mdi-shape-plus:before{content:"\F495"}.mdi-shape-polygon-plus:before{content:"\F65E"}.mdi-shape-rectangle-plus:before{content:"\F65F"}.mdi-shape-square-plus:before{content:"\F660"}.mdi-share:before{content:"\F496"}.mdi-share-outline:before{content:"\F931"}.mdi-share-variant:before{content:"\F497"}.mdi-sheep:before{content:"\FCA2"}.mdi-shield:before{content:"\F498"}.mdi-shield-account:before{content:"\F88E"}.mdi-shield-account-outline:before{content:"\FA11"}.mdi-shield-airplane:before{content:"\F6BA"}.mdi-shield-airplane-outline:before{content:"\FCA3"}.mdi-shield-check:before{content:"\F565"}.mdi-shield-check-outline:before{content:"\FCA4"}.mdi-shield-cross:before{content:"\FCA5"}.mdi-shield-cross-outline:before{content:"\FCA6"}.mdi-shield-half-full:before{content:"\F77F"}.mdi-shield-home:before{content:"\F689"}.mdi-shield-home-outline:before{content:"\FCA7"}.mdi-shield-key:before{content:"\FBA0"}.mdi-shield-key-outline:before{content:"\FBA1"}.mdi-shield-link-variant:before{content:"\FD0F"}.mdi-shield-link-variant-outline:before{content:"\FD10"}.mdi-shield-lock:before{content:"\F99C"}.mdi-shield-lock-outline:before{content:"\FCA8"}.mdi-shield-off:before{content:"\F99D"}.mdi-shield-off-outline:before{content:"\F99B"}.mdi-shield-outline:before{content:"\F499"}.mdi-shield-plus:before{content:"\FAD9"}.mdi-shield-plus-outline:before{content:"\FADA"}.mdi-shield-remove:before{content:"\FADB"}.mdi-shield-remove-outline:before{content:"\FADC"}.mdi-shield-search:before{content:"\FD76"}.mdi-ship-wheel:before{content:"\F832"}.mdi-shoe-formal:before{content:"\FB22"}.mdi-shoe-heel:before{content:"\FB23"}.mdi-shoe-print:before{content:"\FDD6"}.mdi-shopify:before{content:"\FADD"}.mdi-shopping:before{content:"\F49A"}.mdi-shopping-music:before{content:"\F49B"}.mdi-shovel:before{content:"\F70F"}.mdi-shovel-off:before{content:"\F710"}.mdi-shower:before{content:"\F99F"}.mdi-shower-head:before{content:"\F9A0"}.mdi-shredder:before{content:"\F49C"}.mdi-shuffle:before{content:"\F49D"}.mdi-shuffle-disabled:before{content:"\F49E"}.mdi-shuffle-variant:before{content:"\F49F"}.mdi-sigma:before{content:"\F4A0"}.mdi-sigma-lower:before{content:"\F62B"}.mdi-sign-caution:before{content:"\F4A1"}.mdi-sign-direction:before{content:"\F780"}.mdi-sign-text:before{content:"\F781"}.mdi-signal:before{content:"\F4A2"}.mdi-signal-2g:before{content:"\F711"}.mdi-signal-3g:before{content:"\F712"}.mdi-signal-4g:before{content:"\F713"}.mdi-signal-5g:before{content:"\FA6E"}.mdi-signal-cellular-1:before{content:"\F8BB"}.mdi-signal-cellular-2:before{content:"\F8BC"}.mdi-signal-cellular-3:before{content:"\F8BD"}.mdi-signal-cellular-outline:before{content:"\F8BE"}.mdi-signal-hspa:before{content:"\F714"}.mdi-signal-hspa-plus:before{content:"\F715"}.mdi-signal-off:before{content:"\F782"}.mdi-signal-variant:before{content:"\F60A"}.mdi-signature:before{content:"\FDD7"}.mdi-signature-freehand:before{content:"\FDD8"}.mdi-signature-image:before{content:"\FDD9"}.mdi-signature-text:before{content:"\FDDA"}.mdi-silo:before{content:"\FB24"}.mdi-silverware:before{content:"\F4A3"}.mdi-silverware-fork:before{content:"\F4A4"}.mdi-silverware-fork-knife:before{content:"\FA6F"}.mdi-silverware-spoon:before{content:"\F4A5"}.mdi-silverware-variant:before{content:"\F4A6"}.mdi-sim:before{content:"\F4A7"}.mdi-sim-alert:before{content:"\F4A8"}.mdi-sim-off:before{content:"\F4A9"}.mdi-sina-weibo:before{content:"\FADE"}.mdi-sitemap:before{content:"\F4AA"}.mdi-skate:before{content:"\FD11"}.mdi-skew-less:before{content:"\FD12"}.mdi-skew-more:before{content:"\FD13"}.mdi-skip-backward:before{content:"\F4AB"}.mdi-skip-forward:before{content:"\F4AC"}.mdi-skip-next:before{content:"\F4AD"}.mdi-skip-next-circle:before{content:"\F661"}.mdi-skip-next-circle-outline:before{content:"\F662"}.mdi-skip-previous:before{content:"\F4AE"}.mdi-skip-previous-circle:before{content:"\F663"}.mdi-skip-previous-circle-outline:before{content:"\F664"}.mdi-skull:before{content:"\F68B"}.mdi-skull-crossbones:before{content:"\FBA2"}.mdi-skull-crossbones-outline:before{content:"\FBA3"}.mdi-skull-outline:before{content:"\FBA4"}.mdi-skype:before{content:"\F4AF"}.mdi-skype-business:before{content:"\F4B0"}.mdi-slack:before{content:"\F4B1"}.mdi-slackware:before{content:"\F90A"}.mdi-sleep:before{content:"\F4B2"}.mdi-sleep-off:before{content:"\F4B3"}.mdi-slope-downhill:before{content:"\FDDB"}.mdi-slope-uphill:before{content:"\FDDC"}.mdi-smog:before{content:"\FA70"}.mdi-smoke-detector:before{content:"\F392"}.mdi-smoking:before{content:"\F4B4"}.mdi-smoking-off:before{content:"\F4B5"}.mdi-snapchat:before{content:"\F4B6"}.mdi-snowflake:before{content:"\F716"}.mdi-snowman:before{content:"\F4B7"}.mdi-soccer:before{content:"\F4B8"}.mdi-soccer-field:before{content:"\F833"}.mdi-sofa:before{content:"\F4B9"}.mdi-solar-panel:before{content:"\FD77"}.mdi-solar-panel-large:before{content:"\FD78"}.mdi-solar-power:before{content:"\FA71"}.mdi-solid:before{content:"\F68C"}.mdi-sort:before{content:"\F4BA"}.mdi-sort-alphabetical:before{content:"\F4BB"}.mdi-sort-ascending:before{content:"\F4BC"}.mdi-sort-descending:before{content:"\F4BD"}.mdi-sort-numeric:before{content:"\F4BE"}.mdi-sort-variant:before{content:"\F4BF"}.mdi-sort-variant-lock:before{content:"\FCA9"}.mdi-sort-variant-lock-open:before{content:"\FCAA"}.mdi-soundcloud:before{content:"\F4C0"}.mdi-source-branch:before{content:"\F62C"}.mdi-source-commit:before{content:"\F717"}.mdi-source-commit-end:before{content:"\F718"}.mdi-source-commit-end-local:before{content:"\F719"}.mdi-source-commit-local:before{content:"\F71A"}.mdi-source-commit-next-local:before{content:"\F71B"}.mdi-source-commit-start:before{content:"\F71C"}.mdi-source-commit-start-next-local:before{content:"\F71D"}.mdi-source-fork:before{content:"\F4C1"}.mdi-source-merge:before{content:"\F62D"}.mdi-source-pull:before{content:"\F4C2"}.mdi-source-repository:before{content:"\FCAB"}.mdi-source-repository-multiple:before{content:"\FCAC"}.mdi-soy-sauce:before{content:"\F7ED"}.mdi-spa:before{content:"\FCAD"}.mdi-spa-outline:before{content:"\FCAE"}.mdi-space-invaders:before{content:"\FBA5"}.mdi-speaker:before{content:"\F4C3"}.mdi-speaker-bluetooth:before{content:"\F9A1"}.mdi-speaker-multiple:before{content:"\FD14"}.mdi-speaker-off:before{content:"\F4C4"}.mdi-speaker-wireless:before{content:"\F71E"}.mdi-speedometer:before{content:"\F4C5"}.mdi-spellcheck:before{content:"\F4C6"}.mdi-spider-web:before{content:"\FBA6"}.mdi-spotify:before{content:"\F4C7"}.mdi-spotlight:before{content:"\F4C8"}.mdi-spotlight-beam:before{content:"\F4C9"}.mdi-spray:before{content:"\F665"}.mdi-spray-bottle:before{content:"\FADF"}.mdi-square:before{content:"\F763"}.mdi-square-edit-outline:before{content:"\F90B"}.mdi-square-inc:before{content:"\F4CA"}.mdi-square-inc-cash:before{content:"\F4CB"}.mdi-square-medium:before{content:"\FA12"}.mdi-square-medium-outline:before{content:"\FA13"}.mdi-square-outline:before{content:"\F762"}.mdi-square-root:before{content:"\F783"}.mdi-square-root-box:before{content:"\F9A2"}.mdi-square-small:before{content:"\FA14"}.mdi-squeegee:before{content:"\FAE0"}.mdi-ssh:before{content:"\F8BF"}.mdi-stack-exchange:before{content:"\F60B"}.mdi-stack-overflow:before{content:"\F4CC"}.mdi-stadium:before{content:"\F71F"}.mdi-stairs:before{content:"\F4CD"}.mdi-stamper:before{content:"\FD15"}.mdi-standard-definition:before{content:"\F7EE"}.mdi-star:before{content:"\F4CE"}.mdi-star-box:before{content:"\FA72"}.mdi-star-box-outline:before{content:"\FA73"}.mdi-star-circle:before{content:"\F4CF"}.mdi-star-circle-outline:before{content:"\F9A3"}.mdi-star-face:before{content:"\F9A4"}.mdi-star-four-points:before{content:"\FAE1"}.mdi-star-four-points-outline:before{content:"\FAE2"}.mdi-star-half:before{content:"\F4D0"}.mdi-star-off:before{content:"\F4D1"}.mdi-star-outline:before{content:"\F4D2"}.mdi-star-three-points:before{content:"\FAE3"}.mdi-star-three-points-outline:before{content:"\FAE4"}.mdi-steam:before{content:"\F4D3"}.mdi-steam-box:before{content:"\F90C"}.mdi-steering:before{content:"\F4D4"}.mdi-steering-off:before{content:"\F90D"}.mdi-step-backward:before{content:"\F4D5"}.mdi-step-backward-2:before{content:"\F4D6"}.mdi-step-forward:before{content:"\F4D7"}.mdi-step-forward-2:before{content:"\F4D8"}.mdi-stethoscope:before{content:"\F4D9"}.mdi-sticker:before{content:"\F5D0"}.mdi-sticker-emoji:before{content:"\F784"}.mdi-stocking:before{content:"\F4DA"}.mdi-stop:before{content:"\F4DB"}.mdi-stop-circle:before{content:"\F666"}.mdi-stop-circle-outline:before{content:"\F667"}.mdi-store:before{content:"\F4DC"}.mdi-store-24-hour:before{content:"\F4DD"}.mdi-stove:before{content:"\F4DE"}.mdi-strava:before{content:"\FB25"}.mdi-subdirectory-arrow-left:before{content:"\F60C"}.mdi-subdirectory-arrow-right:before{content:"\F60D"}.mdi-subtitles:before{content:"\FA15"}.mdi-subtitles-outline:before{content:"\FA16"}.mdi-subway:before{content:"\F6AB"}.mdi-subway-alert-variant:before{content:"\FD79"}.mdi-subway-variant:before{content:"\F4DF"}.mdi-summit:before{content:"\F785"}.mdi-sunglasses:before{content:"\F4E0"}.mdi-surround-sound:before{content:"\F5C5"}.mdi-surround-sound-2-0:before{content:"\F7EF"}.mdi-surround-sound-3-1:before{content:"\F7F0"}.mdi-surround-sound-5-1:before{content:"\F7F1"}.mdi-surround-sound-7-1:before{content:"\F7F2"}.mdi-svg:before{content:"\F720"}.mdi-swap-horizontal:before{content:"\F4E1"}.mdi-swap-horizontal-bold:before{content:"\FBA9"}.mdi-swap-horizontal-variant:before{content:"\F8C0"}.mdi-swap-vertical:before{content:"\F4E2"}.mdi-swap-vertical-bold:before{content:"\FBAA"}.mdi-swap-vertical-variant:before{content:"\F8C1"}.mdi-swim:before{content:"\F4E3"}.mdi-switch:before{content:"\F4E4"}.mdi-sword:before{content:"\F4E5"}.mdi-sword-cross:before{content:"\F786"}.mdi-symfony:before{content:"\FAE5"}.mdi-sync:before{content:"\F4E6"}.mdi-sync-alert:before{content:"\F4E7"}.mdi-sync-off:before{content:"\F4E8"}.mdi-tab:before{content:"\F4E9"}.mdi-tab-minus:before{content:"\FB26"}.mdi-tab-plus:before{content:"\F75B"}.mdi-tab-remove:before{content:"\FB27"}.mdi-tab-unselected:before{content:"\F4EA"}.mdi-table:before{content:"\F4EB"}.mdi-table-border:before{content:"\FA17"}.mdi-table-column:before{content:"\F834"}.mdi-table-column-plus-after:before{content:"\F4EC"}.mdi-table-column-plus-before:before{content:"\F4ED"}.mdi-table-column-remove:before{content:"\F4EE"}.mdi-table-column-width:before{content:"\F4EF"}.mdi-table-edit:before{content:"\F4F0"}.mdi-table-large:before{content:"\F4F1"}.mdi-table-merge-cells:before{content:"\F9A5"}.mdi-table-of-contents:before{content:"\F835"}.mdi-table-plus:before{content:"\FA74"}.mdi-table-remove:before{content:"\FA75"}.mdi-table-row:before{content:"\F836"}.mdi-table-row-height:before{content:"\F4F2"}.mdi-table-row-plus-after:before{content:"\F4F3"}.mdi-table-row-plus-before:before{content:"\F4F4"}.mdi-table-row-remove:before{content:"\F4F5"}.mdi-table-search:before{content:"\F90E"}.mdi-table-settings:before{content:"\F837"}.mdi-tablet:before{content:"\F4F6"}.mdi-tablet-android:before{content:"\F4F7"}.mdi-tablet-cellphone:before{content:"\F9A6"}.mdi-tablet-ipad:before{content:"\F4F8"}.mdi-taco:before{content:"\F761"}.mdi-tag:before{content:"\F4F9"}.mdi-tag-faces:before{content:"\F4FA"}.mdi-tag-heart:before{content:"\F68A"}.mdi-tag-heart-outline:before{content:"\FBAB"}.mdi-tag-minus:before{content:"\F90F"}.mdi-tag-multiple:before{content:"\F4FB"}.mdi-tag-outline:before{content:"\F4FC"}.mdi-tag-plus:before{content:"\F721"}.mdi-tag-remove:before{content:"\F722"}.mdi-tag-text-outline:before{content:"\F4FD"}.mdi-tank:before{content:"\FD16"}.mdi-tape-measure:before{content:"\FB28"}.mdi-target:before{content:"\F4FE"}.mdi-target-account:before{content:"\FBAC"}.mdi-target-variant:before{content:"\FA76"}.mdi-taxi:before{content:"\F4FF"}.mdi-tea:before{content:"\FD7A"}.mdi-tea-outline:before{content:"\FD7B"}.mdi-teach:before{content:"\F88F"}.mdi-teamviewer:before{content:"\F500"}.mdi-telegram:before{content:"\F501"}.mdi-telescope:before{content:"\FB29"}.mdi-television:before{content:"\F502"}.mdi-television-box:before{content:"\F838"}.mdi-television-classic:before{content:"\F7F3"}.mdi-television-classic-off:before{content:"\F839"}.mdi-television-guide:before{content:"\F503"}.mdi-television-off:before{content:"\F83A"}.mdi-temperature-celsius:before{content:"\F504"}.mdi-temperature-fahrenheit:before{content:"\F505"}.mdi-temperature-kelvin:before{content:"\F506"}.mdi-tennis:before{content:"\FD7C"}.mdi-tennis-ball:before{content:"\F507"}.mdi-tent:before{content:"\F508"}.mdi-terrain:before{content:"\F509"}.mdi-test-tube:before{content:"\F668"}.mdi-test-tube-empty:before{content:"\F910"}.mdi-test-tube-off:before{content:"\F911"}.mdi-text:before{content:"\F9A7"}.mdi-text-shadow:before{content:"\F669"}.mdi-text-short:before{content:"\F9A8"}.mdi-text-subject:before{content:"\F9A9"}.mdi-text-to-speech:before{content:"\F50A"}.mdi-text-to-speech-off:before{content:"\F50B"}.mdi-textbox:before{content:"\F60E"}.mdi-textbox-password:before{content:"\F7F4"}.mdi-texture:before{content:"\F50C"}.mdi-theater:before{content:"\F50D"}.mdi-theme-light-dark:before{content:"\F50E"}.mdi-thermometer:before{content:"\F50F"}.mdi-thermometer-alert:before{content:"\FDDD"}.mdi-thermometer-chevron-down:before{content:"\FDDE"}.mdi-thermometer-chevron-up:before{content:"\FDDF"}.mdi-thermometer-lines:before{content:"\F510"}.mdi-thermometer-minus:before{content:"\FDE0"}.mdi-thermometer-plus:before{content:"\FDE1"}.mdi-thermostat:before{content:"\F393"}.mdi-thermostat-box:before{content:"\F890"}.mdi-thought-bubble:before{content:"\F7F5"}.mdi-thought-bubble-outline:before{content:"\F7F6"}.mdi-thumb-down:before{content:"\F511"}.mdi-thumb-down-outline:before{content:"\F512"}.mdi-thumb-up:before{content:"\F513"}.mdi-thumb-up-outline:before{content:"\F514"}.mdi-thumbs-up-down:before{content:"\F515"}.mdi-ticket:before{content:"\F516"}.mdi-ticket-account:before{content:"\F517"}.mdi-ticket-confirmation:before{content:"\F518"}.mdi-ticket-outline:before{content:"\F912"}.mdi-ticket-percent:before{content:"\F723"}.mdi-tie:before{content:"\F519"}.mdi-tilde:before{content:"\F724"}.mdi-timelapse:before{content:"\F51A"}.mdi-timeline:before{content:"\FBAD"}.mdi-timeline-outline:before{content:"\FBAE"}.mdi-timeline-text:before{content:"\FBAF"}.mdi-timeline-text-outline:before{content:"\FBB0"}.mdi-timer:before{content:"\F51B"}.mdi-timer-10:before{content:"\F51C"}.mdi-timer-3:before{content:"\F51D"}.mdi-timer-off:before{content:"\F51E"}.mdi-timer-sand:before{content:"\F51F"}.mdi-timer-sand-empty:before{content:"\F6AC"}.mdi-timer-sand-full:before{content:"\F78B"}.mdi-timetable:before{content:"\F520"}.mdi-toaster-oven:before{content:"\FCAF"}.mdi-toggle-switch:before{content:"\F521"}.mdi-toggle-switch-off:before{content:"\F522"}.mdi-toggle-switch-off-outline:before{content:"\FA18"}.mdi-toggle-switch-outline:before{content:"\FA19"}.mdi-toilet:before{content:"\F9AA"}.mdi-toolbox:before{content:"\F9AB"}.mdi-toolbox-outline:before{content:"\F9AC"}.mdi-tooltip:before{content:"\F523"}.mdi-tooltip-account:before{content:"\F00C"}.mdi-tooltip-edit:before{content:"\F524"}.mdi-tooltip-image:before{content:"\F525"}.mdi-tooltip-image-outline:before{content:"\FBB1"}.mdi-tooltip-outline:before{content:"\F526"}.mdi-tooltip-plus:before{content:"\FBB2"}.mdi-tooltip-plus-outline:before{content:"\F527"}.mdi-tooltip-text:before{content:"\F528"}.mdi-tooltip-text-outline:before{content:"\FBB3"}.mdi-tooth:before{content:"\F8C2"}.mdi-tooth-outline:before{content:"\F529"}.mdi-tor:before{content:"\F52A"}.mdi-tortoise:before{content:"\FD17"}.mdi-tournament:before{content:"\F9AD"}.mdi-tower-beach:before{content:"\F680"}.mdi-tower-fire:before{content:"\F681"}.mdi-towing:before{content:"\F83B"}.mdi-track-light:before{content:"\F913"}.mdi-trackpad:before{content:"\F7F7"}.mdi-trackpad-lock:before{content:"\F932"}.mdi-tractor:before{content:"\F891"}.mdi-trademark:before{content:"\FA77"}.mdi-traffic-light:before{content:"\F52B"}.mdi-train:before{content:"\F52C"}.mdi-train-car:before{content:"\FBB4"}.mdi-train-variant:before{content:"\F8C3"}.mdi-tram:before{content:"\F52D"}.mdi-transcribe:before{content:"\F52E"}.mdi-transcribe-close:before{content:"\F52F"}.mdi-transfer-down:before{content:"\FD7D"}.mdi-transfer-left:before{content:"\FD7E"}.mdi-transfer-right:before{content:"\F530"}.mdi-transfer-up:before{content:"\FD7F"}.mdi-transit-connection:before{content:"\FD18"}.mdi-transit-connection-variant:before{content:"\FD19"}.mdi-transit-transfer:before{content:"\F6AD"}.mdi-transition:before{content:"\F914"}.mdi-transition-masked:before{content:"\F915"}.mdi-translate:before{content:"\F5CA"}.mdi-translate-off:before{content:"\FDE2"}.mdi-transmission-tower:before{content:"\FD1A"}.mdi-trash-can:before{content:"\FA78"}.mdi-trash-can-outline:before{content:"\FA79"}.mdi-treasure-chest:before{content:"\F725"}.mdi-tree:before{content:"\F531"}.mdi-trello:before{content:"\F532"}.mdi-trending-down:before{content:"\F533"}.mdi-trending-neutral:before{content:"\F534"}.mdi-trending-up:before{content:"\F535"}.mdi-triangle:before{content:"\F536"}.mdi-triangle-outline:before{content:"\F537"}.mdi-triforce:before{content:"\FBB5"}.mdi-trophy:before{content:"\F538"}.mdi-trophy-award:before{content:"\F539"}.mdi-trophy-broken:before{content:"\FD80"}.mdi-trophy-outline:before{content:"\F53A"}.mdi-trophy-variant:before{content:"\F53B"}.mdi-trophy-variant-outline:before{content:"\F53C"}.mdi-truck:before{content:"\F53D"}.mdi-truck-check:before{content:"\FCB0"}.mdi-truck-delivery:before{content:"\F53E"}.mdi-truck-fast:before{content:"\F787"}.mdi-truck-trailer:before{content:"\F726"}.mdi-tshirt-crew:before{content:"\FA7A"}.mdi-tshirt-crew-outline:before{content:"\F53F"}.mdi-tshirt-v:before{content:"\FA7B"}.mdi-tshirt-v-outline:before{content:"\F540"}.mdi-tumble-dryer:before{content:"\F916"}.mdi-tumblr:before{content:"\F541"}.mdi-tumblr-box:before{content:"\F917"}.mdi-tumblr-reblog:before{content:"\F542"}.mdi-tune:before{content:"\F62E"}.mdi-tune-vertical:before{content:"\F66A"}.mdi-turnstile:before{content:"\FCB1"}.mdi-turnstile-outline:before{content:"\FCB2"}.mdi-turtle:before{content:"\FCB3"}.mdi-twitch:before{content:"\F543"}.mdi-twitter:before{content:"\F544"}.mdi-twitter-box:before{content:"\F545"}.mdi-twitter-circle:before{content:"\F546"}.mdi-twitter-retweet:before{content:"\F547"}.mdi-two-factor-authentication:before{content:"\F9AE"}.mdi-uber:before{content:"\F748"}.mdi-ubisoft:before{content:"\FBB6"}.mdi-ubuntu:before{content:"\F548"}.mdi-ultra-high-definition:before{content:"\F7F8"}.mdi-umbraco:before{content:"\F549"}.mdi-umbrella:before{content:"\F54A"}.mdi-umbrella-closed:before{content:"\F9AF"}.mdi-umbrella-outline:before{content:"\F54B"}.mdi-undo:before{content:"\F54C"}.mdi-undo-variant:before{content:"\F54D"}.mdi-unfold-less-horizontal:before{content:"\F54E"}.mdi-unfold-less-vertical:before{content:"\F75F"}.mdi-unfold-more-horizontal:before{content:"\F54F"}.mdi-unfold-more-vertical:before{content:"\F760"}.mdi-ungroup:before{content:"\F550"}.mdi-unity:before{content:"\F6AE"}.mdi-unreal:before{content:"\F9B0"}.mdi-untappd:before{content:"\F551"}.mdi-update:before{content:"\F6AF"}.mdi-upload:before{content:"\F552"}.mdi-upload-multiple:before{content:"\F83C"}.mdi-upload-network:before{content:"\F6F5"}.mdi-upload-network-outline:before{content:"\FCB4"}.mdi-upload-outline:before{content:"\FDE3"}.mdi-usb:before{content:"\F553"}.mdi-van-passenger:before{content:"\F7F9"}.mdi-van-utility:before{content:"\F7FA"}.mdi-vanish:before{content:"\F7FB"}.mdi-variable:before{content:"\FAE6"}.mdi-vector-arrange-above:before{content:"\F554"}.mdi-vector-arrange-below:before{content:"\F555"}.mdi-vector-bezier:before{content:"\FAE7"}.mdi-vector-circle:before{content:"\F556"}.mdi-vector-circle-variant:before{content:"\F557"}.mdi-vector-combine:before{content:"\F558"}.mdi-vector-curve:before{content:"\F559"}.mdi-vector-difference:before{content:"\F55A"}.mdi-vector-difference-ab:before{content:"\F55B"}.mdi-vector-difference-ba:before{content:"\F55C"}.mdi-vector-ellipse:before{content:"\F892"}.mdi-vector-intersection:before{content:"\F55D"}.mdi-vector-line:before{content:"\F55E"}.mdi-vector-point:before{content:"\F55F"}.mdi-vector-polygon:before{content:"\F560"}.mdi-vector-polyline:before{content:"\F561"}.mdi-vector-radius:before{content:"\F749"}.mdi-vector-rectangle:before{content:"\F5C6"}.mdi-vector-selection:before{content:"\F562"}.mdi-vector-square:before{content:"\F001"}.mdi-vector-triangle:before{content:"\F563"}.mdi-vector-union:before{content:"\F564"}.mdi-venmo:before{content:"\F578"}.mdi-vhs:before{content:"\FA1A"}.mdi-vibrate:before{content:"\F566"}.mdi-vibrate-off:before{content:"\FCB5"}.mdi-video:before{content:"\F567"}.mdi-video-3d:before{content:"\F7FC"}.mdi-video-4k-box:before{content:"\F83D"}.mdi-video-account:before{content:"\F918"}.mdi-video-image:before{content:"\F919"}.mdi-video-input-antenna:before{content:"\F83E"}.mdi-video-input-component:before{content:"\F83F"}.mdi-video-input-hdmi:before{content:"\F840"}.mdi-video-input-svideo:before{content:"\F841"}.mdi-video-minus:before{content:"\F9B1"}.mdi-video-off:before{content:"\F568"}.mdi-video-off-outline:before{content:"\FBB7"}.mdi-video-outline:before{content:"\FBB8"}.mdi-video-plus:before{content:"\F9B2"}.mdi-video-stabilization:before{content:"\F91A"}.mdi-video-switch:before{content:"\F569"}.mdi-video-vintage:before{content:"\FA1B"}.mdi-view-agenda:before{content:"\F56A"}.mdi-view-array:before{content:"\F56B"}.mdi-view-carousel:before{content:"\F56C"}.mdi-view-column:before{content:"\F56D"}.mdi-view-dashboard:before{content:"\F56E"}.mdi-view-dashboard-outline:before{content:"\FA1C"}.mdi-view-dashboard-variant:before{content:"\F842"}.mdi-view-day:before{content:"\F56F"}.mdi-view-grid:before{content:"\F570"}.mdi-view-headline:before{content:"\F571"}.mdi-view-list:before{content:"\F572"}.mdi-view-module:before{content:"\F573"}.mdi-view-parallel:before{content:"\F727"}.mdi-view-quilt:before{content:"\F574"}.mdi-view-sequential:before{content:"\F728"}.mdi-view-split-horizontal:before{content:"\FBA7"}.mdi-view-split-vertical:before{content:"\FBA8"}.mdi-view-stream:before{content:"\F575"}.mdi-view-week:before{content:"\F576"}.mdi-vimeo:before{content:"\F577"}.mdi-violin:before{content:"\F60F"}.mdi-virtual-reality:before{content:"\F893"}.mdi-visual-studio:before{content:"\F610"}.mdi-visual-studio-code:before{content:"\FA1D"}.mdi-vk:before{content:"\F579"}.mdi-vk-box:before{content:"\F57A"}.mdi-vk-circle:before{content:"\F57B"}.mdi-vlc:before{content:"\F57C"}.mdi-voice:before{content:"\F5CB"}.mdi-voicemail:before{content:"\F57D"}.mdi-volleyball:before{content:"\F9B3"}.mdi-volume-high:before{content:"\F57E"}.mdi-volume-low:before{content:"\F57F"}.mdi-volume-medium:before{content:"\F580"}.mdi-volume-minus:before{content:"\F75D"}.mdi-volume-mute:before{content:"\F75E"}.mdi-volume-off:before{content:"\F581"}.mdi-volume-plus:before{content:"\F75C"}.mdi-volume-variant-off:before{content:"\FDE4"}.mdi-vote:before{content:"\FA1E"}.mdi-vote-outline:before{content:"\FA1F"}.mdi-vpn:before{content:"\F582"}.mdi-vuejs:before{content:"\F843"}.mdi-walk:before{content:"\F583"}.mdi-wall:before{content:"\F7FD"}.mdi-wall-sconce:before{content:"\F91B"}.mdi-wall-sconce-flat:before{content:"\F91C"}.mdi-wall-sconce-variant:before{content:"\F91D"}.mdi-wallet:before{content:"\F584"}.mdi-wallet-giftcard:before{content:"\F585"}.mdi-wallet-membership:before{content:"\F586"}.mdi-wallet-outline:before{content:"\FBB9"}.mdi-wallet-travel:before{content:"\F587"}.mdi-wallpaper:before{content:"\FDE5"}.mdi-wan:before{content:"\F588"}.mdi-washing-machine:before{content:"\F729"}.mdi-watch:before{content:"\F589"}.mdi-watch-export:before{content:"\F58A"}.mdi-watch-export-variant:before{content:"\F894"}.mdi-watch-import:before{content:"\F58B"}.mdi-watch-import-variant:before{content:"\F895"}.mdi-watch-variant:before{content:"\F896"}.mdi-watch-vibrate:before{content:"\F6B0"}.mdi-watch-vibrate-off:before{content:"\FCB6"}.mdi-water:before{content:"\F58C"}.mdi-water-off:before{content:"\F58D"}.mdi-water-outline:before{content:"\FDE6"}.mdi-water-percent:before{content:"\F58E"}.mdi-water-pump:before{content:"\F58F"}.mdi-watermark:before{content:"\F612"}.mdi-waves:before{content:"\F78C"}.mdi-waze:before{content:"\FBBA"}.mdi-weather-cloudy:before{content:"\F590"}.mdi-weather-fog:before{content:"\F591"}.mdi-weather-hail:before{content:"\F592"}.mdi-weather-hurricane:before{content:"\F897"}.mdi-weather-lightning:before{content:"\F593"}.mdi-weather-lightning-rainy:before{content:"\F67D"}.mdi-weather-night:before{content:"\F594"}.mdi-weather-partlycloudy:before{content:"\F595"}.mdi-weather-pouring:before{content:"\F596"}.mdi-weather-rainy:before{content:"\F597"}.mdi-weather-snowy:before{content:"\F598"}.mdi-weather-snowy-rainy:before{content:"\F67E"}.mdi-weather-sunny:before{content:"\F599"}.mdi-weather-sunset:before{content:"\F59A"}.mdi-weather-sunset-down:before{content:"\F59B"}.mdi-weather-sunset-up:before{content:"\F59C"}.mdi-weather-windy:before{content:"\F59D"}.mdi-weather-windy-variant:before{content:"\F59E"}.mdi-web:before{content:"\F59F"}.mdi-webcam:before{content:"\F5A0"}.mdi-webhook:before{content:"\F62F"}.mdi-webpack:before{content:"\F72A"}.mdi-wechat:before{content:"\F611"}.mdi-weight:before{content:"\F5A1"}.mdi-weight-gram:before{content:"\FD1B"}.mdi-weight-kilogram:before{content:"\F5A2"}.mdi-weight-pound:before{content:"\F9B4"}.mdi-whatsapp:before{content:"\F5A3"}.mdi-wheelchair-accessibility:before{content:"\F5A4"}.mdi-whistle:before{content:"\F9B5"}.mdi-white-balance-auto:before{content:"\F5A5"}.mdi-white-balance-incandescent:before{content:"\F5A6"}.mdi-white-balance-iridescent:before{content:"\F5A7"}.mdi-white-balance-sunny:before{content:"\F5A8"}.mdi-widgets:before{content:"\F72B"}.mdi-wifi:before{content:"\F5A9"}.mdi-wifi-off:before{content:"\F5AA"}.mdi-wifi-star:before{content:"\FDE7"}.mdi-wifi-strength-1:before{content:"\F91E"}.mdi-wifi-strength-1-alert:before{content:"\F91F"}.mdi-wifi-strength-1-lock:before{content:"\F920"}.mdi-wifi-strength-2:before{content:"\F921"}.mdi-wifi-strength-2-alert:before{content:"\F922"}.mdi-wifi-strength-2-lock:before{content:"\F923"}.mdi-wifi-strength-3:before{content:"\F924"}.mdi-wifi-strength-3-alert:before{content:"\F925"}.mdi-wifi-strength-3-lock:before{content:"\F926"}.mdi-wifi-strength-4:before{content:"\F927"}.mdi-wifi-strength-4-alert:before{content:"\F928"}.mdi-wifi-strength-4-lock:before{content:"\F929"}.mdi-wifi-strength-alert-outline:before{content:"\F92A"}.mdi-wifi-strength-lock-outline:before{content:"\F92B"}.mdi-wifi-strength-off:before{content:"\F92C"}.mdi-wifi-strength-off-outline:before{content:"\F92D"}.mdi-wifi-strength-outline:before{content:"\F92E"}.mdi-wii:before{content:"\F5AB"}.mdi-wiiu:before{content:"\F72C"}.mdi-wikipedia:before{content:"\F5AC"}.mdi-wind-turbine:before{content:"\FD81"}.mdi-window-close:before{content:"\F5AD"}.mdi-window-closed:before{content:"\F5AE"}.mdi-window-maximize:before{content:"\F5AF"}.mdi-window-minimize:before{content:"\F5B0"}.mdi-window-open:before{content:"\F5B1"}.mdi-window-restore:before{content:"\F5B2"}.mdi-windows:before{content:"\F5B3"}.mdi-windows-classic:before{content:"\FA20"}.mdi-wiper:before{content:"\FAE8"}.mdi-wiper-wash:before{content:"\FD82"}.mdi-wordpress:before{content:"\F5B4"}.mdi-worker:before{content:"\F5B5"}.mdi-wrap:before{content:"\F5B6"}.mdi-wrap-disabled:before{content:"\FBBB"}.mdi-wrench:before{content:"\F5B7"}.mdi-wrench-outline:before{content:"\FBBC"}.mdi-wunderlist:before{content:"\F5B8"}.mdi-xamarin:before{content:"\F844"}.mdi-xamarin-outline:before{content:"\F845"}.mdi-xaml:before{content:"\F673"}.mdi-xbox:before{content:"\F5B9"}.mdi-xbox-controller:before{content:"\F5BA"}.mdi-xbox-controller-battery-alert:before{content:"\F74A"}.mdi-xbox-controller-battery-charging:before{content:"\FA21"}.mdi-xbox-controller-battery-empty:before{content:"\F74B"}.mdi-xbox-controller-battery-full:before{content:"\F74C"}.mdi-xbox-controller-battery-low:before{content:"\F74D"}.mdi-xbox-controller-battery-medium:before{content:"\F74E"}.mdi-xbox-controller-battery-unknown:before{content:"\F74F"}.mdi-xbox-controller-off:before{content:"\F5BB"}.mdi-xda:before{content:"\F5BC"}.mdi-xing:before{content:"\F5BD"}.mdi-xing-box:before{content:"\F5BE"}.mdi-xing-circle:before{content:"\F5BF"}.mdi-xml:before{content:"\F5C0"}.mdi-xmpp:before{content:"\F7FE"}.mdi-yahoo:before{content:"\FB2A"}.mdi-yammer:before{content:"\F788"}.mdi-yeast:before{content:"\F5C1"}.mdi-yelp:before{content:"\F5C2"}.mdi-yin-yang:before{content:"\F67F"}.mdi-youtube:before{content:"\F5C3"}.mdi-youtube-creator-studio:before{content:"\F846"}.mdi-youtube-gaming:before{content:"\F847"}.mdi-youtube-subscription:before{content:"\FD1C"}.mdi-youtube-tv:before{content:"\F448"}.mdi-z-wave:before{content:"\FAE9"}.mdi-zend:before{content:"\FAEA"}.mdi-zigbee:before{content:"\FD1D"}.mdi-zip-box:before{content:"\F5C4"}.mdi-zip-disk:before{content:"\FA22"}.mdi-zodiac-aquarius:before{content:"\FA7C"}.mdi-zodiac-aries:before{content:"\FA7D"}.mdi-zodiac-cancer:before{content:"\FA7E"}.mdi-zodiac-capricorn:before{content:"\FA7F"}.mdi-zodiac-gemini:before{content:"\FA80"}.mdi-zodiac-leo:before{content:"\FA81"}.mdi-zodiac-libra:before{content:"\FA82"}.mdi-zodiac-pisces:before{content:"\FA83"}.mdi-zodiac-sagittarius:before{content:"\FA84"}.mdi-zodiac-scorpio:before{content:"\FA85"}.mdi-zodiac-taurus:before{content:"\FA86"}.mdi-zodiac-virgo:before{content:"\FA87"}.mdi-blank:before{content:"\F68C";visibility:hidden}.mdi-18px.mdi-set,.mdi-18px.mdi:before{font-size:18px}.mdi-24px.mdi-set,.mdi-24px.mdi:before{font-size:24px}.mdi-36px.mdi-set,.mdi-36px.mdi:before{font-size:36px}.mdi-48px.mdi-set,.mdi-48px.mdi:before{font-size:48px}.mdi-dark:before{color:rgba(0,0,0,0.54)}.mdi-dark.mdi-inactive:before{color:rgba(0,0,0,0.26)}.mdi-light:before{color:#fff}.mdi-light.mdi-inactive:before{color:rgba(255,255,255,0.3)}.mdi-rotate-45:before{-webkit-transform:rotate(45deg);-ms-transform:rotate(45deg);transform:rotate(45deg)}.mdi-rotate-90:before{-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.mdi-rotate-135:before{-webkit-transform:rotate(135deg);-ms-transform:rotate(135deg);transform:rotate(135deg)}.mdi-rotate-180:before{-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.mdi-rotate-225:before{-webkit-transform:rotate(225deg);-ms-transform:rotate(225deg);transform:rotate(225deg)}.mdi-rotate-270:before{-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.mdi-rotate-315:before{-webkit-transform:rotate(315deg);-ms-transform:rotate(315deg);transform:rotate(315deg)}.mdi-flip-h:before{-webkit-transform:scaleX(-1);transform:scaleX(-1);filter:FlipH;-ms-filter:"FlipH"}.mdi-flip-v:before{-webkit-transform:scaleY(-1);transform:scaleY(-1);filter:FlipV;-ms-filter:"FlipV"}.mdi-spin:before{-webkit-animation:mdi-spin 2s infinite linear;animation:mdi-spin 2s infinite linear}@-webkit-keyframes mdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes mdi-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}
+/*# sourceMappingURL=materialdesignicons.min.css.map */
diff --git a/community_server/webroot/js/ensurePassphrase.js b/community_server/webroot/js/ensurePassphrase.js
index 485946050..e2f7a98bb 100644
--- a/community_server/webroot/js/ensurePassphrase.js
+++ b/community_server/webroot/js/ensurePassphrase.js
@@ -265,1165 +265,1165 @@ exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate :
}).call(this,require("timers").setImmediate,require("timers").clearImmediate)
},{"process/browser.js":1,"timers":2}],3:[function(require,module,exports){
(function (global,setImmediate){
-new function() {
-
-function Vnode(tag, key, attrs0, children, text, dom) {
- return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: {}, events: undefined, instance: undefined, skip: false}
-}
-Vnode.normalize = function(node) {
- if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)
- if (node != null && typeof node !== "object") return Vnode("#", undefined, undefined, node === false ? "" : node, undefined, undefined)
- return node
-}
-Vnode.normalizeChildren = function normalizeChildren(children) {
- for (var i = 0; i < children.length; i++) {
- children[i] = Vnode.normalize(children[i])
- }
- return children
-}
-var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
-var selectorCache = {}
-function hyperscript(selector) {
- if (selector == null || typeof selector !== "string" && typeof selector.view !== "function") {
- throw Error("The selector must be either a string or a component.");
- }
- if (typeof selector === "string" && selectorCache[selector] === undefined) {
- var match, tag, classes = [], attributes = {}
- while (match = selectorParser.exec(selector)) {
- var type = match[1], value = match[2]
- if (type === "" && value !== "") tag = value
- else if (type === "#") attributes.id = value
- else if (type === ".") classes.push(value)
- else if (match[3][0] === "[") {
- var attrValue = match[6]
- if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
- if (match[4] === "class") classes.push(attrValue)
- else attributes[match[4]] = attrValue || true
- }
- }
- if (classes.length > 0) attributes.className = classes.join(" ")
- selectorCache[selector] = function(attrs, children) {
- var hasAttrs = false, childList, text
- var className = attrs.className || attrs.class
- for (var key in attributes) attrs[key] = attributes[key]
- if (className !== undefined) {
- if (attrs.class !== undefined) {
- attrs.class = undefined
- attrs.className = className
- }
- if (attributes.className !== undefined) attrs.className = attributes.className + " " + className
- }
- for (var key in attrs) {
- if (key !== "key") {
- hasAttrs = true
- break
- }
- }
- if (Array.isArray(children) && children.length == 1 && children[0] != null && children[0].tag === "#") text = children[0].children
- else childList = children
- return Vnode(tag || "div", attrs.key, hasAttrs ? attrs : undefined, childList, text, undefined)
- }
- }
- var attrs, children, childrenIndex
- if (arguments[1] == null || typeof arguments[1] === "object" && arguments[1].tag === undefined && !Array.isArray(arguments[1])) {
- attrs = arguments[1]
- childrenIndex = 2
- }
- else childrenIndex = 1
- if (arguments.length === childrenIndex + 1) {
- children = Array.isArray(arguments[childrenIndex]) ? arguments[childrenIndex] : [arguments[childrenIndex]]
- }
- else {
- children = []
- for (var i = childrenIndex; i < arguments.length; i++) children.push(arguments[i])
- }
- if (typeof selector === "string") return selectorCache[selector](attrs || {}, Vnode.normalizeChildren(children))
- return Vnode(selector, attrs && attrs.key, attrs || {}, Vnode.normalizeChildren(children), undefined, undefined)
-}
-hyperscript.trust = function(html) {
- if (html == null) html = ""
- return Vnode("<", undefined, undefined, html, undefined, undefined)
-}
-hyperscript.fragment = function(attrs1, children) {
- return Vnode("[", attrs1.key, attrs1, Vnode.normalizeChildren(children), undefined, undefined)
-}
-var m = hyperscript
-/** @constructor */
-var PromisePolyfill = function(executor) {
- if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`")
- if (typeof executor !== "function") throw new TypeError("executor must be a function")
- var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false)
- var instance = self._instance = {resolvers: resolvers, rejectors: rejectors}
- var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
- function handler(list, shouldAbsorb) {
- return function execute(value) {
- var then
- try {
- if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") {
- if (value === self) throw new TypeError("Promise can't be resolved w/ itself")
- executeOnce(then.bind(value))
- }
- else {
- callAsync(function() {
- if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value)
- for (var i = 0; i < list.length; i++) list[i](value)
- resolvers.length = 0, rejectors.length = 0
- instance.state = shouldAbsorb
- instance.retry = function() {execute(value)}
- })
- }
- }
- catch (e) {
- rejectCurrent(e)
- }
- }
- }
- function executeOnce(then) {
- var runs = 0
- function run(fn) {
- return function(value) {
- if (runs++ > 0) return
- fn(value)
- }
- }
- var onerror = run(rejectCurrent)
- try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)}
- }
- executeOnce(executor)
-}
-PromisePolyfill.prototype.then = function(onFulfilled, onRejection) {
- var self = this, instance = self._instance
- function handle(callback, list, next, state) {
- list.push(function(value) {
- if (typeof callback !== "function") next(value)
- else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
- })
- if (typeof instance.retry === "function" && state === instance.state) instance.retry()
- }
- var resolveNext, rejectNext
- var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject})
- handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false)
- return promise
-}
-PromisePolyfill.prototype.catch = function(onRejection) {
- return this.then(null, onRejection)
-}
-PromisePolyfill.resolve = function(value) {
- if (value instanceof PromisePolyfill) return value
- return new PromisePolyfill(function(resolve) {resolve(value)})
-}
-PromisePolyfill.reject = function(value) {
- return new PromisePolyfill(function(resolve, reject) {reject(value)})
-}
-PromisePolyfill.all = function(list) {
- return new PromisePolyfill(function(resolve, reject) {
- var total = list.length, count = 0, values = []
- if (list.length === 0) resolve([])
- else for (var i = 0; i < list.length; i++) {
- (function(i) {
- function consume(value) {
- count++
- values[i] = value
- if (count === total) resolve(values)
- }
- if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") {
- list[i].then(consume, reject)
- }
- else consume(list[i])
- })(i)
- }
- })
-}
-PromisePolyfill.race = function(list) {
- return new PromisePolyfill(function(resolve, reject) {
- for (var i = 0; i < list.length; i++) {
- list[i].then(resolve, reject)
- }
- })
-}
-if (typeof window !== "undefined") {
- if (typeof window.Promise === "undefined") window.Promise = PromisePolyfill
- var PromisePolyfill = window.Promise
-} else if (typeof global !== "undefined") {
- if (typeof global.Promise === "undefined") global.Promise = PromisePolyfill
- var PromisePolyfill = global.Promise
-} else {
-}
-var buildQueryString = function(object) {
- if (Object.prototype.toString.call(object) !== "[object Object]") return ""
- var args = []
- for (var key0 in object) {
- destructure(key0, object[key0])
- }
- return args.join("&")
- function destructure(key0, value) {
- if (Array.isArray(value)) {
- for (var i = 0; i < value.length; i++) {
- destructure(key0 + "[" + i + "]", value[i])
- }
- }
- else if (Object.prototype.toString.call(value) === "[object Object]") {
- for (var i in value) {
- destructure(key0 + "[" + i + "]", value[i])
- }
- }
- else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : ""))
- }
-}
-var _8 = function($window, Promise) {
- var callbackCount = 0
- var oncompletion
- function setCompletionCallback(callback) {oncompletion = callback}
- function finalizer() {
- var count = 0
- function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
- return function finalize(promise0) {
- var then0 = promise0.then
- promise0.then = function() {
- count++
- var next = then0.apply(promise0, arguments)
- next.then(complete, function(e) {
- complete()
- if (count === 0) throw e
- })
- return finalize(next)
- }
- return promise0
- }
- }
- function normalize(args, extra) {
- if (typeof args === "string") {
- var url = args
- args = extra || {}
- if (args.url == null) args.url = url
- }
- return args
- }
- function request(args, extra) {
- var finalize = finalizer()
- args = normalize(args, extra)
- var promise0 = new Promise(function(resolve, reject) {
- if (args.method == null) args.method = "GET"
- args.method = args.method.toUpperCase()
- var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE"
- if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify
- if (typeof args.deserialize !== "function") args.deserialize = deserialize
- if (typeof args.extract !== "function") args.extract = extract
- args.url = interpolate(args.url, args.data)
- if (useBody) args.data = args.serialize(args.data)
- else args.url = assemble(args.url, args.data)
- var xhr = new $window.XMLHttpRequest()
- xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined)
- if (args.serialize === JSON.stringify && useBody) {
- xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8")
- }
- if (args.deserialize === deserialize) {
- xhr.setRequestHeader("Accept", "application/json, text/*")
- }
- if (args.withCredentials) xhr.withCredentials = args.withCredentials
- for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) {
- xhr.setRequestHeader(key, args.headers[key])
- }
- if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr
- xhr.onreadystatechange = function() {
- // Don't throw errors on xhr.abort(). XMLHttpRequests ends up in a state of
- // xhr.status == 0 and xhr.readyState == 4 if aborted after open, but before completion.
- if (xhr.status && xhr.readyState === 4) {
- try {
- var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args))
- if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
- resolve(cast(args.type, response))
- }
- else {
- var error = new Error(xhr.responseText)
- for (var key in response) error[key] = response[key]
- reject(error)
- }
- }
- catch (e) {
- reject(e)
- }
- }
- }
- if (useBody && (args.data != null)) xhr.send(args.data)
- else xhr.send()
- })
- return args.background === true ? promise0 : finalize(promise0)
- }
- function jsonp(args, extra) {
- var finalize = finalizer()
- args = normalize(args, extra)
- var promise0 = new Promise(function(resolve, reject) {
- var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
- var script = $window.document.createElement("script")
- $window[callbackName] = function(data) {
- script.parentNode.removeChild(script)
- resolve(cast(args.type, data))
- delete $window[callbackName]
- }
- script.onerror = function() {
- script.parentNode.removeChild(script)
- reject(new Error("JSONP request failed"))
- delete $window[callbackName]
- }
- if (args.data == null) args.data = {}
- args.url = interpolate(args.url, args.data)
- args.data[args.callbackKey || "callback"] = callbackName
- script.src = assemble(args.url, args.data)
- $window.document.documentElement.appendChild(script)
- })
- return args.background === true? promise0 : finalize(promise0)
- }
- function interpolate(url, data) {
- if (data == null) return url
- var tokens = url.match(/:[^\/]+/gi) || []
- for (var i = 0; i < tokens.length; i++) {
- var key = tokens[i].slice(1)
- if (data[key] != null) {
- url = url.replace(tokens[i], data[key])
- }
- }
- return url
- }
- function assemble(url, data) {
- var querystring = buildQueryString(data)
- if (querystring !== "") {
- var prefix = url.indexOf("?") < 0 ? "?" : "&"
- url += prefix + querystring
- }
- return url
- }
- function deserialize(data) {
- try {return data !== "" ? JSON.parse(data) : null}
- catch (e) {throw new Error(data)}
- }
- function extract(xhr) {return xhr.responseText}
- function cast(type0, data) {
- if (typeof type0 === "function") {
- if (Array.isArray(data)) {
- for (var i = 0; i < data.length; i++) {
- data[i] = new type0(data[i])
- }
- }
- else return new type0(data)
- }
- return data
- }
- return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback}
-}
-var requestService = _8(window, PromisePolyfill)
-var coreRenderer = function($window) {
- var $doc = $window.document
- var $emptyFragment = $doc.createDocumentFragment()
- var onevent
- function setEventCallback(callback) {return onevent = callback}
- //create
- function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
- for (var i = start; i < end; i++) {
- var vnode = vnodes[i]
- if (vnode != null) {
- insertNode(parent, createNode(vnode, hooks, ns), nextSibling)
- }
- }
- }
- function createNode(vnode, hooks, ns) {
- var tag = vnode.tag
- if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
- if (typeof tag === "string") {
- switch (tag) {
- case "#": return createText(vnode)
- case "<": return createHTML(vnode)
- case "[": return createFragment(vnode, hooks, ns)
- default: return createElement(vnode, hooks, ns)
- }
- }
- else return createComponent(vnode, hooks, ns)
- }
- function createText(vnode) {
- return vnode.dom = $doc.createTextNode(vnode.children)
- }
- function createHTML(vnode) {
- var match1 = vnode.children.match(/^\s*?<(\w+)/im) || []
- var parent = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match1[1]] || "div"
- var temp = $doc.createElement(parent)
- temp.innerHTML = vnode.children
- vnode.dom = temp.firstChild
- vnode.domSize = temp.childNodes.length
- var fragment = $doc.createDocumentFragment()
- var child
- while (child = temp.firstChild) {
- fragment.appendChild(child)
- }
- return fragment
- }
- function createFragment(vnode, hooks, ns) {
- var fragment = $doc.createDocumentFragment()
- if (vnode.children != null) {
- var children = vnode.children
- createNodes(fragment, children, 0, children.length, hooks, null, ns)
- }
- vnode.dom = fragment.firstChild
- vnode.domSize = fragment.childNodes.length
- return fragment
- }
- function createElement(vnode, hooks, ns) {
- var tag = vnode.tag
- switch (vnode.tag) {
- case "svg": ns = "http://www.w3.org/2000/svg"; break
- case "math": ns = "http://www.w3.org/1998/Math/MathML"; break
- }
- var attrs2 = vnode.attrs
- var is = attrs2 && attrs2.is
- var element = ns ?
- is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) :
- is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag)
- vnode.dom = element
- if (attrs2 != null) {
- setAttrs(vnode, attrs2, ns)
- }
- if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
- setContentEditable(vnode)
- }
- else {
- if (vnode.text != null) {
- if (vnode.text !== "") element.textContent = vnode.text
- else vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
- }
- if (vnode.children != null) {
- var children = vnode.children
- createNodes(element, children, 0, children.length, hooks, null, ns)
- setLateAttrs(vnode)
- }
- }
- return element
- }
- function createComponent(vnode, hooks, ns) {
- vnode.state = Object.create(vnode.tag)
- var view = vnode.tag.view
- if (view.reentrantLock != null) return $emptyFragment
- view.reentrantLock = true
- initLifecycle(vnode.tag, vnode, hooks)
- vnode.instance = Vnode.normalize(view.call(vnode.state, vnode))
- view.reentrantLock = null
- if (vnode.instance != null) {
- if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as arguments")
- var element = createNode(vnode.instance, hooks, ns)
- vnode.dom = vnode.instance.dom
- vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
- return element
- }
- else {
- vnode.domSize = 0
- return $emptyFragment
- }
- }
- //update
- function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
- if (old === vnodes || old == null && vnodes == null) return
- else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
- else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
- else {
- if (old.length === vnodes.length) {
- var isUnkeyed = false
- for (var i = 0; i < vnodes.length; i++) {
- if (vnodes[i] != null && old[i] != null) {
- isUnkeyed = vnodes[i].key == null && old[i].key == null
- break
- }
- }
- if (isUnkeyed) {
- for (var i = 0; i < old.length; i++) {
- if (old[i] === vnodes[i]) continue
- else if (old[i] == null && vnodes[i] != null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling))
- else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes)
- else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), false, ns)
- }
- return
- }
- }
- var recycling = isRecyclable(old, vnodes)
- if (recycling) old = old.concat(old.pool)
- var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map
- while (oldEnd >= oldStart && end >= start) {
- var o = old[oldStart], v = vnodes[start]
- if (o === v && !recycling) oldStart++, start++
- else if (o == null) oldStart++
- else if (v == null) start++
- else if (o.key === v.key) {
- oldStart++, start++
- updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns)
- if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
- }
- else {
- var o = old[oldEnd]
- if (o === v && !recycling) oldEnd--, start++
- else if (o == null) oldEnd--
- else if (v == null) start++
- else if (o.key === v.key) {
- updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
- if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling))
- oldEnd--, start++
- }
- else break
- }
- }
- while (oldEnd >= oldStart && end >= start) {
- var o = old[oldEnd], v = vnodes[end]
- if (o === v && !recycling) oldEnd--, end--
- else if (o == null) oldEnd--
- else if (v == null) end--
- else if (o.key === v.key) {
- updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
- if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
- if (o.dom != null) nextSibling = o.dom
- oldEnd--, end--
- }
- else {
- if (!map) map = getKeyMap(old, oldEnd)
- if (v != null) {
- var oldIndex = map[v.key]
- if (oldIndex != null) {
- var movable = old[oldIndex]
- updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
- insertNode(parent, toFragment(movable), nextSibling)
- old[oldIndex].skip = true
- if (movable.dom != null) nextSibling = movable.dom
- }
- else {
- var dom = createNode(v, hooks, undefined)
- insertNode(parent, dom, nextSibling)
- nextSibling = dom
- }
- }
- end--
- }
- if (end < start) break
- }
- createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
- removeNodes(old, oldStart, oldEnd + 1, vnodes)
- }
- }
- function updateNode(parent, old, vnode, hooks, nextSibling, recycling, ns) {
- var oldTag = old.tag, tag = vnode.tag
- if (oldTag === tag) {
- vnode.state = old.state
- vnode.events = old.events
- if (shouldUpdate(vnode, old)) return
- if (vnode.attrs != null) {
- updateLifecycle(vnode.attrs, vnode, hooks, recycling)
- }
- if (typeof oldTag === "string") {
- switch (oldTag) {
- case "#": updateText(old, vnode); break
- case "<": updateHTML(parent, old, vnode, nextSibling); break
- case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break
- default: updateElement(old, vnode, hooks, ns)
- }
- }
- else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns)
- }
- else {
- removeNode(old, null)
- insertNode(parent, createNode(vnode, hooks, ns), nextSibling)
- }
- }
- function updateText(old, vnode) {
- if (old.children.toString() !== vnode.children.toString()) {
- old.dom.nodeValue = vnode.children
- }
- vnode.dom = old.dom
- }
- function updateHTML(parent, old, vnode, nextSibling) {
- if (old.children !== vnode.children) {
- toFragment(old)
- insertNode(parent, createHTML(vnode), nextSibling)
- }
- else vnode.dom = old.dom, vnode.domSize = old.domSize
- }
- function updateFragment(parent, old, vnode, hooks, nextSibling, ns) {
- updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns)
- var domSize = 0, children = vnode.children
- vnode.dom = null
- if (children != null) {
- for (var i = 0; i < children.length; i++) {
- var child = children[i]
- if (child != null && child.dom != null) {
- if (vnode.dom == null) vnode.dom = child.dom
- domSize += child.domSize || 1
- }
- }
- if (domSize !== 1) vnode.domSize = domSize
- }
- }
- function updateElement(old, vnode, hooks, ns) {
- var element = vnode.dom = old.dom
- switch (vnode.tag) {
- case "svg": ns = "http://www.w3.org/2000/svg"; break
- case "math": ns = "http://www.w3.org/1998/Math/MathML"; break
- }
- if (vnode.tag === "textarea") {
- if (vnode.attrs == null) vnode.attrs = {}
- if (vnode.text != null) {
- vnode.attrs.value = vnode.text //FIXME handle0 multiple children
- vnode.text = undefined
- }
- }
- updateAttrs(vnode, old.attrs, vnode.attrs, ns)
- if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
- setContentEditable(vnode)
- }
- else if (old.text != null && vnode.text != null && vnode.text !== "") {
- if (old.text.toString() !== vnode.text.toString()) old.dom.firstChild.nodeValue = vnode.text
- }
- else {
- if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)]
- if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
- updateNodes(element, old.children, vnode.children, hooks, null, ns)
- }
- }
- function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) {
- vnode.instance = Vnode.normalize(vnode.tag.view.call(vnode.state, vnode))
- updateLifecycle(vnode.tag, vnode, hooks, recycling)
- if (vnode.instance != null) {
- if (old.instance == null) insertNode(parent, createNode(vnode.instance, hooks, ns), nextSibling)
- else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns)
- vnode.dom = vnode.instance.dom
- vnode.domSize = vnode.instance.domSize
- }
- else if (old.instance != null) {
- removeNode(old.instance, null)
- vnode.dom = undefined
- vnode.domSize = 0
- }
- else {
- vnode.dom = old.dom
- vnode.domSize = old.domSize
- }
- }
- function isRecyclable(old, vnodes) {
- if (old.pool != null && Math.abs(old.pool.length - vnodes.length) <= Math.abs(old.length - vnodes.length)) {
- var oldChildrenLength = old[0] && old[0].children && old[0].children.length || 0
- var poolChildrenLength = old.pool[0] && old.pool[0].children && old.pool[0].children.length || 0
- var vnodesChildrenLength = vnodes[0] && vnodes[0].children && vnodes[0].children.length || 0
- if (Math.abs(poolChildrenLength - vnodesChildrenLength) <= Math.abs(oldChildrenLength - vnodesChildrenLength)) {
- return true
- }
- }
- return false
- }
- function getKeyMap(vnodes, end) {
- var map = {}, i = 0
- for (var i = 0; i < end; i++) {
- var vnode = vnodes[i]
- if (vnode != null) {
- var key2 = vnode.key
- if (key2 != null) map[key2] = i
- }
- }
- return map
- }
- function toFragment(vnode) {
- var count0 = vnode.domSize
- if (count0 != null || vnode.dom == null) {
- var fragment = $doc.createDocumentFragment()
- if (count0 > 0) {
- var dom = vnode.dom
- while (--count0) fragment.appendChild(dom.nextSibling)
- fragment.insertBefore(dom, fragment.firstChild)
- }
- return fragment
- }
- else return vnode.dom
- }
- function getNextSibling(vnodes, i, nextSibling) {
- for (; i < vnodes.length; i++) {
- if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom
- }
- return nextSibling
- }
- function insertNode(parent, dom, nextSibling) {
- if (nextSibling && nextSibling.parentNode) parent.insertBefore(dom, nextSibling)
- else parent.appendChild(dom)
- }
- function setContentEditable(vnode) {
- var children = vnode.children
- if (children != null && children.length === 1 && children[0].tag === "<") {
- var content = children[0].children
- if (vnode.dom.innerHTML !== content) vnode.dom.innerHTML = content
- }
- else if (vnode.text != null || children != null && children.length !== 0) throw new Error("Child node of a contenteditable must be trusted")
- }
- //remove
- function removeNodes(vnodes, start, end, context) {
- for (var i = start; i < end; i++) {
- var vnode = vnodes[i]
- if (vnode != null) {
- if (vnode.skip) vnode.skip = false
- else removeNode(vnode, context)
- }
- }
- }
- function removeNode(vnode, context) {
- var expected = 1, called = 0
- if (vnode.attrs && vnode.attrs.onbeforeremove) {
- var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode)
- if (result != null && typeof result.then === "function") {
- expected++
- result.then(continuation, continuation)
- }
- }
- if (typeof vnode.tag !== "string" && vnode.tag.onbeforeremove) {
- var result = vnode.tag.onbeforeremove.call(vnode.state, vnode)
- if (result != null && typeof result.then === "function") {
- expected++
- result.then(continuation, continuation)
- }
- }
- continuation()
- function continuation() {
- if (++called === expected) {
- onremove(vnode)
- if (vnode.dom) {
- var count0 = vnode.domSize || 1
- if (count0 > 1) {
- var dom = vnode.dom
- while (--count0) {
- removeNodeFromDOM(dom.nextSibling)
- }
- }
- removeNodeFromDOM(vnode.dom)
- if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && typeof vnode.tag === "string") { //TODO test custom elements
- if (!context.pool) context.pool = [vnode]
- else context.pool.push(vnode)
- }
- }
- }
- }
- }
- function removeNodeFromDOM(node) {
- var parent = node.parentNode
- if (parent != null) parent.removeChild(node)
- }
- function onremove(vnode) {
- if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode.state, vnode)
- if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.onremove.call(vnode.state, vnode)
- if (vnode.instance != null) onremove(vnode.instance)
- else {
- var children = vnode.children
- if (Array.isArray(children)) {
- for (var i = 0; i < children.length; i++) {
- var child = children[i]
- if (child != null) onremove(child)
- }
- }
- }
- }
- //attrs2
- function setAttrs(vnode, attrs2, ns) {
- for (var key2 in attrs2) {
- setAttr(vnode, key2, null, attrs2[key2], ns)
- }
- }
- function setAttr(vnode, key2, old, value, ns) {
- var element = vnode.dom
- if (key2 === "key" || key2 === "is" || (old === value && !isFormAttribute(vnode, key2)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key2)) return
- var nsLastIndex = key2.indexOf(":")
- if (nsLastIndex > -1 && key2.substr(0, nsLastIndex) === "xlink") {
- element.setAttributeNS("http://www.w3.org/1999/xlink", key2.slice(nsLastIndex + 1), value)
- }
- else if (key2[0] === "o" && key2[1] === "n" && typeof value === "function") updateEvent(vnode, key2, value)
- else if (key2 === "style") updateStyle(element, old, value)
- else if (key2 in element && !isAttribute(key2) && ns === undefined && !isCustomElement(vnode)) {
- //setting input[value] to same value by typing on focused element moves cursor to end in Chrome
- if (vnode.tag === "input" && key2 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
- //setting select[value] to same value while having select open blinks select dropdown in Chrome
- if (vnode.tag === "select" && key2 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
- //setting option[value] to same value while having select open blinks select dropdown in Chrome
- if (vnode.tag === "option" && key2 === "value" && vnode.dom.value === value) return
- element[key2] = value
- }
- else {
- if (typeof value === "boolean") {
- if (value) element.setAttribute(key2, "")
- else element.removeAttribute(key2)
- }
- else element.setAttribute(key2 === "className" ? "class" : key2, value)
- }
- }
- function setLateAttrs(vnode) {
- var attrs2 = vnode.attrs
- if (vnode.tag === "select" && attrs2 != null) {
- if ("value" in attrs2) setAttr(vnode, "value", null, attrs2.value, undefined)
- if ("selectedIndex" in attrs2) setAttr(vnode, "selectedIndex", null, attrs2.selectedIndex, undefined)
- }
- }
- function updateAttrs(vnode, old, attrs2, ns) {
- if (attrs2 != null) {
- for (var key2 in attrs2) {
- setAttr(vnode, key2, old && old[key2], attrs2[key2], ns)
- }
- }
- if (old != null) {
- for (var key2 in old) {
- if (attrs2 == null || !(key2 in attrs2)) {
- if (key2 === "className") key2 = "class"
- if (key2[0] === "o" && key2[1] === "n" && !isLifecycleMethod(key2)) updateEvent(vnode, key2, undefined)
- else if (key2 !== "key") vnode.dom.removeAttribute(key2)
- }
- }
- }
- }
- function isFormAttribute(vnode, attr) {
- return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === $doc.activeElement
- }
- function isLifecycleMethod(attr) {
- return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate"
- }
- function isAttribute(attr) {
- return attr === "href" || attr === "list" || attr === "form" || attr === "width" || attr === "height"// || attr === "type"
- }
- function isCustomElement(vnode){
- return vnode.attrs.is || vnode.tag.indexOf("-") > -1
- }
- function hasIntegrationMethods(source) {
- return source != null && (source.oncreate || source.onupdate || source.onbeforeremove || source.onremove)
- }
- //style
- function updateStyle(element, old, style) {
- if (old === style) element.style.cssText = "", old = null
- if (style == null) element.style.cssText = ""
- else if (typeof style === "string") element.style.cssText = style
- else {
- if (typeof old === "string") element.style.cssText = ""
- for (var key2 in style) {
- element.style[key2] = style[key2]
- }
- if (old != null && typeof old !== "string") {
- for (var key2 in old) {
- if (!(key2 in style)) element.style[key2] = ""
- }
- }
- }
- }
- //event
- function updateEvent(vnode, key2, value) {
- var element = vnode.dom
- var callback = typeof onevent !== "function" ? value : function(e) {
- var result = value.call(element, e)
- onevent.call(element, e)
- return result
- }
- if (key2 in element) element[key2] = typeof value === "function" ? callback : null
- else {
- var eventName = key2.slice(2)
- if (vnode.events === undefined) vnode.events = {}
- if (vnode.events[key2] === callback) return
- if (vnode.events[key2] != null) element.removeEventListener(eventName, vnode.events[key2], false)
- if (typeof value === "function") {
- vnode.events[key2] = callback
- element.addEventListener(eventName, vnode.events[key2], false)
- }
- }
- }
- //lifecycle
- function initLifecycle(source, vnode, hooks) {
- if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode)
- if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode))
- }
- function updateLifecycle(source, vnode, hooks, recycling) {
- if (recycling) initLifecycle(source, vnode, hooks)
- else if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
- }
- function shouldUpdate(vnode, old) {
- var forceVnodeUpdate, forceComponentUpdate
- if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old)
- if (typeof vnode.tag !== "string" && typeof vnode.tag.onbeforeupdate === "function") forceComponentUpdate = vnode.tag.onbeforeupdate.call(vnode.state, vnode, old)
- if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) {
- vnode.dom = old.dom
- vnode.domSize = old.domSize
- vnode.instance = old.instance
- return true
- }
- return false
- }
- function render(dom, vnodes) {
- if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.")
- var hooks = []
- var active = $doc.activeElement
- // First time0 rendering into a node clears it out
- if (dom.vnodes == null) dom.textContent = ""
- if (!Array.isArray(vnodes)) vnodes = [vnodes]
- updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined)
- dom.vnodes = vnodes
- for (var i = 0; i < hooks.length; i++) hooks[i]()
- if ($doc.activeElement !== active) active.focus()
- }
- return {render: render, setEventCallback: setEventCallback}
-}
-function throttle(callback) {
- //60fps translates to 16.6ms, round it down since setTimeout requires int
- var time = 16
- var last = 0, pending = null
- var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
- return function() {
- var now = Date.now()
- if (last === 0 || now - last >= time) {
- last = now
- callback()
- }
- else if (pending === null) {
- pending = timeout(function() {
- pending = null
- callback()
- last = Date.now()
- }, time - (now - last))
- }
- }
-}
-var _11 = function($window) {
- var renderService = coreRenderer($window)
- renderService.setEventCallback(function(e) {
- if (e.redraw !== false) redraw()
- })
-
- var callbacks = []
- function subscribe(key1, callback) {
- unsubscribe(key1)
- callbacks.push(key1, throttle(callback))
- }
- function unsubscribe(key1) {
- var index = callbacks.indexOf(key1)
- if (index > -1) callbacks.splice(index, 2)
- }
- function redraw() {
- for (var i = 1; i < callbacks.length; i += 2) {
- callbacks[i]()
- }
- }
- return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
-}
-var redrawService = _11(window)
-requestService.setCompletionCallback(redrawService.redraw)
-var _16 = function(redrawService0) {
- return function(root, component) {
- if (component === null) {
- redrawService0.render(root, [])
- redrawService0.unsubscribe(root)
- return
- }
-
- if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode")
-
- var run0 = function() {
- redrawService0.render(root, Vnode(component))
- }
- redrawService0.subscribe(root, run0)
- redrawService0.redraw()
- }
-}
-m.mount = _16(redrawService)
-var Promise = PromisePolyfill
-var parseQueryString = function(string) {
- if (string === "" || string == null) return {}
- if (string.charAt(0) === "?") string = string.slice(1)
- var entries = string.split("&"), data0 = {}, counters = {}
- for (var i = 0; i < entries.length; i++) {
- var entry = entries[i].split("=")
- var key5 = decodeURIComponent(entry[0])
- var value = entry.length === 2 ? decodeURIComponent(entry[1]) : ""
- if (value === "true") value = true
- else if (value === "false") value = false
- var levels = key5.split(/\]\[?|\[/)
- var cursor = data0
- if (key5.indexOf("[") > -1) levels.pop()
- for (var j = 0; j < levels.length; j++) {
- var level = levels[j], nextLevel = levels[j + 1]
- var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10))
- var isValue = j === levels.length - 1
- if (level === "") {
- var key5 = levels.slice(0, j).join()
- if (counters[key5] == null) counters[key5] = 0
- level = counters[key5]++
- }
- if (cursor[level] == null) {
- cursor[level] = isValue ? value : isNumber ? [] : {}
- }
- cursor = cursor[level]
- }
- }
- return data0
-}
-var coreRouter = function($window) {
- var supportsPushState = typeof $window.history.pushState === "function"
- var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout
- function normalize1(fragment0) {
- var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
- if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data
- return data
- }
- var asyncId
- function debounceAsync(callback0) {
- return function() {
- if (asyncId != null) return
- asyncId = callAsync0(function() {
- asyncId = null
- callback0()
- })
- }
- }
- function parsePath(path, queryData, hashData) {
- var queryIndex = path.indexOf("?")
- var hashIndex = path.indexOf("#")
- var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length
- if (queryIndex > -1) {
- var queryEnd = hashIndex > -1 ? hashIndex : path.length
- var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd))
- for (var key4 in queryParams) queryData[key4] = queryParams[key4]
- }
- if (hashIndex > -1) {
- var hashParams = parseQueryString(path.slice(hashIndex + 1))
- for (var key4 in hashParams) hashData[key4] = hashParams[key4]
- }
- return path.slice(0, pathEnd)
- }
- var router = {prefix: "#!"}
- router.getPath = function() {
- var type2 = router.prefix.charAt(0)
- switch (type2) {
- case "#": return normalize1("hash").slice(router.prefix.length)
- case "?": return normalize1("search").slice(router.prefix.length) + normalize1("hash")
- default: return normalize1("pathname").slice(router.prefix.length) + normalize1("search") + normalize1("hash")
- }
- }
- router.setPath = function(path, data, options) {
- var queryData = {}, hashData = {}
- path = parsePath(path, queryData, hashData)
- if (data != null) {
- for (var key4 in data) queryData[key4] = data[key4]
- path = path.replace(/:([^\/]+)/g, function(match2, token) {
- delete queryData[token]
- return data[token]
- })
- }
- var query = buildQueryString(queryData)
- if (query) path += "?" + query
- var hash = buildQueryString(hashData)
- if (hash) path += "#" + hash
- if (supportsPushState) {
- var state = options ? options.state : null
- var title = options ? options.title : null
- $window.onpopstate()
- if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path)
- else $window.history.pushState(state, title, router.prefix + path)
- }
- else $window.location.href = router.prefix + path
- }
- router.defineRoutes = function(routes, resolve, reject) {
- function resolveRoute() {
- var path = router.getPath()
- var params = {}
- var pathname = parsePath(path, params, params)
-
- var state = $window.history.state
- if (state != null) {
- for (var k in state) params[k] = state[k]
- }
- for (var route0 in routes) {
- var matcher = new RegExp("^" + route0.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
- if (matcher.test(pathname)) {
- pathname.replace(matcher, function() {
- var keys = route0.match(/:[^\/]+/g) || []
- var values = [].slice.call(arguments, 1, -2)
- for (var i = 0; i < keys.length; i++) {
- params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
- }
- resolve(routes[route0], params, path, route0)
- })
- return
- }
- }
- reject(path, params)
- }
-
- if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
- else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
- resolveRoute()
- }
-
- return router
-}
-var _20 = function($window, redrawService0) {
- var routeService = coreRouter($window)
- var identity = function(v) {return v}
- var render1, component, attrs3, currentPath, lastUpdate
- var route = function(root, defaultRoute, routes) {
- if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
- var run1 = function() {
- if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3)))
- }
- var bail = function() {
- routeService.setPath(defaultRoute, null, {replace: true})
- }
- routeService.defineRoutes(routes, function(payload, params, path) {
- var update = lastUpdate = function(routeResolver, comp) {
- if (update !== lastUpdate) return
- component = comp != null && typeof comp.view === "function" ? comp : "div", attrs3 = params, currentPath = path, lastUpdate = null
- render1 = (routeResolver.render || identity).bind(routeResolver)
- run1()
- }
- if (payload.view) update({}, payload)
- else {
- if (payload.onmatch) {
- Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
- update(payload, resolved)
- }, bail)
- }
- else update(payload, "div")
- }
- }, bail)
- redrawService0.subscribe(root, run1)
- }
- route.set = function(path, data, options) {
- if (lastUpdate != null) options = {replace: true}
- lastUpdate = null
- routeService.setPath(path, data, options)
- }
- route.get = function() {return currentPath}
- route.prefix = function(prefix0) {routeService.prefix = prefix0}
- route.link = function(vnode1) {
- vnode1.dom.setAttribute("href", routeService.prefix + vnode1.attrs.href)
- vnode1.dom.onclick = function(e) {
- if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
- e.preventDefault()
- e.redraw = false
- var href = this.getAttribute("href")
- if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length)
- route.set(href, undefined, undefined)
- }
- }
- route.param = function(key3) {
- if(typeof attrs3 !== "undefined" && typeof key3 !== "undefined") return attrs3[key3]
- return attrs3
- }
- return route
-}
-m.route = _20(window, redrawService)
-m.withAttr = function(attrName, callback1, context) {
- return function(e) {
- callback1.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
- }
-}
-var _28 = coreRenderer(window)
-m.render = _28.render
-m.redraw = redrawService.redraw
-m.request = requestService.request
-m.jsonp = requestService.jsonp
-m.parseQueryString = parseQueryString
-m.buildQueryString = buildQueryString
-m.version = "1.0.0"
-m.vnode = Vnode
-if (typeof module !== "undefined") module["exports"] = m
-else window.m = m
+new function() {
+
+function Vnode(tag, key, attrs0, children, text, dom) {
+ return {tag: tag, key: key, attrs: attrs0, children: children, text: text, dom: dom, domSize: undefined, state: {}, events: undefined, instance: undefined, skip: false}
+}
+Vnode.normalize = function(node) {
+ if (Array.isArray(node)) return Vnode("[", undefined, undefined, Vnode.normalizeChildren(node), undefined, undefined)
+ if (node != null && typeof node !== "object") return Vnode("#", undefined, undefined, node === false ? "" : node, undefined, undefined)
+ return node
+}
+Vnode.normalizeChildren = function normalizeChildren(children) {
+ for (var i = 0; i < children.length; i++) {
+ children[i] = Vnode.normalize(children[i])
+ }
+ return children
+}
+var selectorParser = /(?:(^|#|\.)([^#\.\[\]]+))|(\[(.+?)(?:\s*=\s*("|'|)((?:\\["'\]]|.)*?)\5)?\])/g
+var selectorCache = {}
+function hyperscript(selector) {
+ if (selector == null || typeof selector !== "string" && typeof selector.view !== "function") {
+ throw Error("The selector must be either a string or a component.");
+ }
+ if (typeof selector === "string" && selectorCache[selector] === undefined) {
+ var match, tag, classes = [], attributes = {}
+ while (match = selectorParser.exec(selector)) {
+ var type = match[1], value = match[2]
+ if (type === "" && value !== "") tag = value
+ else if (type === "#") attributes.id = value
+ else if (type === ".") classes.push(value)
+ else if (match[3][0] === "[") {
+ var attrValue = match[6]
+ if (attrValue) attrValue = attrValue.replace(/\\(["'])/g, "$1").replace(/\\\\/g, "\\")
+ if (match[4] === "class") classes.push(attrValue)
+ else attributes[match[4]] = attrValue || true
+ }
+ }
+ if (classes.length > 0) attributes.className = classes.join(" ")
+ selectorCache[selector] = function(attrs, children) {
+ var hasAttrs = false, childList, text
+ var className = attrs.className || attrs.class
+ for (var key in attributes) attrs[key] = attributes[key]
+ if (className !== undefined) {
+ if (attrs.class !== undefined) {
+ attrs.class = undefined
+ attrs.className = className
+ }
+ if (attributes.className !== undefined) attrs.className = attributes.className + " " + className
+ }
+ for (var key in attrs) {
+ if (key !== "key") {
+ hasAttrs = true
+ break
+ }
+ }
+ if (Array.isArray(children) && children.length == 1 && children[0] != null && children[0].tag === "#") text = children[0].children
+ else childList = children
+ return Vnode(tag || "div", attrs.key, hasAttrs ? attrs : undefined, childList, text, undefined)
+ }
+ }
+ var attrs, children, childrenIndex
+ if (arguments[1] == null || typeof arguments[1] === "object" && arguments[1].tag === undefined && !Array.isArray(arguments[1])) {
+ attrs = arguments[1]
+ childrenIndex = 2
+ }
+ else childrenIndex = 1
+ if (arguments.length === childrenIndex + 1) {
+ children = Array.isArray(arguments[childrenIndex]) ? arguments[childrenIndex] : [arguments[childrenIndex]]
+ }
+ else {
+ children = []
+ for (var i = childrenIndex; i < arguments.length; i++) children.push(arguments[i])
+ }
+ if (typeof selector === "string") return selectorCache[selector](attrs || {}, Vnode.normalizeChildren(children))
+ return Vnode(selector, attrs && attrs.key, attrs || {}, Vnode.normalizeChildren(children), undefined, undefined)
+}
+hyperscript.trust = function(html) {
+ if (html == null) html = ""
+ return Vnode("<", undefined, undefined, html, undefined, undefined)
+}
+hyperscript.fragment = function(attrs1, children) {
+ return Vnode("[", attrs1.key, attrs1, Vnode.normalizeChildren(children), undefined, undefined)
+}
+var m = hyperscript
+/** @constructor */
+var PromisePolyfill = function(executor) {
+ if (!(this instanceof PromisePolyfill)) throw new Error("Promise must be called with `new`")
+ if (typeof executor !== "function") throw new TypeError("executor must be a function")
+ var self = this, resolvers = [], rejectors = [], resolveCurrent = handler(resolvers, true), rejectCurrent = handler(rejectors, false)
+ var instance = self._instance = {resolvers: resolvers, rejectors: rejectors}
+ var callAsync = typeof setImmediate === "function" ? setImmediate : setTimeout
+ function handler(list, shouldAbsorb) {
+ return function execute(value) {
+ var then
+ try {
+ if (shouldAbsorb && value != null && (typeof value === "object" || typeof value === "function") && typeof (then = value.then) === "function") {
+ if (value === self) throw new TypeError("Promise can't be resolved w/ itself")
+ executeOnce(then.bind(value))
+ }
+ else {
+ callAsync(function() {
+ if (!shouldAbsorb && list.length === 0) console.error("Possible unhandled promise rejection:", value)
+ for (var i = 0; i < list.length; i++) list[i](value)
+ resolvers.length = 0, rejectors.length = 0
+ instance.state = shouldAbsorb
+ instance.retry = function() {execute(value)}
+ })
+ }
+ }
+ catch (e) {
+ rejectCurrent(e)
+ }
+ }
+ }
+ function executeOnce(then) {
+ var runs = 0
+ function run(fn) {
+ return function(value) {
+ if (runs++ > 0) return
+ fn(value)
+ }
+ }
+ var onerror = run(rejectCurrent)
+ try {then(run(resolveCurrent), onerror)} catch (e) {onerror(e)}
+ }
+ executeOnce(executor)
+}
+PromisePolyfill.prototype.then = function(onFulfilled, onRejection) {
+ var self = this, instance = self._instance
+ function handle(callback, list, next, state) {
+ list.push(function(value) {
+ if (typeof callback !== "function") next(value)
+ else try {resolveNext(callback(value))} catch (e) {if (rejectNext) rejectNext(e)}
+ })
+ if (typeof instance.retry === "function" && state === instance.state) instance.retry()
+ }
+ var resolveNext, rejectNext
+ var promise = new PromisePolyfill(function(resolve, reject) {resolveNext = resolve, rejectNext = reject})
+ handle(onFulfilled, instance.resolvers, resolveNext, true), handle(onRejection, instance.rejectors, rejectNext, false)
+ return promise
+}
+PromisePolyfill.prototype.catch = function(onRejection) {
+ return this.then(null, onRejection)
+}
+PromisePolyfill.resolve = function(value) {
+ if (value instanceof PromisePolyfill) return value
+ return new PromisePolyfill(function(resolve) {resolve(value)})
+}
+PromisePolyfill.reject = function(value) {
+ return new PromisePolyfill(function(resolve, reject) {reject(value)})
+}
+PromisePolyfill.all = function(list) {
+ return new PromisePolyfill(function(resolve, reject) {
+ var total = list.length, count = 0, values = []
+ if (list.length === 0) resolve([])
+ else for (var i = 0; i < list.length; i++) {
+ (function(i) {
+ function consume(value) {
+ count++
+ values[i] = value
+ if (count === total) resolve(values)
+ }
+ if (list[i] != null && (typeof list[i] === "object" || typeof list[i] === "function") && typeof list[i].then === "function") {
+ list[i].then(consume, reject)
+ }
+ else consume(list[i])
+ })(i)
+ }
+ })
+}
+PromisePolyfill.race = function(list) {
+ return new PromisePolyfill(function(resolve, reject) {
+ for (var i = 0; i < list.length; i++) {
+ list[i].then(resolve, reject)
+ }
+ })
+}
+if (typeof window !== "undefined") {
+ if (typeof window.Promise === "undefined") window.Promise = PromisePolyfill
+ var PromisePolyfill = window.Promise
+} else if (typeof global !== "undefined") {
+ if (typeof global.Promise === "undefined") global.Promise = PromisePolyfill
+ var PromisePolyfill = global.Promise
+} else {
+}
+var buildQueryString = function(object) {
+ if (Object.prototype.toString.call(object) !== "[object Object]") return ""
+ var args = []
+ for (var key0 in object) {
+ destructure(key0, object[key0])
+ }
+ return args.join("&")
+ function destructure(key0, value) {
+ if (Array.isArray(value)) {
+ for (var i = 0; i < value.length; i++) {
+ destructure(key0 + "[" + i + "]", value[i])
+ }
+ }
+ else if (Object.prototype.toString.call(value) === "[object Object]") {
+ for (var i in value) {
+ destructure(key0 + "[" + i + "]", value[i])
+ }
+ }
+ else args.push(encodeURIComponent(key0) + (value != null && value !== "" ? "=" + encodeURIComponent(value) : ""))
+ }
+}
+var _8 = function($window, Promise) {
+ var callbackCount = 0
+ var oncompletion
+ function setCompletionCallback(callback) {oncompletion = callback}
+ function finalizer() {
+ var count = 0
+ function complete() {if (--count === 0 && typeof oncompletion === "function") oncompletion()}
+ return function finalize(promise0) {
+ var then0 = promise0.then
+ promise0.then = function() {
+ count++
+ var next = then0.apply(promise0, arguments)
+ next.then(complete, function(e) {
+ complete()
+ if (count === 0) throw e
+ })
+ return finalize(next)
+ }
+ return promise0
+ }
+ }
+ function normalize(args, extra) {
+ if (typeof args === "string") {
+ var url = args
+ args = extra || {}
+ if (args.url == null) args.url = url
+ }
+ return args
+ }
+ function request(args, extra) {
+ var finalize = finalizer()
+ args = normalize(args, extra)
+ var promise0 = new Promise(function(resolve, reject) {
+ if (args.method == null) args.method = "GET"
+ args.method = args.method.toUpperCase()
+ var useBody = typeof args.useBody === "boolean" ? args.useBody : args.method !== "GET" && args.method !== "TRACE"
+ if (typeof args.serialize !== "function") args.serialize = typeof FormData !== "undefined" && args.data instanceof FormData ? function(value) {return value} : JSON.stringify
+ if (typeof args.deserialize !== "function") args.deserialize = deserialize
+ if (typeof args.extract !== "function") args.extract = extract
+ args.url = interpolate(args.url, args.data)
+ if (useBody) args.data = args.serialize(args.data)
+ else args.url = assemble(args.url, args.data)
+ var xhr = new $window.XMLHttpRequest()
+ xhr.open(args.method, args.url, typeof args.async === "boolean" ? args.async : true, typeof args.user === "string" ? args.user : undefined, typeof args.password === "string" ? args.password : undefined)
+ if (args.serialize === JSON.stringify && useBody) {
+ xhr.setRequestHeader("Content-Type", "application/json; charset=utf-8")
+ }
+ if (args.deserialize === deserialize) {
+ xhr.setRequestHeader("Accept", "application/json, text/*")
+ }
+ if (args.withCredentials) xhr.withCredentials = args.withCredentials
+ for (var key in args.headers) if ({}.hasOwnProperty.call(args.headers, key)) {
+ xhr.setRequestHeader(key, args.headers[key])
+ }
+ if (typeof args.config === "function") xhr = args.config(xhr, args) || xhr
+ xhr.onreadystatechange = function() {
+ // Don't throw errors on xhr.abort(). XMLHttpRequests ends up in a state of
+ // xhr.status == 0 and xhr.readyState == 4 if aborted after open, but before completion.
+ if (xhr.status && xhr.readyState === 4) {
+ try {
+ var response = (args.extract !== extract) ? args.extract(xhr, args) : args.deserialize(args.extract(xhr, args))
+ if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
+ resolve(cast(args.type, response))
+ }
+ else {
+ var error = new Error(xhr.responseText)
+ for (var key in response) error[key] = response[key]
+ reject(error)
+ }
+ }
+ catch (e) {
+ reject(e)
+ }
+ }
+ }
+ if (useBody && (args.data != null)) xhr.send(args.data)
+ else xhr.send()
+ })
+ return args.background === true ? promise0 : finalize(promise0)
+ }
+ function jsonp(args, extra) {
+ var finalize = finalizer()
+ args = normalize(args, extra)
+ var promise0 = new Promise(function(resolve, reject) {
+ var callbackName = args.callbackName || "_mithril_" + Math.round(Math.random() * 1e16) + "_" + callbackCount++
+ var script = $window.document.createElement("script")
+ $window[callbackName] = function(data) {
+ script.parentNode.removeChild(script)
+ resolve(cast(args.type, data))
+ delete $window[callbackName]
+ }
+ script.onerror = function() {
+ script.parentNode.removeChild(script)
+ reject(new Error("JSONP request failed"))
+ delete $window[callbackName]
+ }
+ if (args.data == null) args.data = {}
+ args.url = interpolate(args.url, args.data)
+ args.data[args.callbackKey || "callback"] = callbackName
+ script.src = assemble(args.url, args.data)
+ $window.document.documentElement.appendChild(script)
+ })
+ return args.background === true? promise0 : finalize(promise0)
+ }
+ function interpolate(url, data) {
+ if (data == null) return url
+ var tokens = url.match(/:[^\/]+/gi) || []
+ for (var i = 0; i < tokens.length; i++) {
+ var key = tokens[i].slice(1)
+ if (data[key] != null) {
+ url = url.replace(tokens[i], data[key])
+ }
+ }
+ return url
+ }
+ function assemble(url, data) {
+ var querystring = buildQueryString(data)
+ if (querystring !== "") {
+ var prefix = url.indexOf("?") < 0 ? "?" : "&"
+ url += prefix + querystring
+ }
+ return url
+ }
+ function deserialize(data) {
+ try {return data !== "" ? JSON.parse(data) : null}
+ catch (e) {throw new Error(data)}
+ }
+ function extract(xhr) {return xhr.responseText}
+ function cast(type0, data) {
+ if (typeof type0 === "function") {
+ if (Array.isArray(data)) {
+ for (var i = 0; i < data.length; i++) {
+ data[i] = new type0(data[i])
+ }
+ }
+ else return new type0(data)
+ }
+ return data
+ }
+ return {request: request, jsonp: jsonp, setCompletionCallback: setCompletionCallback}
+}
+var requestService = _8(window, PromisePolyfill)
+var coreRenderer = function($window) {
+ var $doc = $window.document
+ var $emptyFragment = $doc.createDocumentFragment()
+ var onevent
+ function setEventCallback(callback) {return onevent = callback}
+ //create
+ function createNodes(parent, vnodes, start, end, hooks, nextSibling, ns) {
+ for (var i = start; i < end; i++) {
+ var vnode = vnodes[i]
+ if (vnode != null) {
+ insertNode(parent, createNode(vnode, hooks, ns), nextSibling)
+ }
+ }
+ }
+ function createNode(vnode, hooks, ns) {
+ var tag = vnode.tag
+ if (vnode.attrs != null) initLifecycle(vnode.attrs, vnode, hooks)
+ if (typeof tag === "string") {
+ switch (tag) {
+ case "#": return createText(vnode)
+ case "<": return createHTML(vnode)
+ case "[": return createFragment(vnode, hooks, ns)
+ default: return createElement(vnode, hooks, ns)
+ }
+ }
+ else return createComponent(vnode, hooks, ns)
+ }
+ function createText(vnode) {
+ return vnode.dom = $doc.createTextNode(vnode.children)
+ }
+ function createHTML(vnode) {
+ var match1 = vnode.children.match(/^\s*?<(\w+)/im) || []
+ var parent = {caption: "table", thead: "table", tbody: "table", tfoot: "table", tr: "tbody", th: "tr", td: "tr", colgroup: "table", col: "colgroup"}[match1[1]] || "div"
+ var temp = $doc.createElement(parent)
+ temp.innerHTML = vnode.children
+ vnode.dom = temp.firstChild
+ vnode.domSize = temp.childNodes.length
+ var fragment = $doc.createDocumentFragment()
+ var child
+ while (child = temp.firstChild) {
+ fragment.appendChild(child)
+ }
+ return fragment
+ }
+ function createFragment(vnode, hooks, ns) {
+ var fragment = $doc.createDocumentFragment()
+ if (vnode.children != null) {
+ var children = vnode.children
+ createNodes(fragment, children, 0, children.length, hooks, null, ns)
+ }
+ vnode.dom = fragment.firstChild
+ vnode.domSize = fragment.childNodes.length
+ return fragment
+ }
+ function createElement(vnode, hooks, ns) {
+ var tag = vnode.tag
+ switch (vnode.tag) {
+ case "svg": ns = "http://www.w3.org/2000/svg"; break
+ case "math": ns = "http://www.w3.org/1998/Math/MathML"; break
+ }
+ var attrs2 = vnode.attrs
+ var is = attrs2 && attrs2.is
+ var element = ns ?
+ is ? $doc.createElementNS(ns, tag, {is: is}) : $doc.createElementNS(ns, tag) :
+ is ? $doc.createElement(tag, {is: is}) : $doc.createElement(tag)
+ vnode.dom = element
+ if (attrs2 != null) {
+ setAttrs(vnode, attrs2, ns)
+ }
+ if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
+ setContentEditable(vnode)
+ }
+ else {
+ if (vnode.text != null) {
+ if (vnode.text !== "") element.textContent = vnode.text
+ else vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
+ }
+ if (vnode.children != null) {
+ var children = vnode.children
+ createNodes(element, children, 0, children.length, hooks, null, ns)
+ setLateAttrs(vnode)
+ }
+ }
+ return element
+ }
+ function createComponent(vnode, hooks, ns) {
+ vnode.state = Object.create(vnode.tag)
+ var view = vnode.tag.view
+ if (view.reentrantLock != null) return $emptyFragment
+ view.reentrantLock = true
+ initLifecycle(vnode.tag, vnode, hooks)
+ vnode.instance = Vnode.normalize(view.call(vnode.state, vnode))
+ view.reentrantLock = null
+ if (vnode.instance != null) {
+ if (vnode.instance === vnode) throw Error("A view cannot return the vnode it received as arguments")
+ var element = createNode(vnode.instance, hooks, ns)
+ vnode.dom = vnode.instance.dom
+ vnode.domSize = vnode.dom != null ? vnode.instance.domSize : 0
+ return element
+ }
+ else {
+ vnode.domSize = 0
+ return $emptyFragment
+ }
+ }
+ //update
+ function updateNodes(parent, old, vnodes, hooks, nextSibling, ns) {
+ if (old === vnodes || old == null && vnodes == null) return
+ else if (old == null) createNodes(parent, vnodes, 0, vnodes.length, hooks, nextSibling, undefined)
+ else if (vnodes == null) removeNodes(old, 0, old.length, vnodes)
+ else {
+ if (old.length === vnodes.length) {
+ var isUnkeyed = false
+ for (var i = 0; i < vnodes.length; i++) {
+ if (vnodes[i] != null && old[i] != null) {
+ isUnkeyed = vnodes[i].key == null && old[i].key == null
+ break
+ }
+ }
+ if (isUnkeyed) {
+ for (var i = 0; i < old.length; i++) {
+ if (old[i] === vnodes[i]) continue
+ else if (old[i] == null && vnodes[i] != null) insertNode(parent, createNode(vnodes[i], hooks, ns), getNextSibling(old, i + 1, nextSibling))
+ else if (vnodes[i] == null) removeNodes(old, i, i + 1, vnodes)
+ else updateNode(parent, old[i], vnodes[i], hooks, getNextSibling(old, i + 1, nextSibling), false, ns)
+ }
+ return
+ }
+ }
+ var recycling = isRecyclable(old, vnodes)
+ if (recycling) old = old.concat(old.pool)
+ var oldStart = 0, start = 0, oldEnd = old.length - 1, end = vnodes.length - 1, map
+ while (oldEnd >= oldStart && end >= start) {
+ var o = old[oldStart], v = vnodes[start]
+ if (o === v && !recycling) oldStart++, start++
+ else if (o == null) oldStart++
+ else if (v == null) start++
+ else if (o.key === v.key) {
+ oldStart++, start++
+ updateNode(parent, o, v, hooks, getNextSibling(old, oldStart, nextSibling), recycling, ns)
+ if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
+ }
+ else {
+ var o = old[oldEnd]
+ if (o === v && !recycling) oldEnd--, start++
+ else if (o == null) oldEnd--
+ else if (v == null) start++
+ else if (o.key === v.key) {
+ updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
+ if (recycling || start < end) insertNode(parent, toFragment(o), getNextSibling(old, oldStart, nextSibling))
+ oldEnd--, start++
+ }
+ else break
+ }
+ }
+ while (oldEnd >= oldStart && end >= start) {
+ var o = old[oldEnd], v = vnodes[end]
+ if (o === v && !recycling) oldEnd--, end--
+ else if (o == null) oldEnd--
+ else if (v == null) end--
+ else if (o.key === v.key) {
+ updateNode(parent, o, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
+ if (recycling && o.tag === v.tag) insertNode(parent, toFragment(o), nextSibling)
+ if (o.dom != null) nextSibling = o.dom
+ oldEnd--, end--
+ }
+ else {
+ if (!map) map = getKeyMap(old, oldEnd)
+ if (v != null) {
+ var oldIndex = map[v.key]
+ if (oldIndex != null) {
+ var movable = old[oldIndex]
+ updateNode(parent, movable, v, hooks, getNextSibling(old, oldEnd + 1, nextSibling), recycling, ns)
+ insertNode(parent, toFragment(movable), nextSibling)
+ old[oldIndex].skip = true
+ if (movable.dom != null) nextSibling = movable.dom
+ }
+ else {
+ var dom = createNode(v, hooks, undefined)
+ insertNode(parent, dom, nextSibling)
+ nextSibling = dom
+ }
+ }
+ end--
+ }
+ if (end < start) break
+ }
+ createNodes(parent, vnodes, start, end + 1, hooks, nextSibling, ns)
+ removeNodes(old, oldStart, oldEnd + 1, vnodes)
+ }
+ }
+ function updateNode(parent, old, vnode, hooks, nextSibling, recycling, ns) {
+ var oldTag = old.tag, tag = vnode.tag
+ if (oldTag === tag) {
+ vnode.state = old.state
+ vnode.events = old.events
+ if (shouldUpdate(vnode, old)) return
+ if (vnode.attrs != null) {
+ updateLifecycle(vnode.attrs, vnode, hooks, recycling)
+ }
+ if (typeof oldTag === "string") {
+ switch (oldTag) {
+ case "#": updateText(old, vnode); break
+ case "<": updateHTML(parent, old, vnode, nextSibling); break
+ case "[": updateFragment(parent, old, vnode, hooks, nextSibling, ns); break
+ default: updateElement(old, vnode, hooks, ns)
+ }
+ }
+ else updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns)
+ }
+ else {
+ removeNode(old, null)
+ insertNode(parent, createNode(vnode, hooks, ns), nextSibling)
+ }
+ }
+ function updateText(old, vnode) {
+ if (old.children.toString() !== vnode.children.toString()) {
+ old.dom.nodeValue = vnode.children
+ }
+ vnode.dom = old.dom
+ }
+ function updateHTML(parent, old, vnode, nextSibling) {
+ if (old.children !== vnode.children) {
+ toFragment(old)
+ insertNode(parent, createHTML(vnode), nextSibling)
+ }
+ else vnode.dom = old.dom, vnode.domSize = old.domSize
+ }
+ function updateFragment(parent, old, vnode, hooks, nextSibling, ns) {
+ updateNodes(parent, old.children, vnode.children, hooks, nextSibling, ns)
+ var domSize = 0, children = vnode.children
+ vnode.dom = null
+ if (children != null) {
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i]
+ if (child != null && child.dom != null) {
+ if (vnode.dom == null) vnode.dom = child.dom
+ domSize += child.domSize || 1
+ }
+ }
+ if (domSize !== 1) vnode.domSize = domSize
+ }
+ }
+ function updateElement(old, vnode, hooks, ns) {
+ var element = vnode.dom = old.dom
+ switch (vnode.tag) {
+ case "svg": ns = "http://www.w3.org/2000/svg"; break
+ case "math": ns = "http://www.w3.org/1998/Math/MathML"; break
+ }
+ if (vnode.tag === "textarea") {
+ if (vnode.attrs == null) vnode.attrs = {}
+ if (vnode.text != null) {
+ vnode.attrs.value = vnode.text //FIXME handle0 multiple children
+ vnode.text = undefined
+ }
+ }
+ updateAttrs(vnode, old.attrs, vnode.attrs, ns)
+ if (vnode.attrs != null && vnode.attrs.contenteditable != null) {
+ setContentEditable(vnode)
+ }
+ else if (old.text != null && vnode.text != null && vnode.text !== "") {
+ if (old.text.toString() !== vnode.text.toString()) old.dom.firstChild.nodeValue = vnode.text
+ }
+ else {
+ if (old.text != null) old.children = [Vnode("#", undefined, undefined, old.text, undefined, old.dom.firstChild)]
+ if (vnode.text != null) vnode.children = [Vnode("#", undefined, undefined, vnode.text, undefined, undefined)]
+ updateNodes(element, old.children, vnode.children, hooks, null, ns)
+ }
+ }
+ function updateComponent(parent, old, vnode, hooks, nextSibling, recycling, ns) {
+ vnode.instance = Vnode.normalize(vnode.tag.view.call(vnode.state, vnode))
+ updateLifecycle(vnode.tag, vnode, hooks, recycling)
+ if (vnode.instance != null) {
+ if (old.instance == null) insertNode(parent, createNode(vnode.instance, hooks, ns), nextSibling)
+ else updateNode(parent, old.instance, vnode.instance, hooks, nextSibling, recycling, ns)
+ vnode.dom = vnode.instance.dom
+ vnode.domSize = vnode.instance.domSize
+ }
+ else if (old.instance != null) {
+ removeNode(old.instance, null)
+ vnode.dom = undefined
+ vnode.domSize = 0
+ }
+ else {
+ vnode.dom = old.dom
+ vnode.domSize = old.domSize
+ }
+ }
+ function isRecyclable(old, vnodes) {
+ if (old.pool != null && Math.abs(old.pool.length - vnodes.length) <= Math.abs(old.length - vnodes.length)) {
+ var oldChildrenLength = old[0] && old[0].children && old[0].children.length || 0
+ var poolChildrenLength = old.pool[0] && old.pool[0].children && old.pool[0].children.length || 0
+ var vnodesChildrenLength = vnodes[0] && vnodes[0].children && vnodes[0].children.length || 0
+ if (Math.abs(poolChildrenLength - vnodesChildrenLength) <= Math.abs(oldChildrenLength - vnodesChildrenLength)) {
+ return true
+ }
+ }
+ return false
+ }
+ function getKeyMap(vnodes, end) {
+ var map = {}, i = 0
+ for (var i = 0; i < end; i++) {
+ var vnode = vnodes[i]
+ if (vnode != null) {
+ var key2 = vnode.key
+ if (key2 != null) map[key2] = i
+ }
+ }
+ return map
+ }
+ function toFragment(vnode) {
+ var count0 = vnode.domSize
+ if (count0 != null || vnode.dom == null) {
+ var fragment = $doc.createDocumentFragment()
+ if (count0 > 0) {
+ var dom = vnode.dom
+ while (--count0) fragment.appendChild(dom.nextSibling)
+ fragment.insertBefore(dom, fragment.firstChild)
+ }
+ return fragment
+ }
+ else return vnode.dom
+ }
+ function getNextSibling(vnodes, i, nextSibling) {
+ for (; i < vnodes.length; i++) {
+ if (vnodes[i] != null && vnodes[i].dom != null) return vnodes[i].dom
+ }
+ return nextSibling
+ }
+ function insertNode(parent, dom, nextSibling) {
+ if (nextSibling && nextSibling.parentNode) parent.insertBefore(dom, nextSibling)
+ else parent.appendChild(dom)
+ }
+ function setContentEditable(vnode) {
+ var children = vnode.children
+ if (children != null && children.length === 1 && children[0].tag === "<") {
+ var content = children[0].children
+ if (vnode.dom.innerHTML !== content) vnode.dom.innerHTML = content
+ }
+ else if (vnode.text != null || children != null && children.length !== 0) throw new Error("Child node of a contenteditable must be trusted")
+ }
+ //remove
+ function removeNodes(vnodes, start, end, context) {
+ for (var i = start; i < end; i++) {
+ var vnode = vnodes[i]
+ if (vnode != null) {
+ if (vnode.skip) vnode.skip = false
+ else removeNode(vnode, context)
+ }
+ }
+ }
+ function removeNode(vnode, context) {
+ var expected = 1, called = 0
+ if (vnode.attrs && vnode.attrs.onbeforeremove) {
+ var result = vnode.attrs.onbeforeremove.call(vnode.state, vnode)
+ if (result != null && typeof result.then === "function") {
+ expected++
+ result.then(continuation, continuation)
+ }
+ }
+ if (typeof vnode.tag !== "string" && vnode.tag.onbeforeremove) {
+ var result = vnode.tag.onbeforeremove.call(vnode.state, vnode)
+ if (result != null && typeof result.then === "function") {
+ expected++
+ result.then(continuation, continuation)
+ }
+ }
+ continuation()
+ function continuation() {
+ if (++called === expected) {
+ onremove(vnode)
+ if (vnode.dom) {
+ var count0 = vnode.domSize || 1
+ if (count0 > 1) {
+ var dom = vnode.dom
+ while (--count0) {
+ removeNodeFromDOM(dom.nextSibling)
+ }
+ }
+ removeNodeFromDOM(vnode.dom)
+ if (context != null && vnode.domSize == null && !hasIntegrationMethods(vnode.attrs) && typeof vnode.tag === "string") { //TODO test custom elements
+ if (!context.pool) context.pool = [vnode]
+ else context.pool.push(vnode)
+ }
+ }
+ }
+ }
+ }
+ function removeNodeFromDOM(node) {
+ var parent = node.parentNode
+ if (parent != null) parent.removeChild(node)
+ }
+ function onremove(vnode) {
+ if (vnode.attrs && vnode.attrs.onremove) vnode.attrs.onremove.call(vnode.state, vnode)
+ if (typeof vnode.tag !== "string" && vnode.tag.onremove) vnode.tag.onremove.call(vnode.state, vnode)
+ if (vnode.instance != null) onremove(vnode.instance)
+ else {
+ var children = vnode.children
+ if (Array.isArray(children)) {
+ for (var i = 0; i < children.length; i++) {
+ var child = children[i]
+ if (child != null) onremove(child)
+ }
+ }
+ }
+ }
+ //attrs2
+ function setAttrs(vnode, attrs2, ns) {
+ for (var key2 in attrs2) {
+ setAttr(vnode, key2, null, attrs2[key2], ns)
+ }
+ }
+ function setAttr(vnode, key2, old, value, ns) {
+ var element = vnode.dom
+ if (key2 === "key" || key2 === "is" || (old === value && !isFormAttribute(vnode, key2)) && typeof value !== "object" || typeof value === "undefined" || isLifecycleMethod(key2)) return
+ var nsLastIndex = key2.indexOf(":")
+ if (nsLastIndex > -1 && key2.substr(0, nsLastIndex) === "xlink") {
+ element.setAttributeNS("http://www.w3.org/1999/xlink", key2.slice(nsLastIndex + 1), value)
+ }
+ else if (key2[0] === "o" && key2[1] === "n" && typeof value === "function") updateEvent(vnode, key2, value)
+ else if (key2 === "style") updateStyle(element, old, value)
+ else if (key2 in element && !isAttribute(key2) && ns === undefined && !isCustomElement(vnode)) {
+ //setting input[value] to same value by typing on focused element moves cursor to end in Chrome
+ if (vnode.tag === "input" && key2 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
+ //setting select[value] to same value while having select open blinks select dropdown in Chrome
+ if (vnode.tag === "select" && key2 === "value" && vnode.dom.value === value && vnode.dom === $doc.activeElement) return
+ //setting option[value] to same value while having select open blinks select dropdown in Chrome
+ if (vnode.tag === "option" && key2 === "value" && vnode.dom.value === value) return
+ element[key2] = value
+ }
+ else {
+ if (typeof value === "boolean") {
+ if (value) element.setAttribute(key2, "")
+ else element.removeAttribute(key2)
+ }
+ else element.setAttribute(key2 === "className" ? "class" : key2, value)
+ }
+ }
+ function setLateAttrs(vnode) {
+ var attrs2 = vnode.attrs
+ if (vnode.tag === "select" && attrs2 != null) {
+ if ("value" in attrs2) setAttr(vnode, "value", null, attrs2.value, undefined)
+ if ("selectedIndex" in attrs2) setAttr(vnode, "selectedIndex", null, attrs2.selectedIndex, undefined)
+ }
+ }
+ function updateAttrs(vnode, old, attrs2, ns) {
+ if (attrs2 != null) {
+ for (var key2 in attrs2) {
+ setAttr(vnode, key2, old && old[key2], attrs2[key2], ns)
+ }
+ }
+ if (old != null) {
+ for (var key2 in old) {
+ if (attrs2 == null || !(key2 in attrs2)) {
+ if (key2 === "className") key2 = "class"
+ if (key2[0] === "o" && key2[1] === "n" && !isLifecycleMethod(key2)) updateEvent(vnode, key2, undefined)
+ else if (key2 !== "key") vnode.dom.removeAttribute(key2)
+ }
+ }
+ }
+ }
+ function isFormAttribute(vnode, attr) {
+ return attr === "value" || attr === "checked" || attr === "selectedIndex" || attr === "selected" && vnode.dom === $doc.activeElement
+ }
+ function isLifecycleMethod(attr) {
+ return attr === "oninit" || attr === "oncreate" || attr === "onupdate" || attr === "onremove" || attr === "onbeforeremove" || attr === "onbeforeupdate"
+ }
+ function isAttribute(attr) {
+ return attr === "href" || attr === "list" || attr === "form" || attr === "width" || attr === "height"// || attr === "type"
+ }
+ function isCustomElement(vnode){
+ return vnode.attrs.is || vnode.tag.indexOf("-") > -1
+ }
+ function hasIntegrationMethods(source) {
+ return source != null && (source.oncreate || source.onupdate || source.onbeforeremove || source.onremove)
+ }
+ //style
+ function updateStyle(element, old, style) {
+ if (old === style) element.style.cssText = "", old = null
+ if (style == null) element.style.cssText = ""
+ else if (typeof style === "string") element.style.cssText = style
+ else {
+ if (typeof old === "string") element.style.cssText = ""
+ for (var key2 in style) {
+ element.style[key2] = style[key2]
+ }
+ if (old != null && typeof old !== "string") {
+ for (var key2 in old) {
+ if (!(key2 in style)) element.style[key2] = ""
+ }
+ }
+ }
+ }
+ //event
+ function updateEvent(vnode, key2, value) {
+ var element = vnode.dom
+ var callback = typeof onevent !== "function" ? value : function(e) {
+ var result = value.call(element, e)
+ onevent.call(element, e)
+ return result
+ }
+ if (key2 in element) element[key2] = typeof value === "function" ? callback : null
+ else {
+ var eventName = key2.slice(2)
+ if (vnode.events === undefined) vnode.events = {}
+ if (vnode.events[key2] === callback) return
+ if (vnode.events[key2] != null) element.removeEventListener(eventName, vnode.events[key2], false)
+ if (typeof value === "function") {
+ vnode.events[key2] = callback
+ element.addEventListener(eventName, vnode.events[key2], false)
+ }
+ }
+ }
+ //lifecycle
+ function initLifecycle(source, vnode, hooks) {
+ if (typeof source.oninit === "function") source.oninit.call(vnode.state, vnode)
+ if (typeof source.oncreate === "function") hooks.push(source.oncreate.bind(vnode.state, vnode))
+ }
+ function updateLifecycle(source, vnode, hooks, recycling) {
+ if (recycling) initLifecycle(source, vnode, hooks)
+ else if (typeof source.onupdate === "function") hooks.push(source.onupdate.bind(vnode.state, vnode))
+ }
+ function shouldUpdate(vnode, old) {
+ var forceVnodeUpdate, forceComponentUpdate
+ if (vnode.attrs != null && typeof vnode.attrs.onbeforeupdate === "function") forceVnodeUpdate = vnode.attrs.onbeforeupdate.call(vnode.state, vnode, old)
+ if (typeof vnode.tag !== "string" && typeof vnode.tag.onbeforeupdate === "function") forceComponentUpdate = vnode.tag.onbeforeupdate.call(vnode.state, vnode, old)
+ if (!(forceVnodeUpdate === undefined && forceComponentUpdate === undefined) && !forceVnodeUpdate && !forceComponentUpdate) {
+ vnode.dom = old.dom
+ vnode.domSize = old.domSize
+ vnode.instance = old.instance
+ return true
+ }
+ return false
+ }
+ function render(dom, vnodes) {
+ if (!dom) throw new Error("Ensure the DOM element being passed to m.route/m.mount/m.render is not undefined.")
+ var hooks = []
+ var active = $doc.activeElement
+ // First time0 rendering into a node clears it out
+ if (dom.vnodes == null) dom.textContent = ""
+ if (!Array.isArray(vnodes)) vnodes = [vnodes]
+ updateNodes(dom, dom.vnodes, Vnode.normalizeChildren(vnodes), hooks, null, undefined)
+ dom.vnodes = vnodes
+ for (var i = 0; i < hooks.length; i++) hooks[i]()
+ if ($doc.activeElement !== active) active.focus()
+ }
+ return {render: render, setEventCallback: setEventCallback}
+}
+function throttle(callback) {
+ //60fps translates to 16.6ms, round it down since setTimeout requires int
+ var time = 16
+ var last = 0, pending = null
+ var timeout = typeof requestAnimationFrame === "function" ? requestAnimationFrame : setTimeout
+ return function() {
+ var now = Date.now()
+ if (last === 0 || now - last >= time) {
+ last = now
+ callback()
+ }
+ else if (pending === null) {
+ pending = timeout(function() {
+ pending = null
+ callback()
+ last = Date.now()
+ }, time - (now - last))
+ }
+ }
+}
+var _11 = function($window) {
+ var renderService = coreRenderer($window)
+ renderService.setEventCallback(function(e) {
+ if (e.redraw !== false) redraw()
+ })
+
+ var callbacks = []
+ function subscribe(key1, callback) {
+ unsubscribe(key1)
+ callbacks.push(key1, throttle(callback))
+ }
+ function unsubscribe(key1) {
+ var index = callbacks.indexOf(key1)
+ if (index > -1) callbacks.splice(index, 2)
+ }
+ function redraw() {
+ for (var i = 1; i < callbacks.length; i += 2) {
+ callbacks[i]()
+ }
+ }
+ return {subscribe: subscribe, unsubscribe: unsubscribe, redraw: redraw, render: renderService.render}
+}
+var redrawService = _11(window)
+requestService.setCompletionCallback(redrawService.redraw)
+var _16 = function(redrawService0) {
+ return function(root, component) {
+ if (component === null) {
+ redrawService0.render(root, [])
+ redrawService0.unsubscribe(root)
+ return
+ }
+
+ if (component.view == null) throw new Error("m.mount(element, component) expects a component, not a vnode")
+
+ var run0 = function() {
+ redrawService0.render(root, Vnode(component))
+ }
+ redrawService0.subscribe(root, run0)
+ redrawService0.redraw()
+ }
+}
+m.mount = _16(redrawService)
+var Promise = PromisePolyfill
+var parseQueryString = function(string) {
+ if (string === "" || string == null) return {}
+ if (string.charAt(0) === "?") string = string.slice(1)
+ var entries = string.split("&"), data0 = {}, counters = {}
+ for (var i = 0; i < entries.length; i++) {
+ var entry = entries[i].split("=")
+ var key5 = decodeURIComponent(entry[0])
+ var value = entry.length === 2 ? decodeURIComponent(entry[1]) : ""
+ if (value === "true") value = true
+ else if (value === "false") value = false
+ var levels = key5.split(/\]\[?|\[/)
+ var cursor = data0
+ if (key5.indexOf("[") > -1) levels.pop()
+ for (var j = 0; j < levels.length; j++) {
+ var level = levels[j], nextLevel = levels[j + 1]
+ var isNumber = nextLevel == "" || !isNaN(parseInt(nextLevel, 10))
+ var isValue = j === levels.length - 1
+ if (level === "") {
+ var key5 = levels.slice(0, j).join()
+ if (counters[key5] == null) counters[key5] = 0
+ level = counters[key5]++
+ }
+ if (cursor[level] == null) {
+ cursor[level] = isValue ? value : isNumber ? [] : {}
+ }
+ cursor = cursor[level]
+ }
+ }
+ return data0
+}
+var coreRouter = function($window) {
+ var supportsPushState = typeof $window.history.pushState === "function"
+ var callAsync0 = typeof setImmediate === "function" ? setImmediate : setTimeout
+ function normalize1(fragment0) {
+ var data = $window.location[fragment0].replace(/(?:%[a-f89][a-f0-9])+/gim, decodeURIComponent)
+ if (fragment0 === "pathname" && data[0] !== "/") data = "/" + data
+ return data
+ }
+ var asyncId
+ function debounceAsync(callback0) {
+ return function() {
+ if (asyncId != null) return
+ asyncId = callAsync0(function() {
+ asyncId = null
+ callback0()
+ })
+ }
+ }
+ function parsePath(path, queryData, hashData) {
+ var queryIndex = path.indexOf("?")
+ var hashIndex = path.indexOf("#")
+ var pathEnd = queryIndex > -1 ? queryIndex : hashIndex > -1 ? hashIndex : path.length
+ if (queryIndex > -1) {
+ var queryEnd = hashIndex > -1 ? hashIndex : path.length
+ var queryParams = parseQueryString(path.slice(queryIndex + 1, queryEnd))
+ for (var key4 in queryParams) queryData[key4] = queryParams[key4]
+ }
+ if (hashIndex > -1) {
+ var hashParams = parseQueryString(path.slice(hashIndex + 1))
+ for (var key4 in hashParams) hashData[key4] = hashParams[key4]
+ }
+ return path.slice(0, pathEnd)
+ }
+ var router = {prefix: "#!"}
+ router.getPath = function() {
+ var type2 = router.prefix.charAt(0)
+ switch (type2) {
+ case "#": return normalize1("hash").slice(router.prefix.length)
+ case "?": return normalize1("search").slice(router.prefix.length) + normalize1("hash")
+ default: return normalize1("pathname").slice(router.prefix.length) + normalize1("search") + normalize1("hash")
+ }
+ }
+ router.setPath = function(path, data, options) {
+ var queryData = {}, hashData = {}
+ path = parsePath(path, queryData, hashData)
+ if (data != null) {
+ for (var key4 in data) queryData[key4] = data[key4]
+ path = path.replace(/:([^\/]+)/g, function(match2, token) {
+ delete queryData[token]
+ return data[token]
+ })
+ }
+ var query = buildQueryString(queryData)
+ if (query) path += "?" + query
+ var hash = buildQueryString(hashData)
+ if (hash) path += "#" + hash
+ if (supportsPushState) {
+ var state = options ? options.state : null
+ var title = options ? options.title : null
+ $window.onpopstate()
+ if (options && options.replace) $window.history.replaceState(state, title, router.prefix + path)
+ else $window.history.pushState(state, title, router.prefix + path)
+ }
+ else $window.location.href = router.prefix + path
+ }
+ router.defineRoutes = function(routes, resolve, reject) {
+ function resolveRoute() {
+ var path = router.getPath()
+ var params = {}
+ var pathname = parsePath(path, params, params)
+
+ var state = $window.history.state
+ if (state != null) {
+ for (var k in state) params[k] = state[k]
+ }
+ for (var route0 in routes) {
+ var matcher = new RegExp("^" + route0.replace(/:[^\/]+?\.{3}/g, "(.*?)").replace(/:[^\/]+/g, "([^\\/]+)") + "\/?$")
+ if (matcher.test(pathname)) {
+ pathname.replace(matcher, function() {
+ var keys = route0.match(/:[^\/]+/g) || []
+ var values = [].slice.call(arguments, 1, -2)
+ for (var i = 0; i < keys.length; i++) {
+ params[keys[i].replace(/:|\./g, "")] = decodeURIComponent(values[i])
+ }
+ resolve(routes[route0], params, path, route0)
+ })
+ return
+ }
+ }
+ reject(path, params)
+ }
+
+ if (supportsPushState) $window.onpopstate = debounceAsync(resolveRoute)
+ else if (router.prefix.charAt(0) === "#") $window.onhashchange = resolveRoute
+ resolveRoute()
+ }
+
+ return router
+}
+var _20 = function($window, redrawService0) {
+ var routeService = coreRouter($window)
+ var identity = function(v) {return v}
+ var render1, component, attrs3, currentPath, lastUpdate
+ var route = function(root, defaultRoute, routes) {
+ if (root == null) throw new Error("Ensure the DOM element that was passed to `m.route` is not undefined")
+ var run1 = function() {
+ if (render1 != null) redrawService0.render(root, render1(Vnode(component, attrs3.key, attrs3)))
+ }
+ var bail = function() {
+ routeService.setPath(defaultRoute, null, {replace: true})
+ }
+ routeService.defineRoutes(routes, function(payload, params, path) {
+ var update = lastUpdate = function(routeResolver, comp) {
+ if (update !== lastUpdate) return
+ component = comp != null && typeof comp.view === "function" ? comp : "div", attrs3 = params, currentPath = path, lastUpdate = null
+ render1 = (routeResolver.render || identity).bind(routeResolver)
+ run1()
+ }
+ if (payload.view) update({}, payload)
+ else {
+ if (payload.onmatch) {
+ Promise.resolve(payload.onmatch(params, path)).then(function(resolved) {
+ update(payload, resolved)
+ }, bail)
+ }
+ else update(payload, "div")
+ }
+ }, bail)
+ redrawService0.subscribe(root, run1)
+ }
+ route.set = function(path, data, options) {
+ if (lastUpdate != null) options = {replace: true}
+ lastUpdate = null
+ routeService.setPath(path, data, options)
+ }
+ route.get = function() {return currentPath}
+ route.prefix = function(prefix0) {routeService.prefix = prefix0}
+ route.link = function(vnode1) {
+ vnode1.dom.setAttribute("href", routeService.prefix + vnode1.attrs.href)
+ vnode1.dom.onclick = function(e) {
+ if (e.ctrlKey || e.metaKey || e.shiftKey || e.which === 2) return
+ e.preventDefault()
+ e.redraw = false
+ var href = this.getAttribute("href")
+ if (href.indexOf(routeService.prefix) === 0) href = href.slice(routeService.prefix.length)
+ route.set(href, undefined, undefined)
+ }
+ }
+ route.param = function(key3) {
+ if(typeof attrs3 !== "undefined" && typeof key3 !== "undefined") return attrs3[key3]
+ return attrs3
+ }
+ return route
+}
+m.route = _20(window, redrawService)
+m.withAttr = function(attrName, callback1, context) {
+ return function(e) {
+ callback1.call(context || this, attrName in e.currentTarget ? e.currentTarget[attrName] : e.currentTarget.getAttribute(attrName))
+ }
+}
+var _28 = coreRenderer(window)
+m.render = _28.render
+m.redraw = redrawService.redraw
+m.request = requestService.request
+m.jsonp = requestService.jsonp
+m.parseQueryString = parseQueryString
+m.buildQueryString = buildQueryString
+m.version = "1.0.0"
+m.vnode = Vnode
+if (typeof module !== "undefined") module["exports"] = m
+else window.m = m
}
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {},require("timers").setImmediate)
},{"timers":2}],4:[function(require,module,exports){
diff --git a/configs/community_server/app.php b/configs/community_server/app.php
index 84bc93228..c52ae8047 100644
--- a/configs/community_server/app.php
+++ b/configs/community_server/app.php
@@ -1,408 +1,408 @@
- filter_var(env('DEBUG', true), FILTER_VALIDATE_BOOLEAN),
-
- /**
- * Configure basic information about the application.
- *
- * - namespace - The namespace to find app classes under.
- * - defaultLocale - The default locale for translation, formatting currencies and numbers, date and time.
- * - encoding - The encoding used for HTML + database connections.
- * - base - The base directory the app resides in. If false this
- * will be auto detected.
- * - dir - Name of app directory.
- * - webroot - The webroot directory.
- * - wwwRoot - The file path to webroot.
- * - baseUrl - To configure CakePHP to *not* use mod_rewrite and to
- * use CakePHP pretty URLs, remove these .htaccess
- * files:
- * /.htaccess
- * /webroot/.htaccess
- * And uncomment the baseUrl key below.
- * - fullBaseUrl - A base URL to use for absolute links. When set to false (default)
- * CakePHP generates required value based on `HTTP_HOST` environment variable.
- * However, you can define it manually to optimize performance or if you
- * are concerned about people manipulating the `Host` header.
- * - imageBaseUrl - Web path to the public images directory under webroot.
- * - cssBaseUrl - Web path to the public css directory under webroot.
- * - jsBaseUrl - Web path to the public js directory under webroot.
- * - paths - Configure paths for non class based resources. Supports the
- * `plugins`, `templates`, `locales` subkeys, which allow the definition of
- * paths for plugins, view templates and locale files respectively.
- */
- 'App' => [
- 'namespace' => 'App',
- 'encoding' => env('APP_ENCODING', 'UTF-8'),
- 'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'),
- 'defaultTimezone' => env('APP_DEFAULT_TIMEZONE', 'UTC'),
- 'base' => false,
- 'dir' => 'src',
- 'webroot' => 'webroot',
- 'wwwRoot' => WWW_ROOT,
- //'baseUrl' => env('SCRIPT_NAME'),
- 'fullBaseUrl' => false,
- 'imageBaseUrl' => 'img/',
- 'cssBaseUrl' => 'css/',
- 'jsBaseUrl' => 'js/',
- 'paths' => [
- 'plugins' => [ROOT . DS . 'plugins' . DS],
- 'templates' => [APP . 'Template' . DS],
- 'locales' => [APP . 'Locale' . DS],
- ],
- ],
-
- /**
- * Security and encryption configuration
- *
- * - salt - A random string used in security hashing methods.
- * The salt value is also used as the encryption key.
- * You should treat it as extremely sensitive data.
- */
- 'Security' => [
- 'salt' => env('SECURITY_SALT', '7ddf685a27d997ef36e51bdd626e7fc6b50a3abfb2971e8e59974d421116a150'),
- ],
-
- /**
- * Apply timestamps with the last modified time to static assets (js, css, images).
- * Will append a querystring parameter containing the time the file was modified.
- * This is useful for busting browser caches.
- *
- * Set to true to apply timestamps when debug is true. Set to 'force' to always
- * enable timestamping regardless of debug value.
- */
- 'Asset' => [
- //'timestamp' => true,
- // 'cacheTime' => '+1 year'
- ],
-
- /**
- * Configure the cache adapters.
- */
- 'Cache' => [
- 'default' => [
- 'className' => FileEngine::class,
- 'path' => CACHE,
- 'url' => env('CACHE_DEFAULT_URL', null),
- ],
-
- /**
- * Configure the cache used for general framework caching.
- * Translation cache files are stored with this configuration.
- * Duration will be set to '+2 minutes' in bootstrap.php when debug = true
- * If you set 'className' => 'Null' core cache will be disabled.
- */
- '_cake_core_' => [
- 'className' => FileEngine::class,
- 'prefix' => 'myapp_cake_core_',
- 'path' => CACHE . 'persistent/',
- 'serialize' => true,
- 'duration' => '+1 years',
- 'url' => env('CACHE_CAKECORE_URL', null),
- ],
-
- /**
- * Configure the cache for model and datasource caches. This cache
- * configuration is used to store schema descriptions, and table listings
- * in connections.
- * Duration will be set to '+2 minutes' in bootstrap.php when debug = true
- */
- '_cake_model_' => [
- 'className' => FileEngine::class,
- 'prefix' => 'myapp_cake_model_',
- 'path' => CACHE . 'models/',
- 'serialize' => true,
- 'duration' => '+1 years',
- 'url' => env('CACHE_CAKEMODEL_URL', null),
- ],
-
- /**
- * Configure the cache for routes. The cached routes collection is built the
- * first time the routes are processed via `config/routes.php`.
- * Duration will be set to '+2 seconds' in bootstrap.php when debug = true
- */
- '_cake_routes_' => [
- 'className' => FileEngine::class,
- 'prefix' => 'myapp_cake_routes_',
- 'path' => CACHE,
- 'serialize' => true,
- 'duration' => '+1 years',
- 'url' => env('CACHE_CAKEROUTES_URL', null),
- ],
- ],
-
- /**
- * Configure the Error and Exception handlers used by your application.
- *
- * By default errors are displayed using Debugger, when debug is true and logged
- * by Cake\Log\Log when debug is false.
- *
- * In CLI environments exceptions will be printed to stderr with a backtrace.
- * In web environments an HTML page will be displayed for the exception.
- * With debug true, framework errors like Missing Controller will be displayed.
- * When debug is false, framework errors will be coerced into generic HTTP errors.
- *
- * Options:
- *
- * - `errorLevel` - int - The level of errors you are interested in capturing.
- * - `trace` - boolean - Whether or not backtraces should be included in
- * logged errors/exceptions.
- * - `log` - boolean - Whether or not you want exceptions logged.
- * - `exceptionRenderer` - string - The class responsible for rendering
- * uncaught exceptions. If you choose a custom class you should place
- * the file for that class in src/Error. This class needs to implement a
- * render method.
- * - `skipLog` - array - List of exceptions to skip for logging. Exceptions that
- * extend one of the listed exceptions will also be skipped for logging.
- * E.g.:
- * `'skipLog' => ['Cake\Http\Exception\NotFoundException', 'Cake\Http\Exception\UnauthorizedException']`
- * - `extraFatalErrorMemory` - int - The number of megabytes to increase
- * the memory limit by when a fatal error is encountered. This allows
- * breathing room to complete logging or error handling.
- */
- 'Error' => [
- 'errorLevel' => E_ALL,
- 'exceptionRenderer' => ExceptionRenderer::class,
- 'skipLog' => [],
- 'log' => true,
- 'trace' => true,
- ],
-
- /**
- * Email configuration.
- *
- * By defining transports separately from delivery profiles you can easily
- * re-use transport configuration across multiple profiles.
- *
- * You can specify multiple configurations for production, development and
- * testing.
- *
- * Each transport needs a `className`. Valid options are as follows:
- *
- * Mail - Send using PHP mail function
- * Smtp - Send using SMTP
- * Debug - Do not send the email, just return the result
- *
- * You can add custom transports (or override existing transports) by adding the
- * appropriate file to src/Mailer/Transport. Transports should be named
- * 'YourTransport.php', where 'Your' is the name of the transport.
- */
- 'EmailTransport' => [
- 'default' => [
- 'className' => MailTransport::class,
- /*
- * The following keys are used in SMTP transports:
- */
- 'host' => 'localhost',
- 'port' => 25,
- 'timeout' => 30,
- 'username' => null,
- 'password' => null,
- 'client' => null,
- 'tls' => null,
- 'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
- ],
- ],
-
- /**
- * Email delivery profiles
- *
- * Delivery profiles allow you to predefine various properties about email
- * messages from your application and give the settings a name. This saves
- * duplication across your application and makes maintenance and development
- * easier. Each profile accepts a number of keys. See `Cake\Mailer\Email`
- * for more information.
- */
- 'Email' => [
- 'default' => [
- 'transport' => 'default',
- 'from' => 'you@localhost',
- //'charset' => 'utf-8',
- //'headerCharset' => 'utf-8',
- ],
- ],
-
- /**
- * Connection information used by the ORM to connect
- * to your application's datastores.
- *
- * ### Notes
- * - Drivers include Mysql Postgres Sqlite Sqlserver
- * See vendor\cakephp\cakephp\src\Database\Driver for complete list
- * - Do not use periods in database name - it may lead to error.
- * See https://github.com/cakephp/cakephp/issues/6471 for details.
- * - 'encoding' is recommended to be set to full UTF-8 4-Byte support.
- * E.g set it to 'utf8mb4' in MariaDB and MySQL and 'utf8' for any
- * other RDBMS.
- */
- 'Datasources' => [
- 'default' => [
- 'className' => Connection::class,
- 'driver' => Mysql::class,
- 'persistent' => false,
- 'host' => 'mariadb',
- /*
- * CakePHP will use the default DB port based on the driver selected
- * MySQL on MAMP uses port 8889, MAMP users will want to uncomment
- * the following line and set the port accordingly
- */
- //'port' => 'non_standard_port_number',
- 'username' => 'root',
- 'password' => '',
- 'database' => 'gradido_community',
- /*
- * You do not need to set this flag to use full utf-8 encoding (internal default since CakePHP 3.6).
- */
- //'encoding' => 'utf8mb4',
- 'timezone' => 'UTC',
- 'flags' => [],
- 'cacheMetadata' => true,
- 'log' => false,
-
- /**
- * Set identifier quoting to true if you are using reserved words or
- * special characters in your table or column names. Enabling this
- * setting will result in queries built using the Query Builder having
- * identifiers quoted when creating SQL. It should be noted that this
- * decreases performance because each query needs to be traversed and
- * manipulated before being executed.
- */
- 'quoteIdentifiers' => false,
-
- /**
- * During development, if using MySQL < 5.6, uncommenting the
- * following line could boost the speed at which schema metadata is
- * fetched from the database. It can also be set directly with the
- * mysql configuration directive 'innodb_stats_on_metadata = 0'
- * which is the recommended value in production environments
- */
- //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
-
- 'url' => env('DATABASE_URL', null),
- ],
-
- /**
- * The test connection is used during the test suite.
- */
- 'test' => [
- 'className' => Connection::class,
- 'driver' => Mysql::class,
- 'persistent' => false,
- 'host' => 'localhost',
- //'port' => 'non_standard_port_number',
- 'username' => 'my_app',
- 'password' => 'secret',
- 'database' => 'test_myapp',
- //'encoding' => 'utf8mb4',
- 'timezone' => 'UTC',
- 'cacheMetadata' => true,
- 'quoteIdentifiers' => false,
- 'log' => false,
- //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
- 'url' => env('DATABASE_TEST_URL', null),
- ],
- ],
-
- /**
- * Configures logging options
- */
- 'Log' => [
- 'debug' => [
- 'className' => FileLog::class,
- 'path' => LOGS,
- 'file' => 'debug',
- 'url' => env('LOG_DEBUG_URL', null),
- 'scopes' => false,
- 'levels' => ['notice', 'info', 'debug'],
- ],
- 'error' => [
- 'className' => FileLog::class,
- 'path' => LOGS,
- 'file' => 'error',
- 'url' => env('LOG_ERROR_URL', null),
- 'scopes' => false,
- 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
- ],
- // To enable this dedicated query log, you need set your datasource's log flag to true
- 'queries' => [
- 'className' => FileLog::class,
- 'path' => LOGS,
- 'file' => 'queries',
- 'url' => env('LOG_QUERIES_URL', null),
- 'scopes' => ['queriesLog'],
- ],
- ],
-
- /**
- * Session configuration.
- *
- * Contains an array of settings to use for session configuration. The
- * `defaults` key is used to define a default preset to use for sessions, any
- * settings declared here will override the settings of the default config.
- *
- * ## Options
- *
- * - `cookie` - The name of the cookie to use. Defaults to 'CAKEPHP'. Avoid using `.` in cookie names,
- * as PHP will drop sessions from cookies with `.` in the name.
- * - `cookiePath` - The url path for which session cookie is set. Maps to the
- * `session.cookie_path` php.ini config. Defaults to base path of app.
- * - `timeout` - The time in minutes the session should be valid for.
- * Pass 0 to disable checking timeout.
- * Please note that php.ini's session.gc_maxlifetime must be equal to or greater
- * than the largest Session['timeout'] in all served websites for it to have the
- * desired effect.
- * - `defaults` - The default configuration set to use as a basis for your session.
- * There are four built-in options: php, cake, cache, database.
- * - `handler` - Can be used to enable a custom session handler. Expects an
- * array with at least the `engine` key, being the name of the Session engine
- * class to use for managing the session. CakePHP bundles the `CacheSession`
- * and `DatabaseSession` engines.
- * - `ini` - An associative array of additional ini values to set.
- *
- * The built-in `defaults` options are:
- *
- * - 'php' - Uses settings defined in your php.ini.
- * - 'cake' - Saves session files in CakePHP's /tmp directory.
- * - 'database' - Uses CakePHP's database sessions.
- * - 'cache' - Use the Cache class to save sessions.
- *
- * To define a custom session handler, save it at src/Network/Session/.php.
- * Make sure the class implements PHP's `SessionHandlerInterface` and set
- * Session.handler to
- *
- * To use database sessions, load the SQL file located at config/schema/sessions.sql
- */
- 'Session' => [
- 'defaults' => 'php',
- ],
- // Gradido specific configuration
- // Login Server ip and port
- 'LoginServer' => [
- 'host' => 'http://login-server',
- 'port' => 1201
- ],
- 'API' => [
- 'allowedCaller' => ['login-server']
- ],
- 'ServerAdminEmail' => 'info@gradido.net',
- 'noReplyEmail' => 'no-reply@gradido.net',
- 'disableEmail' => true,
-
- 'GroupNode' => false
-];
+ filter_var(env('DEBUG', true), FILTER_VALIDATE_BOOLEAN),
+
+ /**
+ * Configure basic information about the application.
+ *
+ * - namespace - The namespace to find app classes under.
+ * - defaultLocale - The default locale for translation, formatting currencies and numbers, date and time.
+ * - encoding - The encoding used for HTML + database connections.
+ * - base - The base directory the app resides in. If false this
+ * will be auto detected.
+ * - dir - Name of app directory.
+ * - webroot - The webroot directory.
+ * - wwwRoot - The file path to webroot.
+ * - baseUrl - To configure CakePHP to *not* use mod_rewrite and to
+ * use CakePHP pretty URLs, remove these .htaccess
+ * files:
+ * /.htaccess
+ * /webroot/.htaccess
+ * And uncomment the baseUrl key below.
+ * - fullBaseUrl - A base URL to use for absolute links. When set to false (default)
+ * CakePHP generates required value based on `HTTP_HOST` environment variable.
+ * However, you can define it manually to optimize performance or if you
+ * are concerned about people manipulating the `Host` header.
+ * - imageBaseUrl - Web path to the public images directory under webroot.
+ * - cssBaseUrl - Web path to the public css directory under webroot.
+ * - jsBaseUrl - Web path to the public js directory under webroot.
+ * - paths - Configure paths for non class based resources. Supports the
+ * `plugins`, `templates`, `locales` subkeys, which allow the definition of
+ * paths for plugins, view templates and locale files respectively.
+ */
+ 'App' => [
+ 'namespace' => 'App',
+ 'encoding' => env('APP_ENCODING', 'UTF-8'),
+ 'defaultLocale' => env('APP_DEFAULT_LOCALE', 'en_US'),
+ 'defaultTimezone' => env('APP_DEFAULT_TIMEZONE', 'UTC'),
+ 'base' => false,
+ 'dir' => 'src',
+ 'webroot' => 'webroot',
+ 'wwwRoot' => WWW_ROOT,
+ //'baseUrl' => env('SCRIPT_NAME'),
+ 'fullBaseUrl' => false,
+ 'imageBaseUrl' => 'img/',
+ 'cssBaseUrl' => 'css/',
+ 'jsBaseUrl' => 'js/',
+ 'paths' => [
+ 'plugins' => [ROOT . DS . 'plugins' . DS],
+ 'templates' => [APP . 'Template' . DS],
+ 'locales' => [APP . 'Locale' . DS],
+ ],
+ ],
+
+ /**
+ * Security and encryption configuration
+ *
+ * - salt - A random string used in security hashing methods.
+ * The salt value is also used as the encryption key.
+ * You should treat it as extremely sensitive data.
+ */
+ 'Security' => [
+ 'salt' => env('SECURITY_SALT', '7ddf685a27d997ef36e51bdd626e7fc6b50a3abfb2971e8e59974d421116a150'),
+ ],
+
+ /**
+ * Apply timestamps with the last modified time to static assets (js, css, images).
+ * Will append a querystring parameter containing the time the file was modified.
+ * This is useful for busting browser caches.
+ *
+ * Set to true to apply timestamps when debug is true. Set to 'force' to always
+ * enable timestamping regardless of debug value.
+ */
+ 'Asset' => [
+ //'timestamp' => true,
+ // 'cacheTime' => '+1 year'
+ ],
+
+ /**
+ * Configure the cache adapters.
+ */
+ 'Cache' => [
+ 'default' => [
+ 'className' => FileEngine::class,
+ 'path' => CACHE,
+ 'url' => env('CACHE_DEFAULT_URL', null),
+ ],
+
+ /**
+ * Configure the cache used for general framework caching.
+ * Translation cache files are stored with this configuration.
+ * Duration will be set to '+2 minutes' in bootstrap.php when debug = true
+ * If you set 'className' => 'Null' core cache will be disabled.
+ */
+ '_cake_core_' => [
+ 'className' => FileEngine::class,
+ 'prefix' => 'myapp_cake_core_',
+ 'path' => CACHE . 'persistent/',
+ 'serialize' => true,
+ 'duration' => '+1 years',
+ 'url' => env('CACHE_CAKECORE_URL', null),
+ ],
+
+ /**
+ * Configure the cache for model and datasource caches. This cache
+ * configuration is used to store schema descriptions, and table listings
+ * in connections.
+ * Duration will be set to '+2 minutes' in bootstrap.php when debug = true
+ */
+ '_cake_model_' => [
+ 'className' => FileEngine::class,
+ 'prefix' => 'myapp_cake_model_',
+ 'path' => CACHE . 'models/',
+ 'serialize' => true,
+ 'duration' => '+1 years',
+ 'url' => env('CACHE_CAKEMODEL_URL', null),
+ ],
+
+ /**
+ * Configure the cache for routes. The cached routes collection is built the
+ * first time the routes are processed via `config/routes.php`.
+ * Duration will be set to '+2 seconds' in bootstrap.php when debug = true
+ */
+ '_cake_routes_' => [
+ 'className' => FileEngine::class,
+ 'prefix' => 'myapp_cake_routes_',
+ 'path' => CACHE,
+ 'serialize' => true,
+ 'duration' => '+1 years',
+ 'url' => env('CACHE_CAKEROUTES_URL', null),
+ ],
+ ],
+
+ /**
+ * Configure the Error and Exception handlers used by your application.
+ *
+ * By default errors are displayed using Debugger, when debug is true and logged
+ * by Cake\Log\Log when debug is false.
+ *
+ * In CLI environments exceptions will be printed to stderr with a backtrace.
+ * In web environments an HTML page will be displayed for the exception.
+ * With debug true, framework errors like Missing Controller will be displayed.
+ * When debug is false, framework errors will be coerced into generic HTTP errors.
+ *
+ * Options:
+ *
+ * - `errorLevel` - int - The level of errors you are interested in capturing.
+ * - `trace` - boolean - Whether or not backtraces should be included in
+ * logged errors/exceptions.
+ * - `log` - boolean - Whether or not you want exceptions logged.
+ * - `exceptionRenderer` - string - The class responsible for rendering
+ * uncaught exceptions. If you choose a custom class you should place
+ * the file for that class in src/Error. This class needs to implement a
+ * render method.
+ * - `skipLog` - array - List of exceptions to skip for logging. Exceptions that
+ * extend one of the listed exceptions will also be skipped for logging.
+ * E.g.:
+ * `'skipLog' => ['Cake\Http\Exception\NotFoundException', 'Cake\Http\Exception\UnauthorizedException']`
+ * - `extraFatalErrorMemory` - int - The number of megabytes to increase
+ * the memory limit by when a fatal error is encountered. This allows
+ * breathing room to complete logging or error handling.
+ */
+ 'Error' => [
+ 'errorLevel' => E_ALL,
+ 'exceptionRenderer' => ExceptionRenderer::class,
+ 'skipLog' => [],
+ 'log' => true,
+ 'trace' => true,
+ ],
+
+ /**
+ * Email configuration.
+ *
+ * By defining transports separately from delivery profiles you can easily
+ * re-use transport configuration across multiple profiles.
+ *
+ * You can specify multiple configurations for production, development and
+ * testing.
+ *
+ * Each transport needs a `className`. Valid options are as follows:
+ *
+ * Mail - Send using PHP mail function
+ * Smtp - Send using SMTP
+ * Debug - Do not send the email, just return the result
+ *
+ * You can add custom transports (or override existing transports) by adding the
+ * appropriate file to src/Mailer/Transport. Transports should be named
+ * 'YourTransport.php', where 'Your' is the name of the transport.
+ */
+ 'EmailTransport' => [
+ 'default' => [
+ 'className' => MailTransport::class,
+ /*
+ * The following keys are used in SMTP transports:
+ */
+ 'host' => 'localhost',
+ 'port' => 25,
+ 'timeout' => 30,
+ 'username' => null,
+ 'password' => null,
+ 'client' => null,
+ 'tls' => null,
+ 'url' => env('EMAIL_TRANSPORT_DEFAULT_URL', null),
+ ],
+ ],
+
+ /**
+ * Email delivery profiles
+ *
+ * Delivery profiles allow you to predefine various properties about email
+ * messages from your application and give the settings a name. This saves
+ * duplication across your application and makes maintenance and development
+ * easier. Each profile accepts a number of keys. See `Cake\Mailer\Email`
+ * for more information.
+ */
+ 'Email' => [
+ 'default' => [
+ 'transport' => 'default',
+ 'from' => 'you@localhost',
+ //'charset' => 'utf-8',
+ //'headerCharset' => 'utf-8',
+ ],
+ ],
+
+ /**
+ * Connection information used by the ORM to connect
+ * to your application's datastores.
+ *
+ * ### Notes
+ * - Drivers include Mysql Postgres Sqlite Sqlserver
+ * See vendor\cakephp\cakephp\src\Database\Driver for complete list
+ * - Do not use periods in database name - it may lead to error.
+ * See https://github.com/cakephp/cakephp/issues/6471 for details.
+ * - 'encoding' is recommended to be set to full UTF-8 4-Byte support.
+ * E.g set it to 'utf8mb4' in MariaDB and MySQL and 'utf8' for any
+ * other RDBMS.
+ */
+ 'Datasources' => [
+ 'default' => [
+ 'className' => Connection::class,
+ 'driver' => Mysql::class,
+ 'persistent' => false,
+ 'host' => 'mariadb',
+ /*
+ * CakePHP will use the default DB port based on the driver selected
+ * MySQL on MAMP uses port 8889, MAMP users will want to uncomment
+ * the following line and set the port accordingly
+ */
+ //'port' => 'non_standard_port_number',
+ 'username' => 'root',
+ 'password' => '',
+ 'database' => 'gradido_community',
+ /*
+ * You do not need to set this flag to use full utf-8 encoding (internal default since CakePHP 3.6).
+ */
+ //'encoding' => 'utf8mb4',
+ 'timezone' => 'UTC',
+ 'flags' => [],
+ 'cacheMetadata' => true,
+ 'log' => false,
+
+ /**
+ * Set identifier quoting to true if you are using reserved words or
+ * special characters in your table or column names. Enabling this
+ * setting will result in queries built using the Query Builder having
+ * identifiers quoted when creating SQL. It should be noted that this
+ * decreases performance because each query needs to be traversed and
+ * manipulated before being executed.
+ */
+ 'quoteIdentifiers' => false,
+
+ /**
+ * During development, if using MySQL < 5.6, uncommenting the
+ * following line could boost the speed at which schema metadata is
+ * fetched from the database. It can also be set directly with the
+ * mysql configuration directive 'innodb_stats_on_metadata = 0'
+ * which is the recommended value in production environments
+ */
+ //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
+
+ 'url' => env('DATABASE_URL', null),
+ ],
+
+ /**
+ * The test connection is used during the test suite.
+ */
+ 'test' => [
+ 'className' => Connection::class,
+ 'driver' => Mysql::class,
+ 'persistent' => false,
+ 'host' => 'localhost',
+ //'port' => 'non_standard_port_number',
+ 'username' => 'my_app',
+ 'password' => 'secret',
+ 'database' => 'test_myapp',
+ //'encoding' => 'utf8mb4',
+ 'timezone' => 'UTC',
+ 'cacheMetadata' => true,
+ 'quoteIdentifiers' => false,
+ 'log' => false,
+ //'init' => ['SET GLOBAL innodb_stats_on_metadata = 0'],
+ 'url' => env('DATABASE_TEST_URL', null),
+ ],
+ ],
+
+ /**
+ * Configures logging options
+ */
+ 'Log' => [
+ 'debug' => [
+ 'className' => FileLog::class,
+ 'path' => LOGS,
+ 'file' => 'debug',
+ 'url' => env('LOG_DEBUG_URL', null),
+ 'scopes' => false,
+ 'levels' => ['notice', 'info', 'debug'],
+ ],
+ 'error' => [
+ 'className' => FileLog::class,
+ 'path' => LOGS,
+ 'file' => 'error',
+ 'url' => env('LOG_ERROR_URL', null),
+ 'scopes' => false,
+ 'levels' => ['warning', 'error', 'critical', 'alert', 'emergency'],
+ ],
+ // To enable this dedicated query log, you need set your datasource's log flag to true
+ 'queries' => [
+ 'className' => FileLog::class,
+ 'path' => LOGS,
+ 'file' => 'queries',
+ 'url' => env('LOG_QUERIES_URL', null),
+ 'scopes' => ['queriesLog'],
+ ],
+ ],
+
+ /**
+ * Session configuration.
+ *
+ * Contains an array of settings to use for session configuration. The
+ * `defaults` key is used to define a default preset to use for sessions, any
+ * settings declared here will override the settings of the default config.
+ *
+ * ## Options
+ *
+ * - `cookie` - The name of the cookie to use. Defaults to 'CAKEPHP'. Avoid using `.` in cookie names,
+ * as PHP will drop sessions from cookies with `.` in the name.
+ * - `cookiePath` - The url path for which session cookie is set. Maps to the
+ * `session.cookie_path` php.ini config. Defaults to base path of app.
+ * - `timeout` - The time in minutes the session should be valid for.
+ * Pass 0 to disable checking timeout.
+ * Please note that php.ini's session.gc_maxlifetime must be equal to or greater
+ * than the largest Session['timeout'] in all served websites for it to have the
+ * desired effect.
+ * - `defaults` - The default configuration set to use as a basis for your session.
+ * There are four built-in options: php, cake, cache, database.
+ * - `handler` - Can be used to enable a custom session handler. Expects an
+ * array with at least the `engine` key, being the name of the Session engine
+ * class to use for managing the session. CakePHP bundles the `CacheSession`
+ * and `DatabaseSession` engines.
+ * - `ini` - An associative array of additional ini values to set.
+ *
+ * The built-in `defaults` options are:
+ *
+ * - 'php' - Uses settings defined in your php.ini.
+ * - 'cake' - Saves session files in CakePHP's /tmp directory.
+ * - 'database' - Uses CakePHP's database sessions.
+ * - 'cache' - Use the Cache class to save sessions.
+ *
+ * To define a custom session handler, save it at src/Network/Session/.php.
+ * Make sure the class implements PHP's `SessionHandlerInterface` and set
+ * Session.handler to
+ *
+ * To use database sessions, load the SQL file located at config/schema/sessions.sql
+ */
+ 'Session' => [
+ 'defaults' => 'php',
+ ],
+ // Gradido specific configuration
+ // Login Server ip and port
+ 'LoginServer' => [
+ 'host' => 'http://login-server',
+ 'port' => 1201
+ ],
+ 'API' => [
+ 'allowedCaller' => ['login-server']
+ ],
+ 'ServerAdminEmail' => 'info@gradido.net',
+ 'noReplyEmail' => 'no-reply@gradido.net',
+ 'disableEmail' => true,
+
+ 'GroupNode' => false
+];
diff --git a/configs/login_server/grd_login.properties b/configs/login_server/grd_login.properties
index eccde7fb8..853dfe6af 100644
--- a/configs/login_server/grd_login.properties
+++ b/configs/login_server/grd_login.properties
@@ -1,51 +1,51 @@
-HTTPServer.port = 1200
-JSONServer.port = 1201
-Gradido.group_id = 1
-
-crypto.server_admin_public = f909a866baec97c5460b8d7a93b72d3d4d20cc45d9f15d78bd83944eb9286b7f
-crypto.server_key = a51ef8ac7ef1abf162fb7a65261acd7a
-# TODO auto-generate in docker build step
-crypto.app_secret = 21ffbbc616fe
-
-# Server admin Passphrase
-# nerve execute merit pool talk hockey basic win cargo spin disagree ethics swear price purchase say clutch decrease slow half forest reform cheese able
-#
-
-phpServer.url = http://localhost/
-phpServer.host = nginx
-
-loginServer.path = http://localhost/account
-loginServer.default_locale = de
-loginServer.db.host = mariadb
-loginServer.db.name = gradido_login
-loginServer.db.user = root
-loginServer.db.password =
-loginServer.db.port = 3306
-
-email.disable = true
-#email.username =
-#email.sender =
-#email.admin_receiver =
-#email.password =
-#email.smtp.url =
-#email.smtp.port =
-
-# binary is default, for debugging also json is possible
-#hedera.consensus.message_format = json
-# TESTNET or MAINNET, TESTNET is default
-hedera.nettype = TESTNET
-
-# server setup types: test, staging or production
-ServerSetupType=test
-
-
-# Session timeout in minutes
-#
-session.timeout = 15
-
-# Disabling security features for faster develop and testing
-unsecure.allow_passwort_via_json_request = 1
-unsecure.allow_auto_sign_transactions = 1
-unsecure.allow_cors_all = 1
-# default disable, passwords must contain a number, a lower character, a high character, special character, and be at least 8 characters long
+HTTPServer.port = 1200
+JSONServer.port = 1201
+Gradido.group_id = 1
+
+crypto.server_admin_public = f909a866baec97c5460b8d7a93b72d3d4d20cc45d9f15d78bd83944eb9286b7f
+crypto.server_key = a51ef8ac7ef1abf162fb7a65261acd7a
+# TODO auto-generate in docker build step
+crypto.app_secret = 21ffbbc616fe
+
+# Server admin Passphrase
+# nerve execute merit pool talk hockey basic win cargo spin disagree ethics swear price purchase say clutch decrease slow half forest reform cheese able
+#
+
+phpServer.url = http://localhost/
+phpServer.host = nginx
+
+loginServer.path = http://localhost/account
+loginServer.default_locale = de
+loginServer.db.host = mariadb
+loginServer.db.name = gradido_login
+loginServer.db.user = root
+loginServer.db.password =
+loginServer.db.port = 3306
+
+email.disable = true
+#email.username =
+#email.sender =
+#email.admin_receiver =
+#email.password =
+#email.smtp.url =
+#email.smtp.port =
+
+# binary is default, for debugging also json is possible
+#hedera.consensus.message_format = json
+# TESTNET or MAINNET, TESTNET is default
+hedera.nettype = TESTNET
+
+# server setup types: test, staging or production
+ServerSetupType=test
+
+
+# Session timeout in minutes
+#
+session.timeout = 15
+
+# Disabling security features for faster develop and testing
+unsecure.allow_passwort_via_json_request = 1
+unsecure.allow_auto_sign_transactions = 1
+unsecure.allow_cors_all = 1
+# default disable, passwords must contain a number, a lower character, a high character, special character, and be at least 8 characters long
unsecure.allow_all_passwords = 1
\ No newline at end of file
diff --git a/configs/node_server/gradido.conf b/configs/node_server/gradido.conf
index 3edf0897c..c16adf5d5 100644
--- a/configs/node_server/gradido.conf
+++ b/configs/node_server/gradido.conf
@@ -1,20 +1,20 @@
-worker_count = 2
-io_worker_count = 1
-data_root_folder = /opt/instance/.gradido
-hedera_mirror_endpoint = hcs.testnet.mirrornode.hedera.com:5600
-sibling_node_file = /opt/instance/.gradido/sibling_nodes.txt
-#group_requests_endpoint = 0.0.0.0:13701
-#record_requests_endpoint = 0.0.0.0:13702
-#manage_network_requests_endpoint = 0.0.0.0:13703
-grpc_endpoint = 0.0.0.0:13701
-json_rpc_port = 13702
-
-
-# larger value, larger batch, less concurrency
-blockchain_append_batch_size = 1000
-#blochchain_init_batch_size = 1000
-#block_record_outbound_batch_size = 100
-general_batch_size = 1000
-group_register_topic_id = 0.0.79574
-
-topic_reset_allowed = 1
+worker_count = 2
+io_worker_count = 1
+data_root_folder = /opt/instance/.gradido
+hedera_mirror_endpoint = hcs.testnet.mirrornode.hedera.com:5600
+sibling_node_file = /opt/instance/.gradido/sibling_nodes.txt
+#group_requests_endpoint = 0.0.0.0:13701
+#record_requests_endpoint = 0.0.0.0:13702
+#manage_network_requests_endpoint = 0.0.0.0:13703
+grpc_endpoint = 0.0.0.0:13701
+json_rpc_port = 13702
+
+
+# larger value, larger batch, less concurrency
+blockchain_append_batch_size = 1000
+#blochchain_init_batch_size = 1000
+#block_record_outbound_batch_size = 100
+general_batch_size = 1000
+group_register_topic_id = 0.0.79574
+
+topic_reset_allowed = 1
diff --git a/docker-compose.yml b/docker-compose.yml
index ad1317556..ad291eac8 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -1,136 +1,138 @@
-# This file defines the production settings. It is overwritten by docker-compose.override.yml,
-# which defines the development settings. The override.yml is loaded by default. Therefore it
-# is required to explicitly define if you want an production build:
-# > docker-compose -f docker-compose.yml up
-
-version: "3.4"
-
-services:
- ########################################################
- # FRONTEND #############################################
- ########################################################
- frontend:
- image: gradido/frontend:latest
- build:
- context: ./frontend
- target: production
- networks:
- - external-net
- ports:
- - 8080:8080
- environment:
- # Envs used in Dockerfile
- # - DOCKER_WORKDIR="/app"
- # - PORT="8080"
- - BUILD_DATE
- - BUILD_VERSION
- - BUILD_COMMIT
- - NODE_ENV="production"
- # Application only envs
- #- HOST=0.0.0.0 # This is nuxt specific, alternative value is HOST=webapp
- #env_file:
- # - ./frontend/.env
-
- #########################################################
- ## MARIADB ##############################################
- #########################################################
- mariadb:
- build:
- context: .
- dockerfile: ./mariadb/Dockerfile
- target: mariadb_server
- container_name: mariadb
- environment:
- - MARIADB_ALLOW_EMPTY_PASSWORD=1
- - MARIADB_USER=root
- networks:
- - internal-net
- ports:
- - 3306:3306
- volumes:
- - db_vol:/var/lib/mysql
-
- #########################################################
- ## LOGIN SERVER #########################################
- #########################################################
- login-server:
- build:
- context: ./login_server/
- target: login_server
- container_name: login-server
- depends_on:
- - mariadb
- networks:
- - internal-net
- ports:
- - 1200:1200
- - 1201:1201
- volumes:
- - ./configs/login_server:/etc/grd_login
-
- #########################################################
- ## NGINX ################################################
- #########################################################
- ## nginx, connect login-server and community-server together (and php-fpm to community-server)
- nginx:
- build:
- context: .
- dockerfile: ./nginx/Dockerfile
- container_name: nginx
- networks:
- - external-net
- - internal-net
- depends_on:
- - community-server
- - login-server
- ports:
- - 80:80
-
- #########################################################
- ## COMMUNITY SERVER (cakephp with php-fpm) ##############
- #########################################################
- community-server:
- build:
- context: .
- dockerfile: ./community_server/Dockerfile
- container_name: community-server
- environment:
- - DB_PASSWORD=''
- - DB_USER='root'
- - DB_DATABASE='gradido_community'
- depends_on:
- - mariadb
- networks:
- - internal-net
- volumes:
- - ./community_server/config/php-fpm/php-ini-overrides.ini:/etc/php/7.4/fpm/conf.d/99-overrides.ini
-
- #########################################################
- ## GRADIDO NODE v1 ######################################
- #########################################################
- # gradido-node:
- # build:
- # context: .
- # dockerfile: ./gn/docker/deprecated-hedera-node/Dockerfile
- # volumes:
- # - ${GN_INSTANCE_FOLDER}:/opt/instance
- # container_name: ${GN_CONTAINER_NAME}
-
- #########################################################
- ## GRADIDO NODE test ###################################
- #########################################################
- # gradido-node-test:
- # build:
- # context: .
- # dockerfile: ./gn/docker/deprecated-hedera-node/Dockerfile
- # container_name: gn-test
- # working_dir: /opt/gn/build
- # command: ["./unit_tests"]
-
-networks:
- external-net:
- internal-net:
- internal: true
-
-volumes:
- db_vol:
+# This file defines the production settings. It is overwritten by docker-compose.override.yml,
+# which defines the development settings. The override.yml is loaded by default. Therefore it
+# is required to explicitly define if you want an production build:
+# > docker-compose -f docker-compose.yml up
+
+version: "3.4"
+
+services:
+ ########################################################
+ # FRONTEND #############################################
+ ########################################################
+ frontend:
+ image: gradido/frontend:latest
+ build:
+ context: ./frontend
+ target: production
+ networks:
+ - external-net
+ depends_on:
+ - nginx
+ ports:
+ - 8080:8080
+ environment:
+ # Envs used in Dockerfile
+ # - DOCKER_WORKDIR="/app"
+ # - PORT="8080"
+ - BUILD_DATE
+ - BUILD_VERSION
+ - BUILD_COMMIT
+ - NODE_ENV="production"
+ # Application only envs
+ #- HOST=0.0.0.0 # This is nuxt specific, alternative value is HOST=webapp
+ #env_file:
+ # - ./frontend/.env
+
+ #########################################################
+ ## MARIADB ##############################################
+ #########################################################
+ mariadb:
+ build:
+ context: .
+ dockerfile: ./mariadb/Dockerfile
+ target: mariadb_server
+ container_name: mariadb
+ environment:
+ - MARIADB_ALLOW_EMPTY_PASSWORD=1
+ - MARIADB_USER=root
+ networks:
+ - internal-net
+ ports:
+ - 3306:3306
+ volumes:
+ - db_vol:/var/lib/mysql
+
+ #########################################################
+ ## LOGIN SERVER #########################################
+ #########################################################
+ login-server:
+ build:
+ context: ./login_server/
+ target: login_server
+ container_name: login-server
+ depends_on:
+ - mariadb
+ networks:
+ - internal-net
+ ports:
+ - 1200:1200
+ - 1201:1201
+ volumes:
+ - ./configs/login_server:/etc/grd_login
+
+ #########################################################
+ ## NGINX ################################################
+ #########################################################
+ ## nginx, connect login-server and community-server together (and php-fpm to community-server)
+ nginx:
+ build:
+ context: .
+ dockerfile: ./nginx/Dockerfile
+ container_name: nginx
+ networks:
+ - external-net
+ - internal-net
+ depends_on:
+ - community-server
+ - login-server
+ ports:
+ - 80:80
+
+ #########################################################
+ ## COMMUNITY SERVER (cakephp with php-fpm) ##############
+ #########################################################
+ community-server:
+ build:
+ context: .
+ dockerfile: ./community_server/Dockerfile
+ container_name: community-server
+ environment:
+ - DB_PASSWORD=''
+ - DB_USER='root'
+ - DB_DATABASE='gradido_community'
+ depends_on:
+ - mariadb
+ networks:
+ - internal-net
+ volumes:
+ - ./community_server/config/php-fpm/php-ini-overrides.ini:/etc/php/7.4/fpm/conf.d/99-overrides.ini
+
+ #########################################################
+ ## GRADIDO NODE v1 ######################################
+ #########################################################
+ # gradido-node:
+ # build:
+ # context: .
+ # dockerfile: ./gn/docker/deprecated-hedera-node/Dockerfile
+ # volumes:
+ # - ${GN_INSTANCE_FOLDER}:/opt/instance
+ # container_name: ${GN_CONTAINER_NAME}
+
+ #########################################################
+ ## GRADIDO NODE test ###################################
+ #########################################################
+ # gradido-node-test:
+ # build:
+ # context: .
+ # dockerfile: ./gn/docker/deprecated-hedera-node/Dockerfile
+ # container_name: gn-test
+ # working_dir: /opt/gn/build
+ # command: ["./unit_tests"]
+
+networks:
+ external-net:
+ internal-net:
+ internal: true
+
+volumes:
+ db_vol:
diff --git a/docu/community-server.api.md b/docu/community-server.api.md
index d45911c6f..bcd5da61d 100644
--- a/docu/community-server.api.md
+++ b/docu/community-server.api.md
@@ -1,131 +1,131 @@
-# community server api
-
-In this examples I assume that you use gradido with or docker-compose build on your local maschine
-
-## Konto Overview
-return current account balance
-
-GET http://localhost/state-balances/ajaxGetBalance/-127182
-
-If session is valid, return:
-```json
-{"state":"success","balance":174500}
-```
-- balance: Gradido Cent, 4 Nachkommastellen (2 Reserve), 174500 = 17,45 GDD
-
-## List Transactions
-List all transactions from logged in user, currently without paging
-Ajax:
-GET http://localhost/state-balances/ajaxListTransactions/-127182/
-or
-GET http://localhost/state-balances/ajaxListTransactions/-127182/DESC
-to get transaction in descending order
-
-Antwort:
-Wenn alles okay:
-```json
-{"state":"success", "transactions":
- [
- {
- "name": "Max Mustermann",
- "email": "Maxim Mustermann",
- "type": "send",
- "transaction_id": 2,
- "date": "2021-02-19T13:25:36+00:00",
- "balance": 1920000,
- "memo": "a piece of cake :)",
- "pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7"
- }
- ],
- "transactionExecutingCount": 0,
- "count": 1,
- "gdtSum": 0,
- "timeUsed": 0.04562687873840332
-}
-```
-
-- name: name of other involved party or empty if unknown (if other party don't belong to group)
- - if type is send, name is name of receiver
- - if type is receive, name is name of sender
- - if type is creation currently I use a static string ("Gradido Akademie)
-- email: optional, only if type is send or receive and other user is known
-- pubkey: optional, only if type is send or receive and other user isn't known
-- type: type of transaction
- - creation: user has get gradidos created
- - send: user has send another user gradidos
- - receiver: user has received gradidos from another user
-- transaction_id: id of transaction in db, in stage2 also the hedera sequence number of transaction
-- date: date of ordering transaction (booking date)
-- balance: Gradido Cent, 4 Nachkommastellen (2 Reserve), 1920000 = 192,00 GDD
-- memo: Details about transaction
-- pubkey: optional, if other party isn't known, hexadecimal representation of 32 Byte public key of user [0-9a-f]
-
-- transactionExecutingCount: how many transaction for this user currently pending and waiting for signing
-- count: sum of finished transactions user is involved
-- gdtSum: sum of gdt of user in cent with 2 places (Nachkommastellen)
-- timeUsed: time used for getting data from db in seconds, only for analyse backend performance
-
-## Creation Transaction
-Make a creation transaction
-With new Option set in Login-Server:
-```ini
-unsecure.allow_auto_sign_transactions = 1
-```
-transactions can be auto-signed directly with handing in transaction.
-Normally a forwarding to login-server check transactions side is neccessary to minimize security risks.
-
-POST http://localhost/transaction-creations/ajaxCreate
-```json
-{
- "session_id" : -127182,
- "email": "max.musterman@gmail.de",
- "amount": 10000000,
- "target_date":"2021-02-19T13:25:36+00:00",
- "memo":"AGE",
- "auto_sign": true
-}
-```
-return if everything is ok:
-```json
-{"state":"success", "timeUsed": 0.0122}
-```
-- timeUsed: time used for getting data from db in seconds, only for analyse backend performance
-
-## Send Coins Transaction
-Make a simple GDD Transaction, send Coins from one user to other.
-With new Option set in Login-Server:
-```ini
-unsecure.allow_auto_sign_transactions = 1
-```
-transactions can be auto-signed directly with handing in transaction.
-Normally a forwarding to login-server check transactions side is neccessary to minimize security risks.
-
-POST http://localhost/transaction-send-coins/ajaxCreate
-```json
-{
- "session_id" : -127182,
- "amount": 2000000,
- "email": "max.musterman@gmail.de",
- "memo":"Thank you :)",
- "auto_sign": true
-}
-```
-- amout: amount to transfer, 2000000 = 200,00 GDD
-- email: receiver email address, must be differ from user email
-- memo: Details about transaction
-- auto_sign: set to true to directly sign transaction if unsecure.allow_auto_sign_transactions = 1 is set
-
-return if everything is ok:
-```json
-{"state":"success", "timeUsed": 0.0122}
-```
-- timeUsed: time used for getting data from db in seconds, only for analyse backend performance
-
-Than the transaction was created on community server, send to login-server, signed (if unsecure.allow_auto_sign_transactions = 1 and auto_sign = true)
-and send back to community server and put into db.
-After you get this answear you see the new transaction if you list transactions or call for the balance.
-
-Without auto-sign the transaction is pending on login-server and waits for the user to review it at
-http://localhost/account/checkTransactions
-
-
+# community server api
+
+In this examples I assume that you use gradido with or docker-compose build on your local maschine
+
+## Konto Overview
+return current account balance
+
+GET http://localhost/state-balances/ajaxGetBalance/-127182
+
+If session is valid, return:
+```json
+{"state":"success","balance":174500}
+```
+- balance: Gradido Cent, 4 Nachkommastellen (2 Reserve), 174500 = 17,45 GDD
+
+## List Transactions
+List all transactions from logged in user, currently without paging
+Ajax:
+GET http://localhost/state-balances/ajaxListTransactions/-127182/
+or
+GET http://localhost/state-balances/ajaxListTransactions/-127182/DESC
+to get transaction in descending order
+
+Antwort:
+Wenn alles okay:
+```json
+{"state":"success", "transactions":
+ [
+ {
+ "name": "Max Mustermann",
+ "email": "Maxim Mustermann",
+ "type": "send",
+ "transaction_id": 2,
+ "date": "2021-02-19T13:25:36+00:00",
+ "balance": 1920000,
+ "memo": "a piece of cake :)",
+ "pubkey": "038a6f93270dc57b91d76bf110ad3863fcb7d1b08e7692e793fcdb4467e5b6a7"
+ }
+ ],
+ "transactionExecutingCount": 0,
+ "count": 1,
+ "gdtSum": 0,
+ "timeUsed": 0.04562687873840332
+}
+```
+
+- name: name of other involved party or empty if unknown (if other party don't belong to group)
+ - if type is send, name is name of receiver
+ - if type is receive, name is name of sender
+ - if type is creation currently I use a static string ("Gradido Akademie)
+- email: optional, only if type is send or receive and other user is known
+- pubkey: optional, only if type is send or receive and other user isn't known
+- type: type of transaction
+ - creation: user has get gradidos created
+ - send: user has send another user gradidos
+ - receiver: user has received gradidos from another user
+- transaction_id: id of transaction in db, in stage2 also the hedera sequence number of transaction
+- date: date of ordering transaction (booking date)
+- balance: Gradido Cent, 4 Nachkommastellen (2 Reserve), 1920000 = 192,00 GDD
+- memo: Details about transaction
+- pubkey: optional, if other party isn't known, hexadecimal representation of 32 Byte public key of user [0-9a-f]
+
+- transactionExecutingCount: how many transaction for this user currently pending and waiting for signing
+- count: sum of finished transactions user is involved
+- gdtSum: sum of gdt of user in cent with 2 places (Nachkommastellen)
+- timeUsed: time used for getting data from db in seconds, only for analyse backend performance
+
+## Creation Transaction
+Make a creation transaction
+With new Option set in Login-Server:
+```ini
+unsecure.allow_auto_sign_transactions = 1
+```
+transactions can be auto-signed directly with handing in transaction.
+Normally a forwarding to login-server check transactions side is neccessary to minimize security risks.
+
+POST http://localhost/transaction-creations/ajaxCreate
+```json
+{
+ "session_id" : -127182,
+ "email": "max.musterman@gmail.de",
+ "amount": 10000000,
+ "target_date":"2021-02-19T13:25:36+00:00",
+ "memo":"AGE",
+ "auto_sign": true
+}
+```
+return if everything is ok:
+```json
+{"state":"success", "timeUsed": 0.0122}
+```
+- timeUsed: time used for getting data from db in seconds, only for analyse backend performance
+
+## Send Coins Transaction
+Make a simple GDD Transaction, send Coins from one user to other.
+With new Option set in Login-Server:
+```ini
+unsecure.allow_auto_sign_transactions = 1
+```
+transactions can be auto-signed directly with handing in transaction.
+Normally a forwarding to login-server check transactions side is neccessary to minimize security risks.
+
+POST http://localhost/transaction-send-coins/ajaxCreate
+```json
+{
+ "session_id" : -127182,
+ "amount": 2000000,
+ "email": "max.musterman@gmail.de",
+ "memo":"Thank you :)",
+ "auto_sign": true
+}
+```
+- amout: amount to transfer, 2000000 = 200,00 GDD
+- email: receiver email address, must be differ from user email
+- memo: Details about transaction
+- auto_sign: set to true to directly sign transaction if unsecure.allow_auto_sign_transactions = 1 is set
+
+return if everything is ok:
+```json
+{"state":"success", "timeUsed": 0.0122}
+```
+- timeUsed: time used for getting data from db in seconds, only for analyse backend performance
+
+Than the transaction was created on community server, send to login-server, signed (if unsecure.allow_auto_sign_transactions = 1 and auto_sign = true)
+and send back to community server and put into db.
+After you get this answear you see the new transaction if you list transactions or call for the balance.
+
+Without auto-sign the transaction is pending on login-server and waits for the user to review it at
+http://localhost/account/checkTransactions
+
+
diff --git a/login_server/CMakeLists.txt b/login_server/CMakeLists.txt
index 5a484c3e9..177c6a5c6 100644
--- a/login_server/CMakeLists.txt
+++ b/login_server/CMakeLists.txt
@@ -1,177 +1,177 @@
-cmake_minimum_required(VERSION 3.0)
-project(Gradido_LoginServer C CXX)
-SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "bin" )
-
-SET ( CMAKE_CXX_FLAGS "-std=c++17" )
-
-include_directories(
- "dependencies"
- "dependencies/tinf/src/"
- "dependencies/iroha-ed25519/include"
- "dependencies/mariadb-connector-c/include"
- "dependencies/mariadb-connector-c/build/include"
- "dependencies/spirit-po/include"
- "src/cpp/proto"
- #"dependencies/mariadb-connector-c/build/include"
- #"dependencies/mariadb-connector-c/include"
- #"import/mariadb/include"
-)
-
-
-FILE(GLOB CONTROLLER "src/cpp/controller/*.cpp" "src/cpp/controller/*.h")
-FILE(GLOB TINF "dependencies/tinf/src/*.c" "dependencies/tinf/src/*.h")
-FILE(GLOB HTTPInterface "src/cpp/HTTPInterface/*.h" "src/cpp/HTTPInterface/*.cpp")
-FILE(GLOB JSONInterface "src/cpp/JSONInterface/*.h" "src/cpp/JSONInterface/*.cpp")
-FILE(GLOB TASKS "src/cpp/tasks/*.cpp" "src/cpp/tasks/*.h")
-FILE(GLOB SINGLETON_MANAGER "src/cpp/SingletonManager/*.h" "src/cpp/SingletonManager/*.cpp")
-FILE(GLOB LIB_SRC "src/cpp/lib/*.h" "src/cpp/lib/*.cpp")
-FILE(GLOB MODEL "src/cpp/model/*.h" "src/cpp/model/*.cpp")
-FILE(GLOB MODEL_TABLE "src/cpp/model/table/*.h" "src/cpp/model/table/*.cpp")
-FILE(GLOB MODEL_EMAIL "src/cpp/model/email/*.h" "src/cpp/model/email/*.cpp")
-FILE(GLOB CRYPTO "src/cpp/Crypto/*.h" "src/cpp/Crypto/*.cpp")
-FILE(GLOB MAIN "src/cpp/*.cpp" "src/cpp/*.c" "src/cpp/*.h")
-FILE(GLOB MYSQL "src/cpp/MySQL/*.cpp" "src/cpp/MySQL/*.h" "src/cpp/MySQL/Poco/*.h")
-FILE(GLOB PROTO_GRADIDO "src/cpp/proto/gradido/*.cc" "src/cpp/proto/gradido/*.h")
-FILE(GLOB PROTO_HEDERA "src/cpp/proto/hedera/*.cc" "src/cpp/proto/hedera/*.h")
-
-# used only for test project
-FILE(GLOB TEST "src/cpp/test/*.cpp" "src/cpp/test/*.h")
-FILE(GLOB TEST_CRYPTO "src/cpp/test/crypto/*.cpp" "src/cpp/test/crypto/*.h")
-FILE(GLOB TEST_MODEL "src/cpp/test/model/*.cpp" "src/cpp/test/model/*.h")
-FILE(GLOB TEST_MODEL_TABLE "src/cpp/test/model/table/*.cpp" "src/cpp/test/model/table/*.h")
-FILE(GLOB TEST_CONTROLLER "src/cpp/test/controller/*.cpp" "src/cpp/test/controller/*.h")
-
-SET(LOCAL_SRCS
- ${CONTROLLER} ${TINF} ${MAIN} ${HTTPInterface}
- ${JSONInterface} ${CRYPTO} ${MODEL} ${MODEL_TABLE} ${MODEL_EMAIL}
- ${SINGLETON_MANAGER} ${LIB_SRC} ${MYSQL} ${TASKS}
- ${PROTO_GRADIDO} ${PROTO_HEDERA}
-)
-SET(LOCAL_TEST_SRC
- ${TEST} ${TEST_CRYPTO} ${TEST_MODEL} ${TEST_MODEL_TABLE} ${TEST_CONTROLLER}
-)
-aux_source_directory("src/cpp" LOCAL_SRCS)
-
-if(MSVC)
-# src
-source_group("controller" FILES ${CONTROLLER})
-source_group("proto\\gradido" FILES ${PROTO_GRADIDO})
-source_group("proto\\hedera" FILES ${PROTO_HEDERA})
-source_group("tinf" FILES ${TINF})
-source_group("Crypto" FILES ${CRYPTO})
-source_group("tasks" FILES ${TASKS})
-source_group("model\\table" FILES ${MODEL_TABLE})
-source_group("model\\email" FILES ${MODEL_EMAIL})
-source_group("model" FILES ${MODEL})
-source_group("mysql" FILES ${MYSQL})
-source_group("SingletonManager" FILES ${SINGLETON_MANAGER})
-source_group("lib" FILES ${LIB_SRC})
-source_group("HTTP-Interface" FILES ${HTTPInterface})
-source_group("Json-Interface" FILES ${JSONInterface})
-source_group("Test\\crypto" FILES ${TEST_CRYPTO})
-source_group("Test\\model\\table" FILES ${TEST_MODEL_TABLE})
-source_group("Test\\model" FILES ${TEST_MODEL})
-source_group("Test\\controller" FILES ${TEST_CONTROLLER})
-source_group("Test" FILES ${TEST})
-endif(MSVC)
-
-include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
-conan_basic_setup()
-
-#add_subdirectory("dependencies/curl")
-#add_subdirectory("dependencies/mariadb-connector-c")
-
-
-add_executable(Gradido_LoginServer ${LOCAL_SRCS})
-#SUBDIRS("src/test")
-
-if(WIN32)
-
-find_library(MYSQL_LIBRARIES mariadbclient.lib PATHS "dependencies/mariadb-connector-c/build/libmariadb/Release" REQUIRED)
-#find_library(MYSQL_LIBRARIES_DEBUG mariadbclient.lib PATHS "import/mariadb/lib/debug")
-find_library(COMPILED_MARIADB_CLIENT_DEBUG mariadbclient PATHS "dependencies/mariadb-connector-c/build/libmariadb/Debug" REQUIRED)
-find_library(IROHA_ED25519 ed25519 PATHS "dependencies/iroha-ed25519/build/Debug" REQUIRED)
-set(MYSQL_INCLUDE_DIR "dependencies/mariadb-connector-c/include")
-
-#set(POCO_DEBUG_PATH "I:/FremdCode/C++/poco/win64/lib/Debug")
-
-#find_library(POCO_DEBUG_FOUNDATION PocoFoundationd PocoFoundation PATHS ${POCO_DEBUG_PATH} REQUIRED)
-#find_library(POCO_DEBUG_DATA PocoDatad PocoData PATHS ${POCO_DEBUG_PATH} REQUIRED)
-#find_library(POCO_DEBUG_NET PocoNetd PocoNet PATHS ${POCO_DEBUG_PATH} REQUIRED)
-#find_library(POCO_DEBUG_NET_SSL PocoNetSSLd PocoNetSSL PATHS ${POCO_DEBUG_PATH} REQUIRED)
-#find_library(POCO_DEBUG_UTIL PocoUtild PocoUtil PATHS ${POCO_DEBUG_PATH} REQUIRED)
-#find_library(POCO_DEBUG_CRYPTO PocoCryptod PocoCrypto PATHS ${POCO_DEBUG_PATH} REQUIRED)
-
-#set(POCO_DEBUG_LIBS ${POCO_DEBUG_FOUNDATION} ${POCO_DEBUG_UTIL} ${POCO_DEBUG_DATA} ${POCO_DEBUG_NET} ${POCO_DEBUG_NET_SSL} ${POCO_DEBUG_CRYPTO})
-#include_directories(
-# "I:/FremdCode/C++/poco/Foundation/include"
-# "I:/FremdCode/C++/poco/Data/include"
-# "I:/FremdCode/C++/poco/Net/include"
- #"I:/FremdCode/C++/poco/NetSSL_Win/include"
-# "I:/FremdCode/C++/poco/NetSSL_OpenSSL/include"
-# "I:/FremdCode/C++/poco/Crypto/include"
-# "I:/FremdCode/C++/poco/Util/include"
-# "I:/FremdCode/C++/ssl/include"
-#)
-
-set(CMAKE_CXX_FLAGS "/MP /EHsc")
-#set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3")
-#set(CMAKE_CXX_FLAGS_RELEASE "-O3")
-
-else()
-
-find_library(IROHA_ED25519 ed25519 PATHS "dependencies/iroha-ed25519/build" REQUIRED)
-# set vars for mariadb cmake files
-set(INSTALL_BINDIR "bin")
-set(INSTALL_PLUGINDIR "bin")
-add_subdirectory("dependencies/mariadb-connector-c")
-
-
-include_directories(
- "dependencies/mariadb-connector-c/include"
- "build/dependencies/mariadb-connector-c/include"
-)
-
-
-
-endif()
-
-target_link_libraries(Gradido_LoginServer ${CONAN_LIBS} ${IROHA_ED25519})
-if(WIN32)
-TARGET_LINK_LIBRARIES(Gradido_LoginServer optimized ${MYSQL_LIBRARIES} Shlwapi)
-TARGET_LINK_LIBRARIES(Gradido_LoginServer debug ${COMPILED_MARIADB_CLIENT_DEBUG} Shlwapi)
-else()
-target_link_libraries(Gradido_LoginServer libmariadb -pthread)
-endif()
-
-# install
-if(UNIX)
-install(TARGETS Gradido_LoginServer RUNTIME DESTINATION /usr/local/bin)
-#install(FILES lib/libmariadb /usr/local/lib)
-install(FILES DESTINATION lib COMPONENT libmariadb)
-install(DIRECTORY src/LOCALE DESTINATION /etc/grd_login/
- FILES_MATCHING PATTERN "*.po(t)")
-
-
-endif(UNIX)
-
-enable_testing()
-
-# ---------------------- Test -----------------------------------------
-#project(Gradido_LoginServer_Test C CXX)
-#_TEST_BUILD
-
-
-add_executable(Gradido_LoginServer_Test ${LOCAL_SRCS} ${LOCAL_TEST_SRC})
-target_compile_definitions(Gradido_LoginServer_Test PUBLIC "_TEST_BUILD")
-
-target_link_libraries(Gradido_LoginServer_Test ${CONAN_LIBS} ${IROHA_ED25519})
-
-if(WIN32)
- TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test optimized ${MYSQL_LIBRARIES} Shlwapi)
- TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test debug ${COMPILED_MARIADB_CLIENT_DEBUG} Shlwapi)
-else()
- target_link_libraries(Gradido_LoginServer_Test libmariadb -pthread)
-endif()
-
-add_test(NAME main COMMAND Gradido_LoginServer_Test)
+cmake_minimum_required(VERSION 3.0)
+project(Gradido_LoginServer C CXX)
+SET(CMAKE_RUNTIME_OUTPUT_DIRECTORY "bin" )
+
+SET ( CMAKE_CXX_FLAGS "-std=c++17" )
+
+include_directories(
+ "dependencies"
+ "dependencies/tinf/src/"
+ "dependencies/iroha-ed25519/include"
+ "dependencies/mariadb-connector-c/include"
+ "dependencies/mariadb-connector-c/build/include"
+ "dependencies/spirit-po/include"
+ "src/cpp/proto"
+ #"dependencies/mariadb-connector-c/build/include"
+ #"dependencies/mariadb-connector-c/include"
+ #"import/mariadb/include"
+)
+
+
+FILE(GLOB CONTROLLER "src/cpp/controller/*.cpp" "src/cpp/controller/*.h")
+FILE(GLOB TINF "dependencies/tinf/src/*.c" "dependencies/tinf/src/*.h")
+FILE(GLOB HTTPInterface "src/cpp/HTTPInterface/*.h" "src/cpp/HTTPInterface/*.cpp")
+FILE(GLOB JSONInterface "src/cpp/JSONInterface/*.h" "src/cpp/JSONInterface/*.cpp")
+FILE(GLOB TASKS "src/cpp/tasks/*.cpp" "src/cpp/tasks/*.h")
+FILE(GLOB SINGLETON_MANAGER "src/cpp/SingletonManager/*.h" "src/cpp/SingletonManager/*.cpp")
+FILE(GLOB LIB_SRC "src/cpp/lib/*.h" "src/cpp/lib/*.cpp")
+FILE(GLOB MODEL "src/cpp/model/*.h" "src/cpp/model/*.cpp")
+FILE(GLOB MODEL_TABLE "src/cpp/model/table/*.h" "src/cpp/model/table/*.cpp")
+FILE(GLOB MODEL_EMAIL "src/cpp/model/email/*.h" "src/cpp/model/email/*.cpp")
+FILE(GLOB CRYPTO "src/cpp/Crypto/*.h" "src/cpp/Crypto/*.cpp")
+FILE(GLOB MAIN "src/cpp/*.cpp" "src/cpp/*.c" "src/cpp/*.h")
+FILE(GLOB MYSQL "src/cpp/MySQL/*.cpp" "src/cpp/MySQL/*.h" "src/cpp/MySQL/Poco/*.h")
+FILE(GLOB PROTO_GRADIDO "src/cpp/proto/gradido/*.cc" "src/cpp/proto/gradido/*.h")
+FILE(GLOB PROTO_HEDERA "src/cpp/proto/hedera/*.cc" "src/cpp/proto/hedera/*.h")
+
+# used only for test project
+FILE(GLOB TEST "src/cpp/test/*.cpp" "src/cpp/test/*.h")
+FILE(GLOB TEST_CRYPTO "src/cpp/test/crypto/*.cpp" "src/cpp/test/crypto/*.h")
+FILE(GLOB TEST_MODEL "src/cpp/test/model/*.cpp" "src/cpp/test/model/*.h")
+FILE(GLOB TEST_MODEL_TABLE "src/cpp/test/model/table/*.cpp" "src/cpp/test/model/table/*.h")
+FILE(GLOB TEST_CONTROLLER "src/cpp/test/controller/*.cpp" "src/cpp/test/controller/*.h")
+
+SET(LOCAL_SRCS
+ ${CONTROLLER} ${TINF} ${MAIN} ${HTTPInterface}
+ ${JSONInterface} ${CRYPTO} ${MODEL} ${MODEL_TABLE} ${MODEL_EMAIL}
+ ${SINGLETON_MANAGER} ${LIB_SRC} ${MYSQL} ${TASKS}
+ ${PROTO_GRADIDO} ${PROTO_HEDERA}
+)
+SET(LOCAL_TEST_SRC
+ ${TEST} ${TEST_CRYPTO} ${TEST_MODEL} ${TEST_MODEL_TABLE} ${TEST_CONTROLLER}
+)
+aux_source_directory("src/cpp" LOCAL_SRCS)
+
+if(MSVC)
+# src
+source_group("controller" FILES ${CONTROLLER})
+source_group("proto\\gradido" FILES ${PROTO_GRADIDO})
+source_group("proto\\hedera" FILES ${PROTO_HEDERA})
+source_group("tinf" FILES ${TINF})
+source_group("Crypto" FILES ${CRYPTO})
+source_group("tasks" FILES ${TASKS})
+source_group("model\\table" FILES ${MODEL_TABLE})
+source_group("model\\email" FILES ${MODEL_EMAIL})
+source_group("model" FILES ${MODEL})
+source_group("mysql" FILES ${MYSQL})
+source_group("SingletonManager" FILES ${SINGLETON_MANAGER})
+source_group("lib" FILES ${LIB_SRC})
+source_group("HTTP-Interface" FILES ${HTTPInterface})
+source_group("Json-Interface" FILES ${JSONInterface})
+source_group("Test\\crypto" FILES ${TEST_CRYPTO})
+source_group("Test\\model\\table" FILES ${TEST_MODEL_TABLE})
+source_group("Test\\model" FILES ${TEST_MODEL})
+source_group("Test\\controller" FILES ${TEST_CONTROLLER})
+source_group("Test" FILES ${TEST})
+endif(MSVC)
+
+include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
+conan_basic_setup()
+
+#add_subdirectory("dependencies/curl")
+#add_subdirectory("dependencies/mariadb-connector-c")
+
+
+add_executable(Gradido_LoginServer ${LOCAL_SRCS})
+#SUBDIRS("src/test")
+
+if(WIN32)
+
+find_library(MYSQL_LIBRARIES mariadbclient.lib PATHS "dependencies/mariadb-connector-c/build/libmariadb/Release" REQUIRED)
+#find_library(MYSQL_LIBRARIES_DEBUG mariadbclient.lib PATHS "import/mariadb/lib/debug")
+find_library(COMPILED_MARIADB_CLIENT_DEBUG mariadbclient PATHS "dependencies/mariadb-connector-c/build/libmariadb/Debug" REQUIRED)
+find_library(IROHA_ED25519 ed25519 PATHS "dependencies/iroha-ed25519/build/Debug" REQUIRED)
+set(MYSQL_INCLUDE_DIR "dependencies/mariadb-connector-c/include")
+
+#set(POCO_DEBUG_PATH "I:/FremdCode/C++/poco/win64/lib/Debug")
+
+#find_library(POCO_DEBUG_FOUNDATION PocoFoundationd PocoFoundation PATHS ${POCO_DEBUG_PATH} REQUIRED)
+#find_library(POCO_DEBUG_DATA PocoDatad PocoData PATHS ${POCO_DEBUG_PATH} REQUIRED)
+#find_library(POCO_DEBUG_NET PocoNetd PocoNet PATHS ${POCO_DEBUG_PATH} REQUIRED)
+#find_library(POCO_DEBUG_NET_SSL PocoNetSSLd PocoNetSSL PATHS ${POCO_DEBUG_PATH} REQUIRED)
+#find_library(POCO_DEBUG_UTIL PocoUtild PocoUtil PATHS ${POCO_DEBUG_PATH} REQUIRED)
+#find_library(POCO_DEBUG_CRYPTO PocoCryptod PocoCrypto PATHS ${POCO_DEBUG_PATH} REQUIRED)
+
+#set(POCO_DEBUG_LIBS ${POCO_DEBUG_FOUNDATION} ${POCO_DEBUG_UTIL} ${POCO_DEBUG_DATA} ${POCO_DEBUG_NET} ${POCO_DEBUG_NET_SSL} ${POCO_DEBUG_CRYPTO})
+#include_directories(
+# "I:/FremdCode/C++/poco/Foundation/include"
+# "I:/FremdCode/C++/poco/Data/include"
+# "I:/FremdCode/C++/poco/Net/include"
+ #"I:/FremdCode/C++/poco/NetSSL_Win/include"
+# "I:/FremdCode/C++/poco/NetSSL_OpenSSL/include"
+# "I:/FremdCode/C++/poco/Crypto/include"
+# "I:/FremdCode/C++/poco/Util/include"
+# "I:/FremdCode/C++/ssl/include"
+#)
+
+set(CMAKE_CXX_FLAGS "/MP /EHsc")
+#set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g3")
+#set(CMAKE_CXX_FLAGS_RELEASE "-O3")
+
+else()
+
+find_library(IROHA_ED25519 ed25519 PATHS "dependencies/iroha-ed25519/build" REQUIRED)
+# set vars for mariadb cmake files
+set(INSTALL_BINDIR "bin")
+set(INSTALL_PLUGINDIR "bin")
+add_subdirectory("dependencies/mariadb-connector-c")
+
+
+include_directories(
+ "dependencies/mariadb-connector-c/include"
+ "build/dependencies/mariadb-connector-c/include"
+)
+
+
+
+endif()
+
+target_link_libraries(Gradido_LoginServer ${CONAN_LIBS} ${IROHA_ED25519})
+if(WIN32)
+TARGET_LINK_LIBRARIES(Gradido_LoginServer optimized ${MYSQL_LIBRARIES} Shlwapi)
+TARGET_LINK_LIBRARIES(Gradido_LoginServer debug ${COMPILED_MARIADB_CLIENT_DEBUG} Shlwapi)
+else()
+target_link_libraries(Gradido_LoginServer libmariadb -pthread)
+endif()
+
+# install
+if(UNIX)
+install(TARGETS Gradido_LoginServer RUNTIME DESTINATION /usr/local/bin)
+#install(FILES lib/libmariadb /usr/local/lib)
+install(FILES DESTINATION lib COMPONENT libmariadb)
+install(DIRECTORY src/LOCALE DESTINATION /etc/grd_login/
+ FILES_MATCHING PATTERN "*.po(t)")
+
+
+endif(UNIX)
+
+enable_testing()
+
+# ---------------------- Test -----------------------------------------
+#project(Gradido_LoginServer_Test C CXX)
+#_TEST_BUILD
+
+
+add_executable(Gradido_LoginServer_Test ${LOCAL_SRCS} ${LOCAL_TEST_SRC})
+target_compile_definitions(Gradido_LoginServer_Test PUBLIC "_TEST_BUILD")
+
+target_link_libraries(Gradido_LoginServer_Test ${CONAN_LIBS} ${IROHA_ED25519})
+
+if(WIN32)
+ TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test optimized ${MYSQL_LIBRARIES} Shlwapi)
+ TARGET_LINK_LIBRARIES(Gradido_LoginServer_Test debug ${COMPILED_MARIADB_CLIENT_DEBUG} Shlwapi)
+else()
+ target_link_libraries(Gradido_LoginServer_Test libmariadb -pthread)
+endif()
+
+add_test(NAME main COMMAND Gradido_LoginServer_Test)
diff --git a/login_server/skeema/gradido_login/users.sql b/login_server/skeema/gradido_login/users.sql
index 6c3eb5b82..71c2d09e8 100644
--- a/login_server/skeema/gradido_login/users.sql
+++ b/login_server/skeema/gradido_login/users.sql
@@ -1,16 +1,16 @@
-CREATE TABLE `users` (
- `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
- `email` varchar(191) NOT NULL,
- `first_name` varchar(150) NOT NULL,
- `last_name` varchar(255) DEFAULT '',
- `password` bigint unsigned DEFAULT 0,
- `pubkey` binary(32) DEFAULT NULL,
- `privkey` binary(80) DEFAULT NULL,
- `created` datetime NOT NULL DEFAULT current_timestamp(),
- `email_checked` tinyint NOT NULL DEFAULT 0,
- `passphrase_shown` tinyint NOT NULL DEFAULT 0,
- `language` varchar(4) NOT NULL DEFAULT 'de',
- `disabled` BOOLEAN NULL DEFAULT FALSE,
- PRIMARY KEY (`id`),
- UNIQUE KEY `email` (`email`)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+CREATE TABLE `users` (
+ `id` int UNSIGNED NOT NULL AUTO_INCREMENT,
+ `email` varchar(191) NOT NULL,
+ `first_name` varchar(150) NOT NULL,
+ `last_name` varchar(255) DEFAULT '',
+ `password` bigint unsigned DEFAULT 0,
+ `pubkey` binary(32) DEFAULT NULL,
+ `privkey` binary(80) DEFAULT NULL,
+ `created` datetime NOT NULL DEFAULT current_timestamp(),
+ `email_checked` tinyint NOT NULL DEFAULT 0,
+ `passphrase_shown` tinyint NOT NULL DEFAULT 0,
+ `language` varchar(4) NOT NULL DEFAULT 'de',
+ `disabled` BOOLEAN NULL DEFAULT FALSE,
+ PRIMARY KEY (`id`),
+ UNIQUE KEY `email` (`email`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
diff --git a/login_server/src/cpp/Gradido_LoginServer.cpp b/login_server/src/cpp/Gradido_LoginServer.cpp
index 2ad89e19b..423f603df 100644
--- a/login_server/src/cpp/Gradido_LoginServer.cpp
+++ b/login_server/src/cpp/Gradido_LoginServer.cpp
@@ -1,267 +1,267 @@
-#include "Gradido_LoginServer.h"
-#include "ServerConfig.h"
-#include "HTTPInterface/PageRequestHandlerFactory.h"
-#include "JSONInterface/JsonRequestHandlerFactory.h"
-
-#include "lib/Profiler.h"
-
-#include "SingletonManager/ConnectionManager.h"
-#include "SingletonManager/SessionManager.h"
-#include "SingletonManager/EmailManager.h"
-
-#include "controller/User.h"
-
-#include "Poco/Util/HelpFormatter.h"
-#include "Poco/Net/ServerSocket.h"
-#include "Poco/Net/HTTPServer.h"
-#include "Poco/Net/SSLManager.h"
-#include "Poco/Environment.h"
-#include "Poco/Logger.h"
-#include "Poco/Path.h"
-#include "Poco/AsyncChannel.h"
-#include "Poco/SimpleFileChannel.h"
-#include "Poco/ConsoleChannel.h"
-#include "Poco/SplitterChannel.h"
-#include "MySQL/Poco/Connector.h"
-
-
-#include
-
-
-
-Gradido_LoginServer::Gradido_LoginServer()
- : _helpRequested(false)
-{
-}
-
-Gradido_LoginServer::~Gradido_LoginServer()
-{
-}
-
-
-void Gradido_LoginServer::initialize(Application& self)
-{
- loadConfiguration(); // load default configuration files, if present
- ServerApplication::initialize(self);
-}
-
-void Gradido_LoginServer::uninitialize()
-{
- ServerApplication::uninitialize();
-}
-
-void Gradido_LoginServer::defineOptions(Poco::Util::OptionSet& options)
-{
- ServerApplication::defineOptions(options);
-
- /*options.addOption(
- Poco::Util::Option("help", "h", "display help information on command line arguments")
- .required(false)
- .repeatable(false));*/
- options.addOption(
- Poco::Util::Option("config", "c", "use non default config file (default is /etc/grd_login.properties)", false)
- .repeatable(false)
- .argument("Gradido_LoginServer.properties", true)
- .callback(Poco::Util::OptionCallback(this, &Gradido_LoginServer::handleOption)));
-
-}
-
-void Gradido_LoginServer::handleOption(const std::string& name, const std::string& value)
-{
- //printf("handle option: %s with value: %s\n", name.data(), value.data());
- if (name == "config") {
- mConfigPath = value;
- return;
- }
- ServerApplication::handleOption(name, value);
- if (name == "help") _helpRequested = true;
-
-}
-
-void Gradido_LoginServer::displayHelp()
-{
- Poco::Util::HelpFormatter helpFormatter(options());
- helpFormatter.setCommand(commandName());
- helpFormatter.setUsage("OPTIONS");
- helpFormatter.setHeader("Gradido Login Server");
- helpFormatter.format(std::cout);
-}
-
-void Gradido_LoginServer::createConsoleFileAsyncLogger(std::string name, std::string filePath)
-{
- Poco::AutoPtr logConsoleChannel(new Poco::ConsoleChannel);
- Poco::AutoPtr logFileChannel(new Poco::SimpleFileChannel(filePath));
- logFileChannel->setProperty("rotation", "500 K");
- Poco::AutoPtr logSplitter(new Poco::SplitterChannel);
- logSplitter->addChannel(logConsoleChannel);
- logSplitter->addChannel(logFileChannel);
-
- Poco::AutoPtr logAsyncChannel(new Poco::AsyncChannel(logSplitter));
-
- Poco::Logger& log = Poco::Logger::get(name);
- log.setChannel(logAsyncChannel);
- log.setLevel("information");
-}
-
-int Gradido_LoginServer::main(const std::vector& args)
-{
-
- Profiler usedTime;
- if (_helpRequested)
- {
- displayHelp();
- }
- else
- {
- // ********** logging ************************************
- std::string log_Path = "/var/log/grd_login/";
-//#ifdef _WIN32
-#if defined(_WIN32) || defined(_WIN64)
- log_Path = "./";
-#endif
-
- // init speed logger
- Poco::AutoPtr speedLogFileChannel(new Poco::SimpleFileChannel(log_Path + "speedLog.txt"));
- /*
- The optional log file rotation mode:
- never: no rotation (default)
- : rotate if file size exceeds bytes
- K: rotate if file size exceeds Kilobytes
- M: rotate if file size exceeds Megabytes
- */
- speedLogFileChannel->setProperty("rotation", "500 K");
- Poco::AutoPtr speedLogAsyncChannel(new Poco::AsyncChannel(speedLogFileChannel));
-
- Poco::Logger& speedLogger = Poco::Logger::get("SpeedLog");
- speedLogger.setChannel(speedLogAsyncChannel);
- speedLogger.setLevel("information");
-
- // logging for request handling
- createConsoleFileAsyncLogger("requestLog", log_Path + "requestLog.txt");
-
- // error logging
- createConsoleFileAsyncLogger("errorLog", log_Path + "errorLog.txt");
- Poco::Logger& errorLog = Poco::Logger::get("errorLog");
-
- createConsoleFileAsyncLogger("emailLog", log_Path + "emailLog.txt");
-
- // *************** load from config ********************************************
-
- std::string cfg_Path = Poco::Path::config() + "grd_login/grd_login.properties";
- if (mConfigPath != "") {
- cfg_Path = mConfigPath;
- }
-
- try {
- loadConfiguration(cfg_Path);
- }
- catch (Poco::Exception& ex) {
- errorLog.error("error loading config: %s from path: %s", ex.displayText(), cfg_Path);
- }
-
- unsigned short port = (unsigned short)config().getInt("HTTPServer.port", 9980);
- unsigned short json_port = (unsigned short)config().getInt("JSONServer.port", 1201);
-
-
- //printf("show mnemonic list: \n");
- //printf(ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER].getCompleteWordList().data());
- if (!ServerConfig::initServerCrypto(config())) {
- //printf("[Gradido_LoginServer::%s] error init server crypto\n", __FUNCTION__);
- errorLog.error("[Gradido_LoginServer::main] error init server crypto");
- return Application::EXIT_CONFIG;
- }
-
- // first check time for crypto
- auto testUser = new User("email@google.de", "Max", "Mustermann");
- Profiler timeUsed;
- testUser->validatePwd("haz27Newpassword", nullptr);
- ServerConfig::g_FakeLoginSleepTime = (int)std::round(timeUsed.millis());
- delete testUser;
-
- Poco::Int64 i1 = randombytes_random();
- Poco::Int64 i2 = randombytes_random();
- ServerConfig::g_ServerKeySeed->put(1, i1 | (i2 << 8));
-
- ServerConfig::initEMailAccount(config());
- EmailManager::getInstance()->init(config());
-
- // start cpu scheduler
- uint8_t worker_count = Poco::Environment::processorCount() * 2;
-
- ServerConfig::g_CPUScheduler = new UniLib::controller::CPUSheduler(worker_count, "Default Worker");
- ServerConfig::g_CryptoCPUScheduler = new UniLib::controller::CPUSheduler(2, "Crypto Worker");
-
- // load up connection configs
- // register MySQL connector
- Poco::Data::MySQL::Connector::registerConnector();
- //Poco::Data::MySQL::Connector::KEY;
- auto conn = ConnectionManager::getInstance();
- //conn->setConnection()
- //printf("try connect login server mysql db\n");
- try {
- conn->setConnectionsFromConfig(config(), CONNECTION_MYSQL_LOGIN_SERVER);
- }
- catch (Poco::Exception& ex) {
- // maybe we in docker environment and db needs some time to start up
- // let's wait 10 seconds
- int count = 10;
- while (count > 0) {
- printf("\rwait on mysql/mariadb %d seconds...", count);
- count--;
- Poco::Thread::sleep(1000);
- }
- conn->setConnectionsFromConfig(config(), CONNECTION_MYSQL_LOGIN_SERVER);
- }
- //printf("try connect php server mysql \n");
- //conn->setConnectionsFromConfig(config(), CONNECTION_MYSQL_PHP_SERVER);
-
- SessionManager::getInstance()->init();
- // put urandom on linux servers
- //srand();
-
- Poco::Net::initializeSSL();
- if(!ServerConfig::initSSLClientContext()) {
- //printf("[Gradido_LoginServer::%s] error init server SSL Client\n", __FUNCTION__);
- errorLog.error("[Gradido_LoginServer::main] error init server SSL Client\n");
- return Application::EXIT_CONFIG;
- }
-
- // schedule email verification resend
- controller::User::checkIfVerificationEmailsShouldBeResend(ServerConfig::g_CronJobsTimer);
-
- // HTTP Interface Server
- // set-up a server socket
- Poco::Net::ServerSocket svs(port);
- // set-up a HTTPServer instance
- Poco::ThreadPool& pool = Poco::ThreadPool::defaultPool();
- Poco::Net::HTTPServer srv(new PageRequestHandlerFactory, svs, new Poco::Net::HTTPServerParams);
- ServerConfig::g_ServerKeySeed->put(7, 918276611);
-
- // start the HTTPServer
- srv.start();
-
- // JSON Interface Server
- Poco::Net::ServerSocket json_svs(json_port);
- Poco::Net::HTTPServer json_srv(new JsonRequestHandlerFactory, json_svs, new Poco::Net::HTTPServerParams);
-
- // start the json server
- json_srv.start();
-
- printf("[Gradido_LoginServer::main] started in %s\n", usedTime.string().data());
- // wait for CTRL-C or kill
- waitForTerminationRequest();
-
- // Stop the HTTPServer
- srv.stop();
- // Stop the json server
- json_srv.stop();
-
- ServerConfig::unload();
- Poco::Net::uninitializeSSL();
- // Optional: Delete all global objects allocated by libprotobuf.
- google::protobuf::ShutdownProtobufLibrary();
-
- }
- return Application::EXIT_OK;
-}
-
+#include "Gradido_LoginServer.h"
+#include "ServerConfig.h"
+#include "HTTPInterface/PageRequestHandlerFactory.h"
+#include "JSONInterface/JsonRequestHandlerFactory.h"
+
+#include "lib/Profiler.h"
+
+#include "SingletonManager/ConnectionManager.h"
+#include "SingletonManager/SessionManager.h"
+#include "SingletonManager/EmailManager.h"
+
+#include "controller/User.h"
+
+#include "Poco/Util/HelpFormatter.h"
+#include "Poco/Net/ServerSocket.h"
+#include "Poco/Net/HTTPServer.h"
+#include "Poco/Net/SSLManager.h"
+#include "Poco/Environment.h"
+#include "Poco/Logger.h"
+#include "Poco/Path.h"
+#include "Poco/AsyncChannel.h"
+#include "Poco/SimpleFileChannel.h"
+#include "Poco/ConsoleChannel.h"
+#include "Poco/SplitterChannel.h"
+#include "MySQL/Poco/Connector.h"
+
+
+#include
+
+
+
+Gradido_LoginServer::Gradido_LoginServer()
+ : _helpRequested(false)
+{
+}
+
+Gradido_LoginServer::~Gradido_LoginServer()
+{
+}
+
+
+void Gradido_LoginServer::initialize(Application& self)
+{
+ loadConfiguration(); // load default configuration files, if present
+ ServerApplication::initialize(self);
+}
+
+void Gradido_LoginServer::uninitialize()
+{
+ ServerApplication::uninitialize();
+}
+
+void Gradido_LoginServer::defineOptions(Poco::Util::OptionSet& options)
+{
+ ServerApplication::defineOptions(options);
+
+ /*options.addOption(
+ Poco::Util::Option("help", "h", "display help information on command line arguments")
+ .required(false)
+ .repeatable(false));*/
+ options.addOption(
+ Poco::Util::Option("config", "c", "use non default config file (default is /etc/grd_login.properties)", false)
+ .repeatable(false)
+ .argument("Gradido_LoginServer.properties", true)
+ .callback(Poco::Util::OptionCallback(this, &Gradido_LoginServer::handleOption)));
+
+}
+
+void Gradido_LoginServer::handleOption(const std::string& name, const std::string& value)
+{
+ //printf("handle option: %s with value: %s\n", name.data(), value.data());
+ if (name == "config") {
+ mConfigPath = value;
+ return;
+ }
+ ServerApplication::handleOption(name, value);
+ if (name == "help") _helpRequested = true;
+
+}
+
+void Gradido_LoginServer::displayHelp()
+{
+ Poco::Util::HelpFormatter helpFormatter(options());
+ helpFormatter.setCommand(commandName());
+ helpFormatter.setUsage("OPTIONS");
+ helpFormatter.setHeader("Gradido Login Server");
+ helpFormatter.format(std::cout);
+}
+
+void Gradido_LoginServer::createConsoleFileAsyncLogger(std::string name, std::string filePath)
+{
+ Poco::AutoPtr logConsoleChannel(new Poco::ConsoleChannel);
+ Poco::AutoPtr logFileChannel(new Poco::SimpleFileChannel(filePath));
+ logFileChannel->setProperty("rotation", "500 K");
+ Poco::AutoPtr logSplitter(new Poco::SplitterChannel);
+ logSplitter->addChannel(logConsoleChannel);
+ logSplitter->addChannel(logFileChannel);
+
+ Poco::AutoPtr logAsyncChannel(new Poco::AsyncChannel(logSplitter));
+
+ Poco::Logger& log = Poco::Logger::get(name);
+ log.setChannel(logAsyncChannel);
+ log.setLevel("information");
+}
+
+int Gradido_LoginServer::main(const std::vector& args)
+{
+
+ Profiler usedTime;
+ if (_helpRequested)
+ {
+ displayHelp();
+ }
+ else
+ {
+ // ********** logging ************************************
+ std::string log_Path = "/var/log/grd_login/";
+//#ifdef _WIN32
+#if defined(_WIN32) || defined(_WIN64)
+ log_Path = "./";
+#endif
+
+ // init speed logger
+ Poco::AutoPtr speedLogFileChannel(new Poco::SimpleFileChannel(log_Path + "speedLog.txt"));
+ /*
+ The optional log file rotation mode:
+ never: no rotation (default)
+ : rotate if file size exceeds bytes
+ K: rotate if file size exceeds Kilobytes
+ M: rotate if file size exceeds Megabytes
+ */
+ speedLogFileChannel->setProperty("rotation", "500 K");
+ Poco::AutoPtr speedLogAsyncChannel(new Poco::AsyncChannel(speedLogFileChannel));
+
+ Poco::Logger& speedLogger = Poco::Logger::get("SpeedLog");
+ speedLogger.setChannel(speedLogAsyncChannel);
+ speedLogger.setLevel("information");
+
+ // logging for request handling
+ createConsoleFileAsyncLogger("requestLog", log_Path + "requestLog.txt");
+
+ // error logging
+ createConsoleFileAsyncLogger("errorLog", log_Path + "errorLog.txt");
+ Poco::Logger& errorLog = Poco::Logger::get("errorLog");
+
+ createConsoleFileAsyncLogger("emailLog", log_Path + "emailLog.txt");
+
+ // *************** load from config ********************************************
+
+ std::string cfg_Path = Poco::Path::config() + "grd_login/grd_login.properties";
+ if (mConfigPath != "") {
+ cfg_Path = mConfigPath;
+ }
+
+ try {
+ loadConfiguration(cfg_Path);
+ }
+ catch (Poco::Exception& ex) {
+ errorLog.error("error loading config: %s from path: %s", ex.displayText(), cfg_Path);
+ }
+
+ unsigned short port = (unsigned short)config().getInt("HTTPServer.port", 9980);
+ unsigned short json_port = (unsigned short)config().getInt("JSONServer.port", 1201);
+
+
+ //printf("show mnemonic list: \n");
+ //printf(ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER].getCompleteWordList().data());
+ if (!ServerConfig::initServerCrypto(config())) {
+ //printf("[Gradido_LoginServer::%s] error init server crypto\n", __FUNCTION__);
+ errorLog.error("[Gradido_LoginServer::main] error init server crypto");
+ return Application::EXIT_CONFIG;
+ }
+
+ // first check time for crypto
+ auto testUser = new User("email@google.de", "Max", "Mustermann");
+ Profiler timeUsed;
+ testUser->validatePwd("haz27Newpassword", nullptr);
+ ServerConfig::g_FakeLoginSleepTime = (int)std::round(timeUsed.millis());
+ delete testUser;
+
+ Poco::Int64 i1 = randombytes_random();
+ Poco::Int64 i2 = randombytes_random();
+ ServerConfig::g_ServerKeySeed->put(1, i1 | (i2 << 8));
+
+ ServerConfig::initEMailAccount(config());
+ EmailManager::getInstance()->init(config());
+
+ // start cpu scheduler
+ uint8_t worker_count = Poco::Environment::processorCount() * 2;
+
+ ServerConfig::g_CPUScheduler = new UniLib::controller::CPUSheduler(worker_count, "Default Worker");
+ ServerConfig::g_CryptoCPUScheduler = new UniLib::controller::CPUSheduler(2, "Crypto Worker");
+
+ // load up connection configs
+ // register MySQL connector
+ Poco::Data::MySQL::Connector::registerConnector();
+ //Poco::Data::MySQL::Connector::KEY;
+ auto conn = ConnectionManager::getInstance();
+ //conn->setConnection()
+ //printf("try connect login server mysql db\n");
+ try {
+ conn->setConnectionsFromConfig(config(), CONNECTION_MYSQL_LOGIN_SERVER);
+ }
+ catch (Poco::Exception& ex) {
+ // maybe we in docker environment and db needs some time to start up
+ // let's wait 10 seconds
+ int count = 10;
+ while (count > 0) {
+ printf("\rwait on mysql/mariadb %d seconds...", count);
+ count--;
+ Poco::Thread::sleep(1000);
+ }
+ conn->setConnectionsFromConfig(config(), CONNECTION_MYSQL_LOGIN_SERVER);
+ }
+ //printf("try connect php server mysql \n");
+ //conn->setConnectionsFromConfig(config(), CONNECTION_MYSQL_PHP_SERVER);
+
+ SessionManager::getInstance()->init();
+ // put urandom on linux servers
+ //srand();
+
+ Poco::Net::initializeSSL();
+ if(!ServerConfig::initSSLClientContext()) {
+ //printf("[Gradido_LoginServer::%s] error init server SSL Client\n", __FUNCTION__);
+ errorLog.error("[Gradido_LoginServer::main] error init server SSL Client\n");
+ return Application::EXIT_CONFIG;
+ }
+
+ // schedule email verification resend
+ controller::User::checkIfVerificationEmailsShouldBeResend(ServerConfig::g_CronJobsTimer);
+
+ // HTTP Interface Server
+ // set-up a server socket
+ Poco::Net::ServerSocket svs(port);
+ // set-up a HTTPServer instance
+ Poco::ThreadPool& pool = Poco::ThreadPool::defaultPool();
+ Poco::Net::HTTPServer srv(new PageRequestHandlerFactory, svs, new Poco::Net::HTTPServerParams);
+ ServerConfig::g_ServerKeySeed->put(7, 918276611);
+
+ // start the HTTPServer
+ srv.start();
+
+ // JSON Interface Server
+ Poco::Net::ServerSocket json_svs(json_port);
+ Poco::Net::HTTPServer json_srv(new JsonRequestHandlerFactory, json_svs, new Poco::Net::HTTPServerParams);
+
+ // start the json server
+ json_srv.start();
+
+ printf("[Gradido_LoginServer::main] started in %s\n", usedTime.string().data());
+ // wait for CTRL-C or kill
+ waitForTerminationRequest();
+
+ // Stop the HTTPServer
+ srv.stop();
+ // Stop the json server
+ json_srv.stop();
+
+ ServerConfig::unload();
+ Poco::Net::uninitializeSSL();
+ // Optional: Delete all global objects allocated by libprotobuf.
+ google::protobuf::ShutdownProtobufLibrary();
+
+ }
+ return Application::EXIT_OK;
+}
+
diff --git a/login_server/src/cpp/Gradido_LoginServer.h b/login_server/src/cpp/Gradido_LoginServer.h
index 98c3c882c..0a14d9000 100644
--- a/login_server/src/cpp/Gradido_LoginServer.h
+++ b/login_server/src/cpp/Gradido_LoginServer.h
@@ -1,42 +1,42 @@
-#ifndef Gradido_LoginServer_INCLUDED
-#define Gradido_LoginServer_INCLUDED
-
-#include "Poco/Util/ServerApplication.h"
-
-class Gradido_LoginServer : public Poco::Util::ServerApplication
-{
-
- /// The main application class.
- ///
- /// This class handles command-line arguments and
- /// configuration files.
- /// Start the Gradido_LoginServer executable with the help
- /// option (/help on Windows, --help on Unix) for
- /// the available command line options.
- ///
-
-
-public:
- Gradido_LoginServer();
- ~Gradido_LoginServer();
-
-protected:
- void initialize(Application& self);
-
- void uninitialize();
-
- void defineOptions(Poco::Util::OptionSet& options);
-
- void handleOption(const std::string& name, const std::string& value);
- void displayHelp();
-
- int main(const std::vector& args);
-
- void createConsoleFileAsyncLogger(std::string name, std::string filePath);
-
-private:
- bool _helpRequested;
- std::string mConfigPath;
-};
-
-#endif //Gradido_LoginServer_INCLUDED
+#ifndef Gradido_LoginServer_INCLUDED
+#define Gradido_LoginServer_INCLUDED
+
+#include "Poco/Util/ServerApplication.h"
+
+class Gradido_LoginServer : public Poco::Util::ServerApplication
+{
+
+ /// The main application class.
+ ///
+ /// This class handles command-line arguments and
+ /// configuration files.
+ /// Start the Gradido_LoginServer executable with the help
+ /// option (/help on Windows, --help on Unix) for
+ /// the available command line options.
+ ///
+
+
+public:
+ Gradido_LoginServer();
+ ~Gradido_LoginServer();
+
+protected:
+ void initialize(Application& self);
+
+ void uninitialize();
+
+ void defineOptions(Poco::Util::OptionSet& options);
+
+ void handleOption(const std::string& name, const std::string& value);
+ void displayHelp();
+
+ int main(const std::vector& args);
+
+ void createConsoleFileAsyncLogger(std::string name, std::string filePath);
+
+private:
+ bool _helpRequested;
+ std::string mConfigPath;
+};
+
+#endif //Gradido_LoginServer_INCLUDED
diff --git a/login_server/src/cpp/HTTPInterface/LoginPage.cpp b/login_server/src/cpp/HTTPInterface/LoginPage.cpp
index f3e6aa077..65d144c9e 100644
--- a/login_server/src/cpp/HTTPInterface/LoginPage.cpp
+++ b/login_server/src/cpp/HTTPInterface/LoginPage.cpp
@@ -1,365 +1,365 @@
-#include "LoginPage.h"
-#include "Poco/Net/HTTPServerRequest.h"
-#include "Poco/Net/HTTPServerResponse.h"
-#include "Poco/Net/HTMLForm.h"
-#include "Poco/DeflatingStream.h"
-
-
-#line 7 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\login.cpsp"
-
-#include "../gettext.h"
-
-#include "Poco/Net/HTTPCookie.h"
-#include "Poco/Net/HTTPServerParams.h"
-#include "Poco/Logger.h"
-#include "../SingletonManager/SessionManager.h"
-#include "../SingletonManager/LanguageManager.h"
-#include "../SingletonManager/ErrorManager.h"
-
-#line 1 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\header.cpsp"
-
-#include "../ServerConfig.h"
-
-
-LoginPage::LoginPage(Session* arg):
- SessionHTTPRequestHandler(arg)
-{
-}
-
-
-void LoginPage::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response)
-{
- response.setChunkedTransferEncoding(true);
- response.setContentType("text/html");
- bool _compressResponse(request.hasToken("Accept-Encoding", "gzip"));
- if (_compressResponse) response.set("Content-Encoding", "gzip");
-
- Poco::Net::HTMLForm form(request, request.stream());
-#line 18 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\login.cpsp"
-
- const char* pageName = "Login";
- auto sm = SessionManager::getInstance();
- auto lm = LanguageManager::getInstance();
- auto em = ErrorManager::getInstance();
-
- auto lang = chooseLanguage(request);
- //printf("choose language return: %d\n", lang);
- auto langCatalog = lm->getFreeCatalog(lang);
-
- std::string presetEmail("");
- if(mSession && mSession->getUser()) {
- presetEmail = mSession->getUser()->getEmail();
- }
-
- if(!form.empty()) {
-
- bool langUpdatedByBtn = false;
- auto langBtn = form.get("lang", "");
- if(langBtn != "") {
- langUpdatedByBtn = true;
- }
- /*
- auto langInput = form.get("lang", "");
- auto updatedLang = LANG_NULL;
- if(langBtn != "") {
- updatedLang = chooseLanguage(request, langBtn);
- langUpdatedByBtn = true;
- } else if(langInput != "") {
- updatedLang = chooseLanguage(request, langInput);
- }
-
- if(updatedLang != LANG_NULL && updatedLang != lang) {
- lang = updatedLang;
- langCatalog = lm->getFreeCatalog(lang);
- }
- */
- auto email = form.get("login-email", "");
- auto password = form.get("login-password", "");
-
- if(email != "" && password != "") {
- //auto session = sm->getSession(request);
- //if(!mSession) mSession = sm->findByEmail(email);
- if(!mSession) {
- mSession = sm->getNewSession();
- mSession->setLanguageCatalog(langCatalog);
- // get language
- // first check url, second check language header
- // for debugging client ip
- auto client_host = request.clientAddress().host();
- //auto client_ip = request.clientAddress();
- // X-Real-IP forwarded ip from nginx config
- auto client_host_string = request.get("X-Real-IP", client_host.toString());
- std::string clientIpString = "client ip: ";
- client_host = Poco::Net::IPAddress(client_host_string);
- clientIpString += client_host_string;
- Poco::Logger::get("requestLog").information(clientIpString);
- // debugging end
- mSession->setClientIp(client_host);
- response.addCookie(mSession->getLoginCookie());
- } else {
- langCatalog = mSession->getLanguageCatalog();
- }
- UserStates user_state;
- try {
- user_state = mSession->loadUser(email, password);
- } catch (Poco::Exception& ex) {
- addError(new ParamError("login", "exception by calling loadUser: ", ex.displayText()));
- sendErrorsAsEmail();
- addError(new Error("Error", "Intern Server error, please try again later"));
- }
- auto user = mSession->getNewUser();
-
- if(user_state >= USER_LOADED_FROM_DB && !user.isNull() && !user->getModel()->getPublicKey()) {
- if(mSession->generateKeys(true, true)) {
- user_state = USER_COMPLETE;
- if(user->getModel()->isDisabled()) {
- user_state = USER_DISABLED;
- }
- }
- } else {
- //printf("pubkey exist: %p\n",user->getModel()->getPublicKey());
- }
- getErrors(mSession);
-
- auto uri_start = request.serverParams().getServerName();
- auto lastExternReferer = mSession->getLastReferer();
-
- printf("user_state: %d\n", user_state);
-
- switch(user_state) {
- case USER_EMPTY:
- case USER_PASSWORD_INCORRECT:
- addError(new Error(langCatalog->gettext("Login"), langCatalog->gettext("E-Mail or password isn't right, please try again!")), false);
- if(mSession) {
- getErrors(mSession);
- sm->releaseSession(mSession);
- }
- sm->deleteLoginCookies(request, response);
- break;
- case USER_PASSWORD_ENCRYPTION_IN_PROCESS:
- addError(new Error(langCatalog->gettext("Passwort"), langCatalog->gettext("Passwort wird noch berechnet, bitte versuche es in etwa 1 Minute erneut.")), false);
- break;
- case USER_KEYS_DONT_MATCH:
- addError(new Error(langCatalog->gettext("User"), langCatalog->gettext("Error in saved data, the server admin will look at it.")));
- break;
- case USER_DISABLED:
- addError(new Error(langCatalog->gettext("User"), langCatalog->gettext("Benutzer ist deaktiviert, kein Login möglich!")));
- if(mSession) {
- getErrors(mSession);
- sm->releaseSession(mSession);
- }
- sm->deleteLoginCookies(request, response);
- break;
- case USER_NO_PRIVATE_KEY:
- case USER_COMPLETE:
- case USER_EMAIL_NOT_ACTIVATED:
- auto referer = request.find("Referer");
- std::string refererString;
- if (referer != request.end()) {
- refererString = referer->second;
- }
- if(lastExternReferer != "") {
- //printf("redirect to: %s\n", lastExternReferer.data());
- response.redirect(lastExternReferer);
- } else if(refererString != "" &&
- refererString.find("login") == std::string::npos &&
- refererString.find("logout") == std::string::npos &&
- refererString.find("user_delete") == std::string::npos &&
- refererString != ServerConfig::g_serverPath + request.getURI()) {
- std::string uri = request.getURI();
- printf("request uri: %s, redirect to: %s\n", uri.data(), refererString.data());
- response.redirect(refererString);
- } else {
- //printf("redirect to: %s\n", ServerConfig::g_php_serverPath.data());
- response.redirect(ServerConfig::g_php_serverPath + "/");
- }
- return;
- }
-
- } else if(!langUpdatedByBtn) {
- addError(new Error(langCatalog->gettext("Login"), langCatalog->gettext("Username and password are needed!")), false);
- }
-
- } else {
-
- // on enter login page with empty form
- //auto session = sm->getSession(request);
- // remove old cookies and session if exist
- if(mSession) {
- getErrors(mSession);
- sm->releaseSession(mSession);
- }
- sm->deleteLoginCookies(request, response);
- }
-
-#line 3 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\header.cpsp"
-
- bool withMaterialIcons = false;
- std::ostream& _responseStream = response.send();
- Poco::DeflatingOutputStream _gzipStream(_responseStream, Poco::DeflatingStreamBuf::STREAM_GZIP, 1);
- std::ostream& responseStream = _compressResponse ? _gzipStream : _responseStream;
- responseStream << "\n";
- // begin include header.cpsp
- responseStream << "\n";
- responseStream << "\n";
- responseStream << "\n";
- responseStream << "\n";
- responseStream << "\n";
- responseStream << "\n";
- responseStream << "Gradido Login Server: ";
-#line 11 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\header.cpsp"
- responseStream << ( pageName );
- responseStream << "\n";
- responseStream << "\n";
-#line 13 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\header.cpsp"
- if(withMaterialIcons) { responseStream << "\n";
- responseStream << "\n";
-#line 15 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\header.cpsp"
- } responseStream << "\n";
- responseStream << "\n";
- responseStream << "\n";
- responseStream << " \n";
- responseStream << "
\n";
- responseStream << "
\n";
- responseStream << " ";
-#line 6 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\footer.cpsp"
- responseStream << ( mTimeProfiler.string() );
- responseStream << "\n";
- responseStream << "
\n";
- responseStream << "
\n";
- responseStream << "
Login Server in Entwicklung
\n";
- responseStream << "
Alpha ";
-#line 10 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\footer.cpsp"
- responseStream << ( ServerConfig::g_versionString );
- responseStream << "
\n";
- responseStream << "
\n";
- responseStream << "
\n";
- responseStream << "\n";
- responseStream << "\n";
- responseStream << "";
- // end include footer.cpsp
- if (_compressResponse) _gzipStream.close();
-}
+#include "LoginPage.h"
+#include "Poco/Net/HTTPServerRequest.h"
+#include "Poco/Net/HTTPServerResponse.h"
+#include "Poco/Net/HTMLForm.h"
+#include "Poco/DeflatingStream.h"
+
+
+#line 7 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\login.cpsp"
+
+#include "../gettext.h"
+
+#include "Poco/Net/HTTPCookie.h"
+#include "Poco/Net/HTTPServerParams.h"
+#include "Poco/Logger.h"
+#include "../SingletonManager/SessionManager.h"
+#include "../SingletonManager/LanguageManager.h"
+#include "../SingletonManager/ErrorManager.h"
+
+#line 1 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\header.cpsp"
+
+#include "../ServerConfig.h"
+
+
+LoginPage::LoginPage(Session* arg):
+ SessionHTTPRequestHandler(arg)
+{
+}
+
+
+void LoginPage::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response)
+{
+ response.setChunkedTransferEncoding(true);
+ response.setContentType("text/html");
+ bool _compressResponse(request.hasToken("Accept-Encoding", "gzip"));
+ if (_compressResponse) response.set("Content-Encoding", "gzip");
+
+ Poco::Net::HTMLForm form(request, request.stream());
+#line 18 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\login.cpsp"
+
+ const char* pageName = "Login";
+ auto sm = SessionManager::getInstance();
+ auto lm = LanguageManager::getInstance();
+ auto em = ErrorManager::getInstance();
+
+ auto lang = chooseLanguage(request);
+ //printf("choose language return: %d\n", lang);
+ auto langCatalog = lm->getFreeCatalog(lang);
+
+ std::string presetEmail("");
+ if(mSession && mSession->getUser()) {
+ presetEmail = mSession->getUser()->getEmail();
+ }
+
+ if(!form.empty()) {
+
+ bool langUpdatedByBtn = false;
+ auto langBtn = form.get("lang", "");
+ if(langBtn != "") {
+ langUpdatedByBtn = true;
+ }
+ /*
+ auto langInput = form.get("lang", "");
+ auto updatedLang = LANG_NULL;
+ if(langBtn != "") {
+ updatedLang = chooseLanguage(request, langBtn);
+ langUpdatedByBtn = true;
+ } else if(langInput != "") {
+ updatedLang = chooseLanguage(request, langInput);
+ }
+
+ if(updatedLang != LANG_NULL && updatedLang != lang) {
+ lang = updatedLang;
+ langCatalog = lm->getFreeCatalog(lang);
+ }
+ */
+ auto email = form.get("login-email", "");
+ auto password = form.get("login-password", "");
+
+ if(email != "" && password != "") {
+ //auto session = sm->getSession(request);
+ //if(!mSession) mSession = sm->findByEmail(email);
+ if(!mSession) {
+ mSession = sm->getNewSession();
+ mSession->setLanguageCatalog(langCatalog);
+ // get language
+ // first check url, second check language header
+ // for debugging client ip
+ auto client_host = request.clientAddress().host();
+ //auto client_ip = request.clientAddress();
+ // X-Real-IP forwarded ip from nginx config
+ auto client_host_string = request.get("X-Real-IP", client_host.toString());
+ std::string clientIpString = "client ip: ";
+ client_host = Poco::Net::IPAddress(client_host_string);
+ clientIpString += client_host_string;
+ Poco::Logger::get("requestLog").information(clientIpString);
+ // debugging end
+ mSession->setClientIp(client_host);
+ response.addCookie(mSession->getLoginCookie());
+ } else {
+ langCatalog = mSession->getLanguageCatalog();
+ }
+ UserStates user_state;
+ try {
+ user_state = mSession->loadUser(email, password);
+ } catch (Poco::Exception& ex) {
+ addError(new ParamError("login", "exception by calling loadUser: ", ex.displayText()));
+ sendErrorsAsEmail();
+ addError(new Error("Error", "Intern Server error, please try again later"));
+ }
+ auto user = mSession->getNewUser();
+
+ if(user_state >= USER_LOADED_FROM_DB && !user.isNull() && !user->getModel()->getPublicKey()) {
+ if(mSession->generateKeys(true, true)) {
+ user_state = USER_COMPLETE;
+ if(user->getModel()->isDisabled()) {
+ user_state = USER_DISABLED;
+ }
+ }
+ } else {
+ //printf("pubkey exist: %p\n",user->getModel()->getPublicKey());
+ }
+ getErrors(mSession);
+
+ auto uri_start = request.serverParams().getServerName();
+ auto lastExternReferer = mSession->getLastReferer();
+
+ printf("user_state: %d\n", user_state);
+
+ switch(user_state) {
+ case USER_EMPTY:
+ case USER_PASSWORD_INCORRECT:
+ addError(new Error(langCatalog->gettext("Login"), langCatalog->gettext("E-Mail or password isn't right, please try again!")), false);
+ if(mSession) {
+ getErrors(mSession);
+ sm->releaseSession(mSession);
+ }
+ sm->deleteLoginCookies(request, response);
+ break;
+ case USER_PASSWORD_ENCRYPTION_IN_PROCESS:
+ addError(new Error(langCatalog->gettext("Passwort"), langCatalog->gettext("Passwort wird noch berechnet, bitte versuche es in etwa 1 Minute erneut.")), false);
+ break;
+ case USER_KEYS_DONT_MATCH:
+ addError(new Error(langCatalog->gettext("User"), langCatalog->gettext("Error in saved data, the server admin will look at it.")));
+ break;
+ case USER_DISABLED:
+ addError(new Error(langCatalog->gettext("User"), langCatalog->gettext("Benutzer ist deaktiviert, kein Login möglich!")));
+ if(mSession) {
+ getErrors(mSession);
+ sm->releaseSession(mSession);
+ }
+ sm->deleteLoginCookies(request, response);
+ break;
+ case USER_NO_PRIVATE_KEY:
+ case USER_COMPLETE:
+ case USER_EMAIL_NOT_ACTIVATED:
+ auto referer = request.find("Referer");
+ std::string refererString;
+ if (referer != request.end()) {
+ refererString = referer->second;
+ }
+ if(lastExternReferer != "") {
+ //printf("redirect to: %s\n", lastExternReferer.data());
+ response.redirect(lastExternReferer);
+ } else if(refererString != "" &&
+ refererString.find("login") == std::string::npos &&
+ refererString.find("logout") == std::string::npos &&
+ refererString.find("user_delete") == std::string::npos &&
+ refererString != ServerConfig::g_serverPath + request.getURI()) {
+ std::string uri = request.getURI();
+ printf("request uri: %s, redirect to: %s\n", uri.data(), refererString.data());
+ response.redirect(refererString);
+ } else {
+ //printf("redirect to: %s\n", ServerConfig::g_php_serverPath.data());
+ response.redirect(ServerConfig::g_php_serverPath + "/");
+ }
+ return;
+ }
+
+ } else if(!langUpdatedByBtn) {
+ addError(new Error(langCatalog->gettext("Login"), langCatalog->gettext("Username and password are needed!")), false);
+ }
+
+ } else {
+
+ // on enter login page with empty form
+ //auto session = sm->getSession(request);
+ // remove old cookies and session if exist
+ if(mSession) {
+ getErrors(mSession);
+ sm->releaseSession(mSession);
+ }
+ sm->deleteLoginCookies(request, response);
+ }
+
+#line 3 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\header.cpsp"
+
+ bool withMaterialIcons = false;
+ std::ostream& _responseStream = response.send();
+ Poco::DeflatingOutputStream _gzipStream(_responseStream, Poco::DeflatingStreamBuf::STREAM_GZIP, 1);
+ std::ostream& responseStream = _compressResponse ? _gzipStream : _responseStream;
+ responseStream << "\n";
+ // begin include header.cpsp
+ responseStream << "\n";
+ responseStream << "\n";
+ responseStream << "\n";
+ responseStream << "\n";
+ responseStream << "\n";
+ responseStream << "\n";
+ responseStream << "Gradido Login Server: ";
+#line 11 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\header.cpsp"
+ responseStream << ( pageName );
+ responseStream << "\n";
+ responseStream << "\n";
+#line 13 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\header.cpsp"
+ if(withMaterialIcons) { responseStream << "\n";
+ responseStream << "\n";
+#line 15 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\header.cpsp"
+ } responseStream << "\n";
+ responseStream << "\n";
+ responseStream << "\n";
+ responseStream << " \n";
+ responseStream << "
\n";
+ responseStream << "
\n";
+ responseStream << " ";
+#line 6 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\footer.cpsp"
+ responseStream << ( mTimeProfiler.string() );
+ responseStream << "\n";
+ responseStream << "
\n";
+ responseStream << "
\n";
+ responseStream << "
Login Server in Entwicklung
\n";
+ responseStream << "
Alpha ";
+#line 10 "F:\\Gradido\\gradido_local\\login_server\\src\\cpsp\\footer.cpsp"
+ responseStream << ( ServerConfig::g_versionString );
+ responseStream << "
\n";
+ responseStream << "
\n";
+ responseStream << "
\n";
+ responseStream << "\n";
+ responseStream << "\n";
+ responseStream << "";
+ // end include footer.cpsp
+ if (_compressResponse) _gzipStream.close();
+}
diff --git a/login_server/src/cpp/HTTPInterface/LoginPage.h b/login_server/src/cpp/HTTPInterface/LoginPage.h
index be3dfd97d..30c617eaf 100644
--- a/login_server/src/cpp/HTTPInterface/LoginPage.h
+++ b/login_server/src/cpp/HTTPInterface/LoginPage.h
@@ -1,20 +1,20 @@
-#ifndef LoginPage_INCLUDED
-#define LoginPage_INCLUDED
-
-
-#include "Poco/Net/HTTPRequestHandler.h"
-
-
-#include "SessionHTTPRequestHandler.h"
-
-
-class LoginPage: public SessionHTTPRequestHandler
-{
-public:
- LoginPage(Session*);
-
- void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response);
-};
-
-
-#endif // LoginPage_INCLUDED
+#ifndef LoginPage_INCLUDED
+#define LoginPage_INCLUDED
+
+
+#include "Poco/Net/HTTPRequestHandler.h"
+
+
+#include "SessionHTTPRequestHandler.h"
+
+
+class LoginPage: public SessionHTTPRequestHandler
+{
+public:
+ LoginPage(Session*);
+
+ void handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response);
+};
+
+
+#endif // LoginPage_INCLUDED
diff --git a/login_server/src/cpp/JSONInterface/JsonCreateUser.cpp b/login_server/src/cpp/JSONInterface/JsonCreateUser.cpp
index 6b5ecf33d..7b5cf8270 100644
--- a/login_server/src/cpp/JSONInterface/JsonCreateUser.cpp
+++ b/login_server/src/cpp/JSONInterface/JsonCreateUser.cpp
@@ -1,108 +1,108 @@
-#include "JsonCreateUser.h"
-
-#include "../model/email/Email.h"
-#include "../controller/User.h"
-#include "../controller/EmailVerificationCode.h"
-
-#include "../SingletonManager/EmailManager.h"
-#include "../SingletonManager/SessionManager.h"
-
-#include "../tasks/AuthenticatedEncryptionCreateKeyTask.h"
-
-Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
-{
- std::string email;
- std::string first_name;
- std::string last_name;
- std::string password;
- bool login_after_register = false;
- int emailType;
- auto em = EmailManager::getInstance();
- auto sm = SessionManager::getInstance();
-
- // if is json object
- if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
- Poco::JSON::Object::Ptr paramJsonObject = params.extract();
- /// Throws a RangeException if the value does not fit
- /// into the result variable.
- /// Throws a NotImplementedException if conversion is
- /// not available for the given type.
- /// Throws InvalidAccessException if Var is empty.
- try {
- paramJsonObject->get("email").convert(email);
- paramJsonObject->get("first_name").convert(first_name);
- paramJsonObject->get("last_name").convert(last_name);
- paramJsonObject->get("emailType").convert(emailType);
-
- if ((ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS)) {
- paramJsonObject->get("password").convert(password);
- }
- if (!paramJsonObject->isNull("login_after_register")) {
- paramJsonObject->get("login_after_register").convert(login_after_register);
- }
- }
- catch (Poco::Exception& ex) {
- return stateError("json exception", ex.displayText());
- }
- }
- else {
- return stateError("parameter format unknown");
- }
-
- auto user = controller::User::create();
- if (user->load(email) > 0) {
- return customStateError("exist", "user already exist");
- }
-
- if (password.size()) {
- ErrorList errors;
- if (!sm->checkPwdValidation(password, &errors)) {
- Poco::JSON::Object* result = new Poco::JSON::Object;
- result->set("state", "error");
- result->set("msg", errors.getLastError()->getString(false));
- if (errors.errorCount()) {
- result->set("details", errors.getLastError()->getString(false));
- }
- return result;
- }
- }
-
- // create user
- user = controller::User::create(email, first_name, last_name);
- auto userModel = user->getModel();
- Session* session = nullptr;
-
- if (!userModel->insertIntoDB(true)) {
- userModel->sendErrorsAsEmail();
- return stateError("insert user failed");
- }
- if (password.size()) {
- session = sm->getNewSession();
- session->setUser(user);
- session->generateKeys(true, true);
- session->setClientIp(mClientIP);
-
- // calculate encryption key, could need some time, will save encrypted privkey to db
- UniLib::controller::TaskPtr create_authenticated_encrypten_key = new AuthenticatedEncryptionCreateKeyTask(user, password);
- create_authenticated_encrypten_key->scheduleTask(create_authenticated_encrypten_key);
- }
-
- auto emailOptIn = controller::EmailVerificationCode::create(userModel->getID(), model::table::EMAIL_OPT_IN_REGISTER);
- auto emailOptInModel = emailOptIn->getModel();
- if (!emailOptInModel->insertIntoDB(false)) {
- emailOptInModel->sendErrorsAsEmail();
- return stateError("insert emailOptIn failed");
- }
-
- em->addEmail(new model::Email(emailOptIn, user, model::Email::convertTypeFromInt(emailType)));
-
- if (login_after_register && session) {
- Poco::JSON::Object* result = stateSuccess();
-
- result->set("session_id", session->getHandle());
- return result;
- }
-
- return stateSuccess();
-
+#include "JsonCreateUser.h"
+
+#include "../model/email/Email.h"
+#include "../controller/User.h"
+#include "../controller/EmailVerificationCode.h"
+
+#include "../SingletonManager/EmailManager.h"
+#include "../SingletonManager/SessionManager.h"
+
+#include "../tasks/AuthenticatedEncryptionCreateKeyTask.h"
+
+Poco::JSON::Object* JsonCreateUser::handle(Poco::Dynamic::Var params)
+{
+ std::string email;
+ std::string first_name;
+ std::string last_name;
+ std::string password;
+ bool login_after_register = false;
+ int emailType;
+ auto em = EmailManager::getInstance();
+ auto sm = SessionManager::getInstance();
+
+ // if is json object
+ if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
+ Poco::JSON::Object::Ptr paramJsonObject = params.extract();
+ /// Throws a RangeException if the value does not fit
+ /// into the result variable.
+ /// Throws a NotImplementedException if conversion is
+ /// not available for the given type.
+ /// Throws InvalidAccessException if Var is empty.
+ try {
+ paramJsonObject->get("email").convert(email);
+ paramJsonObject->get("first_name").convert(first_name);
+ paramJsonObject->get("last_name").convert(last_name);
+ paramJsonObject->get("emailType").convert(emailType);
+
+ if ((ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS)) {
+ paramJsonObject->get("password").convert(password);
+ }
+ if (!paramJsonObject->isNull("login_after_register")) {
+ paramJsonObject->get("login_after_register").convert(login_after_register);
+ }
+ }
+ catch (Poco::Exception& ex) {
+ return stateError("json exception", ex.displayText());
+ }
+ }
+ else {
+ return stateError("parameter format unknown");
+ }
+
+ auto user = controller::User::create();
+ if (user->load(email) > 0) {
+ return customStateError("exist", "user already exist");
+ }
+
+ if (password.size()) {
+ ErrorList errors;
+ if (!sm->checkPwdValidation(password, &errors)) {
+ Poco::JSON::Object* result = new Poco::JSON::Object;
+ result->set("state", "error");
+ result->set("msg", errors.getLastError()->getString(false));
+ if (errors.errorCount()) {
+ result->set("details", errors.getLastError()->getString(false));
+ }
+ return result;
+ }
+ }
+
+ // create user
+ user = controller::User::create(email, first_name, last_name);
+ auto userModel = user->getModel();
+ Session* session = nullptr;
+
+ if (!userModel->insertIntoDB(true)) {
+ userModel->sendErrorsAsEmail();
+ return stateError("insert user failed");
+ }
+ if (password.size()) {
+ session = sm->getNewSession();
+ session->setUser(user);
+ session->generateKeys(true, true);
+ session->setClientIp(mClientIP);
+
+ // calculate encryption key, could need some time, will save encrypted privkey to db
+ UniLib::controller::TaskPtr create_authenticated_encrypten_key = new AuthenticatedEncryptionCreateKeyTask(user, password);
+ create_authenticated_encrypten_key->scheduleTask(create_authenticated_encrypten_key);
+ }
+
+ auto emailOptIn = controller::EmailVerificationCode::create(userModel->getID(), model::table::EMAIL_OPT_IN_REGISTER);
+ auto emailOptInModel = emailOptIn->getModel();
+ if (!emailOptInModel->insertIntoDB(false)) {
+ emailOptInModel->sendErrorsAsEmail();
+ return stateError("insert emailOptIn failed");
+ }
+
+ em->addEmail(new model::Email(emailOptIn, user, model::Email::convertTypeFromInt(emailType)));
+
+ if (login_after_register && session) {
+ Poco::JSON::Object* result = stateSuccess();
+
+ result->set("session_id", session->getHandle());
+ return result;
+ }
+
+ return stateSuccess();
+
}
\ No newline at end of file
diff --git a/login_server/src/cpp/JSONInterface/JsonGetUserInfos.cpp b/login_server/src/cpp/JSONInterface/JsonGetUserInfos.cpp
index 5b426782a..c9754a14f 100644
--- a/login_server/src/cpp/JSONInterface/JsonGetUserInfos.cpp
+++ b/login_server/src/cpp/JSONInterface/JsonGetUserInfos.cpp
@@ -1,144 +1,144 @@
-#include "JsonGetUserInfos.h"
-
-#include "../lib/DataTypeConverter.h"
-#include "../SingletonManager/SessionManager.h"
-#include "../controller/User.h"
-#include "../controller/EmailVerificationCode.h"
-
-#include "../ServerConfig.h"
-
-Poco::UInt64 JsonGetUserInfos::readOrCreateEmailVerificationCode(int user_id, model::table::EmailOptInType type)
-{
- try {
- auto emailVerificationCode = controller::EmailVerificationCode::load(user_id, type);
- if (!emailVerificationCode) {
- emailVerificationCode = controller::EmailVerificationCode::create(user_id, type);
- UniLib::controller::TaskPtr insert = new model::table::ModelInsertTask(emailVerificationCode->getModel(), false);
- insert->scheduleTask(insert);
- }
- return emailVerificationCode->getModel()->getCode();
- }
- catch (Poco::Exception& ex) {
- ErrorList errors;
- //printf("exception: %s\n", ex.displayText().data());
- errors.addError(new ParamError("JsonGetUserInfos::readOrCreateEmailVerificationCode", "exception: ", ex.displayText()));
- errors.sendErrorsAsEmail();
- }
- return 0;
-}
-
-Poco::JSON::Object* JsonGetUserInfos::handle(Poco::Dynamic::Var params)
-{
- /*
- 'session_id' => $session_id,
- 'email' => $email,
- 'ask' => ['EmailOptIn.Register']
- */
- // incoming
- int session_id = 0;
- std::string email;
- Poco::JSON::Array::Ptr askArray;
-
- auto sm = SessionManager::getInstance();
-
- // if is json object
- if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
- Poco::JSON::Object::Ptr paramJsonObject = params.extract();
- /// Throws a RangeException if the value does not fit
- /// into the result variable.
- /// Throws a NotImplementedException if conversion is
- /// not available for the given type.
- /// Throws InvalidAccessException if Var is empty.
- try {
- paramJsonObject->get("email").convert(email);
- paramJsonObject->get("session_id").convert(session_id);
- askArray = paramJsonObject->getArray("ask");
- }
- catch (Poco::Exception& ex) {
- return stateError("json exception", ex.displayText());
- }
- }
- else {
- return stateError("parameter format unknown");
- }
-
- if (!session_id) {
- return stateError("session_id invalid");
- }
- if (askArray.isNull()) {
- return stateError("ask is zero or not an array");
- }
-
- auto session = sm->getSession(session_id);
- if (!session) {
- return customStateError("not found", "session not found");
- }
-
- auto session_user = session->getNewUser();
- auto session_user_model = session_user->getModel();
- bool isAdmin = false;
- bool emailBelongToUser = false;
- if (model::table::ROLE_ADMIN == session_user_model->getRole()) {
- isAdmin = true;
- }
- if (session_user_model->getEmail() == email) {
- emailBelongToUser = true;
- }
-
- auto user = controller::User::create();
- if (1 != user->load(email)) {
- return customStateError("not found", "user not found");
- }
- auto user_model = user->getModel();
-
-
- Poco::JSON::Object* result = new Poco::JSON::Object;
- result->set("state", "success");
- Poco::JSON::Array jsonErrorsArray;
- Poco::JSON::Object jsonUser;
- Poco::JSON::Object jsonServer;
-
- for (auto it = askArray->begin(); it != askArray->end(); it++) {
- auto parameter = *it;
- std::string parameterString;
- try {
- parameter.convert(parameterString);
- if (parameterString == "EmailVerificationCode.Register" && isAdmin && !emailBelongToUser) {
- auto code = readOrCreateEmailVerificationCode(user_model->getID(), model::table::EMAIL_OPT_IN_REGISTER_DIRECT);
- if (code) {
- jsonUser.set("EmailVerificationCode.Register", std::to_string(code));
- }
- }
- else if (parameterString == "loginServer.path") {
- jsonServer.set("loginServer.path", ServerConfig::g_serverPath);
- }
- else if (parameterString == "user.pubkeyhex") {
- jsonUser.set("pubkeyhex", user_model->getPublicKeyHex());
- }
- else if (parameterString == "user.first_name") {
- jsonUser.set("first_name", user_model->getFirstName());
- }
- else if (parameterString == "user.last_name") {
- jsonUser.set("last_name", user_model->getLastName());
- }
- else if (parameterString == "user.disabled") {
- jsonUser.set("disabled", user_model->isDisabled());
- }
- else if (parameterString == "user.email_checked" && (isAdmin || emailBelongToUser)) {
- jsonUser.set("email_checked", user_model->isEmailChecked());
- }
- else if (parameterString == "user.identHash") {
- auto email = user_model->getEmail();
- jsonUser.set("identHash", DRMakeStringHash(email.data(), email.size()));
- }
- }
- catch (Poco::Exception& ex) {
- jsonErrorsArray.add("ask parameter invalid");
- }
- }
- result->set("errors", jsonErrorsArray);
- result->set("userData", jsonUser);
- result->set("server", jsonServer);
- return result;
-
+#include "JsonGetUserInfos.h"
+
+#include "../lib/DataTypeConverter.h"
+#include "../SingletonManager/SessionManager.h"
+#include "../controller/User.h"
+#include "../controller/EmailVerificationCode.h"
+
+#include "../ServerConfig.h"
+
+Poco::UInt64 JsonGetUserInfos::readOrCreateEmailVerificationCode(int user_id, model::table::EmailOptInType type)
+{
+ try {
+ auto emailVerificationCode = controller::EmailVerificationCode::load(user_id, type);
+ if (!emailVerificationCode) {
+ emailVerificationCode = controller::EmailVerificationCode::create(user_id, type);
+ UniLib::controller::TaskPtr insert = new model::table::ModelInsertTask(emailVerificationCode->getModel(), false);
+ insert->scheduleTask(insert);
+ }
+ return emailVerificationCode->getModel()->getCode();
+ }
+ catch (Poco::Exception& ex) {
+ ErrorList errors;
+ //printf("exception: %s\n", ex.displayText().data());
+ errors.addError(new ParamError("JsonGetUserInfos::readOrCreateEmailVerificationCode", "exception: ", ex.displayText()));
+ errors.sendErrorsAsEmail();
+ }
+ return 0;
+}
+
+Poco::JSON::Object* JsonGetUserInfos::handle(Poco::Dynamic::Var params)
+{
+ /*
+ 'session_id' => $session_id,
+ 'email' => $email,
+ 'ask' => ['EmailOptIn.Register']
+ */
+ // incoming
+ int session_id = 0;
+ std::string email;
+ Poco::JSON::Array::Ptr askArray;
+
+ auto sm = SessionManager::getInstance();
+
+ // if is json object
+ if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
+ Poco::JSON::Object::Ptr paramJsonObject = params.extract();
+ /// Throws a RangeException if the value does not fit
+ /// into the result variable.
+ /// Throws a NotImplementedException if conversion is
+ /// not available for the given type.
+ /// Throws InvalidAccessException if Var is empty.
+ try {
+ paramJsonObject->get("email").convert(email);
+ paramJsonObject->get("session_id").convert(session_id);
+ askArray = paramJsonObject->getArray("ask");
+ }
+ catch (Poco::Exception& ex) {
+ return stateError("json exception", ex.displayText());
+ }
+ }
+ else {
+ return stateError("parameter format unknown");
+ }
+
+ if (!session_id) {
+ return stateError("session_id invalid");
+ }
+ if (askArray.isNull()) {
+ return stateError("ask is zero or not an array");
+ }
+
+ auto session = sm->getSession(session_id);
+ if (!session) {
+ return customStateError("not found", "session not found");
+ }
+
+ auto session_user = session->getNewUser();
+ auto session_user_model = session_user->getModel();
+ bool isAdmin = false;
+ bool emailBelongToUser = false;
+ if (model::table::ROLE_ADMIN == session_user_model->getRole()) {
+ isAdmin = true;
+ }
+ if (session_user_model->getEmail() == email) {
+ emailBelongToUser = true;
+ }
+
+ auto user = controller::User::create();
+ if (1 != user->load(email)) {
+ return customStateError("not found", "user not found");
+ }
+ auto user_model = user->getModel();
+
+
+ Poco::JSON::Object* result = new Poco::JSON::Object;
+ result->set("state", "success");
+ Poco::JSON::Array jsonErrorsArray;
+ Poco::JSON::Object jsonUser;
+ Poco::JSON::Object jsonServer;
+
+ for (auto it = askArray->begin(); it != askArray->end(); it++) {
+ auto parameter = *it;
+ std::string parameterString;
+ try {
+ parameter.convert(parameterString);
+ if (parameterString == "EmailVerificationCode.Register" && isAdmin && !emailBelongToUser) {
+ auto code = readOrCreateEmailVerificationCode(user_model->getID(), model::table::EMAIL_OPT_IN_REGISTER_DIRECT);
+ if (code) {
+ jsonUser.set("EmailVerificationCode.Register", std::to_string(code));
+ }
+ }
+ else if (parameterString == "loginServer.path") {
+ jsonServer.set("loginServer.path", ServerConfig::g_serverPath);
+ }
+ else if (parameterString == "user.pubkeyhex") {
+ jsonUser.set("pubkeyhex", user_model->getPublicKeyHex());
+ }
+ else if (parameterString == "user.first_name") {
+ jsonUser.set("first_name", user_model->getFirstName());
+ }
+ else if (parameterString == "user.last_name") {
+ jsonUser.set("last_name", user_model->getLastName());
+ }
+ else if (parameterString == "user.disabled") {
+ jsonUser.set("disabled", user_model->isDisabled());
+ }
+ else if (parameterString == "user.email_checked" && (isAdmin || emailBelongToUser)) {
+ jsonUser.set("email_checked", user_model->isEmailChecked());
+ }
+ else if (parameterString == "user.identHash") {
+ auto email = user_model->getEmail();
+ jsonUser.set("identHash", DRMakeStringHash(email.data(), email.size()));
+ }
+ }
+ catch (Poco::Exception& ex) {
+ jsonErrorsArray.add("ask parameter invalid");
+ }
+ }
+ result->set("errors", jsonErrorsArray);
+ result->set("userData", jsonUser);
+ result->set("server", jsonServer);
+ return result;
+
}
\ No newline at end of file
diff --git a/login_server/src/cpp/JSONInterface/JsonLogout.cpp b/login_server/src/cpp/JSONInterface/JsonLogout.cpp
index 0b347f767..03cb25fb3 100644
--- a/login_server/src/cpp/JSONInterface/JsonLogout.cpp
+++ b/login_server/src/cpp/JSONInterface/JsonLogout.cpp
@@ -1,44 +1,44 @@
-#include "JsonLogout.h"
-
-
-
-#include "../SingletonManager/SessionManager.h"
-
-
-Poco::JSON::Object* JsonLogout::handle(Poco::Dynamic::Var params)
-{
-
- auto sm = SessionManager::getInstance();
- int session_id = 0;
-
- // if is json object
- if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
- Poco::JSON::Object::Ptr paramJsonObject = params.extract();
- /// Throws a RangeException if the value does not fit
- /// into the result variable.
- /// Throws a NotImplementedException if conversion is
- /// not available for the given type.
- /// Throws InvalidAccessException if Var is empty.
- try {
- paramJsonObject->get("session_id").convert(session_id);
- }
- catch (Poco::Exception& ex) {
- return stateError("json exception", ex.displayText());
- }
- }
- else {
- return stateError("parameter format unknown");
- }
-
- auto session = sm->getSession(session_id);
- if (!session) {
- return stateError("session not found", std::to_string(session_id));
- }
- if (sm->releaseSession(session_id)) {
- return stateSuccess();
- }
- return stateError("error by releasing session");
-
-
-
+#include "JsonLogout.h"
+
+
+
+#include "../SingletonManager/SessionManager.h"
+
+
+Poco::JSON::Object* JsonLogout::handle(Poco::Dynamic::Var params)
+{
+
+ auto sm = SessionManager::getInstance();
+ int session_id = 0;
+
+ // if is json object
+ if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
+ Poco::JSON::Object::Ptr paramJsonObject = params.extract();
+ /// Throws a RangeException if the value does not fit
+ /// into the result variable.
+ /// Throws a NotImplementedException if conversion is
+ /// not available for the given type.
+ /// Throws InvalidAccessException if Var is empty.
+ try {
+ paramJsonObject->get("session_id").convert(session_id);
+ }
+ catch (Poco::Exception& ex) {
+ return stateError("json exception", ex.displayText());
+ }
+ }
+ else {
+ return stateError("parameter format unknown");
+ }
+
+ auto session = sm->getSession(session_id);
+ if (!session) {
+ return stateError("session not found", std::to_string(session_id));
+ }
+ if (sm->releaseSession(session_id)) {
+ return stateSuccess();
+ }
+ return stateError("error by releasing session");
+
+
+
}
\ No newline at end of file
diff --git a/login_server/src/cpp/JSONInterface/JsonLogout.h b/login_server/src/cpp/JSONInterface/JsonLogout.h
index 7243d2b8c..8b3def105 100644
--- a/login_server/src/cpp/JSONInterface/JsonLogout.h
+++ b/login_server/src/cpp/JSONInterface/JsonLogout.h
@@ -1,18 +1,18 @@
-#ifndef __JSON_INTERFACE_JSON_LOGOUT_
-#define __JSON_INTERFACE_JSON_LOGOUT_
-
-#include "JsonRequestHandler.h"
-
-class JsonLogout : public JsonRequestHandler
-{
-public:
- JsonLogout(Poco::Net::IPAddress ip) : mClientIP(ip) {}
- Poco::JSON::Object* handle(Poco::Dynamic::Var params);
-
-protected:
- Poco::Net::IPAddress mClientIP;
-
-
-};
-
+#ifndef __JSON_INTERFACE_JSON_LOGOUT_
+#define __JSON_INTERFACE_JSON_LOGOUT_
+
+#include "JsonRequestHandler.h"
+
+class JsonLogout : public JsonRequestHandler
+{
+public:
+ JsonLogout(Poco::Net::IPAddress ip) : mClientIP(ip) {}
+ Poco::JSON::Object* handle(Poco::Dynamic::Var params);
+
+protected:
+ Poco::Net::IPAddress mClientIP;
+
+
+};
+
#endif // __JSON_INTERFACE_JSON_LOGOUT_
\ No newline at end of file
diff --git a/login_server/src/cpp/JSONInterface/JsonRequestHandler.cpp b/login_server/src/cpp/JSONInterface/JsonRequestHandler.cpp
index b66cba495..3f9bd35b3 100644
--- a/login_server/src/cpp/JSONInterface/JsonRequestHandler.cpp
+++ b/login_server/src/cpp/JSONInterface/JsonRequestHandler.cpp
@@ -1,141 +1,141 @@
-#include "JsonRequestHandler.h"
-
-#include "Poco/Net/HTTPServerRequest.h"
-#include "Poco/Net/HTTPServerResponse.h"
-
-#include "Poco/URI.h"
-#include "Poco/DeflatingStream.h"
-
-#include "Poco/JSON/Parser.h"
-
-#include "../ServerConfig.h"
-
-#include "../lib/DataTypeConverter.h"
-#include "../SingletonManager/SessionManager.h"
-
-
-void JsonRequestHandler::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response)
-{
-
- response.setChunkedTransferEncoding(false);
- response.setContentType("application/json");
- if (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_CORS_ALL) {
- response.set("Access-Control-Allow-Origin", "*");
- response.set("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");
- }
- //bool _compressResponse(request.hasToken("Accept-Encoding", "gzip"));
- //if (_compressResponse) response.set("Content-Encoding", "gzip");
-
- std::ostream& responseStream = response.send();
- //Poco::DeflatingOutputStream _gzipStream(_responseStream, Poco::DeflatingStreamBuf::STREAM_GZIP, 1);
- //std::ostream& responseStream = _compressResponse ? _gzipStream : _responseStream;
-
- auto method = request.getMethod();
- std::istream& request_stream = request.stream();
- Poco::JSON::Object* json_result = nullptr;
- if (method == "POST" || method == "PUT") {
- // extract parameter from request
- Poco::Dynamic::Var parsedResult = parseJsonWithErrorPrintFile(request_stream);
-
- if (parsedResult.size() != 0) {
- json_result = handle(parsedResult);
- }
- else {
- json_result = stateError("empty body");
- }
-
- }
- else if(method == "GET") {
- Poco::URI uri(request.getURI());
- auto queryParameters = uri.getQueryParameters();
- json_result = handle(queryParameters);
- }
-
- if (json_result) {
- if (!json_result->isNull("session_id")) {
- int session_id = 0;
- try {
- json_result->get("session_id").convert(session_id);
- }
- catch (Poco::Exception& e) {
- ErrorList erros;
- erros.addError(new Error("json request", "invalid session_id"));
- erros.sendErrorsAsEmail();
- }
- if (session_id) {
- auto session = SessionManager::getInstance()->getSession(session_id);
- response.addCookie(session->getLoginCookie());
- }
- }
- json_result->stringify(responseStream);
- delete json_result;
- }
-
- //if (_compressResponse) _gzipStream.close();
-}
-
-
-Poco::Dynamic::Var JsonRequestHandler::parseJsonWithErrorPrintFile(std::istream& request_stream, ErrorList* errorHandler /* = nullptr*/, const char* functionName /* = nullptr*/)
-{
- // debugging answer
-
- std::stringstream responseStringStream;
- for (std::string line; std::getline(request_stream, line); ) {
- responseStringStream << line << std::endl;
- }
-
- // extract parameter from request
- Poco::JSON::Parser jsonParser;
- Poco::Dynamic::Var parsedJson;
- try {
- parsedJson = jsonParser.parse(responseStringStream.str());
-
- return parsedJson;
- }
- catch (Poco::Exception& ex) {
- if (errorHandler) {
- errorHandler->addError(new ParamError(functionName, "error parsing request answer", ex.displayText().data()));
- errorHandler->sendErrorsAsEmail(responseStringStream.str());
- }
- std::string dateTimeString = Poco::DateTimeFormatter::format(Poco::DateTime(), "%d_%m_%yT%H_%M_%S");
- std::string filename = dateTimeString + "_response.html";
- FILE* f = fopen(filename.data(), "wt");
- if (f) {
- std::string responseString = responseStringStream.str();
- fwrite(responseString.data(), 1, responseString.size(), f);
- fclose(f);
- }
- return Poco::Dynamic::Var();
- }
- return Poco::Dynamic::Var();
-}
-
-Poco::JSON::Object* JsonRequestHandler::stateError(const char* msg, std::string details)
-{
- Poco::JSON::Object* result = new Poco::JSON::Object;
- result->set("state", "error");
- result->set("msg", msg);
- if (details != "") {
- result->set("details", details);
- }
- return result;
-}
-
-Poco::JSON::Object* JsonRequestHandler::stateSuccess()
-{
- Poco::JSON::Object* result = new Poco::JSON::Object;
- result->set("state", "success");
- return result;
-}
-
-Poco::JSON::Object* JsonRequestHandler::customStateError(const char* state, const char* msg, std::string details/* = ""*/)
-{
- Poco::JSON::Object* result = new Poco::JSON::Object;
- result->set("state", state);
- result->set("msg", msg);
- if (details != "") {
- result->set("details", details);
- }
- return result;
-}
-
+#include "JsonRequestHandler.h"
+
+#include "Poco/Net/HTTPServerRequest.h"
+#include "Poco/Net/HTTPServerResponse.h"
+
+#include "Poco/URI.h"
+#include "Poco/DeflatingStream.h"
+
+#include "Poco/JSON/Parser.h"
+
+#include "../ServerConfig.h"
+
+#include "../lib/DataTypeConverter.h"
+#include "../SingletonManager/SessionManager.h"
+
+
+void JsonRequestHandler::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response)
+{
+
+ response.setChunkedTransferEncoding(false);
+ response.setContentType("application/json");
+ if (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_CORS_ALL) {
+ response.set("Access-Control-Allow-Origin", "*");
+ response.set("Access-Control-Allow-Headers", "Access-Control-Allow-Headers, Origin,Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");
+ }
+ //bool _compressResponse(request.hasToken("Accept-Encoding", "gzip"));
+ //if (_compressResponse) response.set("Content-Encoding", "gzip");
+
+ std::ostream& responseStream = response.send();
+ //Poco::DeflatingOutputStream _gzipStream(_responseStream, Poco::DeflatingStreamBuf::STREAM_GZIP, 1);
+ //std::ostream& responseStream = _compressResponse ? _gzipStream : _responseStream;
+
+ auto method = request.getMethod();
+ std::istream& request_stream = request.stream();
+ Poco::JSON::Object* json_result = nullptr;
+ if (method == "POST" || method == "PUT") {
+ // extract parameter from request
+ Poco::Dynamic::Var parsedResult = parseJsonWithErrorPrintFile(request_stream);
+
+ if (parsedResult.size() != 0) {
+ json_result = handle(parsedResult);
+ }
+ else {
+ json_result = stateError("empty body");
+ }
+
+ }
+ else if(method == "GET") {
+ Poco::URI uri(request.getURI());
+ auto queryParameters = uri.getQueryParameters();
+ json_result = handle(queryParameters);
+ }
+
+ if (json_result) {
+ if (!json_result->isNull("session_id")) {
+ int session_id = 0;
+ try {
+ json_result->get("session_id").convert(session_id);
+ }
+ catch (Poco::Exception& e) {
+ ErrorList erros;
+ erros.addError(new Error("json request", "invalid session_id"));
+ erros.sendErrorsAsEmail();
+ }
+ if (session_id) {
+ auto session = SessionManager::getInstance()->getSession(session_id);
+ response.addCookie(session->getLoginCookie());
+ }
+ }
+ json_result->stringify(responseStream);
+ delete json_result;
+ }
+
+ //if (_compressResponse) _gzipStream.close();
+}
+
+
+Poco::Dynamic::Var JsonRequestHandler::parseJsonWithErrorPrintFile(std::istream& request_stream, ErrorList* errorHandler /* = nullptr*/, const char* functionName /* = nullptr*/)
+{
+ // debugging answer
+
+ std::stringstream responseStringStream;
+ for (std::string line; std::getline(request_stream, line); ) {
+ responseStringStream << line << std::endl;
+ }
+
+ // extract parameter from request
+ Poco::JSON::Parser jsonParser;
+ Poco::Dynamic::Var parsedJson;
+ try {
+ parsedJson = jsonParser.parse(responseStringStream.str());
+
+ return parsedJson;
+ }
+ catch (Poco::Exception& ex) {
+ if (errorHandler) {
+ errorHandler->addError(new ParamError(functionName, "error parsing request answer", ex.displayText().data()));
+ errorHandler->sendErrorsAsEmail(responseStringStream.str());
+ }
+ std::string dateTimeString = Poco::DateTimeFormatter::format(Poco::DateTime(), "%d_%m_%yT%H_%M_%S");
+ std::string filename = dateTimeString + "_response.html";
+ FILE* f = fopen(filename.data(), "wt");
+ if (f) {
+ std::string responseString = responseStringStream.str();
+ fwrite(responseString.data(), 1, responseString.size(), f);
+ fclose(f);
+ }
+ return Poco::Dynamic::Var();
+ }
+ return Poco::Dynamic::Var();
+}
+
+Poco::JSON::Object* JsonRequestHandler::stateError(const char* msg, std::string details)
+{
+ Poco::JSON::Object* result = new Poco::JSON::Object;
+ result->set("state", "error");
+ result->set("msg", msg);
+ if (details != "") {
+ result->set("details", details);
+ }
+ return result;
+}
+
+Poco::JSON::Object* JsonRequestHandler::stateSuccess()
+{
+ Poco::JSON::Object* result = new Poco::JSON::Object;
+ result->set("state", "success");
+ return result;
+}
+
+Poco::JSON::Object* JsonRequestHandler::customStateError(const char* state, const char* msg, std::string details/* = ""*/)
+{
+ Poco::JSON::Object* result = new Poco::JSON::Object;
+ result->set("state", state);
+ result->set("msg", msg);
+ if (details != "") {
+ result->set("details", details);
+ }
+ return result;
+}
+
diff --git a/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp b/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp
index 903ccfdfb..d6eff52f9 100644
--- a/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp
+++ b/login_server/src/cpp/JSONInterface/JsonRequestHandlerFactory.cpp
@@ -1,82 +1,82 @@
-#include "JsonRequestHandlerFactory.h"
-
-#include "Poco/Net/HTTPServerRequest.h"
-
-#include "../SingletonManager/SessionManager.h"
-
-#include "JsonAdminEmailVerificationResend.h"
-#include "JsonCheckSessionState.h"
-#include "JsonCreateUser.h"
-#include "JsonGetLogin.h"
-#include "JsonUnknown.h"
-#include "JsonTransaction.h"
-#include "JsonGetRunningUserTasks.h"
-#include "JsonGetUsers.h"
-#include "JsonLoginViaEmailVerificationCode.h"
-#include "JsonGetUserInfos.h"
-#include "JsonUpdateUserInfos.h"
-#include "JsonUnsecureLogin.h"
-#include "JsonLogout.h"
-
-JsonRequestHandlerFactory::JsonRequestHandlerFactory()
- : mRemoveGETParameters("^/([a-zA-Z0-9_-]*)"), mLogging(Poco::Logger::get("requestLog"))
-{
-}
-
-Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest& request)
-{
- std::string uri = request.getURI();
- std::string url_first_part;
- std::stringstream logStream;
-
- mRemoveGETParameters.extract(uri, url_first_part);
-
- std::string dateTimeString = Poco::DateTimeFormatter::format(Poco::DateTime(), "%d.%m.%y %H:%M:%S");
- logStream << dateTimeString << " call " << uri;
-
- mLogging.information(logStream.str());
-
- auto client_host = request.clientAddress().host();
- //auto client_ip = request.clientAddress();
- // X-Real-IP forwarded ip from nginx config
- auto client_host_string = request.get("X-Real-IP", client_host.toString());
- client_host = Poco::Net::IPAddress(client_host_string);
-
- if (url_first_part == "/login") {
- return new JsonGetLogin;
- }
- else if (url_first_part == "/checkSessionState") {
- return new JsonCheckSessionState;
- }
- else if (url_first_part == "/checkTransaction") {
- return new JsonTransaction;
- }
- else if (url_first_part == "/getRunningUserTasks") {
- return new JsonGetRunningUserTasks;
- }
- else if (url_first_part == "/getUsers") {
- return new JsonGetUsers;
- }
- else if (url_first_part == "/createUser") {
- return new JsonCreateUser(client_host);
- }
- else if (url_first_part == "/adminEmailVerificationResend") {
- return new JsonAdminEmailVerificationResend;
- }
- else if (url_first_part == "/getUserInfos") {
- return new JsonGetUserInfos;
- }
- else if (url_first_part == "/updateUserInfos") {
- return new JsonUpdateUserInfos;
- }
- else if (url_first_part == "/unsecureLogin" && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS)) {
- return new JsonUnsecureLogin(client_host);
- }
- else if (url_first_part == "/loginViaEmailVerificationCode") {
- return new JsonLoginViaEmailVerificationCode(client_host);
- }
- else if (url_first_part == "/logout") {
- return new JsonLogout(client_host);
- }
- return new JsonUnknown;
-}
+#include "JsonRequestHandlerFactory.h"
+
+#include "Poco/Net/HTTPServerRequest.h"
+
+#include "../SingletonManager/SessionManager.h"
+
+#include "JsonAdminEmailVerificationResend.h"
+#include "JsonCheckSessionState.h"
+#include "JsonCreateUser.h"
+#include "JsonGetLogin.h"
+#include "JsonUnknown.h"
+#include "JsonTransaction.h"
+#include "JsonGetRunningUserTasks.h"
+#include "JsonGetUsers.h"
+#include "JsonLoginViaEmailVerificationCode.h"
+#include "JsonGetUserInfos.h"
+#include "JsonUpdateUserInfos.h"
+#include "JsonUnsecureLogin.h"
+#include "JsonLogout.h"
+
+JsonRequestHandlerFactory::JsonRequestHandlerFactory()
+ : mRemoveGETParameters("^/([a-zA-Z0-9_-]*)"), mLogging(Poco::Logger::get("requestLog"))
+{
+}
+
+Poco::Net::HTTPRequestHandler* JsonRequestHandlerFactory::createRequestHandler(const Poco::Net::HTTPServerRequest& request)
+{
+ std::string uri = request.getURI();
+ std::string url_first_part;
+ std::stringstream logStream;
+
+ mRemoveGETParameters.extract(uri, url_first_part);
+
+ std::string dateTimeString = Poco::DateTimeFormatter::format(Poco::DateTime(), "%d.%m.%y %H:%M:%S");
+ logStream << dateTimeString << " call " << uri;
+
+ mLogging.information(logStream.str());
+
+ auto client_host = request.clientAddress().host();
+ //auto client_ip = request.clientAddress();
+ // X-Real-IP forwarded ip from nginx config
+ auto client_host_string = request.get("X-Real-IP", client_host.toString());
+ client_host = Poco::Net::IPAddress(client_host_string);
+
+ if (url_first_part == "/login") {
+ return new JsonGetLogin;
+ }
+ else if (url_first_part == "/checkSessionState") {
+ return new JsonCheckSessionState;
+ }
+ else if (url_first_part == "/checkTransaction") {
+ return new JsonTransaction;
+ }
+ else if (url_first_part == "/getRunningUserTasks") {
+ return new JsonGetRunningUserTasks;
+ }
+ else if (url_first_part == "/getUsers") {
+ return new JsonGetUsers;
+ }
+ else if (url_first_part == "/createUser") {
+ return new JsonCreateUser(client_host);
+ }
+ else if (url_first_part == "/adminEmailVerificationResend") {
+ return new JsonAdminEmailVerificationResend;
+ }
+ else if (url_first_part == "/getUserInfos") {
+ return new JsonGetUserInfos;
+ }
+ else if (url_first_part == "/updateUserInfos") {
+ return new JsonUpdateUserInfos;
+ }
+ else if (url_first_part == "/unsecureLogin" && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_PASSWORD_REQUESTS)) {
+ return new JsonUnsecureLogin(client_host);
+ }
+ else if (url_first_part == "/loginViaEmailVerificationCode") {
+ return new JsonLoginViaEmailVerificationCode(client_host);
+ }
+ else if (url_first_part == "/logout") {
+ return new JsonLogout(client_host);
+ }
+ return new JsonUnknown;
+}
diff --git a/login_server/src/cpp/JSONInterface/JsonTransaction.cpp b/login_server/src/cpp/JSONInterface/JsonTransaction.cpp
index 62f001469..dcc5023f9 100644
--- a/login_server/src/cpp/JSONInterface/JsonTransaction.cpp
+++ b/login_server/src/cpp/JSONInterface/JsonTransaction.cpp
@@ -1,175 +1,175 @@
-#include "JsonTransaction.h"
-#include "Poco/URI.h"
-#include "Poco/Dynamic/Struct.h"
-
-#include "../SingletonManager/SessionManager.h"
-#include "../ServerConfig.h"
-
-Poco::JSON::Object* JsonTransaction::handle(Poco::Dynamic::Var params)
-{
- Poco::JSON::Object* result = new Poco::JSON::Object;
- int session_id = 0;
-
- // if is json object
- if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
- Poco::JSON::Object::Ptr paramJsonObject = params.extract();
-
- try {
- /// Throws a RangeException if the value does not fit
- /// into the result variable.
- /// Throws a NotImplementedException if conversion is
- /// not available for the given type.
- /// Throws InvalidAccessException if Var is empty.
- paramJsonObject->get("session_id").convert(session_id);
- auto sm = SessionManager::getInstance();
- if (session_id != 0) {
- auto session = sm->getSession(session_id);
- if (!session) {
- result->set("state", "error");
- result->set("msg", "session not found");
- return result;
- }
-
- int balance = 0;
-
- if (!paramJsonObject->isNull("balance")) {
- paramJsonObject->get("balance").convert(balance);
- if (balance) {
- auto u = session->getUser();
- if (u) {
- u->setBalance(balance);
- }
- auto nu = session->getNewUser();
- if (!nu.isNull()) {
- nu->setBalance(balance);
- }
- }
- }
-
- std::string transactionBase64String;
- Poco::Dynamic::Var transaction_base64 = paramJsonObject->get("transaction_base64");
- bool auto_sign = false;
- auto auto_sign_json = paramJsonObject->get("auto_sign");
- if (!auto_sign_json.isEmpty()) {
- auto_sign_json.convert(auto_sign);
- }
-
- if (transaction_base64.isString()) {
- paramJsonObject->get("transaction_base64").convert(transactionBase64String);
-
- if (!session->startProcessingTransaction(transactionBase64String, auto_sign)) {
- if (auto_sign) {
- auto errorJson = session->getErrorsArray();
- result->set("state", "error");
- result->set("msg", "error processing transaction");
- result->set("details", errorJson);
- return result;
- }
- auto lastError = session->getLastError();
- if (lastError) delete lastError;
- result->set("state", "error");
- result->set("msg", "already enlisted");
- return result;
- }
-
- } else {
- Poco::DynamicStruct ds = *paramJsonObject;
- int alreadyEnlisted = 0;
-
- for (int i = 0; i < ds["transaction_base64"].size(); i++) {
- ds["transaction_base64"][i].convert(transactionBase64String);
- if (!session->startProcessingTransaction(transactionBase64String, auto_sign)) {
- auto lastError = session->getLastError();
- if (lastError) delete lastError;
- alreadyEnlisted++;
- }
- }
-
- if (alreadyEnlisted > 0) {
- result->set("state", "warning");
- result->set("msg", std::to_string(alreadyEnlisted) + " already enlisted");
- return result;
- }
- }
-
-
-
- result->set("state", "success");
- return result;
- }
-
- }
- catch (Poco::Exception& ex) {
- printf("[JsonTransaction::handle] try to use params as jsonObject: %s\n", ex.displayText().data());
- result->set("state", "error");
- result->set("msg", "json exception");
- result->set("details", ex.displayText());
- return result;
- }
- }
- else if (params.isVector()) {
- const Poco::URI::QueryParameters queryParams = params.extract();
- auto transactionIT = queryParams.begin();
- for (auto it = queryParams.begin(); it != queryParams.end(); it++) {
- if (it->first == "session_id") {
- session_id = stoi(it->second);
- //break;
- }
- else if (it->first == "transaction_base64") {
- transactionIT = it;
- }
- }
- if (session_id) {
- auto sm = SessionManager::getInstance();
- auto session = sm->getSession(session_id);
- if (!session) {
- result->set("state", "error");
- result->set("msg", "session not found");
- return result;
- }
- if (!session->startProcessingTransaction(transactionIT->second)) {
- auto lastError = session->getLastError();
- if (lastError) delete lastError;
- result->set("state", "error");
- result->set("msg", "already enlisted");
- return result;
- }
- result->set("state", "success");
- return result;
- }
- else {
- result->set("state", "error");
- result->set("msg", "session id not set");
- return result;
- }
- }
- else if (params.isStruct()) {
- result->set("state", "error");
- result->set("msg", "struct not implemented yet");
- }
- else if (params.isArray()) {
- result->set("state", "error");
- result->set("msg", "array not implemented yet");
- }
- else if (params.isList()) {
- result->set("state", "error");
- result->set("msg", "list not implemented yet");
- }
- else if (params.isString()) {
- result->set("state", "error");
- result->set("msg", "string not implemented yet");
- }
- else if (params.isDeque()) {
- result->set("state", "error");
- result->set("msg", "deque not implemented yet");
- }
- else {
-
- result->set("state", "error");
- result->set("msg", "format not implemented");
- result->set("details", std::string(params.type().name()));
- }
-
- return result;
-}
-
+#include "JsonTransaction.h"
+#include "Poco/URI.h"
+#include "Poco/Dynamic/Struct.h"
+
+#include "../SingletonManager/SessionManager.h"
+#include "../ServerConfig.h"
+
+Poco::JSON::Object* JsonTransaction::handle(Poco::Dynamic::Var params)
+{
+ Poco::JSON::Object* result = new Poco::JSON::Object;
+ int session_id = 0;
+
+ // if is json object
+ if (params.type() == typeid(Poco::JSON::Object::Ptr)) {
+ Poco::JSON::Object::Ptr paramJsonObject = params.extract();
+
+ try {
+ /// Throws a RangeException if the value does not fit
+ /// into the result variable.
+ /// Throws a NotImplementedException if conversion is
+ /// not available for the given type.
+ /// Throws InvalidAccessException if Var is empty.
+ paramJsonObject->get("session_id").convert(session_id);
+ auto sm = SessionManager::getInstance();
+ if (session_id != 0) {
+ auto session = sm->getSession(session_id);
+ if (!session) {
+ result->set("state", "error");
+ result->set("msg", "session not found");
+ return result;
+ }
+
+ int balance = 0;
+
+ if (!paramJsonObject->isNull("balance")) {
+ paramJsonObject->get("balance").convert(balance);
+ if (balance) {
+ auto u = session->getUser();
+ if (u) {
+ u->setBalance(balance);
+ }
+ auto nu = session->getNewUser();
+ if (!nu.isNull()) {
+ nu->setBalance(balance);
+ }
+ }
+ }
+
+ std::string transactionBase64String;
+ Poco::Dynamic::Var transaction_base64 = paramJsonObject->get("transaction_base64");
+ bool auto_sign = false;
+ auto auto_sign_json = paramJsonObject->get("auto_sign");
+ if (!auto_sign_json.isEmpty()) {
+ auto_sign_json.convert(auto_sign);
+ }
+
+ if (transaction_base64.isString()) {
+ paramJsonObject->get("transaction_base64").convert(transactionBase64String);
+
+ if (!session->startProcessingTransaction(transactionBase64String, auto_sign)) {
+ if (auto_sign) {
+ auto errorJson = session->getErrorsArray();
+ result->set("state", "error");
+ result->set("msg", "error processing transaction");
+ result->set("details", errorJson);
+ return result;
+ }
+ auto lastError = session->getLastError();
+ if (lastError) delete lastError;
+ result->set("state", "error");
+ result->set("msg", "already enlisted");
+ return result;
+ }
+
+ } else {
+ Poco::DynamicStruct ds = *paramJsonObject;
+ int alreadyEnlisted = 0;
+
+ for (int i = 0; i < ds["transaction_base64"].size(); i++) {
+ ds["transaction_base64"][i].convert(transactionBase64String);
+ if (!session->startProcessingTransaction(transactionBase64String, auto_sign)) {
+ auto lastError = session->getLastError();
+ if (lastError) delete lastError;
+ alreadyEnlisted++;
+ }
+ }
+
+ if (alreadyEnlisted > 0) {
+ result->set("state", "warning");
+ result->set("msg", std::to_string(alreadyEnlisted) + " already enlisted");
+ return result;
+ }
+ }
+
+
+
+ result->set("state", "success");
+ return result;
+ }
+
+ }
+ catch (Poco::Exception& ex) {
+ printf("[JsonTransaction::handle] try to use params as jsonObject: %s\n", ex.displayText().data());
+ result->set("state", "error");
+ result->set("msg", "json exception");
+ result->set("details", ex.displayText());
+ return result;
+ }
+ }
+ else if (params.isVector()) {
+ const Poco::URI::QueryParameters queryParams = params.extract();
+ auto transactionIT = queryParams.begin();
+ for (auto it = queryParams.begin(); it != queryParams.end(); it++) {
+ if (it->first == "session_id") {
+ session_id = stoi(it->second);
+ //break;
+ }
+ else if (it->first == "transaction_base64") {
+ transactionIT = it;
+ }
+ }
+ if (session_id) {
+ auto sm = SessionManager::getInstance();
+ auto session = sm->getSession(session_id);
+ if (!session) {
+ result->set("state", "error");
+ result->set("msg", "session not found");
+ return result;
+ }
+ if (!session->startProcessingTransaction(transactionIT->second)) {
+ auto lastError = session->getLastError();
+ if (lastError) delete lastError;
+ result->set("state", "error");
+ result->set("msg", "already enlisted");
+ return result;
+ }
+ result->set("state", "success");
+ return result;
+ }
+ else {
+ result->set("state", "error");
+ result->set("msg", "session id not set");
+ return result;
+ }
+ }
+ else if (params.isStruct()) {
+ result->set("state", "error");
+ result->set("msg", "struct not implemented yet");
+ }
+ else if (params.isArray()) {
+ result->set("state", "error");
+ result->set("msg", "array not implemented yet");
+ }
+ else if (params.isList()) {
+ result->set("state", "error");
+ result->set("msg", "list not implemented yet");
+ }
+ else if (params.isString()) {
+ result->set("state", "error");
+ result->set("msg", "string not implemented yet");
+ }
+ else if (params.isDeque()) {
+ result->set("state", "error");
+ result->set("msg", "deque not implemented yet");
+ }
+ else {
+
+ result->set("state", "error");
+ result->set("msg", "format not implemented");
+ result->set("details", std::string(params.type().name()));
+ }
+
+ return result;
+}
+
diff --git a/login_server/src/cpp/JSONInterface/JsonTransaction.h b/login_server/src/cpp/JSONInterface/JsonTransaction.h
index 7d842fac0..95e9885be 100644
--- a/login_server/src/cpp/JSONInterface/JsonTransaction.h
+++ b/login_server/src/cpp/JSONInterface/JsonTransaction.h
@@ -1,19 +1,19 @@
-#ifndef __JSON_INTERFACE_JSON_TRANSACTION_
-#define __JSON_INTERFACE_JSON_TRANSACTION_
-
-#include "JsonRequestHandler.h"
-
-class Session;
-
-class JsonTransaction : public JsonRequestHandler
-{
-public:
- Poco::JSON::Object* handle(Poco::Dynamic::Var params);
-
-protected:
- bool startProcessingTransaction(Session* session, const std::string& transactionBase64);
-
-
-};
-
+#ifndef __JSON_INTERFACE_JSON_TRANSACTION_
+#define __JSON_INTERFACE_JSON_TRANSACTION_
+
+#include "JsonRequestHandler.h"
+
+class Session;
+
+class JsonTransaction : public JsonRequestHandler
+{
+public:
+ Poco::JSON::Object* handle(Poco::Dynamic::Var params);
+
+protected:
+ bool startProcessingTransaction(Session* session, const std::string& transactionBase64);
+
+
+};
+
#endif // __JSON_INTERFACE_JSON_TRANSACTION_
\ No newline at end of file
diff --git a/login_server/src/cpp/ServerConfig.cpp b/login_server/src/cpp/ServerConfig.cpp
index f319ea460..d37480a80 100644
--- a/login_server/src/cpp/ServerConfig.cpp
+++ b/login_server/src/cpp/ServerConfig.cpp
@@ -1,349 +1,349 @@
-#include "ServerConfig.h"
-#include "Crypto/mnemonic_german.h"
-#include "Crypto/mnemonic_german2.h"
-#include "Crypto/mnemonic_bip0039.h"
-#include "Crypto/DRRandom.h"
-#include "lib/DataTypeConverter.h"
-#include "sodium.h"
-
-
-#include "Poco/Net/SSLManager.h"
-#include "Poco/Net/KeyConsoleHandler.h"
-#include "Poco/Net/RejectCertificateHandler.h"
-#include "Poco/Net/DNS.h"
-#include "Poco/SharedPtr.h"
-
-#include "Poco/Mutex.h"
-#include "Poco/Path.h"
-#include "Poco/FileStream.h"
-#include "Poco/LocalDateTime.h"
-#include "Poco/DateTimeFormat.h"
-#include "Poco/DateTimeFormatter.h"
-
-
-using Poco::Net::SSLManager;
-using Poco::Net::Context;
-using Poco::Net::KeyConsoleHandler;
-using Poco::Net::PrivateKeyPassphraseHandler;
-using Poco::Net::InvalidCertificateHandler;
-using Poco::Net::RejectCertificateHandler;
-using Poco::SharedPtr;
-
-namespace ServerConfig {
-
-#define SESSION_TIMEOUT_DEFAULT 10
-
- Mnemonic g_Mnemonic_WordLists[MNEMONIC_MAX];
- ObfusArray* g_ServerCryptoKey = nullptr;
- ObfusArray* g_ServerKeySeed = nullptr;
-// std::string g_ServerAdminPublic;
- UniLib::controller::CPUSheduler* g_CPUScheduler = nullptr;
- UniLib::controller::CPUSheduler* g_CryptoCPUScheduler = nullptr;
- Context::Ptr g_SSL_CLient_Context = nullptr;
- Poco::Util::Timer g_CronJobsTimer;
- EmailAccount g_EmailAccount;
- int g_SessionTimeout = SESSION_TIMEOUT_DEFAULT;
- std::string g_serverPath;
- int g_serverPort = 0;
- Languages g_default_locale;
- std::string g_php_serverPath;
- std::string g_php_serverHost;
- int g_phpServerPort;
- Poco::Mutex g_TimeMutex;
- int g_FakeLoginSleepTime = 820;
- std::string g_versionString = "";
- bool g_disableEmail = false;
- ServerSetupType g_ServerSetupType = SERVER_TYPE_PRODUCTION;
- std::string g_gRPCRelayServerFullURL;
- MemoryBin* g_CryptoAppSecret = nullptr;
- AllowUnsecure g_AllowUnsecureFlags = NOT_UNSECURE;
-
-#ifdef __linux__
-#include
-#include
-#include
-#include
-#include
-#include
-#endif //#ifdef __linux__
-
- std::string getHostIpString()
- {
-#ifdef __linux__
- struct ifaddrs * ifAddrStruct = NULL;
- struct ifaddrs * ifa = NULL;
- void * tmpAddrPtr = NULL;
-
- getifaddrs(&ifAddrStruct);
- std::string ipAddressString;
-
- for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
- if (!ifa->ifa_addr) {
- continue;
- }
- if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4
- // is a valid IP4 Address
- tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
- char addressBuffer[INET_ADDRSTRLEN];
- inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
- ipAddressString = addressBuffer;
- printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer);
- }
- else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6
- // is a valid IP6 Address
- tmpAddrPtr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
- char addressBuffer[INET6_ADDRSTRLEN];
- inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
- printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer);
- }
- }
- if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
- return ipAddressString;
-#else //__linux__
- std::string ipAddressString = "";
- auto host = Poco::Net::DNS::thisHost();
- for (auto it = host.addresses().begin(); it != host.addresses().end(); it++) {
- auto ipAddress = *it;
- if (!ipAddress.isIPv4Compatible() && !ipAddress.isIPv4Mapped()) {
- continue;
- }
- if (ipAddress.isLoopback()) {
- continue;
- }
- ipAddressString = ipAddress.toString();
- //isIPv4Compatible
- //!isLoopback
- //printf("ipaddress: %s\n", ipAddressString.data());
- break;
- //break;
- }
- return ipAddressString;
-#endif // __linux__
- }
-
- bool replaceZeroIPWithLocalhostIP(std::string& url)
- {
- auto pos = url.find("0.0.0.0", 0);
- if (pos != std::string::npos) {
- std::string ipAddressString = getHostIpString();
- if ("" != ipAddressString) {
- url.replace(pos, 7, ipAddressString);
- }
- }
-
- //printf("ipaddress: %s\n", ipAddress.data());
-
- return true;
- }
-
- ServerSetupType getServerSetupTypeFromString(const std::string& serverSetupTypeString) {
- if ("test" == serverSetupTypeString) {
- return SERVER_TYPE_TEST;
- }
- if ("staging" == serverSetupTypeString) {
- return SERVER_TYPE_STAGING;
- }
- if ("production" == serverSetupTypeString) {
- return SERVER_TYPE_PRODUCTION;
- }
- return SERVER_TYPE_PRODUCTION;
- }
-
-
- bool loadMnemonicWordLists()
- {
- for (int i = 0; i < MNEMONIC_MAX; i++) {
- int iResult = 0;
- switch (i) {
- case MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER:
- iResult = g_Mnemonic_WordLists[i].init(populate_mnemonic_german, g_mnemonic_german_original_size, g_mnemonic_german_compressed_size);
- if (iResult) {
- printf("[%s] error init german mnemonic set, error nr: %d\n", __FUNCTION__, iResult);
- return false;
- }
- g_Mnemonic_WordLists[i].printToFile("de_words.txt");
- break;
- case MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER_FIXED_CASES:
- iResult = g_Mnemonic_WordLists[i].init(populate_mnemonic_german2, g_mnemonic_german2_original_size, g_mnemonic_german2_compressed_size);
- if (iResult) {
- printf("[%s] error init german mnemonic set 2, error nr: %d\n", __FUNCTION__, iResult);
- return false;
- }
- g_Mnemonic_WordLists[i].printToFile("de_words2.txt");
- break;
- case MNEMONIC_BIP0039_SORTED_ORDER:
- iResult = g_Mnemonic_WordLists[i].init(populate_mnemonic_bip0039, g_mnemonic_bip0039_original_size, g_mnemonic_bip0039_compressed_size);
- if (iResult) {
- printf("[%s] error init bip0039 mnemonic set, error nr: %d\n", __FUNCTION__, iResult);
- return false;
- }
- //g_Mnemonic_WordLists[i].printToFile("en_words.txt");
- break;
- default: printf("[%s] unknown MnemonicType\n", __FUNCTION__); return false;
- }
- }
- return true;
- }
-
- bool initServerCrypto(const Poco::Util::LayeredConfiguration& cfg)
- {
- auto serverKey = cfg.getString("crypto.server_key");
- unsigned char key[crypto_shorthash_KEYBYTES];
- size_t realBinSize = 0;
- NULLPAD_10;
- if (sodium_hex2bin(key, crypto_shorthash_KEYBYTES, serverKey.data(), serverKey.size(), nullptr, &realBinSize, nullptr)) {
- printf("[%s] serverKey isn't valid hex: %s\n", __FUNCTION__, serverKey.data());
- return false;
- }
- if (realBinSize != crypto_shorthash_KEYBYTES) {
- printf("[%s] serverKey hasn't valid size, expecting: %u, get: %lu\n",
- __FUNCTION__, crypto_shorthash_KEYBYTES, realBinSize);
- return false;
- }
- g_ServerCryptoKey = new ObfusArray(realBinSize, key);
- g_ServerKeySeed = new ObfusArray(9*8);
- Poco::Int64 i1 = randombytes_random();
- Poco::Int64 i2 = randombytes_random();
- g_ServerKeySeed->put(0, i1 | (i2 << 8));
-
- //g_ServerAdminPublic = cfg.getString("crypto.server_admin_public");
-
- DISASM_FALSERET;
- g_SessionTimeout = cfg.getInt("session.timeout", SESSION_TIMEOUT_DEFAULT);
- g_serverPath = cfg.getString("loginServer.path", "");
- replaceZeroIPWithLocalhostIP(g_serverPath);
- g_default_locale = LanguageManager::languageFromString(cfg.getString("loginServer.default_locale"));
- g_serverPort = cfg.getInt("loginServer.port", 0);
- g_phpServerPort = cfg.getInt("phpServer.port", 0);
- // replace 0.0.0.0 with actual server ip
-
- g_php_serverPath = cfg.getString("phpServer.url", "");
- replaceZeroIPWithLocalhostIP(g_php_serverPath);
- g_php_serverHost = cfg.getString("phpServer.host", "");
- replaceZeroIPWithLocalhostIP(g_php_serverHost);
- //g_ServerSetupType
- auto serverSetupTypeString = cfg.getString("ServerSetupType", "");
- g_ServerSetupType = getServerSetupTypeFromString(serverSetupTypeString);
-
- // app secret for encrypt user private keys
- // TODO: encrypt with server admin key
- auto app_secret_string = cfg.getString("crypto.app_secret", "");
- if ("" != app_secret_string) {
- g_CryptoAppSecret = DataTypeConverter::hexToBin(app_secret_string);
- }
- //g_CryptoAppSecret
-
- g_gRPCRelayServerFullURL = cfg.getString("grpc.server", "");
-
- // unsecure flags
- //g_AllowUnsecureFlags
- if (cfg.getInt("unsecure.allow_passwort_via_json_request", 0) == 1) {
- g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_PASSWORD_REQUESTS);
- }
- if (cfg.getInt("unsecure.allow_auto_sign_transactions", 0) == 1) {
- g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_AUTO_SIGN_TRANSACTIONS);
- }
- if (cfg.getInt("unsecure.allow_cors_all", 0) == 1) {
- g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_CORS_ALL);
- }
- if (cfg.getInt("unsecure.allow_all_passwords", 0) == 1) {
- g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_ALLOW_ALL_PASSWORDS);
- }
-
- return true;
- }
-
- bool initEMailAccount(const Poco::Util::LayeredConfiguration& cfg)
- {
- g_disableEmail = cfg.getBool("email.disable", false);
- if (g_disableEmail) {
- printf("Email is disabled!\n");
- }
- else {
- g_EmailAccount.sender = cfg.getString("email.sender");
- g_EmailAccount.username = cfg.getString("email.username");
- g_EmailAccount.password = cfg.getString("email.password");
- g_EmailAccount.url = cfg.getString("email.smtp.url");
- g_EmailAccount.port = cfg.getInt("email.smtp.port");
- }
- DISASM_FALSERET;
- //g_ServerKeySeed->put(3, DRRandom::r64());
- return true;
- }
-
- bool initSSLClientContext()
- {
- SharedPtr pCert = new RejectCertificateHandler(false); // reject invalid certificates
- /*
- Context(Usage usage,
- const std::string& certificateNameOrPath,
- VerificationMode verMode = VERIFY_RELAXED,
- int options = OPT_DEFAULTS,
- const std::string& certificateStoreName = CERT_STORE_MY);
- */
- try {
-#ifdef POCO_NETSSL_WIN
- g_SSL_CLient_Context = new Context(Context::CLIENT_USE, "cacert.pem", Context::VERIFY_RELAXED, Context::OPT_DEFAULTS);
-#else
-
- g_SSL_CLient_Context = new Context(Context::CLIENT_USE, "", "", Poco::Path::config() + "grd_login/cacert.pem", Context::VERIFY_RELAXED, 9, true, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
-#endif
- } catch(Poco::Exception& ex) {
- printf("[ServerConfig::initSSLClientContext] error init ssl context, maybe no cacert.pem found?\nPlease make sure you have cacert.pem (CA/root certificates) next to binary from https://curl.haxx.se/docs/caextract.html\n");
- return false;
- }
- DISASM_FALSERET;
- SSLManager::instance().initializeClient(0, pCert, g_SSL_CLient_Context);
-
- g_ServerKeySeed->put(5, DRRandom::r64());
-
- return true;
- }
-
- void unload() {
- if (g_ServerCryptoKey) {
- delete g_ServerCryptoKey;
- }
- if (g_ServerKeySeed) {
- delete g_ServerKeySeed;
- }
- if (g_CPUScheduler) {
- delete g_CPUScheduler;
- }
-
- if (g_CryptoCPUScheduler) {
- delete g_CryptoCPUScheduler;
- }
- if (g_CryptoAppSecret) {
- MemoryManager::getInstance()->releaseMemory(g_CryptoAppSecret);
- g_CryptoAppSecret = nullptr;
- }
- }
-
- void writeToFile(std::istream& datas, std::string fileName)
- {
- static Poco::Mutex mutex;
-
- mutex.lock();
-
- Poco::FileOutputStream file(fileName, std::ios::out | std::ios::app);
-
- if (!file.good()) {
- printf("[ServerConfig::writeToFile] error creating file with name: %s\n", fileName.data());
- mutex.unlock();
- return;
- }
-
- Poco::LocalDateTime now;
-
- std::string dateTimeStr = Poco::DateTimeFormatter::format(now, Poco::DateTimeFormat::ISO8601_FORMAT);
- file << dateTimeStr << std::endl;
-
- for (std::string line; std::getline(datas, line); ) {
- file << line << std::endl;
- }
- file << std::endl;
- file.close();
- mutex.unlock();
- }
+#include "ServerConfig.h"
+#include "Crypto/mnemonic_german.h"
+#include "Crypto/mnemonic_german2.h"
+#include "Crypto/mnemonic_bip0039.h"
+#include "Crypto/DRRandom.h"
+#include "lib/DataTypeConverter.h"
+#include "sodium.h"
+
+
+#include "Poco/Net/SSLManager.h"
+#include "Poco/Net/KeyConsoleHandler.h"
+#include "Poco/Net/RejectCertificateHandler.h"
+#include "Poco/Net/DNS.h"
+#include "Poco/SharedPtr.h"
+
+#include "Poco/Mutex.h"
+#include "Poco/Path.h"
+#include "Poco/FileStream.h"
+#include "Poco/LocalDateTime.h"
+#include "Poco/DateTimeFormat.h"
+#include "Poco/DateTimeFormatter.h"
+
+
+using Poco::Net::SSLManager;
+using Poco::Net::Context;
+using Poco::Net::KeyConsoleHandler;
+using Poco::Net::PrivateKeyPassphraseHandler;
+using Poco::Net::InvalidCertificateHandler;
+using Poco::Net::RejectCertificateHandler;
+using Poco::SharedPtr;
+
+namespace ServerConfig {
+
+#define SESSION_TIMEOUT_DEFAULT 10
+
+ Mnemonic g_Mnemonic_WordLists[MNEMONIC_MAX];
+ ObfusArray* g_ServerCryptoKey = nullptr;
+ ObfusArray* g_ServerKeySeed = nullptr;
+// std::string g_ServerAdminPublic;
+ UniLib::controller::CPUSheduler* g_CPUScheduler = nullptr;
+ UniLib::controller::CPUSheduler* g_CryptoCPUScheduler = nullptr;
+ Context::Ptr g_SSL_CLient_Context = nullptr;
+ Poco::Util::Timer g_CronJobsTimer;
+ EmailAccount g_EmailAccount;
+ int g_SessionTimeout = SESSION_TIMEOUT_DEFAULT;
+ std::string g_serverPath;
+ int g_serverPort = 0;
+ Languages g_default_locale;
+ std::string g_php_serverPath;
+ std::string g_php_serverHost;
+ int g_phpServerPort;
+ Poco::Mutex g_TimeMutex;
+ int g_FakeLoginSleepTime = 820;
+ std::string g_versionString = "";
+ bool g_disableEmail = false;
+ ServerSetupType g_ServerSetupType = SERVER_TYPE_PRODUCTION;
+ std::string g_gRPCRelayServerFullURL;
+ MemoryBin* g_CryptoAppSecret = nullptr;
+ AllowUnsecure g_AllowUnsecureFlags = NOT_UNSECURE;
+
+#ifdef __linux__
+#include
+#include
+#include
+#include
+#include
+#include
+#endif //#ifdef __linux__
+
+ std::string getHostIpString()
+ {
+#ifdef __linux__
+ struct ifaddrs * ifAddrStruct = NULL;
+ struct ifaddrs * ifa = NULL;
+ void * tmpAddrPtr = NULL;
+
+ getifaddrs(&ifAddrStruct);
+ std::string ipAddressString;
+
+ for (ifa = ifAddrStruct; ifa != NULL; ifa = ifa->ifa_next) {
+ if (!ifa->ifa_addr) {
+ continue;
+ }
+ if (ifa->ifa_addr->sa_family == AF_INET) { // check it is IP4
+ // is a valid IP4 Address
+ tmpAddrPtr = &((struct sockaddr_in *)ifa->ifa_addr)->sin_addr;
+ char addressBuffer[INET_ADDRSTRLEN];
+ inet_ntop(AF_INET, tmpAddrPtr, addressBuffer, INET_ADDRSTRLEN);
+ ipAddressString = addressBuffer;
+ printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer);
+ }
+ else if (ifa->ifa_addr->sa_family == AF_INET6) { // check it is IP6
+ // is a valid IP6 Address
+ tmpAddrPtr = &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr;
+ char addressBuffer[INET6_ADDRSTRLEN];
+ inet_ntop(AF_INET6, tmpAddrPtr, addressBuffer, INET6_ADDRSTRLEN);
+ printf("%s IP Address %s\n", ifa->ifa_name, addressBuffer);
+ }
+ }
+ if (ifAddrStruct != NULL) freeifaddrs(ifAddrStruct);
+ return ipAddressString;
+#else //__linux__
+ std::string ipAddressString = "";
+ auto host = Poco::Net::DNS::thisHost();
+ for (auto it = host.addresses().begin(); it != host.addresses().end(); it++) {
+ auto ipAddress = *it;
+ if (!ipAddress.isIPv4Compatible() && !ipAddress.isIPv4Mapped()) {
+ continue;
+ }
+ if (ipAddress.isLoopback()) {
+ continue;
+ }
+ ipAddressString = ipAddress.toString();
+ //isIPv4Compatible
+ //!isLoopback
+ //printf("ipaddress: %s\n", ipAddressString.data());
+ break;
+ //break;
+ }
+ return ipAddressString;
+#endif // __linux__
+ }
+
+ bool replaceZeroIPWithLocalhostIP(std::string& url)
+ {
+ auto pos = url.find("0.0.0.0", 0);
+ if (pos != std::string::npos) {
+ std::string ipAddressString = getHostIpString();
+ if ("" != ipAddressString) {
+ url.replace(pos, 7, ipAddressString);
+ }
+ }
+
+ //printf("ipaddress: %s\n", ipAddress.data());
+
+ return true;
+ }
+
+ ServerSetupType getServerSetupTypeFromString(const std::string& serverSetupTypeString) {
+ if ("test" == serverSetupTypeString) {
+ return SERVER_TYPE_TEST;
+ }
+ if ("staging" == serverSetupTypeString) {
+ return SERVER_TYPE_STAGING;
+ }
+ if ("production" == serverSetupTypeString) {
+ return SERVER_TYPE_PRODUCTION;
+ }
+ return SERVER_TYPE_PRODUCTION;
+ }
+
+
+ bool loadMnemonicWordLists()
+ {
+ for (int i = 0; i < MNEMONIC_MAX; i++) {
+ int iResult = 0;
+ switch (i) {
+ case MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER:
+ iResult = g_Mnemonic_WordLists[i].init(populate_mnemonic_german, g_mnemonic_german_original_size, g_mnemonic_german_compressed_size);
+ if (iResult) {
+ printf("[%s] error init german mnemonic set, error nr: %d\n", __FUNCTION__, iResult);
+ return false;
+ }
+ g_Mnemonic_WordLists[i].printToFile("de_words.txt");
+ break;
+ case MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER_FIXED_CASES:
+ iResult = g_Mnemonic_WordLists[i].init(populate_mnemonic_german2, g_mnemonic_german2_original_size, g_mnemonic_german2_compressed_size);
+ if (iResult) {
+ printf("[%s] error init german mnemonic set 2, error nr: %d\n", __FUNCTION__, iResult);
+ return false;
+ }
+ g_Mnemonic_WordLists[i].printToFile("de_words2.txt");
+ break;
+ case MNEMONIC_BIP0039_SORTED_ORDER:
+ iResult = g_Mnemonic_WordLists[i].init(populate_mnemonic_bip0039, g_mnemonic_bip0039_original_size, g_mnemonic_bip0039_compressed_size);
+ if (iResult) {
+ printf("[%s] error init bip0039 mnemonic set, error nr: %d\n", __FUNCTION__, iResult);
+ return false;
+ }
+ //g_Mnemonic_WordLists[i].printToFile("en_words.txt");
+ break;
+ default: printf("[%s] unknown MnemonicType\n", __FUNCTION__); return false;
+ }
+ }
+ return true;
+ }
+
+ bool initServerCrypto(const Poco::Util::LayeredConfiguration& cfg)
+ {
+ auto serverKey = cfg.getString("crypto.server_key");
+ unsigned char key[crypto_shorthash_KEYBYTES];
+ size_t realBinSize = 0;
+ NULLPAD_10;
+ if (sodium_hex2bin(key, crypto_shorthash_KEYBYTES, serverKey.data(), serverKey.size(), nullptr, &realBinSize, nullptr)) {
+ printf("[%s] serverKey isn't valid hex: %s\n", __FUNCTION__, serverKey.data());
+ return false;
+ }
+ if (realBinSize != crypto_shorthash_KEYBYTES) {
+ printf("[%s] serverKey hasn't valid size, expecting: %u, get: %lu\n",
+ __FUNCTION__, crypto_shorthash_KEYBYTES, realBinSize);
+ return false;
+ }
+ g_ServerCryptoKey = new ObfusArray(realBinSize, key);
+ g_ServerKeySeed = new ObfusArray(9*8);
+ Poco::Int64 i1 = randombytes_random();
+ Poco::Int64 i2 = randombytes_random();
+ g_ServerKeySeed->put(0, i1 | (i2 << 8));
+
+ //g_ServerAdminPublic = cfg.getString("crypto.server_admin_public");
+
+ DISASM_FALSERET;
+ g_SessionTimeout = cfg.getInt("session.timeout", SESSION_TIMEOUT_DEFAULT);
+ g_serverPath = cfg.getString("loginServer.path", "");
+ replaceZeroIPWithLocalhostIP(g_serverPath);
+ g_default_locale = LanguageManager::languageFromString(cfg.getString("loginServer.default_locale"));
+ g_serverPort = cfg.getInt("loginServer.port", 0);
+ g_phpServerPort = cfg.getInt("phpServer.port", 0);
+ // replace 0.0.0.0 with actual server ip
+
+ g_php_serverPath = cfg.getString("phpServer.url", "");
+ replaceZeroIPWithLocalhostIP(g_php_serverPath);
+ g_php_serverHost = cfg.getString("phpServer.host", "");
+ replaceZeroIPWithLocalhostIP(g_php_serverHost);
+ //g_ServerSetupType
+ auto serverSetupTypeString = cfg.getString("ServerSetupType", "");
+ g_ServerSetupType = getServerSetupTypeFromString(serverSetupTypeString);
+
+ // app secret for encrypt user private keys
+ // TODO: encrypt with server admin key
+ auto app_secret_string = cfg.getString("crypto.app_secret", "");
+ if ("" != app_secret_string) {
+ g_CryptoAppSecret = DataTypeConverter::hexToBin(app_secret_string);
+ }
+ //g_CryptoAppSecret
+
+ g_gRPCRelayServerFullURL = cfg.getString("grpc.server", "");
+
+ // unsecure flags
+ //g_AllowUnsecureFlags
+ if (cfg.getInt("unsecure.allow_passwort_via_json_request", 0) == 1) {
+ g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_PASSWORD_REQUESTS);
+ }
+ if (cfg.getInt("unsecure.allow_auto_sign_transactions", 0) == 1) {
+ g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_AUTO_SIGN_TRANSACTIONS);
+ }
+ if (cfg.getInt("unsecure.allow_cors_all", 0) == 1) {
+ g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_CORS_ALL);
+ }
+ if (cfg.getInt("unsecure.allow_all_passwords", 0) == 1) {
+ g_AllowUnsecureFlags = (AllowUnsecure)(g_AllowUnsecureFlags | UNSECURE_ALLOW_ALL_PASSWORDS);
+ }
+
+ return true;
+ }
+
+ bool initEMailAccount(const Poco::Util::LayeredConfiguration& cfg)
+ {
+ g_disableEmail = cfg.getBool("email.disable", false);
+ if (g_disableEmail) {
+ printf("Email is disabled!\n");
+ }
+ else {
+ g_EmailAccount.sender = cfg.getString("email.sender");
+ g_EmailAccount.username = cfg.getString("email.username");
+ g_EmailAccount.password = cfg.getString("email.password");
+ g_EmailAccount.url = cfg.getString("email.smtp.url");
+ g_EmailAccount.port = cfg.getInt("email.smtp.port");
+ }
+ DISASM_FALSERET;
+ //g_ServerKeySeed->put(3, DRRandom::r64());
+ return true;
+ }
+
+ bool initSSLClientContext()
+ {
+ SharedPtr pCert = new RejectCertificateHandler(false); // reject invalid certificates
+ /*
+ Context(Usage usage,
+ const std::string& certificateNameOrPath,
+ VerificationMode verMode = VERIFY_RELAXED,
+ int options = OPT_DEFAULTS,
+ const std::string& certificateStoreName = CERT_STORE_MY);
+ */
+ try {
+#ifdef POCO_NETSSL_WIN
+ g_SSL_CLient_Context = new Context(Context::CLIENT_USE, "cacert.pem", Context::VERIFY_RELAXED, Context::OPT_DEFAULTS);
+#else
+
+ g_SSL_CLient_Context = new Context(Context::CLIENT_USE, "", "", Poco::Path::config() + "grd_login/cacert.pem", Context::VERIFY_RELAXED, 9, true, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH");
+#endif
+ } catch(Poco::Exception& ex) {
+ printf("[ServerConfig::initSSLClientContext] error init ssl context, maybe no cacert.pem found?\nPlease make sure you have cacert.pem (CA/root certificates) next to binary from https://curl.haxx.se/docs/caextract.html\n");
+ return false;
+ }
+ DISASM_FALSERET;
+ SSLManager::instance().initializeClient(0, pCert, g_SSL_CLient_Context);
+
+ g_ServerKeySeed->put(5, DRRandom::r64());
+
+ return true;
+ }
+
+ void unload() {
+ if (g_ServerCryptoKey) {
+ delete g_ServerCryptoKey;
+ }
+ if (g_ServerKeySeed) {
+ delete g_ServerKeySeed;
+ }
+ if (g_CPUScheduler) {
+ delete g_CPUScheduler;
+ }
+
+ if (g_CryptoCPUScheduler) {
+ delete g_CryptoCPUScheduler;
+ }
+ if (g_CryptoAppSecret) {
+ MemoryManager::getInstance()->releaseMemory(g_CryptoAppSecret);
+ g_CryptoAppSecret = nullptr;
+ }
+ }
+
+ void writeToFile(std::istream& datas, std::string fileName)
+ {
+ static Poco::Mutex mutex;
+
+ mutex.lock();
+
+ Poco::FileOutputStream file(fileName, std::ios::out | std::ios::app);
+
+ if (!file.good()) {
+ printf("[ServerConfig::writeToFile] error creating file with name: %s\n", fileName.data());
+ mutex.unlock();
+ return;
+ }
+
+ Poco::LocalDateTime now;
+
+ std::string dateTimeStr = Poco::DateTimeFormatter::format(now, Poco::DateTimeFormat::ISO8601_FORMAT);
+ file << dateTimeStr << std::endl;
+
+ for (std::string line; std::getline(datas, line); ) {
+ file << line << std::endl;
+ }
+ file << std::endl;
+ file.close();
+ mutex.unlock();
+ }
}
\ No newline at end of file
diff --git a/login_server/src/cpp/ServerConfig.h b/login_server/src/cpp/ServerConfig.h
index aef4ea28d..444fb524c 100644
--- a/login_server/src/cpp/ServerConfig.h
+++ b/login_server/src/cpp/ServerConfig.h
@@ -1,90 +1,90 @@
-#ifndef __GRADIDO_LOGIN_SERVER_SERVER_CONFIG__
-#define __GRADIDO_LOGIN_SERVER_SERVER_CONFIG__
-
-#include "Crypto/mnemonic.h"
-#include "Crypto/Obfus_array.h"
-#include "Poco/Util/LayeredConfiguration.h"
-#include "Poco/Net/Context.h"
-#include "Poco/Types.h"
-#include "Poco/Util/Timer.h"
-
-#include "tasks/CPUSheduler.h"
-
-#include "SingletonManager/LanguageManager.h"
-#include "SingletonManager/MemoryManager.h"
-
-#define DISABLE_EMAIL
-
-namespace ServerConfig {
-
- enum Mnemonic_Types {
- MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER,
- MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER_FIXED_CASES,
- MNEMONIC_BIP0039_SORTED_ORDER,
- MNEMONIC_MAX
- };
- // depracted, moved to email manager
- struct EmailAccount {
- std::string sender;
- std::string admin_receiver;
- std::string username;
- std::string password;
- std::string url;
- int port;
- };
-
- enum ServerSetupType {
- SERVER_TYPE_TEST,
- SERVER_TYPE_STAGING,
- SERVER_TYPE_PRODUCTION
- };
-
- // used with bit-operators, so only use numbers with control exactly one bit (1,2,4,8,16...)
- enum AllowUnsecure {
- NOT_UNSECURE = 0,
- UNSECURE_PASSWORD_REQUESTS = 1,
- UNSECURE_AUTO_SIGN_TRANSACTIONS = 2,
- UNSECURE_CORS_ALL = 4,
- UNSECURE_ALLOW_ALL_PASSWORDS = 8
- };
-
-
- extern Mnemonic g_Mnemonic_WordLists[MNEMONIC_MAX];
-
- extern ObfusArray* g_ServerCryptoKey;
- extern ObfusArray* g_ServerKeySeed;
-
- //extern unsigned char g_ServerAdminPublic[];
- extern UniLib::controller::CPUSheduler* g_CPUScheduler;
- extern UniLib::controller::CPUSheduler* g_CryptoCPUScheduler;
- extern Poco::Net::Context::Ptr g_SSL_CLient_Context;
- extern Poco::Util::Timer g_CronJobsTimer;
- extern EmailAccount g_EmailAccount;
- extern int g_SessionTimeout;
- extern std::string g_serverPath;
- extern int g_serverPort;
- extern Languages g_default_locale;
- extern std::string g_php_serverPath;
- extern std::string g_php_serverHost;
- extern int g_phpServerPort;
- extern Poco::Mutex g_TimeMutex;
- extern int g_FakeLoginSleepTime;
- extern std::string g_versionString;
- extern bool g_disableEmail;
- extern ServerSetupType g_ServerSetupType;
- extern std::string g_gRPCRelayServerFullURL;
- extern MemoryBin* g_CryptoAppSecret;
- extern AllowUnsecure g_AllowUnsecureFlags;
-
- bool loadMnemonicWordLists();
- bool initServerCrypto(const Poco::Util::LayeredConfiguration& cfg);
- bool initEMailAccount(const Poco::Util::LayeredConfiguration& cfg);
- bool initSSLClientContext();
-
-
- void writeToFile(std::istream& datas, std::string fileName);
-
- void unload();
-};
-
+#ifndef __GRADIDO_LOGIN_SERVER_SERVER_CONFIG__
+#define __GRADIDO_LOGIN_SERVER_SERVER_CONFIG__
+
+#include "Crypto/mnemonic.h"
+#include "Crypto/Obfus_array.h"
+#include "Poco/Util/LayeredConfiguration.h"
+#include "Poco/Net/Context.h"
+#include "Poco/Types.h"
+#include "Poco/Util/Timer.h"
+
+#include "tasks/CPUSheduler.h"
+
+#include "SingletonManager/LanguageManager.h"
+#include "SingletonManager/MemoryManager.h"
+
+#define DISABLE_EMAIL
+
+namespace ServerConfig {
+
+ enum Mnemonic_Types {
+ MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER,
+ MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER_FIXED_CASES,
+ MNEMONIC_BIP0039_SORTED_ORDER,
+ MNEMONIC_MAX
+ };
+ // depracted, moved to email manager
+ struct EmailAccount {
+ std::string sender;
+ std::string admin_receiver;
+ std::string username;
+ std::string password;
+ std::string url;
+ int port;
+ };
+
+ enum ServerSetupType {
+ SERVER_TYPE_TEST,
+ SERVER_TYPE_STAGING,
+ SERVER_TYPE_PRODUCTION
+ };
+
+ // used with bit-operators, so only use numbers with control exactly one bit (1,2,4,8,16...)
+ enum AllowUnsecure {
+ NOT_UNSECURE = 0,
+ UNSECURE_PASSWORD_REQUESTS = 1,
+ UNSECURE_AUTO_SIGN_TRANSACTIONS = 2,
+ UNSECURE_CORS_ALL = 4,
+ UNSECURE_ALLOW_ALL_PASSWORDS = 8
+ };
+
+
+ extern Mnemonic g_Mnemonic_WordLists[MNEMONIC_MAX];
+
+ extern ObfusArray* g_ServerCryptoKey;
+ extern ObfusArray* g_ServerKeySeed;
+
+ //extern unsigned char g_ServerAdminPublic[];
+ extern UniLib::controller::CPUSheduler* g_CPUScheduler;
+ extern UniLib::controller::CPUSheduler* g_CryptoCPUScheduler;
+ extern Poco::Net::Context::Ptr g_SSL_CLient_Context;
+ extern Poco::Util::Timer g_CronJobsTimer;
+ extern EmailAccount g_EmailAccount;
+ extern int g_SessionTimeout;
+ extern std::string g_serverPath;
+ extern int g_serverPort;
+ extern Languages g_default_locale;
+ extern std::string g_php_serverPath;
+ extern std::string g_php_serverHost;
+ extern int g_phpServerPort;
+ extern Poco::Mutex g_TimeMutex;
+ extern int g_FakeLoginSleepTime;
+ extern std::string g_versionString;
+ extern bool g_disableEmail;
+ extern ServerSetupType g_ServerSetupType;
+ extern std::string g_gRPCRelayServerFullURL;
+ extern MemoryBin* g_CryptoAppSecret;
+ extern AllowUnsecure g_AllowUnsecureFlags;
+
+ bool loadMnemonicWordLists();
+ bool initServerCrypto(const Poco::Util::LayeredConfiguration& cfg);
+ bool initEMailAccount(const Poco::Util::LayeredConfiguration& cfg);
+ bool initSSLClientContext();
+
+
+ void writeToFile(std::istream& datas, std::string fileName);
+
+ void unload();
+};
+
#endif //__GRADIDO_LOGIN_SERVER_SERVER_CONFIG__
\ No newline at end of file
diff --git a/login_server/src/cpp/SingletonManager/SessionManager.cpp b/login_server/src/cpp/SingletonManager/SessionManager.cpp
index 743f11d19..e583c8eaf 100644
--- a/login_server/src/cpp/SingletonManager/SessionManager.cpp
+++ b/login_server/src/cpp/SingletonManager/SessionManager.cpp
@@ -1,626 +1,626 @@
-#include "SessionManager.h"
-#include "ErrorManager.h"
-#include "../ServerConfig.h"
-#include "../Crypto/DRRandom.h"
-#include "../controller/EmailVerificationCode.h"
-
-#include
-
-
-SessionManager* SessionManager::getInstance()
-{
- static SessionManager only;
- return &only;
-}
-
-SessionManager::SessionManager()
- : mInitalized(false), mDeadLockedSessionCount(0)
-{
-
-}
-
-SessionManager::~SessionManager()
-{
- if (mInitalized) {
- deinitalize();
- }
-}
-
-
-bool SessionManager::init()
-{
- try {
- //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
- mWorkingMutex.tryLock(500);
- }
- catch (Poco::TimeoutException &ex) {
- printf("[SessionManager::init] exception timout mutex: %s\n", ex.displayText().data());
- return false;
- }
- //mWorkingMutex.lock();
- int i;
- DISASM_MISALIGN;
- for (i = 0; i < VALIDATE_MAX; i++) {
- switch (i) {
- //case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("/^[a-zA-Z_ -]{3,}$/"); break;
- case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("^[^<>&;]{3,}$"); break;
- case VALIDATE_EMAIL: mValidations[i] = new Poco::RegularExpression("^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$"); break;
- case VALIDATE_PASSWORD: mValidations[i] = new Poco::RegularExpression("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[@$!%*?&+-_])[A-Za-z0-9@$!%*?&+-_]{8,}$"); break;
- case VALIDATE_PASSPHRASE: mValidations[i] = new Poco::RegularExpression("^(?:[a-z]* ){23}[a-z]*\s*$"); break;
- case VALIDATE_HAS_NUMBER: mValidations[i] = new Poco::RegularExpression(".*[0-9].*"); break;
- case VALIDATE_HAS_SPECIAL_CHARACTER: mValidations[i] = new Poco::RegularExpression(".*[@$!%*?&+-].*"); break;
- case VALIDATE_HAS_UPPERCASE_LETTER:
- mValidations[i] = new Poco::RegularExpression(".*[A-Z].*");
- ServerConfig::g_ServerKeySeed->put(i, DRRandom::r64());
- break;
- case VALIDATE_HAS_LOWERCASE_LETTER: mValidations[i] = new Poco::RegularExpression(".*[a-z].*"); break;
- default: printf("[SessionManager::%s] unknown validation type\n", __FUNCTION__);
- }
- }
-
-
- mInitalized = true;
- mWorkingMutex.unlock();
- return true;
-}
-
-void SessionManager::deinitalize()
-{
- try {
- //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
- mWorkingMutex.tryLock(500);
- }
- catch (Poco::TimeoutException &ex) {
- printf("[SessionManager::deinitalize] exception timout mutex: %s\n", ex.displayText().data());
- return;
- }
- //mWorkingMutex.lock();
-
- while (mEmptyRequestStack.size()) {
- mEmptyRequestStack.pop();
- }
-
- for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
- delete it->second;
- }
- mRequestSessionMap.clear();
-
- for (int i = 0; i < VALIDATE_MAX; i++) {
- delete mValidations[i];
- }
-
- printf("[SessionManager::deinitalize] count of dead locked sessions: %d\n", mDeadLockedSessionCount);
-
- mInitalized = false;
- mWorkingMutex.unlock();
-}
-
-bool SessionManager::isValid(const std::string& subject, SessionValidationTypes validationType)
-{
- if (validationType >= VALIDATE_MAX) {
- return false;
- }
- return *mValidations[validationType] == subject;
-}
-
-int SessionManager::generateNewUnusedHandle()
-{
- int newHandle = 0;
- int maxTrys = 0;
- do {
- newHandle = randombytes_random();
- maxTrys++;
- } while (mRequestSessionMap.find(newHandle) != mRequestSessionMap.end() && maxTrys < 100);
-
- if (maxTrys >= 100 || 0 == newHandle) {
- auto em = ErrorManager::getInstance();
- em->addError(new ParamError("SessionManager::generateNewUnusedHandle", "can't find new handle, have already ", std::to_string(mRequestSessionMap.size())));
- em->sendErrorsAsEmail();
- //printf("[SessionManager::%s] can't find new handle, have already: %d",
- //__FUNCTION__, mRequestSessionMap.size());
- return 0;
- }
- return newHandle;
-}
-
-Session* SessionManager::getNewSession(int* handle)
-{
- const static char* functionName = "SessionManager::getNewSession";
- if (!mInitalized) {
- printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
- return nullptr;
- }
-
- // first check if we have any timeouted session to directly reuse it
- checkTimeoutSession();
-
- // lock
- try {
- //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
- mWorkingMutex.tryLock(500);
- }
- catch (Poco::TimeoutException &ex) {
- printf("[%s] exception timout mutex: %s\n", functionName, ex.displayText().data());
- return nullptr;
- }
- //mWorkingMutex.lock();
-
- //UniLib::controller::TaskPtr checkSessionTimeout(new CheckSessionTimeouted);
- //checkSessionTimeout->scheduleTask(checkSessionTimeout);
-
- // check if we have an existing session ready to use
- while (mEmptyRequestStack.size() > 0) {
- int local_handle = mEmptyRequestStack.top();
- mEmptyRequestStack.pop();
- auto resultIt = mRequestSessionMap.find(local_handle);
- if (resultIt != mRequestSessionMap.end()) {
- Session* result = resultIt->second;
- // check if dead locked
- if (result->tryLock()) {
- result->unlock();
- if (!result->isActive()) {
- result->reset();
- //mWorkingMutex.unlock();
-
- if (handle) {
- *handle = local_handle;
- }
- result->setActive(true);
- mWorkingMutex.unlock();
- return result;
- }
- }
- else {
- ErrorList errors;
- errors.addError(new Error(functionName, "found dead locked session, keeping in memory without reference"));
- errors.addError(new ParamError(functionName, "last succeeded lock:", result->getLastSucceededLock().data()));
- errors.sendErrorsAsEmail();
-
- mRequestSessionMap.erase(local_handle);
- }
-
- }
- }
-
- // else create new RequestSession Object
- // calculate random handle
- // check if already exist, if get new
- int newHandle = generateNewUnusedHandle();
- if (!newHandle) {
- mWorkingMutex.unlock();
- return nullptr;
- }
-
- auto requestSession = new Session(newHandle);
- mRequestSessionMap.insert(std::pair(newHandle, requestSession));
-
- requestSession->setActive(true);
- //mWorkingMutex.unlock();
-
- if (handle) {
- *handle = newHandle;
- }
- //printf("[SessionManager::getNewSession] handle: %ld, sum: %u\n", newHandle, mRequestSessionMap.size());
- mWorkingMutex.unlock();
- return requestSession;
-
-
- //return nullptr;
-}
-
-bool SessionManager::releaseSession(int requestHandleSession)
-{
- if (!mInitalized) {
- printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
- return false;
- }
- try {
- //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
- mWorkingMutex.tryLock(500);
- }
- catch (Poco::TimeoutException &ex) {
- printf("[SessionManager::releaseSession] exception timout mutex: %s\n", ex.displayText().data());
- return false;
- }
- //mWorkingMutex.lock();
-
- auto it = mRequestSessionMap.find(requestHandleSession);
- if (it == mRequestSessionMap.end()) {
- //printf("[SessionManager::releaseRequestSession] requestSession with handle: %d not found\n", requestHandleSession);
- mWorkingMutex.unlock();
- return false;
- }
- Session* session = it->second;
-
- // delete session, not reuse as workaround for server freeze bug
- mRequestSessionMap.erase(requestHandleSession);
- delete session;
- mWorkingMutex.unlock();
- return true;
-
- // check if dead locked
- if (session->tryLock()) {
- session->unlock();
- session->reset();
- session->setActive(false);
- }
- else {
- ErrorList errors;
- errors.addError(new Error("SessionManager::releaseSession", "found dead locked session"));
- errors.sendErrorsAsEmail();
- mRequestSessionMap.erase(requestHandleSession);
- delete session;
- mWorkingMutex.unlock();
- return true;
- }
-
- // change request handle we don't want session hijacking
-
- // hardcoded disabled session max
- if (mEmptyRequestStack.size() > 100) {
- mRequestSessionMap.erase(requestHandleSession);
- delete session;
- mWorkingMutex.unlock();
- return true;
- }
-
- int newHandle = generateNewUnusedHandle();
- //printf("[SessionManager::releseSession] oldHandle: %ld, newHandle: %ld\n", requestHandleSession, newHandle);
- // erase after generating new number to prevent to getting the same number again
- mRequestSessionMap.erase(requestHandleSession);
-
- if (!newHandle) {
- delete session;
- mWorkingMutex.unlock();
- return true;
- }
-
- session->setHandle(newHandle);
- mRequestSessionMap.insert(std::pair(newHandle, session));
- mEmptyRequestStack.push(newHandle);
-
- mWorkingMutex.unlock();
- return true;
-}
-
-bool SessionManager::isExist(int requestHandleSession)
-{
- static const char* function_name = "SessionManager::isExist";
- if (!mInitalized) {
- printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
- return false;
- }
- bool result = false;
- //mWorkingMutex.lock();
- try {
- //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
- mWorkingMutex.tryLock(500);
- }
- catch (Poco::TimeoutException &ex) {
- printf("[SessionManager::isExist] exception timout mutex: %s\n", ex.displayText().data());
- return false;
- }
- auto it = mRequestSessionMap.find(requestHandleSession);
- if (it != mRequestSessionMap.end()) {
- result = true;
- int iResult = it->second->isActive();
- if (-1 == iResult) {
- auto em = ErrorManager::getInstance();
- em->addError(new Error(function_name, "session return locked"));
- em->sendErrorsAsEmail();
- }
- if (0 == iResult) {
- printf("[SessionManager::isExist] session isn't active\n");
- }
- }
- mWorkingMutex.unlock();
- return result;
-}
-
-Session* SessionManager::getSession(const Poco::Net::HTTPServerRequest& request)
-{
- // check if user has valid session
- Poco::Net::NameValueCollection cookies;
- request.getCookies(cookies);
-
- int session_id = 0;
-
- try {
- session_id = atoi(cookies.get("GRADIDO_LOGIN").data());
- return getSession(session_id);
- }
- catch (...) {}
-
- return nullptr;
-}
-
-Session* SessionManager::getSession(int handle)
-{
- static const char* function_name = "SessionManager::getSession";
- if (!mInitalized) {
- printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
- return nullptr;
- }
- if (0 == handle) return nullptr;
- Session* result = nullptr;
- try {
- //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
- mWorkingMutex.tryLock(500);
- }
- catch (Poco::TimeoutException &ex) {
- printf("[SessionManager::getSession] exception timout mutex: %s\n", ex.displayText().data());
- return result;
- }
- //mWorkingMutex.lock();
- auto it = mRequestSessionMap.find(handle);
- if (it != mRequestSessionMap.end()) {
- result = it->second;
- int iResult = result->isActive();
- if (iResult == -1) {
- auto em = ErrorManager::getInstance();
- em->addError(new Error(function_name, "session is locked"));
- em->sendErrorsAsEmail();
- mWorkingMutex.unlock();
- return nullptr;
- }
- if (0 == iResult) {
- //printf("[SessionManager::getSession] session isn't active\n");
- mWorkingMutex.unlock();
- return nullptr;
- }
- //result->setActive(true);
- result->updateTimeout();
- }
- //printf("[SessionManager::getSession] handle: %ld\n", handle);
- mWorkingMutex.unlock();
- return result;
-}
-
-Session* SessionManager::findByEmailVerificationCode(const Poco::UInt64& emailVerificationCode)
-{
-
- auto email_verification = controller::EmailVerificationCode::load(emailVerificationCode);
- if (email_verification.isNull()) return nullptr;
- auto email_verification_model = email_verification->getModel();
- assert(email_verification_model && email_verification_model->getUserId() > 0);
-
- auto session = findByUserId(email_verification_model->getUserId());
- if (session) {
- session->setEmailVerificationCodeObject(email_verification);
- }
-
- return session;
-}
-
-Session* SessionManager::findByUserId(int userId)
-{
- assert(userId > 0);
- static const char* function_name = "SessionManager::findByUserId";
- try {
- //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
- mWorkingMutex.tryLock(500);
- }
- catch (Poco::TimeoutException &ex) {
- printf("[SessionManager::findByUserId] exception timout mutex: %s\n", ex.displayText().data());
- return nullptr;
- }
- //mWorkingMutex.lock();
- for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
- while (it->second->isDeadLocked())
- {
- it = mRequestSessionMap.erase(it);
- mDeadLockedSessionCount++;
- auto em = ErrorManager::getInstance();
- em->addError(new ParamError("SessionManager::findByUserId", "new dead locked session found, sum dead lock sessions:", mDeadLockedSessionCount));
- em->sendErrorsAsEmail();
- }
- auto user = it->second->getNewUser();
- auto em = ErrorManager::getInstance();
- if(!user) continue;
- if (!user->getModel()) {
- em->addError(new Error(function_name, "user getModel return nullptr"));
- em->addError(new ParamError(function_name, "user id: ", userId));
- em->sendErrorsAsEmail();
- continue;
- }
- if (!user->getModel()->getID()) {
- em->addError(new Error(function_name, "user id is zero"));
- em->addError(new ParamError(function_name, "user id: ", userId));
- em->sendErrorsAsEmail();
- continue;
- }
- //assert(user->getModel() && user->getModel()->getID());
- if (userId == user->getModel()->getID()) {
- mWorkingMutex.unlock();
- return it->second;
- }
- }
- mWorkingMutex.unlock();
- return nullptr;
-}
-
-std::vector SessionManager::findAllByUserId(int userId)
-{
- assert(userId > 0);
- std::vector result;
- try {
- //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
- mWorkingMutex.tryLock(500);
- }
- catch (Poco::TimeoutException &ex) {
- printf("[SessionManager::findAllByUserId] exception timout mutex: %s\n", ex.displayText().data());
- //mWorkingMutex.unlock();
- return result;
- }
- //mWorkingMutex.lock();
- for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
- if (it->second->isDeadLocked()) {
- it = mRequestSessionMap.erase(it);
- mDeadLockedSessionCount++;
- }
- auto user = it->second->getNewUser();
- if (userId == user->getModel()->getID()) {
- //return it->second;
- result.push_back(it->second);
- }
- }
- //mWorkingMutex.unlock();
- mWorkingMutex.unlock();
- return result;
-}
-
-Session* SessionManager::findByEmail(const std::string& email)
-{
- assert(email.size() > 0);
-
- try {
- //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
- mWorkingMutex.tryLock(500);
- }
- catch (Poco::TimeoutException &ex) {
- printf("[SessionManager::findByEmail] exception timout mutex: %s\n", ex.displayText().data());
- //mWorkingMutex.unlock();
- return nullptr;
- }
- //mWorkingMutex.lock();
- for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
- if (it->second->isDeadLocked()) {
- it = mRequestSessionMap.erase(it);
- mDeadLockedSessionCount++;
- }
- auto user = it->second->getNewUser();
-if (email == user->getModel()->getEmail()) {
- return it->second;
-}
- }
- mWorkingMutex.unlock();
- return nullptr;
-}
-
-void SessionManager::checkTimeoutSession()
-{
-
- try {
- //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
- mWorkingMutex.tryLock(500);
- }
- catch (Poco::TimeoutException& ex) {
- printf("[SessionManager::checkTimeoutSession] exception timeout mutex: %s\n", ex.displayText().data());
- return;
- }
- //mWorkingMutex.lock();
- auto now = Poco::DateTime();
- // random variance within 10 seconds for timeout to make it harder to get information and hack the server
- auto timeout = Poco::Timespan(ServerConfig::g_SessionTimeout * 60, randombytes_random() % 10000000);
- //auto timeout = Poco::Timespan(1, 0);
- std::stack toRemove;
- for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
-
- if (it->second->tryLock()) {
- // skip already disabled sessions
- if (!it->second->isActive()) {
- it->second->unlock();
- continue;
- }
- }
- else {
- // skip dead locked sessions
- continue;
- }
-
- Poco::Timespan timeElapsed(now - it->second->getLastActivity());
- it->second->unlock();
- if (timeElapsed > timeout) {
- toRemove.push(it->first);
- }
- }
- mWorkingMutex.unlock();
-
- while (toRemove.size() > 0) {
- int handle = toRemove.top();
- toRemove.pop();
- releaseSession(handle);
- }
-
-}
-
-void SessionManager::deleteLoginCookies(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response, Session* activeSession/* = nullptr*/)
-{
- Poco::Net::NameValueCollection cookies;
- request.getCookies(cookies);
- // go from first login cookie
- for (auto it = cookies.find("GRADIDO_LOGIN"); it != cookies.end(); it++) {
- // break if no login any more
- if (it->first != "GRADIDO_LOGIN") break;
- // skip if it is from the active session
- if (activeSession) {
- try {
- int session_id = atoi(it->second.data());
- if (activeSession->tryLock()) {
- bool session_id_is_handle = session_id == activeSession->getHandle();
- activeSession->unlock();
- if (session_id_is_handle) continue;
- }
- }
- catch (...) {}
- }
- // delete cookie
- auto keks = Poco::Net::HTTPCookie("GRADIDO_LOGIN", it->second);
- keks.setPath("/");
- // max age of 0 delete cookie
- keks.setMaxAge(0);
- response.addCookie(keks);
- }
- // delete also cake php session cookie
- for (auto it = cookies.find("CAKEPHP"); it != cookies.end(); it++) {
- if (it->first != "CAKEPHP") break;
- // delete cookie
- auto keks = Poco::Net::HTTPCookie("CAKEPHP", it->second);
- keks.setPath("/");
- // max age of 0 delete cookie
- keks.setMaxAge(0);
- response.addCookie(keks);
- //printf("remove PHP Kekse\n");
- }
-
-
- //session_id = atoi(cookies.get("GRADIDO_LOGIN").data());
-}
-
-bool SessionManager::checkPwdValidation(const std::string& pwd, ErrorList* errorReciver)
-{
- if ((ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) == ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) {
- return true;
- }
-
- if (!isValid(pwd, VALIDATE_PASSWORD)) {
- errorReciver->addError(new Error("Passwort", "Bitte gebe ein gültiges Password ein mit mindestens 8 Zeichen, Groß- und Kleinbuchstaben, mindestens einer Zahl und einem Sonderzeichen (@$!%*?&+-_) ein!"));
-
- // @$!%*?&+-
- if (pwd.size() < 8) {
- errorReciver->addError(new Error("Passwort", "Dein Passwort ist zu kurz!"));
- }
- else if (!isValid(pwd, VALIDATE_HAS_LOWERCASE_LETTER)) {
- errorReciver->addError(new Error("Passwort", "Dein Passwort enthält keine Kleinbuchstaben!"));
- }
- else if (!isValid(pwd, VALIDATE_HAS_UPPERCASE_LETTER)) {
- errorReciver->addError(new Error("Passwort", "Dein Passwort enthält keine Großbuchstaben!"));
- }
- else if (!isValid(pwd, VALIDATE_HAS_NUMBER)) {
- errorReciver->addError(new Error("Passwort", "Dein Passwort enthält keine Zahlen!"));
- }
- else if (!isValid(pwd, VALIDATE_HAS_SPECIAL_CHARACTER)) {
- errorReciver->addError(new Error("Passwort", "Dein Passwort enthält keine Sonderzeichen (@$!%*?&+-)!"));
- }
-
- return false;
- }
- return true;
-}
-
-
-int CheckSessionTimeouted::run()
-{
- SessionManager::getInstance()->checkTimeoutSession();
- return 0;
-}
+#include "SessionManager.h"
+#include "ErrorManager.h"
+#include "../ServerConfig.h"
+#include "../Crypto/DRRandom.h"
+#include "../controller/EmailVerificationCode.h"
+
+#include
+
+
+SessionManager* SessionManager::getInstance()
+{
+ static SessionManager only;
+ return &only;
+}
+
+SessionManager::SessionManager()
+ : mInitalized(false), mDeadLockedSessionCount(0)
+{
+
+}
+
+SessionManager::~SessionManager()
+{
+ if (mInitalized) {
+ deinitalize();
+ }
+}
+
+
+bool SessionManager::init()
+{
+ try {
+ //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
+ mWorkingMutex.tryLock(500);
+ }
+ catch (Poco::TimeoutException &ex) {
+ printf("[SessionManager::init] exception timout mutex: %s\n", ex.displayText().data());
+ return false;
+ }
+ //mWorkingMutex.lock();
+ int i;
+ DISASM_MISALIGN;
+ for (i = 0; i < VALIDATE_MAX; i++) {
+ switch (i) {
+ //case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("/^[a-zA-Z_ -]{3,}$/"); break;
+ case VALIDATE_NAME: mValidations[i] = new Poco::RegularExpression("^[^<>&;]{3,}$"); break;
+ case VALIDATE_EMAIL: mValidations[i] = new Poco::RegularExpression("^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$"); break;
+ case VALIDATE_PASSWORD: mValidations[i] = new Poco::RegularExpression("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[@$!%*?&+-_])[A-Za-z0-9@$!%*?&+-_]{8,}$"); break;
+ case VALIDATE_PASSPHRASE: mValidations[i] = new Poco::RegularExpression("^(?:[a-z]* ){23}[a-z]*\s*$"); break;
+ case VALIDATE_HAS_NUMBER: mValidations[i] = new Poco::RegularExpression(".*[0-9].*"); break;
+ case VALIDATE_HAS_SPECIAL_CHARACTER: mValidations[i] = new Poco::RegularExpression(".*[@$!%*?&+-].*"); break;
+ case VALIDATE_HAS_UPPERCASE_LETTER:
+ mValidations[i] = new Poco::RegularExpression(".*[A-Z].*");
+ ServerConfig::g_ServerKeySeed->put(i, DRRandom::r64());
+ break;
+ case VALIDATE_HAS_LOWERCASE_LETTER: mValidations[i] = new Poco::RegularExpression(".*[a-z].*"); break;
+ default: printf("[SessionManager::%s] unknown validation type\n", __FUNCTION__);
+ }
+ }
+
+
+ mInitalized = true;
+ mWorkingMutex.unlock();
+ return true;
+}
+
+void SessionManager::deinitalize()
+{
+ try {
+ //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
+ mWorkingMutex.tryLock(500);
+ }
+ catch (Poco::TimeoutException &ex) {
+ printf("[SessionManager::deinitalize] exception timout mutex: %s\n", ex.displayText().data());
+ return;
+ }
+ //mWorkingMutex.lock();
+
+ while (mEmptyRequestStack.size()) {
+ mEmptyRequestStack.pop();
+ }
+
+ for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
+ delete it->second;
+ }
+ mRequestSessionMap.clear();
+
+ for (int i = 0; i < VALIDATE_MAX; i++) {
+ delete mValidations[i];
+ }
+
+ printf("[SessionManager::deinitalize] count of dead locked sessions: %d\n", mDeadLockedSessionCount);
+
+ mInitalized = false;
+ mWorkingMutex.unlock();
+}
+
+bool SessionManager::isValid(const std::string& subject, SessionValidationTypes validationType)
+{
+ if (validationType >= VALIDATE_MAX) {
+ return false;
+ }
+ return *mValidations[validationType] == subject;
+}
+
+int SessionManager::generateNewUnusedHandle()
+{
+ int newHandle = 0;
+ int maxTrys = 0;
+ do {
+ newHandle = randombytes_random();
+ maxTrys++;
+ } while (mRequestSessionMap.find(newHandle) != mRequestSessionMap.end() && maxTrys < 100);
+
+ if (maxTrys >= 100 || 0 == newHandle) {
+ auto em = ErrorManager::getInstance();
+ em->addError(new ParamError("SessionManager::generateNewUnusedHandle", "can't find new handle, have already ", std::to_string(mRequestSessionMap.size())));
+ em->sendErrorsAsEmail();
+ //printf("[SessionManager::%s] can't find new handle, have already: %d",
+ //__FUNCTION__, mRequestSessionMap.size());
+ return 0;
+ }
+ return newHandle;
+}
+
+Session* SessionManager::getNewSession(int* handle)
+{
+ const static char* functionName = "SessionManager::getNewSession";
+ if (!mInitalized) {
+ printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
+ return nullptr;
+ }
+
+ // first check if we have any timeouted session to directly reuse it
+ checkTimeoutSession();
+
+ // lock
+ try {
+ //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
+ mWorkingMutex.tryLock(500);
+ }
+ catch (Poco::TimeoutException &ex) {
+ printf("[%s] exception timout mutex: %s\n", functionName, ex.displayText().data());
+ return nullptr;
+ }
+ //mWorkingMutex.lock();
+
+ //UniLib::controller::TaskPtr checkSessionTimeout(new CheckSessionTimeouted);
+ //checkSessionTimeout->scheduleTask(checkSessionTimeout);
+
+ // check if we have an existing session ready to use
+ while (mEmptyRequestStack.size() > 0) {
+ int local_handle = mEmptyRequestStack.top();
+ mEmptyRequestStack.pop();
+ auto resultIt = mRequestSessionMap.find(local_handle);
+ if (resultIt != mRequestSessionMap.end()) {
+ Session* result = resultIt->second;
+ // check if dead locked
+ if (result->tryLock()) {
+ result->unlock();
+ if (!result->isActive()) {
+ result->reset();
+ //mWorkingMutex.unlock();
+
+ if (handle) {
+ *handle = local_handle;
+ }
+ result->setActive(true);
+ mWorkingMutex.unlock();
+ return result;
+ }
+ }
+ else {
+ ErrorList errors;
+ errors.addError(new Error(functionName, "found dead locked session, keeping in memory without reference"));
+ errors.addError(new ParamError(functionName, "last succeeded lock:", result->getLastSucceededLock().data()));
+ errors.sendErrorsAsEmail();
+
+ mRequestSessionMap.erase(local_handle);
+ }
+
+ }
+ }
+
+ // else create new RequestSession Object
+ // calculate random handle
+ // check if already exist, if get new
+ int newHandle = generateNewUnusedHandle();
+ if (!newHandle) {
+ mWorkingMutex.unlock();
+ return nullptr;
+ }
+
+ auto requestSession = new Session(newHandle);
+ mRequestSessionMap.insert(std::pair(newHandle, requestSession));
+
+ requestSession->setActive(true);
+ //mWorkingMutex.unlock();
+
+ if (handle) {
+ *handle = newHandle;
+ }
+ //printf("[SessionManager::getNewSession] handle: %ld, sum: %u\n", newHandle, mRequestSessionMap.size());
+ mWorkingMutex.unlock();
+ return requestSession;
+
+
+ //return nullptr;
+}
+
+bool SessionManager::releaseSession(int requestHandleSession)
+{
+ if (!mInitalized) {
+ printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
+ return false;
+ }
+ try {
+ //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
+ mWorkingMutex.tryLock(500);
+ }
+ catch (Poco::TimeoutException &ex) {
+ printf("[SessionManager::releaseSession] exception timout mutex: %s\n", ex.displayText().data());
+ return false;
+ }
+ //mWorkingMutex.lock();
+
+ auto it = mRequestSessionMap.find(requestHandleSession);
+ if (it == mRequestSessionMap.end()) {
+ //printf("[SessionManager::releaseRequestSession] requestSession with handle: %d not found\n", requestHandleSession);
+ mWorkingMutex.unlock();
+ return false;
+ }
+ Session* session = it->second;
+
+ // delete session, not reuse as workaround for server freeze bug
+ mRequestSessionMap.erase(requestHandleSession);
+ delete session;
+ mWorkingMutex.unlock();
+ return true;
+
+ // check if dead locked
+ if (session->tryLock()) {
+ session->unlock();
+ session->reset();
+ session->setActive(false);
+ }
+ else {
+ ErrorList errors;
+ errors.addError(new Error("SessionManager::releaseSession", "found dead locked session"));
+ errors.sendErrorsAsEmail();
+ mRequestSessionMap.erase(requestHandleSession);
+ delete session;
+ mWorkingMutex.unlock();
+ return true;
+ }
+
+ // change request handle we don't want session hijacking
+
+ // hardcoded disabled session max
+ if (mEmptyRequestStack.size() > 100) {
+ mRequestSessionMap.erase(requestHandleSession);
+ delete session;
+ mWorkingMutex.unlock();
+ return true;
+ }
+
+ int newHandle = generateNewUnusedHandle();
+ //printf("[SessionManager::releseSession] oldHandle: %ld, newHandle: %ld\n", requestHandleSession, newHandle);
+ // erase after generating new number to prevent to getting the same number again
+ mRequestSessionMap.erase(requestHandleSession);
+
+ if (!newHandle) {
+ delete session;
+ mWorkingMutex.unlock();
+ return true;
+ }
+
+ session->setHandle(newHandle);
+ mRequestSessionMap.insert(std::pair(newHandle, session));
+ mEmptyRequestStack.push(newHandle);
+
+ mWorkingMutex.unlock();
+ return true;
+}
+
+bool SessionManager::isExist(int requestHandleSession)
+{
+ static const char* function_name = "SessionManager::isExist";
+ if (!mInitalized) {
+ printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
+ return false;
+ }
+ bool result = false;
+ //mWorkingMutex.lock();
+ try {
+ //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
+ mWorkingMutex.tryLock(500);
+ }
+ catch (Poco::TimeoutException &ex) {
+ printf("[SessionManager::isExist] exception timout mutex: %s\n", ex.displayText().data());
+ return false;
+ }
+ auto it = mRequestSessionMap.find(requestHandleSession);
+ if (it != mRequestSessionMap.end()) {
+ result = true;
+ int iResult = it->second->isActive();
+ if (-1 == iResult) {
+ auto em = ErrorManager::getInstance();
+ em->addError(new Error(function_name, "session return locked"));
+ em->sendErrorsAsEmail();
+ }
+ if (0 == iResult) {
+ printf("[SessionManager::isExist] session isn't active\n");
+ }
+ }
+ mWorkingMutex.unlock();
+ return result;
+}
+
+Session* SessionManager::getSession(const Poco::Net::HTTPServerRequest& request)
+{
+ // check if user has valid session
+ Poco::Net::NameValueCollection cookies;
+ request.getCookies(cookies);
+
+ int session_id = 0;
+
+ try {
+ session_id = atoi(cookies.get("GRADIDO_LOGIN").data());
+ return getSession(session_id);
+ }
+ catch (...) {}
+
+ return nullptr;
+}
+
+Session* SessionManager::getSession(int handle)
+{
+ static const char* function_name = "SessionManager::getSession";
+ if (!mInitalized) {
+ printf("[SessionManager::%s] not initialized any more\n", __FUNCTION__);
+ return nullptr;
+ }
+ if (0 == handle) return nullptr;
+ Session* result = nullptr;
+ try {
+ //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
+ mWorkingMutex.tryLock(500);
+ }
+ catch (Poco::TimeoutException &ex) {
+ printf("[SessionManager::getSession] exception timout mutex: %s\n", ex.displayText().data());
+ return result;
+ }
+ //mWorkingMutex.lock();
+ auto it = mRequestSessionMap.find(handle);
+ if (it != mRequestSessionMap.end()) {
+ result = it->second;
+ int iResult = result->isActive();
+ if (iResult == -1) {
+ auto em = ErrorManager::getInstance();
+ em->addError(new Error(function_name, "session is locked"));
+ em->sendErrorsAsEmail();
+ mWorkingMutex.unlock();
+ return nullptr;
+ }
+ if (0 == iResult) {
+ //printf("[SessionManager::getSession] session isn't active\n");
+ mWorkingMutex.unlock();
+ return nullptr;
+ }
+ //result->setActive(true);
+ result->updateTimeout();
+ }
+ //printf("[SessionManager::getSession] handle: %ld\n", handle);
+ mWorkingMutex.unlock();
+ return result;
+}
+
+Session* SessionManager::findByEmailVerificationCode(const Poco::UInt64& emailVerificationCode)
+{
+
+ auto email_verification = controller::EmailVerificationCode::load(emailVerificationCode);
+ if (email_verification.isNull()) return nullptr;
+ auto email_verification_model = email_verification->getModel();
+ assert(email_verification_model && email_verification_model->getUserId() > 0);
+
+ auto session = findByUserId(email_verification_model->getUserId());
+ if (session) {
+ session->setEmailVerificationCodeObject(email_verification);
+ }
+
+ return session;
+}
+
+Session* SessionManager::findByUserId(int userId)
+{
+ assert(userId > 0);
+ static const char* function_name = "SessionManager::findByUserId";
+ try {
+ //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
+ mWorkingMutex.tryLock(500);
+ }
+ catch (Poco::TimeoutException &ex) {
+ printf("[SessionManager::findByUserId] exception timout mutex: %s\n", ex.displayText().data());
+ return nullptr;
+ }
+ //mWorkingMutex.lock();
+ for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
+ while (it->second->isDeadLocked())
+ {
+ it = mRequestSessionMap.erase(it);
+ mDeadLockedSessionCount++;
+ auto em = ErrorManager::getInstance();
+ em->addError(new ParamError("SessionManager::findByUserId", "new dead locked session found, sum dead lock sessions:", mDeadLockedSessionCount));
+ em->sendErrorsAsEmail();
+ }
+ auto user = it->second->getNewUser();
+ auto em = ErrorManager::getInstance();
+ if(!user) continue;
+ if (!user->getModel()) {
+ em->addError(new Error(function_name, "user getModel return nullptr"));
+ em->addError(new ParamError(function_name, "user id: ", userId));
+ em->sendErrorsAsEmail();
+ continue;
+ }
+ if (!user->getModel()->getID()) {
+ em->addError(new Error(function_name, "user id is zero"));
+ em->addError(new ParamError(function_name, "user id: ", userId));
+ em->sendErrorsAsEmail();
+ continue;
+ }
+ //assert(user->getModel() && user->getModel()->getID());
+ if (userId == user->getModel()->getID()) {
+ mWorkingMutex.unlock();
+ return it->second;
+ }
+ }
+ mWorkingMutex.unlock();
+ return nullptr;
+}
+
+std::vector SessionManager::findAllByUserId(int userId)
+{
+ assert(userId > 0);
+ std::vector result;
+ try {
+ //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
+ mWorkingMutex.tryLock(500);
+ }
+ catch (Poco::TimeoutException &ex) {
+ printf("[SessionManager::findAllByUserId] exception timout mutex: %s\n", ex.displayText().data());
+ //mWorkingMutex.unlock();
+ return result;
+ }
+ //mWorkingMutex.lock();
+ for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
+ if (it->second->isDeadLocked()) {
+ it = mRequestSessionMap.erase(it);
+ mDeadLockedSessionCount++;
+ }
+ auto user = it->second->getNewUser();
+ if (userId == user->getModel()->getID()) {
+ //return it->second;
+ result.push_back(it->second);
+ }
+ }
+ //mWorkingMutex.unlock();
+ mWorkingMutex.unlock();
+ return result;
+}
+
+Session* SessionManager::findByEmail(const std::string& email)
+{
+ assert(email.size() > 0);
+
+ try {
+ //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
+ mWorkingMutex.tryLock(500);
+ }
+ catch (Poco::TimeoutException &ex) {
+ printf("[SessionManager::findByEmail] exception timout mutex: %s\n", ex.displayText().data());
+ //mWorkingMutex.unlock();
+ return nullptr;
+ }
+ //mWorkingMutex.lock();
+ for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
+ if (it->second->isDeadLocked()) {
+ it = mRequestSessionMap.erase(it);
+ mDeadLockedSessionCount++;
+ }
+ auto user = it->second->getNewUser();
+if (email == user->getModel()->getEmail()) {
+ return it->second;
+}
+ }
+ mWorkingMutex.unlock();
+ return nullptr;
+}
+
+void SessionManager::checkTimeoutSession()
+{
+
+ try {
+ //Poco::Mutex::ScopedLock _lock(mWorkingMutex, 500);
+ mWorkingMutex.tryLock(500);
+ }
+ catch (Poco::TimeoutException& ex) {
+ printf("[SessionManager::checkTimeoutSession] exception timeout mutex: %s\n", ex.displayText().data());
+ return;
+ }
+ //mWorkingMutex.lock();
+ auto now = Poco::DateTime();
+ // random variance within 10 seconds for timeout to make it harder to get information and hack the server
+ auto timeout = Poco::Timespan(ServerConfig::g_SessionTimeout * 60, randombytes_random() % 10000000);
+ //auto timeout = Poco::Timespan(1, 0);
+ std::stack toRemove;
+ for (auto it = mRequestSessionMap.begin(); it != mRequestSessionMap.end(); it++) {
+
+ if (it->second->tryLock()) {
+ // skip already disabled sessions
+ if (!it->second->isActive()) {
+ it->second->unlock();
+ continue;
+ }
+ }
+ else {
+ // skip dead locked sessions
+ continue;
+ }
+
+ Poco::Timespan timeElapsed(now - it->second->getLastActivity());
+ it->second->unlock();
+ if (timeElapsed > timeout) {
+ toRemove.push(it->first);
+ }
+ }
+ mWorkingMutex.unlock();
+
+ while (toRemove.size() > 0) {
+ int handle = toRemove.top();
+ toRemove.pop();
+ releaseSession(handle);
+ }
+
+}
+
+void SessionManager::deleteLoginCookies(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response, Session* activeSession/* = nullptr*/)
+{
+ Poco::Net::NameValueCollection cookies;
+ request.getCookies(cookies);
+ // go from first login cookie
+ for (auto it = cookies.find("GRADIDO_LOGIN"); it != cookies.end(); it++) {
+ // break if no login any more
+ if (it->first != "GRADIDO_LOGIN") break;
+ // skip if it is from the active session
+ if (activeSession) {
+ try {
+ int session_id = atoi(it->second.data());
+ if (activeSession->tryLock()) {
+ bool session_id_is_handle = session_id == activeSession->getHandle();
+ activeSession->unlock();
+ if (session_id_is_handle) continue;
+ }
+ }
+ catch (...) {}
+ }
+ // delete cookie
+ auto keks = Poco::Net::HTTPCookie("GRADIDO_LOGIN", it->second);
+ keks.setPath("/");
+ // max age of 0 delete cookie
+ keks.setMaxAge(0);
+ response.addCookie(keks);
+ }
+ // delete also cake php session cookie
+ for (auto it = cookies.find("CAKEPHP"); it != cookies.end(); it++) {
+ if (it->first != "CAKEPHP") break;
+ // delete cookie
+ auto keks = Poco::Net::HTTPCookie("CAKEPHP", it->second);
+ keks.setPath("/");
+ // max age of 0 delete cookie
+ keks.setMaxAge(0);
+ response.addCookie(keks);
+ //printf("remove PHP Kekse\n");
+ }
+
+
+ //session_id = atoi(cookies.get("GRADIDO_LOGIN").data());
+}
+
+bool SessionManager::checkPwdValidation(const std::string& pwd, ErrorList* errorReciver)
+{
+ if ((ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) == ServerConfig::UNSECURE_ALLOW_ALL_PASSWORDS) {
+ return true;
+ }
+
+ if (!isValid(pwd, VALIDATE_PASSWORD)) {
+ errorReciver->addError(new Error("Passwort", "Bitte gebe ein gültiges Password ein mit mindestens 8 Zeichen, Groß- und Kleinbuchstaben, mindestens einer Zahl und einem Sonderzeichen (@$!%*?&+-_) ein!"));
+
+ // @$!%*?&+-
+ if (pwd.size() < 8) {
+ errorReciver->addError(new Error("Passwort", "Dein Passwort ist zu kurz!"));
+ }
+ else if (!isValid(pwd, VALIDATE_HAS_LOWERCASE_LETTER)) {
+ errorReciver->addError(new Error("Passwort", "Dein Passwort enthält keine Kleinbuchstaben!"));
+ }
+ else if (!isValid(pwd, VALIDATE_HAS_UPPERCASE_LETTER)) {
+ errorReciver->addError(new Error("Passwort", "Dein Passwort enthält keine Großbuchstaben!"));
+ }
+ else if (!isValid(pwd, VALIDATE_HAS_NUMBER)) {
+ errorReciver->addError(new Error("Passwort", "Dein Passwort enthält keine Zahlen!"));
+ }
+ else if (!isValid(pwd, VALIDATE_HAS_SPECIAL_CHARACTER)) {
+ errorReciver->addError(new Error("Passwort", "Dein Passwort enthält keine Sonderzeichen (@$!%*?&+-)!"));
+ }
+
+ return false;
+ }
+ return true;
+}
+
+
+int CheckSessionTimeouted::run()
+{
+ SessionManager::getInstance()->checkTimeoutSession();
+ return 0;
+}
diff --git a/login_server/src/cpp/lib/Error.h b/login_server/src/cpp/lib/Error.h
index 2a4d79e85..7988a9345 100644
--- a/login_server/src/cpp/lib/Error.h
+++ b/login_server/src/cpp/lib/Error.h
@@ -1,63 +1,63 @@
-/*!
-*
-* \author: einhornimmond
-*
-* \date: 07.03.19
-*
-* \brief: error data
-*/
-
-#ifndef DR_LUA_WEB_MODULE_ERROR_ERROR_H
-#define DR_LUA_WEB_MODULE_ERROR_ERROR_H
-
-#include
-#include
-
-class Error
-{
-public:
- Error(const char* functionName, const char* message);
- ~Error();
-
- const char* getFunctionName() { return mFunctionName.data(); }
- const char* getMessage() { return mMessage.data(); }
- virtual std::string getString(bool withNewline = true);
- virtual std::string getHtmlString();
-
-
-
-protected:
- std::string mFunctionName;
- std::string mMessage;
-};
-
-class ParamError : public Error
-{
-public:
- ParamError(const char* functionName, const char* message, const char* param)
- : Error(functionName, message), mParam(param) {}
- ParamError(const char* functionName, const char* message, const std::string& param)
- : Error(functionName, message), mParam(param) {}
-
- ParamError(const char* functioName, const char* message, int param)
- : Error(functioName, message) {
- std::stringstream ss;
- ss << param;
- mParam = ss.str();
- }
-
- virtual std::string getString(bool withNewline = true);
- virtual std::string getHtmlString();
-protected:
- std::string mParam;
-};
-
-
-
-class IErrorCollection
-{
-public:
- virtual void addError(Error*, bool log = true) = 0;
-};
-
-#endif // DR_LUA_WEB_MODULE_ERROR_ERROR_H
+/*!
+*
+* \author: einhornimmond
+*
+* \date: 07.03.19
+*
+* \brief: error data
+*/
+
+#ifndef DR_LUA_WEB_MODULE_ERROR_ERROR_H
+#define DR_LUA_WEB_MODULE_ERROR_ERROR_H
+
+#include
+#include
+
+class Error
+{
+public:
+ Error(const char* functionName, const char* message);
+ ~Error();
+
+ const char* getFunctionName() { return mFunctionName.data(); }
+ const char* getMessage() { return mMessage.data(); }
+ virtual std::string getString(bool withNewline = true);
+ virtual std::string getHtmlString();
+
+
+
+protected:
+ std::string mFunctionName;
+ std::string mMessage;
+};
+
+class ParamError : public Error
+{
+public:
+ ParamError(const char* functionName, const char* message, const char* param)
+ : Error(functionName, message), mParam(param) {}
+ ParamError(const char* functionName, const char* message, const std::string& param)
+ : Error(functionName, message), mParam(param) {}
+
+ ParamError(const char* functioName, const char* message, int param)
+ : Error(functioName, message) {
+ std::stringstream ss;
+ ss << param;
+ mParam = ss.str();
+ }
+
+ virtual std::string getString(bool withNewline = true);
+ virtual std::string getHtmlString();
+protected:
+ std::string mParam;
+};
+
+
+
+class IErrorCollection
+{
+public:
+ virtual void addError(Error*, bool log = true) = 0;
+};
+
+#endif // DR_LUA_WEB_MODULE_ERROR_ERROR_H
diff --git a/login_server/src/cpp/lib/ErrorList.cpp b/login_server/src/cpp/lib/ErrorList.cpp
index a663b67b2..32c408acb 100644
--- a/login_server/src/cpp/lib/ErrorList.cpp
+++ b/login_server/src/cpp/lib/ErrorList.cpp
@@ -1,201 +1,201 @@
-#include "ErrorList.h"
-
-#include "../ServerConfig.h"
-
-//#include "Poco/Net/MailMessage.h"
-#include "Poco/Net/MediaType.h"
-
-#include "../SingletonManager/EmailManager.h"
-
-SendErrorMessage::~SendErrorMessage()
-{
- if (mMessage) {
- delete mMessage;
- mMessage = nullptr;
- }
-}
-
-int SendErrorMessage::run()
-{
- if (ServerConfig::g_disableEmail) return 0;
-
- auto mailClientSession = new Poco::Net::SecureSMTPClientSession(ServerConfig::g_EmailAccount.url, ServerConfig::g_EmailAccount.port);
- mailClientSession->login();
- mailClientSession->startTLS(ServerConfig::g_SSL_CLient_Context);
-
-
- mailClientSession->login(Poco::Net::SMTPClientSession::AUTH_LOGIN, ServerConfig::g_EmailAccount.username, ServerConfig::g_EmailAccount.password);
-
- try {
- mMessage->setSender(ServerConfig::g_EmailAccount.sender);
- mailClientSession->sendMessage(*mMessage);
- mailClientSession->close();
- }
- catch (Poco::Exception& exc) {
- printf("[SendErrorMessage::%s] error sending error message to admin: %s\n",
- __FUNCTION__, exc.displayText().data());
- return -1;
- }
- return 0;
-}
-
-// ------------------------------------------------------------------------------------
-
-
-ErrorList::ErrorList()
- : mLogging(Poco::Logger::get("errorLog"))
-{
-
-}
-
-ErrorList::~ErrorList()
-{
- while (mErrorStack.size() > 0) {
- delete mErrorStack.top();
- mErrorStack.pop();
- }
-}
-
-void ErrorList::addError(Error* error, bool log/* = true */)
-{
-
- if (log) {
- std::string dateTimeString = Poco::DateTimeFormatter::format(Poco::DateTime(), "%d.%m.%y %H:%M:%S");
- mLogging.error("%s [ErrorList::addError] %s", dateTimeString, error->getString(false));
-
- }
- mErrorStack.push(error);
-}
-
-Error* ErrorList::getLastError()
-{
- if (mErrorStack.size() == 0) {
- return nullptr;
- }
-
- Error* error = mErrorStack.top();
- if (error) {
- mErrorStack.pop();
- }
-
- return error;
-}
-
-void ErrorList::clearErrors()
-{
- while (mErrorStack.size()) {
- auto error = mErrorStack.top();
- if (error) {
- delete error;
- }
- mErrorStack.pop();
- }
-}
-
-
-int ErrorList::getErrors(ErrorList* send)
-{
- Error* error = nullptr;
- int iCount = 0;
- while (error = send->getLastError()) {
- addError(error, false);
- iCount++;
- }
- return iCount;
-}
-
-void ErrorList::printErrors()
-{
- while (mErrorStack.size() > 0) {
- auto error = mErrorStack.top();
- mErrorStack.pop();
- printf(error->getString().data());
- delete error;
- }
-}
-
-std::vector ErrorList::getErrorsArray()
-{
- std::vector result;
- result.reserve(mErrorStack.size());
-
- while (mErrorStack.size() > 0) {
- auto error = mErrorStack.top();
- mErrorStack.pop();
- //result->add(error->getString());
- result.push_back(error->getString());
- delete error;
- }
- return result;
-}
-
-std::string ErrorList::getErrorsHtml()
-{
- std::string res;
- res = "";
- while (mErrorStack.size() > 0) {
- auto error = mErrorStack.top();
- mErrorStack.pop();
- res += "- ";
- res += error->getHtmlString();
- res += "
";
- delete error;
- }
- res += "
";
- return res;
-}
-
-std::string ErrorList::getErrorsHtmlNewFormat()
-{
- std::string html;
-
- while (mErrorStack.size() > 0) {
- auto error = std::unique_ptr(mErrorStack.top());
- mErrorStack.pop();
- html += "";
- html += "report_problem";
- html += "";
- html += error->getHtmlString();
- html += "";
- html += "
";
- }
- return html;
-}
-/*
-
-report_problem
-Der Empfänger wurde nicht auf dem Login-Server gefunden, hat er sein Konto schon angelegt?
-
-*/
-
-
-void ErrorList::sendErrorsAsEmail(std::string rawHtml/* = ""*/)
-{
- auto em = EmailManager::getInstance();
- /*auto message = new Poco::Net::MailMessage();
- message->setSender("gradido_loginServer@gradido.net");
- message->addRecipient(Poco::Net::MailRecipient(Poco::Net::MailRecipient::PRIMARY_RECIPIENT, "***REMOVED***"));
- message->setSubject("Error from Gradido Login Server");
- */
- std::string content;
- while (mErrorStack.size() > 0) {
- auto error = mErrorStack.top();
- mErrorStack.pop();
- content += error->getString();
- delete error;
- }
- auto email = new model::Email(content, model::EMAIL_ERROR);
-
- //message->addContent(new Poco::Net::StringPartSource(content));
- if (rawHtml != "") {
- Poco::Net::MediaType mt("text", "html");
- mt.setParameter("charset", "utf-8");
-
- email->addContent(new Poco::Net::StringPartSource(rawHtml, mt.toString()));
- }
- em->addEmail(email);
-
- //UniLib::controller::TaskPtr sendErrorMessageTask(new SendErrorMessage(message, ServerConfig::g_CPUScheduler));
- //sendErrorMessageTask->scheduleTask(sendErrorMessageTask);
-
+#include "ErrorList.h"
+
+#include "../ServerConfig.h"
+
+//#include "Poco/Net/MailMessage.h"
+#include "Poco/Net/MediaType.h"
+
+#include "../SingletonManager/EmailManager.h"
+
+SendErrorMessage::~SendErrorMessage()
+{
+ if (mMessage) {
+ delete mMessage;
+ mMessage = nullptr;
+ }
+}
+
+int SendErrorMessage::run()
+{
+ if (ServerConfig::g_disableEmail) return 0;
+
+ auto mailClientSession = new Poco::Net::SecureSMTPClientSession(ServerConfig::g_EmailAccount.url, ServerConfig::g_EmailAccount.port);
+ mailClientSession->login();
+ mailClientSession->startTLS(ServerConfig::g_SSL_CLient_Context);
+
+
+ mailClientSession->login(Poco::Net::SMTPClientSession::AUTH_LOGIN, ServerConfig::g_EmailAccount.username, ServerConfig::g_EmailAccount.password);
+
+ try {
+ mMessage->setSender(ServerConfig::g_EmailAccount.sender);
+ mailClientSession->sendMessage(*mMessage);
+ mailClientSession->close();
+ }
+ catch (Poco::Exception& exc) {
+ printf("[SendErrorMessage::%s] error sending error message to admin: %s\n",
+ __FUNCTION__, exc.displayText().data());
+ return -1;
+ }
+ return 0;
+}
+
+// ------------------------------------------------------------------------------------
+
+
+ErrorList::ErrorList()
+ : mLogging(Poco::Logger::get("errorLog"))
+{
+
+}
+
+ErrorList::~ErrorList()
+{
+ while (mErrorStack.size() > 0) {
+ delete mErrorStack.top();
+ mErrorStack.pop();
+ }
+}
+
+void ErrorList::addError(Error* error, bool log/* = true */)
+{
+
+ if (log) {
+ std::string dateTimeString = Poco::DateTimeFormatter::format(Poco::DateTime(), "%d.%m.%y %H:%M:%S");
+ mLogging.error("%s [ErrorList::addError] %s", dateTimeString, error->getString(false));
+
+ }
+ mErrorStack.push(error);
+}
+
+Error* ErrorList::getLastError()
+{
+ if (mErrorStack.size() == 0) {
+ return nullptr;
+ }
+
+ Error* error = mErrorStack.top();
+ if (error) {
+ mErrorStack.pop();
+ }
+
+ return error;
+}
+
+void ErrorList::clearErrors()
+{
+ while (mErrorStack.size()) {
+ auto error = mErrorStack.top();
+ if (error) {
+ delete error;
+ }
+ mErrorStack.pop();
+ }
+}
+
+
+int ErrorList::getErrors(ErrorList* send)
+{
+ Error* error = nullptr;
+ int iCount = 0;
+ while (error = send->getLastError()) {
+ addError(error, false);
+ iCount++;
+ }
+ return iCount;
+}
+
+void ErrorList::printErrors()
+{
+ while (mErrorStack.size() > 0) {
+ auto error = mErrorStack.top();
+ mErrorStack.pop();
+ printf(error->getString().data());
+ delete error;
+ }
+}
+
+std::vector ErrorList::getErrorsArray()
+{
+ std::vector result;
+ result.reserve(mErrorStack.size());
+
+ while (mErrorStack.size() > 0) {
+ auto error = mErrorStack.top();
+ mErrorStack.pop();
+ //result->add(error->getString());
+ result.push_back(error->getString());
+ delete error;
+ }
+ return result;
+}
+
+std::string ErrorList::getErrorsHtml()
+{
+ std::string res;
+ res = "";
+ while (mErrorStack.size() > 0) {
+ auto error = mErrorStack.top();
+ mErrorStack.pop();
+ res += "- ";
+ res += error->getHtmlString();
+ res += "
";
+ delete error;
+ }
+ res += "
";
+ return res;
+}
+
+std::string ErrorList::getErrorsHtmlNewFormat()
+{
+ std::string html;
+
+ while (mErrorStack.size() > 0) {
+ auto error = std::unique_ptr(mErrorStack.top());
+ mErrorStack.pop();
+ html += "";
+ html += "report_problem";
+ html += "";
+ html += error->getHtmlString();
+ html += "";
+ html += "
";
+ }
+ return html;
+}
+/*
+
+report_problem
+Der Empfänger wurde nicht auf dem Login-Server gefunden, hat er sein Konto schon angelegt?
+
+*/
+
+
+void ErrorList::sendErrorsAsEmail(std::string rawHtml/* = ""*/)
+{
+ auto em = EmailManager::getInstance();
+ /*auto message = new Poco::Net::MailMessage();
+ message->setSender("gradido_loginServer@gradido.net");
+ message->addRecipient(Poco::Net::MailRecipient(Poco::Net::MailRecipient::PRIMARY_RECIPIENT, "***REMOVED***"));
+ message->setSubject("Error from Gradido Login Server");
+ */
+ std::string content;
+ while (mErrorStack.size() > 0) {
+ auto error = mErrorStack.top();
+ mErrorStack.pop();
+ content += error->getString();
+ delete error;
+ }
+ auto email = new model::Email(content, model::EMAIL_ERROR);
+
+ //message->addContent(new Poco::Net::StringPartSource(content));
+ if (rawHtml != "") {
+ Poco::Net::MediaType mt("text", "html");
+ mt.setParameter("charset", "utf-8");
+
+ email->addContent(new Poco::Net::StringPartSource(rawHtml, mt.toString()));
+ }
+ em->addEmail(email);
+
+ //UniLib::controller::TaskPtr sendErrorMessageTask(new SendErrorMessage(message, ServerConfig::g_CPUScheduler));
+ //sendErrorMessageTask->scheduleTask(sendErrorMessageTask);
+
}
\ No newline at end of file
diff --git a/login_server/src/cpp/lib/ErrorList.h b/login_server/src/cpp/lib/ErrorList.h
index d2913a241..1e937ebfe 100644
--- a/login_server/src/cpp/lib/ErrorList.h
+++ b/login_server/src/cpp/lib/ErrorList.h
@@ -1,76 +1,76 @@
-/*!
-*
-* \author: einhornimmond
-*
-* \date: 07.03.19
-*
-* \brief: error
-*/
-
-#ifndef DR_LUA_WEB_MODULE_ERROR_ERROR_LIST_H
-#define DR_LUA_WEB_MODULE_ERROR_ERROR_LIST_H
-
-#include "Error.h"
-#include
-
-#include "../tasks/CPUTask.h"
-
-#include "Poco/Net/SecureSMTPClientSession.h"
-#include "Poco/Net/StringPartSource.h"
-#include "Poco/Logger.h"
-#include "Poco/JSON/Array.h"
-
-class ErrorList : public IErrorCollection
-{
-public:
- ErrorList();
- ~ErrorList();
-
- // push error, error will be deleted in deconstructor
- virtual void addError(Error* error, bool log = true);
-
- // return error on top of stack, please delete after using
- Error* getLastError();
-
- inline size_t errorCount() { return mErrorStack.size(); }
-
- // delete all errors
- void clearErrors();
-
- static int moveErrors(ErrorList* recv, ErrorList* send) {
- return recv->getErrors(send);
- }
- int getErrors(ErrorList* send);
-
- void printErrors();
- std::string getErrorsHtml();
- std::string getErrorsHtmlNewFormat();
-
- std::vector getErrorsArray();
-
- void sendErrorsAsEmail(std::string rawHtml = "");
-
-protected:
- std::stack mErrorStack;
- // poco logging
- Poco::Logger& mLogging;
-};
-
-class SendErrorMessage : public UniLib::controller::CPUTask
-{
-public:
- SendErrorMessage(Poco::Net::MailMessage* message, UniLib::controller::CPUSheduler* scheduler)
- : UniLib::controller::CPUTask(scheduler), mMessage(message) {}
-
- ~SendErrorMessage();
-
- virtual int run();
- const char* getResourceType() const { return "SendErrorMessage"; };
-
-
-protected:
- Poco::Net::MailMessage* mMessage;
-
-};
-
-#endif // DR_LUA_WEB_MODULE_ERROR_ERROR_LIST_H
+/*!
+*
+* \author: einhornimmond
+*
+* \date: 07.03.19
+*
+* \brief: error
+*/
+
+#ifndef DR_LUA_WEB_MODULE_ERROR_ERROR_LIST_H
+#define DR_LUA_WEB_MODULE_ERROR_ERROR_LIST_H
+
+#include "Error.h"
+#include
+
+#include "../tasks/CPUTask.h"
+
+#include "Poco/Net/SecureSMTPClientSession.h"
+#include "Poco/Net/StringPartSource.h"
+#include "Poco/Logger.h"
+#include "Poco/JSON/Array.h"
+
+class ErrorList : public IErrorCollection
+{
+public:
+ ErrorList();
+ ~ErrorList();
+
+ // push error, error will be deleted in deconstructor
+ virtual void addError(Error* error, bool log = true);
+
+ // return error on top of stack, please delete after using
+ Error* getLastError();
+
+ inline size_t errorCount() { return mErrorStack.size(); }
+
+ // delete all errors
+ void clearErrors();
+
+ static int moveErrors(ErrorList* recv, ErrorList* send) {
+ return recv->getErrors(send);
+ }
+ int getErrors(ErrorList* send);
+
+ void printErrors();
+ std::string getErrorsHtml();
+ std::string getErrorsHtmlNewFormat();
+
+ std::vector getErrorsArray();
+
+ void sendErrorsAsEmail(std::string rawHtml = "");
+
+protected:
+ std::stack mErrorStack;
+ // poco logging
+ Poco::Logger& mLogging;
+};
+
+class SendErrorMessage : public UniLib::controller::CPUTask
+{
+public:
+ SendErrorMessage(Poco::Net::MailMessage* message, UniLib::controller::CPUSheduler* scheduler)
+ : UniLib::controller::CPUTask(scheduler), mMessage(message) {}
+
+ ~SendErrorMessage();
+
+ virtual int run();
+ const char* getResourceType() const { return "SendErrorMessage"; };
+
+
+protected:
+ Poco::Net::MailMessage* mMessage;
+
+};
+
+#endif // DR_LUA_WEB_MODULE_ERROR_ERROR_LIST_H
diff --git a/login_server/src/cpp/main.cpp b/login_server/src/cpp/main.cpp
index be08da820..54de9810b 100644
--- a/login_server/src/cpp/main.cpp
+++ b/login_server/src/cpp/main.cpp
@@ -1,57 +1,57 @@
-#include "Gradido_LoginServer.h"
-#include
-
-#include "proto/gradido/TransactionBody.pb.h"
-
-#include "model/User.h"
-#include "model/Session.h"
-#include "lib/Profiler.h"
-#include "ServerConfig.h"
-#include "ImportantTests.h"
-
-#include "model/table/User.h"
-#include "model/table/EmailOptIn.h"
-
-#include "Poco/DateTimeParser.h"
-
-#ifndef _TEST_BUILD
-
-
-int main(int argc, char** argv)
-{
- GOOGLE_PROTOBUF_VERIFY_VERSION;
- if (sodium_init() < 0) {
- /* panic! the library couldn't be initialized, it is not safe to use */
- printf("error initializing sodium, early exit\n");
- return -1;
- }
-
- std::string dateTimeString = __DATE__;
- //printf("Building date time string: %s\n", dateTimeString.data());
- std::string formatString("%b %d %Y");
- int timeZone = 0;
-
- Poco::DateTime buildDateTime = Poco::DateTimeParser::parse(formatString, dateTimeString, timeZone);
- ServerConfig::g_versionString = Poco::DateTimeFormatter::format(buildDateTime, "0.%y.%m.%d");
- //ServerConfig::g_versionString = "0.20.KW13.02";
- printf("Version: %s\n", ServerConfig::g_versionString.data());
- printf("User size: %d Bytes, Session size: %d Bytes\n", sizeof(User), sizeof(Session));
- printf("model sizes: User: %d Bytes, EmailOptIn: %d Bytes\n", sizeof(model::table::User), sizeof(model::table::EmailOptIn));
-
- // load word lists
- if (!ServerConfig::loadMnemonicWordLists()) {
- //printf("[Gradido_LoginServer::%s] error loading mnemonic Word List\n", __FUNCTION__);
- printf("[Gradido_LoginServer::main] error loading mnemonic Word List");
- return -2;
- }
-
- if (!ImportantTests::passphraseGenerationAndTransformation()) {
- printf("test passphrase generation and transformation failed\n");
- return -3;
- }
-
- Gradido_LoginServer app;
- app.setUnixOptions(true);
- return app.run(argc, argv);
-}
+#include "Gradido_LoginServer.h"
+#include
+
+#include "proto/gradido/TransactionBody.pb.h"
+
+#include "model/User.h"
+#include "model/Session.h"
+#include "lib/Profiler.h"
+#include "ServerConfig.h"
+#include "ImportantTests.h"
+
+#include "model/table/User.h"
+#include "model/table/EmailOptIn.h"
+
+#include "Poco/DateTimeParser.h"
+
+#ifndef _TEST_BUILD
+
+
+int main(int argc, char** argv)
+{
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+ if (sodium_init() < 0) {
+ /* panic! the library couldn't be initialized, it is not safe to use */
+ printf("error initializing sodium, early exit\n");
+ return -1;
+ }
+
+ std::string dateTimeString = __DATE__;
+ //printf("Building date time string: %s\n", dateTimeString.data());
+ std::string formatString("%b %d %Y");
+ int timeZone = 0;
+
+ Poco::DateTime buildDateTime = Poco::DateTimeParser::parse(formatString, dateTimeString, timeZone);
+ ServerConfig::g_versionString = Poco::DateTimeFormatter::format(buildDateTime, "0.%y.%m.%d");
+ //ServerConfig::g_versionString = "0.20.KW13.02";
+ printf("Version: %s\n", ServerConfig::g_versionString.data());
+ printf("User size: %d Bytes, Session size: %d Bytes\n", sizeof(User), sizeof(Session));
+ printf("model sizes: User: %d Bytes, EmailOptIn: %d Bytes\n", sizeof(model::table::User), sizeof(model::table::EmailOptIn));
+
+ // load word lists
+ if (!ServerConfig::loadMnemonicWordLists()) {
+ //printf("[Gradido_LoginServer::%s] error loading mnemonic Word List\n", __FUNCTION__);
+ printf("[Gradido_LoginServer::main] error loading mnemonic Word List");
+ return -2;
+ }
+
+ if (!ImportantTests::passphraseGenerationAndTransformation()) {
+ printf("test passphrase generation and transformation failed\n");
+ return -3;
+ }
+
+ Gradido_LoginServer app;
+ app.setUnixOptions(true);
+ return app.run(argc, argv);
+}
#endif
\ No newline at end of file
diff --git a/login_server/src/cpp/model/Session.cpp b/login_server/src/cpp/model/Session.cpp
index dcad0d40a..3d8a11d76 100644
--- a/login_server/src/cpp/model/Session.cpp
+++ b/login_server/src/cpp/model/Session.cpp
@@ -1,1323 +1,1323 @@
-#include "Session.h"
-#include "../lib/Profiler.h"
-#include "../ServerConfig.h"
-
-#include "Poco/RegularExpression.h"
-#include "Poco/Net/StringPartSource.h"
-#include "Poco/Net/MediaType.h"
-
-#include "../SingletonManager/SessionManager.h"
-#include "../SingletonManager/ConnectionManager.h"
-#include "../SingletonManager/ErrorManager.h"
-#include "../SingletonManager/EmailManager.h"
-#include "../SingletonManager/SingletonTaskObserver.h"
-
-#include "../tasks/SigningTransaction.h"
-#include "../tasks/AuthenticatedEncryptionCreateKeyTask.h"
-#include "../tasks/VerificationEmailResendTask.h"
-
-#include "../lib/JsonRequest.h"
-
-#include "../Crypto/Passphrase.h"
-
-
-#include "../controller/User.h"
-#include "../controller/UserBackups.h"
-#include "../controller/EmailVerificationCode.h"
-
-#include "table/ModelBase.h"
-
-
-#include "sodium.h"
-
-using namespace Poco::Data::Keywords;
-
-int WriteEmailVerification::run()
-{
- auto em = ErrorManager::getInstance();
-
- mEmailVerificationCode->getModel()->setUserId(mUser->getDBId());
- auto emailVerificationModel = mEmailVerificationCode->getModel();
- emailVerificationModel->setUserId(mUser->getDBId());
- if (!emailVerificationModel->insertIntoDB(true) || emailVerificationModel->errorCount() > 0) {
- emailVerificationModel->sendErrorsAsEmail();
- return -1;
- }
-
- return 0;
-}
-
-// ---------------------------------------------------------------------------------------------------------------
-
-int WritePassphraseIntoDB::run()
-{
- Profiler timeUsed;
-
- // TODO: encrypt passphrase, need server admin crypto box pubkey
- //int crypto_box_seal(unsigned char *c, const unsigned char *m,
- //unsigned long long mlen, const unsigned char *pk);
- size_t mlen = mPassphrase.size();
- size_t crypto_size = crypto_box_SEALBYTES + mlen;
-
- auto em = ErrorManager::getInstance();
-
- auto dbSession = ConnectionManager::getInstance()->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
- Poco::Data::Statement insert(dbSession);
- insert << "INSERT INTO user_backups (user_id, passphrase) VALUES(?,?)",
- use(mUserId), use(mPassphrase);
- try {
- if (insert.execute() != 1) {
- em->addError(new ParamError("WritePassphraseIntoDB::run", "inserting passphrase for user failed", std::to_string(mUserId)));
- em->sendErrorsAsEmail();
- }
- }
- catch (Poco::Exception& ex) {
- em->addError(new ParamError("WritePassphraseIntoDB::run", "insert passphrase mysql error", ex.displayText().data()));
- em->sendErrorsAsEmail();
- }
-
- //printf("[WritePassphraseIntoDB] timeUsed: %s\n", timeUsed.string().data());
- return 0;
-}
-
-
-// --------------------------------------------------------------------------------------------------------------
-
-Session::Session(int handle)
- : mHandleId(handle), mSessionUser(nullptr), mState(SESSION_STATE_EMPTY), mActive(false)
-{
-
-}
-
-Session::~Session()
-{
- //printf("[Session::~Session] \n");
- if (tryLock()) {
- unlock();
- reset();
- }
-
-
- //printf("[Session::~Session] finished \n");
-}
-
-
-void Session::reset()
-{
- //printf("[Session::reset]\n");
- lock("Session::reset");
- std::unique_lock _lock(mSharedMutex);
- mSessionUser.assign(nullptr);
- mNewUser.assign(nullptr);
- mEmailVerificationCodeObject.assign(nullptr);
-
- // watch out
- //updateTimeout();
- mLastActivity = Poco::DateTime();
-
- mState = SESSION_STATE_EMPTY;
-
- mPassphrase = "";
- mLastExternReferer = "";
- mClientLoginIP = Poco::Net::IPAddress();
- unlock();
-
- // reset transactions
- mCurrentActiveProcessingTransaction = nullptr;
- mProcessingTransactions.clear();
-
- //printf("[Session::reset] finished\n");
-}
-
-int Session::isActive()
-{
- int ret = 0;
- try {
- mWorkMutex.tryLock(100);
- }
- catch (Poco::TimeoutException &ex) {
- return -1;
- }
- ret = (int)mActive;
- unlock();
- return ret;
-
-}
-
-bool Session::isDeadLocked()
-{
- try {
- mWorkMutex.tryLock(200);
- unlock();
- return false;
- }
- catch (Poco::Exception& ex) {
-
- }
- return true;
-}
-
-bool Session::setActive(bool active)
-{
- try {
- mWorkMutex.tryLock(100);
- }
- catch (Poco::TimeoutException &ex) {
- return false;
- }
- mActive = active;
- unlock();
- return true;
-}
-
-void Session::updateTimeout()
-{
- lock("Session::updateTimeout");
- mLastActivity = Poco::DateTime();
- unlock();
-}
-
-Poco::AutoPtr Session::getEmailVerificationCodeObject()
-{
- lock("Session::getEmailVerificationCodeObject");
- std::shared_lock _lock(mSharedMutex);
- auto ret = mEmailVerificationCodeObject;
- unlock();
- return ret;
-}
-
-bool Session::adminCreateUser(const std::string& first_name, const std::string& last_name, const std::string& email)
-{
- Profiler usedTime;
-
- if (mNewUser->getModel()->getRole() != model::table::ROLE_ADMIN) {
- addError(new Error(gettext("Benutzer"), gettext("Eingeloggter Benutzer ist kein Admin")), false);
- return false;
- }
-
- auto sm = SessionManager::getInstance();
- if (!sm->isValid(first_name, VALIDATE_NAME)) {
- addError(new Error(gettext("Vorname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
- return false;
- }
- if (!sm->isValid(last_name, VALIDATE_NAME)) {
- addError(new Error(gettext("Nachname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
- return false;
- }
- if (!sm->isValid(email, VALIDATE_EMAIL)) {
- addError(new Error(gettext("E-Mail"), gettext("Bitte gebe eine gültige E-Mail Adresse an.")), false);
- return false;
- }
-
-
- // check if user with that email already exist
- if (mNewUser->getModel()->isExistInDB("email", email)) {
- addError(new Error(gettext("E-Mail"), gettext("Für diese E-Mail Adresse gibt es bereits einen Account")), false);
- return false;
- }
-
- auto newUser = controller::User::create(email, first_name, last_name);
- updateTimeout();
-
-
- auto newUserModel = newUser->getModel();
- if (!newUserModel->insertIntoDB(true)) {
- addError(new Error(gettext("Benutzer"), gettext("Fehler beim speichern!")));
- return false;
- }
-
- auto email_verification_code = controller::EmailVerificationCode::create(newUserModel->getID(), model::table::EMAIL_OPT_IN_REGISTER);
- if (!email_verification_code->getModel()->insertIntoDB(false)) {
- addError(new Error(gettext("Email Verification Code"), gettext("Fehler beim speichern!")));
- return false;
- }
-
- EmailManager::getInstance()->addEmail(new model::Email(email_verification_code, newUser, model::EMAIL_ADMIN_USER_VERIFICATION_CODE));
-
- std::unique_lock _lock(mSharedMutex);
- mEmailVerificationCodeObject = email_verification_code;
-
-
- return true;
-}
-//
-bool Session::createUser(const std::string& first_name, const std::string& last_name, const std::string& email, const std::string& password)
-{
- Profiler usedTime;
- auto sm = SessionManager::getInstance();
- if (!sm->isValid(first_name, VALIDATE_NAME)) {
- addError(new Error(gettext("Vorname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
- return false;
- }
- if (!sm->isValid(last_name, VALIDATE_NAME)) {
- addError(new Error(gettext("Nachname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
- return false;
- }
- if (!sm->isValid(email, VALIDATE_EMAIL)) {
- addError(new Error(gettext("E-Mail"), gettext("Bitte gebe eine gültige E-Mail Adresse an.")), false);
- return false;
- }
- if (!sm->checkPwdValidation(password, this)) {
- return false;
- }
- /*if (passphrase.size() > 0 && !sm->isValid(passphrase, VALIDATE_PASSPHRASE)) {
- addError(new Error("Merkspruch", "Der Merkspruch ist nicht gültig, er besteht aus 24 Wörtern, mit Komma getrennt."));
- return false;
- }
- if (passphrase.size() == 0) {
- //mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER]);
- mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER]);
- }
- else {
- //mPassphrase = passphrase;
- }*/
-
- // check if user with that email already exist
-
- auto dbConnection = ConnectionManager::getInstance()->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
- Poco::Data::Statement select(dbConnection);
- select << "SELECT email from users where email = ?;", useRef(email);
- try {
- if (select.execute() > 0) {
- addError(new Error(gettext("E-Mail"), gettext("Für diese E-Mail Adresse gibt es bereits einen Account")), false);
- return false;
- }
- }
- catch (Poco::Exception& exc) {
- printf("mysql exception: %s\n", exc.displayText().data());
- }
-
- mSessionUser = new User(email.data(), first_name.data(), last_name.data());
- mNewUser = controller::User::create(email, first_name, last_name);
- updateTimeout();
-
- // Prepare E-Mail
- //UniLib::controller::TaskPtr prepareEmail(new PrepareEmailTask(ServerConfig::g_CPUScheduler));
- //prepareEmail->scheduleTask(prepareEmail);
-
- // create user crypto key
- UniLib::controller::TaskPtr cryptoKeyTask(new UserCreateCryptoKey(mSessionUser, mNewUser, password, ServerConfig::g_CryptoCPUScheduler));
- cryptoKeyTask->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_CRYPTO_KEY_GENERATED, this));
- cryptoKeyTask->scheduleTask(cryptoKeyTask);
-
- // depends on crypto key, write user record into db
- UniLib::controller::TaskPtr writeUserIntoDB(new UserWriteIntoDB(mSessionUser, ServerConfig::g_CPUScheduler, 1));
- writeUserIntoDB->setParentTaskPtrInArray(cryptoKeyTask, 0);
- writeUserIntoDB->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_USER_WRITTEN, this));
- writeUserIntoDB->scheduleTask(writeUserIntoDB);
-
- std::unique_lock _lock(mSharedMutex);
- mEmailVerificationCodeObject = controller::EmailVerificationCode::create(model::table::EMAIL_OPT_IN_REGISTER);
- UniLib::controller::TaskPtr writeEmailVerification(new WriteEmailVerification(mSessionUser, mEmailVerificationCodeObject, ServerConfig::g_CPUScheduler, 1));
-
- writeEmailVerification->setParentTaskPtrInArray(writeUserIntoDB, 0);
- writeEmailVerification->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_EMAIL_VERIFICATION_WRITTEN, this));
- writeEmailVerification->scheduleTask(writeEmailVerification);
-
-
- /*printf("LastName: %s\n", last_name.data());
- for (int i = 0; i < last_name.size(); i++) {
- char c = last_name.data()[i];
- //printf("%d ", c);
- }
- //printf("\n\n");
- */
-
- // depends on writeUser because need user_id, write email verification into db
- /*auto message = new Poco::Net::MailMessage;
- Poco::Net::MediaType mt("text", "plain");
- mt.setParameter("charset", "utf-8");
- message->setContentType(mt);
-
- message->addRecipient(Poco::Net::MailRecipient(Poco::Net::MailRecipient::PRIMARY_RECIPIENT, email));
- message->setSubject(gettext("Gradido: E-Mail Verification"));
- std::stringstream ss;
- ss << "Hallo " << first_name << " " << last_name << "," << std::endl << std::endl;
- ss << "Du oder jemand anderes hat sich soeben mit dieser E-Mail Adresse bei Gradido registriert. " << std::endl;
- ss << "Wenn du es warst, klicke bitte auf den Link: " << ServerConfig::g_serverPath << "/checkEmail/" << mEmailVerificationCode << std::endl;
- //ss << "oder kopiere den Code: " << mEmailVerificationCode << " selbst dort hinein." << std::endl;
- ss << "oder kopiere den obigen Link in Dein Browserfenster." << std::endl;
- ss << std::endl;
- ss << "Mit freundlichen " << u8"Grüßen" << std::endl;
- ss << "Dario, Gradido Server Admin" << std::endl;
-
-
- message->addContent(new Poco::Net::StringPartSource(ss.str()));
- */
- //UniLib::controller::TaskPtr sendEmail(new SendEmailTask(message, ServerConfig::g_CPUScheduler, 1));
- //Email(AutoPtr emailVerification, AutoPtr user, EmailType type);
- auto em = EmailManager::getInstance();
- em->addEmail(new model::Email(mEmailVerificationCodeObject, mNewUser, model::EMAIL_USER_VERIFICATION_CODE));
- /*UniLib::controller::TaskPtr sendEmail(new SendEmailTask(new model::Email(mEmailVerificationCodeObject, mNewUser, model::EMAIL_USER_VERIFICATION_CODE), ServerConfig::g_CPUScheduler, 1));
- //sendEmail->setParentTaskPtrInArray(prepareEmail, 0);
- sendEmail->setParentTaskPtrInArray(writeEmailVerification, 0);
- sendEmail->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_EMAIL_VERIFICATION_SEND, this));
- sendEmail->scheduleTask(sendEmail);
- */
- // write user into db
- // generate and write email verification into db
- // send email
-
- //printf("[Session::createUser] time: %s\n", usedTime.string().data());
-
- return true;
-}
-
-bool Session::createUserDirect(const std::string& first_name, const std::string& last_name, const std::string& email, const std::string& password)
-{
- std::unique_lock _lock(mSharedMutex);
- static const char* function_name = "Session::createUserDirect";
- auto sm = SessionManager::getInstance();
- auto em = ErrorManager::getInstance();
- auto email_manager = EmailManager::getInstance();
-
- if (!sm->isValid(first_name, VALIDATE_NAME)) {
- addError(new Error(gettext("Vorname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
- return false;
- }
- if (!sm->isValid(last_name, VALIDATE_NAME)) {
- addError(new Error(gettext("Nachname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
- return false;
- }
- if (!sm->isValid(email, VALIDATE_EMAIL)) {
- addError(new Error(gettext("E-Mail"), gettext("Bitte gebe eine gültige E-Mail Adresse an.")), false);
- return false;
- }
- if (!sm->checkPwdValidation(password, this)) {
- return false;
- }
-
- // check if email already exist
- auto user = controller::User::create();
- if (user->load(email) >= 1) {
- addError(new Error(gettext("E-Mail"), gettext("Für diese E-Mail Adresse gibt es bereits ein Konto")), false);
- return false;
- }
-
- // user
- mNewUser = controller::User::create(email, first_name, last_name);
- auto user_model = mNewUser->getModel();
- user_model->insertIntoDB(true);
- auto user_id = user_model->getID();
-
-
- // one retry in case of connection error
- if (!user_id) {
- user_model->insertIntoDB(true);
- auto user_id = user_model->getID();
- if (!user_id) {
- em->addError(new ParamError(function_name, "error saving new user in db, after one retry with email", email));
- em->sendErrorsAsEmail();
- addError(new Error(gettext("Server"), gettext("Fehler beim speichen des Kontos bitte versuche es später noch einmal")), false);
- return false;
- }
- }
-
- generateKeys(true, true);
-
- // calculate encryption key, could need some time, will save encrypted privkey to db
- UniLib::controller::TaskPtr create_authenticated_encrypten_key = new AuthenticatedEncryptionCreateKeyTask(mNewUser, password);
- create_authenticated_encrypten_key->scheduleTask(create_authenticated_encrypten_key);
-
- // email verification code
- auto email_verification = controller::EmailVerificationCode::create(user_id, model::table::EMAIL_OPT_IN_REGISTER_DIRECT);
- email_verification->getModel()->insertIntoDB(false);
- mEmailVerificationCodeObject = email_verification;
-
- auto _7days_later = Poco::DateTime() + Poco::Timespan(7, 0, 0, 0, 0);
- ServerConfig::g_CronJobsTimer.schedule(new VerificationEmailResendTimerTask(user_id), Poco::Timestamp(_7days_later.timestamp()));
-
- email_manager->addEmail(new model::Email(email_verification, mNewUser, model::EMAIL_USER_VERIFICATION_CODE));
-
- return true;
-}
-
-bool Session::ifUserExist(const std::string& email)
-{
- auto em = ErrorManager::getInstance();
- const char* funcName = "Session::ifUserExist";
- auto dbConnection = ConnectionManager::getInstance()->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
- Poco::Data::Statement select(dbConnection);
- bool emailChecked = false;
- int userId = 0;
- select << "SELECT email_checked, id from users where email = ? and email_checked = 1",
- into(emailChecked), into(userId), useRef(email);
-
- try {
- if(select.execute() == 1) return true;
- }
- catch (Poco::Exception& ex) {
- em->addError(new ParamError(funcName, "select user from email verification code mysql error ", ex.displayText().data()));
- em->sendErrorsAsEmail();
- }
- return false;
-}
-
-int Session::updateEmailVerification(Poco::UInt64 emailVerificationCode)
-{
- const static char* funcName = "Session::updateEmailVerification";
- Poco::ScopedLock _lock(mWorkMutex);
- // new mutex, will replace the Poco Mutex complete in the future
- std::unique_lock _lock_shared(mSharedMutex);
- Profiler usedTime;
-
- auto em = ErrorManager::getInstance();
- if (mEmailVerificationCodeObject.isNull()) {
- em->addError(new Error(funcName, "email verification object is zero"));
- em->sendErrorsAsEmail();
-
- return -2;
- }
- auto email_verification_code_model = mEmailVerificationCodeObject->getModel();
- assert(email_verification_code_model);
- if(email_verification_code_model->getCode() == emailVerificationCode) {
- if (mSessionUser && mSessionUser->getDBId() == 0) {
- //addError(new Error("E-Mail Verification", "Benutzer wurde nicht richtig gespeichert, bitte wende dich an den Server-Admin"));
- em->addError(new Error(funcName, "user exist with 0 as id"));
- em->sendErrorsAsEmail();
-
- //return false;
- return -2;
- }
-
- // load correct user from db
- if (mNewUser.isNull() || !mNewUser->getModel() || mNewUser->getModel()->getID() != email_verification_code_model->getUserId()) {
- mNewUser = controller::User::create();
- if (1 != mNewUser->load(email_verification_code_model->getUserId())) {
- em->addError(new ParamError(funcName, "user load didn't return 1 with user_id ", email_verification_code_model->getUserId()));
- em->sendErrorsAsEmail();
-
- return -2;
- }
- }
-
- auto user_model = mNewUser->getModel();
- assert(user_model);
- bool first_email_activation = false;
- auto verification_type = email_verification_code_model->getType();
- if (model::table::EMAIL_OPT_IN_REGISTER == verification_type ||
- model::table::EMAIL_OPT_IN_EMPTY == verification_type ||
- model::table::EMAIL_OPT_IN_REGISTER_DIRECT == verification_type) {
- first_email_activation = true;
- }
- if (first_email_activation && user_model->isEmailChecked()) {
- mSessionUser = new User(mNewUser);
- addError(new Error(gettext("E-Mail Verification"), gettext("Du hast dein Konto bereits aktiviert!")), false);
-
- return 1;
- }
- if (first_email_activation) {
- user_model->setEmailChecked(true);
-
- user_model->updateIntoDB("email_checked", 1);
- if (user_model->errorCount() > 0) {
- user_model->sendErrorsAsEmail();
- }
-
- // no find all active sessions
-
- updateState(SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED);
- return 0;
- }
-
- if (email_verification_code_model->getType() == model::table::EMAIL_OPT_IN_RESET_PASSWORD) {
-
- if (mEmailVerificationCodeObject->deleteFromDB()) {
- mEmailVerificationCodeObject.assign(nullptr);
- }
- else {
- em->getErrors(mEmailVerificationCodeObject->getModel());
- em->addError(new Error(funcName, "error deleting email verification code"));
- em->sendErrorsAsEmail();
- return -2;
- }
- updateState(SESSION_STATE_RESET_PASSWORD_REQUEST);
- return 0;
- }
-
- em->addError(new Error(funcName, "invalid code path"));
- em->sendErrorsAsEmail();
-
- return -2;
-
- /*if (updated_rows == 1) {
- Poco::Data::Statement delete_row(dbConnection);
- delete_row << "DELETE FROM email_opt_in where verification_code = ?", use(emailVerificationCode);
- if (delete_row.execute() != 1) {
- em->addError(new Error(funcName, "delete from email_opt_in entry didn't work as expected, please check db"));
- em->sendErrorsAsEmail();
- }
- if (mSessionUser) {
- mSessionUser->setEmailChecked();
- mSessionUser->setLanguage(getLanguage());
- }
- updateState(SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED);
- //printf("[%s] time: %s\n", funcName, usedTime.string().data());
- unlock();
- return true;
- }
- else {
- em->addError(new ParamError(funcName, "update user work not like expected, updated row count", updated_rows));
- em->sendErrorsAsEmail();
- }*/
-
-
- }
- else {
- addError(new Error(gettext("E-Mail Verification"), gettext("Falscher Code für aktiven Login")));
- //printf("[%s] time: %s\n", funcName, usedTime.string().data());
-
- return -1;
- }
- //printf("[%s] time: %s\n", funcName, usedTime.string().data());
-
- return 0;
-}
-
-
-int Session::sendResetPasswordEmail(Poco::AutoPtr user, bool passphraseMemorized)
-{
- mNewUser = user;
- mSessionUser = new User(user);
- auto em = EmailManager::getInstance();
-
- std::unique_lock _lock(mSharedMutex);
-
- // creating email verification code also for user without passphrase
- // first check if already exist
- // check if email was already send shortly before
- bool frequent_resend = false;
- bool email_already_send = false;
-
- mEmailVerificationCodeObject = controller::EmailVerificationCode::load(user->getModel()->getID(), model::table::EMAIL_OPT_IN_RESET_PASSWORD);
- if (mEmailVerificationCodeObject.isNull()) {
- mEmailVerificationCodeObject = controller::EmailVerificationCode::create(mNewUser->getModel()->getID(), model::table::EMAIL_OPT_IN_RESET_PASSWORD);
- mEmailVerificationCodeObject->getModel()->insertIntoDB(false);
- }
- else {
- email_already_send = true;
- }
- auto email_verification_model = mEmailVerificationCodeObject->getModel();
- if (email_already_send) {
- auto time_elapsed = Poco::DateTime() - email_verification_model->getUpdated();
- if (time_elapsed.totalHours() < 1) {
- frequent_resend = true;
- }
- }
-
- if (!frequent_resend) {
- if (passphraseMemorized) {
- em->addEmail(new model::Email(mEmailVerificationCodeObject, mNewUser, model::EMAIL_USER_RESET_PASSWORD));
- }
- else {
- em->addEmail(new model::Email(user, model::EMAIL_ADMIN_RESET_PASSWORD_REQUEST_WITHOUT_MEMORIZED_PASSPHRASE));
- }
- }
-
- if (frequent_resend) return 2;
- if (email_already_send) return 1;
-
- return 0;
-}
-
-int Session::comparePassphraseWithSavedKeys(const std::string& inputPassphrase, Mnemonic* wordSource)
-{
- KeyPair keys;
- static const char* functionName = "Session::comparePassphraseWithSavedKeys";
- if (!wordSource) {
- addError(new Error(functionName, "wordSource is empty"));
- sendErrorsAsEmail();
- return -2;
- }
- if (!keys.generateFromPassphrase(inputPassphrase.data(), wordSource)) {
- addError(new ParamError(functionName, "invalid passphrase", inputPassphrase));
- if (!mNewUser.isNull() && mNewUser->getModel()) {
- addError(new ParamError(functionName, "user email", mNewUser->getModel()->getEmail()));
- }
- sendErrorsAsEmail();
- addError(new Error(gettext("Passphrase"), gettext("Deine Passphrase ist ungütig")), false);
- return 0;
- }
- auto userModel = mNewUser->getModel();
- auto existingPublic = userModel->getPublicKey();
- if (!existingPublic) {
- userModel->loadFromDB("email", userModel->getEmail());
- existingPublic = userModel->getPublicKey();
- if (!existingPublic) {
- addError(new Error(functionName, "cannot load existing public key from db"));
- addError(new ParamError(functionName, "user email", userModel->getEmail()));
- sendErrorsAsEmail();
- addError(new Error(gettext("Passphrase"), gettext("Ein Fehler trat auf, bitte versuche es erneut")), false);
- return -1;
- }
- }
- if (0 == memcmp(userModel->getPublicKey(), keys.getPublicKey(), crypto_sign_PUBLICKEYBYTES)) {
- mPassphrase = inputPassphrase;
- return 1;
- }
- addError(new Error(gettext("Passphrase"), gettext("Das ist nicht die richtige Passphrase.")), false);
- return 0;
-}
-
-bool Session::startProcessingTransaction(const std::string& proto_message_base64, bool autoSign/* = false*/)
-{
- static const char* funcName = "Session::startProcessingTransaction";
- lock(funcName);
- HASH hs = ProcessingTransaction::calculateHash(proto_message_base64);
- // check if it is already running or waiting
- for (auto it = mProcessingTransactions.begin(); it != mProcessingTransactions.end(); it++) {
- if (it->isNull()) {
- it = mProcessingTransactions.erase(it);
- }
- if (hs == (*it)->getHash()) {
- addError(new Error(funcName, "transaction already in list"));
- unlock();
- return false;
- }
- }
- if (mSessionUser.isNull() || !mSessionUser->getEmail()) {
- addError(new Error(funcName, "user is zero"));
- unlock();
- return false;
- }
-
- Poco::AutoPtr processorTask(
- new ProcessingTransaction(
- proto_message_base64,
- DRMakeStringHash(mSessionUser->getEmail()),
- mSessionUser->getLanguage())
- );
- if (autoSign && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_AUTO_SIGN_TRANSACTIONS) == ServerConfig::UNSECURE_AUTO_SIGN_TRANSACTIONS) {
- if (processorTask->run() != 0) {
- getErrors(processorTask);
- unlock();
- return false;
- }
- Poco::AutoPtr signingTransaction(new SigningTransaction(processorTask, mNewUser));
- //signingTransaction->scheduleTask(signingTransaction);
- if (signingTransaction->run() != 0) {
- getErrors(signingTransaction);
- unlock();
- return false;
- }
-
- }
- else {
- processorTask->scheduleTask(processorTask);
- mProcessingTransactions.push_back(processorTask);
- }
- unlock();
- return true;
-
-}
-
-Poco::AutoPtr Session::getNextReadyTransaction(size_t* working/* = nullptr*/)
-{
- lock("Session::getNextReadyTransaction");
- if (working) {
- *working = 0;
- }
- else if (!mCurrentActiveProcessingTransaction.isNull())
- {
- unlock();
- return mCurrentActiveProcessingTransaction;
- }
- for (auto it = mProcessingTransactions.begin(); it != mProcessingTransactions.end(); it++) {
- if (working && !(*it)->isTaskFinished()) {
- (*working)++;
- }
- if (mCurrentActiveProcessingTransaction.isNull() && (*it)->isTaskFinished()) {
- if (!working) {
- mCurrentActiveProcessingTransaction = *it;
- unlock();
- return mCurrentActiveProcessingTransaction;
- }
- // no early exit
- else {
- mCurrentActiveProcessingTransaction = *it;
- }
-
- }
- }
- unlock();
- return mCurrentActiveProcessingTransaction;
-}
-
-bool Session::finalizeTransaction(bool sign, bool reject)
-{
- int result = -1;
- lock("Session::finalizeTransaction");
- if (mCurrentActiveProcessingTransaction.isNull()) {
- unlock();
- return false;
- }
- mProcessingTransactions.remove(mCurrentActiveProcessingTransaction);
-
- if (!reject) {
- if (sign) {
- Poco::AutoPtr signingTransaction(new SigningTransaction(mCurrentActiveProcessingTransaction, mNewUser));
- //signingTransaction->scheduleTask(signingTransaction);
- result = signingTransaction->run();
- }
- }
- mCurrentActiveProcessingTransaction.assign(nullptr);
- unlock();
- return result == 0;
-}
-
-size_t Session::getProcessingTransactionCount()
-{
- size_t count = 0;
- lock("Session::getProcessingTransactionCount");
-
- for (auto it = mProcessingTransactions.begin(); it != mProcessingTransactions.end(); it++) {
-
- (*it)->lock();
- if ((*it)->errorCount() > 0) {
- (*it)->sendErrorsAsEmail();
- (*it)->unlock();
- it = mProcessingTransactions.erase(it);
- if (it == mProcessingTransactions.end()) break;
- }
- else {
- (*it)->unlock();
- }
-
- }
- count = mProcessingTransactions.size();
- unlock();
- return count;
-}
-
-bool Session::isPwdValid(const std::string& pwd)
-{
- if (mSessionUser) {
- return mSessionUser->validatePwd(pwd, this);
- }
- return false;
-}
-
-UserStates Session::loadUser(const std::string& email, const std::string& password)
-{
- static const char* functionName = "Session::loadUser";
- auto observer = SingletonTaskObserver::getInstance();
- if (email != "") {
- if (observer->getTaskCount(email, TASK_OBSERVER_PASSWORD_CREATION) > 0) {
- return USER_PASSWORD_ENCRYPTION_IN_PROCESS;
- }
- }
- //Profiler usedTime;
- //printf("before lock\n");
- lock(functionName);
- //printf("locked \n");
- if (!mSessionUser.isNull() && mSessionUser->getEmail() != email) {
- mSessionUser.assign(nullptr);
- mNewUser.assign(nullptr);
- //printf("user nullptr assigned\n");
- }
- //printf("after checking if session user is null\n");
- //if (!mSessionUser) {
- if (mNewUser.isNull()) {
- //printf("new user is null\n");
- mNewUser = controller::User::create();
- //printf("new user created\n");
- // load user for email only once from db
- mNewUser->load(email);
- //printf("load new user from db with email: %s\n", email.data());
- mSessionUser = new User(mNewUser);
- //mSessionUser = new User(email.data());
-
- //printf("user loaded from email\n");
- }
- //printf("before get model\n");
- auto user_model = mNewUser->getModel();
- if (user_model && user_model->isDisabled()) {
- return USER_DISABLED;
- }
- //printf("before if login\n");
- if (!mSessionUser.isNull() && mSessionUser->getUserState() >= USER_LOADED_FROM_DB) {
- //printf("before login\n");
- int loginResult = 0;
- int exitCount = 0;
- do {
- loginResult = mNewUser->login(password);
- Poco::Thread::sleep(100);
- exitCount++;
- } while (-3 == loginResult && exitCount < 15);
- if (exitCount > 1) {
- addError(new ParamError(functionName, "login succeed, retrys: ", exitCount));
- addError(new ParamError(functionName, "email: ", email));
- sendErrorsAsEmail();
- }
-
- if (exitCount >= 15)
- {
- auto running_password_creations = observer->getTasksCount(TASK_OBSERVER_PASSWORD_CREATION);
-
- addError(new ParamError(functionName, "login failed after 15 retrys and 100 ms sleep between, currently running passwort creation tasks: ", running_password_creations));
- addError(new ParamError(functionName, "email: ", email));
- sendErrorsAsEmail();
- return USER_PASSWORD_ENCRYPTION_IN_PROCESS;
- }
-
- //printf("new user login with result: %d\n", loginResult);
-
- if (-1 == loginResult) {
- addError(new Error(functionName, "error in user data set, saved pubkey didn't match extracted pubkey from private key"));
- addError(new ParamError(functionName, "user email", mNewUser->getModel()->getEmail()));
- sendErrorsAsEmail();
- //unlock();
- //return USER_KEYS_DONT_MATCH;
- }
- if (0 == loginResult) {
- unlock();
- return USER_PASSWORD_INCORRECT;
- }
- // error decrypting private key
- if (-2 == loginResult) {
- // check if we have access to the passphrase, if so we can reencrypt the private key
- printf("try reencrypting key\n");
- auto user_model = mNewUser->getModel();
- auto user_backups = controller::UserBackups::load(user_model->getID());
- for (auto it = user_backups.begin(); it != user_backups.end(); it++) {
- auto key = std::unique_ptr((*it)->createGradidoKeyPair());
- if (key->isTheSame(user_model->getPublicKey()))
- {
-
- // set valid key pair
- if (1 == mNewUser->setGradidoKeyPair(key.release())) {
- // save new encrypted private key
- user_model->updatePrivkey();
- }
- else {
- auto em = ErrorManager::getInstance();
- em->addError(new Error(functionName, "error reencrypt private key"));
- em->addError(new ParamError(functionName, "for user with email", user_model->getEmail()));
- em->sendErrorsAsEmail();
- }
- break;
- }
- }
- }
- // can be removed if session user isn't used any more
- // don't calculate password two times anymore
- mSessionUser->login(mNewUser);
- //printf("after old user login\n");
- /*if (mNewUser->getModel()->getPasswordHashed() && !mSessionUser->validatePwd(password, this)) {
- unlock();
- return USER_PASSWORD_INCORRECT;
- }*/
- }
- else {
- //printf("before sleep\n");
- User::fakeCreateCryptoKey();
- }
-
- /*if (!mSessionUser->validatePwd(password, this)) {
- addError(new Error("Login", "E-Mail oder Passwort nicht korrekt, bitte versuche es erneut!"));
- unlock();
- return false;
- }
- if (!mSessionUser->isEmailChecked()) {
- addError(new Error("Account", "E-Mail Adresse wurde noch nicht bestätigt, hast du schon eine E-Mail erhalten?"));
- unlock();
- return false;
- }*/
- //printf("before detect session state\n");
- detectSessionState();
- unlock();
- //printf("before return user state\n");
- return mSessionUser->getUserState();
-}
-
-bool Session::deleteUser()
-{
- lock("Session::deleteUser");
- bool bResult = false;
- if(mSessionUser) {
- JsonRequest phpServerRequest(ServerConfig::g_php_serverHost, 443);
- Poco::Net::NameValueCollection payload;
- payload.add("user", std::string(mSessionUser->getPublicKeyHex()));
- //auto ret = phpServerRequest.request("userDelete", payload);
- JsonRequestReturn ret = JSON_REQUEST_RETURN_OK;
- if (ret == JSON_REQUEST_RETURN_ERROR) {
- addError(new Error("Session::deleteUser", "php server error"));
- getErrors(&phpServerRequest);
- sendErrorsAsEmail();
- }
- else if (ret == JSON_REQUEST_RETURN_OK) {
- bResult = mSessionUser->deleteFromDB();
- }
- else {
- addError(new Error(gettext("Benutzer"), gettext("Konnte Community Server nicht erreichen. E-Mail an den Admin ist raus.")));
- unlock();
- return false;
- }
- }
- if(!bResult) {
- addError(new Error(gettext("Benutzer"), gettext("Fehler beim Löschen des Accounts. Bitte logge dich erneut ein und versuche es nochmal.")));
- }
- unlock();
- return bResult;
-}
-
-void Session::setLanguage(Languages lang)
-{
- //printf("[Session::setLanguage] new language: %d\n", lang);
- lock("Session::setLanguage");
- if (mLanguageCatalog.isNull() || mLanguageCatalog->getLanguage() != lang) {
- auto lm = LanguageManager::getInstance();
- mLanguageCatalog = lm->getFreeCatalog(lang);
- }
- unlock();
-}
-
-Languages Session::getLanguage()
-{
- Languages lang = LANG_NULL;
- lock("Session::getLanguage");
- if (!mLanguageCatalog.isNull()) {
- lang = mLanguageCatalog->getLanguage();
- }
- unlock();
- return lang;
-}
-
-
-/*
-SESSION_STATE_CRYPTO_KEY_GENERATED,
-SESSION_STATE_USER_WRITTEN,
-SESSION_STATE_EMAIL_VERIFICATION_WRITTEN,
-SESSION_STATE_EMAIL_VERIFICATION_SEND,
-SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED,
-SESSION_STATE_PASSPHRASE_GENERATED,
-SESSION_STATE_PASSPHRASE_SHOWN,
-SESSION_STATE_PASSPHRASE_WRITTEN,
-SESSION_STATE_KEY_PAIR_GENERATED,
-SESSION_STATE_KEY_PAIR_WRITTEN,
-SESSION_STATE_COUNT
-*/
-void Session::detectSessionState()
-{
- if (mSessionUser.isNull() || !mSessionUser->hasCryptoKey()) {
- return;
- }
- UserStates userState = mSessionUser->getUserState();
-
- int checkEmail = -1, resetPasswd = -1;
- try {
- auto emailVerificationCodeObjects = controller::EmailVerificationCode::load(mSessionUser->getDBId());
-
- for (int i = 0; i < emailVerificationCodeObjects.size(); i++) {
- auto type = emailVerificationCodeObjects[i]->getModel()->getType();
- if (type == model::table::EMAIL_OPT_IN_EMPTY || type == model::table::EMAIL_OPT_IN_REGISTER) {
- checkEmail = i;
- }
- else if (type == model::table::EMAIL_OPT_IN_RESET_PASSWORD) {
- resetPasswd = i;
- }
- }
- std::unique_lock _lock_shared(mSharedMutex);
- if (resetPasswd != -1) {
- mEmailVerificationCodeObject = emailVerificationCodeObjects[resetPasswd];
- }
- else if (checkEmail != -1) {
- mEmailVerificationCodeObject = emailVerificationCodeObjects[checkEmail];
- }
-
- }
- catch (Poco::Exception& ex) {
- printf("[Session::detectSessionState] exception: %s\n", ex.displayText().data());
- //return;
- }
-
- if (userState <= USER_EMAIL_NOT_ACTIVATED) {
-
- if (checkEmail != -1) {
- updateState(SESSION_STATE_EMAIL_VERIFICATION_WRITTEN);
- return;
- }
-
- updateState(SESSION_STATE_USER_WRITTEN);
- return;
- }
-
- if (USER_NO_KEYS == userState) {
-
- auto user_id = mSessionUser->getDBId();
- auto userBackups = controller::UserBackups::load(user_id);
-
- // check passphrase, only possible while passphrase isn't crypted in db
- bool correctPassphraseFound = false;
- // always trigger SESSION_STATE_PASSPHRASE_WRITTEN, else lost of data possible
- bool cryptedPassphrase = userBackups.size() > 0;
- for (auto it = userBackups.begin(); it != userBackups.end(); it++) {
- KeyPair keys;
- auto passphrase = (*it)->getModel()->getPassphrase();
- Mnemonic* wordSource = nullptr;
- if (User::validatePassphrase(passphrase, &wordSource)) {
- if (keys.generateFromPassphrase((*it)->getModel()->getPassphrase().data(), wordSource)) {
- if (sodium_memcmp(mSessionUser->getPublicKey(), keys.getPublicKey(), ed25519_pubkey_SIZE) == 0) {
- correctPassphraseFound = true;
- break;
- }
- }
- }
- else {
- cryptedPassphrase = true;
- }
- }
- /*
- auto dbConnection = ConnectionManager::getInstance()->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
- Poco::Data::Statement select(dbConnection);
- Poco::Nullable passphrase;
-
- select << "SELECT passphrase from user_backups where user_id = ?;",
- into(passphrase), use(user_id);
- try {
- if (select.execute() == 1 && !passphrase.isNull()) {
- //KeyPair keys;keys.generateFromPassphrase(passphrase.value().rawContent())
- updateState(SESSION_STATE_PASSPHRASE_WRITTEN);
- return;
- }
- }
- catch (Poco::Exception& exc) {
- printf("[Session::detectSessionState] 2 mysql exception: %s\n", exc.displayText().data());
- }*/
- if (correctPassphraseFound || cryptedPassphrase) {
- updateState(SESSION_STATE_PASSPHRASE_WRITTEN);
- return;
- }
- if (mPassphrase != "") {
- updateState(SESSION_STATE_PASSPHRASE_GENERATED);
- return;
- }
- updateState(SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED);
- return;
- }
-
- updateState(SESSION_STATE_KEY_PAIR_WRITTEN);
-
- if (resetPasswd != -1) {
- // don't go to reset password screen after login, only throw checkEmail
- //updateState(SESSION_STATE_RESET_PASSWORD_REQUEST);
- return;
- }
-
-}
-
-Poco::Net::HTTPCookie Session::getLoginCookie()
-{
- auto keks = Poco::Net::HTTPCookie("GRADIDO_LOGIN", std::to_string(mHandleId));
- // prevent reading or changing cookie with js
-// keks.setHttpOnly();
-
- keks.setPath("/");
- // send cookie only via https, on linux, except in test builds
-#ifndef WIN32
- if (ServerConfig::SERVER_TYPE_PRODUCTION == ServerConfig::g_ServerSetupType ||
- ServerConfig::SERVER_TYPE_STAGING == ServerConfig::g_ServerSetupType) {
- keks.setSecure(true);
- }
-#endif
-
- return keks;
-}
-
-bool Session::loadFromEmailVerificationCode(Poco::UInt64 emailVerificationCode)
-{
- Profiler usedTime;
- auto em = ErrorManager::getInstance();
- std::unique_lock _lock(mSharedMutex);
- mEmailVerificationCodeObject = controller::EmailVerificationCode::load(emailVerificationCode);
- if (mEmailVerificationCodeObject.isNull()) {
- addError(new Error(gettext("E-Mail Verification"), gettext("Konnte kein passendes Konto finden.")));
- return false;
- }
-
- mNewUser = controller::User::create();
- assert(mEmailVerificationCodeObject->getModel() && mEmailVerificationCodeObject->getModel()->getUserId());
- mNewUser->load(mEmailVerificationCodeObject->getModel()->getUserId());
- if (mNewUser->getModel()->errorCount() > 0) {
- mNewUser->getModel()->sendErrorsAsEmail();
- addError(new Error(gettext("E-Mail Verification"), gettext("Fehler beim laden des Benutzers.")));
- return false;
- }
- mSessionUser = new User(mNewUser);
- mSessionUser->setLanguage(getLanguage());
-
- auto verificationType = mEmailVerificationCodeObject->getModel()->getType();
- if (verificationType == model::table::EMAIL_OPT_IN_RESET_PASSWORD) {
- updateState(SESSION_STATE_RESET_PASSWORD_REQUEST);
- }
- else {
- updateState(SESSION_STATE_EMAIL_VERIFICATION_WRITTEN);
- }
-
- return true;
-}
-
-void Session::updateState(SessionStates newState)
-{
- lock("Session::updateState");
- if (!mActive) {
- unlock();
- return;
- }
- updateTimeout();
- //printf("[%s] newState: %s\n", __FUNCTION__, translateSessionStateToString(newState));
- if (newState > mState) {
- mState = newState;
- }
-
- unlock();
-}
-
-const char* Session::getSessionStateString()
-{
- SessionStates state;
- lock("Session::getSessionStateString");
- state = mState;
- unlock();
- return translateSessionStateToString(state);
-}
-
-
-const char* Session::translateSessionStateToString(SessionStates state)
-{
- switch (state) {
- case SESSION_STATE_EMPTY: return "uninitalized";
- case SESSION_STATE_CRYPTO_KEY_GENERATED: return "crpyto key generated";
- case SESSION_STATE_USER_WRITTEN: return "User saved";
- case SESSION_STATE_EMAIL_VERIFICATION_WRITTEN: return "E-Mail verification code saved";
- case SESSION_STATE_EMAIL_VERIFICATION_SEND: return "Verification E-Mail sended";
- case SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED: return "Verification Code checked";
- case SESSION_STATE_PASSPHRASE_GENERATED: return "Passphrase generated";
- case SESSION_STATE_PASSPHRASE_SHOWN: return "Passphrase shown";
- case SESSION_STATE_PASSPHRASE_WRITTEN: return "Passphrase written";
- case SESSION_STATE_KEY_PAIR_GENERATED: return "Gradido Address created";
- case SESSION_STATE_KEY_PAIR_WRITTEN: return "Gradido Address saved";
- case SESSION_STATE_RESET_PASSWORD_REQUEST: return "Passwort reset requested";
- case SESSION_STATE_RESET_PASSWORD_SUCCEED: return "Passwort reset succeeded";
- default: return "unknown";
- }
-
- return "error";
-}
-
-
-/*
-bool Session::useOrGeneratePassphrase(const std::string& passphase)
-{
- if (passphase != "" && User::validatePassphrase(passphase)) {
- // passphrase is valid
- setPassphrase(passphase);
- updateState(SESSION_STATE_PASSPHRASE_SHOWN);
- return true;
- }
- else {
- mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER]);
- updateState(SESSION_STATE_PASSPHRASE_GENERATED);
- return true;
- }
-}
-*/
-bool Session::generatePassphrase()
-{
- if (mNewUser.isNull()) return false;
-
- auto lang = getLanguage();
- if (lang == LANG_EN) {
- mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER]);
- }
- else {
- mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER]);
- }
- //mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER]);
- updateState(SESSION_STATE_PASSPHRASE_GENERATED);
- return true;
-}
-
-bool Session::generateKeys(bool savePrivkey, bool savePassphrase)
-{
- if (mNewUser.isNull()) {
- addError(new Error(gettext("Benutzer"), gettext("Kein gültiger Benutzer, bitte logge dich erneut ein.")));
- return false;
- }
- static const char* function_name = "Session::generateKeys";
- auto lang = getLanguage();
- auto user_model = mNewUser->getModel();
- auto mnemonic_type = ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
- /*if (LANG_DE == lang) {
- mnemonic_type = ServerConfig::MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER_FIXED_CASES;
- }*/
-
- auto passphrase = Passphrase::generate(&ServerConfig::g_Mnemonic_WordLists[mnemonic_type]);
- if (!passphrase) {
- addError(new ParamError(function_name, "Error generating passphrase with mnemonic: ", mnemonic_type));
- addError(new ParamError(function_name, "user email: ", mNewUser->getModel()->getEmail()));
- sendErrorsAsEmail();
- addError(new Error(gettext("Benutzer"), gettext("Fehler beim generieren der Passphrase, der Admin bekommt eine E-Mail. ")));
- return false;
- }
-
- if (savePassphrase) {
- auto user_backup = controller::UserBackups::create(user_model->getID(), passphrase->getString(), mnemonic_type);
- // sync version
- //user_backup->getModel()->insertIntoDB(false);
-
- // async version
- UniLib::controller::TaskPtr save_user_backup_task = new model::table::ModelInsertTask(user_backup->getModel(), false, true);
- save_user_backup_task->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_PASSPHRASE_WRITTEN, this));
- save_user_backup_task->scheduleTask(save_user_backup_task);
- }
-
- // keys
- auto gradido_key_pair = KeyPairEd25519::create(passphrase);
- auto set_key_result = mNewUser->setGradidoKeyPair(gradido_key_pair);
- size_t result_save_key = 0;
- if (1 == set_key_result && savePrivkey) {
- // save public key and private key in db
- result_save_key = user_model->updatePubkeyAndPrivkey();
- }
- else {
- // save public key in db
- result_save_key = user_model->updatePublickey();
- }
- if (!result_save_key) {
- user_model->addError(new Error(function_name, "Error saving new generated pubkey"));
- user_model->addError(new ParamError(function_name, "e-mail: ", user_model->getEmail()));
- user_model->sendErrorsAsEmail();
- //addError(new Error(gettext("Benutzer"), gettext("Fehler beim Speichern der Keys, der Admin bekommt eine E-Mail. Evt. nochmal versuchen oder abwarten!")));
- return false;
- }
- return true;
- /*
-
- bool validUser = true;
- if (mSessionUser) {
- if (!mSessionUser->generateKeys(savePrivkey, mPassphrase, this)) {
- validUser = false;
- }
- else {
- if (savePassphrase) {
- //printf("[Session::generateKeys] create save passphrase task\n");
- UniLib::controller::TaskPtr savePassphrase(new WritePassphraseIntoDB(mSessionUser->getDBId(), mPassphrase));
- savePassphrase->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_PASSPHRASE_WRITTEN, this));
- savePassphrase->scheduleTask(savePassphrase);
- }
- }
- }
- else {
- validUser = false;
- }
- if (!validUser) {
- addError(new Error(gettext("Benutzer"), gettext("Kein gültiger Benutzer, bitte logge dich erneut ein.")));
- return false;
- }
- // delete passphrase after all went well
- mPassphrase.clear();
-
- return true;
- */
-}
+#include "Session.h"
+#include "../lib/Profiler.h"
+#include "../ServerConfig.h"
+
+#include "Poco/RegularExpression.h"
+#include "Poco/Net/StringPartSource.h"
+#include "Poco/Net/MediaType.h"
+
+#include "../SingletonManager/SessionManager.h"
+#include "../SingletonManager/ConnectionManager.h"
+#include "../SingletonManager/ErrorManager.h"
+#include "../SingletonManager/EmailManager.h"
+#include "../SingletonManager/SingletonTaskObserver.h"
+
+#include "../tasks/PrepareEmailTask.h"
+#include "../tasks/SendEmailTask.h"
+#include "../tasks/SigningTransaction.h"
+#include "../tasks/AuthenticatedEncryptionCreateKeyTask.h"
+#include "../tasks/VerificationEmailResendTask.h"
+
+#include "../lib/JsonRequest.h"
+
+#include "../Crypto/Passphrase.h"
+
+
+#include "../controller/User.h"
+#include "../controller/UserBackups.h"
+#include "../controller/EmailVerificationCode.h"
+
+#include "table/ModelBase.h"
+
+
+#include "sodium.h"
+
+using namespace Poco::Data::Keywords;
+
+int WriteEmailVerification::run()
+{
+ auto em = ErrorManager::getInstance();
+
+ mEmailVerificationCode->getModel()->setUserId(mUser->getDBId());
+ auto emailVerificationModel = mEmailVerificationCode->getModel();
+ emailVerificationModel->setUserId(mUser->getDBId());
+ if (!emailVerificationModel->insertIntoDB(true) || emailVerificationModel->errorCount() > 0) {
+ emailVerificationModel->sendErrorsAsEmail();
+ return -1;
+ }
+
+ return 0;
+}
+
+// ---------------------------------------------------------------------------------------------------------------
+
+int WritePassphraseIntoDB::run()
+{
+ Profiler timeUsed;
+
+ // TODO: encrypt passphrase, need server admin crypto box pubkey
+ //int crypto_box_seal(unsigned char *c, const unsigned char *m,
+ //unsigned long long mlen, const unsigned char *pk);
+ size_t mlen = mPassphrase.size();
+ size_t crypto_size = crypto_box_SEALBYTES + mlen;
+
+ auto em = ErrorManager::getInstance();
+
+ auto dbSession = ConnectionManager::getInstance()->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
+ Poco::Data::Statement insert(dbSession);
+ insert << "INSERT INTO user_backups (user_id, passphrase) VALUES(?,?)",
+ use(mUserId), use(mPassphrase);
+ try {
+ if (insert.execute() != 1) {
+ em->addError(new ParamError("WritePassphraseIntoDB::run", "inserting passphrase for user failed", std::to_string(mUserId)));
+ em->sendErrorsAsEmail();
+ }
+ }
+ catch (Poco::Exception& ex) {
+ em->addError(new ParamError("WritePassphraseIntoDB::run", "insert passphrase mysql error", ex.displayText().data()));
+ em->sendErrorsAsEmail();
+ }
+
+ //printf("[WritePassphraseIntoDB] timeUsed: %s\n", timeUsed.string().data());
+ return 0;
+}
+
+
+// --------------------------------------------------------------------------------------------------------------
+
+Session::Session(int handle)
+ : mHandleId(handle), mSessionUser(nullptr), mState(SESSION_STATE_EMPTY), mActive(false)
+{
+
+}
+
+Session::~Session()
+{
+ //printf("[Session::~Session] \n");
+ if (tryLock()) {
+ unlock();
+ reset();
+ }
+
+
+ //printf("[Session::~Session] finished \n");
+}
+
+
+void Session::reset()
+{
+ //printf("[Session::reset]\n");
+ lock("Session::reset");
+ std::unique_lock _lock(mSharedMutex);
+ mSessionUser.assign(nullptr);
+ mNewUser.assign(nullptr);
+ mEmailVerificationCodeObject.assign(nullptr);
+
+ // watch out
+ //updateTimeout();
+ mLastActivity = Poco::DateTime();
+
+ mState = SESSION_STATE_EMPTY;
+
+ mPassphrase = "";
+ mLastExternReferer = "";
+ mClientLoginIP = Poco::Net::IPAddress();
+ unlock();
+
+ // reset transactions
+ mCurrentActiveProcessingTransaction = nullptr;
+ mProcessingTransactions.clear();
+
+ //printf("[Session::reset] finished\n");
+}
+
+int Session::isActive()
+{
+ int ret = 0;
+ try {
+ mWorkMutex.tryLock(100);
+ }
+ catch (Poco::TimeoutException& ex) {
+ return -1;
+ }
+ ret = (int)mActive;
+ unlock();
+ return ret;
+
+}
+
+bool Session::isDeadLocked()
+{
+ try {
+ mWorkMutex.tryLock(200);
+ unlock();
+ return false;
+ }
+ catch (Poco::Exception& ex) {
+
+ }
+ return true;
+}
+
+bool Session::setActive(bool active)
+{
+ try {
+ mWorkMutex.tryLock(100);
+ }
+ catch (Poco::TimeoutException& ex) {
+ return false;
+ }
+ mActive = active;
+ unlock();
+ return true;
+}
+
+void Session::updateTimeout()
+{
+ lock("Session::updateTimeout");
+ mLastActivity = Poco::DateTime();
+ unlock();
+}
+
+Poco::AutoPtr Session::getEmailVerificationCodeObject()
+{
+ lock("Session::getEmailVerificationCodeObject");
+ std::shared_lock _lock(mSharedMutex);
+ auto ret = mEmailVerificationCodeObject;
+ unlock();
+ return ret;
+}
+
+bool Session::adminCreateUser(const std::string& first_name, const std::string& last_name, const std::string& email)
+{
+ Profiler usedTime;
+
+ if (mNewUser->getModel()->getRole() != model::table::ROLE_ADMIN) {
+ addError(new Error(gettext("Benutzer"), gettext("Eingeloggter Benutzer ist kein Admin")), false);
+ return false;
+ }
+
+ auto sm = SessionManager::getInstance();
+ if (!sm->isValid(first_name, VALIDATE_NAME)) {
+ addError(new Error(gettext("Vorname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
+ return false;
+ }
+ if (!sm->isValid(last_name, VALIDATE_NAME)) {
+ addError(new Error(gettext("Nachname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
+ return false;
+ }
+ if (!sm->isValid(email, VALIDATE_EMAIL)) {
+ addError(new Error(gettext("E-Mail"), gettext("Bitte gebe eine gültige E-Mail Adresse an.")), false);
+ return false;
+ }
+
+
+ // check if user with that email already exist
+ if (mNewUser->getModel()->isExistInDB("email", email)) {
+ addError(new Error(gettext("E-Mail"), gettext("Für diese E-Mail Adresse gibt es bereits einen Account")), false);
+ return false;
+ }
+
+ auto newUser = controller::User::create(email, first_name, last_name);
+ updateTimeout();
+
+
+ auto newUserModel = newUser->getModel();
+ if (!newUserModel->insertIntoDB(true)) {
+ addError(new Error(gettext("Benutzer"), gettext("Fehler beim speichern!")));
+ return false;
+ }
+
+ auto email_verification_code = controller::EmailVerificationCode::create(newUserModel->getID(), model::table::EMAIL_OPT_IN_REGISTER);
+ if (!email_verification_code->getModel()->insertIntoDB(false)) {
+ addError(new Error(gettext("Email Verification Code"), gettext("Fehler beim speichern!")));
+ return false;
+ }
+
+ EmailManager::getInstance()->addEmail(new model::Email(email_verification_code, newUser, model::EMAIL_ADMIN_USER_VERIFICATION_CODE));
+
+ std::unique_lock _lock(mSharedMutex);
+ mEmailVerificationCodeObject = email_verification_code;
+
+
+ return true;
+}
+//
+bool Session::createUser(const std::string& first_name, const std::string& last_name, const std::string& email, const std::string& password)
+{
+ Profiler usedTime;
+ auto sm = SessionManager::getInstance();
+ if (!sm->isValid(first_name, VALIDATE_NAME)) {
+ addError(new Error(gettext("Vorname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
+ return false;
+ }
+ if (!sm->isValid(last_name, VALIDATE_NAME)) {
+ addError(new Error(gettext("Nachname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
+ return false;
+ }
+ if (!sm->isValid(email, VALIDATE_EMAIL)) {
+ addError(new Error(gettext("E-Mail"), gettext("Bitte gebe eine gültige E-Mail Adresse an.")), false);
+ return false;
+ }
+ if (!sm->checkPwdValidation(password, this)) {
+ return false;
+ }
+ /*if (passphrase.size() > 0 && !sm->isValid(passphrase, VALIDATE_PASSPHRASE)) {
+ addError(new Error("Merkspruch", "Der Merkspruch ist nicht gültig, er besteht aus 24 Wörtern, mit Komma getrennt."));
+ return false;
+ }
+ if (passphrase.size() == 0) {
+ //mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER]);
+ mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER]);
+ }
+ else {
+ //mPassphrase = passphrase;
+ }*/
+
+ // check if user with that email already exist
+
+ auto dbConnection = ConnectionManager::getInstance()->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
+ Poco::Data::Statement select(dbConnection);
+ select << "SELECT email from users where email = ?;", useRef(email);
+ try {
+ if (select.execute() > 0) {
+ addError(new Error(gettext("E-Mail"), gettext("Für diese E-Mail Adresse gibt es bereits einen Account")), false);
+ return false;
+ }
+ }
+ catch (Poco::Exception& exc) {
+ printf("mysql exception: %s\n", exc.displayText().data());
+ }
+
+ mSessionUser = new User(email.data(), first_name.data(), last_name.data());
+ mNewUser = controller::User::create(email, first_name, last_name);
+ updateTimeout();
+
+ // Prepare E-Mail
+ //UniLib::controller::TaskPtr prepareEmail(new PrepareEmailTask(ServerConfig::g_CPUScheduler));
+ //prepareEmail->scheduleTask(prepareEmail);
+
+ // create user crypto key
+ UniLib::controller::TaskPtr cryptoKeyTask(new UserCreateCryptoKey(mSessionUser, mNewUser, password, ServerConfig::g_CryptoCPUScheduler));
+ cryptoKeyTask->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_CRYPTO_KEY_GENERATED, this));
+ cryptoKeyTask->scheduleTask(cryptoKeyTask);
+
+ // depends on crypto key, write user record into db
+ UniLib::controller::TaskPtr writeUserIntoDB(new UserWriteIntoDB(mSessionUser, ServerConfig::g_CPUScheduler, 1));
+ writeUserIntoDB->setParentTaskPtrInArray(cryptoKeyTask, 0);
+ writeUserIntoDB->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_USER_WRITTEN, this));
+ writeUserIntoDB->scheduleTask(writeUserIntoDB);
+
+ std::unique_lock _lock(mSharedMutex);
+ mEmailVerificationCodeObject = controller::EmailVerificationCode::create(model::table::EMAIL_OPT_IN_REGISTER);
+ UniLib::controller::TaskPtr writeEmailVerification(new WriteEmailVerification(mSessionUser, mEmailVerificationCodeObject, ServerConfig::g_CPUScheduler, 1));
+
+ writeEmailVerification->setParentTaskPtrInArray(writeUserIntoDB, 0);
+ writeEmailVerification->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_EMAIL_VERIFICATION_WRITTEN, this));
+ writeEmailVerification->scheduleTask(writeEmailVerification);
+
+
+ /*printf("LastName: %s\n", last_name.data());
+ for (int i = 0; i < last_name.size(); i++) {
+ char c = last_name.data()[i];
+ //printf("%d ", c);
+ }
+ //printf("\n\n");
+ */
+
+ // depends on writeUser because need user_id, write email verification into db
+ /*auto message = new Poco::Net::MailMessage;
+ Poco::Net::MediaType mt("text", "plain");
+ mt.setParameter("charset", "utf-8");
+ message->setContentType(mt);
+
+ message->addRecipient(Poco::Net::MailRecipient(Poco::Net::MailRecipient::PRIMARY_RECIPIENT, email));
+ message->setSubject(gettext("Gradido: E-Mail Verification"));
+ std::stringstream ss;
+ ss << "Hallo " << first_name << " " << last_name << "," << std::endl << std::endl;
+ ss << "Du oder jemand anderes hat sich soeben mit dieser E-Mail Adresse bei Gradido registriert. " << std::endl;
+ ss << "Wenn du es warst, klicke bitte auf den Link: " << ServerConfig::g_serverPath << "/checkEmail/" << mEmailVerificationCode << std::endl;
+ //ss << "oder kopiere den Code: " << mEmailVerificationCode << " selbst dort hinein." << std::endl;
+ ss << "oder kopiere den obigen Link in Dein Browserfenster." << std::endl;
+ ss << std::endl;
+ ss << "Mit freundlichen " << u8"Grüßen" << std::endl;
+ ss << "Dario, Gradido Server Admin" << std::endl;
+
+
+ message->addContent(new Poco::Net::StringPartSource(ss.str()));
+ */
+ //UniLib::controller::TaskPtr sendEmail(new SendEmailTask(message, ServerConfig::g_CPUScheduler, 1));
+ //Email(AutoPtr emailVerification, AutoPtr user, EmailType type);
+ UniLib::controller::TaskPtr sendEmail(new SendEmailTask(new model::Email(mEmailVerificationCodeObject, mNewUser, model::EMAIL_USER_VERIFICATION_CODE), ServerConfig::g_CPUScheduler, 1));
+ //sendEmail->setParentTaskPtrInArray(prepareEmail, 0);
+ sendEmail->setParentTaskPtrInArray(writeEmailVerification, 0);
+ sendEmail->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_EMAIL_VERIFICATION_SEND, this));
+ sendEmail->scheduleTask(sendEmail);
+
+ // write user into db
+ // generate and write email verification into db
+ // send email
+
+ //printf("[Session::createUser] time: %s\n", usedTime.string().data());
+
+ return true;
+}
+
+bool Session::createUserDirect(const std::string& first_name, const std::string& last_name, const std::string& email, const std::string& password)
+{
+ std::unique_lock _lock(mSharedMutex);
+ static const char* function_name = "Session::createUserDirect";
+ auto sm = SessionManager::getInstance();
+ auto em = ErrorManager::getInstance();
+ auto email_manager = EmailManager::getInstance();
+
+ if (!sm->isValid(first_name, VALIDATE_NAME)) {
+ addError(new Error(gettext("Vorname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
+ return false;
+ }
+ if (!sm->isValid(last_name, VALIDATE_NAME)) {
+ addError(new Error(gettext("Nachname"), gettext("Bitte gebe einen Namen an. Mindestens 3 Zeichen, keines folgender Zeichen <>&;")), false);
+ return false;
+ }
+ if (!sm->isValid(email, VALIDATE_EMAIL)) {
+ addError(new Error(gettext("E-Mail"), gettext("Bitte gebe eine gültige E-Mail Adresse an.")), false);
+ return false;
+ }
+ if (!sm->checkPwdValidation(password, this)) {
+ return false;
+ }
+
+ // check if email already exist
+ auto user = controller::User::create();
+ if (user->load(email) >= 1) {
+ addError(new Error(gettext("E-Mail"), gettext("Für diese E-Mail Adresse gibt es bereits ein Konto")), false);
+ return false;
+ }
+
+ // user
+ mNewUser = controller::User::create(email, first_name, last_name);
+ auto user_model = mNewUser->getModel();
+ user_model->insertIntoDB(true);
+ auto user_id = user_model->getID();
+
+
+ // one retry in case of connection error
+ if (!user_id) {
+ user_model->insertIntoDB(true);
+ auto user_id = user_model->getID();
+ if (!user_id) {
+ em->addError(new ParamError(function_name, "error saving new user in db, after one retry with email", email));
+ em->sendErrorsAsEmail();
+ addError(new Error(gettext("Server"), gettext("Fehler beim speichen des Kontos bitte versuche es später noch einmal")), false);
+ return false;
+ }
+ }
+
+ generateKeys(true, true);
+
+ // calculate encryption key, could need some time, will save encrypted privkey to db
+ UniLib::controller::TaskPtr create_authenticated_encrypten_key = new AuthenticatedEncryptionCreateKeyTask(mNewUser, password);
+ create_authenticated_encrypten_key->scheduleTask(create_authenticated_encrypten_key);
+
+ // email verification code
+ auto email_verification = controller::EmailVerificationCode::create(user_id, model::table::EMAIL_OPT_IN_REGISTER_DIRECT);
+ email_verification->getModel()->insertIntoDB(false);
+ mEmailVerificationCodeObject = email_verification;
+
+ auto _7days_later = Poco::DateTime() + Poco::Timespan(7, 0, 0, 0, 0);
+ ServerConfig::g_CronJobsTimer.schedule(new VerificationEmailResendTimerTask(user_id), Poco::Timestamp(_7days_later.timestamp()));
+
+ email_manager->addEmail(new model::Email(email_verification, mNewUser, model::EMAIL_USER_VERIFICATION_CODE));
+
+ return true;
+}
+
+bool Session::ifUserExist(const std::string& email)
+{
+ auto em = ErrorManager::getInstance();
+ const char* funcName = "Session::ifUserExist";
+ auto dbConnection = ConnectionManager::getInstance()->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
+ Poco::Data::Statement select(dbConnection);
+ bool emailChecked = false;
+ int userId = 0;
+ select << "SELECT email_checked, id from users where email = ? and email_checked = 1",
+ into(emailChecked), into(userId), useRef(email);
+
+ try {
+ if (select.execute() == 1) return true;
+ }
+ catch (Poco::Exception& ex) {
+ em->addError(new ParamError(funcName, "select user from email verification code mysql error ", ex.displayText().data()));
+ em->sendErrorsAsEmail();
+ }
+ return false;
+}
+
+int Session::updateEmailVerification(Poco::UInt64 emailVerificationCode)
+{
+ const static char* funcName = "Session::updateEmailVerification";
+ Poco::ScopedLock _lock(mWorkMutex);
+ // new mutex, will replace the Poco Mutex complete in the future
+ std::unique_lock _lock_shared(mSharedMutex);
+ Profiler usedTime;
+
+ auto em = ErrorManager::getInstance();
+ if (mEmailVerificationCodeObject.isNull()) {
+ em->addError(new Error(funcName, "email verification object is zero"));
+ em->sendErrorsAsEmail();
+
+ return -2;
+ }
+ auto email_verification_code_model = mEmailVerificationCodeObject->getModel();
+ assert(email_verification_code_model);
+ if (email_verification_code_model->getCode() == emailVerificationCode) {
+ if (mSessionUser && mSessionUser->getDBId() == 0) {
+ //addError(new Error("E-Mail Verification", "Benutzer wurde nicht richtig gespeichert, bitte wende dich an den Server-Admin"));
+ em->addError(new Error(funcName, "user exist with 0 as id"));
+ em->sendErrorsAsEmail();
+
+ //return false;
+ return -2;
+ }
+
+ // load correct user from db
+ if (mNewUser.isNull() || !mNewUser->getModel() || mNewUser->getModel()->getID() != email_verification_code_model->getUserId()) {
+ mNewUser = controller::User::create();
+ if (1 != mNewUser->load(email_verification_code_model->getUserId())) {
+ em->addError(new ParamError(funcName, "user load didn't return 1 with user_id ", email_verification_code_model->getUserId()));
+ em->sendErrorsAsEmail();
+
+ return -2;
+ }
+ }
+
+ auto user_model = mNewUser->getModel();
+ assert(user_model);
+ bool first_email_activation = false;
+ auto verification_type = email_verification_code_model->getType();
+ if (model::table::EMAIL_OPT_IN_REGISTER == verification_type ||
+ model::table::EMAIL_OPT_IN_EMPTY == verification_type ||
+ model::table::EMAIL_OPT_IN_REGISTER_DIRECT == verification_type) {
+ first_email_activation = true;
+ }
+ if (first_email_activation && user_model->isEmailChecked()) {
+ mSessionUser = new User(mNewUser);
+ addError(new Error(gettext("E-Mail Verification"), gettext("Du hast dein Konto bereits aktiviert!")), false);
+
+ return 1;
+ }
+ if (first_email_activation) {
+ user_model->setEmailChecked(true);
+
+ user_model->updateIntoDB("email_checked", 1);
+ if (user_model->errorCount() > 0) {
+ user_model->sendErrorsAsEmail();
+ }
+
+ // no find all active sessions
+
+ updateState(SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED);
+ return 0;
+ }
+
+ if (email_verification_code_model->getType() == model::table::EMAIL_OPT_IN_RESET_PASSWORD) {
+
+ if (mEmailVerificationCodeObject->deleteFromDB()) {
+ mEmailVerificationCodeObject.assign(nullptr);
+ }
+ else {
+ em->getErrors(mEmailVerificationCodeObject->getModel());
+ em->addError(new Error(funcName, "error deleting email verification code"));
+ em->sendErrorsAsEmail();
+ return -2;
+ }
+ updateState(SESSION_STATE_RESET_PASSWORD_REQUEST);
+ return 0;
+ }
+
+ em->addError(new Error(funcName, "invalid code path"));
+ em->sendErrorsAsEmail();
+
+ return -2;
+
+ /*if (updated_rows == 1) {
+ Poco::Data::Statement delete_row(dbConnection);
+ delete_row << "DELETE FROM email_opt_in where verification_code = ?", use(emailVerificationCode);
+ if (delete_row.execute() != 1) {
+ em->addError(new Error(funcName, "delete from email_opt_in entry didn't work as expected, please check db"));
+ em->sendErrorsAsEmail();
+ }
+ if (mSessionUser) {
+ mSessionUser->setEmailChecked();
+ mSessionUser->setLanguage(getLanguage());
+ }
+ updateState(SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED);
+ //printf("[%s] time: %s\n", funcName, usedTime.string().data());
+ unlock();
+ return true;
+ }
+ else {
+ em->addError(new ParamError(funcName, "update user work not like expected, updated row count", updated_rows));
+ em->sendErrorsAsEmail();
+ }*/
+
+
+ }
+ else {
+ addError(new Error(gettext("E-Mail Verification"), gettext("Falscher Code für aktiven Login")));
+ //printf("[%s] time: %s\n", funcName, usedTime.string().data());
+
+ return -1;
+ }
+ //printf("[%s] time: %s\n", funcName, usedTime.string().data());
+
+ return 0;
+}
+
+
+int Session::sendResetPasswordEmail(Poco::AutoPtr user, bool passphraseMemorized)
+{
+ mNewUser = user;
+ mSessionUser = new User(user);
+ auto em = EmailManager::getInstance();
+
+ std::unique_lock _lock(mSharedMutex);
+
+ // creating email verification code also for user without passphrase
+ // first check if already exist
+ // check if email was already send shortly before
+ bool frequent_resend = false;
+ bool email_already_send = false;
+
+ mEmailVerificationCodeObject = controller::EmailVerificationCode::load(user->getModel()->getID(), model::table::EMAIL_OPT_IN_RESET_PASSWORD);
+ if (mEmailVerificationCodeObject.isNull()) {
+ mEmailVerificationCodeObject = controller::EmailVerificationCode::create(mNewUser->getModel()->getID(), model::table::EMAIL_OPT_IN_RESET_PASSWORD);
+ mEmailVerificationCodeObject->getModel()->insertIntoDB(false);
+ }
+ else {
+ email_already_send = true;
+ }
+ auto email_verification_model = mEmailVerificationCodeObject->getModel();
+ if (email_already_send) {
+ auto time_elapsed = Poco::DateTime() - email_verification_model->getUpdated();
+ if (time_elapsed.totalHours() < 1) {
+ frequent_resend = true;
+ }
+ }
+
+ if (!frequent_resend) {
+ if (passphraseMemorized) {
+ em->addEmail(new model::Email(mEmailVerificationCodeObject, mNewUser, model::EMAIL_USER_RESET_PASSWORD));
+ }
+ else {
+ em->addEmail(new model::Email(user, model::EMAIL_ADMIN_RESET_PASSWORD_REQUEST_WITHOUT_MEMORIZED_PASSPHRASE));
+ }
+ }
+
+ if (frequent_resend) return 2;
+ if (email_already_send) return 1;
+
+ return 0;
+}
+
+int Session::comparePassphraseWithSavedKeys(const std::string& inputPassphrase, Mnemonic* wordSource)
+{
+ KeyPair keys;
+ static const char* functionName = "Session::comparePassphraseWithSavedKeys";
+ if (!wordSource) {
+ addError(new Error(functionName, "wordSource is empty"));
+ sendErrorsAsEmail();
+ return -2;
+ }
+ if (!keys.generateFromPassphrase(inputPassphrase.data(), wordSource)) {
+ addError(new ParamError(functionName, "invalid passphrase", inputPassphrase));
+ if (!mNewUser.isNull() && mNewUser->getModel()) {
+ addError(new ParamError(functionName, "user email", mNewUser->getModel()->getEmail()));
+ }
+ sendErrorsAsEmail();
+ addError(new Error(gettext("Passphrase"), gettext("Deine Passphrase ist ungütig")), false);
+ return 0;
+ }
+ auto userModel = mNewUser->getModel();
+ auto existingPublic = userModel->getPublicKey();
+ if (!existingPublic) {
+ userModel->loadFromDB("email", userModel->getEmail());
+ existingPublic = userModel->getPublicKey();
+ if (!existingPublic) {
+ addError(new Error(functionName, "cannot load existing public key from db"));
+ addError(new ParamError(functionName, "user email", userModel->getEmail()));
+ sendErrorsAsEmail();
+ addError(new Error(gettext("Passphrase"), gettext("Ein Fehler trat auf, bitte versuche es erneut")), false);
+ return -1;
+ }
+ }
+ if (0 == memcmp(userModel->getPublicKey(), keys.getPublicKey(), crypto_sign_PUBLICKEYBYTES)) {
+ mPassphrase = inputPassphrase;
+ return 1;
+ }
+ addError(new Error(gettext("Passphrase"), gettext("Das ist nicht die richtige Passphrase.")), false);
+ return 0;
+}
+
+bool Session::startProcessingTransaction(const std::string& proto_message_base64, bool autoSign/* = false*/)
+{
+ static const char* funcName = "Session::startProcessingTransaction";
+ lock(funcName);
+ HASH hs = ProcessingTransaction::calculateHash(proto_message_base64);
+ // check if it is already running or waiting
+ for (auto it = mProcessingTransactions.begin(); it != mProcessingTransactions.end(); it++) {
+ if (it->isNull()) {
+ it = mProcessingTransactions.erase(it);
+ }
+ if (hs == (*it)->getHash()) {
+ addError(new Error(funcName, "transaction already in list"));
+ unlock();
+ return false;
+ }
+ }
+ if (mSessionUser.isNull() || !mSessionUser->getEmail()) {
+ addError(new Error(funcName, "user is zero"));
+ unlock();
+ return false;
+ }
+
+ Poco::AutoPtr processorTask(
+ new ProcessingTransaction(
+ proto_message_base64,
+ DRMakeStringHash(mSessionUser->getEmail()),
+ mSessionUser->getLanguage())
+ );
+ if (autoSign && (ServerConfig::g_AllowUnsecureFlags & ServerConfig::UNSECURE_AUTO_SIGN_TRANSACTIONS) == ServerConfig::UNSECURE_AUTO_SIGN_TRANSACTIONS) {
+ if (processorTask->run() != 0) {
+ getErrors(processorTask);
+ unlock();
+ return false;
+ }
+ Poco::AutoPtr signingTransaction(new SigningTransaction(processorTask, mNewUser));
+ //signingTransaction->scheduleTask(signingTransaction);
+ if (signingTransaction->run() != 0) {
+ getErrors(signingTransaction);
+ unlock();
+ return false;
+ }
+
+ }
+ else {
+ processorTask->scheduleTask(processorTask);
+ mProcessingTransactions.push_back(processorTask);
+ }
+ unlock();
+ return true;
+
+}
+
+Poco::AutoPtr Session::getNextReadyTransaction(size_t* working/* = nullptr*/)
+{
+ lock("Session::getNextReadyTransaction");
+ if (working) {
+ *working = 0;
+ }
+ else if (!mCurrentActiveProcessingTransaction.isNull())
+ {
+ unlock();
+ return mCurrentActiveProcessingTransaction;
+ }
+ for (auto it = mProcessingTransactions.begin(); it != mProcessingTransactions.end(); it++) {
+ if (working && !(*it)->isTaskFinished()) {
+ (*working)++;
+ }
+ if (mCurrentActiveProcessingTransaction.isNull() && (*it)->isTaskFinished()) {
+ if (!working) {
+ mCurrentActiveProcessingTransaction = *it;
+ unlock();
+ return mCurrentActiveProcessingTransaction;
+ }
+ // no early exit
+ else {
+ mCurrentActiveProcessingTransaction = *it;
+ }
+
+ }
+ }
+ unlock();
+ return mCurrentActiveProcessingTransaction;
+}
+
+bool Session::finalizeTransaction(bool sign, bool reject)
+{
+ int result = -1;
+ lock("Session::finalizeTransaction");
+ if (mCurrentActiveProcessingTransaction.isNull()) {
+ unlock();
+ return false;
+ }
+ mProcessingTransactions.remove(mCurrentActiveProcessingTransaction);
+
+ if (!reject) {
+ if (sign) {
+ Poco::AutoPtr signingTransaction(new SigningTransaction(mCurrentActiveProcessingTransaction, mNewUser));
+ //signingTransaction->scheduleTask(signingTransaction);
+ result = signingTransaction->run();
+ }
+ }
+ mCurrentActiveProcessingTransaction.assign(nullptr);
+ unlock();
+ return result == 0;
+}
+
+size_t Session::getProcessingTransactionCount()
+{
+ size_t count = 0;
+ lock("Session::getProcessingTransactionCount");
+
+ for (auto it = mProcessingTransactions.begin(); it != mProcessingTransactions.end(); it++) {
+
+ (*it)->lock();
+ if ((*it)->errorCount() > 0) {
+ (*it)->sendErrorsAsEmail();
+ (*it)->unlock();
+ it = mProcessingTransactions.erase(it);
+ if (it == mProcessingTransactions.end()) break;
+ }
+ else {
+ (*it)->unlock();
+ }
+
+ }
+ count = mProcessingTransactions.size();
+ unlock();
+ return count;
+}
+
+bool Session::isPwdValid(const std::string& pwd)
+{
+ if (mSessionUser) {
+ return mSessionUser->validatePwd(pwd, this);
+ }
+ return false;
+}
+
+UserStates Session::loadUser(const std::string& email, const std::string& password)
+{
+ static const char* functionName = "Session::loadUser";
+ auto observer = SingletonTaskObserver::getInstance();
+ if (email != "") {
+ if (observer->getTaskCount(email, TASK_OBSERVER_PASSWORD_CREATION) > 0) {
+ return USER_PASSWORD_ENCRYPTION_IN_PROCESS;
+ }
+ }
+ //Profiler usedTime;
+ //printf("before lock\n");
+ lock(functionName);
+ //printf("locked \n");
+ if (!mSessionUser.isNull() && mSessionUser->getEmail() != email) {
+ mSessionUser.assign(nullptr);
+ mNewUser.assign(nullptr);
+ //printf("user nullptr assigned\n");
+ }
+ //printf("after checking if session user is null\n");
+ //if (!mSessionUser) {
+ if (mNewUser.isNull()) {
+ //printf("new user is null\n");
+ mNewUser = controller::User::create();
+ //printf("new user created\n");
+ // load user for email only once from db
+ mNewUser->load(email);
+ //printf("load new user from db with email: %s\n", email.data());
+ mSessionUser = new User(mNewUser);
+ //mSessionUser = new User(email.data());
+
+ //printf("user loaded from email\n");
+ }
+ //printf("before get model\n");
+ auto user_model = mNewUser->getModel();
+ if (user_model && user_model->isDisabled()) {
+ return USER_DISABLED;
+ }
+ //printf("before if login\n");
+ if (!mSessionUser.isNull() && mSessionUser->getUserState() >= USER_LOADED_FROM_DB) {
+ //printf("before login\n");
+ int loginResult = 0;
+ int exitCount = 0;
+ do {
+ loginResult = mNewUser->login(password);
+ Poco::Thread::sleep(100);
+ exitCount++;
+ } while (-3 == loginResult && exitCount < 15);
+ if (exitCount > 1) {
+ addError(new ParamError(functionName, "login succeed, retrys: ", exitCount));
+ addError(new ParamError(functionName, "email: ", email));
+ sendErrorsAsEmail();
+ }
+
+ if (exitCount >= 15)
+ {
+ auto running_password_creations = observer->getTasksCount(TASK_OBSERVER_PASSWORD_CREATION);
+
+ addError(new ParamError(functionName, "login failed after 15 retrys and 100 ms sleep between, currently running passwort creation tasks: ", running_password_creations));
+ addError(new ParamError(functionName, "email: ", email));
+ sendErrorsAsEmail();
+ return USER_PASSWORD_ENCRYPTION_IN_PROCESS;
+ }
+
+ //printf("new user login with result: %d\n", loginResult);
+
+ if (-1 == loginResult) {
+ addError(new Error(functionName, "error in user data set, saved pubkey didn't match extracted pubkey from private key"));
+ addError(new ParamError(functionName, "user email", mNewUser->getModel()->getEmail()));
+ sendErrorsAsEmail();
+ //unlock();
+ //return USER_KEYS_DONT_MATCH;
+ }
+ if (0 == loginResult) {
+ unlock();
+ return USER_PASSWORD_INCORRECT;
+ }
+ // error decrypting private key
+ if (-2 == loginResult) {
+ // check if we have access to the passphrase, if so we can reencrypt the private key
+ printf("try reencrypting key\n");
+ auto user_model = mNewUser->getModel();
+ auto user_backups = controller::UserBackups::load(user_model->getID());
+ for (auto it = user_backups.begin(); it != user_backups.end(); it++) {
+ auto key = std::unique_ptr((*it)->createGradidoKeyPair());
+ if (key->isTheSame(user_model->getPublicKey()))
+ {
+
+ // set valid key pair
+ if (1 == mNewUser->setGradidoKeyPair(key.release())) {
+ // save new encrypted private key
+ user_model->updatePrivkey();
+ }
+ else {
+ auto em = ErrorManager::getInstance();
+ em->addError(new Error(functionName, "error reencrypt private key"));
+ em->addError(new ParamError(functionName, "for user with email", user_model->getEmail()));
+ em->sendErrorsAsEmail();
+ }
+ break;
+ }
+ }
+ }
+ // can be removed if session user isn't used any more
+ // don't calculate password two times anymore
+ mSessionUser->login(mNewUser);
+ //printf("after old user login\n");
+ /*if (mNewUser->getModel()->getPasswordHashed() && !mSessionUser->validatePwd(password, this)) {
+ unlock();
+ return USER_PASSWORD_INCORRECT;
+ }*/
+ }
+ else {
+ //printf("before sleep\n");
+ User::fakeCreateCryptoKey();
+ }
+
+ /*if (!mSessionUser->validatePwd(password, this)) {
+ addError(new Error("Login", "E-Mail oder Passwort nicht korrekt, bitte versuche es erneut!"));
+ unlock();
+ return false;
+ }
+ if (!mSessionUser->isEmailChecked()) {
+ addError(new Error("Account", "E-Mail Adresse wurde noch nicht bestätigt, hast du schon eine E-Mail erhalten?"));
+ unlock();
+ return false;
+ }*/
+ //printf("before detect session state\n");
+ detectSessionState();
+ unlock();
+ //printf("before return user state\n");
+ return mSessionUser->getUserState();
+}
+
+bool Session::deleteUser()
+{
+ lock("Session::deleteUser");
+ bool bResult = false;
+ if (mSessionUser) {
+ JsonRequest phpServerRequest(ServerConfig::g_php_serverHost, 443);
+ Poco::Net::NameValueCollection payload;
+ payload.add("user", std::string(mSessionUser->getPublicKeyHex()));
+ //auto ret = phpServerRequest.request("userDelete", payload);
+ JsonRequestReturn ret = JSON_REQUEST_RETURN_OK;
+ if (ret == JSON_REQUEST_RETURN_ERROR) {
+ addError(new Error("Session::deleteUser", "php server error"));
+ getErrors(&phpServerRequest);
+ sendErrorsAsEmail();
+ }
+ else if (ret == JSON_REQUEST_RETURN_OK) {
+ bResult = mSessionUser->deleteFromDB();
+ }
+ else {
+ addError(new Error(gettext("Benutzer"), gettext("Konnte Community Server nicht erreichen. E-Mail an den Admin ist raus.")));
+ unlock();
+ return false;
+ }
+ }
+ if (!bResult) {
+ addError(new Error(gettext("Benutzer"), gettext("Fehler beim Löschen des Accounts. Bitte logge dich erneut ein und versuche es nochmal.")));
+ }
+ unlock();
+ return bResult;
+}
+
+void Session::setLanguage(Languages lang)
+{
+ //printf("[Session::setLanguage] new language: %d\n", lang);
+ lock("Session::setLanguage");
+ if (mLanguageCatalog.isNull() || mLanguageCatalog->getLanguage() != lang) {
+ auto lm = LanguageManager::getInstance();
+ mLanguageCatalog = lm->getFreeCatalog(lang);
+ }
+ unlock();
+}
+
+Languages Session::getLanguage()
+{
+ Languages lang = LANG_NULL;
+ lock("Session::getLanguage");
+ if (!mLanguageCatalog.isNull()) {
+ lang = mLanguageCatalog->getLanguage();
+ }
+ unlock();
+ return lang;
+}
+
+
+/*
+SESSION_STATE_CRYPTO_KEY_GENERATED,
+SESSION_STATE_USER_WRITTEN,
+SESSION_STATE_EMAIL_VERIFICATION_WRITTEN,
+SESSION_STATE_EMAIL_VERIFICATION_SEND,
+SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED,
+SESSION_STATE_PASSPHRASE_GENERATED,
+SESSION_STATE_PASSPHRASE_SHOWN,
+SESSION_STATE_PASSPHRASE_WRITTEN,
+SESSION_STATE_KEY_PAIR_GENERATED,
+SESSION_STATE_KEY_PAIR_WRITTEN,
+SESSION_STATE_COUNT
+*/
+void Session::detectSessionState()
+{
+ if (mSessionUser.isNull() || !mSessionUser->hasCryptoKey()) {
+ return;
+ }
+ UserStates userState = mSessionUser->getUserState();
+
+ int checkEmail = -1, resetPasswd = -1;
+ try {
+ auto emailVerificationCodeObjects = controller::EmailVerificationCode::load(mSessionUser->getDBId());
+
+ for (int i = 0; i < emailVerificationCodeObjects.size(); i++) {
+ auto type = emailVerificationCodeObjects[i]->getModel()->getType();
+ if (type == model::table::EMAIL_OPT_IN_EMPTY || type == model::table::EMAIL_OPT_IN_REGISTER) {
+ checkEmail = i;
+ }
+ else if (type == model::table::EMAIL_OPT_IN_RESET_PASSWORD) {
+ resetPasswd = i;
+ }
+ }
+ std::unique_lock _lock_shared(mSharedMutex);
+ if (resetPasswd != -1) {
+ mEmailVerificationCodeObject = emailVerificationCodeObjects[resetPasswd];
+ }
+ else if (checkEmail != -1) {
+ mEmailVerificationCodeObject = emailVerificationCodeObjects[checkEmail];
+ }
+
+ }
+ catch (Poco::Exception& ex) {
+ printf("[Session::detectSessionState] exception: %s\n", ex.displayText().data());
+ //return;
+ }
+
+ if (userState <= USER_EMAIL_NOT_ACTIVATED) {
+
+ if (checkEmail != -1) {
+ updateState(SESSION_STATE_EMAIL_VERIFICATION_WRITTEN);
+ return;
+ }
+
+ updateState(SESSION_STATE_USER_WRITTEN);
+ return;
+ }
+
+ if (USER_NO_KEYS == userState) {
+
+ auto user_id = mSessionUser->getDBId();
+ auto userBackups = controller::UserBackups::load(user_id);
+
+ // check passphrase, only possible while passphrase isn't crypted in db
+ bool correctPassphraseFound = false;
+ // always trigger SESSION_STATE_PASSPHRASE_WRITTEN, else lost of data possible
+ bool cryptedPassphrase = userBackups.size() > 0;
+ for (auto it = userBackups.begin(); it != userBackups.end(); it++) {
+ KeyPair keys;
+ auto passphrase = (*it)->getModel()->getPassphrase();
+ Mnemonic* wordSource = nullptr;
+ if (User::validatePassphrase(passphrase, &wordSource)) {
+ if (keys.generateFromPassphrase((*it)->getModel()->getPassphrase().data(), wordSource)) {
+ if (sodium_memcmp(mSessionUser->getPublicKey(), keys.getPublicKey(), ed25519_pubkey_SIZE) == 0) {
+ correctPassphraseFound = true;
+ break;
+ }
+ }
+ }
+ else {
+ cryptedPassphrase = true;
+ }
+ }
+ /*
+ auto dbConnection = ConnectionManager::getInstance()->getConnection(CONNECTION_MYSQL_LOGIN_SERVER);
+ Poco::Data::Statement select(dbConnection);
+ Poco::Nullable passphrase;
+
+ select << "SELECT passphrase from user_backups where user_id = ?;",
+ into(passphrase), use(user_id);
+ try {
+ if (select.execute() == 1 && !passphrase.isNull()) {
+ //KeyPair keys;keys.generateFromPassphrase(passphrase.value().rawContent())
+ updateState(SESSION_STATE_PASSPHRASE_WRITTEN);
+ return;
+ }
+ }
+ catch (Poco::Exception& exc) {
+ printf("[Session::detectSessionState] 2 mysql exception: %s\n", exc.displayText().data());
+ }*/
+ if (correctPassphraseFound || cryptedPassphrase) {
+ updateState(SESSION_STATE_PASSPHRASE_WRITTEN);
+ return;
+ }
+ if (mPassphrase != "") {
+ updateState(SESSION_STATE_PASSPHRASE_GENERATED);
+ return;
+ }
+ updateState(SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED);
+ return;
+ }
+
+ updateState(SESSION_STATE_KEY_PAIR_WRITTEN);
+
+ if (resetPasswd != -1) {
+ // don't go to reset password screen after login, only throw checkEmail
+ //updateState(SESSION_STATE_RESET_PASSWORD_REQUEST);
+ return;
+ }
+
+}
+
+Poco::Net::HTTPCookie Session::getLoginCookie()
+{
+ auto keks = Poco::Net::HTTPCookie("GRADIDO_LOGIN", std::to_string(mHandleId));
+ // prevent reading or changing cookie with js
+// keks.setHttpOnly();
+
+ keks.setPath("/");
+ // send cookie only via https, on linux, except in test builds
+#ifndef WIN32
+ if (ServerConfig::SERVER_TYPE_PRODUCTION == ServerConfig::g_ServerSetupType ||
+ ServerConfig::SERVER_TYPE_STAGING == ServerConfig::g_ServerSetupType) {
+ keks.setSecure(true);
+ }
+#endif
+
+ return keks;
+}
+
+bool Session::loadFromEmailVerificationCode(Poco::UInt64 emailVerificationCode)
+{
+ Profiler usedTime;
+ auto em = ErrorManager::getInstance();
+ std::unique_lock _lock(mSharedMutex);
+ mEmailVerificationCodeObject = controller::EmailVerificationCode::load(emailVerificationCode);
+ if (mEmailVerificationCodeObject.isNull()) {
+ addError(new Error(gettext("E-Mail Verification"), gettext("Konnte kein passendes Konto finden.")));
+ return false;
+ }
+
+ mNewUser = controller::User::create();
+ assert(mEmailVerificationCodeObject->getModel() && mEmailVerificationCodeObject->getModel()->getUserId());
+ mNewUser->load(mEmailVerificationCodeObject->getModel()->getUserId());
+ if (mNewUser->getModel()->errorCount() > 0) {
+ mNewUser->getModel()->sendErrorsAsEmail();
+ addError(new Error(gettext("E-Mail Verification"), gettext("Fehler beim laden des Benutzers.")));
+ return false;
+ }
+ mSessionUser = new User(mNewUser);
+ mSessionUser->setLanguage(getLanguage());
+
+ auto verificationType = mEmailVerificationCodeObject->getModel()->getType();
+ if (verificationType == model::table::EMAIL_OPT_IN_RESET_PASSWORD) {
+ updateState(SESSION_STATE_RESET_PASSWORD_REQUEST);
+ }
+ else {
+ updateState(SESSION_STATE_EMAIL_VERIFICATION_WRITTEN);
+ }
+
+ return true;
+}
+
+void Session::updateState(SessionStates newState)
+{
+ lock("Session::updateState");
+ if (!mActive) {
+ unlock();
+ return;
+ }
+ updateTimeout();
+ //printf("[%s] newState: %s\n", __FUNCTION__, translateSessionStateToString(newState));
+ if (newState > mState) {
+ mState = newState;
+ }
+
+ unlock();
+}
+
+const char* Session::getSessionStateString()
+{
+ SessionStates state;
+ lock("Session::getSessionStateString");
+ state = mState;
+ unlock();
+ return translateSessionStateToString(state);
+}
+
+
+const char* Session::translateSessionStateToString(SessionStates state)
+{
+ switch (state) {
+ case SESSION_STATE_EMPTY: return "uninitalized";
+ case SESSION_STATE_CRYPTO_KEY_GENERATED: return "crpyto key generated";
+ case SESSION_STATE_USER_WRITTEN: return "User saved";
+ case SESSION_STATE_EMAIL_VERIFICATION_WRITTEN: return "E-Mail verification code saved";
+ case SESSION_STATE_EMAIL_VERIFICATION_SEND: return "Verification E-Mail sended";
+ case SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED: return "Verification Code checked";
+ case SESSION_STATE_PASSPHRASE_GENERATED: return "Passphrase generated";
+ case SESSION_STATE_PASSPHRASE_SHOWN: return "Passphrase shown";
+ case SESSION_STATE_PASSPHRASE_WRITTEN: return "Passphrase written";
+ case SESSION_STATE_KEY_PAIR_GENERATED: return "Gradido Address created";
+ case SESSION_STATE_KEY_PAIR_WRITTEN: return "Gradido Address saved";
+ case SESSION_STATE_RESET_PASSWORD_REQUEST: return "Passwort reset requested";
+ case SESSION_STATE_RESET_PASSWORD_SUCCEED: return "Passwort reset succeeded";
+ default: return "unknown";
+ }
+
+ return "error";
+}
+
+
+/*
+bool Session::useOrGeneratePassphrase(const std::string& passphase)
+{
+ if (passphase != "" && User::validatePassphrase(passphase)) {
+ // passphrase is valid
+ setPassphrase(passphase);
+ updateState(SESSION_STATE_PASSPHRASE_SHOWN);
+ return true;
+ }
+ else {
+ mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER]);
+ updateState(SESSION_STATE_PASSPHRASE_GENERATED);
+ return true;
+ }
+}
+*/
+bool Session::generatePassphrase()
+{
+ if (mNewUser.isNull()) return false;
+
+ auto lang = getLanguage();
+ if (lang == LANG_EN) {
+ mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER]);
+ }
+ else {
+ mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER]);
+ }
+ //mPassphrase = User::generateNewPassphrase(&ServerConfig::g_Mnemonic_WordLists[ServerConfig::MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER]);
+ updateState(SESSION_STATE_PASSPHRASE_GENERATED);
+ return true;
+}
+
+bool Session::generateKeys(bool savePrivkey, bool savePassphrase)
+{
+ if (mNewUser.isNull()) {
+ addError(new Error(gettext("Benutzer"), gettext("Kein gültiger Benutzer, bitte logge dich erneut ein.")));
+ return false;
+ }
+ static const char* function_name = "Session::generateKeys";
+ auto lang = getLanguage();
+ auto user_model = mNewUser->getModel();
+ auto mnemonic_type = ServerConfig::MNEMONIC_BIP0039_SORTED_ORDER;
+ /*if (LANG_DE == lang) {
+ mnemonic_type = ServerConfig::MNEMONIC_GRADIDO_BOOK_GERMAN_RANDOM_ORDER_FIXED_CASES;
+ }*/
+
+ auto passphrase = Passphrase::generate(&ServerConfig::g_Mnemonic_WordLists[mnemonic_type]);
+ if (!passphrase) {
+ addError(new ParamError(function_name, "Error generating passphrase with mnemonic: ", mnemonic_type));
+ addError(new ParamError(function_name, "user email: ", mNewUser->getModel()->getEmail()));
+ sendErrorsAsEmail();
+ addError(new Error(gettext("Benutzer"), gettext("Fehler beim generieren der Passphrase, der Admin bekommt eine E-Mail. ")));
+ return false;
+ }
+
+ if (savePassphrase) {
+ auto user_backup = controller::UserBackups::create(user_model->getID(), passphrase->getString(), mnemonic_type);
+ // sync version
+ //user_backup->getModel()->insertIntoDB(false);
+
+ // async version
+ UniLib::controller::TaskPtr save_user_backup_task = new model::table::ModelInsertTask(user_backup->getModel(), false, true);
+ save_user_backup_task->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_PASSPHRASE_WRITTEN, this));
+ save_user_backup_task->scheduleTask(save_user_backup_task);
+ }
+
+ // keys
+ auto gradido_key_pair = KeyPairEd25519::create(passphrase);
+ auto set_key_result = mNewUser->setGradidoKeyPair(gradido_key_pair);
+ size_t result_save_key = 0;
+ if (1 == set_key_result && savePrivkey) {
+ // save public key and private key in db
+ result_save_key = user_model->updatePubkeyAndPrivkey();
+ }
+ else {
+ // save public key in db
+ result_save_key = user_model->updatePublickey();
+ }
+ if (!result_save_key) {
+ user_model->addError(new Error(function_name, "Error saving new generated pubkey"));
+ user_model->addError(new ParamError(function_name, "e-mail: ", user_model->getEmail()));
+ user_model->sendErrorsAsEmail();
+ //addError(new Error(gettext("Benutzer"), gettext("Fehler beim Speichern der Keys, der Admin bekommt eine E-Mail. Evt. nochmal versuchen oder abwarten!")));
+ return false;
+ }
+ return true;
+ /*
+
+ bool validUser = true;
+ if (mSessionUser) {
+ if (!mSessionUser->generateKeys(savePrivkey, mPassphrase, this)) {
+ validUser = false;
+ }
+ else {
+ if (savePassphrase) {
+ //printf("[Session::generateKeys] create save passphrase task\n");
+ UniLib::controller::TaskPtr savePassphrase(new WritePassphraseIntoDB(mSessionUser->getDBId(), mPassphrase));
+ savePassphrase->setFinishCommand(new SessionStateUpdateCommand(SESSION_STATE_PASSPHRASE_WRITTEN, this));
+ savePassphrase->scheduleTask(savePassphrase);
+ }
+ }
+ }
+ else {
+ validUser = false;
+ }
+ if (!validUser) {
+ addError(new Error(gettext("Benutzer"), gettext("Kein gültiger Benutzer, bitte logge dich erneut ein.")));
+ return false;
+ }
+ // delete passphrase after all went well
+ mPassphrase.clear();
+
+ return true;
+ */
+}
diff --git a/login_server/src/cpp/model/Session.h b/login_server/src/cpp/model/Session.h
index ec60b6fc3..e10beef89 100644
--- a/login_server/src/cpp/model/Session.h
+++ b/login_server/src/cpp/model/Session.h
@@ -1,282 +1,282 @@
-/*!
-*
-* \author: einhornimmond
-*
-* \date: 02.03.19
-*
-* \brief: store session data
-*/
-
-#ifndef DR_LUA_WEB_MODULE_SESSION_SESSION_H
-#define DR_LUA_WEB_MODULE_SESSION_SESSION_H
-
-#include "../lib/ErrorList.h"
-#include "User.h"
-#include "../controller/User.h"
-
-#include "../lib/MultithreadContainer.h"
-#include "../tasks/ProcessingTransaction.h"
-
-#include "../SingletonManager/LanguageManager.h"
-
-#include "../controller/EmailVerificationCode.h"
-
-#include "Poco/Thread.h"
-#include "Poco/Types.h"
-#include "Poco/DateTime.h"
-#include "Poco/Net/IPAddress.h"
-#include "Poco/Net/HTTPCookie.h"
-
-#include
-
-
-class WriteEmailVerification;
-
-enum SessionStates {
- SESSION_STATE_EMPTY,
- SESSION_STATE_CRYPTO_KEY_GENERATED,
- SESSION_STATE_USER_WRITTEN,
- SESSION_STATE_EMAIL_VERIFICATION_WRITTEN,
- SESSION_STATE_EMAIL_VERIFICATION_SEND,
- SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED,
- SESSION_STATE_PASSPHRASE_GENERATED,
- SESSION_STATE_PASSPHRASE_SHOWN,
- SESSION_STATE_PASSPHRASE_WRITTEN,
- SESSION_STATE_KEY_PAIR_GENERATED,
- SESSION_STATE_KEY_PAIR_WRITTEN,
- SESSION_STATE_RESET_PASSWORD_REQUEST,
- SESSION_STATE_RESET_PASSWORD_SUCCEED,
- SESSION_STATE_COUNT
-};
-
-class SessionManager;
-
-class UpdateUserPasswordPage;
-class PassphrasePage;
-class RepairDefectPassphrase;
-
-class Session : public ErrorList, public UniLib::lib::MultithreadContainer
-{
- friend WriteEmailVerification;
- friend SessionManager;
- friend UpdateUserPasswordPage;
- friend PassphrasePage;
- friend RepairDefectPassphrase;
-public:
- Session(int handle);
- ~Session();
-
- // get new model objects
- Poco::AutoPtr getEmailVerificationCodeObject();
-
- // set new model objects
- inline void setUser(Poco::AutoPtr user) { mNewUser = user; }
- inline Poco::AutoPtr getNewUser() { return mNewUser; }
-
- // ---------------- User functions ----------------------------
- // TODO: register state: written into db, mails sended, update state only if new state is higher as old state
- // create User send e-mail activation link
- bool createUser(const std::string& first_name, const std::string& last_name, const std::string& email, const std::string& password);
-
- //! \brief new register function, without showing user pubkeys, using controller/user
- bool createUserDirect(const std::string& first_name, const std::string& last_name, const std::string& email, const std::string& password);
-
-
- // adminRegister without passwort
- bool adminCreateUser(const std::string& first_name, const std::string& last_name, const std::string& email);
-
- // TODO: check if email exist and if not, fake waiting on password hashing with profiled times of real password hashing
- UserStates loadUser(const std::string& email, const std::string& password);
- bool ifUserExist(const std::string& email);
-
- inline void setUser(Poco::AutoPtr user) { mSessionUser = user; }
-
-
- bool deleteUser();
-
- Poco::AutoPtr getUser() {
- return mSessionUser;
- }
-
- // ------------------------- Email Verification Code functions -------------------------------
-
- bool loadFromEmailVerificationCode(Poco::UInt64 emailVerificationCode);
-
- //! \return 1 = konto already exist
- //! -1 = invalid code
- //! -2 = critical error
- //! 0 = ok
- int updateEmailVerification(Poco::UInt64 emailVerificationCode);
-
- // called from page with same name
- //! \return 1 = reset password email already send
- //! \return 2 = reset password email already shortly before
- //! \return 0 = ok
- int sendResetPasswordEmail(Poco::AutoPtr user, bool passphraseMemorized);
- //
- //! \return 0 = not the same
- //! \return 1 = same
- //! \return -1 = error
- //! \return -2 = critical error
- int comparePassphraseWithSavedKeys(const std::string& inputPassphrase, Mnemonic* wordSource);
-
- Poco::Net::HTTPCookie getLoginCookie();
-
-
- inline int getHandle() { return mHandleId; }
-
- // ------------------------ Passphrase functions ----------------------------
-
- inline void setPassphrase(Poco::AutoPtr passphrase) { mNewPassphrase = passphrase; }
- inline Poco::AutoPtr getPassphrase() { return mNewPassphrase; }
-
- inline void setPassphrase(const std::string& passphrase) { mPassphrase = passphrase; }
-
- inline const std::string& getOldPassphrase() { return mPassphrase; }
- bool generatePassphrase();
- bool generateKeys(bool savePrivkey, bool savePassphrase);
-
- inline void setClientIp(Poco::Net::IPAddress ip) { mClientLoginIP = ip; }
- inline Poco::Net::IPAddress getClientIp() { return mClientLoginIP; }
-
- inline bool isIPValid(Poco::Net::IPAddress ip) { return mClientLoginIP == ip; }
- bool isPwdValid(const std::string& pwd);
- void reset();
-
- void updateState(SessionStates newState);
- const char* getSessionStateString();
- inline SessionStates getSessionState() { SessionStates s; lock("Session::getSessionState"); s = mState; unlock(); return s; }
-
- inline Poco::UInt64 getEmailVerificationCode() {
- std::shared_lock _lock(mSharedMutex);
- if (mEmailVerificationCodeObject.isNull()) return 0; return mEmailVerificationCodeObject->getModel()->getCode();
- }
- inline void setEmailVerificationCodeObject(Poco::AutoPtr emailVerficationObject) {
- std::unique_lock _lock(mSharedMutex);
- mEmailVerificationCodeObject = emailVerficationObject;
- }
- inline model::table::EmailOptInType getEmailVerificationType() {
- std::shared_lock _lock(mSharedMutex);
- if (mEmailVerificationCodeObject.isNull()) {
- return model::table::EMAIL_OPT_IN_EMPTY;
- }
- return mEmailVerificationCodeObject->getModel()->getType();
- }
-
- //! \return -1 if session is locked
- //! \return 1 if session is active
- //! \return 0
- int isActive();
- //! \return false if session is locked
- bool setActive(bool active);
-
- bool isDeadLocked();
-
- inline Poco::DateTime getLastActivity() { return mLastActivity; }
-
- // ------------------------ transactions functions ----------------------------
-
- //! \return true if succeed
- bool startProcessingTransaction(const std::string& proto_message_base64, bool autoSign = false);
- //! \param working if set will filled with transaction running
- Poco::AutoPtr getNextReadyTransaction(size_t* working = nullptr);
- bool finalizeTransaction(bool sign, bool reject);
- size_t getProcessingTransactionCount();
-
- inline LanguageCatalog* getLanguageCatalog() { return mLanguageCatalog.isNull() ? nullptr : mLanguageCatalog; }
- void setLanguage(Languages lang);
- inline void setLanguageCatalog(Poco::AutoPtr languageCatalog) { mLanguageCatalog = languageCatalog; }
- Languages getLanguage();
- inline const char* gettext(const char* text) { if (mLanguageCatalog.isNull()) return text; return mLanguageCatalog->gettext(text); }
-
- // last referer
- inline void setLastReferer(const std::string& lastReferer) { mLastExternReferer = lastReferer; }
- inline const std::string& getLastReferer() const { return mLastExternReferer; }
-
-protected:
- void updateTimeout();
- inline void setHandle(int newHandle) { mHandleId = newHandle; }
-
- void detectSessionState();
- static const char* translateSessionStateToString(SessionStates state);
-
- inline const std::string& getPassphrase() const { return mPassphrase; }
-
-
-private:
- int mHandleId;
- Poco::AutoPtr mSessionUser;
- Poco::AutoPtr mNewUser;
- std::string mPassphrase;
- Poco::AutoPtr mNewPassphrase;
- Poco::DateTime mLastActivity;
- Poco::Net::IPAddress mClientLoginIP;
- std::string mLastExternReferer;
- Poco::AutoPtr mEmailVerificationCodeObject;
- std::shared_mutex mSharedMutex;
-
-
- SessionStates mState;
-
- bool mActive;
- std::list> mProcessingTransactions;
- Poco::AutoPtr mCurrentActiveProcessingTransaction;
-
- Poco::AutoPtr mLanguageCatalog;
-};
-
-
-class WriteEmailVerification : public UniLib::controller::CPUTask
-{
-public:
- WriteEmailVerification(Poco::AutoPtr user, Poco::AutoPtr emailVerificationCode, UniLib::controller::CPUSheduler* cpuScheduler, size_t taskDependenceCount = 0)
- : UniLib::controller::CPUTask(cpuScheduler, taskDependenceCount), mUser(user), mEmailVerificationCode(emailVerificationCode) {
-#ifdef _UNI_LIB_DEBUG
- setName(user->getEmail());
-#endif
- }
-
- virtual const char* getResourceType() const { return "WriteEmailVerification"; };
- virtual int run();
-
-private:
- Poco::AutoPtr mUser;
- Poco::AutoPtr mEmailVerificationCode;
-
-};
-
-class WritePassphraseIntoDB : public UniLib::controller::CPUTask
-{
-public:
- WritePassphraseIntoDB(int userId, const std::string& passphrase)
- : mUserId(userId), mPassphrase(passphrase) {
-#ifdef _UNI_LIB_DEBUG
- setName(std::to_string(userId).data());
-#endif
- }
-
-
- virtual int run();
- virtual const char* getResourceType() const { return "WritePassphraseIntoDB"; };
-
-protected:
- int mUserId;
- std::string mPassphrase;
-};
-
-class SessionStateUpdateCommand : public UniLib::controller::Command
-{
-public:
- SessionStateUpdateCommand(SessionStates state, Session* session)
- : mState(state), mSession(session) {}
- virtual int taskFinished(UniLib::controller::Task* task) {
- mSession->updateState(mState);
- return 0;
- }
-
-protected:
- SessionStates mState;
- Session* mSession;
-};
-
-#endif // DR_LUA_WEB_MODULE_SESSION_SESSION_H
+/*!
+*
+* \author: einhornimmond
+*
+* \date: 02.03.19
+*
+* \brief: store session data
+*/
+
+#ifndef DR_LUA_WEB_MODULE_SESSION_SESSION_H
+#define DR_LUA_WEB_MODULE_SESSION_SESSION_H
+
+#include "../lib/ErrorList.h"
+#include "User.h"
+#include "../controller/User.h"
+
+#include "../lib/MultithreadContainer.h"
+#include "../tasks/ProcessingTransaction.h"
+
+#include "../SingletonManager/LanguageManager.h"
+
+#include "../controller/EmailVerificationCode.h"
+
+#include "Poco/Thread.h"
+#include "Poco/Types.h"
+#include "Poco/DateTime.h"
+#include "Poco/Net/IPAddress.h"
+#include "Poco/Net/HTTPCookie.h"
+
+#include
+
+
+class WriteEmailVerification;
+
+enum SessionStates {
+ SESSION_STATE_EMPTY,
+ SESSION_STATE_CRYPTO_KEY_GENERATED,
+ SESSION_STATE_USER_WRITTEN,
+ SESSION_STATE_EMAIL_VERIFICATION_WRITTEN,
+ SESSION_STATE_EMAIL_VERIFICATION_SEND,
+ SESSION_STATE_EMAIL_VERIFICATION_CODE_CHECKED,
+ SESSION_STATE_PASSPHRASE_GENERATED,
+ SESSION_STATE_PASSPHRASE_SHOWN,
+ SESSION_STATE_PASSPHRASE_WRITTEN,
+ SESSION_STATE_KEY_PAIR_GENERATED,
+ SESSION_STATE_KEY_PAIR_WRITTEN,
+ SESSION_STATE_RESET_PASSWORD_REQUEST,
+ SESSION_STATE_RESET_PASSWORD_SUCCEED,
+ SESSION_STATE_COUNT
+};
+
+class SessionManager;
+
+class UpdateUserPasswordPage;
+class PassphrasePage;
+class RepairDefectPassphrase;
+
+class Session : public ErrorList, public UniLib::lib::MultithreadContainer
+{
+ friend WriteEmailVerification;
+ friend SessionManager;
+ friend UpdateUserPasswordPage;
+ friend PassphrasePage;
+ friend RepairDefectPassphrase;
+public:
+ Session(int handle);
+ ~Session();
+
+ // get new model objects
+ Poco::AutoPtr getEmailVerificationCodeObject();
+
+ // set new model objects
+ inline void setUser(Poco::AutoPtr user) { mNewUser = user; }
+ inline Poco::AutoPtr getNewUser() { return mNewUser; }
+
+ // ---------------- User functions ----------------------------
+ // TODO: register state: written into db, mails sended, update state only if new state is higher as old state
+ // create User send e-mail activation link
+ bool createUser(const std::string& first_name, const std::string& last_name, const std::string& email, const std::string& password);
+
+ //! \brief new register function, without showing user pubkeys, using controller/user
+ bool createUserDirect(const std::string& first_name, const std::string& last_name, const std::string& email, const std::string& password);
+
+
+ // adminRegister without passwort
+ bool adminCreateUser(const std::string& first_name, const std::string& last_name, const std::string& email);
+
+ // TODO: check if email exist and if not, fake waiting on password hashing with profiled times of real password hashing
+ UserStates loadUser(const std::string& email, const std::string& password);
+ bool ifUserExist(const std::string& email);
+
+ inline void setUser(Poco::AutoPtr user) { mSessionUser = user; }
+
+
+ bool deleteUser();
+
+ Poco::AutoPtr getUser() {
+ return mSessionUser;
+ }
+
+ // ------------------------- Email Verification Code functions -------------------------------
+
+ bool loadFromEmailVerificationCode(Poco::UInt64 emailVerificationCode);
+
+ //! \return 1 = konto already exist
+ //! -1 = invalid code
+ //! -2 = critical error
+ //! 0 = ok
+ int updateEmailVerification(Poco::UInt64 emailVerificationCode);
+
+ // called from page with same name
+ //! \return 1 = reset password email already send
+ //! \return 2 = reset password email already shortly before
+ //! \return 0 = ok
+ int sendResetPasswordEmail(Poco::AutoPtr user, bool passphraseMemorized);
+ //
+ //! \return 0 = not the same
+ //! \return 1 = same
+ //! \return -1 = error
+ //! \return -2 = critical error
+ int comparePassphraseWithSavedKeys(const std::string& inputPassphrase, Mnemonic* wordSource);
+
+ Poco::Net::HTTPCookie getLoginCookie();
+
+
+ inline int getHandle() { return mHandleId; }
+
+ // ------------------------ Passphrase functions ----------------------------
+
+ inline void setPassphrase(Poco::AutoPtr passphrase) { mNewPassphrase = passphrase; }
+ inline Poco::AutoPtr getPassphrase() { return mNewPassphrase; }
+
+ inline void setPassphrase(const std::string& passphrase) { mPassphrase = passphrase; }
+
+ inline const std::string& getOldPassphrase() { return mPassphrase; }
+ bool generatePassphrase();
+ bool generateKeys(bool savePrivkey, bool savePassphrase);
+
+ inline void setClientIp(Poco::Net::IPAddress ip) { mClientLoginIP = ip; }
+ inline Poco::Net::IPAddress getClientIp() { return mClientLoginIP; }
+
+ inline bool isIPValid(Poco::Net::IPAddress ip) { return mClientLoginIP == ip; }
+ bool isPwdValid(const std::string& pwd);
+ void reset();
+
+ void updateState(SessionStates newState);
+ const char* getSessionStateString();
+ inline SessionStates getSessionState() { SessionStates s; lock("Session::getSessionState"); s = mState; unlock(); return s; }
+
+ inline Poco::UInt64 getEmailVerificationCode() {
+ std::shared_lock _lock(mSharedMutex);
+ if (mEmailVerificationCodeObject.isNull()) return 0; return mEmailVerificationCodeObject->getModel()->getCode();
+ }
+ inline void setEmailVerificationCodeObject(Poco::AutoPtr emailVerficationObject) {
+ std::unique_lock _lock(mSharedMutex);
+ mEmailVerificationCodeObject = emailVerficationObject;
+ }
+ inline model::table::EmailOptInType getEmailVerificationType() {
+ std::shared_lock _lock(mSharedMutex);
+ if (mEmailVerificationCodeObject.isNull()) {
+ return model::table::EMAIL_OPT_IN_EMPTY;
+ }
+ return mEmailVerificationCodeObject->getModel()->getType();
+ }
+
+ //! \return -1 if session is locked
+ //! \return 1 if session is active
+ //! \return 0
+ int isActive();
+ //! \return false if session is locked
+ bool setActive(bool active);
+
+ bool isDeadLocked();
+
+ inline Poco::DateTime getLastActivity() { return mLastActivity; }
+
+ // ------------------------ transactions functions ----------------------------
+
+ //! \return true if succeed
+ bool startProcessingTransaction(const std::string& proto_message_base64, bool autoSign = false);
+ //! \param working if set will filled with transaction running
+ Poco::AutoPtr getNextReadyTransaction(size_t* working = nullptr);
+ bool finalizeTransaction(bool sign, bool reject);
+ size_t getProcessingTransactionCount();
+
+ inline LanguageCatalog* getLanguageCatalog() { return mLanguageCatalog.isNull() ? nullptr : mLanguageCatalog; }
+ void setLanguage(Languages lang);
+ inline void setLanguageCatalog(Poco::AutoPtr languageCatalog) { mLanguageCatalog = languageCatalog; }
+ Languages getLanguage();
+ inline const char* gettext(const char* text) { if (mLanguageCatalog.isNull()) return text; return mLanguageCatalog->gettext(text); }
+
+ // last referer
+ inline void setLastReferer(const std::string& lastReferer) { mLastExternReferer = lastReferer; }
+ inline const std::string& getLastReferer() const { return mLastExternReferer; }
+
+protected:
+ void updateTimeout();
+ inline void setHandle(int newHandle) { mHandleId = newHandle; }
+
+ void detectSessionState();
+ static const char* translateSessionStateToString(SessionStates state);
+
+ inline const std::string& getPassphrase() const { return mPassphrase; }
+
+
+private:
+ int mHandleId;
+ Poco::AutoPtr mSessionUser;
+ Poco::AutoPtr mNewUser;
+ std::string mPassphrase;
+ Poco::AutoPtr mNewPassphrase;
+ Poco::DateTime mLastActivity;
+ Poco::Net::IPAddress mClientLoginIP;
+ std::string mLastExternReferer;
+ Poco::AutoPtr mEmailVerificationCodeObject;
+ std::shared_mutex mSharedMutex;
+
+
+ SessionStates mState;
+
+ bool mActive;
+ std::list> mProcessingTransactions;
+ Poco::AutoPtr mCurrentActiveProcessingTransaction;
+
+ Poco::AutoPtr mLanguageCatalog;
+};
+
+
+class WriteEmailVerification : public UniLib::controller::CPUTask
+{
+public:
+ WriteEmailVerification(Poco::AutoPtr user, Poco::AutoPtr emailVerificationCode, UniLib::controller::CPUSheduler* cpuScheduler, size_t taskDependenceCount = 0)
+ : UniLib::controller::CPUTask(cpuScheduler, taskDependenceCount), mUser(user), mEmailVerificationCode(emailVerificationCode) {
+#ifdef _UNI_LIB_DEBUG
+ setName(user->getEmail());
+#endif
+ }
+
+ virtual const char* getResourceType() const { return "WriteEmailVerification"; };
+ virtual int run();
+
+private:
+ Poco::AutoPtr mUser;
+ Poco::AutoPtr mEmailVerificationCode;
+
+};
+
+class WritePassphraseIntoDB : public UniLib::controller::CPUTask
+{
+public:
+ WritePassphraseIntoDB(int userId, const std::string& passphrase)
+ : mUserId(userId), mPassphrase(passphrase) {
+#ifdef _UNI_LIB_DEBUG
+ setName(std::to_string(userId).data());
+#endif
+ }
+
+
+ virtual int run();
+ virtual const char* getResourceType() const { return "WritePassphraseIntoDB"; };
+
+protected:
+ int mUserId;
+ std::string mPassphrase;
+};
+
+class SessionStateUpdateCommand : public UniLib::controller::Command
+{
+public:
+ SessionStateUpdateCommand(SessionStates state, Session* session)
+ : mState(state), mSession(session) {}
+ virtual int taskFinished(UniLib::controller::Task* task) {
+ mSession->updateState(mState);
+ return 0;
+ }
+
+protected:
+ SessionStates mState;
+ Session* mSession;
+};
+
+#endif // DR_LUA_WEB_MODULE_SESSION_SESSION_H
diff --git a/login_server/src/cpp/model/TransactionCreation.cpp b/login_server/src/cpp/model/TransactionCreation.cpp
index a04c424c7..6e8e38369 100644
--- a/login_server/src/cpp/model/TransactionCreation.cpp
+++ b/login_server/src/cpp/model/TransactionCreation.cpp
@@ -1,72 +1,72 @@
-#include "TransactionCreation.h"
-#include "Poco/DateTimeFormatter.h"
-#include
-
-TransactionCreation::TransactionCreation(const std::string& memo, const model::messages::gradido::TransactionCreation& protoCreation)
- : TransactionBase(memo), mProtoCreation(protoCreation), mReceiverUser(nullptr)
-{
- memset(mReceiverPublicHex, 0, 65);
-}
-
-TransactionCreation::~TransactionCreation()
-{
- if (mReceiverUser) {
- delete mReceiverUser;
- mReceiverUser = nullptr;
- }
-}
-
-int TransactionCreation::prepare()
-{
- const static char functionName[] = { "TransactionCreation::prepare" };
- if (!mProtoCreation.has_receiveramount()) {
- addError(new Error(functionName, "hasn't receiver amount"));
- return -1;
- }
- auto receiverAmount = mProtoCreation.receiveramount();
-
- if (receiverAmount.amount() <= 0) {
- addError(new Error(functionName, "amount must be > 0"));
- return -4;
- }
- if (receiverAmount.amount() > 10000000) {
- addError(new Error(functionName, "amount must be <= 1000 GDD"));
- return -5;
- }
-
- auto receiverPublic = receiverAmount.ed25519_receiver_pubkey();
- if (receiverPublic.size() != 32) {
- addError(new Error(functionName, "receiver public invalid (size not 32)"));
- return -2;
- }
- mReceiverUser = new User((const unsigned char*)receiverPublic.data());
- getErrors(mReceiverUser);
- if (mReceiverUser->getUserState() == USER_EMPTY) {
- sodium_bin2hex(mReceiverPublicHex, 65, (const unsigned char*)receiverPublic.data(), receiverPublic.size());
- delete mReceiverUser;
- mReceiverUser = nullptr;
- }
- else {
- memcpy(mReceiverPublicHex, mReceiverUser->getPublicKeyHex().data(), 64);
- // uncomment because not correctly working
- /*if (!mReceiverUser->validateIdentHash(mProtoCreation.ident_hash())) {
- addError(new Error(functionName, "ident hash isn't the same"));
- addError(new ParamError(functionName, "hash calculated from email: ", mReceiverUser->getEmail()));
- addError(new ParamError(functionName, "hash: ", std::to_string(mProtoCreation.ident_hash())));
- return -3;
- }*/
- }
- //
-
-
- return 0;
-}
-
-std::string TransactionCreation::getTargetDateString()
-{
- // proto format is seconds, poco timestamp format is microseconds
- Poco::Timestamp pocoStamp(mProtoCreation.target_date().seconds() * 1000*1000);
- //Poco::DateTime(pocoStamp);
- return Poco::DateTimeFormatter::format(pocoStamp, "%d. %b %y");
-}
-
+#include "TransactionCreation.h"
+#include "Poco/DateTimeFormatter.h"
+#include
+
+TransactionCreation::TransactionCreation(const std::string& memo, const model::messages::gradido::TransactionCreation& protoCreation)
+ : TransactionBase(memo), mProtoCreation(protoCreation), mReceiverUser(nullptr)
+{
+ memset(mReceiverPublicHex, 0, 65);
+}
+
+TransactionCreation::~TransactionCreation()
+{
+ if (mReceiverUser) {
+ delete mReceiverUser;
+ mReceiverUser = nullptr;
+ }
+}
+
+int TransactionCreation::prepare()
+{
+ const static char functionName[] = { "TransactionCreation::prepare" };
+ if (!mProtoCreation.has_receiveramount()) {
+ addError(new Error(functionName, "hasn't receiver amount"));
+ return -1;
+ }
+ auto receiverAmount = mProtoCreation.receiveramount();
+
+ if (receiverAmount.amount() <= 0) {
+ addError(new Error(functionName, "amount must be > 0"));
+ return -4;
+ }
+ if (receiverAmount.amount() > 10000000) {
+ addError(new Error(functionName, "amount must be <= 1000 GDD"));
+ return -5;
+ }
+
+ auto receiverPublic = receiverAmount.ed25519_receiver_pubkey();
+ if (receiverPublic.size() != 32) {
+ addError(new Error(functionName, "receiver public invalid (size not 32)"));
+ return -2;
+ }
+ mReceiverUser = new User((const unsigned char*)receiverPublic.data());
+ getErrors(mReceiverUser);
+ if (mReceiverUser->getUserState() == USER_EMPTY) {
+ sodium_bin2hex(mReceiverPublicHex, 65, (const unsigned char*)receiverPublic.data(), receiverPublic.size());
+ delete mReceiverUser;
+ mReceiverUser = nullptr;
+ }
+ else {
+ memcpy(mReceiverPublicHex, mReceiverUser->getPublicKeyHex().data(), 64);
+ // uncomment because not correctly working
+ /*if (!mReceiverUser->validateIdentHash(mProtoCreation.ident_hash())) {
+ addError(new Error(functionName, "ident hash isn't the same"));
+ addError(new ParamError(functionName, "hash calculated from email: ", mReceiverUser->getEmail()));
+ addError(new ParamError(functionName, "hash: ", std::to_string(mProtoCreation.ident_hash())));
+ return -3;
+ }*/
+ }
+ //
+
+
+ return 0;
+}
+
+std::string TransactionCreation::getTargetDateString()
+{
+ // proto format is seconds, poco timestamp format is microseconds
+ Poco::Timestamp pocoStamp(mProtoCreation.target_date().seconds() * 1000*1000);
+ //Poco::DateTime(pocoStamp);
+ return Poco::DateTimeFormatter::format(pocoStamp, "%d. %b %y");
+}
+
diff --git a/login_server/src/cpp/model/TransactionTransfer.cpp b/login_server/src/cpp/model/TransactionTransfer.cpp
index 136258816..4c89d7ad0 100644
--- a/login_server/src/cpp/model/TransactionTransfer.cpp
+++ b/login_server/src/cpp/model/TransactionTransfer.cpp
@@ -1,172 +1,172 @@
-#include "TransactionTransfer.h"
-
-const std::string TransactionTransfer::mInvalidIndexMessage("invalid index");
-
-TransactionTransfer::KontoTableEntry::KontoTableEntry(model::table::User* user, google::protobuf::int64 amount, bool negativeAmount/* = false*/)
-{
- //Normaler User <info@software-labor.de>
- if (!user) return;
-
- composeAmountCellString(amount, negativeAmount);
-
- /*kontoNameCell = "";
- kontoNameCell += user->getFirstName();
- kontoNameCell += " ";
- kontoNameCell += user->getLastName();
- kontoNameCell += " <";
- kontoNameCell += user->getEmail();
- kontoNameCell += "> | ";*/
- kontoNameCell = "";
- kontoNameCell += user->getNameWithEmailHtml();
- kontoNameCell += "";
-}
-
-TransactionTransfer::KontoTableEntry::KontoTableEntry(const std::string& pubkeyHex, google::protobuf::int64 amount, bool negativeAmount/* = false*/)
-{
- composeAmountCellString(amount, negativeAmount);
- //kontoNameCell = "0x" + pubkeyHex + " | ";
- kontoNameCell = "" + pubkeyHex + "";
-}
-
-void TransactionTransfer::KontoTableEntry::composeAmountCellString(google::protobuf::int64 amount, bool negativeAmount)
-{
- //-10 GDD
- //10 GDD
- amountCell = "-";
- }
- else {
- amountCell += "success-color\">";
- }
- amountCell += amountToString(amount);
- //amountCell += " GDD";
- amountCell += " GDD";
-}
-
-// ********************************************************************************************************************************
-
-TransactionTransfer::TransactionTransfer(const std::string& memo, const model::messages::gradido::Transfer& protoTransfer)
- : TransactionBase(memo), mProtoTransfer(protoTransfer)
-{
-
-}
-
-TransactionTransfer::~TransactionTransfer()
-{
- mKontoTable.clear();
-}
-
-int TransactionTransfer::prepare()
-{
- lock();
- const static char functionName[] = { "TransactionTransfer::prepare" };
- if (mProtoTransfer.senderamounts_size() == 0) {
- addError(new Error(functionName, "hasn't sender amount(s)"));
- unlock();
- return -1;
- }
- if (mProtoTransfer.receiveramounts_size() == 0) {
- addError(new Error(functionName, "hasn't receiver amount(s)"));
- unlock();
- return -2;
- }
- mKontoTable.reserve(mProtoTransfer.senderamounts_size() + mProtoTransfer.receiveramounts_size());
-
- //auto receiverAmount = mProtoTransfer.receiveramount();
- //auto senderAmount
- int senderSum = 0;
- int receiverSum = 0;
-
- char pubkeyHexTemp[65];
-
- for (int i = 0; i < mProtoTransfer.senderamounts_size(); i++) {
- auto senderAmount = mProtoTransfer.senderamounts(i);
- auto pubkey = senderAmount.ed25519_sender_pubkey();
- senderSum += senderAmount.amount();
- if (pubkey.size() != 32) {
- addError(new ParamError(functionName, "invalid public key for sender ", i));
- unlock();
- return -3;
- }
- //User user((const unsigned char*)pubkey.data());
- auto user = controller::User::create();
- if (!user->load((const unsigned char*)pubkey.data())) {
- sodium_bin2hex(pubkeyHexTemp, 65, (const unsigned char*)pubkey.data(), pubkey.size());
- mKontoTable.push_back(KontoTableEntry(pubkeyHexTemp, senderAmount.amount(), true));
- }
- else {
- mKontoTable.push_back(KontoTableEntry(user->getModel(), senderAmount.amount(), true));
- }
- }
- for (int i = 0; i < mProtoTransfer.receiveramounts_size(); i++) {
- auto receiverAmount = mProtoTransfer.receiveramounts(i);
- auto pubkey = receiverAmount.ed25519_receiver_pubkey();
- receiverSum += receiverAmount.amount();
- if (receiverAmount.ed25519_receiver_pubkey().size() != 32) {
- addError(new ParamError(functionName, "invalid public key for receiver ", i));
- unlock();
- return -4;
- }
- auto user = controller::User::create();
- if (!user->load((const unsigned char*)pubkey.data())) {
- sodium_bin2hex(pubkeyHexTemp, 65, (const unsigned char*)pubkey.data(), pubkey.size());
- mKontoTable.push_back(KontoTableEntry(pubkeyHexTemp, receiverAmount.amount(), false));
- }
- else {
- mKontoTable.push_back(KontoTableEntry(user->getModel(), receiverAmount.amount(), false));
- }
- }
- if (senderSum != receiverSum) {
- addError(new Error(functionName, "sender amounts sum != receiver amounts sum"));
- unlock();
- return -5;
- }
- if (senderSum < 0) {
- addError(new Error(functionName, "negative amount not supported"));
- unlock();
- return -6;
- }
-
- /*
- mReceiverUser = new User(receiverPublic.data());
- getErrors(mReceiverUser);
- if (mReceiverUser->getUserState() == USER_EMPTY) {
- sodium_bin2hex(mReceiverPublicHex, 65, (const unsigned char*)receiverPublic.data(), receiverPublic.size());
- delete mReceiverUser;
- mReceiverUser = nullptr;
- }
- else {
- memcpy(mReceiverPublicHex, mReceiverUser->getPublicKeyHex().data(), 64);
- }
- //*/
-
- unlock();
- return 0;
-}
-
-const std::string& TransactionTransfer::getKontoNameCell(int index)
-{
-
- lock();
- if (index >= mKontoTable.size()) {
- unlock();
- return mInvalidIndexMessage;
- }
- unlock();
-
- return mKontoTable[index].kontoNameCell;
-}
-
-const std::string& TransactionTransfer::getAmountCell(int index)
-{
- lock();
- if (index >= mKontoTable.size()) {
- unlock();
- return mInvalidIndexMessage;
- }
- unlock();
-
- return mKontoTable[index].amountCell;
-}
-
+#include "TransactionTransfer.h"
+
+const std::string TransactionTransfer::mInvalidIndexMessage("invalid index");
+
+TransactionTransfer::KontoTableEntry::KontoTableEntry(model::table::User* user, google::protobuf::int64 amount, bool negativeAmount/* = false*/)
+{
+ //Normaler User <info@software-labor.de>
+ if (!user) return;
+
+ composeAmountCellString(amount, negativeAmount);
+
+ /*kontoNameCell = "";
+ kontoNameCell += user->getFirstName();
+ kontoNameCell += " ";
+ kontoNameCell += user->getLastName();
+ kontoNameCell += " <";
+ kontoNameCell += user->getEmail();
+ kontoNameCell += "> | ";*/
+ kontoNameCell = "";
+ kontoNameCell += user->getNameWithEmailHtml();
+ kontoNameCell += "";
+}
+
+TransactionTransfer::KontoTableEntry::KontoTableEntry(const std::string& pubkeyHex, google::protobuf::int64 amount, bool negativeAmount/* = false*/)
+{
+ composeAmountCellString(amount, negativeAmount);
+ //kontoNameCell = "0x" + pubkeyHex + " | ";
+ kontoNameCell = "" + pubkeyHex + "";
+}
+
+void TransactionTransfer::KontoTableEntry::composeAmountCellString(google::protobuf::int64 amount, bool negativeAmount)
+{
+ //-10 GDD
+ //10 GDD
+ amountCell = "-";
+ }
+ else {
+ amountCell += "success-color\">";
+ }
+ amountCell += amountToString(amount);
+ //amountCell += " GDD";
+ amountCell += " GDD";
+}
+
+// ********************************************************************************************************************************
+
+TransactionTransfer::TransactionTransfer(const std::string& memo, const model::messages::gradido::Transfer& protoTransfer)
+ : TransactionBase(memo), mProtoTransfer(protoTransfer)
+{
+
+}
+
+TransactionTransfer::~TransactionTransfer()
+{
+ mKontoTable.clear();
+}
+
+int TransactionTransfer::prepare()
+{
+ lock();
+ const static char functionName[] = { "TransactionTransfer::prepare" };
+ if (mProtoTransfer.senderamounts_size() == 0) {
+ addError(new Error(functionName, "hasn't sender amount(s)"));
+ unlock();
+ return -1;
+ }
+ if (mProtoTransfer.receiveramounts_size() == 0) {
+ addError(new Error(functionName, "hasn't receiver amount(s)"));
+ unlock();
+ return -2;
+ }
+ mKontoTable.reserve(mProtoTransfer.senderamounts_size() + mProtoTransfer.receiveramounts_size());
+
+ //auto receiverAmount = mProtoTransfer.receiveramount();
+ //auto senderAmount
+ int senderSum = 0;
+ int receiverSum = 0;
+
+ char pubkeyHexTemp[65];
+
+ for (int i = 0; i < mProtoTransfer.senderamounts_size(); i++) {
+ auto senderAmount = mProtoTransfer.senderamounts(i);
+ auto pubkey = senderAmount.ed25519_sender_pubkey();
+ senderSum += senderAmount.amount();
+ if (pubkey.size() != 32) {
+ addError(new ParamError(functionName, "invalid public key for sender ", i));
+ unlock();
+ return -3;
+ }
+ //User user((const unsigned char*)pubkey.data());
+ auto user = controller::User::create();
+ if (!user->load((const unsigned char*)pubkey.data())) {
+ sodium_bin2hex(pubkeyHexTemp, 65, (const unsigned char*)pubkey.data(), pubkey.size());
+ mKontoTable.push_back(KontoTableEntry(pubkeyHexTemp, senderAmount.amount(), true));
+ }
+ else {
+ mKontoTable.push_back(KontoTableEntry(user->getModel(), senderAmount.amount(), true));
+ }
+ }
+ for (int i = 0; i < mProtoTransfer.receiveramounts_size(); i++) {
+ auto receiverAmount = mProtoTransfer.receiveramounts(i);
+ auto pubkey = receiverAmount.ed25519_receiver_pubkey();
+ receiverSum += receiverAmount.amount();
+ if (receiverAmount.ed25519_receiver_pubkey().size() != 32) {
+ addError(new ParamError(functionName, "invalid public key for receiver ", i));
+ unlock();
+ return -4;
+ }
+ auto user = controller::User::create();
+ if (!user->load((const unsigned char*)pubkey.data())) {
+ sodium_bin2hex(pubkeyHexTemp, 65, (const unsigned char*)pubkey.data(), pubkey.size());
+ mKontoTable.push_back(KontoTableEntry(pubkeyHexTemp, receiverAmount.amount(), false));
+ }
+ else {
+ mKontoTable.push_back(KontoTableEntry(user->getModel(), receiverAmount.amount(), false));
+ }
+ }
+ if (senderSum != receiverSum) {
+ addError(new Error(functionName, "sender amounts sum != receiver amounts sum"));
+ unlock();
+ return -5;
+ }
+ if (senderSum < 0) {
+ addError(new Error(functionName, "negative amount not supported"));
+ unlock();
+ return -6;
+ }
+
+ /*
+ mReceiverUser = new User(receiverPublic.data());
+ getErrors(mReceiverUser);
+ if (mReceiverUser->getUserState() == USER_EMPTY) {
+ sodium_bin2hex(mReceiverPublicHex, 65, (const unsigned char*)receiverPublic.data(), receiverPublic.size());
+ delete mReceiverUser;
+ mReceiverUser = nullptr;
+ }
+ else {
+ memcpy(mReceiverPublicHex, mReceiverUser->getPublicKeyHex().data(), 64);
+ }
+ //*/
+
+ unlock();
+ return 0;
+}
+
+const std::string& TransactionTransfer::getKontoNameCell(int index)
+{
+
+ lock();
+ if (index >= mKontoTable.size()) {
+ unlock();
+ return mInvalidIndexMessage;
+ }
+ unlock();
+
+ return mKontoTable[index].kontoNameCell;
+}
+
+const std::string& TransactionTransfer::getAmountCell(int index)
+{
+ lock();
+ if (index >= mKontoTable.size()) {
+ unlock();
+ return mInvalidIndexMessage;
+ }
+ unlock();
+
+ return mKontoTable[index].amountCell;
+}
+
diff --git a/login_server/src/cpp/tasks/AuthenticatedEncryptionCreateKeyTask.cpp b/login_server/src/cpp/tasks/AuthenticatedEncryptionCreateKeyTask.cpp
index fa8048e6a..ac7a6461b 100644
--- a/login_server/src/cpp/tasks/AuthenticatedEncryptionCreateKeyTask.cpp
+++ b/login_server/src/cpp/tasks/AuthenticatedEncryptionCreateKeyTask.cpp
@@ -1,40 +1,40 @@
-#include "AuthenticatedEncryptionCreateKeyTask.h"
-
-#include "../ServerConfig.h"
-#include "../SingletonManager/SingletonTaskObserver.h"
-#include "../SingletonManager/ErrorManager.h"
-
-#include "../lib/Profiler.h"
-
-AuthenticatedEncryptionCreateKeyTask::AuthenticatedEncryptionCreateKeyTask(Poco::AutoPtr user, const std::string& passwd)
- : UniLib::controller::CPUTask(ServerConfig::g_CryptoCPUScheduler), mUser(user), mPassword(passwd)
-{
- assert(!mUser.isNull());
- SingletonTaskObserver::getInstance()->addTask(mUser->getModel()->getEmail(), TASK_OBSERVER_PASSWORD_CREATION);
-}
-
-AuthenticatedEncryptionCreateKeyTask::~AuthenticatedEncryptionCreateKeyTask()
-{
- SingletonTaskObserver::getInstance()->removeTask(mUser->getModel()->getEmail(), TASK_OBSERVER_PASSWORD_CREATION);
-}
-
-int AuthenticatedEncryptionCreateKeyTask::run()
-{
- auto em = ErrorManager::getInstance();
- const static char* function_name = "AuthenticatedEncryptionCreateKeyTask::run";
- auto authenticated_encryption = new AuthenticatedEncryption;
- Profiler timeUsed;
- if (AuthenticatedEncryption::AUTH_ENCRYPT_OK != authenticated_encryption->createKey(mUser->getModel()->getEmail(), mPassword)) {
- em->addError(new Error(function_name, "error creating key"));
- em->addError(new ParamError(function_name, "for email", mUser->getModel()->getEmail()));
- em->addError(new ParamError(function_name, "strerror: ", strerror(errno)));
- em->sendErrorsAsEmail();
- return -1;
- }
- //printf("create password time: %s\n", timeUsed.string().data());
- timeUsed.reset();
- mUser->setNewPassword(authenticated_encryption);
- //printf("set password time: %s\n", timeUsed.string().data());
-
- return 0;
+#include "AuthenticatedEncryptionCreateKeyTask.h"
+
+#include "../ServerConfig.h"
+#include "../SingletonManager/SingletonTaskObserver.h"
+#include "../SingletonManager/ErrorManager.h"
+
+#include "../lib/Profiler.h"
+
+AuthenticatedEncryptionCreateKeyTask::AuthenticatedEncryptionCreateKeyTask(Poco::AutoPtr user, const std::string& passwd)
+ : UniLib::controller::CPUTask(ServerConfig::g_CryptoCPUScheduler), mUser(user), mPassword(passwd)
+{
+ assert(!mUser.isNull());
+ SingletonTaskObserver::getInstance()->addTask(mUser->getModel()->getEmail(), TASK_OBSERVER_PASSWORD_CREATION);
+}
+
+AuthenticatedEncryptionCreateKeyTask::~AuthenticatedEncryptionCreateKeyTask()
+{
+ SingletonTaskObserver::getInstance()->removeTask(mUser->getModel()->getEmail(), TASK_OBSERVER_PASSWORD_CREATION);
+}
+
+int AuthenticatedEncryptionCreateKeyTask::run()
+{
+ auto em = ErrorManager::getInstance();
+ const static char* function_name = "AuthenticatedEncryptionCreateKeyTask::run";
+ auto authenticated_encryption = new AuthenticatedEncryption;
+ Profiler timeUsed;
+ if (AuthenticatedEncryption::AUTH_ENCRYPT_OK != authenticated_encryption->createKey(mUser->getModel()->getEmail(), mPassword)) {
+ em->addError(new Error(function_name, "error creating key"));
+ em->addError(new ParamError(function_name, "for email", mUser->getModel()->getEmail()));
+ em->addError(new ParamError(function_name, "strerror: ", strerror(errno)));
+ em->sendErrorsAsEmail();
+ return -1;
+ }
+ //printf("create password time: %s\n", timeUsed.string().data());
+ timeUsed.reset();
+ mUser->setNewPassword(authenticated_encryption);
+ //printf("set password time: %s\n", timeUsed.string().data());
+
+ return 0;
}
\ No newline at end of file
diff --git a/login_server/src/cpp/tasks/PrepareEmailTask.cpp b/login_server/src/cpp/tasks/PrepareEmailTask.cpp
new file mode 100644
index 000000000..cc20d9472
--- /dev/null
+++ b/login_server/src/cpp/tasks/PrepareEmailTask.cpp
@@ -0,0 +1,65 @@
+#include "PrepareEmailTask.h"
+#include "../lib/Profiler.h"
+#include "../ServerConfig.h"
+#include "../SingletonManager/ErrorManager.h"
+
+#include "Poco/Net/SSLException.h"
+
+PrepareEmailTask::PrepareEmailTask(UniLib::controller::CPUSheduler* cpuScheduler)
+ : UniLib::controller::CPUTask(cpuScheduler), mMailClientSession(nullptr)
+{
+
+}
+
+PrepareEmailTask::~PrepareEmailTask()
+{
+ if (mMailClientSession) {
+ delete mMailClientSession;
+ }
+}
+
+int PrepareEmailTask::run()
+{
+ if (ServerConfig::g_disableEmail) return 0;
+ Profiler timeUsed;
+ mMailClientSession = new Poco::Net::SecureSMTPClientSession(ServerConfig::g_EmailAccount.url, ServerConfig::g_EmailAccount.port);
+ mMailClientSession->login();
+ try {
+ mMailClientSession->startTLS(ServerConfig::g_SSL_CLient_Context);
+ mMailClientSession->login(Poco::Net::SMTPClientSession::AUTH_LOGIN, ServerConfig::g_EmailAccount.username, ServerConfig::g_EmailAccount.password);
+ } catch(Poco::Net::SSLException& ex) {
+ printf("[PrepareEmailTask] ssl certificate error: %s\nPlease make sure you have cacert.pem (CA/root certificates) next to binary from https://curl.haxx.se/docs/caextract.html\n", ex.displayText().data());
+ return -1;
+ }
+
+ //printf("[PrepareEmailTask] time: %s\n", timeUsed.string().data());
+ /*
+ session.login();
+ session.startTLS(pContext);
+ if (!username.empty())
+ {
+ session.login(SMTPClientSession::AUTH_LOGIN, username, password);
+ }
+ session.sendMessage(message);
+ session.close();
+ */
+
+ return 0;
+}
+
+int PrepareEmailTask::send(Poco::Net::MailMessage* message)
+{
+ if (ServerConfig::g_disableEmail) return 0;
+
+ auto er = ErrorManager::getInstance();
+ try {
+ mMailClientSession->sendMessage(*message);
+ mMailClientSession->close();
+ }
+ catch (Poco::Exception& exc) {
+ er->addError(new ParamError("PrepareEmailTask::send", "error sending email", exc.displayText().data()));
+ printf("[PrepareEmailTask::%s] error sending email: %s\n", __FUNCTION__, exc.displayText().data());
+ return -1;
+ }
+ return 0;
+}
\ No newline at end of file
diff --git a/login_server/src/cpp/tasks/PrepareEmailTask.h b/login_server/src/cpp/tasks/PrepareEmailTask.h
new file mode 100644
index 000000000..4036eefc2
--- /dev/null
+++ b/login_server/src/cpp/tasks/PrepareEmailTask.h
@@ -0,0 +1,25 @@
+#ifndef GRADIDO_LOGIN_SERVER_TASKS_PREPAIRE_EMAIL_TASK_INCLUDE
+#define GRADIDO_LOGIN_SERVER_TASKS_PREPAIRE_EMAIL_TASK_INCLUDE
+
+#include "CPUTask.h"
+#include "Poco/Net/SecureSMTPClientSession.h"
+
+
+
+class PrepareEmailTask : public UniLib::controller::CPUTask
+{
+public:
+ PrepareEmailTask(UniLib::controller::CPUSheduler* cpuScheduler);
+ virtual ~PrepareEmailTask();
+
+ virtual int run();
+ int send(Poco::Net::MailMessage* message);
+ virtual const char* getResourceType() const { return "PrepareEmailTask"; };
+protected:
+
+private:
+ Poco::Net::SecureSMTPClientSession* mMailClientSession;
+};
+
+
+#endif //GRADIDO_LOGIN_SERVER_TASKS_PREPAIRE_EMAIL_TASK_INCLUDE
\ No newline at end of file
diff --git a/login_server/src/cpp/tasks/ProcessingTransaction.cpp b/login_server/src/cpp/tasks/ProcessingTransaction.cpp
index 3cf9905a8..6ed76f974 100644
--- a/login_server/src/cpp/tasks/ProcessingTransaction.cpp
+++ b/login_server/src/cpp/tasks/ProcessingTransaction.cpp
@@ -1,4 +1,4 @@
-#include "ProcessingTransaction.h"
+#include "ProcessingTransaction.h"
#include
#include "../model/TransactionCreation.h"
diff --git a/login_server/src/cpp/tasks/SendEmailTask.cpp b/login_server/src/cpp/tasks/SendEmailTask.cpp
new file mode 100644
index 000000000..e3a5e0069
--- /dev/null
+++ b/login_server/src/cpp/tasks/SendEmailTask.cpp
@@ -0,0 +1,64 @@
+#include "SendEmailTask.h"
+#include "PrepareEmailTask.h"
+#include "../lib/Profiler.h"
+#include "../SingletonManager/ErrorManager.h"
+#include "../SingletonManager/EmailManager.h"
+#include "../ServerConfig.h"
+
+#include "Poco/Net/MediaType.h"
+
+SendEmailTask::SendEmailTask(Poco::Net::MailMessage* mailMessage, UniLib::controller::CPUSheduler* cpuScheduler, size_t additionalTaskDependenceCount/* = 0*/)
+ : UniLib::controller::CPUTask(cpuScheduler, additionalTaskDependenceCount+1), mMailMessage(mailMessage), mEmail(nullptr)
+{
+}
+
+SendEmailTask::SendEmailTask(model::Email*email, UniLib::controller::CPUSheduler* cpuScheduler, size_t additionalTaskDependenceCount/* = 0*/)
+ : UniLib::controller::CPUTask(cpuScheduler, additionalTaskDependenceCount), mMailMessage(nullptr), mEmail(email)
+{
+
+}
+
+SendEmailTask::~SendEmailTask()
+{
+ if (mMailMessage) {
+ delete mMailMessage;
+ mMailMessage = nullptr;
+ }
+ if (mEmail) {
+ delete mEmail;
+ mEmail = nullptr;
+ }
+
+}
+
+int SendEmailTask::run()
+{
+ if(ServerConfig::g_disableEmail) return 0;
+
+ Profiler timeUsed;
+ auto er = ErrorManager::getInstance();
+ auto parent = getParent(0);
+
+ if (mMailMessage) {
+
+ if (strcmp(parent->getResourceType(), "PrepareEmailTask") != 0) {
+ er->addError(new Error("SendEmailTask", "first parent isn't PrepareEmailTask"));
+ er->sendErrorsAsEmail();
+ return -1;
+ }
+ PrepareEmailTask* prepare = (PrepareEmailTask*)&(*parent);
+ mMailMessage->setSender(ServerConfig::g_EmailAccount.sender);
+
+ if (prepare->send(mMailMessage)) {
+ er->sendErrorsAsEmail();
+ return -1;
+ }
+ }
+ else if (mEmail) {
+ auto em = EmailManager::getInstance();
+ em->addEmail(mEmail);
+ mEmail = nullptr;
+ }
+ //printf("[SendEmailTask] time: %s\n", timeUsed.string().data());
+ return 0;
+}
\ No newline at end of file
diff --git a/login_server/src/cpp/tasks/SendEmailTask.h b/login_server/src/cpp/tasks/SendEmailTask.h
new file mode 100644
index 000000000..86e4b76dc
--- /dev/null
+++ b/login_server/src/cpp/tasks/SendEmailTask.h
@@ -0,0 +1,36 @@
+#ifndef GRADIDO_LOGIN_SERVER_TASKS_SEND_EMAIL_TASK_INCLUDE
+#define GRADIDO_LOGIN_SERVER_TASKS_SEND_EMAIL_TASK_INCLUDE
+
+#include "CPUTask.h"
+#include "Poco/Net/MailMessage.h"
+
+#include "../model/email/Email.h"
+
+/*
+ * @author: Dario Rekowski
+ *
+ * @date: 29.09.19
+ * @desc: Task for send an email, the first parent dependence pointer must be a prepare email task
+*/
+
+
+class SendEmailTask : public UniLib::controller::CPUTask
+{
+public:
+
+ SendEmailTask(Poco::Net::MailMessage* mailMessage, UniLib::controller::CPUSheduler* cpuScheduler, size_t additionalTaskDependenceCount = 0);
+ SendEmailTask(model::Email* email, UniLib::controller::CPUSheduler* cpuScheduler, size_t additionalTaskDependenceCount = 0);
+ virtual ~SendEmailTask();
+
+ virtual int run();
+
+ virtual const char* getResourceType() const { return "SendEmailTask"; };
+protected:
+
+private:
+ Poco::Net::MailMessage* mMailMessage;
+ model::Email* mEmail;
+};
+
+
+#endif //GRADIDO_LOGIN_SERVER_TASKS_SEND_EMAIL_TASK_INCLUDE
\ No newline at end of file
diff --git a/login_server/src/cpp/tasks/SigningTransaction.cpp b/login_server/src/cpp/tasks/SigningTransaction.cpp
index f25d37bb4..ed87a03d0 100644
--- a/login_server/src/cpp/tasks/SigningTransaction.cpp
+++ b/login_server/src/cpp/tasks/SigningTransaction.cpp
@@ -1,284 +1,284 @@
-#include "SigningTransaction.h"
-
-#include
-
-#include "../SingletonManager/ErrorManager.h"
-#include "../SingletonManager/MemoryManager.h"
-#include "../SingletonManager/SingletonTaskObserver.h"
-
-#include "../lib/Profiler.h"
-
-#include "../proto/gradido/Transaction.pb.h"
-
-#include "sodium.h"
-
-#include "../ServerConfig.h"
-#include "Poco/JSON/Object.h"
-#include "Poco/JSON/Parser.h"
-#include "Poco/StreamCopier.h"
-#include "Poco/Net/HTTPSClientSession.h"
-#include "Poco/Net/HTTPRequest.h"
-#include "Poco/Net/HTTPResponse.h"
-
-SigningTransaction::SigningTransaction(
- Poco::AutoPtr processingeTransaction,
- Poco::AutoPtr newUser
- , bool sendErrorsToAdmin/* = true*/)
- : mProcessingeTransaction(processingeTransaction), mNewUser(newUser), mSendErrorsToAdminEmail(sendErrorsToAdmin)
-{
- auto ob = SingletonTaskObserver::getInstance();
- auto email = getUserEmail();
-
- if (email != "") {
- ob->addTask(email, TASK_OBSERVER_SIGN_TRANSACTION);
- }
-}
-
-SigningTransaction::~SigningTransaction()
-{
- auto ob = SingletonTaskObserver::getInstance();
- auto email = getUserEmail();
-
- if (email != "") {
- ob->removeTask(email, TASK_OBSERVER_SIGN_TRANSACTION);
- }
-}
-
-std::string SigningTransaction::getUserEmail()
-{
- model::table::User* user_model = nullptr;
-
- if (!mNewUser.isNull()) {
- user_model = mNewUser->getModel();
- }
- if (user_model) {
- return user_model->getEmail();
- }
- return "";
-}
-
-int SigningTransaction::run() {
- auto mm = MemoryManager::getInstance();
-
- Error* transactionError = new Error("SigningTransaction", mProcessingeTransaction->mProtoMessageBase64.data());
- addError(transactionError, false);
-
- //= new Error("SigningTransaction start", mProcessingeTransaction->g)
- //if (mUser.isNull() || !mUser->hasCryptoKey()) {
- if(mNewUser.isNull() || !mNewUser->hasPassword()) {
- addError(new Error("SigningTransaction", "user hasn't crypto key or is null"));
- if(mSendErrorsToAdminEmail) sendErrorsAsEmail();
- return -1;
- }
-
- //auto privKey = mUser->getPrivKey();
- //if (!mUser->hasPrivKey()) {
- auto gradido_key_pair = mNewUser->getGradidoKeyPair();
- KeyPairEd25519* recovered_gradido_key_pair = nullptr;
- if(!gradido_key_pair || !gradido_key_pair->hasPrivateKey()) {
-
- if (!mNewUser->tryLoadPassphraseUserBackup(&recovered_gradido_key_pair)) {
- if(mNewUser->setGradidoKeyPair(recovered_gradido_key_pair))
- {
- mNewUser->getModel()->updatePrivkey();
- }
- }
- else {
- addError(new Error("SigningTransaction", "user cannot decrypt private key"));
- if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
- return -2;
- }
- }
- // get body bytes
- model::messages::gradido::Transaction transaction;
- auto bodyBytes = transaction.mutable_bodybytes();
- *bodyBytes = mProcessingeTransaction->getBodyBytes();
- if (*bodyBytes == "") {
- getErrors(mProcessingeTransaction);
- if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
- return -3;
- }
- // sign
- //auto sign = mUser->sign((const unsigned char*)bodyBytes->data(), bodyBytes->size());
- MemoryBin* sign = nullptr;
- if (gradido_key_pair) {
- sign = gradido_key_pair->sign(*bodyBytes);
- }
- else if (recovered_gradido_key_pair) {
- sign = recovered_gradido_key_pair->sign(*bodyBytes);
- }
- if (!sign) {
- ErrorManager::getInstance()->sendErrorsAsEmail();
- if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
- mm->releaseMemory(sign);
- return -4;
- }
-
- // pubkey for signature
- /*auto pubkeyBin = mm->getFreeMemory(ed25519_pubkey_SIZE);
- size_t realBin = 0;
- if (sodium_hex2bin(*pubkeyBin, *pubkeyBin, pubkeyHex.data(), pubkeyHex.size(), nullptr, &realBin, nullptr)) {
- addError(new Error("SigningTransaction", "error in sodium_hex2bin"));
- sendErrorsAsEmail();
- mm->releaseMemory(pubkeyBin);
- mm->releaseMemory(sign);
- return -5;
- }
- */
- // add to message
- auto sigMap = transaction.mutable_sigmap();
- auto sigPair = sigMap->add_sigpair();
-
- auto pubkeyBytes = sigPair->mutable_pubkey();
- auto pubkeyBin = mNewUser->getModel()->getPublicKey();
- *pubkeyBytes = std::string((const char*)pubkeyBin, crypto_sign_PUBLICKEYBYTES);
-
-
- auto sigBytes = sigPair->mutable_ed25519();
- *sigBytes = std::string((char*)*sign, sign->size());
- mm->releaseMemory(sign);
-
- /*std::string protoPrettyPrint;
- google::protobuf::TextFormat::PrintToString(transaction, &protoPrettyPrint);
- printf("transaction pretty: %s\n", protoPrettyPrint.data());
- model::messages::gradido::TransactionBody transactionBody;
- transactionBody.MergeFromString(transaction.bodybytes());
- google::protobuf::TextFormat::PrintToString(transactionBody, &protoPrettyPrint);
- printf("transaction body pretty: \n%s\n", protoPrettyPrint.data());
- */
- // finalize
- //printf("sigpair size: %d\n", transaction.sigmap().sigpair_size());
- std::string finalTransactionBin = transaction.SerializeAsString();
- if (finalTransactionBin == "") {
- addError(new Error("SigningTransaction", "error serializing final transaction"));
- if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
- return -6;
- }
-
- // finale to base64
- auto finalBase64Size = sodium_base64_encoded_len(finalTransactionBin.size(), sodium_base64_VARIANT_URLSAFE_NO_PADDING);
- auto finalBase64Bin = mm->getFreeMemory(finalBase64Size);
- if (!sodium_bin2base64(*finalBase64Bin, finalBase64Size, (const unsigned char*)finalTransactionBin.data(), finalTransactionBin.size(), sodium_base64_VARIANT_URLSAFE_NO_PADDING)) {
- addError(new Error("SigningTransaction", "error convert final transaction to base64"));
- if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
- mm->releaseMemory(finalBase64Bin);
- return -7;
- }
- addError(new Error("Signing transaction final", *finalBase64Bin), false);
-
- // create json request
-
- Poco::JSON::Object requestJson;
- requestJson.set("method", "putTransaction");
- requestJson.set("transaction", std::string((char*)*finalBase64Bin));
- //printf("\nbase64 transaction: \n%s\n\n", (char*)*finalBase64Bin);
- mm->releaseMemory(finalBase64Bin);
-
-
- //std::string request = requestJson.stringify();
-
- // send post request via https
- // 443 = HTTPS Default
- // or http via port 80 if it is a test server
- // TODO: adding port into ServerConfig
- bool choose_ssl = false;
- try {
- Profiler phpRequestTime;
- Poco::Net::HTTPClientSession* clientSession = nullptr;
-
- if (ServerConfig::g_phpServerPort) {
- clientSession = new Poco::Net::HTTPSClientSession(ServerConfig::g_php_serverHost, ServerConfig::g_phpServerPort);
- choose_ssl = true;
- }
- else if (ServerConfig::SERVER_TYPE_PRODUCTION == ServerConfig::g_ServerSetupType ||
- ServerConfig::SERVER_TYPE_STAGING == ServerConfig::g_ServerSetupType) {
- clientSession = new Poco::Net::HTTPSClientSession(ServerConfig::g_php_serverHost, 443);
- choose_ssl = true;
- }
- else {
- clientSession = new Poco::Net::HTTPClientSession(ServerConfig::g_php_serverHost, 80);
- choose_ssl = false;
- }
- Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, "/JsonRequestHandler");
-
- request.setChunkedTransferEncoding(true);
- std::ostream& requestStream = clientSession->sendRequest(request);
- requestJson.stringify(requestStream);
-
- Poco::Net::HTTPResponse response;
- std::istream& request_stream = clientSession->receiveResponse(response);
-
- // debugging answer
-
- std::stringstream responseStringStream;
- for (std::string line; std::getline(request_stream, line); ) {
- responseStringStream << line << std::endl;
- }
- Poco::Logger& speedLog= Poco::Logger::get("SpeedLog");
- speedLog.information("[putTransaction] php server time: %s", phpRequestTime.string());
-
- // extract parameter from request
- Poco::JSON::Parser jsonParser;
- Poco::Dynamic::Var parsedJson;
- try {
- parsedJson = jsonParser.parse(responseStringStream.str());
- }
- catch (Poco::Exception& ex) {
- //printf("[JsonRequestHandler::handleRequest] Exception: %s\n", ex.displayText().data());
- addError(new ParamError("SigningTransaction", "error parsing request answer", ex.displayText().data()));
-
- std::string log_Path = "/var/log/grd_login/";
- //#ifdef _WIN32
-#if defined(_WIN32) || defined(_WIN64)
- log_Path = "./";
-#endif
- log_Path += "response.html";
- FILE* f = fopen(log_Path.data(), "wt");
- if (f) {
- std::string responseString = responseStringStream.str();
- fwrite(responseString.data(), 1, responseString.size(), f);
- fclose(f);
- }
- // */
- if (mSendErrorsToAdminEmail) sendErrorsAsEmail(responseStringStream.str());
- return -9;
- }
-
- //sendErrorsAsEmail("HalloRote Test ");
-
- Poco::JSON::Object object = *parsedJson.extract();
-
- std::string stateString = "";
- if (!object.isNull("state")) {
- auto state = object.get("state");
- stateString = state.convert();
- }
- if (stateString != "success") {
- addError(new Error("SigningTransaction", "php server don't return success"));
- if (!object.isNull("msg")) {
- addError(new ParamError("SigningTransaction", "msg:", object.get("msg").convert().data()));
- }
- if (!object.isNull("details")) {
- addError(new ParamError("SigningTransaction", "details:", object.get("details").convert().data()));
- }
- if (!object.isNull("user_error")) {
- addError(new ParamError("SigningTransaction", "user_error", object.get("user_error").convert().data()));
- }
- if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
- return -10;
- }
- delete clientSession;
- //printf("state: %s\n", stateString.data());
- //int zahl = 1;
- }
- catch (Poco::Exception& e) {
- addError(new ParamError("SigningTransaction", "connect error to php server", e.displayText().data()));
- addError(new ParamError("SigningTransaction", "url", ServerConfig::g_php_serverHost.data()));
- addError(new ParamError("SigningTransaction", "choose_ssl", choose_ssl));
- if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
- return -8;
- }
-
-
- return 0;
+#include "SigningTransaction.h"
+
+#include
+
+#include "../SingletonManager/ErrorManager.h"
+#include "../SingletonManager/MemoryManager.h"
+#include "../SingletonManager/SingletonTaskObserver.h"
+
+#include "../lib/Profiler.h"
+
+#include "../proto/gradido/Transaction.pb.h"
+
+#include "sodium.h"
+
+#include "../ServerConfig.h"
+#include "Poco/JSON/Object.h"
+#include "Poco/JSON/Parser.h"
+#include "Poco/StreamCopier.h"
+#include "Poco/Net/HTTPSClientSession.h"
+#include "Poco/Net/HTTPRequest.h"
+#include "Poco/Net/HTTPResponse.h"
+
+SigningTransaction::SigningTransaction(
+ Poco::AutoPtr processingeTransaction,
+ Poco::AutoPtr newUser
+ , bool sendErrorsToAdmin/* = true*/)
+ : mProcessingeTransaction(processingeTransaction), mNewUser(newUser), mSendErrorsToAdminEmail(sendErrorsToAdmin)
+{
+ auto ob = SingletonTaskObserver::getInstance();
+ auto email = getUserEmail();
+
+ if (email != "") {
+ ob->addTask(email, TASK_OBSERVER_SIGN_TRANSACTION);
+ }
+}
+
+SigningTransaction::~SigningTransaction()
+{
+ auto ob = SingletonTaskObserver::getInstance();
+ auto email = getUserEmail();
+
+ if (email != "") {
+ ob->removeTask(email, TASK_OBSERVER_SIGN_TRANSACTION);
+ }
+}
+
+std::string SigningTransaction::getUserEmail()
+{
+ model::table::User* user_model = nullptr;
+
+ if (!mNewUser.isNull()) {
+ user_model = mNewUser->getModel();
+ }
+ if (user_model) {
+ return user_model->getEmail();
+ }
+ return "";
+}
+
+int SigningTransaction::run() {
+ auto mm = MemoryManager::getInstance();
+
+ Error* transactionError = new Error("SigningTransaction", mProcessingeTransaction->mProtoMessageBase64.data());
+ addError(transactionError, false);
+
+ //= new Error("SigningTransaction start", mProcessingeTransaction->g)
+ //if (mUser.isNull() || !mUser->hasCryptoKey()) {
+ if(mNewUser.isNull() || !mNewUser->hasPassword()) {
+ addError(new Error("SigningTransaction", "user hasn't crypto key or is null"));
+ if(mSendErrorsToAdminEmail) sendErrorsAsEmail();
+ return -1;
+ }
+
+ //auto privKey = mUser->getPrivKey();
+ //if (!mUser->hasPrivKey()) {
+ auto gradido_key_pair = mNewUser->getGradidoKeyPair();
+ KeyPairEd25519* recovered_gradido_key_pair = nullptr;
+ if(!gradido_key_pair || !gradido_key_pair->hasPrivateKey()) {
+
+ if (!mNewUser->tryLoadPassphraseUserBackup(&recovered_gradido_key_pair)) {
+ if(mNewUser->setGradidoKeyPair(recovered_gradido_key_pair))
+ {
+ mNewUser->getModel()->updatePrivkey();
+ }
+ }
+ else {
+ addError(new Error("SigningTransaction", "user cannot decrypt private key"));
+ if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
+ return -2;
+ }
+ }
+ // get body bytes
+ model::messages::gradido::Transaction transaction;
+ auto bodyBytes = transaction.mutable_bodybytes();
+ *bodyBytes = mProcessingeTransaction->getBodyBytes();
+ if (*bodyBytes == "") {
+ getErrors(mProcessingeTransaction);
+ if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
+ return -3;
+ }
+ // sign
+ //auto sign = mUser->sign((const unsigned char*)bodyBytes->data(), bodyBytes->size());
+ MemoryBin* sign = nullptr;
+ if (gradido_key_pair) {
+ sign = gradido_key_pair->sign(*bodyBytes);
+ }
+ else if (recovered_gradido_key_pair) {
+ sign = recovered_gradido_key_pair->sign(*bodyBytes);
+ }
+ if (!sign) {
+ ErrorManager::getInstance()->sendErrorsAsEmail();
+ if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
+ mm->releaseMemory(sign);
+ return -4;
+ }
+
+ // pubkey for signature
+ /*auto pubkeyBin = mm->getFreeMemory(ed25519_pubkey_SIZE);
+ size_t realBin = 0;
+ if (sodium_hex2bin(*pubkeyBin, *pubkeyBin, pubkeyHex.data(), pubkeyHex.size(), nullptr, &realBin, nullptr)) {
+ addError(new Error("SigningTransaction", "error in sodium_hex2bin"));
+ sendErrorsAsEmail();
+ mm->releaseMemory(pubkeyBin);
+ mm->releaseMemory(sign);
+ return -5;
+ }
+ */
+ // add to message
+ auto sigMap = transaction.mutable_sigmap();
+ auto sigPair = sigMap->add_sigpair();
+
+ auto pubkeyBytes = sigPair->mutable_pubkey();
+ auto pubkeyBin = mNewUser->getModel()->getPublicKey();
+ *pubkeyBytes = std::string((const char*)pubkeyBin, crypto_sign_PUBLICKEYBYTES);
+
+
+ auto sigBytes = sigPair->mutable_ed25519();
+ *sigBytes = std::string((char*)*sign, sign->size());
+ mm->releaseMemory(sign);
+
+ /*std::string protoPrettyPrint;
+ google::protobuf::TextFormat::PrintToString(transaction, &protoPrettyPrint);
+ printf("transaction pretty: %s\n", protoPrettyPrint.data());
+ model::messages::gradido::TransactionBody transactionBody;
+ transactionBody.MergeFromString(transaction.bodybytes());
+ google::protobuf::TextFormat::PrintToString(transactionBody, &protoPrettyPrint);
+ printf("transaction body pretty: \n%s\n", protoPrettyPrint.data());
+ */
+ // finalize
+ //printf("sigpair size: %d\n", transaction.sigmap().sigpair_size());
+ std::string finalTransactionBin = transaction.SerializeAsString();
+ if (finalTransactionBin == "") {
+ addError(new Error("SigningTransaction", "error serializing final transaction"));
+ if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
+ return -6;
+ }
+
+ // finale to base64
+ auto finalBase64Size = sodium_base64_encoded_len(finalTransactionBin.size(), sodium_base64_VARIANT_URLSAFE_NO_PADDING);
+ auto finalBase64Bin = mm->getFreeMemory(finalBase64Size);
+ if (!sodium_bin2base64(*finalBase64Bin, finalBase64Size, (const unsigned char*)finalTransactionBin.data(), finalTransactionBin.size(), sodium_base64_VARIANT_URLSAFE_NO_PADDING)) {
+ addError(new Error("SigningTransaction", "error convert final transaction to base64"));
+ if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
+ mm->releaseMemory(finalBase64Bin);
+ return -7;
+ }
+ addError(new Error("Signing transaction final", *finalBase64Bin), false);
+
+ // create json request
+
+ Poco::JSON::Object requestJson;
+ requestJson.set("method", "putTransaction");
+ requestJson.set("transaction", std::string((char*)*finalBase64Bin));
+ //printf("\nbase64 transaction: \n%s\n\n", (char*)*finalBase64Bin);
+ mm->releaseMemory(finalBase64Bin);
+
+
+ //std::string request = requestJson.stringify();
+
+ // send post request via https
+ // 443 = HTTPS Default
+ // or http via port 80 if it is a test server
+ // TODO: adding port into ServerConfig
+ bool choose_ssl = false;
+ try {
+ Profiler phpRequestTime;
+ Poco::Net::HTTPClientSession* clientSession = nullptr;
+
+ if (ServerConfig::g_phpServerPort) {
+ clientSession = new Poco::Net::HTTPSClientSession(ServerConfig::g_php_serverHost, ServerConfig::g_phpServerPort);
+ choose_ssl = true;
+ }
+ else if (ServerConfig::SERVER_TYPE_PRODUCTION == ServerConfig::g_ServerSetupType ||
+ ServerConfig::SERVER_TYPE_STAGING == ServerConfig::g_ServerSetupType) {
+ clientSession = new Poco::Net::HTTPSClientSession(ServerConfig::g_php_serverHost, 443);
+ choose_ssl = true;
+ }
+ else {
+ clientSession = new Poco::Net::HTTPClientSession(ServerConfig::g_php_serverHost, 80);
+ choose_ssl = false;
+ }
+ Poco::Net::HTTPRequest request(Poco::Net::HTTPRequest::HTTP_POST, "/JsonRequestHandler");
+
+ request.setChunkedTransferEncoding(true);
+ std::ostream& requestStream = clientSession->sendRequest(request);
+ requestJson.stringify(requestStream);
+
+ Poco::Net::HTTPResponse response;
+ std::istream& request_stream = clientSession->receiveResponse(response);
+
+ // debugging answer
+
+ std::stringstream responseStringStream;
+ for (std::string line; std::getline(request_stream, line); ) {
+ responseStringStream << line << std::endl;
+ }
+ Poco::Logger& speedLog= Poco::Logger::get("SpeedLog");
+ speedLog.information("[putTransaction] php server time: %s", phpRequestTime.string());
+
+ // extract parameter from request
+ Poco::JSON::Parser jsonParser;
+ Poco::Dynamic::Var parsedJson;
+ try {
+ parsedJson = jsonParser.parse(responseStringStream.str());
+ }
+ catch (Poco::Exception& ex) {
+ //printf("[JsonRequestHandler::handleRequest] Exception: %s\n", ex.displayText().data());
+ addError(new ParamError("SigningTransaction", "error parsing request answer", ex.displayText().data()));
+
+ std::string log_Path = "/var/log/grd_login/";
+ //#ifdef _WIN32
+#if defined(_WIN32) || defined(_WIN64)
+ log_Path = "./";
+#endif
+ log_Path += "response.html";
+ FILE* f = fopen(log_Path.data(), "wt");
+ if (f) {
+ std::string responseString = responseStringStream.str();
+ fwrite(responseString.data(), 1, responseString.size(), f);
+ fclose(f);
+ }
+ // */
+ if (mSendErrorsToAdminEmail) sendErrorsAsEmail(responseStringStream.str());
+ return -9;
+ }
+
+ //sendErrorsAsEmail("HalloRote Test ");
+
+ Poco::JSON::Object object = *parsedJson.extract();
+
+ std::string stateString = "";
+ if (!object.isNull("state")) {
+ auto state = object.get("state");
+ stateString = state.convert();
+ }
+ if (stateString != "success") {
+ addError(new Error("SigningTransaction", "php server don't return success"));
+ if (!object.isNull("msg")) {
+ addError(new ParamError("SigningTransaction", "msg:", object.get("msg").convert().data()));
+ }
+ if (!object.isNull("details")) {
+ addError(new ParamError("SigningTransaction", "details:", object.get("details").convert().data()));
+ }
+ if (!object.isNull("user_error")) {
+ addError(new ParamError("SigningTransaction", "user_error", object.get("user_error").convert().data()));
+ }
+ if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
+ return -10;
+ }
+ delete clientSession;
+ //printf("state: %s\n", stateString.data());
+ //int zahl = 1;
+ }
+ catch (Poco::Exception& e) {
+ addError(new ParamError("SigningTransaction", "connect error to php server", e.displayText().data()));
+ addError(new ParamError("SigningTransaction", "url", ServerConfig::g_php_serverHost.data()));
+ addError(new ParamError("SigningTransaction", "choose_ssl", choose_ssl));
+ if (mSendErrorsToAdminEmail) sendErrorsAsEmail();
+ return -8;
+ }
+
+
+ return 0;
}
\ No newline at end of file
diff --git a/login_server/src/cpp/tasks/SigningTransaction.h b/login_server/src/cpp/tasks/SigningTransaction.h
index 2038c0669..a35da915c 100644
--- a/login_server/src/cpp/tasks/SigningTransaction.h
+++ b/login_server/src/cpp/tasks/SigningTransaction.h
@@ -1,46 +1,46 @@
-#ifndef GRADIDO_LOGIN_SERVER_TASKS_SIGNING_TRANSACTION_INCLUDE
-#define GRADIDO_LOGIN_SERVER_TASKS_SIGNING_TRANSACTION_INCLUDE
-
-#include "CPUTask.h"
-
-#include "../lib/ErrorList.h"
-#include "../model/TransactionBase.h"
-#include "../model/User.h"
-#include "../controller/User.h"
-
-#include "../proto/gradido/Transaction.pb.h"
-
-#include "ProcessingTransaction.h"
-
-/*
-* @author: Dario Rekowski
-*
-* @date: 28.10.19
-* @desc: Task for signing Transactions
-*/
-
-class SigningTransaction : public UniLib::controller::CPUTask, public ErrorList
-{
-public:
- SigningTransaction(Poco::AutoPtr processingeTransaction, Poco::AutoPtr newUser, bool sendErrorsToAdmin = true);
- virtual ~SigningTransaction();
-
- int run();
-
- const char* getResourceType() const { return "SigningTransaction"; };
-
-
-
-protected:
- Poco::AutoPtr mProcessingeTransaction;
- Poco::AutoPtr mNewUser;
- bool mSendErrorsToAdminEmail;
-
-private:
-
- std::string getUserEmail();
-
-};
-
-
+#ifndef GRADIDO_LOGIN_SERVER_TASKS_SIGNING_TRANSACTION_INCLUDE
+#define GRADIDO_LOGIN_SERVER_TASKS_SIGNING_TRANSACTION_INCLUDE
+
+#include "CPUTask.h"
+
+#include "../lib/ErrorList.h"
+#include "../model/TransactionBase.h"
+#include "../model/User.h"
+#include "../controller/User.h"
+
+#include "../proto/gradido/Transaction.pb.h"
+
+#include "ProcessingTransaction.h"
+
+/*
+* @author: Dario Rekowski
+*
+* @date: 28.10.19
+* @desc: Task for signing Transactions
+*/
+
+class SigningTransaction : public UniLib::controller::CPUTask, public ErrorList
+{
+public:
+ SigningTransaction(Poco::AutoPtr processingeTransaction, Poco::AutoPtr newUser, bool sendErrorsToAdmin = true);
+ virtual ~SigningTransaction();
+
+ int run();
+
+ const char* getResourceType() const { return "SigningTransaction"; };
+
+
+
+protected:
+ Poco::AutoPtr mProcessingeTransaction;
+ Poco::AutoPtr mNewUser;
+ bool mSendErrorsToAdminEmail;
+
+private:
+
+ std::string getUserEmail();
+
+};
+
+
#endif //GRADIDO_LOGIN_SERVER_TASKS_SIGNING_TRANSACTION_INCLUDE
\ No newline at end of file
diff --git a/login_server/src/cpsp/login.cpsp b/login_server/src/cpsp/login.cpsp
index 87a2aac3d..fc82adf2c 100644
--- a/login_server/src/cpsp/login.cpsp
+++ b/login_server/src/cpsp/login.cpsp
@@ -1,205 +1,205 @@
-<%@ page class="LoginPage" %>
-<%@ page form="true" %>
-<%@ page baseClass="SessionHTTPRequestHandler" %>
-<%@ page ctorArg="Session*" %>
-<%@ header include="SessionHTTPRequestHandler.h" %>
-<%@ page compressed="true" %>
-<%!
-#include "../gettext.h"
-
-#include "Poco/Net/HTTPCookie.h"
-#include "Poco/Net/HTTPServerParams.h"
-#include "Poco/Logger.h"
-#include "../SingletonManager/SessionManager.h"
-#include "../SingletonManager/LanguageManager.h"
-#include "../SingletonManager/ErrorManager.h"
-
-%>
-<%%
- const char* pageName = "Login";
- auto sm = SessionManager::getInstance();
- auto lm = LanguageManager::getInstance();
- auto em = ErrorManager::getInstance();
-
- auto lang = chooseLanguage(request);
- //printf("choose language return: %d\n", lang);
- auto langCatalog = lm->getFreeCatalog(lang);
-
- std::string presetEmail("");
- if(mSession && mSession->getUser()) {
- presetEmail = mSession->getUser()->getEmail();
- }
-
- if(!form.empty()) {
-
- bool langUpdatedByBtn = false;
- auto langBtn = form.get("lang", "");
- if(langBtn != "") {
- langUpdatedByBtn = true;
- }
- /*
- auto langInput = form.get("lang", "");
- auto updatedLang = LANG_NULL;
- if(langBtn != "") {
- updatedLang = chooseLanguage(request, langBtn);
- langUpdatedByBtn = true;
- } else if(langInput != "") {
- updatedLang = chooseLanguage(request, langInput);
- }
-
- if(updatedLang != LANG_NULL && updatedLang != lang) {
- lang = updatedLang;
- langCatalog = lm->getFreeCatalog(lang);
- }
- */
- auto email = form.get("login-email", "");
- auto password = form.get("login-password", "");
-
- if(email != "" && password != "") {
- //auto session = sm->getSession(request);
- //if(!mSession) mSession = sm->findByEmail(email);
- if(!mSession) {
- mSession = sm->getNewSession();
- mSession->setLanguageCatalog(langCatalog);
- // get language
- // first check url, second check language header
- // for debugging client ip
- auto client_host = request.clientAddress().host();
- //auto client_ip = request.clientAddress();
- // X-Real-IP forwarded ip from nginx config
- auto client_host_string = request.get("X-Real-IP", client_host.toString());
- std::string clientIpString = "client ip: ";
- client_host = Poco::Net::IPAddress(client_host_string);
- clientIpString += client_host_string;
- Poco::Logger::get("requestLog").information(clientIpString);
- // debugging end
- mSession->setClientIp(client_host);
- response.addCookie(mSession->getLoginCookie());
- } else {
- langCatalog = mSession->getLanguageCatalog();
- }
- UserStates user_state;
- try {
- user_state = mSession->loadUser(email, password);
- } catch (Poco::Exception& ex) {
- addError(new ParamError("login", "exception by calling loadUser: ", ex.displayText()));
- sendErrorsAsEmail();
- addError(new Error("Error", "Intern Server error, please try again later"));
- }
- auto user = mSession->getNewUser();
-
- if(user_state >= USER_LOADED_FROM_DB && !user.isNull() && !user->getModel()->getPublicKey()) {
- if(mSession->generateKeys(true, true)) {
- user_state = USER_COMPLETE;
- if(user->getModel()->isDisabled()) {
- user_state = USER_DISABLED;
- }
- }
- } else {
- //printf("pubkey exist: %p\n",user->getModel()->getPublicKey());
- }
- getErrors(mSession);
-
- auto uri_start = request.serverParams().getServerName();
- auto lastExternReferer = mSession->getLastReferer();
-
- printf("user_state: %d\n", user_state);
-
- switch(user_state) {
- case USER_EMPTY:
- case USER_PASSWORD_INCORRECT:
- addError(new Error(langCatalog->gettext("Login"), langCatalog->gettext("E-Mail or password isn't right, please try again!")), false);
- if(mSession) {
- getErrors(mSession);
- sm->releaseSession(mSession);
- }
- sm->deleteLoginCookies(request, response);
- break;
- case USER_PASSWORD_ENCRYPTION_IN_PROCESS:
- addError(new Error(langCatalog->gettext("Passwort"), langCatalog->gettext("Passwort wird noch berechnet, bitte versuche es in etwa 1 Minute erneut.")), false);
- break;
- case USER_KEYS_DONT_MATCH:
- addError(new Error(langCatalog->gettext("User"), langCatalog->gettext("Error in saved data, the server admin will look at it.")));
- break;
- case USER_DISABLED:
- addError(new Error(langCatalog->gettext("User"), langCatalog->gettext("Benutzer ist deaktiviert, kein Login möglich!")));
- if(mSession) {
- getErrors(mSession);
- sm->releaseSession(mSession);
- }
- sm->deleteLoginCookies(request, response);
- break;
- case USER_NO_PRIVATE_KEY:
- case USER_COMPLETE:
- case USER_EMAIL_NOT_ACTIVATED:
- auto referer = request.find("Referer");
- std::string refererString;
- if (referer != request.end()) {
- refererString = referer->second;
- }
- if(lastExternReferer != "") {
- //printf("redirect to: %s\n", lastExternReferer.data());
- response.redirect(lastExternReferer);
- } else if(refererString != "" &&
- refererString.find("login") == std::string::npos &&
- refererString.find("logout") == std::string::npos &&
- refererString.find("user_delete") == std::string::npos &&
- refererString != ServerConfig::g_serverPath + request.getURI()) {
- std::string uri = request.getURI();
- printf("request uri: %s, redirect to: %s\n", uri.data(), refererString.data());
- response.redirect(refererString);
- } else {
- //printf("redirect to: %s\n", ServerConfig::g_php_serverPath.data());
- response.redirect(ServerConfig::g_php_serverPath + "/");
- }
- return;
- }
-
- } else if(!langUpdatedByBtn) {
- addError(new Error(langCatalog->gettext("Login"), langCatalog->gettext("Username and password are needed!")), false);
- }
-
- } else {
-
- // on enter login page with empty form
- //auto session = sm->getSession(request);
- // remove old cookies and session if exist
- if(mSession) {
- getErrors(mSession);
- sm->releaseSession(mSession);
- }
- sm->deleteLoginCookies(request, response);
- }
-
-%><%@ include file="header.cpsp" %>
-<%= getErrorsHtml() %>
-
-
-
-
+<%@ page class="LoginPage" %>
+<%@ page form="true" %>
+<%@ page baseClass="SessionHTTPRequestHandler" %>
+<%@ page ctorArg="Session*" %>
+<%@ header include="SessionHTTPRequestHandler.h" %>
+<%@ page compressed="true" %>
+<%!
+#include "../gettext.h"
+
+#include "Poco/Net/HTTPCookie.h"
+#include "Poco/Net/HTTPServerParams.h"
+#include "Poco/Logger.h"
+#include "../SingletonManager/SessionManager.h"
+#include "../SingletonManager/LanguageManager.h"
+#include "../SingletonManager/ErrorManager.h"
+
+%>
+<%%
+ const char* pageName = "Login";
+ auto sm = SessionManager::getInstance();
+ auto lm = LanguageManager::getInstance();
+ auto em = ErrorManager::getInstance();
+
+ auto lang = chooseLanguage(request);
+ //printf("choose language return: %d\n", lang);
+ auto langCatalog = lm->getFreeCatalog(lang);
+
+ std::string presetEmail("");
+ if(mSession && mSession->getUser()) {
+ presetEmail = mSession->getUser()->getEmail();
+ }
+
+ if(!form.empty()) {
+
+ bool langUpdatedByBtn = false;
+ auto langBtn = form.get("lang", "");
+ if(langBtn != "") {
+ langUpdatedByBtn = true;
+ }
+ /*
+ auto langInput = form.get("lang", "");
+ auto updatedLang = LANG_NULL;
+ if(langBtn != "") {
+ updatedLang = chooseLanguage(request, langBtn);
+ langUpdatedByBtn = true;
+ } else if(langInput != "") {
+ updatedLang = chooseLanguage(request, langInput);
+ }
+
+ if(updatedLang != LANG_NULL && updatedLang != lang) {
+ lang = updatedLang;
+ langCatalog = lm->getFreeCatalog(lang);
+ }
+ */
+ auto email = form.get("login-email", "");
+ auto password = form.get("login-password", "");
+
+ if(email != "" && password != "") {
+ //auto session = sm->getSession(request);
+ //if(!mSession) mSession = sm->findByEmail(email);
+ if(!mSession) {
+ mSession = sm->getNewSession();
+ mSession->setLanguageCatalog(langCatalog);
+ // get language
+ // first check url, second check language header
+ // for debugging client ip
+ auto client_host = request.clientAddress().host();
+ //auto client_ip = request.clientAddress();
+ // X-Real-IP forwarded ip from nginx config
+ auto client_host_string = request.get("X-Real-IP", client_host.toString());
+ std::string clientIpString = "client ip: ";
+ client_host = Poco::Net::IPAddress(client_host_string);
+ clientIpString += client_host_string;
+ Poco::Logger::get("requestLog").information(clientIpString);
+ // debugging end
+ mSession->setClientIp(client_host);
+ response.addCookie(mSession->getLoginCookie());
+ } else {
+ langCatalog = mSession->getLanguageCatalog();
+ }
+ UserStates user_state;
+ try {
+ user_state = mSession->loadUser(email, password);
+ } catch (Poco::Exception& ex) {
+ addError(new ParamError("login", "exception by calling loadUser: ", ex.displayText()));
+ sendErrorsAsEmail();
+ addError(new Error("Error", "Intern Server error, please try again later"));
+ }
+ auto user = mSession->getNewUser();
+
+ if(user_state >= USER_LOADED_FROM_DB && !user.isNull() && !user->getModel()->getPublicKey()) {
+ if(mSession->generateKeys(true, true)) {
+ user_state = USER_COMPLETE;
+ if(user->getModel()->isDisabled()) {
+ user_state = USER_DISABLED;
+ }
+ }
+ } else {
+ //printf("pubkey exist: %p\n",user->getModel()->getPublicKey());
+ }
+ getErrors(mSession);
+
+ auto uri_start = request.serverParams().getServerName();
+ auto lastExternReferer = mSession->getLastReferer();
+
+ printf("user_state: %d\n", user_state);
+
+ switch(user_state) {
+ case USER_EMPTY:
+ case USER_PASSWORD_INCORRECT:
+ addError(new Error(langCatalog->gettext("Login"), langCatalog->gettext("E-Mail or password isn't right, please try again!")), false);
+ if(mSession) {
+ getErrors(mSession);
+ sm->releaseSession(mSession);
+ }
+ sm->deleteLoginCookies(request, response);
+ break;
+ case USER_PASSWORD_ENCRYPTION_IN_PROCESS:
+ addError(new Error(langCatalog->gettext("Passwort"), langCatalog->gettext("Passwort wird noch berechnet, bitte versuche es in etwa 1 Minute erneut.")), false);
+ break;
+ case USER_KEYS_DONT_MATCH:
+ addError(new Error(langCatalog->gettext("User"), langCatalog->gettext("Error in saved data, the server admin will look at it.")));
+ break;
+ case USER_DISABLED:
+ addError(new Error(langCatalog->gettext("User"), langCatalog->gettext("Benutzer ist deaktiviert, kein Login möglich!")));
+ if(mSession) {
+ getErrors(mSession);
+ sm->releaseSession(mSession);
+ }
+ sm->deleteLoginCookies(request, response);
+ break;
+ case USER_NO_PRIVATE_KEY:
+ case USER_COMPLETE:
+ case USER_EMAIL_NOT_ACTIVATED:
+ auto referer = request.find("Referer");
+ std::string refererString;
+ if (referer != request.end()) {
+ refererString = referer->second;
+ }
+ if(lastExternReferer != "") {
+ //printf("redirect to: %s\n", lastExternReferer.data());
+ response.redirect(lastExternReferer);
+ } else if(refererString != "" &&
+ refererString.find("login") == std::string::npos &&
+ refererString.find("logout") == std::string::npos &&
+ refererString.find("user_delete") == std::string::npos &&
+ refererString != ServerConfig::g_serverPath + request.getURI()) {
+ std::string uri = request.getURI();
+ printf("request uri: %s, redirect to: %s\n", uri.data(), refererString.data());
+ response.redirect(refererString);
+ } else {
+ //printf("redirect to: %s\n", ServerConfig::g_php_serverPath.data());
+ response.redirect(ServerConfig::g_php_serverPath + "/");
+ }
+ return;
+ }
+
+ } else if(!langUpdatedByBtn) {
+ addError(new Error(langCatalog->gettext("Login"), langCatalog->gettext("Username and password are needed!")), false);
+ }
+
+ } else {
+
+ // on enter login page with empty form
+ //auto session = sm->getSession(request);
+ // remove old cookies and session if exist
+ if(mSession) {
+ getErrors(mSession);
+ sm->releaseSession(mSession);
+ }
+ sm->deleteLoginCookies(request, response);
+ }
+
+%><%@ include file="header.cpsp" %>
+<%= getErrorsHtml() %>
+
+
+
+
<%@ include file="footer.cpsp" %>
\ No newline at end of file
diff --git a/mariadb/Dockerfile b/mariadb/Dockerfile
index 526ebf250..d88257a8e 100644
--- a/mariadb/Dockerfile
+++ b/mariadb/Dockerfile
@@ -1,20 +1,20 @@
-#########################################################################################################
-# mariadb server
-#########################################################################################################
-From mariadb/server:10.5 as mariadb_server
-
-ENV DOCKER_WORKDIR="/docker-entrypoint-initdb.d"
-
-RUN mkdir -p ${DOCKER_WORKDIR}
-WORKDIR ${DOCKER_WORKDIR}
-
-# create databases
-COPY ./mariadb/setup_dbs.sql a_setup_dbs.sql
-# login server db
-COPY ./login_server/skeema/ .
-RUN cd ./gradido_login/ && for f in *.sql; do cp -- "$f" "../b_$f"; sed -i '1i use gradido_login;' "../b_$f"; done
-# community server db
-COPY ./community_server/skeema/ .
-RUN cd ./gradido_community/ && for f in *.sql; do cp -- "$f" "../d_$f"; sed -i '1i use gradido_community;' "../d_$f"; done
-RUN cd ./gradido_community/insert && for f in *.sql; do cp -- "$f" "../../e_$f"; sed -i '1i use gradido_community;' "../../e_$f"; done
-
+#########################################################################################################
+# mariadb server
+#########################################################################################################
+From mariadb/server:10.5 as mariadb_server
+
+ENV DOCKER_WORKDIR="/docker-entrypoint-initdb.d"
+
+RUN mkdir -p ${DOCKER_WORKDIR}
+WORKDIR ${DOCKER_WORKDIR}
+
+# create databases
+COPY ./mariadb/setup_dbs.sql a_setup_dbs.sql
+# login server db
+COPY ./login_server/skeema/ .
+RUN cd ./gradido_login/ && for f in *.sql; do cp -- "$f" "../b_$f"; sed -i '1i use gradido_login;' "../b_$f"; done
+# community server db
+COPY ./community_server/skeema/ .
+RUN cd ./gradido_community/ && for f in *.sql; do cp -- "$f" "../d_$f"; sed -i '1i use gradido_community;' "../d_$f"; done
+RUN cd ./gradido_community/insert && for f in *.sql; do cp -- "$f" "../../e_$f"; sed -i '1i use gradido_community;' "../../e_$f"; done
+
diff --git a/nginx/Dockerfile b/nginx/Dockerfile
index 763ce0ed6..879d3b2c8 100644
--- a/nginx/Dockerfile
+++ b/nginx/Dockerfile
@@ -1,10 +1,10 @@
-FROM nginx:latest
-
-WORKDIR /var/www/cakephp
-
-COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf
-COPY ./nginx/fastcgi.conf /etc/nginx/
-COPY ./nginx/mime.types /etc/nginx/
-
-COPY ./community_server/webroot webroot
-
+FROM nginx:latest
+
+WORKDIR /var/www/cakephp
+
+COPY ./nginx/nginx.conf /etc/nginx/conf.d/default.conf
+COPY ./nginx/fastcgi.conf /etc/nginx/
+COPY ./nginx/mime.types /etc/nginx/
+
+COPY ./community_server/webroot webroot
+
diff --git a/nginx/fastcgi.conf b/nginx/fastcgi.conf
index c2976fe91..238f7869f 100644
--- a/nginx/fastcgi.conf
+++ b/nginx/fastcgi.conf
@@ -1,25 +1,25 @@
-fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
-fastcgi_param QUERY_STRING $query_string;
-fastcgi_param REQUEST_METHOD $request_method;
-fastcgi_param CONTENT_TYPE $content_type;
-fastcgi_param CONTENT_LENGTH $content_length;
-
-fastcgi_param SCRIPT_NAME $fastcgi_script_name;
-fastcgi_param REQUEST_URI $request_uri;
-fastcgi_param DOCUMENT_URI $document_uri;
-fastcgi_param DOCUMENT_ROOT $document_root;
-fastcgi_param SERVER_PROTOCOL $server_protocol;
-fastcgi_param REQUEST_SCHEME $scheme;
-fastcgi_param HTTPS $https if_not_empty;
-
-fastcgi_param GATEWAY_INTERFACE CGI/1.1;
-fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
-
-fastcgi_param REMOTE_ADDR $remote_addr;
-fastcgi_param REMOTE_PORT $remote_port;
-fastcgi_param SERVER_ADDR $server_addr;
-fastcgi_param SERVER_PORT $server_port;
-fastcgi_param SERVER_NAME $server_name;
-
-# PHP only, required if PHP was built with --enable-force-cgi-redirect
+fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+fastcgi_param QUERY_STRING $query_string;
+fastcgi_param REQUEST_METHOD $request_method;
+fastcgi_param CONTENT_TYPE $content_type;
+fastcgi_param CONTENT_LENGTH $content_length;
+
+fastcgi_param SCRIPT_NAME $fastcgi_script_name;
+fastcgi_param REQUEST_URI $request_uri;
+fastcgi_param DOCUMENT_URI $document_uri;
+fastcgi_param DOCUMENT_ROOT $document_root;
+fastcgi_param SERVER_PROTOCOL $server_protocol;
+fastcgi_param REQUEST_SCHEME $scheme;
+fastcgi_param HTTPS $https if_not_empty;
+
+fastcgi_param GATEWAY_INTERFACE CGI/1.1;
+fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
+
+fastcgi_param REMOTE_ADDR $remote_addr;
+fastcgi_param REMOTE_PORT $remote_port;
+fastcgi_param SERVER_ADDR $server_addr;
+fastcgi_param SERVER_PORT $server_port;
+fastcgi_param SERVER_NAME $server_name;
+
+# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;
\ No newline at end of file
diff --git a/nginx/mime.types b/nginx/mime.types
index 84c644fc7..cd3d700ea 100644
--- a/nginx/mime.types
+++ b/nginx/mime.types
@@ -1,88 +1,88 @@
-types {
- text/html html htm shtml;
- text/css css;
- text/xml xml;
- image/gif gif;
- image/jpeg jpeg jpg;
- application/javascript js;
- application/atom+xml atom;
- application/rss+xml rss;
-
- text/mathml mml;
- text/plain txt;
- text/vnd.sun.j2me.app-descriptor jad;
- text/vnd.wap.wml wml;
- text/x-component htc;
-
- image/png png;
- image/tiff tif tiff;
- image/vnd.wap.wbmp wbmp;
- image/x-icon ico;
- image/x-jng jng;
- image/x-ms-bmp bmp;
- image/svg+xml svg svgz;
- image/webp webp;
-
- application/font-woff woff;
- application/java-archive jar war ear;
- application/json json;
- application/mac-binhex40 hqx;
- application/msword doc;
- application/pdf pdf;
- application/postscript ps eps ai;
- application/rtf rtf;
- application/vnd.apple.mpegurl m3u8;
- application/vnd.ms-excel xls;
- application/vnd.ms-fontobject eot;
- application/vnd.ms-powerpoint ppt;
- application/vnd.wap.wmlc wmlc;
- application/vnd.google-earth.kml+xml kml;
- application/vnd.google-earth.kmz kmz;
- application/x-7z-compressed 7z;
- application/x-cocoa cco;
- application/x-java-archive-diff jardiff;
- application/x-java-jnlp-file jnlp;
- application/x-makeself run;
- application/x-perl pl pm;
- application/x-pilot prc pdb;
- application/x-rar-compressed rar;
- application/x-redhat-package-manager rpm;
- application/x-sea sea;
- application/x-shockwave-flash swf;
- application/x-stuffit sit;
- application/x-tcl tcl tk;
- application/x-x509-ca-cert der pem crt;
- application/x-xpinstall xpi;
- application/xhtml+xml xhtml;
- application/xspf+xml xspf;
- application/zip zip;
-
- application/octet-stream bin exe dll;
- application/octet-stream deb;
- application/octet-stream dmg;
- application/octet-stream iso img;
- application/octet-stream msi msp msm;
-
- application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
- application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
- application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
-
- audio/midi mid midi kar;
- audio/mpeg mp3;
- audio/ogg ogg;
- audio/x-m4a m4a;
- audio/x-realaudio ra;
-
- video/3gpp 3gpp 3gp;
- video/mp2t ts;
- video/mp4 mp4;
- video/mpeg mpeg mpg;
- video/quicktime mov;
- video/webm webm;
- video/x-flv flv;
- video/x-m4v m4v;
- video/x-mng mng;
- video/x-ms-asf asx asf;
- video/x-ms-wmv wmv;
- video/x-msvideo avi;
-}
+types {
+ text/html html htm shtml;
+ text/css css;
+ text/xml xml;
+ image/gif gif;
+ image/jpeg jpeg jpg;
+ application/javascript js;
+ application/atom+xml atom;
+ application/rss+xml rss;
+
+ text/mathml mml;
+ text/plain txt;
+ text/vnd.sun.j2me.app-descriptor jad;
+ text/vnd.wap.wml wml;
+ text/x-component htc;
+
+ image/png png;
+ image/tiff tif tiff;
+ image/vnd.wap.wbmp wbmp;
+ image/x-icon ico;
+ image/x-jng jng;
+ image/x-ms-bmp bmp;
+ image/svg+xml svg svgz;
+ image/webp webp;
+
+ application/font-woff woff;
+ application/java-archive jar war ear;
+ application/json json;
+ application/mac-binhex40 hqx;
+ application/msword doc;
+ application/pdf pdf;
+ application/postscript ps eps ai;
+ application/rtf rtf;
+ application/vnd.apple.mpegurl m3u8;
+ application/vnd.ms-excel xls;
+ application/vnd.ms-fontobject eot;
+ application/vnd.ms-powerpoint ppt;
+ application/vnd.wap.wmlc wmlc;
+ application/vnd.google-earth.kml+xml kml;
+ application/vnd.google-earth.kmz kmz;
+ application/x-7z-compressed 7z;
+ application/x-cocoa cco;
+ application/x-java-archive-diff jardiff;
+ application/x-java-jnlp-file jnlp;
+ application/x-makeself run;
+ application/x-perl pl pm;
+ application/x-pilot prc pdb;
+ application/x-rar-compressed rar;
+ application/x-redhat-package-manager rpm;
+ application/x-sea sea;
+ application/x-shockwave-flash swf;
+ application/x-stuffit sit;
+ application/x-tcl tcl tk;
+ application/x-x509-ca-cert der pem crt;
+ application/x-xpinstall xpi;
+ application/xhtml+xml xhtml;
+ application/xspf+xml xspf;
+ application/zip zip;
+
+ application/octet-stream bin exe dll;
+ application/octet-stream deb;
+ application/octet-stream dmg;
+ application/octet-stream iso img;
+ application/octet-stream msi msp msm;
+
+ application/vnd.openxmlformats-officedocument.wordprocessingml.document docx;
+ application/vnd.openxmlformats-officedocument.spreadsheetml.sheet xlsx;
+ application/vnd.openxmlformats-officedocument.presentationml.presentation pptx;
+
+ audio/midi mid midi kar;
+ audio/mpeg mp3;
+ audio/ogg ogg;
+ audio/x-m4a m4a;
+ audio/x-realaudio ra;
+
+ video/3gpp 3gpp 3gp;
+ video/mp2t ts;
+ video/mp4 mp4;
+ video/mpeg mpeg mpg;
+ video/quicktime mov;
+ video/webm webm;
+ video/x-flv flv;
+ video/x-m4v m4v;
+ video/x-mng mng;
+ video/x-ms-asf asx asf;
+ video/x-ms-wmv wmv;
+ video/x-msvideo avi;
+}
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
index cdc418d05..55d35e264 100644
--- a/nginx/nginx.conf
+++ b/nginx/nginx.conf
@@ -1,92 +1,79 @@
-
-server {
-
- listen 80 ;
- listen [::]:80;
- server_name 0.0.0.0;
-
- #include /etc/nginx/common/protect.conf;
- #include /etc/nginx/common/protect_add_header.conf;
- #include /etc/nginx/common/ssl.conf;
-
-
- root /var/www/cakephp/webroot;
- index index.php;
-
- location ~ \.php$ {
- fastcgi_pass community-server:9000;
- fastcgi_index index.php;
- fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
- # fastcgi_param PHP_VALUE "error_log=/var/www/myapp/logs/php_errors.log";
- fastcgi_buffers 16 16k;
- fastcgi_buffer_size 32k;
- include fastcgi_params;
-
- }
-
- location ~ /\.ht {
- deny all;
- }
-
- location /account {
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection 'upgrade';
- proxy_cache_bypass $http_upgrade;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $remote_addr;
- proxy_set_header Host $host;
- rewrite /account/(.*) /$1 break;
-
- #proxy_next_upstream error timeout invalid_header http_502 non_idempotent;
- proxy_pass http://login-server:1200;
- proxy_redirect off;
-
-
- }
-
- location /login_api {
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection 'upgrade';
- proxy_cache_bypass $http_upgrade;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header X-Forwarded-For $remote_addr;
- proxy_set_header Host $host;
- rewrite /login_api/(.*) /$1 break;
-
- proxy_pass http://login-server:1201;
- proxy_redirect off;
- }
-
- location /vue {
-
-
- location /vue/sockjs-node {
- rewrite /vue/(.*) /$1;
- }
- location ~* \.(png) {
- expires 1d;
- rewrite /vue/(.*) /$1;
- }
-
-
- proxy_http_version 1.1;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection 'upgrade';
- proxy_set_header X-Forwarded-For $remote_addr;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header Host $host;
- #rewrite /vue/(.*) /$1 break;
-
- proxy_pass http://frontend:8080;
- proxy_redirect off;
- }
-
- location / {
- try_files $uri $uri/ /index.php?$args;
- }
-
-# access_log /var/log/nginx/access.log main;
-
+
+
+server {
+
+ listen 80 ;
+ listen [::]:80;
+ server_name 0.0.0.0;
+
+ #include /etc/nginx/common/protect.conf;
+ #include /etc/nginx/common/protect_add_header.conf;
+ #include /etc/nginx/common/ssl.conf;
+
+
+ root /var/www/cakephp/webroot;
+ index index.php;
+
+ location ~* \.(png|jpg|ico|webp)$ {
+ expires 30d;
+ }
+
+ location ~* \.(js|css) {
+ # expires 1d;
+ expires 1d;
+ }
+
+ location ~ \.php$ {
+ fastcgi_pass community-server:9000;
+ fastcgi_index index.php;
+ fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
+ # fastcgi_param PHP_VALUE "error_log=/var/www/myapp/logs/php_errors.log";
+ fastcgi_buffers 16 16k;
+ fastcgi_buffer_size 32k;
+ include fastcgi_params;
+
+ }
+
+ location ~ /\.ht {
+ deny all;
+ }
+
+ location /account {
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_cache_bypass $http_upgrade;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $remote_addr;
+ proxy_set_header Host $host;
+ rewrite /account/(.*) /$1 break;
+
+ #proxy_next_upstream error timeout invalid_header http_502 non_idempotent;
+ proxy_pass http://login-server:1200;
+ proxy_redirect off;
+
+
+ }
+
+ location /login_api {
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection 'upgrade';
+ proxy_cache_bypass $http_upgrade;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $remote_addr;
+ proxy_set_header Host $host;
+ rewrite /login_api/(.*) /$1 break;
+
+ proxy_pass http://login-server:1201;
+ proxy_redirect off;
+ }
+
+ location / {
+ try_files $uri $uri/ /index.php?$args;
+ }
+
+# access_log /var/log/nginx/access.log main;
+
+
}
\ No newline at end of file
diff --git a/php-fpm/Dockerfile b/php-fpm/Dockerfile
index 87a9a1456..d648fb564 100644
--- a/php-fpm/Dockerfile
+++ b/php-fpm/Dockerfile
@@ -1,9 +1,9 @@
-From phpdockerio/php74-fpm as php-fpm
-
-# Install selected extensions and other stuff
-RUN apt-get update \
- && apt-get -y --no-install-recommends install curl php7.4-curl php7.4-fpm php7.4-mbstring php7.4-intl php7.4-xml php7.4-pdo php7.4-mysql\
- && apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
-
-
+From phpdockerio/php74-fpm as php-fpm
+
+# Install selected extensions and other stuff
+RUN apt-get update \
+ && apt-get -y --no-install-recommends install curl php7.4-curl php7.4-fpm php7.4-mbstring php7.4-intl php7.4-xml php7.4-pdo php7.4-mysql\
+ && apt-get clean; rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
+
+
WORKDIR "/var/www/cakephp"
\ No newline at end of file