Compare commits

...

34 Commits

Author SHA1 Message Date
be053ac0dc tsk-56: Added API docs
All checks were successful
Rust Build / Check (pull_request) Successful in 2m3s
Rust Build / Test Suite (pull_request) Successful in 2m54s
Rust Build / Rustfmt (pull_request) Successful in 29s
Rust Build / Clippy (pull_request) Successful in 2m2s
Rust Build / build (pull_request) Successful in 3m36s
2025-08-24 19:02:20 -04:00
d4ec3c5d1c tsk-56: Added utoipa Swagger crates 2025-08-24 15:50:40 -04:00
50f517140e tsk-56: Dependency version brump
icarus_models and icarus_envy version bump
2025-08-24 15:48:39 -04:00
a4c943189c Next release (#32)
All checks were successful
Rust Build / Rustfmt (push) Successful in 29s
Rust Build / Test Suite (push) Successful in 48s
Rust Build / Check (push) Successful in 57s
Rust Build / Clippy (push) Successful in 34s
Rust Build / build (push) Successful in 1m2s
Reviewed-on: #32
2025-08-12 21:46:01 +00:00
eb1e2990f9 tsk-51: Refresh token endpoint (#54)
All checks were successful
Rust Build / Check (push) Successful in 46s
Rust Build / Test Suite (push) Successful in 57s
Release Tagging / release (push) Successful in 59s
Rust Build / Rustfmt (push) Successful in 37s
Rust Build / Clippy (push) Successful in 48s
Rust Build / Check (pull_request) Successful in 45s
Rust Build / build (push) Successful in 59s
Rust Build / Rustfmt (pull_request) Successful in 38s
Rust Build / Clippy (pull_request) Successful in 43s
Rust Build / Test Suite (pull_request) Successful in 54s
Rust Build / build (pull_request) Successful in 59s
Closes #51

Reviewed-on: #54
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-08-11 22:15:17 +00:00
99390ce8b7 tsk-50: Create Special endpoint for services to obtain a token (#53)
All checks were successful
Rust Build / Check (push) Successful in 32s
Release Tagging / release (push) Successful in 51s
Rust Build / Rustfmt (push) Successful in 25s
Rust Build / build (push) Successful in 42s
Rust Build / Check (pull_request) Successful in 36s
Rust Build / Clippy (push) Successful in 2m2s
Rust Build / Test Suite (pull_request) Successful in 42s
Rust Build / Rustfmt (pull_request) Successful in 32s
Rust Build / Clippy (pull_request) Successful in 37s
Rust Build / Test Suite (push) Successful in 3m57s
Rust Build / build (pull_request) Successful in 3m14s
Reviewed-on: #53
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-08-03 23:09:50 +00:00
5967ed5b13 minor refactoring (#52)
All checks were successful
Release Tagging / release (push) Successful in 35s
Rust Build / Check (push) Successful in 37s
Rust Build / Rustfmt (push) Successful in 27s
Rust Build / Test Suite (push) Successful in 41s
Rust Build / build (push) Successful in 46s
Rust Build / Clippy (push) Successful in 1m46s
Rust Build / Check (pull_request) Successful in 37s
Rust Build / Rustfmt (pull_request) Successful in 28s
Rust Build / Test Suite (pull_request) Successful in 39s
Rust Build / build (pull_request) Successful in 45s
Rust Build / Clippy (pull_request) Successful in 1m44s
Reviewed-on: #52
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-08-01 20:49:15 +00:00
be4d1109a7 Update rust in docker (#48)
Some checks failed
Release Tagging / release (push) Successful in 29s
Rust Build / Check (push) Successful in 36s
Rust Build / Test Suite (push) Successful in 45s
Rust Build / Rustfmt (push) Successful in 27s
Rust Build / Clippy (push) Successful in 35s
Rust Build / build (push) Successful in 59s
Rust Build / Check (pull_request) Failing after 44s
Rust Build / Test Suite (pull_request) Failing after 1m17s
Rust Build / Rustfmt (pull_request) Failing after 39s
Rust Build / Clippy (pull_request) Failing after 38s
Rust Build / build (pull_request) Failing after 37s
Reviewed-on: #48
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-07-13 23:01:16 +00:00
4353414c69 Upgrade postgresql (#47)
All checks were successful
Rust Build / Check (push) Successful in 36s
Rust Build / Test Suite (push) Successful in 49s
Rust Build / Rustfmt (push) Successful in 29s
Rust Build / Clippy (push) Successful in 40s
Rust Build / build (push) Successful in 1m4s
Release Tagging / release (push) Successful in 30s
Rust Build / Check (pull_request) Successful in 39s
Rust Build / Test Suite (pull_request) Successful in 46s
Rust Build / Rustfmt (pull_request) Successful in 25s
Rust Build / Clippy (pull_request) Successful in 37s
Rust Build / build (pull_request) Successful in 56s
Reviewed-on: #47

Closes #46

Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-07-12 19:32:56 +00:00
c176d0fcf3 Version bump (#45)
All checks were successful
Release Tagging / release (push) Successful in 31s
Rust Build / Check (push) Successful in 40s
Rust Build / Test Suite (push) Successful in 52s
Rust Build / Rustfmt (push) Successful in 25s
Rust Build / Clippy (push) Successful in 41s
Rust Build / build (push) Successful in 1m2s
Rust Build / Check (pull_request) Successful in 2m18s
Rust Build / Test Suite (pull_request) Successful in 2m29s
Rust Build / Rustfmt (pull_request) Successful in 26s
Rust Build / Clippy (pull_request) Successful in 2m23s
Rust Build / build (pull_request) Successful in 4m51s
Reviewed-on: #45
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-06-29 20:50:55 +00:00
c8b8d470dc Refactoring (#44)
All checks were successful
Release Tagging / release (push) Successful in 28s
Rust Build / Check (push) Successful in 37s
Rust Build / Test Suite (push) Successful in 47s
Rust Build / Rustfmt (push) Successful in 25s
Rust Build / Clippy (push) Successful in 39s
Rust Build / build (push) Successful in 1m2s
Rust Build / Check (pull_request) Successful in 38s
Rust Build / Test Suite (pull_request) Successful in 50s
Rust Build / Rustfmt (pull_request) Successful in 27s
Rust Build / Clippy (pull_request) Successful in 40s
Rust Build / build (pull_request) Successful in 1m6s
Reviewed-on: #44
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-06-29 20:39:04 +00:00
bcd0e607ef Update dependencies (#43)
Some checks failed
Release Tagging / release (push) Successful in 31s
Rust Build / Check (push) Successful in 36s
Rust Build / Rustfmt (push) Successful in 25s
Rust Build / Clippy (push) Failing after 39s
Rust Build / Check (pull_request) Successful in 40s
Rust Build / Test Suite (pull_request) Successful in 52s
Rust Build / build (pull_request) Successful in 1m6s
Rust Build / Test Suite (push) Successful in 47s
Rust Build / build (push) Successful in 1m2s
Rust Build / Rustfmt (pull_request) Successful in 27s
Rust Build / Clippy (pull_request) Failing after 41s
Reviewed-on: #43
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-06-29 20:26:58 +00:00
70de6b862f rust std change (#42)
Some checks failed
Release Tagging / release (push) Successful in 31s
Rust Build / Check (push) Successful in 39s
Rust Build / Test Suite (push) Successful in 52s
Rust Build / Rustfmt (push) Successful in 27s
Rust Build / Clippy (push) Failing after 40s
Rust Build / build (push) Successful in 59s
Rust Build / Check (pull_request) Successful in 38s
Rust Build / Test Suite (pull_request) Successful in 51s
Rust Build / Rustfmt (pull_request) Successful in 27s
Rust Build / Clippy (pull_request) Failing after 40s
Rust Build / build (pull_request) Successful in 1m6s
Reviewed-on: #42
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-06-29 20:03:41 +00:00
8c902b9d61 Version bump (#41)
All checks were successful
Release Tagging / release (push) Successful in 27s
Rust Build / Test Suite (push) Successful in 56s
Rust Build / Rustfmt (push) Successful in 22s
Rust Build / Test Suite (pull_request) Successful in 54s
Rust Build / build (pull_request) Successful in 1m9s
Rust Build / Check (push) Successful in 40s
Rust Build / Clippy (push) Successful in 41s
Rust Build / build (push) Successful in 1m7s
Rust Build / Check (pull_request) Successful in 41s
Rust Build / Rustfmt (pull_request) Successful in 24s
Rust Build / Clippy (pull_request) Successful in 43s
Reviewed-on: #41
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-06-01 23:41:05 +00:00
480a428e8b Including cargo.lock in source control (#40)
All checks were successful
Release Tagging / release (push) Successful in 27s
Rust Build / Check (push) Successful in 40s
Rust Build / Test Suite (push) Successful in 57s
Rust Build / Rustfmt (push) Successful in 23s
Rust Build / Clippy (push) Successful in 44s
Rust Build / build (push) Successful in 1m9s
Rust Build / Check (pull_request) Successful in 40s
Rust Build / Test Suite (pull_request) Successful in 57s
Rust Build / Rustfmt (pull_request) Successful in 23s
Rust Build / Clippy (pull_request) Successful in 44s
Rust Build / build (pull_request) Successful in 1m9s
Reviewed-on: #40
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-06-01 23:23:24 +00:00
02697b2fd9 Adding icarus_envy (#39)
All checks were successful
Release Tagging / release (push) Successful in 28s
Rust Build / Check (push) Successful in 36s
Rust Build / Test Suite (push) Successful in 50s
Rust Build / Rustfmt (push) Successful in 23s
Rust Build / Clippy (push) Successful in 44s
Rust Build / build (push) Successful in 1m3s
Rust Build / Check (pull_request) Successful in 38s
Rust Build / Test Suite (pull_request) Successful in 49s
Rust Build / Rustfmt (pull_request) Successful in 22s
Rust Build / Clippy (pull_request) Successful in 39s
Rust Build / build (pull_request) Successful in 1m0s
Reviewed-on: #39
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-06-01 23:02:09 +00:00
d4faa7976e Update icarus_models (#38)
All checks were successful
Release Tagging / release (push) Successful in 25s
Rust Build / Check (push) Successful in 35s
Rust Build / Test Suite (push) Successful in 43s
Rust Build / Rustfmt (push) Successful in 22s
Rust Build / Clippy (push) Successful in 35s
Rust Build / build (push) Successful in 53s
Rust Build / Check (pull_request) Successful in 55s
Rust Build / Test Suite (pull_request) Successful in 1m6s
Rust Build / Rustfmt (pull_request) Successful in 21s
Rust Build / Clippy (pull_request) Successful in 52s
Rust Build / build (pull_request) Successful in 1m48s
Reviewed-on: #38
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-05-28 23:26:17 +00:00
ed77cab700 Environment and docker changes (#37)
All checks were successful
Release Tagging / release (push) Successful in 33s
Rust Build / Check (push) Successful in 38s
Rust Build / Test Suite (push) Successful in 49s
Rust Build / Rustfmt (push) Successful in 25s
Rust Build / Clippy (push) Successful in 36s
Rust Build / build (push) Successful in 1m6s
Rust Build / Check (pull_request) Successful in 39s
Rust Build / Test Suite (pull_request) Successful in 47s
Rust Build / Rustfmt (pull_request) Successful in 22s
Rust Build / Clippy (pull_request) Successful in 35s
Rust Build / build (pull_request) Successful in 54s
Reviewed-on: #37
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-05-27 20:55:53 +00:00
2c30abb5c6 Updated gitignore (#36)
All checks were successful
Release Tagging / release (push) Successful in 33s
Rust Build / Check (push) Successful in 46s
Rust Build / Test Suite (push) Successful in 55s
Rust Build / Rustfmt (push) Successful in 27s
Rust Build / Clippy (push) Successful in 41s
Rust Build / build (push) Successful in 1m5s
Rust Build / Check (pull_request) Successful in 39s
Rust Build / Test Suite (pull_request) Successful in 53s
Rust Build / Rustfmt (pull_request) Successful in 25s
Rust Build / Clippy (pull_request) Successful in 39s
Rust Build / build (pull_request) Successful in 1m1s
Reviewed-on: #36
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-05-27 20:23:50 +00:00
1817ab01d6 Test fix (#35)
All checks were successful
Release Tagging / release (push) Successful in 32s
Rust Build / Check (push) Successful in 46s
Rust Build / Test Suite (push) Successful in 57s
Rust Build / Rustfmt (push) Successful in 34s
Rust Build / Clippy (push) Successful in 47s
Rust Build / build (push) Successful in 1m18s
Rust Build / Check (pull_request) Successful in 46s
Rust Build / Test Suite (pull_request) Successful in 1m3s
Rust Build / Rustfmt (pull_request) Successful in 27s
Rust Build / Clippy (pull_request) Successful in 46s
Rust Build / build (pull_request) Successful in 1m24s
Reviewed-on: #35
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-04-27 18:06:13 +00:00
31be156be3 Added ssh default for building docker image (#33)
All checks were successful
Rust Build / Check (push) Successful in 46s
Rust Build / Test Suite (push) Successful in 1m3s
Rust Build / Rustfmt (push) Successful in 28s
Rust Build / Clippy (push) Successful in 46s
Rust Build / build (push) Successful in 1m16s
Rust Build / Check (pull_request) Successful in 53s
Rust Build / Test Suite (pull_request) Successful in 1m0s
Rust Build / Rustfmt (pull_request) Successful in 27s
Rust Build / Clippy (pull_request) Successful in 52s
Rust Build / build (pull_request) Successful in 1m15s
Release Tagging / release (push) Successful in 43s
Reviewed-on: #33
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-04-27 16:31:11 +00:00
fc6b66f2e6 Docker changes (#31)
All checks were successful
Release Tagging / release (push) Successful in 34s
Rust Build / Check (push) Successful in 49s
Rust Build / Test Suite (push) Successful in 1m18s
Rust Build / Rustfmt (push) Successful in 33s
Rust Build / Clippy (push) Successful in 52s
Rust Build / build (push) Successful in 1m30s
Rust Build / Check (pull_request) Successful in 58s
Rust Build / Test Suite (pull_request) Successful in 1m8s
Rust Build / Rustfmt (pull_request) Successful in 32s
Rust Build / Clippy (pull_request) Successful in 52s
Rust Build / build (pull_request) Successful in 1m19s
Reviewed-on: #31
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-04-13 18:38:38 +00:00
eb7e394cf0 Next release (#27)
All checks were successful
Rust Build / Check (push) Successful in 53s
Rust Build / Test Suite (push) Successful in 1m2s
Rust Build / Rustfmt (push) Successful in 32s
Rust Build / Clippy (push) Successful in 59s
Rust Build / build (push) Successful in 1m20s
Reviewed-on: #27
2025-04-12 00:27:47 +00:00
6dec9942cc Version bump (#29)
All checks were successful
Release Tagging / release (push) Successful in 37s
Rust Build / Check (push) Successful in 57s
Rust Build / Test Suite (push) Successful in 1m14s
Rust Build / Rustfmt (push) Successful in 29s
Rust Build / Clippy (push) Successful in 1m5s
Rust Build / build (push) Successful in 1m22s
Rust Build / Check (pull_request) Successful in 57s
Rust Build / Test Suite (pull_request) Successful in 1m15s
Rust Build / Rustfmt (pull_request) Successful in 39s
Rust Build / Clippy (pull_request) Successful in 55s
Rust Build / build (pull_request) Successful in 1m40s
Reviewed-on: #29
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-04-12 00:16:34 +00:00
a855db9ecc Workflow changes (#30)
Some checks failed
Rust Build / Check (push) Successful in 49s
Rust Build / Test Suite (push) Successful in 1m36s
Rust Build / Rustfmt (push) Successful in 29s
Rust Build / Clippy (push) Successful in 52s
Rust Build / build (push) Successful in 1m33s
Rust Build / Check (pull_request) Successful in 1m1s
Rust Build / Test Suite (pull_request) Successful in 1m17s
Rust Build / Rustfmt (pull_request) Successful in 36s
Rust Build / Clippy (pull_request) Successful in 53s
Rust Build / build (pull_request) Successful in 1m31s
Release Tagging / release (push) Failing after 38s
Reviewed-on: #30
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-04-11 23:57:37 +00:00
17af1a00c0 Add docker (#28)
All checks were successful
Release Tagging / release (push) Successful in 36s
Rust Build / Check (push) Successful in 48s
Rust Build / Test Suite (push) Successful in 1m9s
Rust Build / Rustfmt (push) Successful in 29s
Rust Build / Clippy (push) Successful in 56s
Rust Build / build (push) Successful in 1m22s
Rust Build / Check (pull_request) Successful in 47s
Rust Build / Test Suite (pull_request) Successful in 1m2s
Rust Build / Rustfmt (pull_request) Successful in 35s
Rust Build / Clippy (pull_request) Successful in 52s
Rust Build / build (pull_request) Successful in 1m24s
Reviewed-on: #28
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-04-11 01:07:20 +00:00
50e735e1a9 Update last_login of user (#26)
All checks were successful
Release Tagging / release (push) Successful in 55s
Rust Build / Check (pull_request) Successful in 58s
Rust Build / Test Suite (pull_request) Successful in 1m3s
Rust Build / Rustfmt (pull_request) Successful in 33s
Rust Build / Clippy (pull_request) Successful in 50s
Rust Build / build (pull_request) Successful in 1m18s
Rust Build / Check (push) Successful in 44s
Rust Build / Test Suite (push) Successful in 57s
Rust Build / Rustfmt (push) Successful in 31s
Rust Build / Clippy (push) Successful in 46s
Rust Build / build (push) Successful in 1m11s
Reviewed-on: #26
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-04-11 01:01:18 +00:00
f6cf968f86 main merge (#25)
All checks were successful
Release Tagging / release (push) Successful in 35s
Rust Build / Check (push) Successful in 49s
Rust Build / Test Suite (push) Successful in 1m8s
Rust Build / Rustfmt (push) Successful in 29s
Rust Build / Clippy (push) Successful in 1m0s
Rust Build / build (push) Successful in 1m24s
Rust Build / Check (pull_request) Successful in 48s
Rust Build / Test Suite (pull_request) Successful in 1m5s
Rust Build / Rustfmt (pull_request) Successful in 29s
Rust Build / Clippy (pull_request) Successful in 50s
Rust Build / build (pull_request) Successful in 1m19s
Reviewed-on: #25
2025-04-10 22:46:29 +00:00
70a547ca94 Next release (#23)
All checks were successful
Rust Build / Check (push) Successful in 45s
Rust Build / Test Suite (push) Successful in 1m12s
Rust Build / Rustfmt (push) Successful in 27s
Rust Build / Clippy (push) Successful in 1m1s
Rust Build / build (push) Successful in 1m38s
Release Tagging / release (push) Successful in 40s
Rust Build / Check (pull_request) Successful in 55s
Rust Build / Clippy (pull_request) Successful in 1m10s
Rust Build / build (pull_request) Successful in 1m38s
Rust Build / Test Suite (pull_request) Successful in 1m12s
Rust Build / Rustfmt (pull_request) Successful in 38s
Reviewed-on: #23
2025-04-08 23:42:04 +00:00
89c89a5524 Login endpoint bug fix (#24)
All checks were successful
Release Tagging / release (push) Successful in 46s
Rust Build / Check (push) Successful in 57s
Rust Build / Test Suite (push) Successful in 1m0s
Rust Build / Rustfmt (push) Successful in 35s
Rust Build / Clippy (push) Successful in 50s
Rust Build / build (push) Successful in 1m20s
Rust Build / Check (pull_request) Successful in 50s
Rust Build / Test Suite (pull_request) Successful in 1m7s
Rust Build / Rustfmt (pull_request) Successful in 29s
Rust Build / Clippy (pull_request) Successful in 1m7s
Rust Build / build (pull_request) Successful in 1m31s
Reviewed-on: #24
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-04-07 20:15:58 +00:00
a58b0cb40b Changes to token (#21)
All checks were successful
Release Tagging / release (push) Successful in 31s
Rust Build / Check (push) Successful in 43s
Rust Build / Clippy (push) Successful in 48s
Rust Build / build (push) Successful in 1m17s
Rust Build / Test Suite (push) Successful in 1m0s
Rust Build / Rustfmt (push) Successful in 27s
Rust Build / Check (pull_request) Successful in 42s
Rust Build / Test Suite (pull_request) Successful in 57s
Rust Build / Rustfmt (pull_request) Successful in 28s
Rust Build / Clippy (pull_request) Successful in 44s
Rust Build / build (pull_request) Successful in 1m11s
Reviewed-on: #21
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-04-07 17:35:47 +00:00
f601442f0e Release with login functionality (#19)
All checks were successful
Rust Build / Check (push) Successful in 46s
Rust Build / Test Suite (push) Successful in 55s
Rust Build / Rustfmt (push) Successful in 28s
Rust Build / Clippy (push) Successful in 46s
Rust Build / build (push) Successful in 1m10s
Release Tagging / release (push) Successful in 35s
Reviewed-on: #19
2025-04-07 02:04:46 +00:00
3424d31151 Login endpoint (#20)
All checks were successful
Release Tagging / release (push) Successful in 32s
Rust Build / Check (push) Successful in 45s
Rust Build / Test Suite (push) Successful in 58s
Rust Build / Rustfmt (push) Successful in 30s
Rust Build / Clippy (push) Successful in 48s
Rust Build / build (push) Successful in 1m9s
Rust Build / Check (pull_request) Successful in 43s
Rust Build / Test Suite (pull_request) Successful in 1m1s
Rust Build / Rustfmt (pull_request) Successful in 25s
Rust Build / Clippy (pull_request) Successful in 47s
Rust Build / build (pull_request) Successful in 1m9s
Reviewed-on: #20
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-04-07 01:22:57 +00:00
332e9d3378 remove_test_dependency (#18)
All checks were successful
Release Tagging / release (push) Successful in 34s
Rust Build / Check (push) Successful in 44s
Rust Build / Test Suite (push) Successful in 56s
Rust Build / Rustfmt (push) Successful in 27s
Rust Build / Clippy (push) Successful in 41s
Rust Build / build (push) Successful in 1m5s
Rust Build / Check (pull_request) Successful in 44s
Rust Build / Test Suite (pull_request) Successful in 59s
Rust Build / Rustfmt (pull_request) Successful in 26s
Rust Build / Clippy (pull_request) Successful in 45s
Rust Build / build (pull_request) Successful in 1m8s
Reviewed-on: #18
Co-authored-by: phoenix <kundeng94@gmail.com>
Co-committed-by: phoenix <kundeng94@gmail.com>
2025-04-05 20:23:56 +00:00
23 changed files with 4012 additions and 160 deletions

21
.dockerignore.yaml Normal file
View File

@@ -0,0 +1,21 @@
# Ignore build artifacts
target/
pkg/
# Ignore git directory
.git/
.gitea/
# Ignore environment files (configure via docker-compose instead)
.env*
# Ignore IDE/editor specific files
.idea/
.vscode/
# Ignore OS specific files
*.DS_Store
# Add any other files/directories you don't need in the image
# e.g., logs/, tmp/

7
.env.docker.sample Normal file
View File

@@ -0,0 +1,7 @@
SECRET_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h
SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH
POSTGRES_AUTH_USER=icarus_op
POSTGRES_AUTH_PASSWORD=password
POSTGRES_AUTH_DB=icarus_auth_db
POSTGRES_AUTH_HOST=auth_db
DATABASE_URL=postgresql://${POSTGRES_AUTH_USER}:${POSTGRES_AUTH_PASSWORD}@${POSTGRES_AUTH_HOST}:5432/${POSTGRES_AUTH_DB}

View File

@@ -1 +1,7 @@
DATABASE_URL=postgres://username:password@localhost/database_name
SECRET_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h
SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH
POSTGRES_AUTH_USER=icarus_op_test
POSTGRES_AUTH_PASSWORD=password
POSTGRES_AUTH_DB=icarus_auth_test_db
POSTGRES_AUTH_HOST=localhost
DATABASE_URL=postgresql://${POSTGRES_AUTH_USER}:${POSTGRES_AUTH_PASSWORD}@${POSTGRES_AUTH_HOST}:5432/${POSTGRES_AUTH_DB}

View File

@@ -4,8 +4,6 @@ on:
push:
branches:
- devel
tags:
- 'v*' # Trigger on tags matching v*
jobs:
release:
@@ -19,7 +17,7 @@ jobs:
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: 1.86.0
toolchain: 1.88.0
components: cargo
- name: Extract Version from Cargo.toml
@@ -51,7 +49,3 @@ jobs:
release_name: Release ${{ steps.version.outputs.project_tag_release }}
body: |
Release of version ${{ steps.version.outputs.project_tag_release }}
# draft: false
# prerelease: ${{ startsWith(github.ref, 'v') == false }} # prerelease if not a valid release tag

View File

@@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.86.0
toolchain: 1.88.0
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
@@ -36,7 +36,7 @@ jobs:
# --- Add database service definition ---
services:
postgres:
image: postgres:17.4 # Or pin to a more specific version like 14.9
image: postgres:17.5
env:
# Use secrets for DB init, with fallbacks for flexibility
POSTGRES_USER: ${{ secrets.DB_TEST_USER || 'testuser' }}
@@ -53,7 +53,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.86.0
toolchain: 1.88.0
# --- Add this step for explicit verification ---
- name: Verify Docker Environment
run: |
@@ -73,6 +73,7 @@ jobs:
# Define DATABASE_URL for tests to use
DATABASE_URL: postgresql://${{ secrets.DB_TEST_USER || 'testuser' }}:${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}@postgres:5432/${{ secrets.DB_TEST_NAME || 'testdb' }}
RUST_LOG: info # Optional: configure test log level
SECRET_KEY: ${{ secrets.TOKEN_SECRET_KEY }}
# Make SSH agent available if tests fetch private dependencies
SSH_AUTH_SOCK: ${{ env.SSH_AUTH_SOCK }}
run: |
@@ -93,7 +94,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.86.0
toolchain: 1.88.0
- run: rustup component add rustfmt
- run: |
mkdir -p ~/.ssh
@@ -112,7 +113,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.86.0
toolchain: 1.88.0
- run: rustup component add clippy
- run: |
mkdir -p ~/.ssh
@@ -131,7 +132,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions-rust-lang/setup-rust-toolchain@v1
with:
toolchain: 1.86.0
toolchain: 1.88.0
- run: |
mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
/target
Cargo.lock
.env
.env.local
.env.docker

2804
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,27 +1,29 @@
[package]
name = "icarus_auth"
version = "0.2.0"
version = "0.4.3"
edition = "2024"
rust-version = "1.86"
rust-version = "1.88"
[dependencies]
axum = { version = "0.8.3" }
serde = { version = "1.0.218", features = ["derive"] }
serde_json = { version = "1.0.139" }
tokio = { version = "1.44.1", features = ["rt-multi-thread"] }
axum = { version = "0.8.4" }
serde = { version = "1.0.219", features = ["derive"] }
serde_json = { version = "1.0.140" }
tokio = { version = "1.45.1", features = ["rt-multi-thread"] }
tracing-subscriber = { version = "0.3.19" }
tower = { version = "0.5.2" }
hyper = { version = "1.6.0" }
sqlx = { version = "0.8.3", features = ["postgres", "runtime-tokio-native-tls", "time", "uuid"] }
dotenvy = { version = "0.15.7" }
uuid = { version = "1.16.0", features = ["v4", "serde"] }
sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio-native-tls", "time", "uuid"] }
uuid = { version = "1.17.0", features = ["v4", "serde"] }
argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x version
rand = { version = "0.9" }
rand = { version = "0.9.1" }
time = { version = "0.3.41", features = ["macros", "serde"] }
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.4.0" }
josekit = { version = "0.10.3" }
utoipa = { version = "5.4.0", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.5.6-58-13b030bbca-111" }
icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.3.2" }
[dev-dependencies]
http-body-util = { version = "0.1.3" }
url = { version = "2.5" }
reqwest = { version = "0.12.5", features = ["json"] } # For making HTTP requests in tests
once_cell = { version = "1.19" } # Useful for lazy initialization in tests/app setup
url = { version = "2.5.4" }
once_cell = { version = "1.21.3" } # Useful for lazy initialization in tests/app setup

71
Dockerfile Normal file
View File

@@ -0,0 +1,71 @@
# Stage 1: Build the application
# Use a specific Rust version for reproducibility. Choose one that matches your development environment.
# Using slim variant for smaller base image
FROM rust:1.88 as builder
# Set the working directory inside the container
WORKDIR /usr/src/app
# Install build dependencies if needed (e.g., for certain crates like sqlx with native TLS)
# RUN apt-get update && apt-get install -y pkg-config libssl-dev
# Install build dependencies if needed (e.g., git for cloning)
RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config libssl3 \
ca-certificates \
openssh-client git \
&& rm -rf /var/lib/apt/lists/*
# << --- ADD HOST KEY HERE --- >>
# Replace 'yourgithost.com' with the actual hostname (e.g., github.com)
RUN mkdir -p -m 0700 ~/.ssh && \
ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts
# Copy Cargo manifests
COPY Cargo.toml Cargo.lock ./
# Build *only* dependencies to leverage Docker cache
# This dummy build caches dependencies as a separate layer
RUN --mount=type=ssh mkdir src && \
echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs && \
cargo build --release --quiet && \
rm -rf src target/release/deps/icarus_auth* # Clean up dummy build artifacts (replace icarus_auth)
# Copy the actual source code
COPY src ./src
# If you have other directories like `templates` or `static`, copy them too
COPY .env ./.env
COPY migrations ./migrations
# << --- SSH MOUNT ADDED HERE --- >>
# Build *only* dependencies to leverage Docker cache
# This dummy build caches dependencies as a separate layer
# Mount the SSH agent socket for this command
RUN --mount=type=ssh \
cargo build --release --quiet
# Stage 2: Create the final, smaller runtime image
# Use a minimal base image like debian-slim or even distroless for security/size
FROM ubuntu:24.04
# Install runtime dependencies if needed (e.g., SSL certificates)
RUN apt-get update && apt-get install -y ca-certificates libssl-dev libssl3 && rm -rf /var/lib/apt/lists/*
# Set the working directory
WORKDIR /usr/local/bin
# Copy the compiled binary from the builder stage
# Replace 'icarus_auth' with the actual name of your binary (usually the crate name)
COPY --from=builder /usr/src/app/target/release/icarus_auth .
# Copy other necessary files like .env (if used for runtime config) or static assets
# It's generally better to configure via environment variables in Docker though
COPY --from=builder /usr/src/app/.env .
COPY --from=builder /usr/src/app/migrations ./migrations
# Expose the port your Axum app listens on (e.g., 3000 or 8000)
EXPOSE 3000
# Set the command to run your application
# Ensure this matches the binary name copied above
CMD ["./icarus_auth"]

26
README.md Normal file
View File

@@ -0,0 +1,26 @@
# Getting Started
Copy the `.env.sample` file to `.env` and ensure that the variables are populated. This project
can be used with regular hosting or with docker. For the sake of getting up to speed quickly,
Docker will be covered. Make sure docker is running and your ssh identity has been loaded.
Build image
```
docker compose build
```
Start images
```
docker compose up -d --force-recreate
```
Bring it down
```
docker compose down -v
```
Pruning
```
docker system prune -a
```

45
docker-compose.yaml Normal file
View File

@@ -0,0 +1,45 @@
version: '3.8' # Use a recent version
services:
# Your Rust Application Service
auth_api:
build: # Tells docker-compose to build the Dockerfile in the current directory
context: .
ssh: ["default"] # Uses host's SSH agent
container_name: icarus_auth # Optional: Give the container a specific name
ports:
# Map host port 8000 to container port 3000 (adjust as needed)
- "8000:3000"
env_file:
- .env
depends_on:
auth_db:
condition: service_healthy # Wait for the DB to be healthy before starting the app
restart: unless-stopped # Optional: Restart policy
# PostgreSQL Database Service
auth_db:
image: postgres:17.5-alpine # Use an official Postgres image (Alpine variant is smaller)
container_name: icarus_auth_db # Optional: Give the container a specific name
environment:
# These MUST match the user, password, and database name in the DATABASE_URL above
POSTGRES_USER: ${POSTGRES_AUTH_USER:-icarus_op}
POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password}
POSTGRES_DB: ${POSTGRES_AUTH_DB:-icarus_auth_db}
volumes:
# Persist database data using a named volume
- postgres_data:/var/lib/postgresql/data
ports: []
healthcheck:
# Checks if Postgres is ready to accept connections
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
start_period: 10s
restart: always # Optional: Restart policy
# Define the named volume for data persistence
volumes:
postgres_data:
driver: local # Use the default local driver

View File

@@ -20,3 +20,9 @@ CREATE TABLE IF NOT EXISTS "salt" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
salt TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS "passphrase" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
passphrase TEXT NOT NULL,
date_created TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

View File

@@ -0,0 +1,2 @@
-- Add migration script here
INSERT INTO "passphrase" (id, passphrase) VALUES('22f9c775-cce9-457a-a147-9dafbb801f61', 'iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH');

View File

@@ -1,3 +1,5 @@
TODO: At some point, move this somewhere that is appropriate
# Make sure role has CREATEDB
ALTER ROLE username_that_needs_permission CREATEDB;

View File

@@ -1,30 +1,54 @@
use axum::{Extension, Json, http::StatusCode};
pub mod response {
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
pub struct TestResult {
message: String,
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct TestResult {
pub message: String,
}
}
// basic handler that responds with a static string
pub async fn root() -> &'static str {
pub mod endpoint {
use super::*;
use axum::{Extension, Json, http::StatusCode};
/// Endpoint to hit the root
/// basic handler that responds with a static string
#[utoipa::path(
get,
path = super::super::endpoints::ROOT,
responses(
(status = 200, description = "Test", body = &str),
)
)]
pub async fn root() -> &'static str {
"Hello, World!"
}
}
pub async fn db_ping(Extension(pool): Extension<sqlx::PgPool>) -> (StatusCode, Json<TestResult>) {
/// Endpoint to do a database ping
#[utoipa::path(
get,
path = super::super::endpoints::DBTEST,
responses(
(status = 200, description = "Successful ping of the db", body = super::response::TestResult),
(status = 400, description = "Failure in pinging the db", body = super::response::TestResult)
)
)]
pub async fn db_ping(
Extension(pool): Extension<sqlx::PgPool>,
) -> (StatusCode, Json<response::TestResult>) {
match sqlx::query("SELECT 1").execute(&pool).await {
Ok(_) => {
let tr = TestResult {
let tr = response::TestResult {
message: String::from("This works"),
};
(StatusCode::OK, Json(tr))
}
Err(e) => (
StatusCode::BAD_REQUEST,
Json(TestResult {
Json(response::TestResult {
message: e.to_string(),
}),
),
}
}
}

268
src/callers/login.rs Normal file
View File

@@ -0,0 +1,268 @@
pub mod request {
use serde::{Deserialize, Serialize};
#[derive(Default, Deserialize, Serialize, utoipa::ToSchema)]
pub struct Request {
pub username: String,
pub password: String,
}
pub mod service_login {
#[derive(Debug, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct Request {
pub passphrase: String,
}
}
pub mod refresh_token {
#[derive(Debug, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct Request {
pub access_token: String,
}
}
}
pub mod response {
use serde::{Deserialize, Serialize};
#[derive(Default, Deserialize, Serialize, utoipa::ToSchema)]
pub struct Response {
pub message: String,
pub data: Vec<icarus_models::login_result::LoginResult>,
}
pub mod service_login {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct Response {
pub message: String,
pub data: Vec<icarus_models::login_result::LoginResult>,
}
}
pub mod refresh_token {
#[derive(Debug, Default, serde::Deserialize, serde::Serialize, utoipa::ToSchema)]
pub struct Response {
pub message: String,
pub data: Vec<icarus_models::login_result::LoginResult>,
}
}
}
/// Module for login endpoints
pub mod endpoint {
use axum::{Json, http::StatusCode};
use crate::hashing;
use crate::repo;
use crate::token_stuff;
use super::request;
use super::response;
// TODO: At some point, get the username from the DB
// Name of service username when returning a login result
pub const SERVICE_USERNAME: &str = "service";
async fn not_found(message: &str) -> (StatusCode, Json<response::Response>) {
(
StatusCode::NOT_FOUND,
Json(response::Response {
message: String::from(message),
data: Vec::new(),
}),
)
}
/// Endpoint to login
#[utoipa::path(
post,
path = super::super::endpoints::LOGIN,
request_body(
content = request::Request,
description = "Data required to login",
content_type = "application/json"
),
responses(
(status = 200, description = "Successfully logged in", body = response::Response),
(status = 404, description = "Could not login with credentials", body = response::Response)
)
)]
pub async fn login(
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
Json(payload): Json<request::Request>,
) -> (StatusCode, Json<response::Response>) {
// Check if user exists
match repo::user::get(&pool, &payload.username).await {
Ok(user) => {
if hashing::verify_password(&payload.password, user.password.clone()).unwrap() {
// Create token
let key = icarus_envy::environment::get_secret_key().await;
let (token_literal, duration) =
token_stuff::create_token(&key, &user.id).unwrap();
if token_stuff::verify_token(&key, &token_literal) {
let current_time = time::OffsetDateTime::now_utc();
let _ = repo::user::update_last_login(&pool, &user, &current_time).await;
(
StatusCode::OK,
Json(response::Response {
message: String::from("Successful"),
data: vec![icarus_models::login_result::LoginResult {
id: user.id,
username: user.username.clone(),
token: token_literal,
token_type: String::from(icarus_models::token::TOKEN_TYPE),
expiration: duration,
}],
}),
)
} else {
return not_found("Could not verify password").await;
}
} else {
return not_found("Error Hashing").await;
}
}
Err(err) => {
return not_found(&err.to_string()).await;
}
}
}
/// Endpoint to login as a service user
#[utoipa::path(
post,
path = super::super::endpoints::SERVICE_LOGIN,
request_body(
content = request::service_login::Request,
description = "Data required to login as a service user",
content_type = "application/json"
),
responses(
(status = 200, description = "Login successful", body = response::Response),
(status = 400, description = "Error logging in with credentials", body = response::Response)
)
)]
pub async fn service_login(
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
axum::Json(payload): axum::Json<request::service_login::Request>,
) -> (
axum::http::StatusCode,
axum::Json<response::service_login::Response>,
) {
let mut response = response::service_login::Response::default();
match repo::service::valid_passphrase(&pool, &payload.passphrase).await {
Ok((id, _passphrase, _date_created)) => {
let key = icarus_envy::environment::get_secret_key().await;
let (token_literal, duration) =
token_stuff::create_service_token(&key, &id).unwrap();
if token_stuff::verify_token(&key, &token_literal) {
let login_result = icarus_models::login_result::LoginResult {
id,
username: String::from(SERVICE_USERNAME),
token: token_literal,
token_type: String::from(icarus_models::token::TOKEN_TYPE),
expiration: duration,
};
response.data.push(login_result);
response.message = String::from("Successful");
(axum::http::StatusCode::OK, axum::Json(response))
} else {
(axum::http::StatusCode::OK, axum::Json(response))
}
}
Err(err) => {
response.message = err.to_string();
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
}
}
}
/// Endpoint to retrieve a refresh token
#[utoipa::path(
post,
path = super::super::endpoints::REFRESH_TOKEN,
request_body(
content = request::refresh_token::Request,
description = "Data required to retrieve a refresh token",
content_type = "application/json"
),
responses(
(status = 200, description = "Refresh token generated", body = response::Response),
(status = 400, description = "Error verifying token", body = response::Response),
(status = 404, description = "Could not validate token", body = response::Response),
(status = 500, description = "Error extracting token", body = response::Response)
)
)]
pub async fn refresh_token(
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
axum::Json(payload): axum::Json<request::refresh_token::Request>,
) -> (
axum::http::StatusCode,
axum::Json<response::refresh_token::Response>,
) {
let mut response = response::refresh_token::Response::default();
let key = icarus_envy::environment::get_secret_key().await;
if token_stuff::verify_token(&key, &payload.access_token) {
let token_type = token_stuff::get_token_type(&key, &payload.access_token).unwrap();
if token_stuff::is_token_type_valid(&token_type) {
// Get passphrase record with id
match token_stuff::extract_id_from_token(&key, &payload.access_token) {
Ok(id) => match repo::service::get_passphrase(&pool, &id).await {
Ok((returned_id, _, _)) => {
match token_stuff::create_service_refresh_token(&key, &returned_id) {
Ok((access_token, exp_dur)) => {
let login_result = icarus_models::login_result::LoginResult {
id: returned_id,
token: access_token,
expiration: exp_dur,
token_type: String::from(icarus_models::token::TOKEN_TYPE),
username: String::from(SERVICE_USERNAME),
};
response.message = String::from("Successful");
response.data.push(login_result);
(axum::http::StatusCode::OK, axum::Json(response))
}
Err(err) => {
response.message = err.to_string();
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
}
}
}
Err(err) => {
response.message = err.to_string();
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
}
},
Err(err) => {
response.message = err.to_string();
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
}
}
} else {
response.message = String::from("Invalid token type");
(axum::http::StatusCode::NOT_FOUND, axum::Json(response))
}
} else {
response.message = String::from("Could not verify token");
(axum::http::StatusCode::BAD_REQUEST, axum::Json(response))
}
}
}

View File

@@ -1,8 +1,12 @@
pub mod common;
pub mod login;
pub mod register;
pub mod endpoints {
pub const ROOT: &str = "/";
pub const REGISTER: &str = "/api/v2/register";
pub const DBTEST: &str = "/api/v2/test/db";
pub const LOGIN: &str = "/api/v2/login";
pub const SERVICE_LOGIN: &str = "/api/v2/service/login";
pub const REFRESH_TOKEN: &str = "/api/v2/token/refresh";
}

View File

@@ -6,7 +6,7 @@ use crate::repo;
pub mod request {
use serde::{Deserialize, Serialize};
#[derive(Default, Deserialize, Serialize)]
#[derive(Default, Deserialize, Serialize, utoipa::ToSchema)]
pub struct Request {
#[serde(skip_serializing_if = "String::is_empty")]
pub username: String,
@@ -26,13 +26,28 @@ pub mod request {
pub mod response {
use serde::{Deserialize, Serialize};
#[derive(Deserialize, Serialize)]
#[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct Response {
pub message: String,
pub data: Vec<icarus_models::user::User>,
}
}
/// Endpoint to register a user
#[utoipa::path(
post,
path = super::endpoints::REGISTER,
request_body(
content = request::Request,
description = "Data required to register",
content_type = "application/json"
),
responses(
(status = 201, description = "User created", body = response::Response),
(status = 404, description = "User already exists", body = response::Response),
(status = 400, description = "Issue creating user", body = response::Response)
)
)]
pub async fn register_user(
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
Json(payload): Json<request::Request>,

View File

@@ -11,8 +11,11 @@ use argon2::{
pub fn generate_salt() -> Result<SaltString, argon2::Error> {
// Generate a random salt
// SaltString::generate uses OsRng internally for cryptographic security
let salt = SaltString::generate(&mut OsRng);
Ok(salt)
Ok(SaltString::generate(&mut OsRng))
}
pub fn get_salt(s: &str) -> Result<SaltString, argon2::password_hash::Error> {
SaltString::from_b64(s)
}
pub fn hash_password(
@@ -28,9 +31,7 @@ pub fn hash_password(
// Hash the password with the salt
// The output is a PasswordHash string format that includes algorithm, version,
// parameters, salt, and the hash itself.
let password_hash = argon2.hash_password(password_bytes, salt)?.to_string();
Ok(password_hash)
Ok(argon2.hash_password(password_bytes, salt)?.to_string())
}
pub fn verify_password(
@@ -44,11 +45,9 @@ pub fn verify_password(
let parsed_hash = argon2::PasswordHash::new(stored_hash.as_str())?;
// Create an Argon2 instance (it will use the parameters from the parsed hash)
let argon2 = Argon2::default();
// Verify the password against the parsed hash
// This automatically uses the correct salt and parameters embedded in `parsed_hash`
match argon2.verify_password(password_bytes, &parsed_hash) {
match Argon2::default().verify_password(password_bytes, &parsed_hash) {
Ok(()) => Ok(true), // Passwords match
Err(argon2::password_hash::Error::Password) => Ok(false), // Passwords don't match
Err(e) => Err(e), // Some other error occurred (e.g., invalid hash format)
@@ -62,8 +61,7 @@ mod tests {
#[test]
fn test_hash_password() {
let some_password = String::from("somethingrandom");
let salt = generate_salt().unwrap();
match hash_password(&some_password, &salt) {
match hash_password(&some_password, &generate_salt().unwrap()) {
Ok(p) => match verify_password(&some_password, p.clone()) {
Ok(res) => {
assert_eq!(res, true);
@@ -77,4 +75,27 @@ mod tests {
}
}
}
#[test]
fn test_wrong_password() {
let some_password = String::from("somethingrandom");
match hash_password(&some_password, &generate_salt().unwrap()) {
Ok(p) => {
match verify_password(&some_password, p.clone()) {
Ok(res) => {
assert_eq!(res, true, "Passwords are not verified");
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
}
let wrong_password = String::from("Differentanotherlevel");
let result = verify_password(&wrong_password, p.clone()).unwrap();
assert_eq!(false, result, "Passwords should not match");
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
}
}
}

View File

@@ -2,29 +2,21 @@ pub mod callers;
pub mod config;
pub mod hashing;
pub mod repo;
pub mod keys {
pub const DBURL: &str = "DATABASE_URL";
pub mod error {
pub const ERROR: &str = "DATABASE_URL must be set in .env";
}
}
pub mod token_stuff;
mod connection_settings {
pub const MAXCONN: u32 = 5;
}
pub mod db_pool {
pub mod db {
use sqlx::postgres::PgPoolOptions;
use std::env;
use crate::{connection_settings, keys};
use crate::connection_settings;
pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> {
let database_url = get_db_url().await;
println!("Database url: {:?}", database_url);
let database_url = icarus_envy::environment::get_db_url().await;
println!("Database url: {database_url}");
PgPoolOptions::new()
.max_connections(connection_settings::MAXCONN)
@@ -32,10 +24,12 @@ pub mod db_pool {
.await
}
async fn get_db_url() -> String {
#[cfg(debug_assertions)] // Example: Only load .env in debug builds
dotenvy::dotenv().ok();
env::var(keys::DBURL).expect(keys::error::ERROR)
pub async fn migrations(pool: &sqlx::PgPool) {
// Run migrations using the sqlx::migrate! macro
// Assumes your migrations are in a ./migrations folder relative to Cargo.toml
sqlx::migrate!("./migrations")
.run(pool)
.await
.expect("Failed to run migrations");
}
}

View File

@@ -14,45 +14,80 @@ async fn main() {
axum::serve(listener, app).await.unwrap();
}
mod db {
pub async fn migrations(pool: &sqlx::PgPool) {
// Run migrations using the sqlx::migrate! macro
// Assumes your migrations are in a ./migrations folder relative to Cargo.toml
sqlx::migrate!("./migrations")
.run(pool)
.await
.expect("Failed to run migrations on testcontainer DB");
}
}
mod init {
use axum::{
Router,
routing::{get, post},
};
use utoipa::OpenApi;
use crate::callers;
use crate::db;
use callers::common as common_callers;
use callers::login as login_caller;
use callers::register as register_caller;
use login_caller::endpoint as login_endpoints;
use login_caller::response as login_responses;
use register_caller::response as register_responses;
#[derive(utoipa::OpenApi)]
#[openapi(
paths(
common_callers::endpoint::db_ping, common_callers::endpoint::root,
register_caller::register_user,
login_endpoints::login, login_endpoints::service_login, login_endpoints::refresh_token
),
components(schemas(common_callers::response::TestResult,
register_responses::Response,
login_responses::Response, login_responses::service_login::Response, login_responses::refresh_token::Response)),
tags(
(name = "Icarus Auth API", description = "Auth API for Icarus API")
)
)]
struct ApiDoc;
pub async fn routes() -> Router {
// build our application with a route
Router::new()
.route(callers::endpoints::DBTEST, get(callers::common::db_ping))
.route(callers::endpoints::ROOT, get(callers::common::root))
.route(
callers::endpoints::DBTEST,
get(callers::common::endpoint::db_ping),
)
.route(
callers::endpoints::ROOT,
get(callers::common::endpoint::root),
)
.route(
callers::endpoints::REGISTER,
post(callers::register::register_user),
)
.route(
callers::endpoints::LOGIN,
post(callers::login::endpoint::login),
)
.route(
callers::endpoints::SERVICE_LOGIN,
post(callers::login::endpoint::service_login),
)
.route(
callers::endpoints::REFRESH_TOKEN,
post(callers::login::endpoint::refresh_token),
)
}
pub async fn app() -> Router {
let pool = icarus_auth::db_pool::create_pool()
let pool = icarus_auth::db::create_pool()
.await
.expect("Failed to create pool");
db::migrations(&pool).await;
icarus_auth::db::migrations(&pool).await;
routes().await.layer(axum::Extension(pool))
routes()
.await
.merge(
utoipa_swagger_ui::SwaggerUi::new("/swagger-ui")
.url("/api-docs/openapi.json", ApiDoc::openapi()),
)
.layer(axum::Extension(pool))
}
}
@@ -71,24 +106,23 @@ mod tests {
mod db_mgr {
use std::str::FromStr;
use icarus_auth::keys;
pub const LIMIT: usize = 6;
pub async fn get_pool() -> Result<sqlx::PgPool, sqlx::Error> {
let tm_db_url = std::env::var(keys::DBURL).expect("DATABASE_URL must be present");
let tm_db_url = icarus_envy::environment::get_db_url().await;
let tm_options = sqlx::postgres::PgConnectOptions::from_str(&tm_db_url).unwrap();
sqlx::PgPool::connect_with(tm_options).await
}
pub async fn generate_db_name() -> String {
let db_name =
get_database_name().unwrap() + &"_" + &uuid::Uuid::new_v4().to_string()[..LIMIT];
let db_name = get_database_name().await.unwrap()
+ &"_"
+ &uuid::Uuid::new_v4().to_string()[..LIMIT];
db_name
}
pub async fn connect_to_db(db_name: &str) -> Result<sqlx::PgPool, sqlx::Error> {
let db_url = std::env::var(keys::DBURL).expect("DATABASE_URL must be set for tests");
let db_url = icarus_envy::environment::get_db_url().await;
let options = sqlx::postgres::PgConnectOptions::from_str(&db_url)?.database(db_name);
sqlx::PgPool::connect_with(options).await
}
@@ -114,11 +148,9 @@ mod tests {
Ok(())
}
pub fn get_database_name() -> Result<String, Box<dyn std::error::Error>> {
dotenvy::dotenv().ok(); // Load .env file if it exists
pub async fn get_database_name() -> Result<String, Box<dyn std::error::Error>> {
let database_url = icarus_envy::environment::get_db_url().await;
match std::env::var(keys::DBURL) {
Ok(database_url) => {
let parsed_url = url::Url::parse(&database_url)?;
if parsed_url.scheme() == "postgres" || parsed_url.scheme() == "postgresql" {
match parsed_url
@@ -133,11 +165,48 @@ mod tests {
Err("Error parsing".into())
}
}
Err(_) => {
// DATABASE_URL environment variable not found
Err("Error parsing".into())
}
fn get_test_register_request() -> icarus_auth::callers::register::request::Request {
icarus_auth::callers::register::request::Request {
username: String::from("somethingsss"),
password: String::from("Raindown!"),
email: String::from("dev@null.com"),
phone: String::from("1234567890"),
firstname: String::from("Bob"),
lastname: String::from("Smith"),
}
}
fn get_test_register_payload(
usr: &icarus_auth::callers::register::request::Request,
) -> serde_json::Value {
json!({
"username": &usr.username,
"password": &usr.password,
"email": &usr.email,
"phone": &usr.phone,
"firstname": &usr.firstname,
"lastname": &usr.lastname,
})
}
pub mod requests {
use tower::ServiceExt; // for `call`, `oneshot`, and `ready`
pub async fn register(
app: &axum::Router,
usr: &icarus_auth::callers::register::request::Request,
) -> Result<axum::response::Response, std::convert::Infallible> {
let payload = super::get_test_register_payload(&usr);
let req = axum::http::Request::builder()
.method(axum::http::Method::POST)
.uri(crate::callers::endpoints::REGISTER)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(axum::body::Body::from(payload.to_string()))
.unwrap();
app.clone().oneshot(req).await
}
}
@@ -180,38 +249,13 @@ mod tests {
let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
db::migrations(&pool).await;
icarus_auth::db::migrations(&pool).await;
let app = init::routes().await.layer(axum::Extension(pool));
let usr = icarus_auth::callers::register::request::Request {
username: String::from("somethingsss"),
password: String::from("Raindown!"),
email: String::from("dev@null.com"),
phone: String::from("1234567890"),
firstname: String::from("Bob"),
lastname: String::from("Smith"),
};
let usr = get_test_register_request();
let payload = json!({
"username": &usr.username,
"password": &usr.password,
"email": &usr.email,
"phone": &usr.phone,
"firstname": &usr.firstname,
"lastname": &usr.lastname,
});
let response = app
.oneshot(
Request::builder()
.method(axum::http::Method::POST)
.uri(callers::endpoints::REGISTER)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(Body::from(payload.to_string()))
.unwrap(),
)
.await;
let response = requests::register(&app, &usr).await;
match response {
Ok(resp) => {
@@ -244,4 +288,213 @@ mod tests {
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
#[tokio::test]
async fn test_login_user() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(e) => {
assert!(false, "Error: {:?}", e.to_string());
}
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
icarus_auth::db::migrations(&pool).await;
let app = init::routes().await.layer(axum::Extension(pool));
let usr = get_test_register_request();
let response = requests::register(&app, &usr).await;
match response {
Ok(resp) => {
assert_eq!(
resp.status(),
StatusCode::CREATED,
"Message: {:?} {:?}",
resp,
usr.username
);
let body = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap();
let parsed_body: callers::register::response::Response =
serde_json::from_slice(&body).unwrap();
let returned_usr = &parsed_body.data[0];
assert_eq!(false, returned_usr.id.is_nil(), "Id is not populated");
assert_eq!(
usr.username, returned_usr.username,
"Usernames do not match"
);
assert!(returned_usr.date_created.is_some(), "Date Created is empty");
let login_payload = json!({
"username": &usr.username,
"password": &usr.password,
});
match app
.oneshot(
Request::builder()
.method(axum::http::Method::POST)
.uri(callers::endpoints::LOGIN)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(Body::from(login_payload.to_string()))
.unwrap(),
)
.await
{
Ok(resp) => {
assert_eq!(StatusCode::OK, resp.status(), "Status is not right");
let body = axum::body::to_bytes(resp.into_body(), usize::MAX)
.await
.unwrap();
let parsed_body: callers::login::response::Response =
serde_json::from_slice(&body).unwrap();
let login_result = &parsed_body.data[0];
assert!(!login_result.id.is_nil(), "Id is nil");
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
}
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
};
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
#[tokio::test]
async fn test_service_login_user() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(e) => {
assert!(false, "Error: {:?}", e.to_string());
}
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
icarus_auth::db::migrations(&pool).await;
let app = init::routes().await.layer(axum::Extension(pool));
let passphrase =
String::from("iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH");
let payload = serde_json::json!({
"passphrase": passphrase
});
match app
.oneshot(
Request::builder()
.method(axum::http::Method::POST)
.uri(callers::endpoints::SERVICE_LOGIN)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(Body::from(payload.to_string()))
.unwrap(),
)
.await
{
Ok(response) => {
assert_eq!(StatusCode::OK, response.status(), "Status is not right");
let body = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
let parsed_body: callers::login::response::service_login::Response =
serde_json::from_slice(&body).unwrap();
let _login_result = &parsed_body.data[0];
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
}
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
#[tokio::test]
async fn test_refresh_token() {
let tm_pool = db_mgr::get_pool().await.unwrap();
let db_name = db_mgr::generate_db_name().await;
match db_mgr::create_database(&tm_pool, &db_name).await {
Ok(_) => {
println!("Success");
}
Err(e) => {
assert!(false, "Error: {:?}", e.to_string());
}
}
let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
icarus_auth::db::migrations(&pool).await;
let app = init::routes().await.layer(axum::Extension(pool));
let id = uuid::Uuid::parse_str("22f9c775-cce9-457a-a147-9dafbb801f61").unwrap();
let key = icarus_envy::environment::get_secret_key().await;
match icarus_auth::token_stuff::create_service_token(&key, &id) {
Ok((token, _expire)) => {
let payload = serde_json::json!({
"access_token": token
});
match app
.oneshot(
Request::builder()
.method(axum::http::Method::POST)
.uri(callers::endpoints::REFRESH_TOKEN)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(Body::from(payload.to_string()))
.unwrap(),
)
.await
{
Ok(response) => {
let body = axum::body::to_bytes(response.into_body(), usize::MAX)
.await
.unwrap();
let parsed_body: callers::login::response::service_login::Response =
serde_json::from_slice(&body).unwrap();
let login_result = &parsed_body.data[0];
assert_eq!(
id, login_result.id,
"The Id from the response does not match {id:?} {:?}",
login_result.id
);
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
}
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
}
let _ = db_mgr::drop_database(&tm_pool, &db_name).await;
}
}

View File

@@ -7,6 +7,74 @@ pub mod user {
pub date_created: Option<time::OffsetDateTime>,
}
pub async fn get(
pool: &sqlx::PgPool,
username: &String,
) -> Result<icarus_models::user::User, sqlx::Error> {
let result = sqlx::query(
r#"
SELECT * FROM "user" WHERE username = $1
"#,
)
.bind(username)
.fetch_optional(pool)
.await;
match result {
Ok(r) => match r {
Some(r) => Ok(icarus_models::user::User {
id: r.try_get("id")?,
username: r.try_get("username")?,
password: r.try_get("password")?,
email: r.try_get("email")?,
email_verified: r.try_get("email_verified")?,
phone: r.try_get("phone")?,
salt_id: r.try_get("salt_id")?,
firstname: r.try_get("firstname")?,
lastname: r.try_get("lastname")?,
date_created: r.try_get("date_created")?,
last_login: r.try_get("last_login")?,
status: r.try_get("status")?,
}),
None => Err(sqlx::Error::RowNotFound),
},
Err(e) => Err(e),
}
}
pub async fn update_last_login(
pool: &sqlx::PgPool,
user: &icarus_models::user::User,
time: &time::OffsetDateTime,
) -> Result<time::OffsetDateTime, sqlx::Error> {
let result = sqlx::query(
r#"
UPDATE "user" SET last_login = $1 WHERE id = $2 RETURNING last_login
"#,
)
.bind(time)
.bind(user.id)
.fetch_optional(pool)
.await
.map_err(|e| {
eprintln!("Error updating time: {e}");
e
});
match result {
Ok(row) => match row {
Some(r) => {
let last_login: time::OffsetDateTime = r
.try_get("last_login")
.map_err(|_e| sqlx::Error::RowNotFound)?;
Ok(last_login)
}
None => Err(sqlx::Error::RowNotFound),
},
Err(err) => Err(err),
}
}
pub async fn exists(pool: &sqlx::PgPool, username: &String) -> Result<bool, sqlx::Error> {
let result = sqlx::query(
r#"
@@ -45,7 +113,7 @@ pub mod user {
.fetch_one(pool)
.await
.map_err(|e| {
eprintln!("Error inserting item: {}", e);
eprintln!("Error inserting item: {e}");
e
})?;
@@ -72,6 +140,31 @@ pub mod salt {
pub id: uuid::Uuid,
}
pub async fn get(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
) -> Result<icarus_models::user::salt::Salt, sqlx::Error> {
let result = sqlx::query(
r#"
SELECT * FROM "salt" WHERE id = $1
"#,
)
.bind(id)
.fetch_optional(pool)
.await;
match result {
Ok(r) => match r {
Some(r) => Ok(icarus_models::user::salt::Salt {
id: r.try_get("id")?,
salt: r.try_get("salt")?,
}),
None => Err(sqlx::Error::RowNotFound),
},
Err(e) => Err(e),
}
}
pub async fn insert(
pool: &sqlx::PgPool,
salt: &icarus_models::user::salt::Salt,
@@ -87,7 +180,7 @@ pub mod salt {
.fetch_one(pool)
.await
.map_err(|e| {
eprintln!("Error inserting item: {}", e);
eprintln!("Error inserting item: {e}");
e
})?;
@@ -102,3 +195,56 @@ pub mod salt {
}
}
}
pub mod service {
use sqlx::Row;
pub async fn valid_passphrase(
pool: &sqlx::PgPool,
passphrase: &String,
) -> Result<(uuid::Uuid, String, time::OffsetDateTime), sqlx::Error> {
let result = sqlx::query(
r#"
SELECT * FROM "passphrase" WHERE passphrase = $1
"#,
)
.bind(passphrase)
.fetch_one(pool)
.await;
match result {
Ok(row) => {
let id: uuid::Uuid = row.try_get("id")?;
let passphrase: String = row.try_get("passphrase")?;
let date_created: Option<time::OffsetDateTime> = row.try_get("date_created")?;
Ok((id, passphrase, date_created.unwrap()))
}
Err(err) => Err(err),
}
}
pub async fn get_passphrase(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
) -> Result<(uuid::Uuid, String, time::OffsetDateTime), sqlx::Error> {
let result = sqlx::query(
r#"
SELECT * FROM "passphrase" WHERE id = $1;
"#,
)
.bind(id)
.fetch_one(pool)
.await;
match result {
Ok(row) => {
let returned_id: uuid::Uuid = row.try_get("id")?;
let passphrase: String = row.try_get("passphrase")?;
let date_created: time::OffsetDateTime = row.try_get("date_created")?;
Ok((returned_id, passphrase, date_created))
}
Err(err) => Err(err),
}
}
}

139
src/token_stuff/mod.rs Normal file
View File

@@ -0,0 +1,139 @@
use josekit::{
self,
jws::alg::hmac::HmacJwsAlgorithm::Hs256,
jwt::{self},
};
use time;
pub const KEY_ENV: &str = "SECRET_KEY";
pub const MESSAGE: &str = "Something random";
pub const ISSUER: &str = "icarus_auth";
pub const AUDIENCE: &str = "icarus";
pub fn get_issued() -> time::Result<time::OffsetDateTime> {
Ok(time::OffsetDateTime::now_utc())
}
pub fn get_expiration(issued: &time::OffsetDateTime) -> Result<time::OffsetDateTime, time::Error> {
let duration_expire = time::Duration::hours(4);
Ok(*issued + duration_expire)
}
pub fn create_token(
provided_key: &String,
id: &uuid::Uuid,
) -> Result<(String, i64), josekit::JoseError> {
let resource = icarus_models::token::TokenResource {
message: String::from(MESSAGE),
issuer: String::from(ISSUER),
audiences: vec![String::from(AUDIENCE)],
id: *id,
};
icarus_models::token::create_token(provided_key, &resource, time::Duration::hours(4))
}
pub fn create_service_token(
provided: &String,
id: &uuid::Uuid,
) -> Result<(String, i64), josekit::JoseError> {
let resource = icarus_models::token::TokenResource {
message: String::from(SERVICE_SUBJECT),
issuer: String::from(ISSUER),
audiences: vec![String::from(AUDIENCE)],
id: *id,
};
icarus_models::token::create_token(provided, &resource, time::Duration::hours(1))
}
pub fn create_service_refresh_token(
key: &String,
id: &uuid::Uuid,
) -> Result<(String, i64), josekit::JoseError> {
let resource = icarus_models::token::TokenResource {
message: String::from(SERVICE_SUBJECT),
issuer: String::from(ISSUER),
audiences: vec![String::from(AUDIENCE)],
id: *id,
};
icarus_models::token::create_token(key, &resource, time::Duration::hours(4))
}
pub fn verify_token(key: &String, token: &String) -> bool {
match get_payload(key, token) {
Ok((payload, _header)) => match payload.subject() {
Some(_sub) => true,
None => false,
},
Err(_err) => false,
}
}
pub fn extract_id_from_token(key: &String, token: &String) -> Result<uuid::Uuid, std::io::Error> {
match get_payload(key, token) {
Ok((payload, _header)) => match payload.claim("id") {
Some(id) => match uuid::Uuid::parse_str(id.as_str().unwrap()) {
Ok(extracted) => Ok(extracted),
Err(err) => Err(std::io::Error::other(err.to_string())),
},
None => Err(std::io::Error::other("No claim found")),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
pub const APP_TOKEN_TYPE: &str = "Icarus_App";
pub const APP_SUBJECT: &str = "Something random";
pub const SERVICE_TOKEN_TYPE: &str = "Icarus_Service";
pub const SERVICE_SUBJECT: &str = "Service random";
pub fn get_token_type(key: &String, token: &String) -> Result<String, std::io::Error> {
match get_payload(key, token) {
Ok((payload, _header)) => match payload.subject() {
Some(subject) => {
if subject == APP_SUBJECT {
Ok(String::from(APP_TOKEN_TYPE))
} else if subject == SERVICE_SUBJECT {
Ok(String::from(SERVICE_TOKEN_TYPE))
} else {
Err(std::io::Error::other(String::from("Invalid subject")))
}
}
None => Err(std::io::Error::other(String::from("Invalid payload"))),
},
Err(err) => Err(std::io::Error::other(err.to_string())),
}
}
pub fn is_token_type_valid(token_type: &String) -> bool {
token_type == SERVICE_TOKEN_TYPE
}
fn get_payload(
key: &String,
token: &String,
) -> Result<(josekit::jwt::JwtPayload, josekit::jws::JwsHeader), josekit::JoseError> {
let ver = Hs256.verifier_from_bytes(key.as_bytes()).unwrap();
jwt::decode_with_verifier(token, &ver)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tokenize() {
let rt = tokio::runtime::Runtime::new().unwrap();
let special_key = rt.block_on(icarus_envy::environment::get_secret_key());
let id = uuid::Uuid::new_v4();
match create_token(&special_key, &id) {
Ok((token, _duration)) => {
let result = verify_token(&special_key, &token);
assert!(result, "Token not verified");
}
Err(err) => {
assert!(false, "Error: {:?}", err.to_string());
}
};
}
}