diff --git a/Cargo.lock b/Cargo.lock
index 61881398ee44d31b94580aef26bf877a8b22e407..14f438db6a9a22bccba7aca373539dca447603e3 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,5 +1,15 @@
 # This file is automatically @generated by Cargo.
 # It is not intended for manual editing.
+[[package]]
+name = "Inflector"
+version = "0.11.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
+dependencies = [
+ "lazy_static",
+ "regex",
+]
+
 [[package]]
 name = "actix-codec"
 version = "0.3.0"
@@ -31,8 +41,8 @@ dependencies = [
  "futures-util",
  "http",
  "log",
- "rustls",
- "tokio-rustls",
+ "rustls 0.18.1",
+ "tokio-rustls 0.14.1",
  "trust-dns-proto",
  "trust-dns-resolver",
  "webpki",
@@ -96,14 +106,14 @@ dependencies = [
  "mime",
  "percent-encoding",
  "pin-project 1.0.8",
- "rand",
+ "rand 0.7.3",
  "regex",
  "serde",
  "serde_json",
  "serde_urlencoded",
  "sha-1",
  "slab",
- "time",
+ "time 0.2.27",
 ]
 
 [[package]]
@@ -213,10 +223,10 @@ dependencies = [
  "actix-service",
  "actix-utils",
  "futures-util",
- "rustls",
- "tokio-rustls",
+ "rustls 0.18.1",
+ "tokio-rustls 0.14.1",
  "webpki",
- "webpki-roots",
+ "webpki-roots 0.20.0",
 ]
 
 [[package]]
@@ -269,12 +279,12 @@ dependencies = [
  "mime",
  "pin-project 1.0.8",
  "regex",
- "rustls",
+ "rustls 0.18.1",
  "serde",
  "serde_json",
  "serde_urlencoded",
  "socket2",
- "time",
+ "time 0.2.27",
  "tinyvec",
  "url",
 ]
@@ -301,12 +311,33 @@ dependencies = [
  "futures",
 ]
 
+[[package]]
+name = "addr2line"
+version = "0.15.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a"
+dependencies = [
+ "gimli",
+]
+
 [[package]]
 name = "adler"
 version = "1.0.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
 
+[[package]]
+name = "aes"
+version = "0.7.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
+dependencies = [
+ "cfg-if 1.0.0",
+ "cipher",
+ "cpufeatures",
+ "opaque-debug",
+]
+
 [[package]]
 name = "aho-corasick"
 version = "0.7.15"
@@ -362,19 +393,40 @@ dependencies = [
  "log",
  "mime",
  "percent-encoding",
- "rand",
- "rustls",
+ "rand 0.7.3",
+ "rustls 0.18.1",
  "serde",
  "serde_json",
  "serde_urlencoded",
 ]
 
+[[package]]
+name = "backtrace"
+version = "0.3.59"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744"
+dependencies = [
+ "addr2line",
+ "cc",
+ "cfg-if 1.0.0",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
+]
+
 [[package]]
 name = "base-x"
 version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a4521f3e3d031370679b3b140beb36dfe4801b09ac77e30c61941f97df3ef28b"
 
+[[package]]
+name = "base64"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
+
 [[package]]
 name = "base64"
 version = "0.12.3"
@@ -399,9 +451,26 @@ version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
 dependencies = [
+ "block-padding",
  "generic-array",
 ]
 
+[[package]]
+name = "block-modes"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e"
+dependencies = [
+ "block-padding",
+ "cipher",
+]
+
+[[package]]
+name = "block-padding"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d696c370c750c948ada61c69a0ee2cbbb9c50b1019ddb86d9317157a99c2cae"
+
 [[package]]
 name = "brotli-sys"
 version = "0.3.2"
@@ -422,6 +491,23 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "bson"
+version = "1.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de0aa578035b938855a710ba58d43cfb4d435f3619f99236fb35922a574d6cb1"
+dependencies = [
+ "base64 0.13.0",
+ "chrono",
+ "hex",
+ "lazy_static",
+ "linked-hash-map",
+ "rand 0.7.3",
+ "serde",
+ "serde_json",
+ "uuid",
+]
+
 [[package]]
 name = "buf-min"
 version = "0.4.0"
@@ -482,6 +568,28 @@ version = "1.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
 
+[[package]]
+name = "chrono"
+version = "0.4.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73"
+dependencies = [
+ "libc",
+ "num-integer",
+ "num-traits",
+ "time 0.1.43",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "cipher"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
+dependencies = [
+ "generic-array",
+]
+
 [[package]]
 name = "const_fn"
 version = "0.4.8"
@@ -501,7 +609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "03a5d7b21829bc7b4bf4754a978a241ae54ea55a40f92bb20216e54096f4b951"
 dependencies = [
  "percent-encoding",
- "time",
+ "time 0.2.27",
  "version_check 0.9.3",
 ]
 
@@ -520,6 +628,15 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "crc-any"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "073375684a58dece169afbdc9879a027f3698118ad3814938316c6002b7aa921"
+dependencies = [
+ "debug-helper",
+]
+
 [[package]]
 name = "crc32fast"
 version = "1.2.1"
@@ -529,6 +646,103 @@ dependencies = [
  "cfg-if 1.0.0",
 ]
 
+[[package]]
+name = "crypto-mac"
+version = "0.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1d1a86f49236c215f271d40892d5fc950490551400b02ef360692c29815c714"
+dependencies = [
+ "generic-array",
+ "subtle",
+]
+
+[[package]]
+name = "darling"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
+dependencies = [
+ "darling_core 0.10.2",
+ "darling_macro 0.10.2",
+]
+
+[[package]]
+name = "darling"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "757c0ded2af11d8e739c4daea1ac623dd1624b06c844cf3f5a39f1bdbd99bb12"
+dependencies = [
+ "darling_core 0.13.0",
+ "darling_macro 0.13.0",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.9.3",
+ "syn",
+]
+
+[[package]]
+name = "darling_core"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2c34d8efb62d0c2d7f60ece80f75e5c63c1588ba68032740494b0b9a996466e3"
+dependencies = [
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.10.0",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
+dependencies = [
+ "darling_core 0.10.2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "darling_macro"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade7bff147130fe5e6d39f089c6bd49ec0250f35d70b2eebf72afdfc919f15cc"
+dependencies = [
+ "darling_core 0.13.0",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "debug-helper"
+version = "0.3.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "76fbd10dce159c002b9c688ae8ab7cd531151e185e0ad360f4bfea3b0eede3a8"
+
+[[package]]
+name = "derivative"
+version = "2.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "derive_more"
 version = "0.99.16"
@@ -542,6 +756,17 @@ dependencies = [
  "syn",
 ]
 
+[[package]]
+name = "des"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac41dd49fb554432020d52c875fc290e110113f864c6b1b525cd62c7e7747a5d"
+dependencies = [
+ "byteorder",
+ "cipher",
+ "opaque-debug",
+]
+
 [[package]]
 name = "digest"
 version = "0.9.0"
@@ -603,6 +828,20 @@ dependencies = [
  "termcolor",
 ]
 
+[[package]]
+name = "err-derive"
+version = "0.2.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "22deed3a8124cff5fa835713fa105621e43bbdc46690c3a6b68328a012d350d4"
+dependencies = [
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "rustversion",
+ "syn",
+ "synstructure",
+]
+
 [[package]]
 name = "flate2"
 version = "1.0.22"
@@ -768,9 +1007,26 @@ checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
 dependencies = [
  "cfg-if 1.0.0",
  "libc",
- "wasi",
+ "wasi 0.9.0+wasi-snapshot-preview1",
+]
+
+[[package]]
+name = "getrandom"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
+dependencies = [
+ "cfg-if 1.0.0",
+ "libc",
+ "wasi 0.10.2+wasi-snapshot-preview1",
 ]
 
+[[package]]
+name = "gimli"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189"
+
 [[package]]
 name = "h2"
 version = "0.2.7"
@@ -815,6 +1071,22 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "hex"
+version = "0.4.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
+
+[[package]]
+name = "hmac"
+version = "0.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a2a2320eb7ec0ebe8da8f744d7812d9fc4cb4d09344ac01898dbcb6a20ae69b"
+dependencies = [
+ "crypto-mac",
+ "digest",
+]
+
 [[package]]
 name = "hostname"
 version = "0.3.1"
@@ -837,18 +1109,80 @@ dependencies = [
  "itoa",
 ]
 
+[[package]]
+name = "http-body"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13d5ff830006f7646652e057693569bfe0d51760c0085a071769d142a205111b"
+dependencies = [
+ "bytes 0.5.6",
+ "http",
+]
+
 [[package]]
 name = "httparse"
 version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503"
 
+[[package]]
+name = "httpdate"
+version = "0.3.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
+
 [[package]]
 name = "humantime"
 version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
+[[package]]
+name = "hyper"
+version = "0.13.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8a6f157065790a3ed2f88679250419b5cdd96e714a0d65f7797fd337186e96bb"
+dependencies = [
+ "bytes 0.5.6",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "h2",
+ "http",
+ "http-body",
+ "httparse",
+ "httpdate",
+ "itoa",
+ "pin-project 1.0.8",
+ "socket2",
+ "tokio",
+ "tower-service",
+ "tracing",
+ "want",
+]
+
+[[package]]
+name = "hyper-rustls"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37743cc83e8ee85eacfce90f2f4102030d9ff0a95244098d781e9bee4a90abb6"
+dependencies = [
+ "bytes 0.5.6",
+ "futures-util",
+ "hyper",
+ "log",
+ "rustls 0.18.1",
+ "tokio",
+ "tokio-rustls 0.14.1",
+ "webpki",
+]
+
+[[package]]
+name = "ident_case"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
+
 [[package]]
 name = "idna"
 version = "0.2.3"
@@ -897,9 +1231,15 @@ dependencies = [
  "socket2",
  "widestring",
  "winapi 0.3.9",
- "winreg",
+ "winreg 0.6.2",
 ]
 
+[[package]]
+name = "ipnet"
+version = "2.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
+
 [[package]]
 name = "itoa"
 version = "0.4.8"
@@ -932,9 +1272,17 @@ dependencies = [
  "actix-files",
  "actix-web",
  "actix-web-middleware-redirect-https",
+ "chrono",
  "dotenv",
  "env_logger",
- "rustls",
+ "futures",
+ "magic-crypt",
+ "rand 0.8.4",
+ "rustls 0.18.1",
+ "serde",
+ "serde_json",
+ "tokio",
+ "wither",
 ]
 
 [[package]]
@@ -988,6 +1336,23 @@ dependencies = [
  "linked-hash-map",
 ]
 
+[[package]]
+name = "magic-crypt"
+version = "3.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0f89f3c9a23ba052e4fc602770944c7ef16096ade1ca110a5c722efb16da7395"
+dependencies = [
+ "aes",
+ "base64 0.13.0",
+ "block-modes",
+ "crc-any",
+ "des",
+ "digest",
+ "md-5",
+ "sha2",
+ "tiger",
+]
+
 [[package]]
 name = "match_cfg"
 version = "0.1.0"
@@ -1000,6 +1365,17 @@ version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
 
+[[package]]
+name = "md-5"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b5a279bb9607f9f53c22d496eade00d138d1bdcccd07d74650387cf94942a15"
+dependencies = [
+ "block-buffer",
+ "digest",
+ "opaque-debug",
+]
+
 [[package]]
 name = "memchr"
 version = "2.3.4"
@@ -1045,12 +1421,24 @@ dependencies = [
  "kernel32-sys",
  "libc",
  "log",
- "miow",
+ "miow 0.2.2",
  "net2",
  "slab",
  "winapi 0.2.8",
 ]
 
+[[package]]
+name = "mio-named-pipes"
+version = "0.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656"
+dependencies = [
+ "log",
+ "mio",
+ "miow 0.3.7",
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "mio-uds"
 version = "0.6.8"
@@ -1075,12 +1463,66 @@ dependencies = [
 ]
 
 [[package]]
-name = "net2"
-version = "0.2.37"
+name = "miow"
+version = "0.3.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
+checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
 dependencies = [
- "cfg-if 0.1.10",
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "mongodb"
+version = "1.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd2a1cb6fd58c27e51ee650dca3b6924c4ce533dc0384f05b3219709ea1f1eb6"
+dependencies = [
+ "async-trait",
+ "base64 0.11.0",
+ "bitflags",
+ "bson",
+ "chrono",
+ "derivative",
+ "err-derive",
+ "futures",
+ "hex",
+ "hmac",
+ "lazy_static",
+ "md-5",
+ "os_info",
+ "pbkdf2",
+ "percent-encoding",
+ "rand 0.7.3",
+ "reqwest",
+ "rustls 0.17.0",
+ "serde",
+ "serde_bytes",
+ "serde_with",
+ "sha-1",
+ "sha2",
+ "socket2",
+ "stringprep",
+ "strsim 0.10.0",
+ "take_mut",
+ "time 0.1.43",
+ "tokio",
+ "tokio-rustls 0.13.1",
+ "trust-dns-proto",
+ "trust-dns-resolver",
+ "typed-builder",
+ "uuid",
+ "version_check 0.9.3",
+ "webpki",
+ "webpki-roots 0.21.1",
+]
+
+[[package]]
+name = "net2"
+version = "0.2.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "391630d12b68002ae1e25e8f974306474966550ad82dac6886fb8910c19568ae"
+dependencies = [
+ "cfg-if 0.1.10",
  "libc",
  "winapi 0.3.9",
 ]
@@ -1095,6 +1537,25 @@ dependencies = [
  "version_check 0.1.5",
 ]
 
+[[package]]
+name = "num-integer"
+version = "0.1.44"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
+dependencies = [
+ "autocfg",
+ "num-traits",
+]
+
+[[package]]
+name = "num-traits"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
+dependencies = [
+ "autocfg",
+]
+
 [[package]]
 name = "num_cpus"
 version = "1.13.0"
@@ -1105,6 +1566,12 @@ dependencies = [
  "libc",
 ]
 
+[[package]]
+name = "object"
+version = "0.24.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170"
+
 [[package]]
 name = "once_cell"
 version = "1.8.0"
@@ -1117,6 +1584,16 @@ version = "0.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
 
+[[package]]
+name = "os_info"
+version = "3.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac91020bfed8cc3f8aa450d4c3b5fa1d3373fc091c8a92009f3b27749d5a227"
+dependencies = [
+ "log",
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "parking_lot"
 version = "0.11.2"
@@ -1142,6 +1619,15 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "pbkdf2"
+version = "0.8.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d95f5254224e617595d2cc3cc73ff0a5eaf2637519e25f03388154e9378b6ffa"
+dependencies = [
+ "crypto-mac",
+]
+
 [[package]]
 name = "percent-encoding"
 version = "2.1.0"
@@ -1221,6 +1707,30 @@ version = "0.2.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
 
+[[package]]
+name = "proc-macro-error"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
+dependencies = [
+ "proc-macro-error-attr",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "version_check 0.9.3",
+]
+
+[[package]]
+name = "proc-macro-error-attr"
+version = "1.0.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "version_check 0.9.3",
+]
+
 [[package]]
 name = "proc-macro-hack"
 version = "0.5.19"
@@ -1263,11 +1773,24 @@ version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
 dependencies = [
- "getrandom",
+ "getrandom 0.1.16",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc 0.2.0",
+ "rand_pcg",
+]
+
+[[package]]
+name = "rand"
+version = "0.8.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+dependencies = [
  "libc",
- "rand_chacha",
- "rand_core",
- "rand_hc",
+ "rand_chacha 0.3.1",
+ "rand_core 0.6.3",
+ "rand_hc 0.3.1",
 ]
 
 [[package]]
@@ -1277,7 +1800,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
 dependencies = [
  "ppv-lite86",
- "rand_core",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
+dependencies = [
+ "ppv-lite86",
+ "rand_core 0.6.3",
 ]
 
 [[package]]
@@ -1286,7 +1819,16 @@ version = "0.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
 dependencies = [
- "getrandom",
+ "getrandom 0.1.16",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.6.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
+dependencies = [
+ "getrandom 0.2.3",
 ]
 
 [[package]]
@@ -1295,7 +1837,25 @@ version = "0.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
 dependencies = [
- "rand_core",
+ "rand_core 0.5.1",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
+dependencies = [
+ "rand_core 0.6.3",
+]
+
+[[package]]
+name = "rand_pcg"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429"
+dependencies = [
+ "rand_core 0.5.1",
 ]
 
 [[package]]
@@ -1324,6 +1884,43 @@ version = "0.6.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
 
+[[package]]
+name = "reqwest"
+version = "0.10.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0718f81a8e14c4dbb3b34cf23dc6aaf9ab8a0dfec160c534b3dbca1aaa21f47c"
+dependencies = [
+ "base64 0.13.0",
+ "bytes 0.5.6",
+ "encoding_rs",
+ "futures-core",
+ "futures-util",
+ "http",
+ "http-body",
+ "hyper",
+ "hyper-rustls",
+ "ipnet",
+ "js-sys",
+ "lazy_static",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "pin-project-lite 0.2.7",
+ "rustls 0.18.1",
+ "serde",
+ "serde_json",
+ "serde_urlencoded",
+ "tokio",
+ "tokio-rustls 0.14.1",
+ "url",
+ "wasm-bindgen",
+ "wasm-bindgen-futures",
+ "web-sys",
+ "webpki-roots 0.20.0",
+ "winreg 0.7.0",
+]
+
 [[package]]
 name = "resolv-conf"
 version = "0.7.0"
@@ -1349,6 +1946,12 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "rustc-demangle"
+version = "0.1.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
+
 [[package]]
 name = "rustc_version"
 version = "0.2.3"
@@ -1367,6 +1970,19 @@ dependencies = [
  "semver 0.11.0",
 ]
 
+[[package]]
+name = "rustls"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1"
+dependencies = [
+ "base64 0.11.0",
+ "log",
+ "ring",
+ "sct",
+ "webpki",
+]
+
 [[package]]
 name = "rustls"
 version = "0.18.1"
@@ -1380,6 +1996,12 @@ dependencies = [
  "webpki",
 ]
 
+[[package]]
+name = "rustversion"
+version = "1.0.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "61b3909d758bb75c79f23d4736fac9433868679d3ad2ea7a61e3c25cfda9a088"
+
 [[package]]
 name = "ryu"
 version = "1.0.5"
@@ -1444,6 +2066,15 @@ dependencies = [
  "serde_derive",
 ]
 
+[[package]]
+name = "serde_bytes"
+version = "0.11.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9"
+dependencies = [
+ "serde",
+]
+
 [[package]]
 name = "serde_derive"
 version = "1.0.130"
@@ -1461,6 +2092,7 @@ version = "1.0.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0f690853975602e1bfe1ccbf50504d67174e3bcf340f23b5ea9992e0587a52d8"
 dependencies = [
+ "indexmap",
  "itoa",
  "ryu",
  "serde",
@@ -1478,6 +2110,29 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_with"
+version = "1.11.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ad6056b4cb69b6e43e3a0f055def223380baecc99da683884f205bf347f7c4b3"
+dependencies = [
+ "rustversion",
+ "serde",
+ "serde_with_macros",
+]
+
+[[package]]
+name = "serde_with_macros"
+version = "1.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12e47be9471c72889ebafb5e14d5ff930d89ae7a67bbdb5f8abb564f845a927e"
+dependencies = [
+ "darling 0.13.0",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "sha-1"
 version = "0.9.8"
@@ -1497,6 +2152,19 @@ version = "0.6.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
 
+[[package]]
+name = "sha2"
+version = "0.9.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
+dependencies = [
+ "block-buffer",
+ "cfg-if 1.0.0",
+ "cpufeatures",
+ "digest",
+ "opaque-debug",
+]
+
 [[package]]
 name = "signal-hook-registry"
 version = "1.4.0"
@@ -1593,6 +2261,34 @@ version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0"
 
+[[package]]
+name = "stringprep"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ee348cb74b87454fff4b551cbf727025810a004f88aeacae7f85b87f4e9a1c1"
+dependencies = [
+ "unicode-bidi",
+ "unicode-normalization",
+]
+
+[[package]]
+name = "strsim"
+version = "0.9.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
+
+[[package]]
+name = "strsim"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
+
+[[package]]
+name = "subtle"
+version = "2.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601"
+
 [[package]]
 name = "syn"
 version = "1.0.76"
@@ -1604,6 +2300,24 @@ dependencies = [
  "unicode-xid",
 ]
 
+[[package]]
+name = "synstructure"
+version = "0.12.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unicode-xid",
+]
+
+[[package]]
+name = "take_mut"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60"
+
 [[package]]
 name = "termcolor"
 version = "1.1.2"
@@ -1642,6 +2356,27 @@ dependencies = [
  "num_cpus",
 ]
 
+[[package]]
+name = "tiger"
+version = "0.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "443e531cbcf9de83258cfef70bcd56c91188de5819ebd4b19c85f589e0617005"
+dependencies = [
+ "block-buffer",
+ "byteorder",
+ "digest",
+]
+
+[[package]]
+name = "time"
+version = "0.1.43"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
+dependencies = [
+ "libc",
+ "winapi 0.3.9",
+]
+
 [[package]]
 name = "time"
 version = "0.2.27"
@@ -1702,19 +2437,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092"
 dependencies = [
  "bytes 0.5.6",
+ "fnv",
  "futures-core",
  "iovec",
  "lazy_static",
  "libc",
  "memchr",
  "mio",
+ "mio-named-pipes",
  "mio-uds",
+ "num_cpus",
  "pin-project-lite 0.1.12",
  "signal-hook-registry",
  "slab",
+ "tokio-macros",
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "tokio-macros"
+version = "0.2.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "tokio-rustls"
+version = "0.13.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "15cb62a0d2770787abc96e99c1cd98fcf17f94959f3af63ca85bdfb203f051b4"
+dependencies = [
+ "futures-core",
+ "rustls 0.17.0",
+ "tokio",
+ "webpki",
+]
+
 [[package]]
 name = "tokio-rustls"
 version = "0.14.1"
@@ -1722,7 +2484,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "e12831b255bcfa39dc0436b01e19fea231a37db570686c06ee72c423479f889a"
 dependencies = [
  "futures-core",
- "rustls",
+ "rustls 0.18.1",
  "tokio",
  "webpki",
 ]
@@ -1741,6 +2503,12 @@ dependencies = [
  "tokio",
 ]
 
+[[package]]
+name = "tower-service"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
+
 [[package]]
 name = "tracing"
 version = "0.1.28"
@@ -1779,13 +2547,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1cad71a0c0d68ab9941d2fb6e82f8fb2e86d9945b94e1661dd0aaea2b88215a9"
 dependencies = [
  "async-trait",
+ "backtrace",
  "cfg-if 1.0.0",
  "enum-as-inner",
  "futures",
  "idna",
  "lazy_static",
  "log",
- "rand",
+ "rand 0.7.3",
  "smallvec",
  "thiserror",
  "tokio",
@@ -1811,6 +2580,23 @@ dependencies = [
  "trust-dns-proto",
 ]
 
+[[package]]
+name = "try-lock"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
+
+[[package]]
+name = "typed-builder"
+version = "0.4.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dfc955f27acc7a547f328f52f4a5a568986a31efec2fc6de865279f3995787b9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
 [[package]]
 name = "typenum"
 version = "1.14.0"
@@ -1877,6 +2663,15 @@ dependencies = [
  "percent-encoding",
 ]
 
+[[package]]
+name = "uuid"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
+dependencies = [
+ "getrandom 0.2.3",
+]
+
 [[package]]
 name = "v_escape"
 version = "0.15.0"
@@ -1921,12 +2716,28 @@ version = "0.9.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
 
+[[package]]
+name = "want"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
+dependencies = [
+ "log",
+ "try-lock",
+]
+
 [[package]]
 name = "wasi"
 version = "0.9.0+wasi-snapshot-preview1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
 
+[[package]]
+name = "wasi"
+version = "0.10.2+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
+
 [[package]]
 name = "wasm-bindgen"
 version = "0.2.78"
@@ -1934,6 +2745,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "632f73e236b219150ea279196e54e610f5dbafa5d61786303d4da54f84e47fce"
 dependencies = [
  "cfg-if 1.0.0",
+ "serde",
+ "serde_json",
  "wasm-bindgen-macro",
 ]
 
@@ -1952,6 +2765,18 @@ dependencies = [
  "wasm-bindgen-shared",
 ]
 
+[[package]]
+name = "wasm-bindgen-futures"
+version = "0.4.28"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e8d7523cb1f2a4c96c1317ca690031b714a51cc14e05f712446691f413f5d39"
+dependencies = [
+ "cfg-if 1.0.0",
+ "js-sys",
+ "wasm-bindgen",
+ "web-sys",
+]
+
 [[package]]
 name = "wasm-bindgen-macro"
 version = "0.2.78"
@@ -2010,6 +2835,15 @@ dependencies = [
  "webpki",
 ]
 
+[[package]]
+name = "webpki-roots"
+version = "0.21.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
+dependencies = [
+ "webpki",
+]
+
 [[package]]
 name = "widestring"
 version = "0.4.3"
@@ -2068,6 +2902,47 @@ dependencies = [
  "winapi 0.3.9",
 ]
 
+[[package]]
+name = "winreg"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
+dependencies = [
+ "winapi 0.3.9",
+]
+
+[[package]]
+name = "wither"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45e6fce5f641da433789cf4376cd698874666bf2741eb4d72444b4006cc0954a"
+dependencies = [
+ "async-trait",
+ "chrono",
+ "futures",
+ "log",
+ "mongodb",
+ "serde",
+ "thiserror",
+ "wither_derive",
+]
+
+[[package]]
+name = "wither_derive"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7cc57cffdfd2239926b5b9e068591944444c71c8e9baa2bd1cbde3492d78973"
+dependencies = [
+ "Inflector",
+ "async-trait",
+ "darling 0.10.2",
+ "proc-macro-error",
+ "proc-macro2",
+ "quote",
+ "serde",
+ "syn",
+]
+
 [[package]]
 name = "ws2_32-sys"
 version = "0.2.1"
diff --git a/Cargo.toml b/Cargo.toml
index 2f390d40cc86e14a3e61aab05d42e13997b5cbde..b8a874e5fe4b15c0bbb40d5ba89902411d1770a5 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -12,5 +12,13 @@ actix-web = { version = "3", features=["rustls"] }
 actix-web-middleware-redirect-https = "3.0.1"
 rustls="0.18.1"
 actix-files="0.5"
+futures = "0.3.17"
+serde = "1"
+serde_json="1"
+wither="0.9"
+magic-crypt="3"
 env_logger="0.9"
+chrono="0.4"
+rand="0.8"
 dotenv="0.15"
+tokio = { version = "0.2", features = ["full"] }
\ No newline at end of file
diff --git a/dev.docker-compose.yml b/dev.docker-compose.yml
index 0552a29e35b9facd676617c5dc451b7ae7433248..bc1bd9724d27d18a7bd42b25e01c36635c56fd0a 100644
--- a/dev.docker-compose.yml
+++ b/dev.docker-compose.yml
@@ -5,6 +5,8 @@ services:
             context: .
             dockerfile: ./dev.Dockerfile
         container_name: "kuadrado_server"
+        depends_on:
+            - ${DATABASE_NAME}
         restart: unless-stopped
         ports:
             - 80:${SERVER_PORT}
@@ -17,3 +19,17 @@ services:
         command: cargo run
         env_file:
             - ./.env
+    kuadradodb:
+        build: ./mongo/
+        container_name: ${DATABASE_NAME}
+        environment:
+            - MONGO_INITDB_DATABASE=${DATABASE_NAME}
+            - MONGO_INITDB_ROOT_USERNAME=${DB_ROOT_USERNAME}
+            - MONGO_INITDB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
+            - MONGO_INITDB_NON_ROOT_USERNAME=${DB_USERNAME}
+            - MONGO_INITDB_NON_ROOT_PASSWORD=${DB_USER_PASSWORD}
+        volumes:
+            - ./mongo/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
+            - /var/${DATABASE_NAME}-volume:/data/db
+        ports:
+            - "27017-27019:27017-27019"
diff --git a/docker-compose.yml b/docker-compose.yml
index d65e02efb0ff4d1c6443ec06b82e76caee85e484..35cef6e09d669b7b328b8afc4ee37f9b9d572f8f 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -5,6 +5,8 @@ services:
             context: .
             dockerfile: ./Dockerfile
         container_name: "kuadrado_server"
+        depends_on:
+            - ${DATABASE_NAME}
         restart: unless-stopped
         ports:
             - 80:${SERVER_PORT}
@@ -14,3 +16,17 @@ services:
             - /etc/letsencrypt/:${RESOURCES_DIR}/certs:ro
         env_file:
             - ./.env
+    kuadradodb:
+        build: ./mongo/
+        container_name: ${DATABASE_NAME}
+        environment:
+            - MONGO_INITDB_DATABASE=${DATABASE_NAME}
+            - MONGO_INITDB_ROOT_USERNAME=${DB_ROOT_USERNAME}
+            - MONGO_INITDB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
+            - MONGO_INITDB_NON_ROOT_USERNAME=${DB_USERNAME}
+            - MONGO_INITDB_NON_ROOT_PASSWORD=${DB_USER_PASSWORD}
+        volumes:
+            - ./mongo/init-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro
+            - /var/${DATABASE_NAME}-volume:/data/db
+        ports:
+            - "27017-27019:27017-27019"
diff --git a/mongo/Dockerfile b/mongo/Dockerfile
new file mode 100644
index 0000000000000000000000000000000000000000..1623104f669b51bcdbfda018f9ba40dcb39c1471
--- /dev/null
+++ b/mongo/Dockerfile
@@ -0,0 +1,2 @@
+from mongo
+RUN mkdir /mongoinit && chown mongodb -R /mongoinit && chgrp mongodb -R /mongoinit
\ No newline at end of file
diff --git a/mongo/init-mongo.js b/mongo/init-mongo.js
new file mode 100644
index 0000000000000000000000000000000000000000..52495ec801115591c325a1fb66688ec64ce99199
--- /dev/null
+++ b/mongo/init-mongo.js
@@ -0,0 +1,33 @@
+function getEnvVariable(envVar) {
+    // Thanks for the tip: https://dev.to/jsheridanwells/dockerizing-a-mongo-database-4jf2
+
+    const command = run("sh", "-c", `printenv --null ${envVar} >/mongoinit/${envVar}.txt`);
+    // note: 'printenv --null' prevents adding line break to value
+
+    if (command != 0) return Error("Failed to retrieve env variable : " + envVar);
+
+    // .replace(/\0/g, '') removes the NULL characters
+    return cat(`/mongoinit/${envVar}.txt`).replace(/\0/g, '');
+}
+
+db.createUser({
+    user: getEnvVariable("MONGO_INITDB_NON_ROOT_USERNAME"),
+    pwd: getEnvVariable("MONGO_INITDB_NON_ROOT_PASSWORD"),
+    roles: [
+        {
+            role: "readWrite",
+            db: getEnvVariable("MONGO_INITDB_DATABASE")
+        }
+    ]
+});
+
+db = new Mongo().getDB(getEnvVariable("MONGO_INITDB_DATABASE"));
+
+db.createCollection("articles");
+db.createCollection("administrators");
+
+db.administrators.createIndex({ "username": 1 }, { unique: true });
+db.administrators.createIndex({ "auth_token": 1 }, { unique: true });
+
+
+run("sh", "-c", "rm -rf /mongoinit/*");
\ No newline at end of file
diff --git a/public/views/404/404.html b/public/views/404/404.html
new file mode 100644
index 0000000000000000000000000000000000000000..435c37509f8eeec80b791649c27a33caebca3c5e
--- /dev/null
+++ b/public/views/404/404.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="utf-8">
+    <title>Page not found</title>
+</head>
+
+<body>
+    <h1>404 : Page not found</h1>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/public/views/admin-login/assets/login.js b/public/views/admin-login/assets/login.js
new file mode 100644
index 0000000000000000000000000000000000000000..aee791408d27aad0cb9475a11d6755cc8b12f9ab
--- /dev/null
+++ b/public/views/admin-login/assets/login.js
@@ -0,0 +1,16 @@
+const login_form = document.getElementById("login-form");
+login_form.onsubmit = e => {
+    e.preventDefault();
+    fetch("/admin-auth", {
+        method: "POST",
+        body: new URLSearchParams(new FormData(e.target))
+    }).then(res => {
+        if (res.status >= 400) {
+            alert(res.statusText)
+        } else {
+            window.location.assign("/v/admin-panel")
+        }
+    }).catch(err => {
+        alert(err.statusText || "Server error")
+    });
+}
\ No newline at end of file
diff --git a/public/views/admin-login/index.html b/public/views/admin-login/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..ce8dfbf824d5682b33b3e1f454f28dc55b6caad2
--- /dev/null
+++ b/public/views/admin-login/index.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="utf-8">
+    <title>Kuadrado admin login</title>
+</head>
+
+<body>
+    <h1>Admin login</h1>
+    <form id="login-form">
+        <label for="username"></label>
+        <input type="text" id="username" name="username">
+        <label for="password"></label>
+        <input type="password" id="password" name="password">
+        <input type="submit">
+    </form>
+</body>
+<script src="/v/admin-login/assets/login.js"></script>
+
+</html>
\ No newline at end of file
diff --git a/public/views/admin-panel/assets/script.js b/public/views/admin-panel/assets/script.js
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/public/views/admin-panel/index.html b/public/views/admin-panel/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..b9fc1fea3ed2f7165f1fc31c26001cda206df7fa
--- /dev/null
+++ b/public/views/admin-panel/index.html
@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="utf-8">
+    <title>Kuadrado admin panel</title>
+    <link rel="shortcut icon" type="image/x-icon" href="/v/admin-panel/assets/favicon.ico" />
+    <link rel="stylesheet" href="/v/admin-panel/assets/style.css">
+</head>
+
+<body>
+    <main>
+        Kuadrado admin panel TODO
+        FORM: post article
+        FORM: update/delete article
+    </main>
+</body>
+
+<script src="/v/admin-panel/assets/script.js"></script>
+
+</html>
\ No newline at end of file
diff --git a/public/views/test-view-auth/index.html b/public/views/test-view-auth/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..91c451a43d074893c7a157b6d2e13aea1c79fec2
--- /dev/null
+++ b/public/views/test-view-auth/index.html
@@ -0,0 +1 @@
+<h1>TEST AUTH</h1>
\ No newline at end of file
diff --git a/public/views/test-view/index.html b/public/views/test-view/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..6e4c7653e016d0d4cbb6b93fd020a37242de4202
--- /dev/null
+++ b/public/views/test-view/index.html
@@ -0,0 +1 @@
+<h1>TEST</h1>
\ No newline at end of file
diff --git a/public/views/unauthorized/unauthorized.html b/public/views/unauthorized/unauthorized.html
new file mode 100644
index 0000000000000000000000000000000000000000..af9827a5bc04bff7fb0c3cbb5a753a7ecfd59508
--- /dev/null
+++ b/public/views/unauthorized/unauthorized.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+
+<head>
+    <meta charset="utf-8">
+    <title>Unauthorized</title>
+</head>
+
+<body>
+    <h1>Unauthorized</h1>
+    <p>You must login as an administrator to access this page</p>
+    <a href='/v/admin-login'>Login page</a>
+</body>
+
+</html>
\ No newline at end of file
diff --git a/src/app_state.rs b/src/app_state.rs
new file mode 100644
index 0000000000000000000000000000000000000000..c1744971b663d64b95f1f77909f114b77b3b4f25
--- /dev/null
+++ b/src/app_state.rs
@@ -0,0 +1,66 @@
+use crate::{crypto::Encryption, env::Env, init_admin::create_default_admin_if_none};
+use wither::mongodb::{options::ClientOptions, Client, Database};
+
+#[derive(Debug, Clone)]
+/// The app_state that must be given to the actix::App instance using App::new().app_data(web::Data::new(AppState::new()))
+/// It holds the database client connection, an Env struct which provides all values defined in the .env file,
+/// and an Encryption struct which is reponsible for encrypting and decrypting data such as passwords and auth tokens.
+pub struct AppState {
+    pub db: Database,
+    pub env: Env,
+    pub encryption: Encryption,
+}
+
+impl AppState {
+    /// Creates the Mongodb database client connection
+    async fn get_db_connection(host: &str, env: &Env) -> Database {
+        let db_connection_string = format!(
+            "mongodb://{}:{}@{}:{}/{}",
+            env.db_username, env.db_user_pwd, host, env.db_port, env.db_name
+        );
+
+        let client_options = ClientOptions::parse(&db_connection_string)
+            .await
+            .expect("Error creating database client options");
+
+        let client = Client::with_options(client_options).expect("Couldn't connect to database.");
+        client.database(&env.db_name)
+    }
+
+    pub async fn new() -> Self {
+        let env = Env::new();
+        let db = Self::get_db_connection(&env.db_name, &env).await;
+
+        let encryption = Encryption::new(env.crypt_key.to_owned());
+
+        AppState {
+            db,
+            env,
+            encryption,
+        }
+    }
+
+    /// This calls Self::new() and creates a default administrator before returning the instance
+    pub async fn with_default_admin_user() -> Self {
+        let instance = Self::new().await;
+        if let Err(e) = create_default_admin_if_none(&instance).await {
+            panic!("Error creating admin user: {}\nWill exit process now.", e);
+        };
+        instance
+    }
+
+    #[cfg(test)]
+    /// Provides an instance with some specificities for testing
+    pub async fn for_test() -> Self {
+        let env = Env::for_test();
+        let db = Self::get_db_connection("localhost", &env).await;
+
+        let encryption = Encryption::new(env.crypt_key.to_owned());
+
+        AppState {
+            db,
+            env,
+            encryption,
+        }
+    }
+}
diff --git a/src/crypto.rs b/src/crypto.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fb02f6af89913bb0685ddd41222af196ebeeaadb
--- /dev/null
+++ b/src/crypto.rs
@@ -0,0 +1,91 @@
+use magic_crypt::{MagicCrypt, MagicCryptTrait, SecureBit};
+use rand::{distributions::Alphanumeric, Rng};
+
+#[derive(Debug, Clone)]
+/// A structure responsible of encrypting and decrypting data such as auth token, passwords and email addresses.
+pub struct Encryption {
+    /// The encryption key must be keeped secret and is loaded from the $CRYPT_KEY environment variable
+    pub key: String,
+}
+
+impl Encryption {
+    pub fn new(key: String) -> Self {
+        Encryption { key }
+    }
+
+    /// Gets a string as an argument and returns a base64 hash of the string based on the secret key and magic_crypt::SecureBit::Bit256 algorithm
+    pub fn encrypt(&self, source: &String) -> String {
+        let mc = MagicCrypt::new(&self.key, SecureBit::Bit256, None::<String>);
+        mc.encrypt_str_to_base64(source)
+    }
+
+    /// Gets a string base64 hash as an argument and returns the decryted string.
+    /// Panics if the source base64 string cannot be decrypted (should happen if trying to decrypt a regular string)
+    #[cfg(test)]
+    pub fn decrypt(&self, source: &String) -> String {
+        let mc = MagicCrypt::new(&self.key, SecureBit::Bit256, None::<String>);
+        mc.decrypt_base64_to_string(source).unwrap()
+    }
+
+    /// Generates a random ascii lowercase string. Length being given as argument.
+    pub fn random_ascii_lc_string(&self, length: usize) -> String {
+        // Thanks to https://stackoverflow.com/questions/54275459/how-do-i-create-a-random-string-by-sampling-from-alphanumeric-characters#54277357
+        rand::thread_rng()
+            .sample_iter(&Alphanumeric)
+            .take(length)
+            .map(char::from)
+            .collect::<String>()
+            .to_ascii_lowercase()
+    }
+}
+
+/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
+ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
+ *  _______   ______    ______   _______   *@@
+ * |__   __@ |  ____@  /  ____@ |__   __@  *@@
+ *    |  @   |  @__    \_ @_       |  @    *@@
+ *    |  @   |   __@     \  @_     |  @    *@@
+ *    |  @   |  @___   ____\  @    |  @    *@@
+ *    |__@   |______@  \______@    |__@    *@@
+ *                                         *@@
+ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
+ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@*/
+
+#[cfg(test)]
+mod test_encryption {
+    use super::*;
+
+    #[test]
+    fn test_random_ascii_lc_string() {
+        dotenv::dotenv().ok();
+        let key = std::env::var("CRYPT_KEY").unwrap();
+        let enc = Encryption::new(key);
+        let rdm_str = enc.random_ascii_lc_string(32);
+        assert_eq!(rdm_str.len(), 32);
+        assert!(rdm_str.chars().all(char::is_alphanumeric));
+        assert_eq!(rdm_str, rdm_str.to_lowercase());
+    }
+
+    #[test]
+    fn test_encrypt() {
+        dotenv::dotenv().ok();
+        let key = std::env::var("CRYPT_KEY").unwrap();
+        let enc = Encryption::new(key);
+
+        let an_email = String::from("kuadrado-email@test.com");
+        let email_hash = enc.encrypt(&an_email);
+        assert_ne!(an_email, email_hash);
+    }
+
+    #[test]
+    fn test_decrypt() {
+        dotenv::dotenv().ok();
+        let key = std::env::var("CRYPT_KEY").unwrap();
+        let enc = Encryption::new(key);
+
+        let an_email = String::from("kuadrado-email@test.com");
+        let email_hash = enc.encrypt(&an_email);
+        let decrypted = enc.decrypt(&email_hash);
+        assert_eq!(an_email, decrypted);
+    }
+}
diff --git a/src/env.rs b/src/env.rs
index e1ab149816238b5dec64c2015e023d341fbd31f2..cf5860ba3b3edac6a6ce52757cd4b1000b0c336a 100644
--- a/src/env.rs
+++ b/src/env.rs
@@ -1,5 +1,19 @@
 use std::env;
 
+#[derive(Debug, Clone)]
+/// Makes a copy of all required values defined in the system environment variables
+pub struct Env {
+    pub release_mode: String,
+    pub db_username: String,
+    pub db_user_pwd: String,
+    pub db_name: String,
+    pub db_port: String,
+    pub server_host: String,
+    pub crypt_key: String,
+    pub default_admin_username: String,
+    pub default_admin_password: String,
+}
+
 static RELEASE_MODES: [&str; 3] = ["debug", "test", "prod"];
 
 pub fn get_release_mode() -> String {
@@ -25,3 +39,39 @@ pub fn get_log_level() -> String {
         _ => String::from("info"),
     }
 }
+
+impl Env {
+    pub fn new() -> Env {
+        Env {
+            release_mode: get_release_mode(),
+            db_username: env::var("DB_USERNAME").expect("DB_USERNAME is not defined."),
+            db_user_pwd: env::var("DB_USER_PASSWORD").expect("DB_USER_PASSWORD is not defined."),
+            db_name: env::var("DATABASE_NAME").expect("DATABASE_NAME is not defined."),
+            db_port: env::var("DB_PORT").expect("DB_PORT is not defined."),
+            server_host: env::var("SERVER_HOST").expect("SERVER_HOST is not defined"),
+            crypt_key: env::var("CRYPT_KEY").expect("CRYPT_KEY is not defined."),
+            default_admin_username: env::var("DEFAULT_ADMIN_USERNAME")
+                .expect("DEFAULT_ADMIN_USERNAME is not defined"),
+            default_admin_password: env::var("DEFAULT_ADMIN_PASSWORD")
+                .expect("DEFAULT_ADMIN_PASSWORD is not defined"),
+        }
+    }
+
+    #[cfg(test)]
+    /// Returns an instance with some values adjusted for testing such as email addresses
+    pub fn for_test() -> Env {
+        Env {
+            release_mode: String::from("debug"),
+            db_username: env::var("DB_USERNAME").expect("DB_USERNAME is not defined."),
+            db_user_pwd: env::var("DB_USER_PASSWORD").expect("DB_USER_PASSWORD is not defined."),
+            db_name: env::var("DATABASE_NAME").expect("DATABASE_NAME is not defined."),
+            db_port: env::var("DB_PORT").expect("DB_PORT is not defined."),
+            server_host: env::var("SERVER_HOST").expect("SERVER_HOST is not defined"),
+            crypt_key: env::var("CRYPT_KEY").expect("CRYPT_KEY is not defined."),
+            default_admin_username: env::var("DEFAULT_ADMIN_USERNAME")
+                .expect("DEFAULT_ADMIN_USERNAME is not defined"),
+            default_admin_password: env::var("DEFAULT_ADMIN_PASSWORD")
+                .expect("DEFAULT_ADMIN_PASSWORD is not defined"),
+        }
+    }
+}
diff --git a/src/init_admin.rs b/src/init_admin.rs
new file mode 100644
index 0000000000000000000000000000000000000000..dbe1f2906689770d962d15b74a2ea8c4a6cbc927
--- /dev/null
+++ b/src/init_admin.rs
@@ -0,0 +1,35 @@
+use crate::model::Administrator;
+use crate::AppState;
+use wither::{bson::doc, prelude::Model};
+
+/// Creates the default administrator if it doesn't already exists and returns a Result.
+pub async fn create_default_admin_if_none(app_state: &AppState) -> Result<(), String> {
+    let admin_username = app_state.env.default_admin_username.to_owned();
+    let admin_password = app_state.env.default_admin_password.to_owned();
+
+    let admin = Administrator::from_values(app_state, admin_username, admin_password);
+
+    let admin_doc = doc! {
+        "username": &admin.username,
+        "password_hash": &admin.password_hash
+    };
+
+    match Administrator::find_one(&app_state.db, admin_doc, None).await {
+        Ok(found_user) => match found_user {
+            Some(_) => Ok(()),
+            None => {
+                println!("Kuadrado admin will be created");
+                match app_state
+                    .db
+                    .collection_with_type::<Administrator>("administrators")
+                    .insert_one(admin, None)
+                    .await
+                {
+                    Ok(_) => Ok(()),
+                    Err(e) => Err(format!("Error creating administrator: {:?}", e)),
+                }
+            }
+        },
+        Err(e) => Err(format!("Error creating administrator: {:?}", e)),
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index fa51ed907681b9ee70647924e5f625a97ba26284..4b3b905d78a291a9c426ad782798a2c722aa3046 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,19 +1,32 @@
-//! # REST API server for the Mentalo application
+//! # WEB SERVER FOR THE KUADRADO SOFTWARE WEBSITE
+mod app_state;
+mod crypto;
 mod env;
+mod init_admin;
+mod middleware;
+mod model;
+mod service;
 mod standard_static_files;
 mod tls;
+mod view;
+mod view_resource;
 use actix_files::Files;
 use actix_web::{
-    middleware::Logger,
-    web::{get, resource, to},
+    middleware::{normalize::TrailingSlash, Logger, NormalizePath},
+    web::{get, resource, scope, to, Data},
     App, HttpResponse, HttpServer,
 };
 use actix_web_middleware_redirect_https::RedirectHTTPS;
+use app_state::AppState;
 use env::get_log_level;
 use env_logger::Env;
+use middleware::AuthenticatedAdminMiddleware;
+use service::admin_auth::admin_auth;
 use standard_static_files::{favicon, robots, sitemap};
 use std::env::var as env_var;
 use tls::get_tls_config;
+use view::get_view;
+use view_resource::{ViewResourceDescriptor, ViewResourceManager};
 
 #[actix_web::main]
 async fn main() -> std::io::Result<()> {
@@ -25,6 +38,8 @@ async fn main() -> std::io::Result<()> {
         std::path::PathBuf::from(env_var("RESOURCES_DIR").expect("RESOURCES_DIR is not defined"))
             .join("public");
 
+    let app_state = AppState::with_default_admin_user().await;
+
     HttpServer::new(move || {
         App::new()
             .wrap(Logger::default())
@@ -33,7 +48,38 @@ async fn main() -> std::io::Result<()> {
                 format!(":{}", server_port),
                 format!(":{}", server_port_tls),
             )]))
-            // .wrap(NormalizePath::new(TrailingSlash::Trim))
+            .app_data(Data::new(app_state.clone()))
+            .app_data(Data::new(AuthenticatedAdminMiddleware::new(
+                "kuadrado-admin-auth",
+            )))
+            .app_data(Data::new(ViewResourceManager::with_views(vec![
+                ViewResourceDescriptor {
+                    path_str: "admin-panel",
+                    index_file_name: "index.html",
+                    resource_name: "admin-panel",
+                    apply_auth_middleware: true,
+                },
+                ViewResourceDescriptor {
+                    path_str: "admin-login",
+                    index_file_name: "index.html",
+                    resource_name: "admin-login",
+                    apply_auth_middleware: false,
+                },
+                ViewResourceDescriptor {
+                    path_str: "404",
+                    index_file_name: "404.html",
+                    resource_name: "404",
+                    apply_auth_middleware: false,
+                },
+                ViewResourceDescriptor {
+                    path_str: "unauthorized",
+                    index_file_name: "unauthorized.html",
+                    resource_name: "unauthorized",
+                    apply_auth_middleware: false,
+                },
+            ])))
+            .wrap(NormalizePath::new(TrailingSlash::Trim))
+            .service(admin_auth)
             // Allow json payload to have size until ~32MB
             // .app_data(JsonConfig::default().limit(1 << 25u8))
             /////////////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -42,6 +88,21 @@ async fn main() -> std::io::Result<()> {
             .service(resource("/robots.txt").route(get().to(robots)))
             .service(resource("/sitemap.xml").route(get().to(sitemap)))
             /////////////////////////////////////////////////////////////////////////////////////////////////////////////
+            // VIEWS ////////////////////////////////////////////////////////////////////////////////////////////////////
+            .service(
+                scope("/v")
+                    .service(Files::new(
+                        "/admin-panel/assets",
+                        public_dir.join("views/admin-panel/assets"),
+                    ))
+                    .service(Files::new(
+                        "/admin-login/assets",
+                        public_dir.join("views/admin-login/assets"),
+                    ))
+                    // get_view will match any url to we put it at last
+                    .service(get_view),
+            )
+            /////////////////////////////////////////////////////////////////////////////////////////////////////////////
             // PUBLIC WEBSITE //////////////////////////////////////////////////////////////////////////////////////////////
             .service(Files::new("/", &public_dir).index_file("index.html"))
             /////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/src/middleware.rs b/src/middleware.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a239355e41c5c75adadd7f8a4fc89aed3c2789b3
--- /dev/null
+++ b/src/middleware.rs
@@ -0,0 +1,120 @@
+use crate::{
+    model::{AdminAuthCredentials, Administrator},
+    AppState,
+};
+use actix_web::{http::Cookie, web::Form, HttpMessage, HttpRequest};
+use wither::{bson::doc, prelude::Model};
+
+/// Returns a actix_web::http::Cookie instance designed to be an authentication cookie.
+pub fn get_auth_cookie(name: &'static str, value: String) -> Cookie<'static> {
+    Cookie::build(name, value)
+        .secure(true)
+        .http_only(true)
+        .path("/")
+        .finish()
+}
+
+/// This is not a real middleware as it is meant to be executed only after having processed the request and not before.
+/// It must be registered in the actix App instance with app_data.
+/// ```
+/// App::new()
+///     .app_data(Data::new(AuthenticatedAdminMiddleware::new("some-auth-cookie-name")))
+/// ```
+/// If a service need to perform an authentication before doing anything, this "pseudo-middleware" should be run before anything else in the function.
+/// Example:
+/// ```
+/// #[post("/some-url")]
+/// pub async fn some_service(
+///     app_state: Data<AppState>,
+///     middleware: Data<AuthenticatedAdminMiddleware<'_>>,
+///     req: HttpRequest,
+/// ) -> impl Responder {
+///     if middleware.exec(&app_state, &req, None).await.is_err() {
+///         return HttpResponse::Unauthorized().finish();
+///     }
+///     ... Authenticated action ....
+/// }
+/// ```
+#[derive(Debug, Clone)]
+pub struct AuthenticatedAdminMiddleware<'a> {
+    /// The name of the authentication cookie
+    pub cookie_name: &'a str,
+}
+
+impl<'a> AuthenticatedAdminMiddleware<'a> {
+    pub fn new(cookie_name: &'a str) -> Self {
+        AuthenticatedAdminMiddleware { cookie_name }
+    }
+
+    /// Performs Administrator authentication from form data with username and password
+    /// Returns an authentication Cookie instance if the authentication succeeds, or an error.
+    async fn try_auth_from_form_data(
+        &self,
+        app_state: &AppState,
+        form_data: Form<AdminAuthCredentials>,
+    ) -> Result<Cookie<'static>, ()> {
+        match Administrator::authenticated(app_state, form_data.into_inner()).await {
+            Ok(ref mut admin) => {
+                let auth_token = app_state.encryption.random_ascii_lc_string(256);
+                admin.auth_token = Some(app_state.encryption.encrypt(&auth_token));
+
+                if admin
+                    .save(&app_state.db, Some(doc!("_id": admin.id().unwrap())))
+                    .await
+                    .is_err()
+                {
+                    println!("Failed to update admin auth_token");
+                    return Err(());
+                }
+
+                let cookie = get_auth_cookie("kuadrado-admin-auth", auth_token.to_owned());
+
+                return Ok(cookie);
+            }
+            Err(_) => return Err(()),
+        }
+    }
+
+    /// Performs Administrator authentication from the authentication cookie value
+    async fn try_auth_from_auth_cookie(
+        &self,
+        app_state: &AppState,
+        cookie: &Cookie<'static>,
+    ) -> Result<Cookie<'static>, ()> {
+        match Administrator::authenticated_with_cookie(app_state, &cookie).await {
+            Ok(_) => return Ok(cookie.clone()),
+            Err(_) => return Err(()),
+        }
+    }
+
+    /// The function that must be called in order to execute the verification.
+    /// Example :
+    /// ```
+    /// #[post("/some-url")]
+    /// pub async fn some_service(
+    ///     app_state: Data<AppState>,
+    ///     middleware: Data<AuthenticatedAdminMiddleware<'_>>,
+    ///     req: HttpRequest,
+    /// ) -> impl Responder {
+    ///     if middleware.exec(&app_state, &req, None).await.is_err() {
+    ///         return HttpResponse::Unauthorized().finish();
+    ///     }
+    ///     ... Authenticated actions ....
+    /// }
+    /// ```
+    pub async fn exec(
+        &self,
+        app_state: &AppState,
+        req: &HttpRequest,
+        auth_form_data: Option<Form<AdminAuthCredentials>>,
+    ) -> Result<Cookie<'static>, ()> {
+        let auth_cookie = req.cookie(self.cookie_name);
+        if let Some(form_data) = auth_form_data {
+            return self.try_auth_from_form_data(app_state, form_data).await;
+        } else if let Some(cookie) = auth_cookie {
+            return self.try_auth_from_auth_cookie(app_state, &cookie).await;
+        } else {
+            return Err(());
+        }
+    }
+}
diff --git a/src/model.rs b/src/model.rs
new file mode 100644
index 0000000000000000000000000000000000000000..b1c6c6fc9a9c137ad3577d3e30d87c7de36ca1be
--- /dev/null
+++ b/src/model.rs
@@ -0,0 +1,2 @@
+mod administrator;
+pub use administrator::*;
diff --git a/src/model/administrator.rs b/src/model/administrator.rs
new file mode 100644
index 0000000000000000000000000000000000000000..852881ee77b737185433d500a7b1b8229076914a
--- /dev/null
+++ b/src/model/administrator.rs
@@ -0,0 +1,83 @@
+use crate::AppState;
+use actix_web::http::Cookie;
+use serde::{Deserialize, Serialize};
+use wither::{
+    bson::{doc, oid::ObjectId},
+    prelude::Model,
+};
+
+#[derive(Debug, Serialize, Deserialize)]
+/// The data type that must sent by form data POST to authenticate an administrator.
+pub struct AdminAuthCredentials {
+    pub username: String,
+    pub password: String,
+}
+
+#[derive(Debug, Deserialize, Serialize, Model)]
+#[model(index(
+    keys = r#"doc!{"email": 1, "username": 1}"#,
+    options = r#"doc!{"unique": true}"#
+))]
+/// An administrator is a user with registered authentication credentials access right to the admin-panel and has ability to perform admin actions such as gam review, moderation, etc.
+pub struct Administrator {
+    #[serde(rename = "_id", skip_serializing_if = "Option::is_none")]
+    pub id: Option<ObjectId>,
+    pub username: String,
+    pub password_hash: String,
+    pub auth_token: Option<String>,
+}
+
+impl Administrator {
+    /// Creates an administrator with values for username and password.
+    /// The auth_token fields remains None as it must be created if the user authenticates itself with the provided credentials
+    /// The password is stored as password_hash, it is encrypted with the AppState::Encryption.
+    pub fn from_values(app_state: &AppState, username: String, password: String) -> Self {
+        Administrator {
+            id: None,
+            password_hash: app_state.encryption.encrypt(&password),
+            username,
+            auth_token: None,
+        }
+    }
+
+    /// Performs authentication with form data <username, password>.
+    /// Returns a Result with either an authenticated Administrator instance or an error.
+    pub async fn authenticated(
+        app_state: &AppState,
+        credentials: AdminAuthCredentials,
+    ) -> Result<Self, ()> {
+        let filter_doc = doc! {
+            "password_hash": app_state.encryption.encrypt(&credentials.password),
+            "username": credentials.username
+        };
+
+        match Administrator::find_one(&app_state.db, filter_doc, None).await {
+            Ok(user_option) => match user_option {
+                Some(admin) => Ok(admin),
+                None => Err(()),
+            },
+            Err(_) => Err(()),
+        }
+    }
+
+    /// Performs authenticattion with auth cookie. The cookie value must match the Administrator auth_token value.
+    /// Returns a result with either the authenticated admin, or an empty Err.
+    pub async fn authenticated_with_cookie(
+        app_state: &AppState,
+        auth_cookie: &Cookie<'_>,
+    ) -> Result<Self, ()> {
+        let cookie_value = auth_cookie.value().to_string();
+
+        let filter_doc = doc! {
+            "auth_token": app_state.encryption.encrypt(&cookie_value),
+        };
+
+        match Administrator::find_one(&app_state.db, filter_doc, None).await {
+            Ok(user_option) => match user_option {
+                Some(admin) => Ok(admin),
+                None => Err(()),
+            },
+            Err(_) => Err(()),
+        }
+    }
+}
diff --git a/src/service.rs b/src/service.rs
new file mode 100644
index 0000000000000000000000000000000000000000..ee031a2bfed8a87c699691c425cf5c996db73240
--- /dev/null
+++ b/src/service.rs
@@ -0,0 +1 @@
+pub mod admin_auth;
diff --git a/src/service/admin_auth.rs b/src/service/admin_auth.rs
new file mode 100644
index 0000000000000000000000000000000000000000..989c22fe9a2af59a6cddad38789e092b31ba8019
--- /dev/null
+++ b/src/service/admin_auth.rs
@@ -0,0 +1,122 @@
+use crate::{middleware::AuthenticatedAdminMiddleware, model::AdminAuthCredentials, AppState};
+use actix_web::{
+    post,
+    web::{Data, Form},
+    HttpMessage, HttpRequest, HttpResponse, Responder,
+};
+
+/// Performs administrator authentication from form data
+/// If the authentication succeed, a cookie with an auth token is returned
+/// If not, 401 is returned and if an auth cookie is found it is deleted.
+#[post("/admin-auth")]
+pub async fn admin_auth<'a>(
+    app_state: Data<AppState>,
+    auth_mw: Data<AuthenticatedAdminMiddleware<'a>>,
+    req: HttpRequest,
+    form_data: Form<AdminAuthCredentials>,
+) -> impl Responder {
+    let cookie_opt = auth_mw.exec(&app_state, &req, Some(form_data)).await;
+    match cookie_opt {
+        Ok(cookie) => HttpResponse::Accepted().cookie(cookie).finish(),
+        Err(_) => {
+            return match req.cookie(auth_mw.cookie_name) {
+                Some(c) => {
+                    // Invalidate auth_cookie if auth failed in any way
+                    HttpResponse::Unauthorized().del_cookie(&c).finish()
+                }
+                None => HttpResponse::Unauthorized().finish(),
+            };
+        }
+    }
+}
+
+/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
+ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
+ *  _______   ______    ______   _______   *@@
+ * |__   __@ |  ____@  /  ____@ |__   __@  *@@
+ *    |  @   |  @__    \_ @_       |  @    *@@
+ *    |  @   |   __@     \  @_     |  @    *@@
+ *    |  @   |  @___   ____\  @    |  @    *@@
+ *    |__@   |______@  \______@    |__@    *@@
+ *                                         *@@
+ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
+ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@*/
+
+#[cfg(test)]
+mod test_admin_auth {
+    use super::*;
+    use crate::model::Administrator;
+    use actix_web::{
+        http::{Method, StatusCode},
+        test,
+        web::Data,
+        App,
+    };
+    use futures::stream::StreamExt;
+    use wither::prelude::Model;
+
+    #[tokio::test]
+    async fn test_admin_auth() {
+        dotenv::dotenv().ok();
+
+        let app_state = AppState::for_test().await;
+        let admin_user = Administrator::find(&app_state.db, None, None)
+            .await
+            .unwrap()
+            .next()
+            .await
+            .unwrap()
+            .unwrap(); // Get the first admin user we find. At least one should exist.
+
+        let password = app_state.encryption.decrypt(&admin_user.password_hash);
+        let username = admin_user.username.to_owned();
+
+        let mut app = test::init_service(
+            App::new()
+                .app_data(Data::new(app_state.clone()))
+                .app_data(Data::new(AuthenticatedAdminMiddleware::new(
+                    "kuadrado-admin-auth",
+                )))
+                .service(admin_auth),
+        )
+        .await;
+
+        let req = test::TestRequest::with_uri("/admin-auth")
+            .method(Method::POST)
+            .set_form(&AdminAuthCredentials { username, password })
+            .to_request();
+
+        let resp = test::call_service(&mut app, req).await;
+
+        assert_eq!(resp.status(), StatusCode::ACCEPTED);
+    }
+
+    #[tokio::test]
+    async fn test_admin_auth_unauthorized() {
+        dotenv::dotenv().ok();
+
+        let app_state = AppState::for_test().await;
+
+        let mut app = test::init_service(
+            App::new()
+                .app_data(Data::new(app_state.clone()))
+                .app_data(Data::new(AuthenticatedAdminMiddleware::new(
+                    "kuadrado-admin-auth",
+                )))
+                .service(admin_auth),
+        )
+        .await;
+
+        let req = test::TestRequest::with_uri("/admin-auth")
+            .method(Method::POST)
+            .set_form(&AdminAuthCredentials {
+                username: String::from("whatever"),
+                password: String::from("whatever"),
+            })
+            .to_request();
+
+        let resp = test::call_service(&mut app, req).await;
+
+        assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
+    }
+}
diff --git a/src/view.rs b/src/view.rs
new file mode 100644
index 0000000000000000000000000000000000000000..49f05b1507ab12596f6ecf282cd013923e1f10f3
--- /dev/null
+++ b/src/view.rs
@@ -0,0 +1,191 @@
+use crate::{
+    middleware::AuthenticatedAdminMiddleware, view_resource::ViewResourceManager, AppState,
+};
+use actix_web::{
+    get,
+    web::{Data, Path},
+    HttpRequest, Responder,
+};
+
+/// Returns the content of a ViewResource after retrieving it by name.
+/// If the resource is not found (has not been registered), it returns the 404 ViewResource.
+/// The regex matches uris with more than 3 characters so we don't match the /fr /es etc urls used for the websites translation directories
+#[get("/{resource_name:.{3,}}")]
+pub async fn get_view<'a>(
+    app_state: Data<AppState>,
+    resource_manager: Data<ViewResourceManager>,
+    auth_middleware: Data<AuthenticatedAdminMiddleware<'a>>,
+    req: HttpRequest,
+    resource_name: Path<String>,
+) -> impl Responder {
+    resource_manager
+        .get_resource_as_http_response(
+            &app_state,
+            &auth_middleware,
+            &req,
+            None,
+            &resource_name.into_inner(),
+        )
+        .await
+}
+
+/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
+ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
+ *  _______   ______    ______   _______   *@@
+ * |__   __@ |  ____@  /  ____@ |__   __@  *@@
+ *    |  @   |  @__    \_ @_       |  @    *@@
+ *    |  @   |   __@     \  @_     |  @    *@@
+ *    |  @   |  @___   ____\  @    |  @    *@@
+ *    |__@   |______@  \______@    |__@    *@@
+ *                                         *@@
+ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@@
+ *@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*@*/
+
+#[cfg(test)]
+mod test_views {
+    use super::*;
+    use crate::{
+        middleware::get_auth_cookie,
+        model::{AdminAuthCredentials, Administrator},
+        view_resource::ViewResourceDescriptor,
+    };
+    use actix_web::{
+        http::StatusCode,
+        test,
+        web::{Bytes, Data},
+        App,
+    };
+
+    fn get_views_manager() -> ViewResourceManager {
+        ViewResourceManager::with_views(vec![
+            ViewResourceDescriptor {
+                path_str: "test-view",
+                index_file_name: "index.html",
+                resource_name: "test-view",
+                apply_auth_middleware: false,
+            },
+            ViewResourceDescriptor {
+                path_str: "test-view-auth",
+                index_file_name: "index.html",
+                resource_name: "test-view-auth",
+                apply_auth_middleware: true,
+            },
+        ])
+    }
+
+    async fn get_authenticated_admin(app_state: &AppState) -> Administrator {
+        Administrator::authenticated(
+            app_state,
+            AdminAuthCredentials {
+                username: app_state.env.default_admin_username.to_owned(),
+                password: app_state.env.default_admin_password.to_owned(),
+            },
+        )
+        .await
+        .unwrap()
+    }
+
+    #[tokio::test]
+    async fn test_get_view() {
+        dotenv::dotenv().ok();
+
+        let app_state = AppState::for_test().await;
+
+        let mut app = test::init_service(
+            App::new()
+                .app_data(Data::new(app_state.clone()))
+                .app_data(Data::new(AuthenticatedAdminMiddleware::new(
+                    "kuadrado-admin-auth",
+                )))
+                .app_data(Data::new(get_views_manager()))
+                .service(get_view),
+        )
+        .await;
+
+        let req = test::TestRequest::with_uri("/test-view").to_request();
+        let resp = test::call_service(&mut app, req).await;
+        assert_eq!(resp.status(), StatusCode::OK);
+
+        let body = test::read_body(resp).await;
+        assert_eq!(body, Bytes::from("<h1>TEST</h1>"));
+    }
+
+    #[tokio::test]
+    async fn test_get_view_auth() {
+        dotenv::dotenv().ok();
+
+        let app_state = AppState::for_test().await;
+
+        let mut app = test::init_service(
+            App::new()
+                .app_data(Data::new(app_state.clone()))
+                .app_data(Data::new(AuthenticatedAdminMiddleware::new(
+                    "kuadrado-admin-auth",
+                )))
+                .app_data(Data::new(get_views_manager()))
+                .service(get_view),
+        )
+        .await;
+        let admin_user = get_authenticated_admin(&app_state).await;
+
+        let req = test::TestRequest::with_uri("/test-view-auth")
+            .cookie(get_auth_cookie(
+                "kuadrado-admin-auth",
+                app_state
+                    .encryption
+                    .decrypt(&admin_user.auth_token.unwrap())
+                    .to_owned(),
+            ))
+            .to_request();
+
+        let resp = test::call_service(&mut app, req).await;
+        assert_eq!(resp.status(), StatusCode::OK);
+
+        let body = test::read_body(resp).await;
+        assert_eq!(body, Bytes::from("<h1>TEST AUTH</h1>"));
+    }
+
+    #[tokio::test]
+    async fn test_get_view_auth_unauthorized() {
+        dotenv::dotenv().ok();
+
+        let app_state = AppState::for_test().await;
+
+        let mut app = test::init_service(
+            App::new()
+                .app_data(Data::new(app_state.clone()))
+                .app_data(Data::new(AuthenticatedAdminMiddleware::new(
+                    "kuadrado-admin-auth",
+                )))
+                .app_data(Data::new(get_views_manager()))
+                .service(get_view),
+        )
+        .await;
+
+        let req = test::TestRequest::with_uri("/test-view-auth").to_request();
+        let resp = test::call_service(&mut app, req).await;
+        assert_eq!(resp.status(), StatusCode::UNAUTHORIZED);
+    }
+
+    #[tokio::test]
+    async fn test_get_view_not_found() {
+        dotenv::dotenv().ok();
+
+        let app_state = AppState::for_test().await;
+
+        let mut app = test::init_service(
+            App::new()
+                .app_data(Data::new(app_state.clone()))
+                .app_data(Data::new(AuthenticatedAdminMiddleware::new(
+                    "kuadrado-admin-auth",
+                )))
+                .app_data(Data::new(get_views_manager()))
+                .service(get_view),
+        )
+        .await;
+
+        let req = test::TestRequest::with_uri("/whatever").to_request();
+        let resp = test::call_service(&mut app, req).await;
+        assert_eq!(resp.status(), StatusCode::NOT_FOUND);
+    }
+}
diff --git a/src/view_resource.rs b/src/view_resource.rs
new file mode 100644
index 0000000000000000000000000000000000000000..1a4449032bd7467c0b05548c05d6304ed162a62a
--- /dev/null
+++ b/src/view_resource.rs
@@ -0,0 +1,156 @@
+use crate::{
+    middleware::AuthenticatedAdminMiddleware,
+    model::AdminAuthCredentials,
+    // view_resource::{ViewResource, ViewResourceDescriptor},
+    AppState,
+};
+use actix_web::{web::Form, HttpMessage, HttpRequest, HttpResponse};
+use std::{env::var as env_var, fs::read_to_string as file_to_string, path::PathBuf};
+
+#[derive(Debug, Clone)]
+/// Loads a static resource data allowing it to be served by the get_view service.
+/// It holds a name, allowing the resource to be retrived by name,
+/// a content which can be any text content stored in a string (like an html document),
+/// a path to the directory of the actual static resource, and a boolean which indicates wether
+/// or not an authentication verification should be applied.
+pub struct ViewResource {
+    pub name: String,
+    pub string_contents: String,
+    pub dir_path: PathBuf,
+    pub apply_auth_middleware: bool,
+}
+
+#[derive(Debug, Clone)]
+/// Defines the values that will be used to construct a ViewResource.
+/// It must be passed to the AppViewResourceManager for resource registration
+pub struct ViewResourceDescriptor<'a> {
+    pub path_str: &'a str,
+    pub index_file_name: &'a str,
+    pub resource_name: &'a str,
+    pub apply_auth_middleware: bool,
+}
+
+#[derive(Debug, Clone)]
+/// A structure reponsible of registering and retrieving static resources.
+pub struct ViewResourceManager {
+    resources: Vec<ViewResource>,
+}
+
+impl ViewResourceManager {
+    pub fn new() -> Self {
+        ViewResourceManager { resources: vec![] }
+    }
+
+    /// Calls the constructor and registers the resources described as argument before returning the instance
+    pub fn with_views(resource_descriptors: Vec<ViewResourceDescriptor>) -> Self {
+        let mut instance = Self::new();
+        instance.register_batch(resource_descriptors);
+        instance
+    }
+
+    /// Registers a new static resource in the instance.
+    /// The path provided in the argument must point to an existing file
+    pub fn register(&mut self, desc: ViewResourceDescriptor) {
+        let static_dir = std::path::PathBuf::from(
+            env_var("RESOURCES_DIR").expect("RESOURCES_DIR is not defined"),
+        )
+        .join("public/views");
+
+        let dir_path = static_dir.join(desc.path_str);
+
+        let path: PathBuf = format!("{}/{}", dir_path.to_str().unwrap(), desc.index_file_name)
+            .parse()
+            .expect(&format!(
+                "Failed to pare resource index file path {:?}",
+                desc.index_file_name
+            ));
+
+        let string_contents = file_to_string(path).unwrap();
+
+        &self.resources.push(ViewResource {
+            name: desc.resource_name.to_string(),
+            dir_path,
+            string_contents,
+            apply_auth_middleware: desc.apply_auth_middleware,
+        });
+    }
+
+    /// Registers a collection of multiple resources.
+    pub fn register_batch(&mut self, resource_descriptors: Vec<ViewResourceDescriptor>) {
+        for desc in resource_descriptors.iter() {
+            self.register(desc.clone());
+        }
+    }
+
+    /// Retrieves a resource by name and returns a reference to it or None.
+    pub fn get_resource(&self, name: &str) -> Option<&ViewResource> {
+        self.resources.iter().find(|res| res.name == name)
+    }
+
+    /// Retrieves a resource by name and returns it as an http response.
+    /// This can be returned as it by a service.
+    pub async fn get_resource_as_http_response<'a>(
+        &self,
+        app_state: &AppState,
+        auth_middleware: &AuthenticatedAdminMiddleware<'a>,
+        req: &HttpRequest,
+        auth_data: Option<Form<AdminAuthCredentials>>,
+        resource_name: &str,
+    ) -> HttpResponse {
+        match self.get_resource(resource_name) {
+            Some(res) => {
+                if res.apply_auth_middleware {
+                    let auth_cookie = auth_middleware.exec(app_state, req, auth_data).await;
+                    if auth_cookie.is_err() {
+                        let unauthorized_view = match self.get_resource("unauthorized") {
+                            Some(res_404) => res_404.string_contents.to_string(),
+                            None => {
+                                println!("WARNING: missing Unauthorized view resource");
+
+                                "
+                                    <h1>Unauthorized</h1>
+                                    <p>You must login as an administrator to access this page</p>
+                                    <a href='/v/admin-login'>Login page</a>
+                                "
+                                .to_string()
+                            }
+                        };
+
+                        let mut response_builder = HttpResponse::Unauthorized();
+
+                        return match req.cookie(auth_middleware.cookie_name) {
+                            Some(cookie) => {
+                                // Invalidate auth_cookie if auth failed in any way
+                                response_builder
+                                    .del_cookie(&cookie)
+                                    .content_type("text/html")
+                                    .body(unauthorized_view)
+                            }
+                            None => response_builder
+                                .content_type("text/html")
+                                .body(unauthorized_view),
+                        };
+                    } else {
+                        return HttpResponse::Ok()
+                            .content_type("text/html")
+                            .cookie(auth_cookie.unwrap())
+                            .body(&res.string_contents);
+                    }
+                }
+
+                HttpResponse::Ok()
+                    .content_type("text/html")
+                    .body(&res.string_contents)
+            }
+            None => match self.get_resource("404") {
+                Some(res_404) => HttpResponse::NotFound()
+                    .content_type("text/html")
+                    .body(&res_404.string_contents),
+                None => {
+                    println!("WARNING: missing 404 view resource");
+                    HttpResponse::NotFound().finish()
+                }
+            },
+        }
+    }
+}