Compare commits

..

10 Commits

Author SHA1 Message Date
cba3e3db79 tsk-68: Remove run_migrations.txt (#77)
All checks were successful
Release Tagging / release (push) Successful in 35s
Rust Build / Check (push) Successful in 40s
Rust Build / Test Suite (push) Successful in 1m17s
Rust Build / Rustfmt (push) Successful in 32s
Rust Build / Clippy (push) Successful in 42s
Rust Build / build (push) Successful in 1m2s
Close #68

Reviewed-on: #77
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-22 15:48:39 +00:00
5407462def tsk-69: Retrieve username from the db (#76)
All checks were successful
Release Tagging / release (push) Successful in 36s
Rust Build / Check (push) Successful in 43s
Rust Build / Test Suite (push) Successful in 1m17s
Rust Build / Rustfmt (push) Successful in 35s
Rust Build / Clippy (push) Successful in 41s
Rust Build / build (push) Successful in 57s
Closes #69

Reviewed-on: #76
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-22 15:35:29 +00:00
eb6ddbc97a tsk-70: Remove src/lib.rs (#74)
All checks were successful
Release Tagging / release (push) Successful in 36s
Rust Build / Check (push) Successful in 39s
Rust Build / Test Suite (push) Successful in 1m8s
Rust Build / Rustfmt (push) Successful in 33s
Rust Build / Clippy (push) Successful in 41s
Rust Build / build (push) Successful in 59s
Closes #70

Reviewed-on: #74
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-20 17:12:54 +00:00
e86ca4b2c8 tsk-61: Registration configuration (#73)
All checks were successful
Rust Build / Check (push) Successful in 42s
Rust Build / Test Suite (push) Successful in 1m18s
Rust Build / Rustfmt (push) Successful in 29s
Rust Build / Clippy (push) Successful in 40s
Rust Build / build (push) Successful in 57s
Release Tagging / release (push) Successful in 35s
Closes #61

Reviewed-on: #73
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-20 16:48:48 +00:00
6ec3b25e7d tsk-55: Register endpoint bug fix (#72)
All checks were successful
Release Tagging / release (push) Successful in 32s
Rust Build / Check (push) Successful in 41s
Rust Build / Test Suite (push) Successful in 1m7s
Rust Build / Rustfmt (push) Successful in 35s
Rust Build / Clippy (push) Successful in 40s
Rust Build / build (push) Successful in 57s
Closes #55

Reviewed-on: #72
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-20 16:05:30 +00:00
7e44d523f2 icarus_models version bump (#71)
All checks were successful
Release Tagging / release (push) Successful in 40s
Rust Build / Check (push) Successful in 1m14s
Rust Build / Rustfmt (push) Successful in 37s
Rust Build / Test Suite (push) Successful in 1m30s
Rust Build / Clippy (push) Successful in 37s
Rust Build / build (push) Successful in 1m6s
Reviewed-on: #71
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-20 02:57:36 +00:00
59d0ca0c0b tsk-63: Rust update (#67)
All checks were successful
Rust Build / Check (push) Successful in 44s
Rust Build / Rustfmt (push) Successful in 56s
Rust Build / build (push) Successful in 5m16s
Rust Build / Clippy (push) Successful in 21m20s
Rust Build / Test Suite (push) Successful in 25m26s
Closes #63

Reviewed-on: #67
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-12 22:01:50 +00:00
a02b15c65c icarus_models version bump (#66)
All checks were successful
Rust Build / Check (push) Successful in 40s
Rust Build / Clippy (push) Successful in 41s
Rust Build / build (push) Successful in 1m1s
Rust Build / Rustfmt (push) Successful in 2m29s
Rust Build / Test Suite (push) Successful in 5m52s
Reviewed-on: #66
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-11 23:32:01 +00:00
ebe29df991 icarus_envy version bump (#65)
All checks were successful
Rust Build / Test Suite (push) Successful in 3m10s
Rust Build / Clippy (push) Successful in 2m22s
Rust Build / build (push) Successful in 3m46s
Rust Build / Rustfmt (push) Successful in 25s
Rust Build / Check (push) Successful in 40s
Reviewed-on: #65
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-10 21:03:18 +00:00
473b4ec762 Cors support (#62)
Some checks failed
Rust Build / Check (push) Successful in 38s
Rust Build / Test Suite (push) Failing after 1m0s
Rust Build / Rustfmt (push) Successful in 30s
Rust Build / Clippy (push) Successful in 39s
Rust Build / build (push) Successful in 51s
Reviewed-on: #62
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-10-10 20:32:29 +00:00
22 changed files with 787 additions and 648 deletions

View File

@@ -1,3 +1,8 @@
APP_ENV=development
BACKEND_PORT=8001
FRONTEND_URL=http://localhost:4200
RUST_LOG=debug
ALLOWED_ORIGINS=https://soaricarus.com,https://www.soaricarus.com
SECRET_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h SECRET_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h
SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH
POSTGRES_AUTH_USER=icarus_op POSTGRES_AUTH_USER=icarus_op
@@ -5,3 +10,4 @@ POSTGRES_AUTH_PASSWORD=password
POSTGRES_AUTH_DB=icarus_auth_db POSTGRES_AUTH_DB=icarus_auth_db
POSTGRES_AUTH_HOST=auth_db POSTGRES_AUTH_HOST=auth_db
DATABASE_URL=postgresql://${POSTGRES_AUTH_USER}:${POSTGRES_AUTH_PASSWORD}@${POSTGRES_AUTH_HOST}:5432/${POSTGRES_AUTH_DB} DATABASE_URL=postgresql://${POSTGRES_AUTH_USER}:${POSTGRES_AUTH_PASSWORD}@${POSTGRES_AUTH_HOST}:5432/${POSTGRES_AUTH_DB}
ENABLE_REGISTRATION=TRUE

View File

@@ -1,3 +1,8 @@
APP_ENV=development
BACKEND_PORT=8001
FRONTEND_URL=http://localhost:4200
RUST_LOG=debug
ALLOWED_ORIGINS=https://soaricarus.com,https://www.soaricarus.com
SECRET_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h SECRET_KEY=refero34o8rfhfjn983thf39fhc943rf923n3h
SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH SERVICE_PASSPHRASE=iUOo1fxshf3y1tUGn1yU8l9raPApHCdinW0VdCHdRFEjqhR3Bf02aZzsKbLtaDFH
POSTGRES_AUTH_USER=icarus_op_test POSTGRES_AUTH_USER=icarus_op_test
@@ -5,3 +10,4 @@ POSTGRES_AUTH_PASSWORD=password
POSTGRES_AUTH_DB=icarus_auth_test_db POSTGRES_AUTH_DB=icarus_auth_test_db
POSTGRES_AUTH_HOST=localhost POSTGRES_AUTH_HOST=localhost
DATABASE_URL=postgresql://${POSTGRES_AUTH_USER}:${POSTGRES_AUTH_PASSWORD}@${POSTGRES_AUTH_HOST}:5432/${POSTGRES_AUTH_DB} DATABASE_URL=postgresql://${POSTGRES_AUTH_USER}:${POSTGRES_AUTH_PASSWORD}@${POSTGRES_AUTH_HOST}:5432/${POSTGRES_AUTH_DB}
ENABLE_REGISTRATION=TRUE

View File

@@ -3,21 +3,21 @@ name: Release Tagging
on: on:
push: push:
branches: branches:
- devel - main
jobs: jobs:
release: release:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- name: Checkout code - name: Checkout code
uses: actions/checkout@v3 uses: actions/checkout@v5
with: with:
fetch-depth: 0 # Important for git describe --tags fetch-depth: 0 # Important for git describe --tags
- name: Install Rust - name: Install Rust
uses: actions-rs/toolchain@v1 uses: actions-rs/toolchain@v1
with: with:
toolchain: 1.88.0 toolchain: 1.90.0
components: cargo components: cargo
- name: Extract Version from Cargo.toml - name: Extract Version from Cargo.toml
@@ -27,8 +27,10 @@ jobs:
PROJECT_COMMIT_HASH=$(git rev-parse HEAD | cut -c 1-10) PROJECT_COMMIT_HASH=$(git rev-parse HEAD | cut -c 1-10)
BRANCH_REF="${GITHUB_REF}" BRANCH_REF="${GITHUB_REF}"
BRANCH_NAME=$(echo "$BRANCH_REF" | cut -d '/' -f 3) BRANCH_NAME=$(echo "$BRANCH_REF" | cut -d '/' -f 3)
PROJECT_TAG_RELEASE="v$VERSION-$BRANCH_NAME-$PROJECT_COMMIT_HASH" PROJECT_TAG_RELEASE="v$VERSION-$BRANCH_NAME-$PROJECT_COMMIT_HASH-950"
echo "::set-output name=project_tag_release::$PROJECT_TAG_RELEASE-950"
echo "::set-output name=project_tag_release::$PROJECT_TAG_RELEASE"
echo "Version: $VERSION" echo "Version: $VERSION"
echo "Hash: $PROJECT_COMMIT_HASH" echo "Hash: $PROJECT_COMMIT_HASH"
echo "Branch: $BRANCH_NAME" echo "Branch: $BRANCH_NAME"

View File

@@ -15,10 +15,10 @@ jobs:
name: Check name: Check
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.88.0 toolchain: 1.90.0
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
@@ -50,10 +50,10 @@ jobs:
--health-retries 5 --health-retries 5
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.88.0 toolchain: 1.90.0
# --- Add this step for explicit verification --- # --- Add this step for explicit verification ---
- name: Verify Docker Environment - name: Verify Docker Environment
run: | run: |
@@ -76,6 +76,7 @@ jobs:
SECRET_KEY: ${{ secrets.TOKEN_SECRET_KEY }} SECRET_KEY: ${{ secrets.TOKEN_SECRET_KEY }}
# Make SSH agent available if tests fetch private dependencies # Make SSH agent available if tests fetch private dependencies
SSH_AUTH_SOCK: ${{ env.SSH_AUTH_SOCK }} SSH_AUTH_SOCK: ${{ env.SSH_AUTH_SOCK }}
ENABLE_REGISTRATION: 'TRUE'
run: | run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key
@@ -91,10 +92,10 @@ jobs:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.88.0 toolchain: 1.90.0
- run: rustup component add rustfmt - run: rustup component add rustfmt
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
@@ -110,10 +111,10 @@ jobs:
name: Clippy name: Clippy
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.88.0 toolchain: 1.90.0
- run: rustup component add clippy - run: rustup component add clippy
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
@@ -129,10 +130,10 @@ jobs:
name: build name: build
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v5
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.88.0 toolchain: 1.90.0
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/icarus_models_deploy_key

865
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,29 +1,30 @@
[package] [package]
name = "icarus_auth" name = "icarus_auth"
version = "0.5.0" version = "0.6.5"
edition = "2024" edition = "2024"
rust-version = "1.88" rust-version = "1.90"
[dependencies] [dependencies]
axum = { version = "0.8.4" } axum = { version = "0.8.6" }
serde = { version = "1.0.219", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.140" } serde_json = { version = "1.0.145" }
tokio = { version = "1.45.1", features = ["rt-multi-thread"] } tokio = { version = "1.47.1", features = ["rt-multi-thread"] }
tracing-subscriber = { version = "0.3.19" } tracing-subscriber = { version = "0.3.20" }
tower = { version = "0.5.2" } tower = { version = "0.5.2", features = ["full"] }
hyper = { version = "1.6.0" } tower-http = { version = "0.6.6", features = ["cors"] }
hyper = { version = "1.7.0" }
sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio-native-tls", "time", "uuid"] } sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio-native-tls", "time", "uuid"] }
uuid = { version = "1.17.0", features = ["v4", "serde"] } uuid = { version = "1.18.1", features = ["v4", "serde"] }
argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x version argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x version
rand = { version = "0.9.1" } rand = { version = "0.9.2" }
time = { version = "0.3.41", features = ["macros", "serde"] } time = { version = "0.3.41", features = ["macros", "serde"] }
josekit = { version = "0.10.3" } josekit = { version = "0.10.3" }
utoipa = { version = "5.4.0", features = ["axum_extras"] } utoipa = { version = "5.4.0", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } 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" } icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.8.0" }
icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.3.2" } icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.5.0" }
[dev-dependencies] [dev-dependencies]
http-body-util = { version = "0.1.3" } http-body-util = { version = "0.1.3" }
url = { version = "2.5.4" } url = { version = "2.5.7" }
once_cell = { version = "1.21.3" } # Useful for lazy initialization in tests/app setup once_cell = { version = "1.21.3" } # Useful for lazy initialization in tests/app setup

View File

@@ -1,7 +1,7 @@
# Stage 1: Build the application # Stage 1: Build the application
# Use a specific Rust version for reproducibility. Choose one that matches your development environment. # Use a specific Rust version for reproducibility. Choose one that matches your development environment.
# Using slim variant for smaller base image # Using slim variant for smaller base image
FROM rust:1.88 as builder FROM rust:1.90 as builder
# Set the working directory inside the container # Set the working directory inside the container
WORKDIR /usr/src/app WORKDIR /usr/src/app

View File

@@ -2,31 +2,42 @@ A auth web API services for the Icarus project.
# Getting Started # Getting Started
Install the `sqlx` tool to use migrations.
```
cargo install sqlx-cli
```
This will be used to scaffold development for local environments.
The easiest way to get started is through docker. This assumes that docker is already installed The easiest way to get started is through docker. This assumes that docker is already installed
on your system. Copy the `.env.docker.sample` as `.env`. Most of the data in the env file doesn't on your system. Copy the `.env.docker.sample` as `.env`. Most of the data in the env file doesn't
need to be modified. The `SECRET_KEY` variable should be changed since it will be used for token need to be modified. The `SECRET_KEY` variable should be changed since it will be used for token
generation. The `SECRET_PASSPHASE` should also be changed when in production mode, but make sure generation. The `SECRET_PASSPHASE` should also be changed when in production mode, but make sure
the respective `passphrase` database table record exists. the respective `passphrase` database table record exists.
Build image To enable or disable registrations, use `TRUE` or `FALSE` for the `ENABLE_REGISTRATION` variable.
By default it is `TRUE`.
### Build image
``` ```
docker compose build docker compose build
``` ```
Start images ### Start images
``` ```
docker compose up -d --force-recreate docker compose up -d --force-recreate
``` ```
Bring it down ### Bring it down
``` ```
docker compose down -v docker compose down -v
``` ```
Pruning ### Pruning
``` ```
docker system prune -a docker system prune -a
``` ```
To view the OpenAPI spec, run the project and access `/swagger-ui`. If running through docker, To view the OpenAPI spec, run the project and access `/swagger-ui`. If running through docker,
the url would be something like `http://localhost:8000/swagger-ui`. the url would be something like `http://localhost:8001/swagger-ui`.

View File

@@ -9,7 +9,7 @@ services:
container_name: icarus_auth # Optional: Give the container a specific name container_name: icarus_auth # Optional: Give the container a specific name
ports: ports:
# Map host port 8000 to container port 3000 (adjust as needed) # Map host port 8000 to container port 3000 (adjust as needed)
- "8000:3000" - "8001:8001"
env_file: env_file:
- .env - .env
depends_on: depends_on:

View File

@@ -23,6 +23,7 @@ CREATE TABLE IF NOT EXISTS "salt" (
CREATE TABLE IF NOT EXISTS "passphrase" ( CREATE TABLE IF NOT EXISTS "passphrase" (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
username TEXT NOT NULL,
passphrase TEXT NOT NULL, passphrase TEXT NOT NULL,
date_created TIMESTAMPTZ NOT NULL DEFAULT NOW() date_created TIMESTAMPTZ NOT NULL DEFAULT NOW()
); );

View File

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

View File

@@ -1,27 +0,0 @@
TODO: At some point, move this somewhere that is appropriate
# Make sure role has CREATEDB
ALTER ROLE username_that_needs_permission CREATEDB;
# Install migrations
cargo install sqlx-cli
# Make sure to populate DATABASE_URL with correct value.
# By default, the DATABASE_URL found in .env file will be used
export DATABASE_URL="postgres://icarus_op_test:password@localhost/icarus_auth_test"
# init
sqlx migrate add init_migration
sqlx migrate run
# Create
sqlx database create
# Drop
sqlx database drop
# setup
sqlx database setup
# Reset
sqlx database reset

View File

@@ -59,10 +59,6 @@ pub mod endpoint {
use super::request; use super::request;
use super::response; 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>) { async fn not_found(message: &str) -> (StatusCode, Json<response::Response>) {
( (
StatusCode::NOT_FOUND, StatusCode::NOT_FOUND,
@@ -96,7 +92,7 @@ pub mod endpoint {
Ok(user) => { Ok(user) => {
if hashing::verify_password(&payload.password, user.password.clone()).unwrap() { if hashing::verify_password(&payload.password, user.password.clone()).unwrap() {
// Create token // Create token
let key = icarus_envy::environment::get_secret_key().await; let key = icarus_envy::environment::get_secret_key().await.value;
let (token_literal, duration) = let (token_literal, duration) =
token_stuff::create_token(&key, &user.id).unwrap(); token_stuff::create_token(&key, &user.id).unwrap();
@@ -118,7 +114,7 @@ pub mod endpoint {
}), }),
) )
} else { } else {
return not_found("Could not verify password").await; return not_found("Could not verify token").await;
} }
} else { } else {
return not_found("Error Hashing").await; return not_found("Error Hashing").await;
@@ -154,15 +150,15 @@ pub mod endpoint {
let mut response = response::service_login::Response::default(); let mut response = response::service_login::Response::default();
match repo::service::valid_passphrase(&pool, &payload.passphrase).await { match repo::service::valid_passphrase(&pool, &payload.passphrase).await {
Ok((id, _passphrase, _date_created)) => { Ok((id, username, _date_created)) => {
let key = icarus_envy::environment::get_secret_key().await; let key = icarus_envy::environment::get_secret_key().await.value;
let (token_literal, duration) = let (token_literal, duration) =
token_stuff::create_service_token(&key, &id).unwrap(); token_stuff::create_service_token(&key, &id).unwrap();
if token_stuff::verify_token(&key, &token_literal) { if token_stuff::verify_token(&key, &token_literal) {
let login_result = icarus_models::login_result::LoginResult { let login_result = icarus_models::login_result::LoginResult {
id, id,
username: String::from(SERVICE_USERNAME), username,
token: token_literal, token: token_literal,
token_type: String::from(icarus_models::token::TOKEN_TYPE), token_type: String::from(icarus_models::token::TOKEN_TYPE),
expiration: duration, expiration: duration,
@@ -207,7 +203,7 @@ pub mod endpoint {
axum::Json<response::refresh_token::Response>, axum::Json<response::refresh_token::Response>,
) { ) {
let mut response = response::refresh_token::Response::default(); let mut response = response::refresh_token::Response::default();
let key = icarus_envy::environment::get_secret_key().await; let key = icarus_envy::environment::get_secret_key().await.value;
if token_stuff::verify_token(&key, &payload.access_token) { if token_stuff::verify_token(&key, &payload.access_token) {
let token_type = token_stuff::get_token_type(&key, &payload.access_token).unwrap(); let token_type = token_stuff::get_token_type(&key, &payload.access_token).unwrap();
@@ -216,15 +212,15 @@ pub mod endpoint {
// Get passphrase record with id // Get passphrase record with id
match token_stuff::extract_id_from_token(&key, &payload.access_token) { match token_stuff::extract_id_from_token(&key, &payload.access_token) {
Ok(id) => match repo::service::get_passphrase(&pool, &id).await { Ok(id) => match repo::service::get_passphrase(&pool, &id).await {
Ok((returned_id, _, _)) => { Ok((username, _, _)) => {
match token_stuff::create_service_refresh_token(&key, &returned_id) { match token_stuff::create_service_refresh_token(&key, &id) {
Ok((access_token, exp_dur)) => { Ok((access_token, exp_dur)) => {
let login_result = icarus_models::login_result::LoginResult { let login_result = icarus_models::login_result::LoginResult {
id: returned_id, id,
token: access_token, token: access_token,
expiration: exp_dur, expiration: exp_dur,
token_type: String::from(icarus_models::token::TOKEN_TYPE), token_type: String::from(icarus_models::token::TOKEN_TYPE),
username: String::from(SERVICE_USERNAME), username,
}; };
response.message = String::from("Successful"); response.message = String::from("Successful");
response.data.push(login_result); response.data.push(login_result);

View File

@@ -52,69 +52,108 @@ pub async fn register_user(
axum::Extension(pool): axum::Extension<sqlx::PgPool>, axum::Extension(pool): axum::Extension<sqlx::PgPool>,
Json(payload): Json<request::Request>, Json(payload): Json<request::Request>,
) -> (StatusCode, Json<response::Response>) { ) -> (StatusCode, Json<response::Response>) {
let mut user = icarus_models::user::User { let registration_enabled = match is_registration_enabled().await {
id: uuid::Uuid::nil(), Ok(value) => value,
username: payload.username.clone(), Err(err) => {
password: payload.password.clone(), eprintln!("Error: {err:?}");
email: payload.email.clone(), return (
phone: payload.phone.clone(), axum::http::StatusCode::INTERNAL_SERVER_ERROR,
firstname: payload.firstname.clone(), Json(response::Response {
lastname: payload.lastname.clone(), message: String::from("Registration check failed"),
status: String::from("Active"), data: Vec::new(),
email_verified: true, }),
date_created: Some(time::OffsetDateTime::now_utc()), );
last_login: None, }
salt_id: uuid::Uuid::nil(),
}; };
match repo::user::exists(&pool, &user.username).await { if registration_enabled {
Ok(res) => { let mut user = icarus_models::user::User {
if res { username: payload.username.clone(),
( password: payload.password.clone(),
StatusCode::NOT_FOUND, email: payload.email.clone(),
Json(response::Response { phone: payload.phone.clone(),
message: String::from("Error"), firstname: payload.firstname.clone(),
data: vec![user], lastname: payload.lastname.clone(),
}), status: String::from("Active"),
) email_verified: true,
} else { ..Default::default()
let salt_string = hashing::generate_salt().unwrap(); };
let mut salt = icarus_models::user::salt::Salt::default();
let generated_salt = salt_string;
salt.salt = generated_salt.to_string();
salt.id = repo::salt::insert(&pool, &salt).await.unwrap();
user.salt_id = salt.id;
let hashed_password =
hashing::hash_password(&user.password, &generated_salt).unwrap();
user.password = hashed_password;
match repo::user::insert(&pool, &user).await { match repo::user::exists(&pool, &user.username).await {
Ok(id) => { Ok(res) => {
user.id = id; if res {
( (
StatusCode::CREATED,
Json(response::Response {
message: String::from("User created"),
data: vec![user],
}),
)
}
Err(err) => (
StatusCode::BAD_REQUEST, StatusCode::BAD_REQUEST,
Json(response::Response { Json(response::Response {
message: err.to_string(), message: String::from("Error"),
data: vec![user], data: Vec::new(),
}), }),
), )
} else {
let salt_string = hashing::generate_salt().unwrap();
let mut salt = icarus_models::user::salt::Salt::default();
let generated_salt = salt_string;
salt.salt = generated_salt.to_string();
salt.id = repo::salt::insert(&pool, &salt).await.unwrap();
user.salt_id = salt.id;
let hashed_password =
hashing::hash_password(&user.password, &generated_salt).unwrap();
user.password = hashed_password;
match repo::user::insert(&pool, &user).await {
Ok((id, date_created)) => {
user.id = id;
user.date_created = date_created;
(
StatusCode::CREATED,
Json(response::Response {
message: String::from("User created"),
data: vec![user],
}),
)
}
Err(err) => (
StatusCode::BAD_REQUEST,
Json(response::Response {
message: err.to_string(),
data: vec![user],
}),
),
}
} }
} }
Err(err) => (
StatusCode::BAD_REQUEST,
Json(response::Response {
message: err.to_string(),
data: vec![user],
}),
),
} }
Err(err) => ( } else {
StatusCode::BAD_REQUEST, (
axum::http::StatusCode::NOT_ACCEPTABLE,
Json(response::Response { Json(response::Response {
message: err.to_string(), message: String::from("Registration is not enabled"),
data: vec![user], data: Vec::new(),
}), }),
), )
}
}
/// Checks to see if registration is enabled
async fn is_registration_enabled() -> Result<bool, std::io::Error> {
let key = String::from("ENABLE_REGISTRATION");
let var = icarus_envy::environment::get_env(&key).await;
let parsed_value = var.value.to_uppercase();
if parsed_value == "TRUE" {
Ok(true)
} else if parsed_value == "FALSE" {
Ok(false)
} else {
Err(std::io::Error::other(
"Could not determine value of ENABLE_REGISTRATION",
))
} }
} }

View File

@@ -6,5 +6,5 @@ fn get_address() -> String {
} }
fn get_port() -> String { fn get_port() -> String {
String::from("3000") String::from("8001")
} }

20
src/db/init.rs Normal file
View File

@@ -0,0 +1,20 @@
use sqlx::postgres::PgPoolOptions;
pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> {
let database_url = icarus_envy::environment::get_db_url().await.value;
println!("Database url: {database_url}");
PgPoolOptions::new()
.max_connections(super::connection_settings::MAXCONN)
.connect(&database_url)
.await
}
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");
}

5
src/db/mod.rs Normal file
View File

@@ -0,0 +1,5 @@
pub mod init;
mod connection_settings {
pub const MAXCONN: u32 = 5;
}

View File

@@ -1,35 +0,0 @@
pub mod callers;
pub mod config;
pub mod hashing;
pub mod repo;
pub mod token_stuff;
mod connection_settings {
pub const MAXCONN: u32 = 5;
}
pub mod db {
use sqlx::postgres::PgPoolOptions;
use crate::connection_settings;
pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> {
let database_url = icarus_envy::environment::get_db_url().await;
println!("Database url: {database_url}");
PgPoolOptions::new()
.max_connections(connection_settings::MAXCONN)
.connect(&database_url)
.await
}
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

@@ -1,5 +1,9 @@
use icarus_auth::callers; pub mod callers;
use icarus_auth::config; pub mod config;
pub mod db;
pub mod hashing;
pub mod repo;
pub mod token_stuff;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
@@ -8,7 +12,7 @@ async fn main() {
let app = init::app().await; let app = init::app().await;
// run our app with hyper, listening globally on port 3000 // run our app with hyper, listening globally on port 8001
let url = config::get_full(); let url = config::get_full();
let listener = tokio::net::TcpListener::bind(url).await.unwrap(); let listener = tokio::net::TcpListener::bind(url).await.unwrap();
axum::serve(listener, app).await.unwrap(); axum::serve(listener, app).await.unwrap();
@@ -21,7 +25,7 @@ mod init {
}; };
use utoipa::OpenApi; use utoipa::OpenApi;
use crate::callers; use super::callers;
use callers::common as common_callers; use callers::common as common_callers;
use callers::login as login_caller; use callers::login as login_caller;
use callers::register as register_caller; use callers::register as register_caller;
@@ -45,6 +49,54 @@ mod init {
)] )]
struct ApiDoc; struct ApiDoc;
mod cors {
pub async fn configure_cors() -> tower_http::cors::CorsLayer {
// Start building the CORS layer with common settings
let cors = tower_http::cors::CorsLayer::new()
.allow_methods([
axum::http::Method::GET,
axum::http::Method::POST,
axum::http::Method::PUT,
axum::http::Method::DELETE,
]) // Specify allowed methods:cite[2]
.allow_headers([
axum::http::header::CONTENT_TYPE,
axum::http::header::AUTHORIZATION,
]) // Specify allowed headers:cite[2]
.allow_credentials(true) // If you need to send cookies or authentication headers:cite[2]
.max_age(std::time::Duration::from_secs(3600)); // Cache the preflight response for 1 hour:cite[2]
// Dynamically set the allowed origin based on the environment
match std::env::var(icarus_envy::keys::APP_ENV).as_deref() {
Ok("production") => {
let allowed_origins_env = icarus_envy::environment::get_allowed_origins().await;
match icarus_envy::utility::delimitize(&allowed_origins_env) {
Ok(alwd) => {
let allowed_origins: Vec<axum::http::HeaderValue> = alwd
.into_iter()
.map(|s| s.parse::<axum::http::HeaderValue>().unwrap())
.collect();
cors.allow_origin(allowed_origins)
}
Err(err) => {
eprintln!(
"Could not parse out allowed origins from env: Error: {err:?}"
);
std::process::exit(-1);
}
}
}
_ => {
// Development (default): Allow localhost origins
cors.allow_origin(vec![
"http://localhost:4200".parse().unwrap(),
"http://127.0.0.1:4200".parse().unwrap(),
])
}
}
}
}
pub async fn routes() -> Router { pub async fn routes() -> Router {
// build our application with a route // build our application with a route
Router::new() Router::new()
@@ -72,14 +124,15 @@ mod init {
callers::endpoints::REFRESH_TOKEN, callers::endpoints::REFRESH_TOKEN,
post(callers::login::endpoint::refresh_token), post(callers::login::endpoint::refresh_token),
) )
.layer(cors::configure_cors().await)
} }
pub async fn app() -> Router { pub async fn app() -> Router {
let pool = icarus_auth::db::create_pool() let pool = super::db::init::create_pool()
.await .await
.expect("Failed to create pool"); .expect("Failed to create pool");
icarus_auth::db::migrations(&pool).await; super::db::init::migrations(&pool).await;
routes() routes()
.await .await
@@ -109,7 +162,7 @@ mod tests {
pub const LIMIT: usize = 6; pub const LIMIT: usize = 6;
pub async fn get_pool() -> Result<sqlx::PgPool, sqlx::Error> { pub async fn get_pool() -> Result<sqlx::PgPool, sqlx::Error> {
let tm_db_url = icarus_envy::environment::get_db_url().await; let tm_db_url = icarus_envy::environment::get_db_url().await.value;
let tm_options = sqlx::postgres::PgConnectOptions::from_str(&tm_db_url).unwrap(); let tm_options = sqlx::postgres::PgConnectOptions::from_str(&tm_db_url).unwrap();
sqlx::PgPool::connect_with(tm_options).await sqlx::PgPool::connect_with(tm_options).await
} }
@@ -122,7 +175,7 @@ mod tests {
} }
pub async fn connect_to_db(db_name: &str) -> Result<sqlx::PgPool, sqlx::Error> { pub async fn connect_to_db(db_name: &str) -> Result<sqlx::PgPool, sqlx::Error> {
let db_url = icarus_envy::environment::get_db_url().await; let db_url = icarus_envy::environment::get_db_url().await.value;
let options = sqlx::postgres::PgConnectOptions::from_str(&db_url)?.database(db_name); let options = sqlx::postgres::PgConnectOptions::from_str(&db_url)?.database(db_name);
sqlx::PgPool::connect_with(options).await sqlx::PgPool::connect_with(options).await
} }
@@ -149,7 +202,7 @@ mod tests {
} }
pub async fn get_database_name() -> Result<String, Box<dyn std::error::Error>> { pub async fn get_database_name() -> Result<String, Box<dyn std::error::Error>> {
let database_url = icarus_envy::environment::get_db_url().await; let database_url = icarus_envy::environment::get_db_url().await.value;
let parsed_url = url::Url::parse(&database_url)?; let parsed_url = url::Url::parse(&database_url)?;
if parsed_url.scheme() == "postgres" || parsed_url.scheme() == "postgresql" { if parsed_url.scheme() == "postgres" || parsed_url.scheme() == "postgresql" {
@@ -167,8 +220,8 @@ mod tests {
} }
} }
fn get_test_register_request() -> icarus_auth::callers::register::request::Request { fn get_test_register_request() -> callers::register::request::Request {
icarus_auth::callers::register::request::Request { callers::register::request::Request {
username: String::from("somethingsss"), username: String::from("somethingsss"),
password: String::from("Raindown!"), password: String::from("Raindown!"),
email: String::from("dev@null.com"), email: String::from("dev@null.com"),
@@ -178,9 +231,7 @@ mod tests {
} }
} }
fn get_test_register_payload( fn get_test_register_payload(usr: &callers::register::request::Request) -> serde_json::Value {
usr: &icarus_auth::callers::register::request::Request,
) -> serde_json::Value {
json!({ json!({
"username": &usr.username, "username": &usr.username,
"password": &usr.password, "password": &usr.password,
@@ -196,7 +247,7 @@ mod tests {
pub async fn register( pub async fn register(
app: &axum::Router, app: &axum::Router,
usr: &icarus_auth::callers::register::request::Request, usr: &super::callers::register::request::Request,
) -> Result<axum::response::Response, std::convert::Infallible> { ) -> Result<axum::response::Response, std::convert::Infallible> {
let payload = super::get_test_register_payload(&usr); let payload = super::get_test_register_payload(&usr);
let req = axum::http::Request::builder() let req = axum::http::Request::builder()
@@ -249,7 +300,7 @@ mod tests {
let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
icarus_auth::db::migrations(&pool).await; db::init::migrations(&pool).await;
let app = init::routes().await.layer(axum::Extension(pool)); let app = init::routes().await.layer(axum::Extension(pool));
@@ -306,7 +357,7 @@ mod tests {
let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
icarus_auth::db::migrations(&pool).await; db::init::migrations(&pool).await;
let app = init::routes().await.layer(axum::Extension(pool)); let app = init::routes().await.layer(axum::Extension(pool));
@@ -394,7 +445,7 @@ mod tests {
let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
icarus_auth::db::migrations(&pool).await; db::init::migrations(&pool).await;
let app = init::routes().await.layer(axum::Extension(pool)); let app = init::routes().await.layer(axum::Extension(pool));
let passphrase = let passphrase =
@@ -448,13 +499,13 @@ mod tests {
let pool = db_mgr::connect_to_db(&db_name).await.unwrap(); let pool = db_mgr::connect_to_db(&db_name).await.unwrap();
icarus_auth::db::migrations(&pool).await; db::init::migrations(&pool).await;
let app = init::routes().await.layer(axum::Extension(pool)); let app = init::routes().await.layer(axum::Extension(pool));
let id = uuid::Uuid::parse_str("22f9c775-cce9-457a-a147-9dafbb801f61").unwrap(); let id = uuid::Uuid::parse_str("22f9c775-cce9-457a-a147-9dafbb801f61").unwrap();
let key = icarus_envy::environment::get_secret_key().await; let key = icarus_envy::environment::get_secret_key().await.value;
match icarus_auth::token_stuff::create_service_token(&key, &id) { match token_stuff::create_service_token(&key, &id) {
Ok((token, _expire)) => { Ok((token, _expire)) => {
let payload = serde_json::json!({ let payload = serde_json::json!({
"access_token": token "access_token": token

View File

@@ -1,3 +1,5 @@
pub mod service;
pub mod user { pub mod user {
use sqlx::Row; use sqlx::Row;
@@ -94,7 +96,7 @@ pub mod user {
pub async fn insert( pub async fn insert(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
user: &icarus_models::user::User, user: &icarus_models::user::User,
) -> Result<uuid::Uuid, sqlx::Error> { ) -> Result<(uuid::Uuid, std::option::Option<time::OffsetDateTime>), sqlx::Error> {
let row = sqlx::query( let row = sqlx::query(
r#" r#"
INSERT INTO "user" (username, password, email, phone, firstname, lastname, email_verified, status, salt_id) INSERT INTO "user" (username, password, email, phone, firstname, lastname, email_verified, status, salt_id)
@@ -124,10 +126,10 @@ pub mod user {
.map_err(|_e| sqlx::Error::RowNotFound)?, .map_err(|_e| sqlx::Error::RowNotFound)?,
}; };
if !result.id.is_nil() { if result.id.is_nil() && result.date_created.is_none() {
Ok(result.id)
} else {
Err(sqlx::Error::RowNotFound) Err(sqlx::Error::RowNotFound)
} else {
Ok((result.id, result.date_created))
} }
} }
} }
@@ -195,56 +197,3 @@ 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),
}
}
}

50
src/repo/service.rs Normal file
View File

@@ -0,0 +1,50 @@
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 id, username, date_created FROM "passphrase" WHERE passphrase = $1
"#,
)
.bind(passphrase)
.fetch_one(pool)
.await;
match result {
Ok(row) => {
let id: uuid::Uuid = row.try_get("id")?;
let username: String = row.try_get("username")?;
let date_created: Option<time::OffsetDateTime> = row.try_get("date_created")?;
Ok((id, username, date_created.unwrap()))
}
Err(err) => Err(err),
}
}
pub async fn get_passphrase(
pool: &sqlx::PgPool,
id: &uuid::Uuid,
) -> Result<(String, String, time::OffsetDateTime), sqlx::Error> {
let result = sqlx::query(
r#"
SELECT username, passphrase, date_created FROM "passphrase" WHERE id = $1;
"#,
)
.bind(id)
.fetch_one(pool)
.await;
match result {
Ok(row) => {
let username: String = row.try_get("username")?;
let passphrase: String = row.try_get("passphrase")?;
let date_created: time::OffsetDateTime = row.try_get("date_created")?;
Ok((username, passphrase, date_created))
}
Err(err) => Err(err),
}
}

View File

@@ -124,7 +124,9 @@ mod tests {
#[test] #[test]
fn test_tokenize() { fn test_tokenize() {
let rt = tokio::runtime::Runtime::new().unwrap(); let rt = tokio::runtime::Runtime::new().unwrap();
let special_key = rt.block_on(icarus_envy::environment::get_secret_key()); let special_key = rt
.block_on(icarus_envy::environment::get_secret_key())
.value;
let id = uuid::Uuid::new_v4(); let id = uuid::Uuid::new_v4();
match create_token(&special_key, &id) { match create_token(&special_key, &id) {
Ok((token, _duration)) => { Ok((token, _duration)) => {