Browse Source

Many updates

Split codebase into modules
Added custom all in one error type
Added password to user model
Added password hashing using bcrypt
Added model validation for username using regex
devel
Rohan Sircar 4 years ago
parent
commit
cb26393fcd
  1. 350
      Cargo.lock
  2. 91
      Cargo.toml
  3. 1
      README.md
  4. 6
      migrations/2020-05-02-115427_create_users/up.sql
  5. 69
      src/actions.rs
  6. 83
      src/actions/users.rs
  7. 2
      src/errors.rs
  8. 88
      src/errors/domain_error.rs
  9. 188
      src/main.rs
  10. 3
      src/middlewares.rs
  11. 276
      src/middlewares/csrf.rs
  12. 28
      src/models.rs
  13. 13
      src/models/errors.rs
  14. 0
      src/models/roles.rs
  15. 38
      src/models/users.rs
  16. 2
      src/routes.rs
  17. 45
      src/routes/auth.rs
  18. 92
      src/routes/users.rs
  19. 2
      src/schema.rs
  20. 4
      src/utils.rs
  21. 14
      src/utils/auth.rs
  22. 5
      src/utils/regexs.rs
  23. BIN
      test.db

350
Cargo.lock

@ -45,22 +45,32 @@ dependencies = [
"actix-service",
"actix-web",
"actix-web-httpauth",
"bcrypt",
"bytes",
"chrono",
"comp",
"custom_error",
"diesel",
"dotenv",
"env_logger 0.7.1",
"futures",
"json",
"jsonwebtoken",
"lazy-regex",
"lazy_static",
"listenfd",
"log",
"nanoid",
"r2d2",
"rand 0.7.3",
"regex",
"rusqlite",
"serde",
"serde_json",
"timeago",
"uuid 0.8.1",
"validator",
"validator_derive",
"yarte",
"yarte_helpers",
]
@ -501,6 +511,25 @@ version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "base64"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d1ccbaf7d9ec9537465a97bf19edc1a4e158ecb49fc16178202238c569cc42"
[[package]]
name = "bcrypt"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f02d7d008a57bcb2251ba115b803934e02315edbde9a861c88713493e381b63"
dependencies = [
"base64 0.12.1",
"blowfish",
"byteorder",
"lazy_static",
"rand 0.7.3",
]
[[package]]
name = "bincode"
version = "1.2.1"
@ -551,6 +580,26 @@ dependencies = [
"constant_time_eq",
]
[[package]]
name = "block-cipher-trait"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
dependencies = [
"generic-array",
]
[[package]]
name = "blowfish"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3"
dependencies = [
"block-cipher-trait",
"byteorder",
"opaque-debug",
]
[[package]]
name = "brotli-sys"
version = "0.3.2"
@ -627,6 +676,7 @@ checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
dependencies = [
"num-integer",
"num-traits",
"serde",
"time",
]
@ -677,6 +727,12 @@ dependencies = [
"bitflags",
]
[[package]]
name = "comp"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b6cae29f71a26f0dae0e291da438d6fced0e22e78aa1484cbbc085b5170949"
[[package]]
name = "console"
version = "0.10.3"
@ -735,6 +791,12 @@ dependencies = [
"lazy_static",
]
[[package]]
name = "custom_error"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93a0fc65739ae998afc8d68e64bdac2efd1bc4ffa1a0703d171ef2defae3792f"
[[package]]
name = "darling"
version = "0.10.2"
@ -813,6 +875,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33d7ca63eb2efea87a7f56a283acc49e2ce4b2bd54adf7465dc1d81fef13d8fc"
dependencies = [
"byteorder",
"chrono",
"diesel_derives",
"libsqlite3-sys",
"r2d2",
@ -1029,6 +1092,18 @@ dependencies = [
"synstructure",
]
[[package]]
name = "fallible-iterator"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
[[package]]
name = "fallible-streaming-iterator"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a"
[[package]]
name = "flate2"
version = "1.0.14"
@ -1179,6 +1254,15 @@ dependencies = [
"byteorder",
]
[[package]]
name = "generic-array"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
dependencies = [
"typenum",
]
[[package]]
name = "getrandom"
version = "0.1.14"
@ -1198,9 +1282,9 @@ checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
[[package]]
name = "h2"
version = "0.2.4"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42"
checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff"
dependencies = [
"bytes",
"fnv",
@ -1287,6 +1371,12 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "if_chain"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3360c7b59e5ffa2653671fb74b4741a5d343c03f331c0a4aeda42b5c2b0ec7d"
[[package]]
name = "indexmap"
version = "1.3.2"
@ -1317,6 +1407,16 @@ dependencies = [
"winreg",
]
[[package]]
name = "isolang"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "265ef164908329e47e753c769b14cbb27434abf0c41984dca201484022f09ce5"
dependencies = [
"phf 0.7.24",
"phf_codegen 0.7.24",
]
[[package]]
name = "itoa"
version = "0.4.5"
@ -1368,6 +1468,15 @@ version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
[[package]]
name = "lazy-regex"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03d91276c62198fd9dd1be0d8a4ed647d0a51d5d6a0679dc324dd0b499d024ff"
dependencies = [
"lazy_static",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -1402,6 +1511,7 @@ version = "0.17.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56d90181c2904c287e5390186be820e5ef311a3c62edebb7d6ca3d6a48ce041d"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
]
@ -1417,9 +1527,9 @@ dependencies = [
[[package]]
name = "linked-hash-map"
version = "0.5.2"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]]
name = "listenfd"
@ -1472,8 +1582,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aae38d669396ca9b707bfc3db254bc382ddb94f57cc5c235f34623a669a01dab"
dependencies = [
"log",
"phf",
"phf_codegen",
"phf 0.8.0",
"phf_codegen 0.8.0",
"serde",
"serde_derive",
"serde_json",
@ -1672,6 +1782,12 @@ dependencies = [
"pkg-config",
]
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "parking_lot"
version = "0.10.2"
@ -1719,13 +1835,32 @@ version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "phf"
version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18"
dependencies = [
"phf_shared 0.7.24",
]
[[package]]
name = "phf"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12"
dependencies = [
"phf_shared",
"phf_shared 0.8.0",
]
[[package]]
name = "phf_codegen"
version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e"
dependencies = [
"phf_generator 0.7.24",
"phf_shared 0.7.24",
]
[[package]]
@ -1734,8 +1869,18 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815"
dependencies = [
"phf_generator",
"phf_shared",
"phf_generator 0.8.0",
"phf_shared 0.8.0",
]
[[package]]
name = "phf_generator"
version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662"
dependencies = [
"phf_shared 0.7.24",
"rand 0.6.5",
]
[[package]]
@ -1744,33 +1889,42 @@ version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526"
dependencies = [
"phf_shared",
"phf_shared 0.8.0",
"rand 0.7.3",
]
[[package]]
name = "phf_shared"
version = "0.7.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0"
dependencies = [
"siphasher 0.2.3",
]
[[package]]
name = "phf_shared"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7"
dependencies = [
"siphasher",
"siphasher 0.3.3",
]
[[package]]
name = "pin-project"
version = "0.4.10"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36e3dcd42688c05a66f841d22c5d8390d9a5d4c9aaf57b9285eae4900a080063"
checksum = "82c3bfbfb5bb42f99498c7234bbd768c220eb0cea6818259d0d18a1aa3d2595d"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "0.4.10"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4d7346ac577ff1296e06a418e7618e22655bae834d4970cb6e39d6da8119969"
checksum = "ccbf6449dcfb18562c015526b085b8df1aa3cdab180af8ec2ebd300a3bd28f63"
dependencies = [
"proc-macro2 1.0.12",
"quote 1.0.4",
@ -1779,9 +1933,9 @@ dependencies = [
[[package]]
name = "pin-project-lite"
version = "0.1.4"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
checksum = "f7505eeebd78492e0f6108f7171c4948dbb120ee8119d9d77d0afa5469bef67f"
[[package]]
name = "pin-utils"
@ -1872,15 +2026,6 @@ dependencies = [
"unicode-xid 0.2.0",
]
[[package]]
name = "proc-macro2-impersonated"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d32df793782c132f437089d84b487d617a0baac886fa8519751dca07db9266e0"
dependencies = [
"unicode-xid 0.2.0",
]
[[package]]
name = "quick-error"
version = "1.2.3"
@ -1905,15 +2050,6 @@ dependencies = [
"proc-macro2 1.0.12",
]
[[package]]
name = "quote-impersonated"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e3e450e6f50c99055b77f01eb206d8accbb7a1d14c2c7be2b0a829286296dc9"
dependencies = [
"proc-macro2-impersonated",
]
[[package]]
name = "r2d2"
version = "0.8.8"
@ -2160,6 +2296,21 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "rusqlite"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a656821bb6317a84b257737b7934f79c0dbb7eb694710475908280ebad3e64"
dependencies = [
"bitflags",
"fallible-iterator",
"fallible-streaming-iterator",
"libsqlite3-sys",
"lru-cache",
"memchr",
"time",
]
[[package]]
name = "rust-argon2"
version = "0.7.0"
@ -2311,6 +2462,12 @@ dependencies = [
"num-traits",
]
[[package]]
name = "siphasher"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac"
[[package]]
name = "siphasher"
version = "0.3.3"
@ -2355,7 +2512,7 @@ checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a"
dependencies = [
"lazy_static",
"new_debug_unreachable",
"phf_shared",
"phf_shared 0.8.0",
"precomputed-hash",
"serde",
]
@ -2366,8 +2523,8 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97"
dependencies = [
"phf_generator",
"phf_shared",
"phf_generator 0.8.0",
"phf_shared 0.8.0",
"proc-macro2 1.0.12",
"quote 1.0.4",
]
@ -2386,26 +2543,15 @@ checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "syn"
version = "1.0.18"
version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "410a7488c0a728c7ceb4ad59b9567eb4053d02e8cc7f5c0e0eeeb39518369213"
checksum = "e8e5aa70697bb26ee62214ae3288465ecec0000f05182f039b477001f08f5ae7"
dependencies = [
"proc-macro2 1.0.12",
"quote 1.0.4",
"unicode-xid 0.2.0",
]
[[package]]
name = "syn-impersonated"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c11664eb1aded8a1be30656bedb3c2ee3d6b594842c90ec635cf7b855a5d8478"
dependencies = [
"proc-macro2-impersonated",
"quote-impersonated",
"unicode-xid 0.2.0",
]
[[package]]
name = "synstructure"
version = "0.12.3"
@ -2529,6 +2675,16 @@ dependencies = [
"winapi 0.3.8",
]
[[package]]
name = "timeago"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aff2f3f1ac92d664adfdea85496dceb8c044f66d62e7d953a059023385967cfc"
dependencies = [
"chrono",
"isolang",
]
[[package]]
name = "tokio"
version = "0.2.20"
@ -2536,7 +2692,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c1d570eb1a36f0345a5ce9c6c6e665b70b73d11236912c0b477616aeec47b1"
dependencies = [
"bytes",
"fnv",
"futures-core",
"iovec",
"lazy_static",
@ -2639,6 +2794,12 @@ dependencies = [
"trust-dns-proto",
]
[[package]]
name = "typenum"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "unicase"
version = "2.6.0"
@ -2755,12 +2916,12 @@ dependencies = [
[[package]]
name = "v_eval"
version = "0.5.3"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a419cdda656c514fbb974d5634994278be348867645cb65bf21a9b0676b9c2"
checksum = "0dd8b599d797eb038d0dde9a3860aacb6bbba3bffa4ac64f807c8673820cc9d9"
dependencies = [
"regex",
"syn-impersonated",
"syn",
]
[[package]]
@ -2788,6 +2949,21 @@ dependencies = [
"url",
]
[[package]]
name = "validator_derive"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e668e9cd05c5009b833833aa1147e5727b5396ea401f22dd1167618eed4a10c9"
dependencies = [
"if_chain",
"lazy_static",
"proc-macro2 1.0.12",
"quote 1.0.4",
"regex",
"syn",
"validator",
]
[[package]]
name = "vcpkg"
version = "0.2.8"
@ -2796,9 +2972,9 @@ checksum = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
[[package]]
name = "vec_map"
version = "0.8.1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
@ -2988,9 +3164,9 @@ dependencies = [
[[package]]
name = "yarte"
version = "0.8.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "884cff3b02a11d026c493282e598a5c990b17fa22b91fb73aad2bfeee47f4f01"
checksum = "819b7b4da6e6b0192ef2f8da43ed7d79716737a57246aff58ba75611f8083b94"
dependencies = [
"yarte_derive",
"yarte_helpers",
@ -2998,13 +3174,13 @@ dependencies = [
[[package]]
name = "yarte_codegen"
version = "0.8.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a64e8ba00f9d77cd05b665b3515251544c5b1f4885e7e3f7ffc54ea27f52eabc"
checksum = "e3c178dcde178349d41d3b6c85353614fa43c2ddceba3e86073a864454149165"
dependencies = [
"proc-macro2-impersonated",
"quote-impersonated",
"syn-impersonated",
"proc-macro2 1.0.12",
"quote 1.0.4",
"syn",
"yarte_dom",
"yarte_helpers",
"yarte_hir",
@ -3013,14 +3189,14 @@ dependencies = [
[[package]]
name = "yarte_derive"
version = "0.8.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50f11ebd841875bd9b906017844640aaac1571a4a188b3f7577efd6c02616da1"
checksum = "3a549fd6831f6354c39bb83bf78d7587e9b5ff18f7d390aefc4ba89435ba114f"
dependencies = [
"prettyprint",
"proc-macro2-impersonated",
"quote-impersonated",
"syn-impersonated",
"proc-macro2 1.0.12",
"quote 1.0.4",
"syn",
"tempfile",
"toolchain_find",
"yarte_codegen",
@ -3031,13 +3207,13 @@ dependencies = [
[[package]]
name = "yarte_dom"
version = "0.8.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43cc56a6351af550001f7f7e07be3581cde4908a8ddcbba34630168afc4ebabc"
checksum = "c08abf1e4cbf721cce79c77a7da1e42b37e7321966724b094891dc0c3d1d5cda"
dependencies = [
"markup5ever",
"quote-impersonated",
"syn-impersonated",
"quote 1.0.4",
"syn",
"yarte_helpers",
"yarte_hir",
"yarte_html",
@ -3045,9 +3221,9 @@ dependencies = [
[[package]]
name = "yarte_helpers"
version = "0.8.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "771250e834a31e39a6e34e48e3fd9032542d211cb3f194f9a71b603fb841a793"
checksum = "e981464e47692f747fbc3f29cf271f65ac363cde5518ae77a50617d5490b9fce"
dependencies = [
"serde",
"toml",
@ -3056,14 +3232,14 @@ dependencies = [
[[package]]
name = "yarte_hir"
version = "0.8.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c86bad7da0c2ae56374390919fa1d7abaa067f6325a3e27865ae8598bfc24057"
checksum = "6105dad390184a8e4afdce19cdd0956ae1af057da1571d7add988527cbd59953"
dependencies = [
"derive_more",
"proc-macro2-impersonated",
"quote-impersonated",
"syn-impersonated",
"proc-macro2 1.0.12",
"quote 1.0.4",
"syn",
"v_eval",
"v_htmlescape",
"yarte_helpers",
@ -3072,9 +3248,9 @@ dependencies = [
[[package]]
name = "yarte_html"
version = "0.8.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "261043d486d9ecdcb33c68d0323444db6ab6915f90e973c5cbd4331560ec0281"
checksum = "a0e8c01747147af821238b33c76c3045420a245fcfb3e2987becf2860b54598a"
dependencies = [
"log",
"mac",
@ -3087,15 +3263,15 @@ dependencies = [
[[package]]
name = "yarte_parser"
version = "0.8.3"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f855c622d29c8a8af4f4b7a3f81e83ac0be6e6074977a017c3945db05fba311"
checksum = "061ed8ddf990b97d89827c42346f1273d434e79a3618786d3f595f2f1befe418"
dependencies = [
"annotate-snippets",
"derive_more",
"proc-macro2-impersonated",
"quote-impersonated",
"syn-impersonated",
"proc-macro2 1.0.12",
"quote 1.0.4",
"syn",
"unicode-xid 0.2.0",
"yarte_helpers",
]

91
Cargo.toml

@ -1,43 +1,70 @@
[package]
name = "actix-demo"
version = "0.1.0"
authors = ["Rohan Sircar <rohansircar@tuta.io>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
name = 'actix-demo'
version = '0.1.0'
authors = ['Rohan Sircar <rohansircar@tuta.io>']
edition = '2018'
[dependencies]
actix-web = "2"
actix-rt = "1"
actix-service = "1.0.5"
actix-files = "0.2.1"
actix-web = '2'
actix-rt = '1'
actix-service = '1.0.5'
actix-files = '0.2.1'
bytes = '0.5.4'
futures = '0.3.4'
log = '0.4.8'
env_logger = '0.7'
serde_json = '1.0.52'
json = '0.12.4'
listenfd = '0.3.3'
dotenv = '0.15'
r2d2 = '0.8.8'
validator = '0.10.0'
validator_derive = '0.10'
jsonwebtoken = '7'
actix-identity = '0.2.1'
actix-web-httpauth = '0.4.1'
actix-http = '1.0.1'
rand = '0.7.3'
nanoid = '0.3.0'
bcrypt = '0.7'
timeago = '0.2.1'
comp = '0.2.1'
regex = '1.3.7'
lazy_static = '1.4.0'
lazy-regex = '0.1.2'
custom_error = '1.7.1'
bytes = "0.5.4"
futures = "0.3.4"
log = "0.4.8"
env_logger = "0.7"
[dependencies.serde]
version = '1.0.106'
features = ['derive']
serde = { version = "1.0.106", features = ["derive"] }
serde_json = "1.0.52"
json = "0.12.4"
[dependencies.yarte]
version = '0.9.0'
features = ['html-min']
yarte = { version = "0.8.3", features = ["html-min"] }
[dependencies.diesel]
version = '1.4.4'
features = [
'sqlite',
'r2d2',
'chrono',
]
listenfd = "0.3.3"
[dependencies.uuid]
version = '0.8'
features = [
'serde',
'v4',
]
diesel = { version = "1.4.4", features = ["sqlite", "r2d2"] }
dotenv = "0.15"
r2d2 = "0.8.8"
uuid = { version = "0.8", features = ["serde", "v4"] }
validator = "0.10.0"
jsonwebtoken = "7"
actix-identity = "0.2.1"
actix-web-httpauth = "0.4.1"
actix-http = "1.0.1"
rand = "0.7.3"
nanoid = "0.3.0"
[dependencies.rusqlite]
version = '0.21.0'
features = ['bundled']
[dependencies.chrono]
version = '0.4.11'
features = ['serde']
[build-dependencies.yarte_helpers]
version = "0.8"
version = '0.9.0'
default-features = false
features = ["config"]
features = ['config']

1
README.md

@ -0,0 +1 @@
Testing out the Rust framework Actix-Web to create a JSON API CRUD Web App.

6
migrations/2020-05-02-115427_create_users/up.sql

@ -1,5 +1,7 @@
-- Your SQL goes here
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY,
name VARCHAR NOT NULL
id INTEGER PRIMARY KEY NOT NULL ,
name VARCHAR NOT NULL,
password VARCHAR NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
)

69
src/actions.rs

@ -1,67 +1,2 @@
use diesel::prelude::*;
use crate::models;
pub fn find_user_by_uid(
uid: i32,
conn: &SqliteConnection,
) -> Result<Option<models::User>, diesel::result::Error> {
use crate::schema::users::dsl::*;
let maybe_user = users.find(uid).first::<models::User>(conn).optional();
// Ok(user)
maybe_user
}
pub fn find_user_by_name(
user_name: String,
conn: &SqliteConnection,
) -> Result<Option<models::User>, diesel::result::Error> {
use crate::schema::users::dsl::*;
let maybe_user = users
.filter(name.eq(user_name))
.first::<models::User>(conn)
.optional();
maybe_user
}
pub fn get_all(
conn: &SqliteConnection,
) -> Result<Option<Vec<models::User>>, diesel::result::Error> {
use crate::schema::users::dsl::*;
users.load::<models::User>(conn).optional()
}
/// Run query using Diesel to insert a new database row and return the result.
pub fn insert_new_user(
nu: &models::NewUser,
conn: &SqliteConnection,
) -> Result<models::User, diesel::result::Error> {
// It is common when using Diesel with Actix web to import schema-related
// modules inside a function's scope (rather than the normal module's scope)
// to prevent import collisions and namespace pollution.
use crate::schema::users::dsl::*;
// let new_user = models::User {
// id: Uuid::new_v4().to_string(),
// name: nu.name.to_string(),
// };
// let x = users.load::<models::User>(conn).optional();
// let target = users.find("4");
// let test_user = models::User {
// id: "5".to_owned(),
// name: "who".to_owned(),
// };
// let update_result = diesel::update(target).set(&test_user).execute(conn);
diesel::insert_into(users).values(nu).execute(conn)?;
let user = users
.filter(name.eq(nu.name.clone()))
.first::<models::User>(conn);
user
// Ok(nu.clone())
}
pub mod users;
pub use self::users::*;

83
src/actions/users.rs

@ -0,0 +1,83 @@
use diesel::prelude::*;
use crate::errors;
use crate::models;
use bcrypt::{hash, DEFAULT_COST};
use std::rc::Rc;
pub fn find_user_by_uid(
uid: i32,
conn: &SqliteConnection,
) -> Result<Option<models::UserDTO>, errors::DomainError> {
use crate::schema::users::dsl::*;
let maybe_user = users
.select((name, created_at))
.find(uid)
.first::<models::UserDTO>(conn)
.optional();
Ok(maybe_user?)
}
pub fn _find_user_by_name(
user_name: String,
conn: &SqliteConnection,
) -> Result<Option<models::UserDTO>, errors::DomainError> {
use crate::schema::users::dsl::*;
let maybe_user = users
.select((name, created_at))
.filter(name.eq(user_name))
.first::<models::UserDTO>(conn)
.optional();
Ok(maybe_user?)
}
pub fn get_all(
conn: &SqliteConnection,
) -> Result<Option<Vec<models::UserDTO>>, errors::DomainError> {
use crate::schema::users::dsl::*;
Ok(users
.select((name, created_at))
.load::<models::UserDTO>(conn)
.optional()?)
}
/// Run query using Diesel to insert a new database row and return the result.
pub fn insert_new_user(
mut nu: Rc<models::NewUser>,
conn: &SqliteConnection,
) -> Result<models::UserDTO, errors::DomainError> {
// It is common when using Diesel with Actix web to import schema-related
// modules inside a function's scope (rather than the normal module's scope)
// to prevent import collisions and namespace pollution.
use crate::schema::users::dsl::*;
// let new_user = models::User {
// id: Uuid::new_v4().to_string(),
// name: nu.name.to_string(),
// };
// let x = users.load::<models::User>(conn).optional();
// let target = users.find("4");
// let test_user = models::User {
// id: "5".to_owned(),
// name: "who".to_owned(),
// };
// let update_result = diesel::update(target).set(&test_user).execute(conn);
// let mut nu2 = nu.clone();
let mut nu2 = Rc::make_mut(&mut nu);
nu2.password = hash(nu2.password.clone(), DEFAULT_COST)?;
diesel::insert_into(users)
.values(nu.as_ref())
.execute(conn)?;
let user = users
.select((name, created_at))
.filter(name.eq(nu.name.clone()))
.first::<models::UserDTO>(conn);
Ok(user?)
}

2
src/errors.rs

@ -0,0 +1,2 @@
pub mod domain_error;
pub use self::domain_error::*;

88
src/errors/domain_error.rs

@ -0,0 +1,88 @@
use actix_web::{HttpResponse, ResponseError};
use bcrypt::BcryptError;
use custom_error::custom_error;
// use derive_more::Display;
// use diesel::result::DatabaseErrorKind;
use crate::models::errors::*;
use r2d2;
use std::convert::From;
// use std::error::Error;
// pub enum DomainError {
// #[display(fmt = "PasswordHashError")]
// PwdHashError,
// #[display(fmt = "Bad Id")]
// IdError,
// #[display(fmt = "Generic Error")]
// GenericError,
// DuplicateValue,
// }
// impl Error for DomainError {
// fn source(&self) -> Option<&(dyn error::Error + 'static)> {
// // Generic error, underlying cause isn't tracked.
// None
// }
// }
// impl From<DBError> for DomainError {
// fn from(error: DBError) -> DomainError {
// // We only care about UniqueViolations
// match error {
// DBError::DatabaseError(kind, info) => {
// let message = info.details().unwrap_or_else(|| info.message()).to_string();
// match kind {
// DatabaseErrorKind::UniqueViolation => DomainError::DuplicateValue(message),
// _ => DomainError::GenericError(message),
// }
// }
// _ => DomainError::GenericError(String::from("Some database error occured")),
// }
// }
// }
custom_error! { pub DomainError
PwdHashError {source: BcryptError} = "Failed to has password",
DbError {source: diesel::result::Error} = "Database error",
DbPoolError {source: r2d2::Error} = "Failed to get connection from pool",
GenericError {cause: String} = "Generic Error - Reason: {cause}"
}
impl ResponseError for DomainError {
fn error_response(&self) -> HttpResponse {
match self {
DomainError::PwdHashError { source } => {
HttpResponse::InternalServerError().json(ErrorModel {
status_code: 500,
reason: format!(
"{} {}",
"Unexpected Error - Failed to hash password", source
),
})
}
DomainError::DbError { source } => {
HttpResponse::InternalServerError().json(ErrorModel {
status_code: 500,
reason: format!("{} {}", "Unexpected Database Error", source),
})
}
DomainError::DbPoolError { source } => {
HttpResponse::InternalServerError().json(ErrorModel {
status_code: 500,
reason: format!(
"{} {}",
"Unexpected Error - Failed to get connection from pool", source
),
})
}
DomainError::GenericError { cause } => HttpResponse::BadRequest().json(ErrorModel {
status_code: 400,
reason: format!(
"{} {}, ",
"Unexpected Database Error - ".to_owned(),
cause.clone()
),
}),
}
}
}

188
src/main.rs

@ -1,32 +1,43 @@
#[macro_use]
extern crate diesel;
#[macro_use]
extern crate comp;
#[macro_use]
extern crate validator_derive;
extern crate bcrypt;
extern crate custom_error;
extern crate regex;
extern crate validator;
use actix_web::{
dev::ServiceRequest, error, get, middleware, post, web, App, Error, HttpRequest, HttpResponse,
HttpServer, Responder,
error, get, middleware, post, web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder,
};
use yarte::Template;
use actix_web_httpauth::{extractors::basic::BasicAuth, middleware::HttpAuthentication};
use actix_web_httpauth::middleware::HttpAuthentication;
use actix_http::cookie::SameSite;
use actix_identity::{CookieIdentityPolicy, Identity, IdentityService};
use actix_identity::{CookieIdentityPolicy, IdentityService};
use rand::Rng;
// use actix_http::*;
use actix_files as fs;
use diesel::prelude::*;
use diesel::r2d2::{self, ConnectionManager};
use routes::*;
// use middlewares::csrf;
// use routes;
// use routes::users;
// use utils;
mod actions;
mod errors;
mod middlewares;
mod models;
mod routes;
mod schema;
mod types;
mod utils;
#[macro_use]
extern crate log;
@ -68,161 +79,14 @@ impl UserServiceImpl {
}
}
pub trait UserService {
fn do_something(&self);
}
impl UserService for UserServiceImpl {
fn do_something(&self) {
println!("hello");
}
}
fn fun1(user_service: &dyn UserService) {
user_service.do_something();
}
fn fun2<T>(user_service: T)
where
T: UserService,
{
user_service.do_something();
}
/// In this example validator returns immediately,
/// but since it is required to return anything
/// that implements `IntoFuture` trait,
/// it can be extended to query database
/// or to do something else in a async manner.
async fn validator(req: ServiceRequest, credentials: BasicAuth) -> Result<ServiceRequest, Error> {
// All users are great and more than welcome!
// let pool = req.app_data::<DbPool>();
// let maybe_header = req.headers().get("Authorization");
// match maybe_header {
// Some(value) => {
// info!("{:?}", *value);
// let x: Result<Basic, _> = Scheme::parse(value);
// let y = x.expect("Error parsing header");
// println!("{}", y.user_id());
// println!("{:?}", y.password().clone());
// }
// None => debug!("Header not found"),
// }
// maybe_header
// .map(|value| {
// let x: Result<Basic, _> = Scheme::parse(value);
// x
// })
// .map(|maybe_basic| {
// maybe_basic
// .map(|x| {
// println!("{}", x.user_id());
// println!("{:?}", x.password().clone());
// })
// .map_err(|x| println!("error parsing reason - {}", x.to_string()))
// // maybe_basic
// });
// let auth = Authorization::<Basic>;
println!("{}", credentials.user_id());
println!("{:?}", credentials.password());
Ok(req)
}
// fn parse(header: &HeaderValue) -> Result<Basic, ParseError> {
// // "Basic *" length
// if header.len() < 7 {
// return Err(ParseError::Invalid);
// }
// let mut parts = header.to_str()?.splitn(2, ' ');
// match parts.next() {
// Some(scheme) if scheme == "Basic" => (),
// _ => return Err(ParseError::MissingScheme),
// }
// let decoded = base64::decode(parts.next().ok_or(ParseError::Invalid)?)?;
// let mut credentials = str::from_utf8(&decoded)?.splitn(2, ':');
// let user_id = credentials
// .next()
// .ok_or(ParseError::MissingField("user_id"))
// .map(|user_id| user_id.to_string().into())?;
// let password = credentials
// .next()
// .ok_or(ParseError::MissingField("password"))
// .map(|password| {
// if password.is_empty() {
// None
// } else {
// Some(password.to_string().into())
// }
// })?;
// Ok(Basic { user_id, password })
// }
#[get("/login")]
async fn login(id: Identity) -> HttpResponse {
let maybe_identity = id.identity();
// id.remember("user1".to_owned());
let response = if let Some(identity) = maybe_identity {
HttpResponse::Ok()
.header("location", "/")
.content_type("text/plain")
.body(format!("Already logged in {}", identity))
} else {
id.remember("user1".to_owned());
HttpResponse::Found().header("location", "/").finish()
};
// HttpResponse::Found().header("location", "/").finish()
response
}
#[get("/logout")]
async fn logout(id: Identity) -> HttpResponse {
let maybe_identity = id.identity();
// id.remember("user1".to_owned());
let response = if let Some(identity) = maybe_identity {
info!("Logging out {user}", user = identity);
id.forget();
HttpResponse::Found().header("location", "/").finish()
} else {
HttpResponse::Ok()
.header("location", "/")
.content_type("text/plain")
.body("Not logged in")
};
// id.forget();
// HttpResponse::Found().header("location", "/").finish()
response
}
#[get("/")]
async fn index2(id: Identity) -> String {
format!(
"Hello {}",
id.identity().unwrap_or_else(|| "Anonymous".to_owned())
)
}
#[actix_rt::main]
async fn main() -> std::io::Result<()> {
std::env::set_var("RUST_LOG", "debug");
env_logger::init();
dotenv::dotenv().ok();
let user_service: Box<dyn UserService> = Box::new(UserServiceImpl::new());
user_service.do_something();
fun1(user_service.as_ref());
let user_service_impl = UserServiceImpl::new();
fun2(user_service_impl);
let basic_auth_middleware = HttpAuthentication::basic(validator);
let basic_auth_middleware = HttpAuthentication::basic(utils::auth::validator);
// fun1(Rc::clone(&user_service).as_ref());
// set up database connection pool
let connspec = std::env::var("DATABASE_URL").expect("DATABASE_URL NOT FOUND");
let manager = ConnectionManager::<SqliteConnection>::new(connspec);
@ -240,18 +104,18 @@ async fn main() -> std::io::Result<()> {
CookieIdentityPolicy::new(&private_key)
.name("my-app-auth")
.secure(false)
.same_site(SameSite::Lax), // .same_site(),
.same_site(SameSite::Lax),
))
.wrap(middleware::Logger::default())
.service(web::scope("/chat").wrap(basic_auth_middleware.clone()))
// .service(extract_my_obj)
// .service(index)
.service(get_user)
.service(add_user)
.service(get_all_users)
.service(login)
.service(logout)
.service(index2)
.service(routes::users::get_user)
.service(routes::users::add_user)
.service(routes::users::get_all_users)
.service(routes::auth::login)
.service(routes::auth::logout)
.service(routes::auth::index)
.service(fs::Files::new("/", "./static"))
};
HttpServer::new(app).bind(addr)?.run().await

3
src/middlewares.rs

@ -0,0 +1,3 @@
pub mod csrf;
pub use self::csrf::*;

276
src/middlewares/csrf.rs

@ -0,0 +1,276 @@
// //! A filter for cross-site request forgery (CSRF).
// //!
// //! This middleware is stateless and [based on request
// //! headers](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet#Verifying_Same_Origin_with_Standard_Headers).
// //!
// //! By default requests are allowed only if one of these is true:
// //!
// //! * The request method is safe (`GET`, `HEAD`, `OPTIONS`). It is the
// //! applications responsibility to ensure these methods cannot be used to
// //! execute unwanted actions. Note that upgrade requests for websockets are
// //! also considered safe.
// //! * The `Origin` header (added automatically by the browser) matches one
// //! of the allowed origins.
// //! * There is no `Origin` header but the `Referer` header matches one of
// //! the allowed origins.
// //!
// //! Use [`CsrfFilter::allow_xhr()`](struct.CsrfFilter.html#method.allow_xhr)
// //! if you want to allow requests with unprotected methods via
// //! [CORS](../cors/struct.Cors.html).
// //!
// //! # Example
// //!
// //! ```
// //! # extern crate actix_web;
// //! use actix_web::middleware::csrf;
// //! use actix_web::{http, App, HttpRequest, HttpResponse};
// //!
// //! fn handle_post(_: &HttpRequest) -> &'static str {
// //! "This action should only be triggered with requests from the same site"
// //! }
// //!
// //! fn main() {
// //! let app = App::new()
// //! .middleware(
// //! csrf::CsrfFilter::new().allowed_origin("https://www.example.com"),
// //! )
// //! .resource("/", |r| {
// //! r.method(http::Method::GET).f(|_| HttpResponse::Ok());
// //! r.method(http::Method::POST).f(handle_post);
// //! })
// //! .finish();
// //! }
// //! ```
// //!
// //! In this example the entire application is protected from CSRF.
// use std::borrow::Cow;
// use std::collections::HashSet;
// use bytes::Bytes;
// use error::{ResponseError, Result};
// use http::{header, HeaderMap, HttpTryFrom, Uri};
// use httprequest::HttpRequest;
// use httpresponse::HttpResponse;
// use middleware::{Middleware, Started};
// use server::Request;
// /// Potential cross-site request forgery detected.
// #[derive(Debug, Fail)]
// pub enum CsrfError {
// /// The HTTP request header `Origin` was required but not provided.
// #[fail(display = "Origin header required")]
// MissingOrigin,
// /// The HTTP request header `Origin` could not be parsed correctly.
// #[fail(display = "Could not parse Origin header")]
// BadOrigin,
// /// The cross-site request was denied.
// #[fail(display = "Cross-site request denied")]
// CsrDenied,
// }
// impl ResponseError for CsrfError {
// fn error_response(&self) -> HttpResponse {
// HttpResponse::Forbidden().body(self.to_string())
// }
// }
// fn uri_origin(uri: &Uri) -> Option<String> {
// match (
// uri.scheme_part(),
// uri.host(),
// uri.port_part().map(|port| port.as_u16()),
// ) {
// (Some(scheme), Some(host), Some(port)) => Some(format!("{}://{}:{}", scheme, host, port)),
// (Some(scheme), Some(host), None) => Some(format!("{}://{}", scheme, host)),
// _ => None,
// }
// }
// fn origin(headers: &HeaderMap) -> Option<Result<Cow<str>, CsrfError>> {
// headers
// .get(header::ORIGIN)
// .map(|origin| {
// origin
// .to_str()
// .map_err(|_| CsrfError::BadOrigin)
// .map(|o| o.into())
// })
// .or_else(|| {
// headers.get(header::REFERER).map(|referer| {
// Uri::try_from(Bytes::from(referer.as_bytes()))
// .ok()
// .as_ref()
// .and_then(uri_origin)
// .ok_or(CsrfError::BadOrigin)
// .map(|o| o.into())
// })
// })
// }
// /// A middleware that filters cross-site requests.
// ///
// /// To construct a CSRF filter:
// ///
// /// 1. Call [`CsrfFilter::build`](struct.CsrfFilter.html#method.build) to
// /// start building.
// /// 2. [Add](struct.CsrfFilterBuilder.html#method.allowed_origin) allowed
// /// origins.
// /// 3. Call [finish](struct.CsrfFilterBuilder.html#method.finish) to retrieve
// /// the constructed filter.
// ///
// /// # Example
// ///
// /// ```
// /// use actix_web::middleware::csrf;
// /// use actix_web::App;
// ///
// /// # fn main() {
// /// let app = App::new()
// /// .middleware(csrf::CsrfFilter::new().allowed_origin("https://www.example.com"));
// /// # }
// /// ```
// #[derive(Default)]
// pub struct CsrfFilter {
// origins: HashSet<String>,
// allow_xhr: bool,
// allow_missing_origin: bool,
// allow_upgrade: bool,
// }
// impl CsrfFilter {
// /// Start building a `CsrfFilter`.
// pub fn new() -> CsrfFilter {
// CsrfFilter {
// origins: HashSet::new(),
// allow_xhr: false,
// allow_missing_origin: false,
// allow_upgrade: false,
// }
// }
// /// Add an origin that is allowed to make requests. Will be verified
// /// against the `Origin` request header.
// pub fn allowed_origin<T: Into<String>>(mut self, origin: T) -> CsrfFilter {
// self.origins.insert(origin.into());
// self
// }
// /// Allow all requests with an `X-Requested-With` header.
// ///
// /// A cross-site attacker should not be able to send requests with custom
// /// headers unless a CORS policy whitelists them. Therefore it should be
// /// safe to allow requests with an `X-Requested-With` header (added
// /// automatically by many JavaScript libraries).
// ///
// /// This is disabled by default, because in Safari it is possible to
// /// circumvent this using redirects and Flash.
// ///
// /// Use this method to enable more lax filtering.
// pub fn allow_xhr(mut self) -> CsrfFilter {
// self.allow_xhr = true;
// self
// }
// /// Allow requests if the expected `Origin` header is missing (and
// /// there is no `Referer` to fall back on).
// ///
// /// The filter is conservative by default, but it should be safe to allow
// /// missing `Origin` headers because a cross-site attacker cannot prevent
// /// the browser from sending `Origin` on unprotected requests.
// pub fn allow_missing_origin(mut self) -> CsrfFilter {
// self.allow_missing_origin = true;
// self
// }
// /// Allow cross-site upgrade requests (for example to open a WebSocket).
// pub fn allow_upgrade(mut self) -> CsrfFilter {
// self.allow_upgrade = true;
// self
// }
// fn validate(&self, req: &Request) -> Result<(), CsrfError> {
// let is_upgrade = req.headers().contains_key(header::UPGRADE);
// let is_safe = req.method().is_safe() && (self.allow_upgrade || !is_upgrade);
// if is_safe || (self.allow_xhr && req.headers().contains_key("x-requested-with")) {
// Ok(())
// } else if let Some(header) = origin(req.headers()) {
// match header {
// Ok(ref origin) if self.origins.contains(origin.as_ref()) => Ok(()),
// Ok(_) => Err(CsrfError::CsrDenied),
// Err(err) => Err(err),
// }
// } else if self.allow_missing_origin {
// Ok(())
// } else {
// Err(CsrfError::MissingOrigin)
// }
// }
// }
// impl<S> Middleware<S> for CsrfFilter {
// fn start(&self, req: &HttpRequest<S>) -> Result<Started> {
// self.validate(req)?;
// Ok(Started::Done)
// }
// }
// #[cfg(test)]
// mod tests {
// use super::*;
// use http::Method;
// use test::TestRequest;
// #[test]
// fn test_safe() {
// let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
// let req = TestRequest::with_header("Origin", "https://www.w3.org")
// .method(Method::HEAD)
// .finish();
// assert!(csrf.start(&req).is_ok());
// }
// #[test]
// fn test_csrf() {
// let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
// let req = TestRequest::with_header("Origin", "https://www.w3.org")
// .method(Method::POST)
// .finish();
// assert!(csrf.start(&req).is_err());
// }
// #[test]
// fn test_referer() {
// let csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
// let req =
// TestRequest::with_header("Referer", "https://www.example.com/some/path?query=param")
// .method(Method::POST)
// .finish();
// assert!(csrf.start(&req).is_ok());
// }
// #[test]
// fn test_upgrade() {
// let strict_csrf = CsrfFilter::new().allowed_origin("https://www.example.com");
// let lax_csrf = CsrfFilter::new()
// .allowed_origin("https://www.example.com")
// .allow_upgrade();
// let req = TestRequest::with_header("Origin", "https://cswsh.com")
// .header("Connection", "Upgrade")
// .header("Upgrade", "websocket")
// .method(Method::GET)
// .finish();
// assert!(strict_csrf.start(&req).is_err());
// assert!(lax_csrf.start(&req).is_ok());
// }
// }

28
src/models.rs

@ -1,24 +1,4 @@
use serde::{Deserialize, Serialize};
use crate::schema::users;
use yarte::Template;
#[derive(Debug, Clone, Serialize, Queryable, Identifiable, Deserialize)]
pub struct User {
pub id: i32,
pub name: String,
}
#[derive(Debug, Clone, Serialize, Insertable, Deserialize)]
#[table_name = "users"]
pub struct NewUser {
pub name: String,
}
#[derive(Template)]
#[template(path = "hello.hbs")]
pub struct CardTemplate<'a> {
pub title: &'a str,
pub body: String,
pub num: u32,
}
pub mod users;
pub use self::users::*;
pub mod errors;
pub use self::errors::*;

13
src/models/errors.rs

@ -0,0 +1,13 @@
use serde::Serialize;
#[derive(Debug, Clone, Serialize)]
pub struct JsonErrorModel {
status_code: i16,
pub line: String,
pub reason: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct ErrorModel {
pub status_code: i16,
pub reason: String,
}

0
src/models/roles.rs

38
src/models/users.rs

@ -0,0 +1,38 @@
use serde::{Deserialize, Serialize};
use crate::schema::users;
use crate::utils::regexs;
use chrono;
use validator::Validate;
use validator_derive::*;
use yarte::Template;
#[derive(Debug, Clone, Serialize, Queryable, Identifiable, Deserialize, PartialEq)]
pub struct User {
pub id: i32,
pub name: String,
pub password: String,
pub created_at: chrono::NaiveDateTime,
}
#[derive(Debug, Clone, Serialize, Insertable, Deserialize, Validate)]
#[table_name = "users"]
pub struct NewUser {
#[validate(regex = "regexs::USERNAME_REG", length(min = 4, max = 10))]
pub name: String,
pub password: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, Queryable)]
pub struct UserDTO {
pub name: String,
pub registration_date: chrono::NaiveDateTime,
}
#[derive(Template)]
#[template(path = "hello.hbs")]
pub struct CardTemplate<'a> {
pub title: &'a str,
pub body: String,
pub num: u32,
}

2
src/routes.rs

@ -1,2 +1,4 @@
pub mod auth;
pub mod users;
pub use self::auth::*;
pub use self::users::*;

45
src/routes/auth.rs

@ -0,0 +1,45 @@
use actix_web_httpauth::extractors::basic::BasicAuth;
use actix_identity::Identity;
use actix_web::{get, post, Error, HttpResponse};
#[get("/login")]
pub async fn login(id: Identity, credentials: BasicAuth) -> Result<HttpResponse, Error> {
let maybe_identity = id.identity();
let response = if let Some(identity) = maybe_identity {
HttpResponse::Found()
.header("location", "/")
.content_type("text/plain")
.json(format!("Already logged in as {}", identity))
} else {
id.remember(credentials.user_id().to_string());
HttpResponse::Found().header("location", "/").finish()
};
println!("{}", credentials.user_id());
println!("{:?}", credentials.password());
Ok(response)
}
#[get("/logout")]
pub async fn logout(id: Identity, _credentials: BasicAuth) -> Result<HttpResponse, Error> {
let maybe_identity = id.identity();
let response = if let Some(identity) = maybe_identity {
info!("Logging out {user}", user = identity);
id.forget();
HttpResponse::Found().header("location", "/").finish()
} else {
HttpResponse::Found()
.header("location", "/")
.content_type("text/plain")
.json("Not logged in")
};
Ok(response)
}
#[get("/")]
pub async fn index(id: Identity) -> String {
format!(
"Hello {}",
id.identity().unwrap_or_else(|| "Anonymous".to_owned())
)
}

92
src/routes/users.rs

@ -1,76 +1,82 @@
use actix_web::{get, post, web, Error, HttpResponse};
use actix_web::{get, post, web, HttpResponse};
use crate::actions;
use crate::models;
use crate::types::DbPool;
use actix_web::error::ResponseError;
use std::rc::Rc;
/// Finds user by UID.
#[get("/api/authzd/users/get/{user_id}")]
pub async fn get_user(
pool: web::Data<DbPool>,
user_uid: web::Path<i32>,
) -> Result<HttpResponse, Error> {
) -> Result<HttpResponse, impl ResponseError> {
let user_uid = user_uid.into_inner();
// use web::block to offload blocking Diesel code without blocking server thread
let maybe_user = web::block(move || {
let conn = pool.get().map_err(|e| e.to_string())?;
actions::find_user_by_uid(user_uid.into(), &conn).map_err(|e| e.to_string())
let res = web::block(move || {
let conn = pool.get()?;
actions::find_user_by_uid(user_uid, &conn)
})
.await
.map_err(|e| {
error!("{}", e);
HttpResponse::InternalServerError().finish()
})?;
if let Some(user) = maybe_user {
Ok(HttpResponse::Ok().json(user))
} else {
let res = HttpResponse::NotFound().body(format!("No user found with uid: {}", user_uid));
Ok(res)
}
.and_then(|maybe_user| {
if let Some(user) = maybe_user {
Ok(HttpResponse::Ok().json(user))
} else {
let res =
HttpResponse::NotFound().body(format!("No user found with uid: {}", user_uid));
Ok(res)
}
});
res
}
#[get("/api/authzd/users/get")]
pub async fn get_all_users(pool: web::Data<DbPool>) -> Result<HttpResponse, Error> {
pub async fn get_all_users(pool: web::Data<DbPool>) -> Result<HttpResponse, impl ResponseError> {
// use web::block to offload blocking Diesel code without blocking server thread
let maybe_users = web::block(move || {
let conn = pool.get().map_err(|e| e.to_string())?;
actions::get_all(&conn).map_err(|e| e.to_string())
let res = web::block(move || {
let conn = pool.get()?;
actions::get_all(&conn)
})
.await
.map_err(|e| {
eprintln!("{}", e);
HttpResponse::InternalServerError().finish()
})?;
if let Some(users) = maybe_users {
Ok(HttpResponse::Ok().json(users))
} else {
let res = HttpResponse::NotFound().body(format!("No users available"));
Ok(res)
}
// Ok(HttpResponse::Ok().json(users))
.and_then(|maybe_users| {
debug!("{:?}", maybe_users);
if let Some(users) = maybe_users {
if users.is_empty() {
let res = HttpResponse::Ok().json(models::ErrorModel {
status_code: 200,
reason: "No users available".to_string(),
});
Ok(res)
} else {
Ok(HttpResponse::Ok().json(users))
}
} else {
let res = HttpResponse::Ok().json(models::ErrorModel {
status_code: 200,
reason: "No users available".to_string(),
});
Ok(res)
}
});
res
}
/// Inserts new user with name defined in form.
#[post("/api/authzd/users/post")]
#[post("/do_registration")]
pub async fn add_user(
pool: web::Data<DbPool>,
form: web::Json<models::NewUser>,
) -> Result<HttpResponse, Error> {
) -> Result<HttpResponse, impl ResponseError> {
// use web::block to offload blocking Diesel code without blocking server thread
let user = web::block(move || {
let conn = pool.get().map_err(|e| e.to_string())?;
actions::insert_new_user(&form, &conn).map_err(|e| e.to_string())
let conn = pool.get()?;
actions::insert_new_user(Rc::new(form.0), &conn)
})
.await
.map(|user| {
.and_then(|user| {
debug!("{:?}", user);
Ok(HttpResponse::Ok().json(user))
})
.map_err(|e| {
eprintln!("{}", e);
HttpResponse::InternalServerError().finish()
})?;
Ok(HttpResponse::Created().json(user))
});
user
}

2
src/schema.rs

@ -2,5 +2,7 @@ table! {
users (id) {
id -> Integer,
name -> Text,
password -> Text,
created_at -> Timestamp,
}
}

4
src/utils.rs

@ -0,0 +1,4 @@
pub mod auth;
pub mod regexs;
pub use self::auth::*;
pub use self::regexs::*;

14
src/utils/auth.rs

@ -0,0 +1,14 @@
use actix_web_httpauth::extractors::basic::BasicAuth;
// use actix_identity::Identity;
use actix_web::{dev::ServiceRequest, Error};
pub async fn validator(
req: ServiceRequest,
credentials: BasicAuth,
) -> Result<ServiceRequest, Error> {
println!("{}", credentials.user_id());
println!("{:?}", credentials.password());
// verify credentials from db
Ok(req)
}

5
src/utils/regexs.rs

@ -0,0 +1,5 @@
use lazy_static::lazy_static;
use regex::Regex;
lazy_static! {
pub static ref USERNAME_REG: Regex = Regex::new(r"^([a-z\d]+-)*[a-z\d]+$").unwrap();
}

BIN
test.db

Loading…
Cancel
Save