7 Commits
Author SHA1 Message Date
phoenix 010ee6aef5 Adding license (#23)
schedtxt_auth Build / Rustfmt (push) Successful in 57s
schedtxt_auth Build / Test Suite (push) Successful in 1m23s
schedtxt_auth Build / Check (push) Successful in 1m23s
schedtxt_auth Build / build (push) Successful in 1m20s
schedtxt_auth Build / Clippy (push) Successful in 1m47s
Reviewed-on: #23
2026-07-14 15:27:37 -04:00
phoenix 039609a99c Dependency name change (#22)
schedtxt_auth Build / Rustfmt (push) Successful in 46s
schedtxt_auth Build / Check (push) Successful in 1m35s
schedtxt_auth Build / Clippy (push) Successful in 1m4s
schedtxt_auth Build / Test Suite (push) Successful in 2m6s
schedtxt_auth Build / build (push) Successful in 4m28s
Reviewed-on: #22
2026-07-13 17:24:39 -04:00
phoenix b3899c1480 Update rust (#21)
textsender_auth Build / Rustfmt (push) Successful in 44s
textsender_auth Build / Check (push) Successful in 1m42s
textsender_auth Build / Test Suite (push) Successful in 2m5s
textsender_auth Build / Clippy (push) Successful in 1m38s
textsender_auth Build / build (push) Successful in 3m5s
Reviewed-on: phoenix/textsender_auth#21
2026-07-10 17:42:38 -04:00
phoenix 3423c1eeb1 Update rust (#20)
Rust Build / Rustfmt (push) Successful in 48s
Rust Build / Check (push) Successful in 1m30s
Rust Build / Test Suite (push) Successful in 2m29s
Rust Build / Clippy (push) Successful in 1m47s
Rust Build / build (push) Successful in 2m13s
Rust Build / Check (pull_request) Successful in 3m34s
Rust Build / Rustfmt (pull_request) Successful in 36s
Rust Build / Clippy (pull_request) Successful in 38s
Reviewed-on: phoenix/textsender_auth#20
2026-07-06 22:03:16 -04:00
phoenix 21bfaf7657 Update dependencies (#19)
Rust Build / Rustfmt (push) Successful in 50s
Rust Build / Check (push) Successful in 1m7s
Rust Build / Test Suite (push) Failing after 2m2s
Rust Build / Clippy (push) Successful in 1m27s
Rust Build / build (push) Successful in 1m30s
Reviewed-on: phoenix/textsender_auth#19
2026-07-04 20:00:46 -04:00
phoenix 9dba08f179 Get User profile endpoint (#18)
Rust Build / Rustfmt (push) Successful in 38s
Rust Build / Test Suite (push) Failing after 1m43s
Rust Build / Check (push) Successful in 1m52s
Rust Build / Clippy (push) Successful in 1m10s
Rust Build / build (push) Successful in 2m4s
Rust Build / Rustfmt (pull_request) Successful in 1m9s
Rust Build / Clippy (pull_request) Successful in 1m17s
Rust Build / Check (pull_request) Successful in 1m36s
Reviewed-on: phoenix/textsender_auth#18
2026-07-01 22:37:25 -04:00
phoenix 438b19e170 Update dependencies (#17)
Rust Build / Rustfmt (push) Successful in 44s
Rust Build / Clippy (push) Successful in 1m10s
Rust Build / Test Suite (push) Failing after 2m0s
Rust Build / build (push) Successful in 2m7s
Rust Build / Check (push) Successful in 1m12s
Rust Build / Check (pull_request) Has been cancelled
Rust Build / Clippy (pull_request) Has been cancelled
Rust Build / Rustfmt (pull_request) Has been cancelled
Reviewed-on: phoenix/textsender_auth#17
2026-06-27 17:47:19 -04:00
20 changed files with 710 additions and 1199 deletions
+4 -3
View File
@@ -1,10 +1,11 @@
SECRET_KEY=NULqYIzgt28bTiyziCd7IOO7b6LnWDW! SECRET_KEY=NULqYIzgt28bTiyziCd7IOO7b6LnWDW!
DB_AUTH_NAME=textsender_auth_db DB_AUTH_NAME=schedtxt_auth_db
DB_AUTH_USER=textsender_auth DB_AUTH_USER=schedtxt_auth
DB_AUTH_PASSWORD=password DB_AUTH_PASSWORD=password
DB_AUTH_HOST=auth_db DB_AUTH_HOST=auth_db
DB_AUTH_PORT=5432 DB_AUTH_PORT=5432
DB_AUTH_SSLMODE=disable DB_AUTH_SSLMODE=disable
DATABASE_URL=postgres://${DB_AUTH_USER}:${DB_AUTH_PASSWORD}@${DB_AUTH_HOST}:${DB_AUTH_PORT}/${DB_AUTH_NAME} DATABASE_URL=postgres://${DB_AUTH_USER}:${DB_AUTH_PASSWORD}@${DB_AUTH_HOST}:${DB_AUTH_PORT}/${DB_AUTH_NAME}
ENABLE_REGISTRATION=true ENABLE_REGISTRATION=true
ALLOWED_ORIGINS="http://textsender.com" ALLOWED_ORIGINS="http://schedtxt.com,http://localhost:5173,http://localhost:9080"
APP_ENV=production
+4 -3
View File
@@ -1,10 +1,11 @@
SECRET_KEY=NULqYIzgt28bTiyziCd7IOO7b6LnWDW! SECRET_KEY=NULqYIzgt28bTiyziCd7IOO7b6LnWDW!
DB_AUTH_NAME=textsender_auth_db DB_AUTH_NAME=schedtxt_auth_db
DB_AUTH_USER=textsender_auth DB_AUTH_USER=schedtxt_auth
DB_AUTH_PASSWORD=password DB_AUTH_PASSWORD=password
DB_AUTH_HOST=localhost DB_AUTH_HOST=localhost
DB_AUTH_PORT=5432 DB_AUTH_PORT=5432
DB_AUTH_SSLMODE=disable DB_AUTH_SSLMODE=disable
DATABASE_URL=postgres://${DB_AUTH_USER}:${DB_AUTH_PASSWORD}@${DB_AUTH_HOST}:${DB_AUTH_PORT}/${DB_AUTH_NAME} DATABASE_URL=postgres://${DB_AUTH_USER}:${DB_AUTH_PASSWORD}@${DB_AUTH_HOST}:${DB_AUTH_PORT}/${DB_AUTH_NAME}
ENABLE_REGISTRATION=true ENABLE_REGISTRATION=true
ALLOWED_ORIGINS="http://textsender.com" ALLOWED_ORIGINS="http://schedtxt.com,http://localhost:5173,http://localhost:9080"
APP_ENV=production
+20 -16
View File
@@ -1,28 +1,32 @@
name: Rust Build name: pr Build
on: on:
pull_request: pull_request:
branches: branches:
- main - main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
check: check:
name: Check name: Check
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/schedtxt-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/schedtxt-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/schedtxt-models_deploy_key
cargo check cargo check
@@ -31,38 +35,38 @@ jobs:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt - run: rustup component add rustfmt
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/schedtxt-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/schedtxt-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/schedtxt-models_deploy_key
cargo fmt --all -- --check cargo fmt --all -- --check
clippy: clippy:
name: Clippy name: Clippy
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: rustup component add clippy - run: rustup component add clippy
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/schedtxt-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/schedtxt-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/schedtxt-models_deploy_key
cargo clippy -- -D warnings cargo clippy -- -D warnings
+30 -26
View File
@@ -1,28 +1,32 @@
name: Rust Build name: schedtxt_auth Build
on: on:
push: push:
branches: branches:
- main - main
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs: jobs:
check: check:
name: Check name: Check
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/schedtxt-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/schedtxt-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/schedtxt-models_deploy_key
cargo check cargo check
@@ -47,10 +51,10 @@ jobs:
--health-retries 5 --health-retries 5
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
# --- Add this step for explicit verification --- # --- Add this step for explicit verification ---
- name: Verify Docker Environment - name: Verify Docker Environment
@@ -77,12 +81,12 @@ jobs:
ENABLE_REGISTRATION: 'TRUE' ENABLE_REGISTRATION: 'TRUE'
run: | run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/schedtxt-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/schedtxt-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/schedtxt-models_deploy_key
cargo test cargo test
@@ -90,57 +94,57 @@ jobs:
name: Rustfmt name: Rustfmt
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: rustup component add rustfmt - run: rustup component add rustfmt
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/schedtxt-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/schedtxt-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/schedtxt-models_deploy_key
cargo fmt --all -- --check cargo fmt --all -- --check
clippy: clippy:
name: Clippy name: Clippy
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: rustup component add clippy - run: rustup component add clippy
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/schedtxt-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/schedtxt-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/schedtxt-models_deploy_key
cargo clippy -- -D warnings cargo clippy -- -D warnings
build: build:
name: build name: build
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
steps: steps:
- uses: actions/checkout@v5 - uses: actions/checkout@v6
- uses: actions-rust-lang/setup-rust-toolchain@v1 - uses: actions-rust-lang/setup-rust-toolchain@v1
with: with:
toolchain: 1.96 toolchain: 1.97
- uses: Swatinem/rust-cache@v2 - uses: Swatinem/rust-cache@v2
- run: | - run: |
mkdir -p ~/.ssh mkdir -p ~/.ssh
echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/schedtxt-models_deploy_key
chmod 600 ~/.ssh/textsender-models_deploy_key chmod 600 ~/.ssh/schedtxt-models_deploy_key
ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts
eval $(ssh-agent -s) eval $(ssh-agent -s)
ssh-add -v ~/.ssh/textsender-models_deploy_key ssh-add -v ~/.ssh/schedtxt-models_deploy_key
cargo build --release cargo build --release
Generated
+241 -900
View File
File diff suppressed because it is too large Load Diff
+12 -15
View File
@@ -1,8 +1,10 @@
[package] [package]
name = "textsender_auth" name = "schedtxt_auth"
version = "0.1.23" version = "0.2.4"
edition = "2024" edition = "2024"
rust-version = "1.96" license = "MIT"
description = "Auth API for sending text messages"
rust-version = "1.97"
[dependencies] [dependencies]
axum = { version = "0.8.9" } axum = { version = "0.8.9" }
@@ -10,21 +12,16 @@ serde = { version = "1.0.228", features = ["derive"] }
serde_json = { version = "1.0.150" } serde_json = { version = "1.0.150" }
tokio = { version = "1.52.3", features = ["rt-multi-thread"] } tokio = { version = "1.52.3", features = ["rt-multi-thread"] }
tracing-subscriber = { version = "0.3.23" } tracing-subscriber = { version = "0.3.23" }
tower = { version = "0.5.3", features = ["full"] } tower-http = { version = "0.7.0", features = ["cors"] }
tower-http = { version = "0.6.11", features = ["cors"] } sqlx = { version = "0.9.0", features = ["runtime-tokio", "tls-native-tls", "postgres", "time", "uuid"] }
hyper = { version = "1.10.1" } uuid = { version = "1.23.5", features = ["v4", "serde"] }
sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio-native-tls", "time", "uuid"] } argon2 = { version = "0.5.3", features = ["std"] }
uuid = { version = "1.23.3", features = ["v4", "serde"] } time = { version = "0.3.53", features = ["macros", "serde"] }
argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x version
rand = { version = "0.10.1" }
time = { version = "0.3.49", features = ["macros", "serde"] }
josekit = { version = "0.10.3" } josekit = { version = "0.10.3" }
utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa = { version = "5.5.0", features = ["axum_extras"] }
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender_models.git", tag = "v0.4.0", features = ["config", "user"] } schedtxt_models = { git = "ssh://git@git.kundeng.us/phoenix/schedtxt_models.git", tag = "v0.5.3", features = ["config", "user"] }
[dev-dependencies] [dev-dependencies]
http-body-util = { version = "0.1.3" } tower = { version = "0.5.3", features = ["full"] }
url = { version = "2.5.8" } url = { version = "2.5.8" }
once_cell = { version = "1.21.4" } # Useful for lazy initialization in tests/app setup
async-std = { version = "1.13.2" }
+4 -30
View File
@@ -1,14 +1,7 @@
# Stage 1: Build the application FROM rust:1.97 as builder
# Use a specific Rust version for reproducibility. Choose one that matches your development environment.
# Using slim variant for smaller base image
FROM rust:1.96 as builder
# Set the working directory inside the container
WORKDIR /usr/src/app 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) # Install build dependencies if needed (e.g., git for cloning)
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
pkg-config libssl3 \ pkg-config libssl3 \
@@ -16,55 +9,36 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
openssh-client git \ openssh-client git \
&& rm -rf /var/lib/apt/lists/* && 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 && \ RUN mkdir -p -m 0700 ~/.ssh && \
ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts
# Copy Cargo manifests
COPY Cargo.toml Cargo.lock ./ 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 && \ RUN --mount=type=ssh mkdir src && \
echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs && \ echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs && \
cargo build --release --quiet && \ cargo build --release --quiet && \
rm -rf src target/release/deps/textsender_auth* # Clean up dummy build artifacts rm -rf src target/release/deps/schedtxt_auth*
# Copy the actual source code
COPY src ./src COPY src ./src
# If you have other directories like `templates` or `static`, copy them too
COPY .env ./.env COPY .env ./.env
COPY migrations ./migrations 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 \ RUN --mount=type=ssh \
cargo build --release --quiet 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 debian:trixie-slim FROM debian:trixie-slim
# Install runtime dependencies if needed (e.g., SSL certificates) # 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/* 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 WORKDIR /usr/local/bin
# Copy the compiled binary from the builder stage COPY --from=builder /usr/src/app/target/release/schedtxt_auth .
COPY --from=builder /usr/src/app/target/release/textsender_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/.env .
COPY --from=builder /usr/src/app/migrations ./migrations COPY --from=builder /usr/src/app/migrations ./migrations
# Expose the port your Axum app listens on (e.g., 3000 or 8000)
EXPOSE 9080 EXPOSE 9080
# Set the command to run your application # Set the command to run your application
# Ensure this matches the binary name copied above CMD ["./schedtxt_auth"]
CMD ["./textsender_auth"]
+22
View File
@@ -0,0 +1,22 @@
Copyright (c) 2026 Kun Deng.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
+21
View File
@@ -1 +1,22 @@
# schedtxt_auth
Auth API for `schedtxt` project.
## Getting started
Quickest way to get started is with docker, make sure that it is installed. Copy over
the `.env.docker.sample` file and name it `.env`.
For `SECRET_KEY` provide a key that is at least 32-characters. This is used for token
creation. Additionally, provide credentials for the database for the `DB_*` keys.
Make sure that `ENABLE_REGISTRATION` is set to true, otherwise registration will not work.
Build the container
```
docker compose build
```
Bring up the container
```
docker compose up
```
+11 -15
View File
@@ -1,31 +1,28 @@
version: '3.8' # Use a recent version version: '3.8' # Use a recent version
services: services:
# Your Rust Application Service
auth_api: auth_api:
build: # Tells docker-compose to build the Dockerfile in the current directory build:
context: . context: .
ssh: ["default"] # Uses host's SSH agent ssh: ["default"]
container_name: textsender_auth # Optional: Give the container a specific name container_name: schedtxt_auth
ports: ports:
# Map host port 8000 to container port 3000 (adjust as needed)
- "9080:9080" - "9080:9080"
env_file: env_file:
- .env - .env
depends_on: depends_on:
auth_db: auth_db:
condition: service_healthy # Wait for the DB to be healthy before starting the app condition: service_healthy
restart: unless-stopped # Optional: Restart policy restart: unless-stopped
# PostgreSQL Database Service # PostgreSQL Database Service
auth_db: auth_db:
image: postgres:18.4-alpine # Use an official Postgres image (Alpine variant is smaller) image: postgres:18.4-alpine
container_name: textsender_auth_db # Optional: Give the container a specific name container_name: schedtxt_auth_db
environment: environment:
# These MUST match the user, password, and database name in the DATABASE_URL above POSTGRES_USER: ${DB_AUTH_USER:-schedtxt_op}
POSTGRES_USER: ${DB_AUTH_USER:-textsender_op}
POSTGRES_PASSWORD: ${DB_AUTH_PASSWORD:-password} POSTGRES_PASSWORD: ${DB_AUTH_PASSWORD:-password}
POSTGRES_DB: ${DB_AUTH_NAME:-textsender_auth_db} POSTGRES_DB: ${DB_AUTH_NAME:-schedtxt_auth_db}
volumes: volumes:
# Persist database data using a named volume # Persist database data using a named volume
- postgres_data:/var/lib/postgresql - postgres_data:/var/lib/postgresql
@@ -38,9 +35,8 @@ services:
timeout: 5s timeout: 5s
retries: 5 retries: 5
start_period: 10s start_period: 10s
restart: always # Optional: Restart policy restart: always
# Define the named volume for data persistence
volumes: volumes:
postgres_data: postgres_data:
driver: local # Use the default local driver driver: local
+141 -101
View File
@@ -84,29 +84,37 @@ pub mod response {
#[derive(Default, Deserialize, Serialize, utoipa::ToSchema)] #[derive(Default, Deserialize, Serialize, utoipa::ToSchema)]
pub struct LoginResponse { pub struct LoginResponse {
pub message: String, pub message: String,
pub data: Vec<textsender_models::token::LoginResult>, pub data: Vec<schedtxt_models::token::LoginResult>,
} }
#[derive(Default, Deserialize, Serialize, utoipa::ToSchema)] #[derive(Default, Deserialize, Serialize, utoipa::ToSchema)]
pub struct ServiceUserLoginResponse { pub struct ServiceUserLoginResponse {
pub message: String, pub message: String,
pub data: Vec<textsender_models::token::LoginResult>, pub data: Vec<schedtxt_models::token::LoginResult>,
} }
pub async fn extract( pub async fn extract(
response: axum::response::Response, response: axum::response::Response,
) -> Result<LoginResponse, std::io::Error> { ) -> Result<LoginResponse, std::io::Error> {
let body = axum::body::to_bytes(response.into_body(), usize::MAX) let body = match axum::body::to_bytes(response.into_body(), usize::MAX).await {
.await Ok(body) => body,
.unwrap(); Err(err) => {
let _parsed_body: LoginResponse = serde_json::from_slice(&body).unwrap(); return Err(std::io::Error::other(err));
todo!("Add code to convert axum::Response to this type"); }
};
let parsed_body: LoginResponse = match serde_json::from_slice(&body) {
Ok(body) => body,
Err(err) => {
return Err(std::io::Error::other(err));
}
};
Ok(parsed_body)
} }
#[derive(Deserialize, Serialize, utoipa::ToSchema)] #[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct RefreshTokenResponse { pub struct RefreshTokenResponse {
pub message: String, pub message: String,
pub data: Vec<textsender_models::token::LoginResult>, pub data: Vec<schedtxt_models::token::LoginResult>,
} }
#[derive(Deserialize, Serialize, utoipa::ToSchema)] #[derive(Deserialize, Serialize, utoipa::ToSchema)]
@@ -118,7 +126,13 @@ pub mod response {
#[derive(Deserialize, Serialize, utoipa::ToSchema)] #[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct UserUpdateNameResponse { pub struct UserUpdateNameResponse {
pub message: String, pub message: String,
pub data: Vec<textsender_models::user::User>, pub data: Vec<schedtxt_models::user::User>,
}
#[derive(Default, Deserialize, Serialize, utoipa::ToSchema)]
pub struct GetUserProfileResponse {
pub message: String,
pub data: Vec<schedtxt_models::user::UserProfile>,
} }
} }
@@ -186,13 +200,22 @@ pub async fn user_login(
Ok(matches) => { Ok(matches) => {
if matches { if matches {
// Create token // Create token
let key = textsender_models::envy::environment::get_secret_key() let key =
.await schedtxt_models::envy::environment::get_secret_key().value;
.value; let cst = match token_stuff::create_token(&key, &user.id) {
let (token_literal, duration) = Ok(token) => token,
token_stuff::create_token(&key, &user.id).unwrap(); Err(_err) => {
return (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response::LoginResponse {
message: String::from("Error creating token"),
data: Vec::new(),
}),
);
}
};
if token_stuff::verify_token(&key, &token_literal) { if token_stuff::verify_token(&key, &cst.access_token) {
let current_time = time::OffsetDateTime::now_utc(); let current_time = time::OffsetDateTime::now_utc();
let _ = let _ =
repo::user::update_last_login(&pool, &user, &current_time) repo::user::update_last_login(&pool, &user, &current_time)
@@ -202,14 +225,14 @@ pub async fn user_login(
axum::http::StatusCode::OK, axum::http::StatusCode::OK,
axum::Json(response::LoginResponse { axum::Json(response::LoginResponse {
message: String::from("Successful"), message: String::from("Successful"),
data: vec![textsender_models::token::LoginResult { data: vec![schedtxt_models::token::LoginResult {
user_id: user.id, user_id: user.id,
access_token: token_literal, access_token: cst.access_token,
token_type: String::from( token_type: String::from(
textsender_models::token::TOKEN_TYPE, schedtxt_models::token::TOKEN_TYPE,
), ),
issued_at: duration, issued_at: cst.issued,
..Default::default() expires_in: cst.expires_in,
}], }],
}), }),
) )
@@ -327,13 +350,12 @@ pub async fn service_user_login(
if matches { if matches {
// Create token // Create token
println!("Creating token"); println!("Creating token");
let key = textsender_models::envy::environment::get_secret_key() let key =
.await schedtxt_models::envy::environment::get_secret_key().value;
.value; let cst =
let (token_literal, duration) =
token_stuff::create_token(&key, &service_user.id).unwrap(); token_stuff::create_token(&key, &service_user.id).unwrap();
if token_stuff::verify_token(&key, &token_literal) { if token_stuff::verify_token(&key, &cst.access_token) {
let current_time = time::OffsetDateTime::now_utc(); let current_time = time::OffsetDateTime::now_utc();
let _ = repo::service::update_last_login( let _ = repo::service::update_last_login(
&pool, &pool,
@@ -348,14 +370,14 @@ pub async fn service_user_login(
message: String::from( message: String::from(
super::messages::SUCCESSFUL_MESSAGE, super::messages::SUCCESSFUL_MESSAGE,
), ),
data: vec![textsender_models::token::LoginResult { data: vec![schedtxt_models::token::LoginResult {
user_id: service_user.id, user_id: service_user.id,
access_token: token_literal, access_token: cst.access_token,
token_type: String::from( token_type: String::from(
textsender_models::token::TOKEN_TYPE, schedtxt_models::token::TOKEN_TYPE,
), ),
issued_at: duration, issued_at: cst.issued,
..Default::default() expires_in: cst.expires_in,
}], }],
}), }),
) )
@@ -434,18 +456,14 @@ pub async fn refresh_token(
}), }),
) )
} else { } else {
let key = textsender_models::envy::environment::get_secret_key() let key = schedtxt_models::envy::environment::get_secret_key().value;
.await
.value;
if token_stuff::verify_token(&key, &payload.access_token) { if token_stuff::verify_token(&key, &payload.access_token) {
match token_stuff::extract_id_from_token(&key, &payload.access_token) { match token_stuff::extract_id_from_token(&key, &payload.access_token) {
Ok(id) => { Ok(id) => {
let generate_service_token = |id, key| -> (Option<String>, Option<i64>) { let generate_service_token =
match token_stuff::create_service_refresh_token(key, id) { |id, key| -> Option<schedtxt_models::token::CreateTokenResult> {
Ok((token, issued)) => (Some(token), Some(issued)), token_stuff::create_service_refresh_token(key, id).ok()
Err(_err) => (None, None), };
}
};
let mut response = response::RefreshTokenResponse { let mut response = response::RefreshTokenResponse {
message: String::new(), message: String::new(),
@@ -453,21 +471,43 @@ pub async fn refresh_token(
}; };
match repo::user::get_with_id(&pool, &id).await { match repo::user::get_with_id(&pool, &id).await {
Ok(_user) => { Ok(_user) => match generate_service_token(&id, &key) {
let (refresh_token, issued) = generate_service_token(&id, &key); Some(cst) => {
match refresh_token { response.message =
Some(token) => match issued { String::from(super::messages::SUCCESSFUL_MESSAGE);
Some(issued_at) => { let lr = schedtxt_models::token::LoginResult {
user_id: id,
access_token: cst.access_token,
issued_at: cst.issued,
expires_in: cst.expires_in,
token_type: String::from(schedtxt_models::token::TOKEN_TYPE),
};
response.data.push(lr);
(axum::http::StatusCode::OK, axum::Json(response))
}
None => (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response::RefreshTokenResponse {
message: String::from("Refresh token not generated"),
data: Vec::new(),
}),
),
},
Err(err) => {
println!("Unable to find user, checking service user: {err:?}");
match repo::service::get(&pool, &id).await {
Ok(_service_user) => match generate_service_token(&id, &key) {
Some(cst) => {
response.message = response.message =
String::from(super::messages::SUCCESSFUL_MESSAGE); String::from(super::messages::SUCCESSFUL_MESSAGE);
let lr = textsender_models::token::LoginResult { let lr = schedtxt_models::token::LoginResult {
user_id: id, user_id: id,
access_token: token, access_token: cst.access_token,
issued_at, issued_at: cst.issued,
expires_in: cst.expires_in,
token_type: String::from( token_type: String::from(
textsender_models::token::TOKEN_TYPE, schedtxt_models::token::TOKEN_TYPE,
), ),
..Default::default()
}; };
response.data.push(lr); response.data.push(lr);
(axum::http::StatusCode::OK, axum::Json(response)) (axum::http::StatusCode::OK, axum::Json(response))
@@ -480,57 +520,6 @@ pub async fn refresh_token(
}), }),
), ),
}, },
None => (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response::RefreshTokenResponse {
message: String::from("Refresh token not generated"),
data: Vec::new(),
}),
),
}
}
Err(err) => {
println!("Unable to find user, checking service user: {err:?}");
match repo::service::get(&pool, &id).await {
Ok(_service_user) => {
let (refresh_token, issued) = generate_service_token(&id, &key);
match refresh_token {
Some(token) => match issued {
Some(issued_at) => {
response.message = String::from(
super::messages::SUCCESSFUL_MESSAGE,
);
let lr = textsender_models::token::LoginResult {
user_id: id,
access_token: token,
issued_at,
token_type: String::from(
textsender_models::token::TOKEN_TYPE,
),
..Default::default()
};
response.data.push(lr);
(axum::http::StatusCode::OK, axum::Json(response))
}
None => (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response::RefreshTokenResponse {
message: String::from("Issued at not returned"),
data: Vec::new(),
}),
),
},
None => (
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response::RefreshTokenResponse {
message: String::from(
"Refresh token not generated",
),
data: Vec::new(),
}),
),
}
}
Err(err) => ( Err(err) => (
axum::http::StatusCode::INTERNAL_SERVER_ERROR, axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response::RefreshTokenResponse { axum::Json(response::RefreshTokenResponse {
@@ -796,7 +785,7 @@ pub async fn update_name_of_user(
axum::http::StatusCode::OK, axum::http::StatusCode::OK,
axum::Json(response::UserUpdateNameResponse { axum::Json(response::UserUpdateNameResponse {
message: String::from(super::messages::SUCCESSFUL_MESSAGE), message: String::from(super::messages::SUCCESSFUL_MESSAGE),
data: vec![textsender_models::user::User { data: vec![schedtxt_models::user::User {
id: user.id, id: user.id,
phone_number: user.phone_number, phone_number: user.phone_number,
firstname: payload.firstname, firstname: payload.firstname,
@@ -828,3 +817,54 @@ pub async fn update_name_of_user(
} }
} }
} }
/// Endpoint to get User Profile
#[utoipa::path(
delete,
path = super::endpoints::GET_USER_PROFILE,
params(("id" = uuid::Uuid, Path, description = "User Id")),
responses(
(status = 200, description = "Deleted", body = response::GetUserProfileResponse),
(status = 400, description = "Bad request", body = response::GetUserProfileResponse),
(status = 500, description = "Error", body = response::GetUserProfileResponse)
)
)]
pub async fn get_user_profile(
axum::Extension(pool): axum::Extension<sqlx::PgPool>,
axum::extract::Path(id): axum::extract::Path<uuid::Uuid>,
) -> (
axum::http::StatusCode,
axum::Json<response::GetUserProfileResponse>,
) {
let mut response = response::GetUserProfileResponse::default();
if id.is_nil() {
response.message = String::from("Invalid");
return (axum::http::StatusCode::BAD_REQUEST, axum::Json(response));
}
match repo::user::get_with_id(&pool, &id).await {
Ok(user) => {
let user_profile = schedtxt_models::user::UserProfile {
user_id: user.id,
phone_number: user.phone_number,
firstname: user.firstname,
lastname: user.lastname,
last_login: user.last_login,
created: user.created,
username: user.username,
};
response.message = String::from(super::messages::SUCCESSFUL_MESSAGE);
response.data.push(user_profile);
(axum::http::StatusCode::OK, axum::Json(response))
}
Err(err) => {
eprintln!("Error: {err:?}");
response.message = String::from("Bad request");
(
axum::http::StatusCode::INTERNAL_SERVER_ERROR,
axum::Json(response),
)
}
}
}
+2
View File
@@ -19,4 +19,6 @@ pub mod endpoints {
pub const UPDATE_PASSWORD: &str = "/api/v1/user/password/update"; pub const UPDATE_PASSWORD: &str = "/api/v1/user/password/update";
/// Endpoint constant for updating user's name /// Endpoint constant for updating user's name
pub const UPDATE_USER_NAME: &str = "/api/v1/user/name/update"; pub const UPDATE_USER_NAME: &str = "/api/v1/user/name/update";
/// Endpoint constant for getting user profile
pub const GET_USER_PROFILE: &str = "/api/v1/user/profile/{id}";
} }
+8 -10
View File
@@ -49,22 +49,22 @@ pub mod response {
#[derive(Deserialize, Serialize, utoipa::ToSchema)] #[derive(Deserialize, Serialize, utoipa::ToSchema)]
pub struct Response { pub struct Response {
pub message: String, pub message: String,
pub data: Vec<textsender_models::user::User>, pub data: Vec<schedtxt_models::user::User>,
} }
#[derive(Default, Deserialize, Serialize, utoipa::ToSchema)] #[derive(Default, Deserialize, Serialize, utoipa::ToSchema)]
pub struct RegisterServiceUserResponse { pub struct RegisterServiceUserResponse {
pub message: String, pub message: String,
pub data: Vec<textsender_models::user::ServiceUser>, pub data: Vec<schedtxt_models::user::ServiceUser>,
} }
} }
pub fn generate_the_salt() -> ( pub fn generate_the_salt() -> (
argon2::password_hash::SaltString, argon2::password_hash::SaltString,
textsender_models::user::Salt, schedtxt_models::user::Salt,
) { ) {
let salt_string = hashing::generate_salt().unwrap(); let salt_string = hashing::generate_salt().unwrap();
let salt = textsender_models::user::Salt::default(); let salt = schedtxt_models::user::Salt::default();
(salt_string, salt) (salt_string, salt)
} }
@@ -80,7 +80,7 @@ pub fn generate_the_salt() -> (
responses( responses(
(status = 201, description = "User created", body = response::Response), (status = 201, description = "User created", body = response::Response),
(status = 404, description = "User already exists", body = response::Response), (status = 404, description = "User already exists", body = response::Response),
(status = 400, description = "Issue creating user", body = response::Response) (status = 500, description = "Issue creating user", body = response::Response)
) )
)] )]
pub async fn register_user( pub async fn register_user(
@@ -102,12 +102,10 @@ pub async fn register_user(
}; };
if registration_enabled { if registration_enabled {
let mut user = textsender_models::user::User { let mut user = schedtxt_models::user::User {
username: payload.username.clone(), username: payload.username.clone(),
password: payload.password.clone(), password: payload.password.clone(),
// email: payload.email.clone(),
phone_number: payload.phone_number.clone(), phone_number: payload.phone_number.clone(),
// email_verified: true,
..Default::default() ..Default::default()
}; };
@@ -183,7 +181,7 @@ pub async fn register_user(
/// Checks to see if registration is enabled /// Checks to see if registration is enabled
async fn is_registration_enabled() -> Result<bool, std::io::Error> { async fn is_registration_enabled() -> Result<bool, std::io::Error> {
let key = String::from("ENABLE_REGISTRATION"); let key = String::from("ENABLE_REGISTRATION");
let var = textsender_models::envy::environment::get_env(&key).await; let var = schedtxt_models::envy::environment::get_env(&key);
let parsed_value = var.value.to_uppercase(); let parsed_value = var.value.to_uppercase();
if parsed_value == "TRUE" { if parsed_value == "TRUE" {
@@ -250,7 +248,7 @@ pub async fn register_service_user(
} else { } else {
let (generate_salt, mut salt) = generate_the_salt(); let (generate_salt, mut salt) = generate_the_salt();
salt.id = repo::salt::insert(&pool, &salt).await.unwrap(); salt.id = repo::salt::insert(&pool, &salt).await.unwrap();
let mut service_user = textsender_models::user::ServiceUser { let mut service_user = schedtxt_models::user::ServiceUser {
username: payload.username.clone(), username: payload.username.clone(),
passphrase: hashing::hash_password(&payload.passphrase, &generate_salt) passphrase: hashing::hash_password(&payload.passphrase, &generate_salt)
.unwrap(), .unwrap(),
+1 -3
View File
@@ -1,9 +1,7 @@
use sqlx::postgres::PgPoolOptions; use sqlx::postgres::PgPoolOptions;
pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> { pub async fn create_pool() -> Result<sqlx::PgPool, sqlx::Error> {
let database_url = textsender_models::envy::environment::get_db_url() let database_url = schedtxt_models::envy::environment::get_db_url().value;
.await
.value;
println!("Database url: {database_url}"); println!("Database url: {database_url}");
PgPoolOptions::new() PgPoolOptions::new()
+20 -8
View File
@@ -24,9 +24,12 @@ pub mod init {
paths( paths(
common_callers::endpoint::db_ping, common_callers::endpoint::root, common_callers::endpoint::db_ping, common_callers::endpoint::root,
register_caller::register_user, login_caller::user_login, register_caller::register_user, login_caller::user_login,
login_caller::get_user_profile,
), ),
components(schemas(common_callers::response::TestResult, components(schemas(common_callers::response::TestResult,
register_responses::Response, login_responses::LoginResponse)), register_responses::Response, login_responses::LoginResponse,
login_responses::GetUserProfileResponse,
)),
tags( tags(
(name = "TextSender Auth API", description = "Auth API for TextSender API") (name = "TextSender Auth API", description = "Auth API for TextSender API")
) )
@@ -35,27 +38,27 @@ pub mod init {
mod cors { mod cors {
pub async fn configure_cors() -> tower_http::cors::CorsLayer { pub async fn configure_cors() -> tower_http::cors::CorsLayer {
// Start building the CORS layer with common settings
let cors = tower_http::cors::CorsLayer::new() let cors = tower_http::cors::CorsLayer::new()
.allow_methods([ .allow_methods([
axum::http::Method::GET, axum::http::Method::GET,
axum::http::Method::PATCH,
axum::http::Method::POST, axum::http::Method::POST,
axum::http::Method::PUT, axum::http::Method::PUT,
axum::http::Method::DELETE, axum::http::Method::DELETE,
]) // Specify allowed methods:cite[2] ])
.allow_headers([ .allow_headers([
axum::http::header::CONTENT_TYPE, axum::http::header::CONTENT_TYPE,
axum::http::header::AUTHORIZATION, axum::http::header::AUTHORIZATION,
]) // Specify allowed headers:cite[2] ])
.allow_credentials(true) // If you need to send cookies or authentication 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] .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 // Dynamically set the allowed origin based on the environment
match std::env::var(textsender_models::envy::keys::APP_ENV).as_deref() { match std::env::var(schedtxt_models::envy::keys::APP_ENV).as_deref() {
Ok("production") => { Ok("production") => {
let allowed_origins_env = let allowed_origins_env =
textsender_models::envy::environment::get_allowed_origins().await; schedtxt_models::envy::environment::get_allowed_origins();
match textsender_models::envy::utility::delimitize(&allowed_origins_env) { match schedtxt_models::envy::utility::delimitize(&allowed_origins_env) {
Ok(alwd) => { Ok(alwd) => {
let allowed_origins: Vec<axum::http::HeaderValue> = alwd let allowed_origins: Vec<axum::http::HeaderValue> = alwd
.into_iter() .into_iter()
@@ -76,6 +79,12 @@ pub mod init {
cors.allow_origin(vec![ cors.allow_origin(vec![
"http://localhost:4200".parse().unwrap(), "http://localhost:4200".parse().unwrap(),
"http://127.0.0.1:4200".parse().unwrap(), "http://127.0.0.1:4200".parse().unwrap(),
"http://localhost:5173".parse().unwrap(),
"http://127.0.0.1:5173".parse().unwrap(),
"http://localhost:9080".parse().unwrap(),
"http://127.0.0.1:9080".parse().unwrap(),
"http://localhost:9081".parse().unwrap(),
"http://127.0.0.1:9081".parse().unwrap(),
]) ])
} }
} }
@@ -83,7 +92,6 @@ pub mod init {
} }
pub async fn routes() -> Router { pub async fn routes() -> Router {
// build our application with a route
Router::new() Router::new()
.route( .route(
callers::endpoints::DBTEST, callers::endpoints::DBTEST,
@@ -118,6 +126,10 @@ pub mod init {
callers::endpoints::UPDATE_USER_NAME, callers::endpoints::UPDATE_USER_NAME,
patch(callers::login::update_name_of_user), patch(callers::login::update_name_of_user),
) )
.route(
callers::endpoints::GET_USER_PROFILE,
get(callers::login::get_user_profile),
)
.layer(cors::configure_cors().await) .layer(cors::configure_cors().await)
} }
+2 -2
View File
@@ -3,10 +3,10 @@ async fn main() {
// initialize tracing // initialize tracing
tracing_subscriber::fmt::init(); tracing_subscriber::fmt::init();
let app = textsender_auth::init::app().await; let app = schedtxt_auth::init::app().await;
// run our app with hyper, listening globally on port 9080 // run our app with hyper, listening globally on port 9080
let url = textsender_auth::config::get_full(); let url = schedtxt_auth::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();
} }
+10 -10
View File
@@ -12,7 +12,7 @@ pub mod user {
pub async fn get( pub async fn get(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
username: &String, username: &String,
) -> Result<textsender_models::user::User, sqlx::Error> { ) -> Result<schedtxt_models::user::User, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
SELECT id, username, password, phone_number, salt_id, firstname, lastname, created, last_login FROM "user" WHERE username = $1 SELECT id, username, password, phone_number, salt_id, firstname, lastname, created, last_login FROM "user" WHERE username = $1
@@ -24,7 +24,7 @@ pub mod user {
match result { match result {
Ok(r) => match r { Ok(r) => match r {
Some(r) => Ok(textsender_models::user::User { Some(r) => Ok(schedtxt_models::user::User {
id: r.try_get("id")?, id: r.try_get("id")?,
username: r.try_get("username")?, username: r.try_get("username")?,
password: r.try_get("password")?, password: r.try_get("password")?,
@@ -44,7 +44,7 @@ pub mod user {
pub async fn get_with_id( pub async fn get_with_id(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<textsender_models::user::User, sqlx::Error> { ) -> Result<schedtxt_models::user::User, sqlx::Error> {
match sqlx::query( match sqlx::query(
r#" r#"
SELECT id, username, password, phone_number, salt_id, firstname, lastname, created, last_login FROM "user" WHERE id = $1 SELECT id, username, password, phone_number, salt_id, firstname, lastname, created, last_login FROM "user" WHERE id = $1
@@ -54,7 +54,7 @@ pub mod user {
.fetch_optional(pool) .fetch_optional(pool)
.await { .await {
Ok(r) => match r { Ok(r) => match r {
Some(r) => Ok(textsender_models::user::User { Some(r) => Ok(schedtxt_models::user::User {
id: r.try_get("id")?, id: r.try_get("id")?,
username: r.try_get("username")?, username: r.try_get("username")?,
password: r.try_get("password")?, password: r.try_get("password")?,
@@ -73,7 +73,7 @@ pub mod user {
pub async fn update_last_login( pub async fn update_last_login(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
user: &textsender_models::user::User, user: &schedtxt_models::user::User,
time: &time::OffsetDateTime, time: &time::OffsetDateTime,
) -> Result<time::OffsetDateTime, sqlx::Error> { ) -> Result<time::OffsetDateTime, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
@@ -106,7 +106,7 @@ pub mod user {
pub async fn update_password( pub async fn update_password(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
user: &textsender_models::user::User, user: &schedtxt_models::user::User,
updated_hashed_password: &String, updated_hashed_password: &String,
) -> Result<(), sqlx::Error> { ) -> Result<(), sqlx::Error> {
match sqlx::query( match sqlx::query(
@@ -189,7 +189,7 @@ pub mod user {
pub async fn insert( pub async fn insert(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
user: &textsender_models::user::User, user: &schedtxt_models::user::User,
) -> Result<(uuid::Uuid, std::option::Option<time::OffsetDateTime>), sqlx::Error> { ) -> Result<(uuid::Uuid, std::option::Option<time::OffsetDateTime>), sqlx::Error> {
let row = sqlx::query( let row = sqlx::query(
r#" r#"
@@ -237,7 +237,7 @@ pub mod salt {
pub async fn get( pub async fn get(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<textsender_models::user::Salt, sqlx::Error> { ) -> Result<schedtxt_models::user::Salt, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
r#" r#"
SELECT id, salt FROM "salt" WHERE id = $1 SELECT id, salt FROM "salt" WHERE id = $1
@@ -249,7 +249,7 @@ pub mod salt {
match result { match result {
Ok(r) => match r { Ok(r) => match r {
Some(r) => Ok(textsender_models::user::Salt { Some(r) => Ok(schedtxt_models::user::Salt {
id: r.try_get("id")?, id: r.try_get("id")?,
salt: r.try_get("salt")?, salt: r.try_get("salt")?,
}), }),
@@ -261,7 +261,7 @@ pub mod salt {
pub async fn insert( pub async fn insert(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
salt: &textsender_models::user::Salt, salt: &schedtxt_models::user::Salt,
) -> Result<uuid::Uuid, sqlx::Error> { ) -> Result<uuid::Uuid, sqlx::Error> {
let row = sqlx::query( let row = sqlx::query(
r#" r#"
+7 -7
View File
@@ -52,7 +52,7 @@ pub async fn get_passphrase(
pub async fn get( pub async fn get(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<textsender_models::user::ServiceUser, sqlx::Error> { ) -> Result<schedtxt_models::user::ServiceUser, sqlx::Error> {
match sqlx::query( match sqlx::query(
r#"SELECT id, username, passphrase, created, last_login, salt_id FROM "service_user" WHERE id = $1"# r#"SELECT id, username, passphrase, created, last_login, salt_id FROM "service_user" WHERE id = $1"#
).bind(id) ).bind(id)
@@ -68,7 +68,7 @@ pub async fn get(
} }
}; };
let service_user = textsender_models::user::ServiceUser { let service_user = schedtxt_models::user::ServiceUser {
id: row.try_get("id")?, id: row.try_get("id")?,
username: row.try_get("username")?, username: row.try_get("username")?,
passphrase: row.try_get("passphrase")?, passphrase: row.try_get("passphrase")?,
@@ -88,7 +88,7 @@ pub async fn get(
pub async fn get_with_username( pub async fn get_with_username(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
username: &String, username: &String,
) -> Result<textsender_models::user::ServiceUser, sqlx::Error> { ) -> Result<schedtxt_models::user::ServiceUser, sqlx::Error> {
match sqlx::query( match sqlx::query(
r#"SELECT id, username, passphrase, created, last_login, salt_id FROM "service_user" WHERE username = $1"# r#"SELECT id, username, passphrase, created, last_login, salt_id FROM "service_user" WHERE username = $1"#
).bind(username) ).bind(username)
@@ -104,7 +104,7 @@ pub async fn get_with_username(
} }
}; };
let service_user = textsender_models::user::ServiceUser { let service_user = schedtxt_models::user::ServiceUser {
id: row.try_get("id")?, id: row.try_get("id")?,
username: row.try_get("username")?, username: row.try_get("username")?,
passphrase: row.try_get("passphrase")?, passphrase: row.try_get("passphrase")?,
@@ -123,7 +123,7 @@ pub async fn get_with_username(
pub async fn update_last_login( pub async fn update_last_login(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
service_user: &textsender_models::user::ServiceUser, service_user: &schedtxt_models::user::ServiceUser,
time: &time::OffsetDateTime, time: &time::OffsetDateTime,
) -> Result<time::OffsetDateTime, sqlx::Error> { ) -> Result<time::OffsetDateTime, sqlx::Error> {
let result = sqlx::query( let result = sqlx::query(
@@ -156,7 +156,7 @@ pub async fn update_last_login(
pub async fn update_passphrase( pub async fn update_passphrase(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
user: &textsender_models::user::ServiceUser, user: &schedtxt_models::user::ServiceUser,
updated_hashed_passphrase: &String, updated_hashed_passphrase: &String,
) -> Result<(), sqlx::Error> { ) -> Result<(), sqlx::Error> {
match sqlx::query( match sqlx::query(
@@ -182,7 +182,7 @@ pub async fn update_passphrase(
pub async fn insert( pub async fn insert(
pool: &sqlx::PgPool, pool: &sqlx::PgPool,
service_user: &textsender_models::user::ServiceUser, service_user: &schedtxt_models::user::ServiceUser,
) -> Result<(uuid::Uuid, time::OffsetDateTime), sqlx::Error> { ) -> Result<(uuid::Uuid, time::OffsetDateTime), sqlx::Error> {
match sqlx::query( match sqlx::query(
r#"INSERT INTO "service_user" (username, passphrase, salt_id) r#"INSERT INTO "service_user" (username, passphrase, salt_id)
+26 -29
View File
@@ -8,8 +8,8 @@ use time;
pub const KEY_ENV: &str = "SECRET_KEY"; pub const KEY_ENV: &str = "SECRET_KEY";
pub const MESSAGE: &str = "Something random"; pub const MESSAGE: &str = "Something random";
pub const ISSUER: &str = "textsender_auth"; pub const ISSUER: &str = "schedtxt_auth";
pub const AUDIENCE: &str = "textsender"; pub const AUDIENCE: &str = "schedtxt";
pub fn get_expiration(issued: &time::OffsetDateTime) -> Result<time::OffsetDateTime, time::Error> { pub fn get_expiration(issued: &time::OffsetDateTime) -> Result<time::OffsetDateTime, time::Error> {
let duration_expire = time::Duration::hours(4); let duration_expire = time::Duration::hours(4);
@@ -17,45 +17,45 @@ pub fn get_expiration(issued: &time::OffsetDateTime) -> Result<time::OffsetDateT
} }
pub fn create_token( pub fn create_token(
provided_key: &String, provided_key: &str,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<(String, i64), josekit::JoseError> { ) -> Result<schedtxt_models::token::CreateTokenResult, josekit::JoseError> {
let resource = textsender_models::token::TokenResource { let resource = schedtxt_models::token::TokenResource {
message: String::from(MESSAGE), message: String::from(MESSAGE),
issuer: String::from(ISSUER), issuer: String::from(ISSUER),
audiences: vec![String::from(AUDIENCE)], audiences: vec![String::from(AUDIENCE)],
user_id: *id, user_id: *id,
}; };
textsender_models::token::create_token(provided_key, &resource, time::Duration::hours(4)) schedtxt_models::token::create_token(provided_key, resource, time::Duration::hours(4))
} }
pub fn create_service_token( pub fn create_service_token(
provided: &String, provided: &str,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<(String, i64), josekit::JoseError> { ) -> Result<schedtxt_models::token::CreateTokenResult, josekit::JoseError> {
let resource = textsender_models::token::TokenResource { let resource = schedtxt_models::token::TokenResource {
message: String::from(SERVICE_SUBJECT), message: String::from(SERVICE_SUBJECT),
issuer: String::from(ISSUER), issuer: String::from(ISSUER),
audiences: vec![String::from(AUDIENCE)], audiences: vec![String::from(AUDIENCE)],
user_id: *id, user_id: *id,
}; };
textsender_models::token::create_token(provided, &resource, time::Duration::hours(1)) schedtxt_models::token::create_token(provided, resource, time::Duration::hours(1))
} }
pub fn create_service_refresh_token( pub fn create_service_refresh_token(
key: &String, key: &str,
id: &uuid::Uuid, id: &uuid::Uuid,
) -> Result<(String, i64), josekit::JoseError> { ) -> Result<schedtxt_models::token::CreateTokenResult, josekit::JoseError> {
let resource = textsender_models::token::TokenResource { let resource = schedtxt_models::token::TokenResource {
message: String::from(SERVICE_SUBJECT), message: String::from(SERVICE_SUBJECT),
issuer: String::from(ISSUER), issuer: String::from(ISSUER),
audiences: vec![String::from(AUDIENCE)], audiences: vec![String::from(AUDIENCE)],
user_id: *id, user_id: *id,
}; };
textsender_models::token::create_token(key, &resource, time::Duration::hours(4)) schedtxt_models::token::create_token(key, resource, time::Duration::hours(4))
} }
pub fn verify_token(key: &String, token: &String) -> bool { pub fn verify_token(key: &str, token: &str) -> bool {
match get_payload(key, token) { match get_payload(key, token) {
Ok((payload, _header)) => match payload.subject() { Ok((payload, _header)) => match payload.subject() {
Some(_sub) => true, Some(_sub) => true,
@@ -65,7 +65,7 @@ pub fn verify_token(key: &String, token: &String) -> bool {
} }
} }
pub fn extract_id_from_token(key: &String, token: &String) -> Result<uuid::Uuid, std::io::Error> { pub fn extract_id_from_token(key: &str, token: &str) -> Result<uuid::Uuid, std::io::Error> {
match get_payload(key, token) { match get_payload(key, token) {
Ok((payload, _header)) => match payload.claim("user_id") { Ok((payload, _header)) => match payload.claim("user_id") {
Some(id) => match uuid::Uuid::parse_str(id.as_str().unwrap()) { Some(id) => match uuid::Uuid::parse_str(id.as_str().unwrap()) {
@@ -78,12 +78,12 @@ pub fn extract_id_from_token(key: &String, token: &String) -> Result<uuid::Uuid,
} }
} }
pub const APP_TOKEN_TYPE: &str = "Textsender_App"; pub const APP_TOKEN_TYPE: &str = "Schedtxt_App";
pub const APP_SUBJECT: &str = "Something random"; pub const APP_SUBJECT: &str = "Something random";
pub const SERVICE_TOKEN_TYPE: &str = "Textsender_Service"; pub const SERVICE_TOKEN_TYPE: &str = "Schedtxt_Service";
pub const SERVICE_SUBJECT: &str = "Service random"; pub const SERVICE_SUBJECT: &str = "Service random";
pub fn get_token_type(key: &String, token: &String) -> Result<String, std::io::Error> { pub fn get_token_type(key: &str, token: &str) -> Result<String, std::io::Error> {
match get_payload(key, token) { match get_payload(key, token) {
Ok((payload, _header)) => match payload.subject() { Ok((payload, _header)) => match payload.subject() {
Some(subject) => { Some(subject) => {
@@ -101,13 +101,13 @@ pub fn get_token_type(key: &String, token: &String) -> Result<String, std::io::E
} }
} }
pub fn is_token_type_valid(token_type: &String) -> bool { pub fn is_token_type_valid(token_type: &str) -> bool {
token_type == SERVICE_TOKEN_TYPE token_type == SERVICE_TOKEN_TYPE
} }
fn get_payload( fn get_payload(
key: &String, key: &str,
token: &String, token: &str,
) -> Result<(josekit::jwt::JwtPayload, josekit::jws::JwsHeader), josekit::JoseError> { ) -> Result<(josekit::jwt::JwtPayload, josekit::jws::JwsHeader), josekit::JoseError> {
let ver = Hs256.verifier_from_bytes(key.as_bytes()).unwrap(); let ver = Hs256.verifier_from_bytes(key.as_bytes()).unwrap();
jwt::decode_with_verifier(token, &ver) jwt::decode_with_verifier(token, &ver)
@@ -119,14 +119,11 @@ mod tests {
#[test] #[test]
fn test_tokenize() { fn test_tokenize() {
let rt = tokio::runtime::Runtime::new().unwrap(); let special_key = schedtxt_models::envy::environment::get_secret_key();
let special_key = rt
.block_on(textsender_models::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.value, &id) {
Ok((token, _duration)) => { Ok(cst) => {
let result = verify_token(&special_key, &token); let result = verify_token(&special_key.value, &cst.access_token);
assert!(result, "Token not verified"); assert!(result, "Token not verified");
} }
Err(err) => { Err(err) => {
+124 -21
View File
@@ -1,7 +1,7 @@
use textsender_auth; use schedtxt_auth;
use textsender_auth::callers; use schedtxt_auth::callers;
use textsender_auth::db; use schedtxt_auth::db;
use textsender_auth::init; use schedtxt_auth::init;
mod db_mgr { mod db_mgr {
use std::str::FromStr; use std::str::FromStr;
@@ -9,9 +9,7 @@ mod db_mgr {
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 = textsender_models::envy::environment::get_db_url() let tm_db_url = schedtxt_models::envy::environment::get_db_url().value;
.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
} }
@@ -23,9 +21,7 @@ mod db_mgr {
} }
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 = textsender_models::envy::environment::get_db_url() let db_url = schedtxt_models::envy::environment::get_db_url().value;
.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
} }
@@ -35,7 +31,10 @@ mod db_mgr {
db_name: &str, db_name: &str,
) -> Result<(), sqlx::Error> { ) -> Result<(), sqlx::Error> {
let create_query = format!("CREATE DATABASE {}", db_name); let create_query = format!("CREATE DATABASE {}", db_name);
match sqlx::query(&create_query).execute(template_pool).await { match sqlx::query(sqlx::AssertSqlSafe(create_query))
.execute(template_pool)
.await
{
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(e) => Err(e), Err(e) => Err(e),
} }
@@ -47,14 +46,14 @@ mod db_mgr {
db_name: &str, db_name: &str,
) -> Result<(), sqlx::Error> { ) -> Result<(), sqlx::Error> {
let drop_query = format!("DROP DATABASE IF EXISTS {} WITH (FORCE)", db_name); let drop_query = format!("DROP DATABASE IF EXISTS {} WITH (FORCE)", db_name);
sqlx::query(&drop_query).execute(template_pool).await?; sqlx::query(sqlx::AssertSqlSafe(drop_query))
.execute(template_pool)
.await?;
Ok(()) Ok(())
} }
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 = textsender_models::envy::environment::get_db_url() let database_url = schedtxt_models::envy::environment::get_db_url().value;
.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" {
@@ -225,6 +224,29 @@ pub mod requests {
Err(err) => Err(err), Err(err) => Err(err),
} }
} }
/// Function to call get user profile endpoint
pub async fn get_user_profile(
app: &axum::Router,
user_id: &uuid::Uuid,
) -> Result<axum::response::Response, axum::http::Error> {
let url = super::util::format_url_with_value(
schedtxt_auth::callers::endpoints::GET_USER_PROFILE,
user_id,
);
match axum::http::Request::builder()
.method(axum::http::Method::GET)
.uri(url)
.header(axum::http::header::CONTENT_TYPE, "application/json")
.body(axum::body::Body::empty())
{
Ok(req) => match app.clone().oneshot(req).await {
Ok(resp) => Ok(resp),
Err(err) => Err(axum::http::Error::from(err)),
},
Err(err) => Err(err),
}
}
} }
/// Test user firstname /// Test user firstname
@@ -272,6 +294,11 @@ mod util {
Err(err) => Err(std::io::Error::other(err.to_string())), Err(err) => Err(std::io::Error::other(err.to_string())),
} }
} }
pub fn format_url_with_value(endpoint: &str, value: &uuid::Uuid) -> String {
let last = endpoint.len() - 5;
format!("{}/{value}", &endpoint[0..last])
}
} }
mod flow { mod flow {
@@ -281,7 +308,7 @@ mod flow {
pub async fn register_user( pub async fn register_user(
app: &axum::Router, app: &axum::Router,
) -> Result<textsender_models::user::User, std::io::Error> { ) -> Result<schedtxt_models::user::User, std::io::Error> {
match requests::register_user(&app).await { match requests::register_user(&app).await {
Ok(response) => { Ok(response) => {
if axum::http::StatusCode::CREATED != response.status() { if axum::http::StatusCode::CREATED != response.status() {
@@ -313,7 +340,7 @@ mod flow {
app: &axum::Router, app: &axum::Router,
username: &str, username: &str,
password: &str, password: &str,
) -> Result<textsender_models::token::LoginResult, std::io::Error> { ) -> Result<schedtxt_models::token::LoginResult, std::io::Error> {
match requests::login_user(&app, username, password).await { match requests::login_user(&app, username, password).await {
Ok(response) => { Ok(response) => {
if axum::http::StatusCode::OK != response.status() { if axum::http::StatusCode::OK != response.status() {
@@ -345,7 +372,7 @@ mod flow {
pub async fn register_service_user( pub async fn register_service_user(
app: &axum::Router, app: &axum::Router,
) -> Result<textsender_models::user::ServiceUser, std::io::Error> { ) -> Result<schedtxt_models::user::ServiceUser, std::io::Error> {
match requests::register_service_user(&app).await { match requests::register_service_user(&app).await {
Ok(response) => { Ok(response) => {
if axum::http::StatusCode::CREATED != response.status() { if axum::http::StatusCode::CREATED != response.status() {
@@ -379,7 +406,7 @@ mod flow {
app: &axum::Router, app: &axum::Router,
username: &str, username: &str,
passphrase: &str, passphrase: &str,
) -> Result<textsender_models::token::LoginResult, std::io::Error> { ) -> Result<schedtxt_models::token::LoginResult, std::io::Error> {
match requests::login_service_user(&app, username, passphrase).await { match requests::login_service_user(&app, username, passphrase).await {
Ok(response) => { Ok(response) => {
if axum::http::StatusCode::OK != response.status() { if axum::http::StatusCode::OK != response.status() {
@@ -412,7 +439,7 @@ mod flow {
pub async fn refresh_token( pub async fn refresh_token(
app: &axum::Router, app: &axum::Router,
access_token: &str, access_token: &str,
) -> Result<textsender_models::token::LoginResult, std::io::Error> { ) -> Result<schedtxt_models::token::LoginResult, std::io::Error> {
match requests::refresh_token(app, access_token).await { match requests::refresh_token(app, access_token).await {
Ok(response) => { Ok(response) => {
if axum::http::StatusCode::OK != response.status() { if axum::http::StatusCode::OK != response.status() {
@@ -491,7 +518,7 @@ mod flow {
user_id: &uuid::Uuid, user_id: &uuid::Uuid,
firstname: &str, firstname: &str,
lastname: &str, lastname: &str,
) -> Result<textsender_models::user::User, std::io::Error> { ) -> Result<schedtxt_models::user::User, std::io::Error> {
match requests::update_name_of_user(app, user_id, firstname, lastname).await { match requests::update_name_of_user(app, user_id, firstname, lastname).await {
Ok(response) => { Ok(response) => {
if axum::http::StatusCode::OK != response.status() { if axum::http::StatusCode::OK != response.status() {
@@ -989,3 +1016,79 @@ async fn test_update_name_of_password() {
} }
} }
} }
#[tokio::test]
async fn test_get_user_profile() {
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();
db::init::migrations(&pool).await;
let app = init::routes().await.layer(axum::Extension(pool));
let user_id = match flow::register_user(&app).await {
Ok(user) => match flow::login_user(&app, &user.username, TEST_PASSWORD).await {
Ok(login_result) => {
assert_eq!(
false,
login_result.access_token.is_empty(),
"Access token is empty when it should not be"
);
login_result.user_id
}
Err(err) => {
assert!(false, "Error: {err:?}");
uuid::Uuid::nil()
}
},
Err(err) => {
assert!(false, "Error: {err:?}");
uuid::Uuid::nil()
}
};
assert_eq!(false, user_id.is_nil(), "User Id should not be empty");
match requests::get_user_profile(&app, &user_id).await {
Ok(response) => {
match util::convert_response::<
schedtxt_auth::callers::login::response::GetUserProfileResponse,
>(response)
.await
{
Ok(response) => {
assert_eq!(false, response.data.is_empty(), "No User Profile found");
let user_profile = &response.data[0];
assert_eq!(
TEST_USERNAME, user_profile.username,
"Username does not match"
);
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
}
}
Err(err) => {
assert!(false, "Error: {err:?}");
}
}
match db_mgr::drop_database(&tm_pool, &db_name).await {
Ok(()) => {}
Err(err) => {
assert!(false, "Error: {err:?}");
}
}
}