From 0d1c0961e2379f69b05b0eea1a3db167ec9a6449 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 30 May 2026 23:02:38 -0400 Subject: [PATCH 01/29] Removing go files --- Dockerfile | 54 -- Makefile | 21 - cmd/api/main.go | 144 ------ cmd/api/main_test.go | 96 ---- docker-compose.yaml | 44 -- docs/docs.go | 684 -------------------------- docs/swagger.json | 660 ------------------------- docs/swagger.yaml | 421 ---------------- go.mod | 45 -- go.sum | 92 ---- internal/config/config.go | 168 ------- internal/db/connection.go | 136 ----- internal/handler/endpoint/endpoint.go | 14 - internal/handler/login.go | 191 ------- internal/handler/login_test.go | 70 --- internal/handler/refresh.go | 100 ---- internal/handler/refresh_test.go | 70 --- internal/handler/register.go | 188 ------- internal/handler/register_test.go | 65 --- internal/handler/service.go | 185 ------- internal/handler/service_test.go | 76 --- internal/handler/utility.go | 21 - internal/handler/utility_test.go | 63 --- internal/middleware/auth.go | 47 -- internal/middleware/middleware.go | 22 - internal/services/jwt_service.go | 49 -- internal/store/mock/service_store.go | 125 ----- internal/store/mock/user_store.go | 192 -------- internal/store/service.go | 95 ---- internal/store/user.go | 173 ------- internal/utility/hashing.go | 62 --- internal/utility/token.go | 130 ----- internal/version/version.go | 17 - 33 files changed, 4520 deletions(-) delete mode 100644 Dockerfile delete mode 100644 Makefile delete mode 100644 cmd/api/main.go delete mode 100644 cmd/api/main_test.go delete mode 100644 docker-compose.yaml delete mode 100644 docs/docs.go delete mode 100644 docs/swagger.json delete mode 100644 docs/swagger.yaml delete mode 100644 go.mod delete mode 100644 go.sum delete mode 100644 internal/config/config.go delete mode 100644 internal/db/connection.go delete mode 100644 internal/handler/endpoint/endpoint.go delete mode 100644 internal/handler/login.go delete mode 100644 internal/handler/login_test.go delete mode 100644 internal/handler/refresh.go delete mode 100644 internal/handler/refresh_test.go delete mode 100644 internal/handler/register.go delete mode 100644 internal/handler/register_test.go delete mode 100644 internal/handler/service.go delete mode 100644 internal/handler/service_test.go delete mode 100644 internal/handler/utility.go delete mode 100644 internal/handler/utility_test.go delete mode 100644 internal/middleware/auth.go delete mode 100644 internal/middleware/middleware.go delete mode 100644 internal/services/jwt_service.go delete mode 100644 internal/store/mock/service_store.go delete mode 100644 internal/store/mock/user_store.go delete mode 100644 internal/store/service.go delete mode 100644 internal/store/user.go delete mode 100644 internal/utility/hashing.go delete mode 100644 internal/utility/token.go delete mode 100644 internal/version/version.go diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index ce72ee9..0000000 --- a/Dockerfile +++ /dev/null @@ -1,54 +0,0 @@ -# Multi-stage Dockerfile for Go application -FROM golang:1.26.2 AS builder - -WORKDIR /app - -RUN apt-get update && apt-get install -y --no-install-recommends \ - ca-certificates \ - openssh-client git - -RUN mkdir -p -m 0700 ~/.ssh && \ - ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts - -# Configure Git to use SSH for GitHub -RUN git config --global url."git@git.kundeng.us:".insteadOf "https://git.kundeng.us/" && \ - git config --global url."git@git.kundeng.us:".insteadOf "http://git.kundeng.us/" - -# Set up the Go environment for private modules -ENV GOPRIVATE=git.kundeng.us - - -# Copy go mod and sum files -COPY go.mod go.sum ./ - -RUN --mount=type=ssh mkdir src && \ - go mod download - -# Copy source code -COPY ./cmd ./cmd -COPY ./internal ./internal -COPY ./Makefile . -COPY ./.env . -COPY ./migrations ./migrations -COPY ./docs ./docs - -# Build the application -RUN CGO_ENABLED=0 GOOS=linux make build - -# Runtime stage -FROM alpine:latest AS production - -RUN apk --no-cache add ca-certificates - -WORKDIR /root/ - -# Copy the pre-built binary file from the previous stage -COPY --from=builder /app/textsender-auth . -COPY --from=builder /app/.env ./ -COPY --from=builder /app/migrations ./migrations - -# Expose port -EXPOSE 9080 - -# Command to run the executable -CMD ["./textsender-auth"] diff --git a/Makefile b/Makefile deleted file mode 100644 index e958bce..0000000 --- a/Makefile +++ /dev/null @@ -1,21 +0,0 @@ -VERSION ?= $(shell git describe --tags 2>/dev/null || echo "dev") -COMMIT ?= $(shell git rev-parse --short HEAD) -BUILD_TIME ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) -GO_VERSION ?= $(shell go version | awk '{print $$3}') - -.PHONY: build -build: - go build -ldflags="\ - -X 'git.kundeng.us/phoenix/textsender-auth/internal/version.Version=$(VERSION)' \ - -X 'git.kundeng.us/phoenix/textsender-auth/internal/version.BuildTime=$(BUILD_TIME)' \ - -X 'git.kundeng.us/phoenix/textsender-auth/internal/version.Commit=$(COMMIT)' \ - -X 'git.kundeng.us/phoenix/textsender-auth/internal/version.GoVersion=$(GO_VERSION)'" \ - -o textsender-auth cmd/api/main.go - -.PHONY: install -install: - go install -ldflags="\ - -X 'git.kundeng.us/phoenix/textsender-auth/internal/version.Version=$(VERSION)' \ - -X 'git.kundeng.us/phoenix/textsender-auth/internal/version.BuildTime=$(BUILD_TIME)' \ - -X 'git.kundeng.us/phoenix/textsender-auth/internal/version.Commit=$(COMMIT)' \ - -X 'git.kundeng.us/phoenix/textsender-auth/internal/version.GoVersion=$(GO_VERSION)'" diff --git a/cmd/api/main.go b/cmd/api/main.go deleted file mode 100644 index d36fe79..0000000 --- a/cmd/api/main.go +++ /dev/null @@ -1,144 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/go-chi/chi/v5" - "github.com/go-chi/chi/v5/middleware" - "github.com/go-chi/cors" - "github.com/swaggo/http-swagger/v2" - - _ "git.kundeng.us/phoenix/textsender-auth/docs" - "git.kundeng.us/phoenix/textsender-auth/internal/config" - database "git.kundeng.us/phoenix/textsender-auth/internal/db" - "git.kundeng.us/phoenix/textsender-auth/internal/handler" - "git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint" - mdleware "git.kundeng.us/phoenix/textsender-auth/internal/middleware" - "git.kundeng.us/phoenix/textsender-auth/internal/services" - "git.kundeng.us/phoenix/textsender-auth/internal/store" -) - -// @title textsender-auth -// @version 1.0 -// @description Auth API to send text messages - -// @host localhost:9080 -// @BasePath /api/v1 - -// @securityDefinitions.apikey BearerAuth -// @in header -// @name Authorization -// @description JWT Bearer Token -func main() { - cfg := config.Load() - if cfg == nil { - fmt.Println("Error initializing config") - os.Exit(-1) - } - - db, err := database.NewDatabase(cfg.GetDBConnString()) - if err != nil { - log.Fatalf("Failed to connect to database: %v", err) - } - defer db.Close() - - ctx := context.Background() - - if cfg.ResetDB { - if err := db.ResetDatabase(ctx); err != nil { - log.Fatalf("Failed to reset database: %v", err) - } else { - log.Println("Resetting database") - log.Println("Database reset completed. Exiting.") - } - return - } else { - if exists, err := database.TableExists(ctx, db.Pool, "users"); err == nil { - if !exists { - if err = db.ResetDatabase(ctx); err != nil { - fmt.Println("Error:", err) - } else { - fmt.Println("Database reset") - } - } - } else { - fmt.Println("Error:", err) - } - } - - // Services - userStore := store.NewUserStore(db.Pool) - serviceStore := store.NewServiceStore(db.Pool) - - userHandler := handler.NewUserHandler(cfg, userStore) - loginHandler := handler.NewLoginHandler(cfg, userStore) - serviceHandler := handler.NewServiceHandler(cfg, serviceStore) - refreshHandler := handler.NewRefreshHandler(cfg, userStore, serviceStore) - - router := chi.NewRouter() - jwtService := services.NewJWTService(config.GetSecretKey()) - - // Configure CORS - router.Use(cors.Handler(cors.Options{ - AllowedOrigins: cfg.AllowedOrigins, - AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"}, - AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, - ExposedHeaders: []string{"Link", "X-Total-Count"}, - AllowCredentials: true, - MaxAge: 300, // 5 minutes - })) - router.Use(middleware.Logger) - router.Use(middleware.Recoverer) - router.Use(middleware.Timeout(60 * time.Second)) - router.Use(mdleware.JSONContentType) - - router.Method("Post", endpoint.Register, http.HandlerFunc(userHandler.Register)) - router.Method("Post", endpoint.Login, http.HandlerFunc(loginHandler.Login)) - router.Method("Post", endpoint.CreateServiceUser, http.HandlerFunc(serviceHandler.Register)) - router.Method("Post", endpoint.LoginServiceUser, http.HandlerFunc(serviceHandler.Login)) - router.Method("Post", endpoint.TokenRefresh, http.HandlerFunc(refreshHandler.Refresh)) - router.Method("PATCH", endpoint.UpdatePassword, mdleware.AuthMiddleware(jwtService)(http.HandlerFunc(loginHandler.UpdatePassword))) - - router.Method("GET", "/swagger/*", httpSwagger.Handler( - httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.Port)), - )) - - // Start server - server := &http.Server{ - Addr: ":" + cfg.ServerPort, - Handler: router, - ReadTimeout: 15 * time.Second, - WriteTimeout: 15 * time.Second, - IdleTimeout: 60 * time.Second, - } - - // Graceful shutdown - go func() { - log.Printf("Server starting on port %s", cfg.ServerPort) - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - log.Fatalf("Server failed to start: %v", err) - } - }() - - // Wait for interrupt signal - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - log.Println("Shutting down server...") - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - if err := server.Shutdown(ctx); err != nil { - log.Fatalf("Server forced to shutdown: %v", err) - } - - log.Println("Server exited") -} diff --git a/cmd/api/main_test.go b/cmd/api/main_test.go deleted file mode 100644 index 47b1172..0000000 --- a/cmd/api/main_test.go +++ /dev/null @@ -1,96 +0,0 @@ -package main - -import ( - "context" - "flag" - "fmt" - "net/http" - "os" - "path" - "testing" - - "github.com/go-chi/chi/v5" - "github.com/joho/godotenv" - - "git.kundeng.us/phoenix/textsender-auth/internal/config" - "git.kundeng.us/phoenix/textsender-auth/internal/db" - "git.kundeng.us/phoenix/textsender-auth/internal/handler" - "git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint" - mdleware "git.kundeng.us/phoenix/textsender-auth/internal/middleware" - "git.kundeng.us/phoenix/textsender-auth/internal/services" - "git.kundeng.us/phoenix/textsender-auth/internal/store" -) - -var testRouter *chi.Mux - -func TestMain(m *testing.M) { - cfg := load() - - database, err := db.NewDatabase(cfg.GetDBConnString()) - if err != nil { - fmt.Println(err.Error()) - panic("Failed to initialize database") - } - defer database.Close() - - ctx := context.Background() - err = database.ResetDatabase(ctx) - if err != nil { - fmt.Println(err.Error()) - panic("Failed to initialize database") - } - - userStore := store.NewUserStore(database.Pool) - serviceStore := store.NewServiceStore(database.Pool) - userHandler := handler.NewUserHandler(cfg, userStore) - loginHandler := handler.NewLoginHandler(cfg, userStore) - serviceHandler := handler.NewServiceHandler(cfg, serviceStore) - refreshHandler := handler.NewRefreshHandler(cfg, userStore, serviceStore) - - testRouter = chi.NewRouter() - jwtService := services.NewJWTService(config.GetSecretKey()) - testRouter.Method("POST", endpoint.Register, http.HandlerFunc(userHandler.Register)) - testRouter.Method("POST", endpoint.Login, http.HandlerFunc(loginHandler.Login)) - testRouter.Method("POST", endpoint.CreateServiceUser, http.HandlerFunc(serviceHandler.Register)) - testRouter.Method("POST", endpoint.LoginServiceUser, http.HandlerFunc(serviceHandler.Login)) - testRouter.Method("POST", endpoint.TokenRefresh, http.HandlerFunc(refreshHandler.Refresh)) - testRouter.Method("PATCH", endpoint.UpdatePassword, mdleware.AuthMiddleware(jwtService)(http.HandlerFunc(loginHandler.UpdatePassword))) - - code := m.Run() - os.Exit(code) -} - -func load() *config.Config { - resetDb := flag.Bool("reset-db", false, "Reset the database schema and exit") - port := flag.String("port", config.Port, "Server port") - flag.Parse() - - cwd, _ := os.Getwd() - envPath := path.Join(cwd, ".env") - - err := godotenv.Load(envPath) - if err != nil { - envPath = path.Join(cwd, "../..", ".env") - if err := godotenv.Load(envPath); err != nil { - panic("Error loading .env file: " + err.Error()) - } - } - - unpackedConnString := config.UnpackDBConnString() - dbConnString := unpackedConnString.Parse() - - return &config.Config{ - DBConnString: dbConnString, - ServerPort: *port, - ResetDB: *resetDb, - EnableRegistration: config.CheckRegistration(), - } -} - -func resetTestDB(t *testing.T) { - t.Helper() - _, err := db.Pool.Exec(context.Background(), "DELETE FROM users") - if err != nil { - t.Fatalf("Failed to reset test database: %v", err) - } -} diff --git a/docker-compose.yaml b/docker-compose.yaml deleted file mode 100644 index 56afe67..0000000 --- a/docker-compose.yaml +++ /dev/null @@ -1,44 +0,0 @@ -version: '3.8' # Use a recent version - -services: - auth_api: - build: # Tells docker-compose to build the Dockerfile in the current directory - context: . - ssh: ["default"] # Uses host's SSH agent - container_name: textsender_auth # Optional: Give the container a specific name - ports: - # Map host port 8000 to container port 3000 (adjust as needed) - - "9080:9080" - env_file: - - .env - depends_on: - auth_db: - condition: service_healthy # Wait for the DB to be healthy before starting the app - restart: unless-stopped # Optional: Restart policy - - # PostgreSQL Database Service - auth_db: - image: postgres:18.4-alpine # Use an official Postgres image (Alpine variant is smaller) - container_name: textsender_auth_db # Optional: Give the container a specific name - environment: - # These MUST match the user, password, and database name in the DATABASE_URL above - POSTGRES_USER: ${POSTGRES_AUTH_USER:-textsender_auth} - POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password} - POSTGRES_DB: ${POSTGRES_AUTH_DB:-textsender_auth_db} - volumes: - # Persist database data using a named volume - - postgres_data:/var/lib/postgresql - ports: [] - healthcheck: - # Checks if Postgres is ready to accept connections - test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] - interval: 10s - timeout: 5s - retries: 5 - start_period: 10s - restart: always # Optional: Restart policy - -# Define the named volume for data persistence -volumes: - postgres_data: - driver: local # Use the default local driver diff --git a/docs/docs.go b/docs/docs.go deleted file mode 100644 index a5b3551..0000000 --- a/docs/docs.go +++ /dev/null @@ -1,684 +0,0 @@ -// Package docs Code generated by swaggo/swag. DO NOT EDIT -package docs - -import "github.com/swaggo/swag" - -const docTemplate = `{ - "schemes": {{ marshal .Schemes }}, - "swagger": "2.0", - "info": { - "description": "{{escape .Description}}", - "title": "{{.Title}}", - "contact": {}, - "version": "{{.Version}}" - }, - "host": "{{.Host}}", - "basePath": "{{.BasePath}}", - "paths": { - "/login": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Login and be given an access token (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "users" - ], - "summary": "Login", - "parameters": [ - { - "description": "Data to obtain a token", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.LoginAccount" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.LoginResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.LoginResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.LoginResponse" - } - } - } - } - }, - "/register": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Create a user that can send texts (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "users" - ], - "summary": "Register user", - "parameters": [ - { - "description": "Data to add user", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RegisterUser" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.RegisterResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.RegisterResponse" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/handler.RegisterResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.RegisterResponse" - } - } - } - } - }, - "/service/login": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Servce login and be given an access token (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "service users" - ], - "summary": "Service login", - "parameters": [ - { - "description": "Data to obtain a service token", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.ServiceLoginRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.ServiceLoginResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.ServiceLoginResponse" - } - } - } - } - }, - "/service/register": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Create a service user that can send texts (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "service users" - ], - "summary": "Register service user", - "parameters": [ - { - "description": "Data to add user", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.ServiceCreationRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.ServiceCreationResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.ServiceCreationResponse" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/handler.ServiceCreationResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.ServiceCreationResponse" - } - } - } - } - }, - "/token/refresh": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Refresh token endpoint (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "refresh" - ], - "summary": "Obtain a refresh token", - "parameters": [ - { - "description": "Data to refresh token", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RefreshRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.RefreshResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.RefreshResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.RefreshResponse" - } - } - } - } - }, - "/user/name/update": { - "patch": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update the first or last name of a user (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "users" - ], - "summary": "Update name of user", - "parameters": [ - { - "description": "Data to update name of user", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.UpdateNameRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - } - } - } - }, - "/user/password/update": { - "patch": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update the password of a regular account (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "users" - ], - "summary": "Update Password", - "parameters": [ - { - "description": "Needed data to update password", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.UpdatePasswordRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.UpdatePasswordResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.UpdatePasswordResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.UpdatePasswordResponse" - } - } - } - } - } - }, - "definitions": { - "git_kundeng_us_phoenix_textsender-models_tx0_user.User": { - "type": "object", - "properties": { - "created": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "id": { - "type": "string" - }, - "last_login": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.LoginAccount": { - "type": "object", - "properties": { - "password": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.LoginResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/token.Login" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.RefreshRequest": { - "type": "object", - "properties": { - "access_token": { - "type": "string" - } - } - }, - "handler.RefreshResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/token.Login" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.RegisterResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/handler.RegisterResponseItem" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.RegisterResponseItem": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.RegisterUser": { - "type": "object", - "properties": { - "password": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.ServiceCreationRequest": { - "type": "object", - "properties": { - "passphrase": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.ServiceCreationResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/user.ServiceUser" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.ServiceLoginRequest": { - "type": "object", - "properties": { - "passphrase": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.ServiceLoginResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/token.Login" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.UpdateNameRequest": { - "type": "object", - "properties": { - "first_name": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.UpdateNameResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/git_kundeng_us_phoenix_textsender-models_tx0_user.User" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.UpdatePasswordRequest": { - "type": "object", - "properties": { - "confirmed_password": { - "type": "string" - }, - "current_password": { - "type": "string" - }, - "updated_password": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.UpdatePasswordResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/git_kundeng_us_phoenix_textsender-models_tx0_user.User" - } - }, - "message": { - "type": "string" - } - } - }, - "token.Login": { - "type": "object", - "properties": { - "access_token": { - "type": "string" - }, - "expires_in": { - "type": "integer" - }, - "issued_at": { - "type": "integer" - }, - "token_type": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "user.ServiceUser": { - "type": "object", - "properties": { - "created": { - "type": "string" - }, - "id": { - "type": "string" - }, - "last_login": { - "type": "string" - }, - "passphrase": { - "type": "string" - }, - "username": { - "description": "TODO: Remove the omitempty tags at a later point", - "type": "string" - } - } - } - }, - "securityDefinitions": { - "BearerAuth": { - "description": "JWT Bearer Token", - "type": "apiKey", - "name": "Authorization", - "in": "header" - } - } -}` - -// SwaggerInfo holds exported Swagger Info so clients can modify it -var SwaggerInfo = &swag.Spec{ - Version: "1.0", - Host: "localhost:9080", - BasePath: "/api/v1", - Schemes: []string{}, - Title: "textsender-auth", - Description: "Auth API to send text messages", - InfoInstanceName: "swagger", - SwaggerTemplate: docTemplate, - LeftDelim: "{{", - RightDelim: "}}", -} - -func init() { - swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) -} diff --git a/docs/swagger.json b/docs/swagger.json deleted file mode 100644 index 37f7ab1..0000000 --- a/docs/swagger.json +++ /dev/null @@ -1,660 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "description": "Auth API to send text messages", - "title": "textsender-auth", - "contact": {}, - "version": "1.0" - }, - "host": "localhost:9080", - "basePath": "/api/v1", - "paths": { - "/login": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Login and be given an access token (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "users" - ], - "summary": "Login", - "parameters": [ - { - "description": "Data to obtain a token", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.LoginAccount" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.LoginResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.LoginResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.LoginResponse" - } - } - } - } - }, - "/register": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Create a user that can send texts (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "users" - ], - "summary": "Register user", - "parameters": [ - { - "description": "Data to add user", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RegisterUser" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.RegisterResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.RegisterResponse" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/handler.RegisterResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.RegisterResponse" - } - } - } - } - }, - "/service/login": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Servce login and be given an access token (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "service users" - ], - "summary": "Service login", - "parameters": [ - { - "description": "Data to obtain a service token", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.ServiceLoginRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.ServiceLoginResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.ServiceLoginResponse" - } - } - } - } - }, - "/service/register": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Create a service user that can send texts (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "service users" - ], - "summary": "Register service user", - "parameters": [ - { - "description": "Data to add user", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.ServiceCreationRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.ServiceCreationResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.ServiceCreationResponse" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/handler.ServiceCreationResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.ServiceCreationResponse" - } - } - } - } - }, - "/token/refresh": { - "post": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Refresh token endpoint (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "refresh" - ], - "summary": "Obtain a refresh token", - "parameters": [ - { - "description": "Data to refresh token", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.RefreshRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.RefreshResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.RefreshResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.RefreshResponse" - } - } - } - } - }, - "/user/name/update": { - "patch": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update the first or last name of a user (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "users" - ], - "summary": "Update name of user", - "parameters": [ - { - "description": "Data to update name of user", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.UpdateNameRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - }, - "403": { - "description": "Forbidden", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.UpdateNameResponse" - } - } - } - } - }, - "/user/password/update": { - "patch": { - "security": [ - { - "BearerAuth": [] - } - ], - "description": "Update the password of a regular account (requires JWT)", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "users" - ], - "summary": "Update Password", - "parameters": [ - { - "description": "Needed data to update password", - "name": "request", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/handler.UpdatePasswordRequest" - } - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/handler.UpdatePasswordResponse" - } - }, - "400": { - "description": "Bad Request", - "schema": { - "$ref": "#/definitions/handler.UpdatePasswordResponse" - } - }, - "500": { - "description": "Internal Server Error", - "schema": { - "$ref": "#/definitions/handler.UpdatePasswordResponse" - } - } - } - } - } - }, - "definitions": { - "git_kundeng_us_phoenix_textsender-models_tx0_user.User": { - "type": "object", - "properties": { - "created": { - "type": "string" - }, - "first_name": { - "type": "string" - }, - "id": { - "type": "string" - }, - "last_login": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "password": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.LoginAccount": { - "type": "object", - "properties": { - "password": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.LoginResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/token.Login" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.RefreshRequest": { - "type": "object", - "properties": { - "access_token": { - "type": "string" - } - } - }, - "handler.RefreshResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/token.Login" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.RegisterResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/handler.RegisterResponseItem" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.RegisterResponseItem": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.RegisterUser": { - "type": "object", - "properties": { - "password": { - "type": "string" - }, - "phone_number": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.ServiceCreationRequest": { - "type": "object", - "properties": { - "passphrase": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.ServiceCreationResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/user.ServiceUser" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.ServiceLoginRequest": { - "type": "object", - "properties": { - "passphrase": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "handler.ServiceLoginResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/token.Login" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.UpdateNameRequest": { - "type": "object", - "properties": { - "first_name": { - "type": "string" - }, - "last_name": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.UpdateNameResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/git_kundeng_us_phoenix_textsender-models_tx0_user.User" - } - }, - "message": { - "type": "string" - } - } - }, - "handler.UpdatePasswordRequest": { - "type": "object", - "properties": { - "confirmed_password": { - "type": "string" - }, - "current_password": { - "type": "string" - }, - "updated_password": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "handler.UpdatePasswordResponse": { - "type": "object", - "properties": { - "data": { - "type": "array", - "items": { - "$ref": "#/definitions/git_kundeng_us_phoenix_textsender-models_tx0_user.User" - } - }, - "message": { - "type": "string" - } - } - }, - "token.Login": { - "type": "object", - "properties": { - "access_token": { - "type": "string" - }, - "expires_in": { - "type": "integer" - }, - "issued_at": { - "type": "integer" - }, - "token_type": { - "type": "string" - }, - "user_id": { - "type": "string" - } - } - }, - "user.ServiceUser": { - "type": "object", - "properties": { - "created": { - "type": "string" - }, - "id": { - "type": "string" - }, - "last_login": { - "type": "string" - }, - "passphrase": { - "type": "string" - }, - "username": { - "description": "TODO: Remove the omitempty tags at a later point", - "type": "string" - } - } - } - }, - "securityDefinitions": { - "BearerAuth": { - "description": "JWT Bearer Token", - "type": "apiKey", - "name": "Authorization", - "in": "header" - } - } -} \ No newline at end of file diff --git a/docs/swagger.yaml b/docs/swagger.yaml deleted file mode 100644 index c726852..0000000 --- a/docs/swagger.yaml +++ /dev/null @@ -1,421 +0,0 @@ -basePath: /api/v1 -definitions: - git_kundeng_us_phoenix_textsender-models_tx0_user.User: - properties: - created: - type: string - first_name: - type: string - id: - type: string - last_login: - type: string - last_name: - type: string - password: - type: string - phone_number: - type: string - username: - type: string - type: object - handler.LoginAccount: - properties: - password: - type: string - username: - type: string - type: object - handler.LoginResponse: - properties: - data: - items: - $ref: '#/definitions/token.Login' - type: array - message: - type: string - type: object - handler.RefreshRequest: - properties: - access_token: - type: string - type: object - handler.RefreshResponse: - properties: - data: - items: - $ref: '#/definitions/token.Login' - type: array - message: - type: string - type: object - handler.RegisterResponse: - properties: - data: - items: - $ref: '#/definitions/handler.RegisterResponseItem' - type: array - message: - type: string - type: object - handler.RegisterResponseItem: - properties: - id: - type: string - phone_number: - type: string - username: - type: string - type: object - handler.RegisterUser: - properties: - password: - type: string - phone_number: - type: string - username: - type: string - type: object - handler.ServiceCreationRequest: - properties: - passphrase: - type: string - username: - type: string - type: object - handler.ServiceCreationResponse: - properties: - data: - items: - $ref: '#/definitions/user.ServiceUser' - type: array - message: - type: string - type: object - handler.ServiceLoginRequest: - properties: - passphrase: - type: string - username: - type: string - type: object - handler.ServiceLoginResponse: - properties: - data: - items: - $ref: '#/definitions/token.Login' - type: array - message: - type: string - type: object - handler.UpdateNameRequest: - properties: - first_name: - type: string - last_name: - type: string - user_id: - type: string - type: object - handler.UpdateNameResponse: - properties: - data: - items: - $ref: '#/definitions/git_kundeng_us_phoenix_textsender-models_tx0_user.User' - type: array - message: - type: string - type: object - handler.UpdatePasswordRequest: - properties: - confirmed_password: - type: string - current_password: - type: string - updated_password: - type: string - user_id: - type: string - type: object - handler.UpdatePasswordResponse: - properties: - data: - items: - $ref: '#/definitions/git_kundeng_us_phoenix_textsender-models_tx0_user.User' - type: array - message: - type: string - type: object - token.Login: - properties: - access_token: - type: string - expires_in: - type: integer - issued_at: - type: integer - token_type: - type: string - user_id: - type: string - type: object - user.ServiceUser: - properties: - created: - type: string - id: - type: string - last_login: - type: string - passphrase: - type: string - username: - description: 'TODO: Remove the omitempty tags at a later point' - type: string - type: object -host: localhost:9080 -info: - contact: {} - description: Auth API to send text messages - title: textsender-auth - version: "1.0" -paths: - /login: - post: - consumes: - - application/json - description: Login and be given an access token (requires JWT) - parameters: - - description: Data to obtain a token - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.LoginAccount' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.LoginResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.LoginResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.LoginResponse' - security: - - BearerAuth: [] - summary: Login - tags: - - users - /register: - post: - consumes: - - application/json - description: Create a user that can send texts (requires JWT) - parameters: - - description: Data to add user - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.RegisterUser' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.RegisterResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.RegisterResponse' - "403": - description: Forbidden - schema: - $ref: '#/definitions/handler.RegisterResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.RegisterResponse' - security: - - BearerAuth: [] - summary: Register user - tags: - - users - /service/login: - post: - consumes: - - application/json - description: Servce login and be given an access token (requires JWT) - parameters: - - description: Data to obtain a service token - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.ServiceLoginRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.ServiceLoginResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.ServiceLoginResponse' - security: - - BearerAuth: [] - summary: Service login - tags: - - service users - /service/register: - post: - consumes: - - application/json - description: Create a service user that can send texts (requires JWT) - parameters: - - description: Data to add user - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.ServiceCreationRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.ServiceCreationResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.ServiceCreationResponse' - "403": - description: Forbidden - schema: - $ref: '#/definitions/handler.ServiceCreationResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.ServiceCreationResponse' - security: - - BearerAuth: [] - summary: Register service user - tags: - - service users - /token/refresh: - post: - consumes: - - application/json - description: Refresh token endpoint (requires JWT) - parameters: - - description: Data to refresh token - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.RefreshRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.RefreshResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.RefreshResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.RefreshResponse' - security: - - BearerAuth: [] - summary: Obtain a refresh token - tags: - - refresh - /user/name/update: - patch: - consumes: - - application/json - description: Update the first or last name of a user (requires JWT) - parameters: - - description: Data to update name of user - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.UpdateNameRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.UpdateNameResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.UpdateNameResponse' - "403": - description: Forbidden - schema: - $ref: '#/definitions/handler.UpdateNameResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.UpdateNameResponse' - security: - - BearerAuth: [] - summary: Update name of user - tags: - - users - /user/password/update: - patch: - consumes: - - application/json - description: Update the password of a regular account (requires JWT) - parameters: - - description: Needed data to update password - in: body - name: request - required: true - schema: - $ref: '#/definitions/handler.UpdatePasswordRequest' - produces: - - application/json - responses: - "200": - description: OK - schema: - $ref: '#/definitions/handler.UpdatePasswordResponse' - "400": - description: Bad Request - schema: - $ref: '#/definitions/handler.UpdatePasswordResponse' - "500": - description: Internal Server Error - schema: - $ref: '#/definitions/handler.UpdatePasswordResponse' - security: - - BearerAuth: [] - summary: Update Password - tags: - - users -securityDefinitions: - BearerAuth: - description: JWT Bearer Token - in: header - name: Authorization - type: apiKey -swagger: "2.0" diff --git a/go.mod b/go.mod deleted file mode 100644 index 6cf2236..0000000 --- a/go.mod +++ /dev/null @@ -1,45 +0,0 @@ -module git.kundeng.us/phoenix/textsender-auth - -go 1.26.2 - -require ( - git.kundeng.us/phoenix/textsender-models v0.2.1 - github.com/go-chi/chi/v5 v5.2.5 - github.com/go-chi/cors v1.2.2 - github.com/golang-jwt/jwt/v5 v5.3.1 - github.com/google/uuid v1.6.0 - github.com/jackc/pgx/v5 v5.9.2 - github.com/joho/godotenv v1.5.1 - github.com/stretchr/testify v1.11.1 - github.com/swaggo/http-swagger/v2 v2.0.2 - github.com/swaggo/swag v1.16.6 - golang.org/x/crypto v0.50.0 -) - -require ( - github.com/KyleBanks/depth v1.2.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-openapi/jsonpointer v0.23.1 // indirect - github.com/go-openapi/jsonreference v0.21.5 // indirect - github.com/go-openapi/spec v0.22.4 // indirect - github.com/go-openapi/swag/conv v0.26.0 // indirect - github.com/go-openapi/swag/jsonname v0.26.0 // indirect - github.com/go-openapi/swag/jsonutils v0.26.0 // indirect - github.com/go-openapi/swag/loading v0.26.0 // indirect - github.com/go-openapi/swag/stringutils v0.26.0 // indirect - github.com/go-openapi/swag/typeutils v0.26.0 // indirect - github.com/go-openapi/swag/yamlutils v0.26.0 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect - github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/kr/text v0.2.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/swaggo/files/v2 v2.0.2 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/mod v0.35.0 // indirect - golang.org/x/sync v0.20.0 // indirect - golang.org/x/text v0.36.0 // indirect - golang.org/x/tools v0.44.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect -) diff --git a/go.sum b/go.sum deleted file mode 100644 index 3940a58..0000000 --- a/go.sum +++ /dev/null @@ -1,92 +0,0 @@ -git.kundeng.us/phoenix/textsender-models v0.2.1 h1:21br4NF58aUFuCx8laKxC5RvZMl4GsSIaMX4bvf5plw= -git.kundeng.us/phoenix/textsender-models v0.2.1/go.mod h1:nu5QWy9o+spx/t9NFipaGmF5qiBJS/0QhxyCjoi3Z3E= -github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= -github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug= -github.com/go-chi/chi/v5 v5.2.5/go.mod h1:X7Gx4mteadT3eDOMTsXzmI4/rwUpOwBHLpAfupzFJP0= -github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= -github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-openapi/jsonpointer v0.23.1 h1:1HBACs7XIwR2RcmItfdSFlALhGbe6S92p0ry4d1GWg4= -github.com/go-openapi/jsonpointer v0.23.1/go.mod h1:iWRmZTrGn7XwYhtPt/fvdSFj1OfNBngqRT2UG3BxSqY= -github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE= -github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= -github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ= -github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ= -github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= -github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= -github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= -github.com/go-openapi/swag/jsonname v0.26.0 h1:gV1NFX9M8avo0YSpmWogqfQISigCmpaiNci8cGECU5w= -github.com/go-openapi/swag/jsonname v0.26.0/go.mod h1:urBBR8bZNoDYGr653ynhIx+gTeIz0ARZxHkAPktJK2M= -github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= -github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= -github.com/go-openapi/swag/loading v0.26.0 h1:Apg6zaKhCJurpJer0DCxq99qwmhFddBhaMX7kilDcko= -github.com/go-openapi/swag/loading v0.26.0/go.mod h1:dBxQ/6V2uBaAQdevN18VELE6xSpJWZxLX4txe12JwDg= -github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= -github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= -github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= -github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= -github.com/go-openapi/swag/yamlutils v0.26.0 h1:H7O8l/8NJJQ/oiReEN+oMpnGMyt8G0hl460nRZxhLMQ= -github.com/go-openapi/swag/yamlutils v0.26.0/go.mod h1:1evKEGAtP37Pkwcc7EWMF0hedX0/x3Rkvei2wtG/TbU= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= -github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= -github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= -github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= -github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= -github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= -github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.9.2 h1:3ZhOzMWnR4yJ+RW1XImIPsD1aNSz4T4fyP7zlQb56hw= -github.com/jackc/pgx/v5 v5.9.2/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= -github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= -github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU= -github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0= -github.com/swaggo/http-swagger/v2 v2.0.2 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg= -github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ= -github.com/swaggo/swag v1.16.6 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI= -github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= -golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= -golang.org/x/mod v0.35.0 h1:Ww1D637e6Pg+Zb2KrWfHQUnH2dQRLBQyAtpr/haaJeM= -golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= -golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -golang.org/x/tools v0.44.0 h1:UP4ajHPIcuMjT1GqzDWRlalUEoY+uzoZKnhOjbIPD2c= -golang.org/x/tools v0.44.0/go.mod h1:KA0AfVErSdxRZIsOVipbv3rQhVXTnlU6UhKxHd1seDI= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/config/config.go b/internal/config/config.go deleted file mode 100644 index 74ec025..0000000 --- a/internal/config/config.go +++ /dev/null @@ -1,168 +0,0 @@ -package config - -import ( - "flag" - "fmt" - "log" - "os" - "path" - "strconv" - "strings" - - "github.com/joho/godotenv" - - "git.kundeng.us/phoenix/textsender-auth/internal/version" -) - -type Config struct { - DBConnString string - ServerPort string - ResetDB bool - EnableRegistration bool - AllowedOrigins []string -} - -type ConnectionInfo struct { - Username string - Password string - Database string - Host string - Port int - SslMode string -} - -const Port = "9080" -const App_Name = "textsender_auth" - -func (ci ConnectionInfo) Parse() string { - return fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=%s", ci.Username, ci.Password, ci.Host, ci.Port, ci.Database, ci.SslMode) -} - -func PrintName() { - fmt.Println(App_Name) - fmt.Println(version.String()) -} - -func Load() *Config { - versionFlag := flag.Bool("version", false, "Print version information") - resetDb := flag.Bool("reset-db", false, "Reset the database schema and exit") - port := flag.String("port", Port, "Server port") - flag.Parse() - - if *versionFlag { - fmt.Println(version.String()) - os.Exit(-1) - } - - err := godotenv.Load() - if err != nil { - cwd, _ := os.Getwd() - envPath := path.Join(cwd, "../..", ".env") - if err = godotenv.Load(envPath); err != nil { - prevPath := path.Join(envPath, "../..", ".env") - if err = godotenv.Load(prevPath); err != nil { - log.Fatal("Error loading .env file") - } - } - } - - unpackedConnString := UnpackDBConnString() - dbConnString := unpackedConnString.Parse() - allowedOrigins := unpackAllowedOrigin() - - if len(allowedOrigins) > 0 { - return &Config{ - DBConnString: dbConnString, - ServerPort: *port, - ResetDB: *resetDb, - EnableRegistration: CheckRegistration(), - AllowedOrigins: allowedOrigins, - } - } else { - return &Config{ - DBConnString: dbConnString, - ServerPort: *port, - ResetDB: *resetDb, - EnableRegistration: CheckRegistration(), - } - } -} - -func GetSecretKey() string { - return os.Getenv("SECRET_KEY") -} - -func UnpackDBConnString() (connInfo ConnectionInfo) { - username := os.Getenv("DB_USER") - password := os.Getenv("DB_PASSWORD") - host := os.Getenv("DB_HOST") - port := os.Getenv("DB_PORT") - database := os.Getenv("DB_NAME") - sslMode := os.Getenv("DB_SSLMODE") - - if username != "" { - connInfo.Username = username - } else { - connInfo.Username = "user" - } - - if password != "" { - connInfo.Password = password - } else { - connInfo.Password = "password" - } - - if host != "" { - connInfo.Host = host - } else { - connInfo.Host = "localhost" - } - - if port != "" { - num, err := strconv.Atoi(port) - if err != nil { - return - } - connInfo.Port = num - } else { - connInfo.Port = 5432 - } - - if database != "" { - connInfo.Database = database - } else { - connInfo.Database = "textsender_auth_db" - } - - if sslMode != "" { - connInfo.SslMode = sslMode - } else { - connInfo.SslMode = "disable" - } - - return -} - -func unpackAllowedOrigin() []string { - allowedOriginsRaw := os.Getenv("ALLOWED_ORIGINS") - allowedOriginsSplit := strings.Split(allowedOriginsRaw, ",") - return allowedOriginsSplit -} - -func CheckRegistration() bool { - if flagValue := os.Getenv("ENABLE_REGISTRATION"); flagValue != "" { - if val := strings.ToUpper(flagValue); val == "TRUE" { - return true - } else if val == "FALSE" { - return false - } else { - return true - } - } else { - return true - } -} - -func (c *Config) GetDBConnString() string { - return c.DBConnString -} diff --git a/internal/db/connection.go b/internal/db/connection.go deleted file mode 100644 index feab2a3..0000000 --- a/internal/db/connection.go +++ /dev/null @@ -1,136 +0,0 @@ -// db/connection.go -package db - -import ( - "context" - "fmt" - "log" - "os" - "path" - "strings" - "time" - - "github.com/jackc/pgx/v5/pgxpool" -) - -var Pool *pgxpool.Pool - -type Database struct { - Pool *pgxpool.Pool -} - -func NewDatabase(connString string) (*Database, error) { - ctx := context.Background() - // Parse the connection string and create pool configuration - config, err := pgxpool.ParseConfig(connString) - if err != nil { - return nil, fmt.Errorf("unable to parse DSN: %v", err) - } - - // Configure connection pool settings - config.MaxConns = 25 - config.MinConns = 5 - config.MaxConnLifetime = time.Hour - config.MaxConnIdleTime = 30 * time.Minute - config.HealthCheckPeriod = time.Minute - - // Configure connection timeouts - config.ConnConfig.ConnectTimeout = 10 * time.Second - config.ConnConfig.RuntimeParams["timezone"] = "UTC" - - // Create the connection pool - pool, err := pgxpool.NewWithConfig(ctx, config) - if err != nil { - return nil, fmt.Errorf("unable to create connection pool: %v", err) - } - - // Test the connection with a short timeout - if err := pool.Ping(ctx); err != nil { - pool.Close() - return nil, fmt.Errorf("unable to ping database: %v", err) - } - - log.Printf("Successfully connected to database") - return &Database{Pool: pool}, nil -} - -func TableExists(ctx context.Context, conn *pgxpool.Pool, tableName string) (bool, error) { - var exists bool - - query := ` - SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name = $1 - ); - ` - - err := conn.QueryRow(ctx, query, tableName).Scan(&exists) - if err != nil { - return false, fmt.Errorf("error checking if table exists: %w", err) - } - - return exists, nil -} - -func (db *Database) Close() { - if db.Pool != nil { - db.Pool.Close() - log.Println("Database connection pool closed") - } -} - -// HealthCheck verifies the database connection is still alive -func (db *Database) HealthCheck(ctx context.Context) error { - return db.Pool.Ping(ctx) -} - -// GetPoolStats returns connection pool statistics -func (db *Database) GetPoolStats() string { - stats := db.Pool.Stat() - return fmt.Sprintf("TotalConns: %d, IdleConns: %d, AcquiredConns: %d", - stats.TotalConns(), stats.IdleConns(), stats.AcquiredConns()) -} - -func (db *Database) ResetDatabase(ctx context.Context) error { - tx, err := db.Pool.Begin(ctx) - if err != nil { - return fmt.Errorf("Transaction unable to begin: %v", err) - } - defer tx.Rollback(ctx) - - schemaContent, err := os.ReadFile("migrations/schema.sql") - if err != nil { - log.Println("Default migrations not found. Checking different directory") - cwd, _ := os.Getwd() - migrationsPath := path.Join(cwd, "../..", "migrations/schema.sql") - schemaContent, err = os.ReadFile(migrationsPath) - if err != nil { - return fmt.Errorf("error reading schema file: %v", err) - } - } - - for _, stmt := range strings.Split(string(schemaContent), ";") { - stmt = strings.TrimSpace(stmt) - if stmt == "" { - continue - } - - if strings.HasPrefix(stmt, "--") { - continue - } - - _, err := tx.Exec(ctx, stmt) - if err != nil { - return fmt.Errorf("error executing SQL statement: %v\nStatement: %s", err, stmt) - } - } - - if err := tx.Commit(ctx); err != nil { - return fmt.Errorf("unable to commit transaction: %v", err) - } - - log.Println("Database schema applied successfully") - - return nil -} diff --git a/internal/handler/endpoint/endpoint.go b/internal/handler/endpoint/endpoint.go deleted file mode 100644 index 8e3b61f..0000000 --- a/internal/handler/endpoint/endpoint.go +++ /dev/null @@ -1,14 +0,0 @@ -package endpoint - -const ( - // Endpoint for registering a user - Register = "/api/v1/register" - Login = "/api/v1/login" - UpdatePassword = "/api/v1/user/password/update" - UpdateName = "/api/v1/user/name/update" - - CreateServiceUser = "/api/v1/service/register" - LoginServiceUser = "/api/v1/service/login" - - TokenRefresh = "/api/v1/token/refresh" -) diff --git a/internal/handler/login.go b/internal/handler/login.go deleted file mode 100644 index 3ddab7a..0000000 --- a/internal/handler/login.go +++ /dev/null @@ -1,191 +0,0 @@ -package handler - -import ( - "log" - "net/http" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/token" - "git.kundeng.us/phoenix/textsender-models/tx0/user" - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-auth/internal/config" - "git.kundeng.us/phoenix/textsender-auth/internal/store" - "git.kundeng.us/phoenix/textsender-auth/internal/utility" -) - -type LoginHandler struct { - Config *config.Config - UserStore store.UserStore -} - -func NewLoginHandler(cfg *config.Config, userStore store.UserStore) *LoginHandler { - return &LoginHandler{Config: cfg, UserStore: userStore} -} - -type LoginAccount struct { - Username string `json:"username"` - Password string `json:"password"` -} - -type LoginResponse struct { - Message string `json:"message"` - Data []token.Login `json:"data"` -} - -// Login godoc -// @Summary Login -// @Description Login and be given an access token (requires JWT) -// @Tags users -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body LoginAccount true "Data to obtain a token" -// @Success 200 {object} LoginResponse -// @Failure 400 {object} LoginResponse -// @Failure 500 {object} LoginResponse -// @Router /login [post] -func (l *LoginHandler) Login(w http.ResponseWriter, r *http.Request) { - var req LoginAccount - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - var statusCode int - var resp LoginResponse - - ctx := r.Context() - - if exists, err := l.UserStore.UserExists(ctx, req.Username); err != nil { - log.Println("Error:", err) - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if !exists { - statusCode = http.StatusBadRequest - resp.Message = "Failure in user check" - } else { - if user, err := l.UserStore.GetUserByUsername(ctx, req.Username); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - hashing := utility.HashMash{} - if err := hashing.SetPassword(req.Password); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if hashing.CheckPasswordHash(req.Password, user.Password) { - lastLogin := time.Now() - var tokGen utility.TokenGenerator - secretKey := config.GetSecretKey() - tokGen.SetSecretKey(secretKey) - if myToken, err := tokGen.GenerateToken(*user); err != nil { - log.Println(err.Error()) - statusCode = http.StatusInternalServerError - resp.Message = "Error generating token" - } else { - log.Println("Updating user's last login") - if rowsAffected, err := l.UserStore.UpdateLastLogin(ctx, user.Id, lastLogin); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - log.Println("Rows updated:", rowsAffected) - statusCode = http.StatusOK - resp.Data = append(resp.Data, *myToken) - resp.Message = "Successful" - } - } - } else { - statusCode = http.StatusNotFound - resp.Message = "User not found" - } - } - } - } - } - - RespondWithJson(w, statusCode, &resp) -} - -type UpdatePasswordRequest struct { - UserId uuid.UUID `json:"user_id"` - CurrentPassword string `json:"current_password"` - UpdatedPassword string `json:"updated_password"` - ConfirmedPassword string `json:"confirmed_password"` -} - -type UpdatePasswordResponse struct { - Message string `json:"message"` - Data []user.User `json:"data"` -} - -// UpdatePassword godoc -// @Summary Update Password -// @Description Update the password of a regular account (requires JWT) -// @Tags users -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body UpdatePasswordRequest true "Needed data to update password" -// @Success 200 {object} UpdatePasswordResponse -// @Failure 400 {object} UpdatePasswordResponse -// @Failure 500 {object} UpdatePasswordResponse -// @Router /user/password/update [patch] -func (l *LoginHandler) UpdatePassword(w http.ResponseWriter, r *http.Request) { - var req UpdatePasswordRequest - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - var statusCode int - var resp UpdatePasswordResponse - - ctx := r.Context() - - if usr, err := l.UserStore.GetUserByID(ctx, req.UserId); err != nil { - log.Println("Error:", err) - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - hashing := utility.HashMash{} - if err := hashing.SetPassword(req.CurrentPassword); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if hashing.CheckPasswordHash(req.CurrentPassword, usr.Password) { - if req.UpdatedPassword == req.ConfirmedPassword { - // Hash password - err := hashing.SetPassword(req.UpdatedPassword) - hashedPassword, err := hashing.HashPassword() - if err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - // Update user password - usr.Password = hashedPassword - // Save user in DB - if rowsAffected, err := l.UserStore.UpdatePassword(ctx, usr.Id, usr.Password); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - log.Println("Rows affected:", rowsAffected) - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, *usr) - } - } - } else { - statusCode = http.StatusBadRequest - resp.Message = "Passwords do not match" - } - } else { - statusCode = http.StatusBadRequest - resp.Message = "User not found" - } - } - } - - RespondWithJson(w, statusCode, &resp) -} diff --git a/internal/handler/login_test.go b/internal/handler/login_test.go deleted file mode 100644 index bfd9f83..0000000 --- a/internal/handler/login_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package handler - -import ( - "context" - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" - "time" - - "github.com/stretchr/testify/assert" - - "git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint" - "git.kundeng.us/phoenix/textsender-auth/internal/store/mock" -) - -func TestLogin(t *testing.T) { - cfg := GetConfig() - mockstore := mock.NewMockUserStore() - handler := NewLoginHandler(cfg, mockstore) - - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - testUser, unhashedPassword, err := createUser(ctx, mockstore) - assert.NoError(t, err, "Error Creating user") - - loginUser := LoginAccount{Username: testUser.Username, Password: *unhashedPassword} - jsonValue, _ := json.Marshal(loginUser) - - req, _ := http.NewRequest("POST", endpoint.Login, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.Login(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response LoginResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err) - - assert.NotEmpty(t, response.Data, "An access token should have been returned") -} - -func TestUpdatePassword(t *testing.T) { - cfg := GetConfig() - mockstore := mock.NewMockUserStore() - handler := NewLoginHandler(cfg, mockstore) - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - testUser, unhashedPassword, err := createUser(ctx, mockstore) - assert.NoError(t, err, "Error Creating user") - assert.NotNil(t, testUser, "User should not be nil") - - updatedPassword := "TakeATrip2yonder!" - newPassword := UpdatePasswordRequest{UserId: testUser.Id, CurrentPassword: *unhashedPassword, UpdatedPassword: updatedPassword, ConfirmedPassword: updatedPassword} - jsonValue, _ := json.Marshal(newPassword) - - req, _ := http.NewRequest("PATCH", endpoint.UpdatePassword, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.UpdatePassword(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - - var response UpdatePasswordResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err) -} diff --git a/internal/handler/refresh.go b/internal/handler/refresh.go deleted file mode 100644 index 8975da6..0000000 --- a/internal/handler/refresh.go +++ /dev/null @@ -1,100 +0,0 @@ -package handler - -import ( - "net/http" - - "git.kundeng.us/phoenix/textsender-models/tx0/token" - - "git.kundeng.us/phoenix/textsender-auth/internal/config" - "git.kundeng.us/phoenix/textsender-auth/internal/store" - "git.kundeng.us/phoenix/textsender-auth/internal/utility" -) - -type RefreshHandler struct { - Config *config.Config - UserStore store.UserStore - ServiceStore store.ServiceStore -} - -func NewRefreshHandler(cfg *config.Config, userStore store.UserStore, serviceStore store.ServiceStore) *RefreshHandler { - return &RefreshHandler{Config: cfg, UserStore: userStore, ServiceStore: serviceStore} -} - -type RefreshRequest struct { - AccessToken string `json:"access_token"` -} - -type RefreshResponse struct { - Message string `json:"message"` - Data []*token.Login `json:"data"` -} - -// Refresh godoc -// @Summary Obtain a refresh token -// @Description Refresh token endpoint (requires JWT) -// @Tags refresh -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body RefreshRequest true "Data to refresh token" -// @Success 200 {object} RefreshResponse -// @Failure 400 {object} RefreshResponse -// @Failure 500 {object} RefreshResponse -// @Router /token/refresh [post] -func (rh *RefreshHandler) Refresh(w http.ResponseWriter, r *http.Request) { - var req RefreshRequest - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - var statusCode int - var resp RefreshResponse - - secretKey := config.GetSecretKey() - tokGen := utility.TokenGenerator{} - tokGen.SetSecretKey(secretKey) - tokGen.SetHourOffset(12) - if verified, err := tokGen.VerifyToken(req.AccessToken); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if verified { - if id, err := tokGen.ExtractIdFromToken(req.AccessToken); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - ctx := r.Context() - if usr, err := rh.UserStore.GetUserByID(ctx, id); err != nil || usr == nil { - if serviceUsr, err := rh.ServiceStore.GetWithId(ctx, id); err != nil || serviceUsr == nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if myToken, err := tokGen.GenerateToken(serviceUsr); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - statusCode = http.StatusOK - resp.Data = append(resp.Data, myToken) - resp.Message = "Successful" - } - } - } else { - if myToken, err := tokGen.GenerateToken(usr); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - statusCode = http.StatusOK - resp.Data = append(resp.Data, myToken) - resp.Message = "Successful" - } - } - } - } else { - statusCode = http.StatusBadRequest - resp.Message = "Unverified" - } - } - - RespondWithJson(w, statusCode, &resp) -} diff --git a/internal/handler/refresh_test.go b/internal/handler/refresh_test.go deleted file mode 100644 index 091a5d8..0000000 --- a/internal/handler/refresh_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package handler - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "git.kundeng.us/phoenix/textsender-models/tx0/user" - "github.com/stretchr/testify/assert" - - "git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint" - "git.kundeng.us/phoenix/textsender-auth/internal/store/mock" - "git.kundeng.us/phoenix/textsender-auth/internal/utility" -) - -func TestRefreshTokenWithMock(t *testing.T) { - var serviceUser user.ServiceUser - var hashedPassword string - var err error - unhashed := "A9328nr29nudx3292m320!" - hashing := utility.HashMash{} - if err := hashing.SetPassword(unhashed); err != nil { - assert.NoError(t, err, "Error setting password") - } - - if hashedPassword, err = hashing.HashPassword(); err != nil { - assert.NoError(t, err, "Error hashing password: %v", err) - } else { - serviceUser.Passphrase = hashedPassword - } - serviceUser.Username = "swoon" - ctx := t.Context() - mockStore := mock.NewMockServiceUserStore() - userStore := mock.NewMockUserStore() - - if err := mockStore.Create(ctx, &serviceUser); err != nil { - assert.NoError(t, err, "Error creating service user: %v", err) - } - - cfg := GetConfig() - handler := NewServiceHandler(cfg, mockStore) - testService := ServiceLoginRequest{Username: serviceUser.Username, Passphrase: unhashed} - jsonValue, err := json.Marshal(testService) - assert.NoError(t, err, "Error marshaling request") - - req, _ := http.NewRequest("POST", endpoint.LoginServiceUser, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.Login(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - - var response ServiceLoginResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err) - - accessToken := response.Data[0].AccessToken - - testReq := RefreshRequest{AccessToken: accessToken} - jsonValue, err = json.Marshal(testReq) - assert.NoError(t, err, "Error marshaling request") - - newHandler := NewRefreshHandler(cfg, userStore, mockStore) - req, _ = http.NewRequest("POST", endpoint.TokenRefresh, strings.NewReader(string(jsonValue))) - rr = httptest.NewRecorder() - - newHandler.Refresh(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) -} diff --git a/internal/handler/register.go b/internal/handler/register.go deleted file mode 100644 index b9b22d6..0000000 --- a/internal/handler/register.go +++ /dev/null @@ -1,188 +0,0 @@ -package handler - -import ( - "fmt" - "log" - "net/http" - - "git.kundeng.us/phoenix/textsender-models/tx0/user" - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-auth/internal/config" - "git.kundeng.us/phoenix/textsender-auth/internal/store" - "git.kundeng.us/phoenix/textsender-auth/internal/utility" -) - -type UserHandler struct { - Config *config.Config - UserStore store.UserStore -} - -func NewUserHandler(cfg *config.Config, userStore store.UserStore) *UserHandler { - return &UserHandler{Config: cfg, UserStore: userStore} -} - -type RegisterUser struct { - PhoneNumber string `json:"phone_number"` - Username string `json:"username"` - Password string `json:"password"` -} - -type RegisterResponseItem struct { - Id uuid.UUID `json:"id"` - PhoneNumber string `json:"phone_number"` - Username string `json:"username"` -} - -type RegisterResponse struct { - Message string `json:"message"` - Data []RegisterResponseItem `json:"data"` -} - -// Register godoc -// @Summary Register user -// @Description Create a user that can send texts (requires JWT) -// @Tags users -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body RegisterUser true "Data to add user" -// @Success 200 {object} RegisterResponse -// @Failure 400 {object} RegisterResponse -// @Failure 403 {object} RegisterResponse -// @Failure 500 {object} RegisterResponse -// @Router /register [post] -func (u *UserHandler) Register(w http.ResponseWriter, r *http.Request) { - var req RegisterUser - err := ExtractFromRequest(r, &req) - if err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - return - } - - defer r.Body.Close() - - var statusCode int - var resp RegisterResponse - if !u.Config.EnableRegistration { - statusCode = http.StatusForbidden - resp.Message = "Registration disabled" - RespondWithJson(w, statusCode, &resp) - return - } - user := user.User{Username: req.Username, Password: req.Password, PhoneNumber: req.PhoneNumber} - - fmt.Println("Username:", user.Username) - - ctx := r.Context() - - if exists, err := u.UserStore.UserExists(ctx, user.Username); err != nil { - fmt.Printf("Error: %v", err) - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if exists { - // User already exists - statusCode = http.StatusBadRequest - resp.Message = "Failure in creating User" - } else { - hashing := utility.HashMash{} - if err := hashing.SetPassword(req.Password); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if hashedPassword, err := hashing.HashPassword(); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - user.Password = hashedPassword - err := u.UserStore.CreateUser(ctx, &user) - if err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - resp.Message = "Successful" - statusCode = http.StatusOK - resp.Data = append(resp.Data, RegisterResponseItem{Id: user.Id, PhoneNumber: user.PhoneNumber, Username: user.Username}) - } - } - } - } - - } - - RespondWithJson(w, statusCode, &resp) -} - -type UpdateNameRequest struct { - Firstname *string `json:"first_name,omitempty"` - Lastname *string `json:"last_name,omitempty"` - UserId uuid.UUID `json:"user_id"` -} - -type UpdateNameResponse struct { - Message string `json:"message"` - Data []*user.User `json:"data"` -} - -// UpdateName godoc -// @Summary Update name of user -// @Description Update the first or last name of a user (requires JWT) -// @Tags users -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body UpdateNameRequest true "Data to update name of user" -// @Success 200 {object} UpdateNameResponse -// @Failure 400 {object} UpdateNameResponse -// @Failure 403 {object} UpdateNameResponse -// @Failure 500 {object} UpdateNameResponse -// @Router /user/name/update [patch] -func (u *UserHandler) UpdateName(w http.ResponseWriter, r *http.Request) { - var req UpdateNameRequest - err := ExtractFromRequest(r, &req) - if err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - return - } - - defer r.Body.Close() - - var statusCode int - var resp UpdateNameResponse - updateFirstname := req.Firstname != nil && len(*req.Firstname) > 0 - updateLastname := req.Lastname != nil && len(*req.Lastname) > 0 - - if req.UserId == uuid.Nil { - statusCode = http.StatusBadRequest - resp.Message = "User Id not provided" - } else if !updateFirstname && !updateLastname { - statusCode = http.StatusBadRequest - resp.Message = "No name provided" - } else { - ctx := r.Context() - if usr, err := u.UserStore.GetUserByID(ctx, req.UserId); err != nil { - log.Println("Error:", err) - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if usr == nil { - statusCode = http.StatusNotFound - resp.Message = "User not found" - } else { - // Add query to update names - if rowsAffected, err := u.UserStore.UpdateName(ctx, req.Firstname, req.Lastname, usr); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - log.Println("Rows updated:", rowsAffected) - statusCode = http.StatusOK - resp.Message = "Successful" - resp.Data = append(resp.Data, usr) - } - } - } - } - - RespondWithJson(w, statusCode, &resp) -} diff --git a/internal/handler/register_test.go b/internal/handler/register_test.go deleted file mode 100644 index 4bd35c7..0000000 --- a/internal/handler/register_test.go +++ /dev/null @@ -1,65 +0,0 @@ -package handler - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "github.com/stretchr/testify/assert" - - "git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint" - "git.kundeng.us/phoenix/textsender-auth/internal/store/mock" -) - -func TestCreateUserWithMock(t *testing.T) { - cfg := GetConfig() - mockstore := mock.NewMockUserStore() - handler := NewUserHandler(cfg, mockstore) - - testUser := GetTestUser() - jsonValue, _ := json.Marshal(testUser) - - req, _ := http.NewRequest("POST", endpoint.Register, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.Register(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response RegisterResponse - err := json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err) - - assert.NotNil(t, response.Data[0].Id, "Id should not be nil") -} - -func TestUpdateNameOfUser(t *testing.T) { - ctx := t.Context() - cfg := GetConfig() - userStore := mock.NewMockUserStore() - - usr := GetTestUser() - if err := userStore.CreateUser(ctx, &usr); err != nil { - assert.NoError(t, err, "Error creating user") - } - - testNameChange := UpdateNameRequest{} - testNameChange.Firstname = &[]string{"Bob"}[0] - testNameChange.Lastname = &[]string{"De-Buildor"}[0] - testNameChange.UserId = usr.Id - jsonValue, _ := json.Marshal(testNameChange) - - req, _ := http.NewRequest("PATCH", endpoint.UpdateName, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler := NewUserHandler(cfg, userStore) - handler.UpdateName(rr, req) - - assert.Equal(t, http.StatusOK, rr.Code) - - var response UpdateNameResponse - err := json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err) -} diff --git a/internal/handler/service.go b/internal/handler/service.go deleted file mode 100644 index 6e2dd9f..0000000 --- a/internal/handler/service.go +++ /dev/null @@ -1,185 +0,0 @@ -package handler - -import ( - "log" - "net/http" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/token" - "git.kundeng.us/phoenix/textsender-models/tx0/user" - - "git.kundeng.us/phoenix/textsender-auth/internal/config" - "git.kundeng.us/phoenix/textsender-auth/internal/store" - "git.kundeng.us/phoenix/textsender-auth/internal/utility" -) - -type ServiceHandler struct { - Config *config.Config - ServiceStore store.ServiceStore -} - -func NewServiceHandler(cfg *config.Config, serviceStore store.ServiceStore) *ServiceHandler { - return &ServiceHandler{Config: cfg, ServiceStore: serviceStore} -} - -type ServiceCreationRequest struct { - Username string `json:"username"` - Passphrase string `json:"passphrase"` -} - -type ServiceCreationResponse struct { - Message string `json:"message"` - Data []*user.ServiceUser `json:"data"` -} - -// Register godoc -// @Summary Register service user -// @Description Create a service user that can send texts (requires JWT) -// @Tags service users -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body ServiceCreationRequest true "Data to add user" -// @Success 200 {object} ServiceCreationResponse -// @Failure 400 {object} ServiceCreationResponse -// @Failure 403 {object} ServiceCreationResponse -// @Failure 500 {object} ServiceCreationResponse -// @Router /service/register [post] -func (s *ServiceHandler) Register(w http.ResponseWriter, r *http.Request) { - var req ServiceCreationRequest - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - return - } - - defer r.Body.Close() - - var statusCode int - var resp ServiceCreationResponse - if !s.Config.EnableRegistration { - statusCode = http.StatusForbidden - resp.Message = "Registration disabled" - RespondWithJson(w, statusCode, &resp) - return - } - - ctx := r.Context() - if exists, err := s.ServiceStore.CheckWithUsername(ctx, req.Username); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if exists { - statusCode = http.StatusBadRequest - resp.Message = "Service user already exists" - } else { - hashing := utility.HashMash{} - if err := hashing.SetPassword(req.Passphrase); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if hashedPassword, err := hashing.HashPassword(); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - serviceUser := user.ServiceUser{Username: req.Username, Passphrase: hashedPassword} - if err := s.ServiceStore.Create(ctx, &serviceUser); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - statusCode = http.StatusCreated - resp.Message = "Successful" - resp.Data = append(resp.Data, &serviceUser) - } - } - } - } - } - - RespondWithJson(w, statusCode, &resp) -} - -type ServiceLoginRequest struct { - Username string `json:"username"` - Passphrase string `json:"passphrase"` -} - -type ServiceLoginResponse struct { - Message string `json:"message"` - Data []*token.Login `json:"data"` -} - -// Login godoc -// @Summary Service login -// @Description Servce login and be given an access token (requires JWT) -// @Tags service users -// @Accept json -// @Produce json -// @Security BearerAuth -// @Param request body ServiceLoginRequest true "Data to obtain a service token" -// @Success 200 {object} ServiceLoginResponse -// @Failure 500 {object} ServiceLoginResponse -// @Router /service/login [post] -func (s *ServiceHandler) Login(w http.ResponseWriter, r *http.Request) { - var req ServiceLoginRequest - if err := ExtractFromRequest(r, &req); err != nil { - http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest) - } - defer r.Body.Close() - - var statusCode int - var resp ServiceLoginResponse - - if len(req.Username) == 0 || len(req.Passphrase) == 0 { - statusCode = http.StatusBadRequest - resp.Message = "Invalid request" - RespondWithJson(w, statusCode, &resp) - return - } - - ctx := r.Context() - - if serviceUser, err := s.ServiceStore.GetWithUsername(ctx, req.Username); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if serviceUser == nil { - statusCode = http.StatusNotFound - resp.Message = "Not found" - } else { - hashing := utility.HashMash{} - if err := hashing.SetPassword(req.Passphrase); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - if !hashing.CheckPasswordHash(req.Passphrase, serviceUser.Passphrase) { - statusCode = http.StatusInternalServerError - resp.Message = "Not valid" - } else { - lastLogin := time.Now() - var tokGen utility.TokenGenerator - tokGen.SetHourOffset(8) - secretKey := config.GetSecretKey() - tokGen.SetSecretKey(secretKey) - - if myToken, err := tokGen.GenerateToken(*serviceUser); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - log.Println("Updating service user's last login") - if rowsAffected, err := s.ServiceStore.UpdateLastLogin(ctx, serviceUser.Id, lastLogin); err != nil { - statusCode = http.StatusInternalServerError - resp.Message = err.Error() - } else { - log.Println("Rows updated:", rowsAffected) - statusCode = http.StatusOK - resp.Data = append(resp.Data, myToken) - resp.Message = "Successful" - } - } - } - } - } - } - - RespondWithJson(w, statusCode, &resp) -} diff --git a/internal/handler/service_test.go b/internal/handler/service_test.go deleted file mode 100644 index 4fbc78d..0000000 --- a/internal/handler/service_test.go +++ /dev/null @@ -1,76 +0,0 @@ -package handler - -import ( - "encoding/json" - "net/http" - "net/http/httptest" - "strings" - "testing" - - "git.kundeng.us/phoenix/textsender-models/tx0/user" - "github.com/stretchr/testify/assert" - - "git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint" - "git.kundeng.us/phoenix/textsender-auth/internal/store/mock" - "git.kundeng.us/phoenix/textsender-auth/internal/utility" -) - -func TestCreateServiceUserWithMock(t *testing.T) { - cfg := GetConfig() - mockStore := mock.NewMockServiceUserStore() - handler := NewServiceHandler(cfg, mockStore) - - testService := ServiceCreationRequest{Username: "swoon", Passphrase: "Ewrewr329n12y3x2!2"} - jsonValue, err := json.Marshal(testService) - assert.NoError(t, err, "Error marshaling request") - - req, _ := http.NewRequest("POST", endpoint.CreateServiceUser, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.Register(rr, req) - assert.Equal(t, http.StatusCreated, rr.Code) - - var response ServiceCreationResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err) -} - -func TestLoginServiceUserWithMock(t *testing.T) { - var serviceUser user.ServiceUser - var hashedPassword string - var err error - unhashed := "A9328nr29nudx3292m320!" - hashing := utility.HashMash{} - if err := hashing.SetPassword(unhashed); err != nil { - assert.NoError(t, err, "Error setting password") - } - - if hashedPassword, err = hashing.HashPassword(); err != nil { - assert.NoError(t, err, "Error hashing password: %v", err) - } else { - serviceUser.Passphrase = hashedPassword - } - serviceUser.Username = "swoon" - ctx := t.Context() - mockStore := mock.NewMockServiceUserStore() - - if err := mockStore.Create(ctx, &serviceUser); err != nil { - assert.NoError(t, err, "Error creating service user: %v", err) - } - - cfg := GetConfig() - handler := NewServiceHandler(cfg, mockStore) - testService := ServiceLoginRequest{Username: serviceUser.Username, Passphrase: unhashed} - jsonValue, err := json.Marshal(testService) - assert.NoError(t, err, "Error marshaling request") - - req, _ := http.NewRequest("POST", endpoint.LoginServiceUser, strings.NewReader(string(jsonValue))) - rr := httptest.NewRecorder() - - handler.Login(rr, req) - assert.Equal(t, http.StatusOK, rr.Code) - - var response ServiceLoginResponse - err = json.Unmarshal(rr.Body.Bytes(), &response) - assert.NoError(t, err) -} diff --git a/internal/handler/utility.go b/internal/handler/utility.go deleted file mode 100644 index 155ef3b..0000000 --- a/internal/handler/utility.go +++ /dev/null @@ -1,21 +0,0 @@ -package handler - -import ( - "encoding/json" - "net/http" -) - -func ExtractFromRequest(r *http.Request, reqItem interface{}) error { - err := json.NewDecoder(r.Body).Decode(&reqItem) - if err != nil { - return err - } else { - return nil - } -} - -func RespondWithJson(w http.ResponseWriter, statusCode int, data interface{}) { - w.Header().Set("Content-type", "application/json") - w.WriteHeader(statusCode) - json.NewEncoder(w).Encode(data) -} diff --git a/internal/handler/utility_test.go b/internal/handler/utility_test.go deleted file mode 100644 index 9e2885b..0000000 --- a/internal/handler/utility_test.go +++ /dev/null @@ -1,63 +0,0 @@ -package handler - -import ( - "context" - "fmt" - "log" - "os" - "path" - - "git.kundeng.us/phoenix/textsender-models/tx0/user" - "github.com/joho/godotenv" - - "git.kundeng.us/phoenix/textsender-auth/internal/config" - "git.kundeng.us/phoenix/textsender-auth/internal/store/mock" - "git.kundeng.us/phoenix/textsender-auth/internal/utility" -) - -func GetTestUser() user.User { - return user.User{Username: "ghost", PhoneNumber: "+1234567890", Password: "Dfgdffd343dfd!"} -} - -func GetConfig() *config.Config { - err := godotenv.Load() - if err != nil { - cwd, _ := os.Getwd() - envPath := path.Join(cwd, "../..", ".env") - if err = godotenv.Load(envPath); err != nil { - prevPath := path.Join(envPath, "../..", ".env") - if err = godotenv.Load(prevPath); err != nil { - log.Fatal("Error loading .env file") - } - } - } - - unpackedConnString := config.UnpackDBConnString() - dbConnString := unpackedConnString.Parse() - - return &config.Config{ - DBConnString: dbConnString, - ServerPort: config.Port, - ResetDB: false, - EnableRegistration: config.CheckRegistration(), - } -} - -func createUser(ctx context.Context, userStore *mock.MockUserStore) (*user.User, *string, error) { - testUser := GetTestUser() - unhashedPassword := testUser.Password - hashing := utility.HashMash{} - if err := hashing.SetPassword(testUser.Password); err != nil { - return nil, nil, fmt.Errorf("Error setting password: %v", err) - } - - hashedPassword, err := hashing.HashPassword() - if err != nil { - return nil, nil, err - } - - testUser.Password = hashedPassword - userStore.CreateUser(ctx, &testUser) - - return &testUser, &unhashedPassword, nil -} diff --git a/internal/middleware/auth.go b/internal/middleware/auth.go deleted file mode 100644 index 137938d..0000000 --- a/internal/middleware/auth.go +++ /dev/null @@ -1,47 +0,0 @@ -package middleware - -import ( - "context" - "net/http" - "strings" - - "git.kundeng.us/phoenix/textsender-auth/internal/services" -) - -type contextKey string - -const ( - UserContextKey contextKey = "user" -) - -func AuthMiddleware(authService *services.JWTService) func(http.Handler) http.Handler { - return func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - authHeader := r.Header.Get("Authorization") - if authHeader == "" { - http.Error(w, "Authorization header required", http.StatusUnauthorized) - return - } - - // Extract token from "Bearer " - parts := strings.Split(authHeader, " ") - if len(parts) != 2 || parts[0] != "Bearer" { - http.Error(w, "Invalid authorization header format", http.StatusUnauthorized) - return - } - - token := parts[1] - - // Validate token with auth service - user, err := authService.ValidateToken(token) - if err != nil { - http.Error(w, "Invalid token", http.StatusUnauthorized) - return - } - - // Add user to context - ctx := context.WithValue(r.Context(), UserContextKey, user) - next.ServeHTTP(w, r.WithContext(ctx)) - }) - } -} diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go deleted file mode 100644 index 86739a5..0000000 --- a/internal/middleware/middleware.go +++ /dev/null @@ -1,22 +0,0 @@ -package middleware - -import ( - "log" - "net/http" - "time" -) - -func Logging(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - start := time.Now() - next.ServeHTTP(w, r) - log.Printf("%s %s %v", r.Method, r.URL.Path, time.Since(start)) - }) -} - -func JSONContentType(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - next.ServeHTTP(w, r) - }) -} diff --git a/internal/services/jwt_service.go b/internal/services/jwt_service.go deleted file mode 100644 index ee38e90..0000000 --- a/internal/services/jwt_service.go +++ /dev/null @@ -1,49 +0,0 @@ -package services - -import ( - "time" - - txtmodels_token "git.kundeng.us/phoenix/textsender-models/tx0/token" - txtmodels_user "git.kundeng.us/phoenix/textsender-models/tx0/user" - "github.com/golang-jwt/jwt/v5" -) - -type JWTService struct { - secretKey []byte -} - -func NewJWTService(secretKey string) *JWTService { - return &JWTService{ - secretKey: []byte(secretKey), - } -} - -func (s *JWTService) ValidateToken(tokenString string) (*txtmodels_user.User, error) { - // TODO: Include more user information in the claims to populate user - claims := &txtmodels_token.Claims{} - - token, err := jwt.ParseWithClaims(tokenString, claims, func(token *jwt.Token) (interface{}, error) { - // Validate the signing method - if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, jwt.ErrSignatureInvalid - } - return s.secretKey, nil - }) - - if err != nil { - return nil, err - } - - if !token.Valid { - return nil, jwt.ErrSignatureInvalid - } - - // Check token expiration - if time.Now().After(claims.ExpiresAt.Time) { - return nil, jwt.ErrTokenExpired - } - - return &txtmodels_user.User{ - Id: claims.UserId, - }, nil -} diff --git a/internal/store/mock/service_store.go b/internal/store/mock/service_store.go deleted file mode 100644 index c19a71b..0000000 --- a/internal/store/mock/service_store.go +++ /dev/null @@ -1,125 +0,0 @@ -package mock - -import ( - "context" - "errors" - "fmt" - "sync" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/user" - "github.com/google/uuid" -) - -type MockServiceUserStore struct { - ServiceUsers map[uuid.UUID]*user.ServiceUser - ServiceUsersByUsername map[string]*user.ServiceUser - mu sync.RWMutex - Error error // Optional: simulate errors -} - -func NewMockServiceUserStore() *MockServiceUserStore { - return &MockServiceUserStore{ - ServiceUsers: make(map[uuid.UUID]*user.ServiceUser), - ServiceUsersByUsername: make(map[string]*user.ServiceUser), - } -} - -func (m *MockServiceUserStore) Create(ctx context.Context, user *user.ServiceUser) error { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return m.Error - } - - if user.Id == uuid.Nil { - user.Id = uuid.New() - } - - if _, exists := m.ServiceUsersByUsername[user.Username]; exists { - return errors.New("service User with username already exists") - } - - m.ServiceUsers[user.Id] = user - m.ServiceUsersByUsername[user.Username] = user - return nil -} - -func (m *MockServiceUserStore) CheckWithUsername(ctx context.Context, username string) (bool, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return false, m.Error - } - - var exists bool - - for _, serviceUser := range m.ServiceUsers { - if serviceUser.Username == username { - exists = true - break - } - } - - if !exists { - return exists, nil - } else { - return exists, nil - } -} - -func (m *MockServiceUserStore) GetWithUsername(ctx context.Context, username string) (*user.ServiceUser, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - serviceUser := m.ServiceUsersByUsername[username] - if serviceUser != nil { - return serviceUser, nil - } else { - return nil, fmt.Errorf("User not found") - } -} - -func (m *MockServiceUserStore) GetWithId(ctx context.Context, id uuid.UUID) (*user.ServiceUser, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - for _, serviceUser := range m.ServiceUsers { - if serviceUser.Id == id { - return serviceUser, nil - } - } - - return nil, nil -} - -func (m *MockServiceUserStore) UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return 0, m.Error - } - - serviceUser, exists := m.ServiceUsers[id] - if !exists { - return 0, errors.New("Service user not found") - } - - serviceUser.LastLogin = &lastLogin - - m.ServiceUsers[id] = serviceUser - m.ServiceUsersByUsername[serviceUser.Username] = serviceUser - - return 1, nil -} diff --git a/internal/store/mock/user_store.go b/internal/store/mock/user_store.go deleted file mode 100644 index 202a497..0000000 --- a/internal/store/mock/user_store.go +++ /dev/null @@ -1,192 +0,0 @@ -package mock - -import ( - "context" - "errors" - "fmt" - "sync" - "time" - - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-models/tx0/user" -) - -type MockUserStore struct { - Users map[uuid.UUID]*user.User - UsersByUsername map[string]*user.User - mu sync.RWMutex - Error error // Optional: simulate errors -} - -func NewMockUserStore() *MockUserStore { - return &MockUserStore{ - Users: make(map[uuid.UUID]*user.User), - UsersByUsername: make(map[string]*user.User), - } -} - -func (m *MockUserStore) CreateUser(ctx context.Context, user *user.User) error { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return m.Error - } - - if user.Id == uuid.Nil { - user.Id = uuid.New() - } - - if _, exists := m.UsersByUsername[user.Username]; exists { - return errors.New("User with username already exists") - } - - user.Created = time.Now() - - m.Users[user.Id] = user - m.UsersByUsername[user.Username] = user - return nil -} - -func (m *MockUserStore) GetUserByID(ctx context.Context, id uuid.UUID) (*user.User, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - if m.Error != nil { - return nil, m.Error - } - - user, exists := m.Users[id] - if !exists { - return nil, errors.New("User not found") - } - - return user, nil -} - -func (m *MockUserStore) GetUserByUsername(ctx context.Context, username string) (*user.User, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - user, exists := m.UsersByUsername[username] - if !exists { - return nil, errors.New("User not found") - } - - return user, nil -} - -func (m *MockUserStore) GetAllUsers(ctx context.Context) ([]*user.User, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return nil, m.Error - } - - users := make([]*user.User, 0, len(m.Users)) - for _, user := range m.Users { - users = append(users, user) - } - - return users, nil -} - -func (m *MockUserStore) UserExists(ctx context.Context, username string) (bool, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return false, m.Error - } - - _, exists := m.UsersByUsername[username] - if !exists { - return exists, nil - } else { - return exists, nil - } -} - -func (m *MockUserStore) UpdatePassword(ctx context.Context, id uuid.UUID, password string) (int64, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return 0, m.Error - } - - user, exists := m.Users[id] - if !exists { - return 0, errors.New("User not found") - } - - user.Password = password - - m.Users[id] = user - m.UsersByUsername[user.Username] = user - - return 1, nil -} - -func (m *MockUserStore) UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return 0, m.Error - } - - user, exists := m.Users[id] - if !exists { - return 0, errors.New("User not found") - } - - user.LastLogin = &lastLogin - - m.Users[id] = user - m.UsersByUsername[user.Username] = user - - return 1, nil -} - -func (m *MockUserStore) UpdateName(ctx context.Context, firstname *string, lastname *string, usr *user.User) (int64, error) { - m.mu.Lock() - defer m.mu.Unlock() - - if m.Error != nil { - return 0, m.Error - } - - if firstname == nil && lastname == nil { - return 0, fmt.Errorf("Names not provided") - } - - user, exists := m.Users[usr.Id] - if !exists { - return 0, errors.New("User not found") - } - - if firstname != nil && lastname != nil { - user.Firstname = firstname - user.Lastname = lastname - } else if firstname != nil { - user.Firstname = firstname - } else { - user.Lastname = lastname - } - - m.Users[usr.Id] = user - m.UsersByUsername[user.Username] = user - - return 1, nil -} diff --git a/internal/store/service.go b/internal/store/service.go deleted file mode 100644 index f61d280..0000000 --- a/internal/store/service.go +++ /dev/null @@ -1,95 +0,0 @@ -package store - -import ( - "context" - "fmt" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/user" - "github.com/google/uuid" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" -) - -// TODO: Rename this to ServiceUserStore -type ServiceStore interface { - CheckWithUsername(ctx context.Context, username string) (bool, error) - GetWithUsername(ctx context.Context, username string) (*user.ServiceUser, error) - GetWithId(ctx context.Context, id uuid.UUID) (*user.ServiceUser, error) - Create(ctx context.Context, serviceUser *user.ServiceUser) error - UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) -} - -type PGServiceStore struct { - db *pgxpool.Pool -} - -func NewServiceStore(db *pgxpool.Pool) *PGServiceStore { - return &PGServiceStore{db: db} -} - -func (s *PGServiceStore) CheckWithUsername(ctx context.Context, username string) (bool, error) { - var exists bool - query := `SELECT EXISTS(SELECT 1 FROM service_users WHERE Username = $1)` - - if err := s.db.QueryRow(ctx, query, username).Scan(&exists); err != nil { - if err == pgx.ErrNoRows { - return exists, nil - } else { - return exists, fmt.Errorf("Error querying row: %v", err) - } - } else { - return exists, nil - } -} - -func (s *PGServiceStore) GetWithUsername(ctx context.Context, username string) (*user.ServiceUser, error) { - var serviceUser user.ServiceUser - query := `SELECT id, username, passphrase, created FROM service_users WHERE username = $1` - - if err := s.db.QueryRow(ctx, query, username).Scan(&serviceUser.Id, &serviceUser.Username, &serviceUser.Passphrase, &serviceUser.Created); err != nil { - if err == pgx.ErrNoRows { - return nil, nil - } else { - return nil, fmt.Errorf("Error querying row: %v", err) - } - } else { - return &serviceUser, nil - } -} - -func (s *PGServiceStore) GetWithId(ctx context.Context, id uuid.UUID) (*user.ServiceUser, error) { - var serviceUser user.ServiceUser - query := `SELECT id, username, passphrase, created FROM service_users WHERE id = $1` - - if err := s.db.QueryRow(ctx, query, id).Scan(&serviceUser.Id, &serviceUser.Username, &serviceUser.Passphrase, &serviceUser.Created); err != nil { - if err == pgx.ErrNoRows { - return nil, nil - } else { - return nil, fmt.Errorf("Error querying row: %v", err) - } - } else { - return &serviceUser, nil - } -} - -func (s *PGServiceStore) Create(ctx context.Context, serviceUser *user.ServiceUser) error { - query := ` - INSERT INTO service_users (username, passphrase) - VALUES ($1, $2) - RETURNING id, created - ` - - return s.db.QueryRow(ctx, query, serviceUser.Username, serviceUser.Passphrase).Scan( - &serviceUser.Id, &serviceUser.Created, - ) -} - -func (s *PGServiceStore) UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) { - query := `UPDATE service_users SET last_login = $1 WHERE id = $2` - if affected, err := s.db.Exec(ctx, query, lastLogin, id); err != nil { - return 0, err - } else { - return affected.RowsAffected(), nil - } -} diff --git a/internal/store/user.go b/internal/store/user.go deleted file mode 100644 index baa0444..0000000 --- a/internal/store/user.go +++ /dev/null @@ -1,173 +0,0 @@ -package store - -import ( - "context" - "fmt" - "time" - - "github.com/google/uuid" - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgxpool" - - "git.kundeng.us/phoenix/textsender-models/tx0/user" -) - -type UserStore interface { - CreateUser(ctx context.Context, user *user.User) error - GetUserByID(ctx context.Context, id uuid.UUID) (*user.User, error) - GetUserByUsername(ctx context.Context, username string) (*user.User, error) - GetAllUsers(ctx context.Context) ([]*user.User, error) - UserExists(ctx context.Context, username string) (bool, error) - UpdatePassword(ctx context.Context, id uuid.UUID, password string) (int64, error) - UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) - UpdateName(ctx context.Context, firstname *string, lastname *string, usr *user.User) (int64, error) -} - -type PGUserStore struct { - db *pgxpool.Pool -} - -func NewUserStore(db *pgxpool.Pool) *PGUserStore { - return &PGUserStore{db: db} -} - -func (s *PGUserStore) CreateUser(ctx context.Context, user *user.User) error { - query := ` - INSERT INTO users (phone_number, username, password) - VALUES ($1, $2, $3) - RETURNING id, phone_number, username, created - ` - - return s.db.QueryRow(ctx, query, user.PhoneNumber, user.Username, user.Password).Scan( - &user.Id, &user.PhoneNumber, &user.Username, &user.Created, - ) -} - -func (s *PGUserStore) GetUserByID(ctx context.Context, id uuid.UUID) (*user.User, error) { - query := `SELECT id, username, password, phone_number, created FROM users WHERE id = $1` - - var user user.User - err := s.db.QueryRow(ctx, query, id).Scan( - &user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Created, - ) - - if err == pgx.ErrNoRows { - return nil, nil - } - if err != nil { - return nil, fmt.Errorf("getting user by ID: %w", err) - } - - return &user, nil -} - -func (s *PGUserStore) GetUserByUsername(ctx context.Context, username string) (*user.User, error) { - query := `SELECT id, username, password, phone_number, created FROM users WHERE username = $1` - - var user user.User - err := s.db.QueryRow(ctx, query, username).Scan( - &user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Created, - ) - - if err == pgx.ErrNoRows { - return nil, nil - } - if err != nil { - return nil, fmt.Errorf("getting user by ID: %w", err) - } - - return &user, nil -} - -func (s *PGUserStore) GetAllUsers(ctx context.Context) ([]*user.User, error) { - query := `SELECT id, username, password, phone_number, created FROM users` - - rows, err := s.db.Query(ctx, query) - if err != nil { - return nil, fmt.Errorf("querying all users: %w", err) - } - defer rows.Close() - - var users []*user.User - for rows.Next() { - var user user.User - if err := rows.Scan( - &user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Created, - ); err != nil { - return nil, fmt.Errorf("scanning user row: %w", err) - } - users = append(users, &user) - } - - if err := rows.Err(); err != nil { - return nil, fmt.Errorf("iterating user rows: %w", err) - } - - return users, nil -} - -func (s *PGUserStore) UserExists(ctx context.Context, username string) (bool, error) { - query := `SELECT EXISTS(SELECT 1 FROM users WHERE username = $1)` - - var exists bool - err := s.db.QueryRow(ctx, query, username).Scan(&exists) - if err != nil { - return false, fmt.Errorf("checking if user exists: %w", err) - } - - return exists, nil -} - -func (s *PGUserStore) UpdatePassword(ctx context.Context, id uuid.UUID, password string) (int64, error) { - query := `UPDATE users SET password = $1 WHERE id = $2` - if affected, err := s.db.Exec(ctx, query, password, id); err != nil { - return 0, err - } else { - return affected.RowsAffected(), nil - } -} - -func (s *PGUserStore) UpdateLastLogin(ctx context.Context, id uuid.UUID, lastLogin time.Time) (int64, error) { - query := `UPDATE users SET last_login = $1 WHERE id = $2` - if affected, err := s.db.Exec(ctx, query, lastLogin, id); err != nil { - return 0, err - } else { - return affected.RowsAffected(), nil - } -} - -func (s *PGUserStore) UpdateName(ctx context.Context, firstname *string, lastname *string, usr *user.User) (int64, error) { - var query string - - if firstname == nil && lastname == nil { - return 0, fmt.Errorf("Provided names are empty") - } else { - tableName := "users" - if firstname != nil && lastname != nil { - query = fmt.Sprintf("UPDATE %s SET first_name = $1, last_name = $2 WHERE id = $3", tableName) - if affected, err := s.db.Exec(ctx, query, firstname, lastname, usr.Id); err != nil { - return 0, err - } else { - usr.Firstname = firstname - usr.Lastname = lastname - return affected.RowsAffected(), nil - } - } else if firstname != nil { - query = fmt.Sprintf("UPDATE %s SET first_name = $1 WHERE id = $2", tableName) - if affected, err := s.db.Exec(ctx, query, firstname, usr.Id); err != nil { - return 0, err - } else { - usr.Firstname = firstname - return affected.RowsAffected(), nil - } - } else { - query = fmt.Sprintf("UPDATE %s SET last_name = $1 WHERE id = $2", tableName) - if affected, err := s.db.Exec(ctx, query, lastname, usr.Id); err != nil { - return 0, err - } else { - usr.Lastname = lastname - return affected.RowsAffected(), nil - } - } - } -} diff --git a/internal/utility/hashing.go b/internal/utility/hashing.go deleted file mode 100644 index 1b4b64b..0000000 --- a/internal/utility/hashing.go +++ /dev/null @@ -1,62 +0,0 @@ -package utility - -import ( - "fmt" - "strings" - "unicode" - - "golang.org/x/crypto/bcrypt" -) - -type HashMash struct { - password string -} - -func (h *HashMash) HashPassword() (string, error) { - bytes, err := bcrypt.GenerateFromPassword([]byte(h.password), bcrypt.DefaultCost) - return string(bytes), err -} - -func (h *HashMash) CheckPasswordHash(password string, hash string) bool { - err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) - return err == nil -} - -func (h *HashMash) SetPassword(password string) error { - if len(password) < 8 { - return fmt.Errorf("Password length is not enought") - } else if len(password) > 32 { - return fmt.Errorf("Password length is too long") - } else { - specialCharacters := "!@#$%^&*?" - numbers := "0123456789" - if strings.ContainsAny(password, specialCharacters) && strings.ContainsAny(password, numbers) { - var hasAtleastOneUpper, hasAtleastOneLower bool - - for _, c := range password { - if unicode.IsUpper(c) { - hasAtleastOneUpper = true - } else if unicode.IsLower(c) { - hasAtleastOneLower = true - } - - if hasAtleastOneLower && hasAtleastOneUpper { - break - } - } - - if hasAtleastOneUpper && hasAtleastOneLower { - h.password = password - return nil - } else { - if !hasAtleastOneUpper { - return fmt.Errorf("Password requires at least one upper case letter") - } else { - return fmt.Errorf("Password requires at least one lower case letter") - } - } - } else { - return fmt.Errorf("Password should contain special characters and numbers") - } - } -} diff --git a/internal/utility/token.go b/internal/utility/token.go deleted file mode 100644 index d20c974..0000000 --- a/internal/utility/token.go +++ /dev/null @@ -1,130 +0,0 @@ -package utility - -import ( - "fmt" - "time" - - "git.kundeng.us/phoenix/textsender-models/tx0/token" - "git.kundeng.us/phoenix/textsender-models/tx0/user" - "github.com/golang-jwt/jwt/v5" - "github.com/google/uuid" - - "git.kundeng.us/phoenix/textsender-auth/internal/config" -) - -const ROLE_TYPE = "regular" -const TOKEN_TYPE = "Bearer" - -type TokenGenerator struct { - SecretKey []byte - hourOffset time.Duration -} - -func (t *TokenGenerator) SetSecretKey(secretKey string) { - t.SecretKey = []byte(secretKey) -} - -func (t *TokenGenerator) SetHourOffset(offset time.Duration) error { - if offset < 48 { - t.hourOffset = offset - return nil - } else { - return fmt.Errorf("No change") - } -} - -func (t *TokenGenerator) GenerateToken(usr any) (*token.Login, error) { - issuedAt := time.Now() - if t.hourOffset == 0 { - t.hourOffset = 4 - } - expirationTime := time.Now().Add(t.hourOffset * time.Hour) - - if claims, err := t.generateClaims(usr, TOKEN_TYPE, issuedAt, expirationTime); err != nil { - return nil, fmt.Errorf("Error generating claims: %v", err) - } else { - myToken := jwt.NewWithClaims(jwt.SigningMethodHS256, *claims) - if tokenString, err := myToken.SignedString(t.SecretKey); err != nil { - return nil, err - } else { - return &token.Login{UserId: claims.UserId, AccessToken: tokenString, TokenType: TOKEN_TYPE, ExpiresIn: expirationTime.Unix(), IssuedAt: issuedAt.Unix()}, nil - } - } -} - -func (t *TokenGenerator) VerifyToken(accessToken string) (bool, error) { - clms := &token.Claims{} - - if tken, err := t.parseTokenWithClaims(accessToken, clms); err != nil { - return false, nil - } else { - if tken.Valid { - if clms.UserId != uuid.Nil { - return true, nil - } else { - return false, fmt.Errorf("User Id was not set") - } - } else { - return false, fmt.Errorf("Invalid access token") - } - } -} - -func (t *TokenGenerator) ExtractIdFromToken(accessToken string) (uuid.UUID, error) { - clms := &token.Claims{} - - if tken, err := t.parseTokenWithClaims(accessToken, clms); err != nil { - return uuid.Nil, nil - } else { - if tken.Valid { - if clms.UserId != uuid.Nil { - return clms.UserId, nil - } else { - return uuid.Nil, fmt.Errorf("User Id was not set") - } - } else { - return uuid.Nil, fmt.Errorf("Invalid access token") - } - } -} - -func (t *TokenGenerator) parseTokenWithClaims(accessToken string, claims *token.Claims) (*jwt.Token, error) { - tken, err := jwt.ParseWithClaims(accessToken, claims, func(tken *jwt.Token) (any, error) { - if _, ok := tken.Method.(*jwt.SigningMethodHMAC); !ok { - return nil, fmt.Errorf("unexpected signing method: %v", tken.Header["alg"]) - } - return t.SecretKey, nil - }, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()})) - - if err != nil { - return nil, err - } else { - return tken, nil - } -} - -func (t *TokenGenerator) generateClaims(usr any, role string, issuedAt time.Time, expiredAt time.Time) (*token.Claims, error) { - var id uuid.UUID - switch val := usr.(type) { - case user.User: - id = val.Id - case *user.User: - id = val.Id - case user.ServiceUser: - id = val.Id - case *user.ServiceUser: - id = val.Id - default: - return nil, fmt.Errorf("Invalid type") - } - - return &token.Claims{ - UserId: id, - Role: role, - RegisteredClaims: jwt.RegisteredClaims{ - Issuer: config.App_Name, - ExpiresAt: jwt.NewNumericDate(expiredAt), - IssuedAt: jwt.NewNumericDate(issuedAt), - }, - }, nil -} diff --git a/internal/version/version.go b/internal/version/version.go deleted file mode 100644 index 91e3782..0000000 --- a/internal/version/version.go +++ /dev/null @@ -1,17 +0,0 @@ -package version - -import "fmt" - -var ( - Version = "dev" - BuildTime = "unknown" - Commit = "unknown" - GoVersion = "unknown" -) - -func String() string { - return fmt.Sprintf( - "Version: %s\nBuild Date: %s\nCommit: %s\nGo Version: %s", - Version, BuildTime, Commit, GoVersion, - ) -} From e2f3dbbb32e06846b10f0c6e5b5d9cc33be6d777 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 30 May 2026 23:03:28 -0400 Subject: [PATCH 02/29] Removing workflow file --- .gitea/workflows/workflow.yaml | 134 --------------------------------- 1 file changed, 134 deletions(-) delete mode 100644 .gitea/workflows/workflow.yaml diff --git a/.gitea/workflows/workflow.yaml b/.gitea/workflows/workflow.yaml deleted file mode 100644 index 5cb7111..0000000 --- a/.gitea/workflows/workflow.yaml +++ /dev/null @@ -1,134 +0,0 @@ -name: Go - -on: - push: - branches: [ main ] - pull_request: - branches: [ main ] - -jobs: - build: - runs-on: ubuntu-24.04 # You can change this to macos-latest or windows-latest if needed - - steps: - - uses: actions/checkout@v5 - - - name: Set up Go - uses: actions/setup-go@v6 - with: - go-version: '1.26.2' - - - name: Build - run: | - echo "Initializing config" - mkdir -p ~/.ssh - echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender_models_deploy_key - chmod 600 ~/.ssh/textsender_models_deploy_key - ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts - - eval $(ssh-agent -s) - ssh-add -v ~/.ssh/textsender_models_deploy_key - - go env -w GOPRIVATE='${{ secrets.GIT_HOST_ROOT }}' - - echo "Creating local .gitconfig" - touch ~/.gitconfig - cat > ~/.gitconfig << "EOF" - [url "ssh://git@${{ secrets.GIT_HOST_ROOT }}"] - insteadOf = https://${{ secrets.GIT_HOST_ROOT }} - [url "ssh://git@${{ secrets.GIT_HOST_ROOT }}"] - insteadOf = http://${{ secrets.GIT_HOST_ROOT }} - EOF - - echo "Building binary" - make build - - echo "Binary built" - file textsender-auth - - test: - name: Test - runs-on: ubuntu-24.04 - services: - postgres: - image: postgres:18.4-alpine - env: - POSTGRES_USER: ${{ secrets.DB_TEST_USER }} - POSTGRES_PASSWORD: ${{ secrets.DB_TEST_PASSWORD }} - POSTGRES_DB: ${{ secrets.DB_TEST_NAME }} - ports: - - 5432:5432 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - steps: - - name: Checkout code - uses: actions/checkout@v5 - - - name: Setup Go - uses: actions/setup-go@v6 - with: - go-version: '1.26.2' - - - name: Install PostgreSQL client - run: sudo apt update && sudo apt-get install -y postgresql-client - - - name: Wait for PostgreSQL to be ready - run: | - for i in {1..30}; do - if pg_isready -h postgres -p 5432; then - echo "PostgreSQL is ready" - exit 0 - fi - echo "Waiting for PostgreSQL... Attempt $i" - sleep 2 - done - echo "PostgreSQL did not start in time" - exit 1 - - - name: Run tests - env: - DB_NAME: ${{ secrets.DB_TEST_NAME }} - DB_USER: ${{ secrets.DB_TEST_USER }} - DB_PASSWORD: ${{ secrets.DB_TEST_PASSWORD }} - DB_HOST: postgres - DB_PORT: 5432 - DB_SSLMODE: disable - SECRET_KEY: ${{ secrets.SECRET_KEY }} - run: | - echo "Parent directory" - echo `pwd` - - echo "SECRET_KEY=$SECRET_KEY" > .env - echo "DB_NAME=$DB_NAME" >> .env - echo "DB_USER=$DB_USER" >> .env - echo "DB_PASSWORD=$DB_PASSWORD" >> .env - echo "DB_HOST=$DB_HOST" >> .env - echo "DB_PORT=$DB_PORT" >> .env - echo "DB_SSLMODE=$DB_SSLMODE" >> .env - echo "ENABLE_REGISTRATION=true" >> .env - echo "ALLOWED_ORIGINS=http://localhost:9080" >> .env - - echo "Initializing config" - mkdir -p ~/.ssh - echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender_models_deploy_key - chmod 600 ~/.ssh/textsender_models_deploy_key - ssh-keyscan ${{ secrets.MY_HOST }} >> ~/.ssh/known_hosts - - eval $(ssh-agent -s) - ssh-add -v ~/.ssh/textsender_models_deploy_key - - go env -w GOPRIVATE='${{ secrets.GIT_HOST_ROOT }}' - - echo "Creating local .gitconfig" - touch ~/.gitconfig - cat > ~/.gitconfig << "EOF" - [url "ssh://git@${{ secrets.GIT_HOST_ROOT }}"] - insteadOf = https://${{ secrets.GIT_HOST_ROOT }} - [url "ssh://git@${{ secrets.GIT_HOST_ROOT }}"] - insteadOf = http://${{ secrets.GIT_HOST_ROOT }} - EOF - - go test -v ./... From bceadfe7a68b504948fa93d7801b3e3966cd4c61 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 30 May 2026 23:04:00 -0400 Subject: [PATCH 03/29] Blank readme --- README.md | 29 ----------------------------- 1 file changed, 29 deletions(-) diff --git a/README.md b/README.md index 1917a61..8b13789 100644 --- a/README.md +++ b/README.md @@ -1,30 +1 @@ -# textsender-auth -A service that handles the authorization aspect of the textsender project. - -## Getting started -Assumes that the postgresql database has already been created with privileges to -create and drop databases. Copy the `.env.sample` file to `.env`. Within the `.env` -file, update the database keys. The `SECRET_KEY` is used for token generation. - - -### Building api -``` -make build -``` - - -### Resetting the database -``` -./textsender-auth -reset-db -``` - - -Generate API documentation -``` -go install github.com/swaggo/swag/cmd/swag@latest - -swag init --generalInfo main.go --dir ./cmd/api,./internal/handler --output docs/ --parseDependency --parseInternal -``` - -The API documentation can be viewed from `http://localhost:9080/swagger/index.html`. From 8d358356b975ecb40e82faabdab0353e4dc57fd6 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 30 May 2026 23:11:04 -0400 Subject: [PATCH 04/29] Adding rust files --- Cargo.lock | 2819 +++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 30 + src/main.rs | 5 + 3 files changed, 2854 insertions(+) create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/main.rs diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..d00c127 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,2819 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d036a3c4ab069c7b410a2ce876bd74808d2d0888a82667669f8e783a898bf1" +dependencies = [ + "derive_arbitrary", +] + +[[package]] +name = "argon2" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3610892ee6e0cbce8ae2700349fcf8f98adb0dbfbee85aec3c9179d29cc072" +dependencies = [ + "base64ct", + "blake2", + "cpufeatures 0.2.17", + "password-hash", +] + +[[package]] +name = "atoi" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528" +dependencies = [ + "num-traits", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "axum" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90" +dependencies = [ + "axum-core", + "bytes", + "form_urlencoded", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "serde_core", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" + +[[package]] +name = "cc" +version = "1.2.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e016178bb5662a08681bbe0f00f8e17631781a4dfc8c45e466e4b185ec27f" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chacha20" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f8d983286843e49675a4b7a2d174efe136dc93a18d69130dd18198a6c167601" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "rand_core 0.10.1", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b2a41393f66f16b0823bb79094d54ac5fbd34ab292ddafb9a0456ac9f87d201" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derive_arbitrary" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e567bd82dcff979e4b03460c307b3cdc9e96fde3d73bed1496d2bc75d9dd62a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "dotenvy" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" + +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" +dependencies = [ + "serde", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "etcetera" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943" +dependencies = [ + "cfg-if", + "home", + "windows-sys 0.48.0", +] + +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", + "zlib-rs", +] + +[[package]] +name = "flume" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" +dependencies = [ + "futures-core", + "futures-sink", + "spin", +] + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-intrusive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f" +dependencies = [ + "futures-core", + "lock_api", + "parking_lot", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "rand_core 0.10.1", + "wasip2", + "wasip3", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hashlink" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" +dependencies = [ + "hashbrown 0.15.5", +] + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "byteorder", + "num-traits", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + +[[package]] +name = "home" +version = "0.5.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "http" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be7462df143984c4598a256ef469b251d7d7f9e271135073e78fc535414f3d0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55281c53a1894c864990125767da440a4e630446785086f52523b20033b74498" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "bytes", + "http", + "http-body", + "hyper", + "pin-project-lite", + "tokio", + "tower-service", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "josekit" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a808e078330e6af222eb0044b71d4b1ff981bfef43e7bc8133a88234e0c86a0c" +dependencies = [ + "anyhow", + "base64", + "flate2", + "openssl", + "regex", + "serde", + "serde_json", + "thiserror", + "time", +] + +[[package]] +name = "js-sys" +version = "0.3.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bc4740e452c1e57ade0cbc129f139c9093e354346f0872ef985f4f5cf5f11" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.8.0", +] + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "pkg-config", + "vcpkg", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" + +[[package]] +name = "matchit" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" + +[[package]] +name = "md-5" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf" +dependencies = [ + "cfg-if", + "digest", +] + +[[package]] +name = "memchr" +version = "2.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" +dependencies = [ + "mime", + "unicase", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02bd0af71c67b473010cbbc60715ee815645a4dc942899111f494b4b737d6fda" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "native-tls" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-bigint-dig" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" +dependencies = [ + "lazy_static", + "libm", + "num-integer", + "num-iter", + "num-traits", + "rand 0.8.6", + "smallvec", + "zeroize", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "openssl" +version = "0.10.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a45fa2aa886c42762255da344f0a0d313e254066c46aad76f300c3d3da62d967" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f28a22dc7140cda5f096e5e7724a6962ca81a7f8bfd2979f9b18c11af56318c4" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall 0.5.18", + "smallvec", + "windows-link", +] + +[[package]] +name = "password-hash" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166" +dependencies = [ + "base64ct", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs1" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +dependencies = [ + "der", + "pkcs8", + "spki", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" +dependencies = [ + "chacha20", + "getrandom 0.4.2", + "rand_core 0.10.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c7591fa2c6b601dfcfe5f043f65a1c39fcdf50efefcd7f1572e538c1f4b398d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "rsa" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" +dependencies = [ + "const-oid", + "digest", + "num-bigint-dig", + "num-integer", + "num-traits", + "pkcs1", + "pkcs8", + "rand_core 0.6.4", + "signature", + "spki", + "subtle", + "zeroize", +] + +[[package]] +name = "rust-embed" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04113cb9355a377d83f06ef1f0a45b8ab8cd7d8b1288160717d66df5c7988d27" +dependencies = [ + "rust-embed-impl", + "rust-embed-utils", + "walkdir", +] + +[[package]] +name = "rust-embed-impl" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da0902e4c7c8e997159ab384e6d0fc91c221375f6894346ae107f47dd0f3ccaa" +dependencies = [ + "proc-macro2", + "quote", + "rust-embed-utils", + "syn", + "walkdir", +] + +[[package]] +name = "rust-embed-utils" +version = "8.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bcdef0be6fe7f6fa333b1073c949729274b05f123a0ad7efcb8efd878e5c3b1" +dependencies = [ + "sha2", + "walkdir", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "indexmap", + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha1" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest", + "rand_core 0.6.4", +] + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "socket2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52d1cfed4120b4d927bf7c0f86d2087a4a7d6027c906d9f9d525a80573b9be51" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "sqlx" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc" +dependencies = [ + "sqlx-core", + "sqlx-macros", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", +] + +[[package]] +name = "sqlx-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6" +dependencies = [ + "base64", + "bytes", + "crc", + "crossbeam-queue", + "either", + "event-listener", + "futures-core", + "futures-intrusive", + "futures-io", + "futures-util", + "hashbrown 0.15.5", + "hashlink", + "indexmap", + "log", + "memchr", + "native-tls", + "once_cell", + "percent-encoding", + "serde", + "serde_json", + "sha2", + "smallvec", + "thiserror", + "time", + "tokio", + "tokio-stream", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "sqlx-macros" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d" +dependencies = [ + "proc-macro2", + "quote", + "sqlx-core", + "sqlx-macros-core", + "syn", +] + +[[package]] +name = "sqlx-macros-core" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b" +dependencies = [ + "dotenvy", + "either", + "heck", + "hex", + "once_cell", + "proc-macro2", + "quote", + "serde", + "serde_json", + "sha2", + "sqlx-core", + "sqlx-mysql", + "sqlx-postgres", + "sqlx-sqlite", + "syn", + "tokio", + "url", +] + +[[package]] +name = "sqlx-mysql" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "bytes", + "crc", + "digest", + "dotenvy", + "either", + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "generic-array", + "hex", + "hkdf", + "hmac", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "percent-encoding", + "rand 0.8.6", + "rsa", + "serde", + "sha1", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-postgres" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46" +dependencies = [ + "atoi", + "base64", + "bitflags", + "byteorder", + "crc", + "dotenvy", + "etcetera", + "futures-channel", + "futures-core", + "futures-util", + "hex", + "hkdf", + "hmac", + "home", + "itoa", + "log", + "md-5", + "memchr", + "once_cell", + "rand 0.8.6", + "serde", + "serde_json", + "sha2", + "smallvec", + "sqlx-core", + "stringprep", + "thiserror", + "time", + "tracing", + "uuid", + "whoami", +] + +[[package]] +name = "sqlx-sqlite" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea" +dependencies = [ + "atoi", + "flume", + "futures-channel", + "futures-core", + "futures-executor", + "futures-intrusive", + "futures-util", + "libsqlite3-sys", + "log", + "percent-encoding", + "serde", + "serde_urlencoded", + "sqlx-core", + "thiserror", + "time", + "tracing", + "url", + "uuid", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "stringprep" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1" +dependencies = [ + "unicode-bidi", + "unicode-normalization", + "unicode-properties", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[package]] +name = "textsender_auth" +version = "0.1.14" +dependencies = [ + "argon2", + "axum", + "http-body-util", + "hyper", + "josekit", + "once_cell", + "rand 0.10.1", + "serde", + "serde_json", + "sqlx", + "time", + "tokio", + "tower", + "tower-http", + "tracing-subscriber", + "url", + "utoipa", + "utoipa-swagger-ui", + "uuid", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "hdrhistogram", + "indexmap", + "pin-project-lite", + "slab", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags", + "bytes", + "http", + "pin-project-lite", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "nu-ansi-term", + "sharded-slab", + "smallvec", + "thread_local", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicase" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" + +[[package]] +name = "unicode-bidi" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-properties" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utoipa" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bde15df68e80b16c7d16b9616e80770ad158988daa56a27dccd1e55558b0160" +dependencies = [ + "indexmap", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "5.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba0b99ee52df3028635d93840c797102da61f8a7bb3cf751032455895b52ef8" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn", +] + +[[package]] +name = "utoipa-swagger-ui" +version = "9.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d047458f1b5b65237c2f6dc6db136945667f40a7668627b3490b9513a3d43a55" +dependencies = [ + "axum", + "base64", + "mime_guess", + "regex", + "rust-embed", + "serde", + "serde_json", + "url", + "utoipa", + "zip", +] + +[[package]] +name = "uuid" +version = "1.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d258b83ceec21034727ecee8c382cfa6c3e133699b0742c64571814fb420c9f7" +dependencies = [ + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasite" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed04576f974d2b2fba0f38c51dbc5518011e38c36bf1143164be765528fd409" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916151b09da36bd82f6615cbf3a419e2f0ba23a03c6160e8e92eb6bd4aa1dec6" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "299047362ccbfce148b67ab7e73349f77748e00c8296f9542adfad2ad82c5c5e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.122" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a929b2c61f11ba3e9bc35b50c1f25cb38e0e892c0c231ae2b8cf78d5dad4437" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap", + "semver", +] + +[[package]] +name = "whoami" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d4a4db5077702ca3015d3d02d74974948aba2ad9e12ab7df718ee64ccd7e97d" +dependencies = [ + "libredox", + "wasite", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap", + "prettyplease", + "syn", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b065d4f0e55f82fae73202e189638116a87c55ab6b8e6c2721e13dd9d854ad1" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b631b19d36a892ab55420c92dbc83ccd79274f25be714855d3074aa71cab639" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "zip" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12598812502ed0105f607f941c386f43d441e00148fce9dec3ca5ffb0bde9308" +dependencies = [ + "arbitrary", + "crc32fast", + "flate2", + "indexmap", + "memchr", + "zopfli", +] + +[[package]] +name = "zlib-rs" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513" + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zopfli" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05cd8797d63865425ff89b5c4a48804f35ba0ce8d125800027ad6017d2b5249" +dependencies = [ + "bumpalo", + "crc32fast", + "log", + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..856bd8e --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "textsender_auth" +version = "0.1.14" +edition = "2024" +rust-version = "1.95" + +[dependencies] +axum = { version = "0.8.9" } +serde = { version = "1.0.228", features = ["derive"] } +serde_json = { version = "1.0.149" } +tokio = { version = "1.52.2", features = ["rt-multi-thread"] } +tracing-subscriber = { version = "0.3.23" } +tower = { version = "0.5.3", features = ["full"] } +tower-http = { version = "0.6.10", features = ["cors"] } +hyper = { version = "1.9.0" } +sqlx = { version = "0.8.6", features = ["postgres", "runtime-tokio-native-tls", "time", "uuid"] } +uuid = { version = "1.23.1", features = ["v4", "serde"] } +argon2 = { version = "0.5.3", features = ["std"] } # Use the latest 0.5.x version +rand = { version = "0.10.1" } +time = { version = "0.3.47", features = ["macros", "serde"] } +josekit = { version = "0.10.3" } +utoipa = { version = "5.5.0", features = ["axum_extras"] } +utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } +# icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.11.1" } +# icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.7.0" } + +[dev-dependencies] +http-body-util = { version = "0.1.3" } +url = { version = "2.5.8" } +once_cell = { version = "1.21.4" } # Useful for lazy initialization in tests/app setup diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..0a99738 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,5 @@ + + +pub fn main() { + println!("textsender_auth"); +} From 9d70311648919a35c9c6ef73bb07646896873926 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 30 May 2026 23:11:40 -0400 Subject: [PATCH 05/29] Updating gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 22e71ed..9ae854f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ .env.local .env.docker -/vendor +/target From 601193738b4d4b4e3caf2811d52ab1444191c9ff Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 30 May 2026 23:15:41 -0400 Subject: [PATCH 06/29] Adding textsender_models --- Cargo.lock | 13 +++++++++++++ Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index d00c127..ad4cc38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1973,6 +1973,7 @@ dependencies = [ "serde", "serde_json", "sqlx", + "textsender_models", "time", "tokio", "tower", @@ -1984,6 +1985,18 @@ dependencies = [ "uuid", ] +[[package]] +name = "textsender_models" +version = "0.3.0" +source = "git+ssh://git@git.kundeng.us/phoenix/textsender-models.git?tag=v0.3.0-v0.3.0-migrate-638da70649-111#638da70649b49735fa8244c95e25f9f8a28f1eac" +dependencies = [ + "rand 0.10.1", + "serde", + "serde_json", + "time", + "uuid", +] + [[package]] name = "thiserror" version = "2.0.18" diff --git a/Cargo.toml b/Cargo.toml index 856bd8e..1a8c89d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ time = { version = "0.3.47", features = ["macros", "serde"] } josekit = { version = "0.10.3" } utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } -# icarus_models = { git = "ssh://git@git.kundeng.us/phoenix/icarus_models.git", tag = "v0.11.1" } +textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.3.0-v0.3.0-migrate-638da70649-111" } # icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.7.0" } [dev-dependencies] From f6c43c283a4aa0d4b578726b7876ead884a74f4e Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 30 May 2026 23:18:56 -0400 Subject: [PATCH 07/29] Adding workflow file --- .gitea/workflows/workflow.yml | 145 ++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 .gitea/workflows/workflow.yml diff --git a/.gitea/workflows/workflow.yml b/.gitea/workflows/workflow.yml new file mode 100644 index 0000000..a18b278 --- /dev/null +++ b/.gitea/workflows/workflow.yml @@ -0,0 +1,145 @@ +name: Rust Build + +on: + push: + branches: + - v0.2.0 + - v0.2.0 + pull_request: + branches: + - v0.2.0 + - v0.2.0 + +jobs: + check: + name: Check + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: 1.95 + - run: | + mkdir -p ~/.ssh + echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key + chmod 600 ~/.ssh/textsender-models_deploy_key + ssh-keyscan ${{ secret.MYHOST }} >> ~/.ssh/known_hosts + + eval $(ssh-agent -s) + ssh-add -v ~/.ssh/textsender-models_deploy_key + + cargo check + + test: + name: Test Suite + runs-on: ubuntu-24.04 + # --- Add database service definition --- + services: + postgres: + image: postgres:18.3-alpine + env: + # Use secrets for DB init, with fallbacks for flexibility + POSTGRES_USER: ${{ secrets.DB_TEST_USER || 'testuser' }} + POSTGRES_PASSWORD: ${{ secrets.DB_TEST_PASSWORD || 'testpassword' }} + POSTGRES_DB: ${{ secrets.DB_TEST_NAME || 'testdb' }} + # Options to wait until the database is ready + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v5 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: 1.95 + # --- Add this step for explicit verification --- + - name: Verify Docker Environment + run: | + echo "Runner User Info:" + id + echo "Checking Docker Version:" + docker --version + echo "Checking Docker Daemon Status (info):" + docker info + echo "Checking Docker Daemon Status (ps):" + docker ps -a + echo "Docker environment check complete." + # NOTE: Do NOT use continue-on-error here. + # If Docker isn't working as expected, the job SHOULD fail here. + - name: Run tests + env: + # Define DATABASE_URL for tests to use + DATABASE_URL: postgresql://${{ secrets.DB_TEST_USER || 'testuser' }}:${{ secrets.DB_TEST_PASSWORD || 'testpassword' }}@postgres:5432/${{ secrets.DB_TEST_NAME || 'testdb' }} + RUST_LOG: info # Optional: configure test log level + SECRET_KEY: ${{ secrets.TOKEN_SECRET_KEY }} + # Make SSH agent available if tests fetch private dependencies + SSH_AUTH_SOCK: ${{ env.SSH_AUTH_SOCK }} + ENABLE_REGISTRATION: 'TRUE' + run: | + mkdir -p ~/.ssh + echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key + chmod 600 ~/.ssh/textsender-models_deploy_key + ssh-keyscan ${{ secret.MYHOST }} >> ~/.ssh/known_hosts + + eval $(ssh-agent -s) + ssh-add -v ~/.ssh/textsender-models_deploy_key + + cargo test + + fmt: + name: Rustfmt + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: 1.95 + - run: rustup component add rustfmt + - run: | + mkdir -p ~/.ssh + echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key + chmod 600 ~/.ssh/textsender-models_deploy_key + ssh-keyscan ${{ secret.MYHOST }} >> ~/.ssh/known_hosts + + eval $(ssh-agent -s) + ssh-add -v ~/.ssh/textsender-models_deploy_key + cargo fmt --all -- --check + + clippy: + name: Clippy + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: 1.95 + - run: rustup component add clippy + - run: | + mkdir -p ~/.ssh + echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key + chmod 600 ~/.ssh/textsender-models_deploy_key + ssh-keyscan ${{ secret.MYHOST }} >> ~/.ssh/known_hosts + + eval $(ssh-agent -s) + ssh-add -v ~/.ssh/textsender-models_deploy_key + cargo clippy -- -D warnings + + build: + name: build + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v5 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: 1.95 + - run: | + mkdir -p ~/.ssh + echo "${{ secrets.MYREPO_TOKEN }}" > ~/.ssh/textsender-models_deploy_key + chmod 600 ~/.ssh/textsender-models_deploy_key + ssh-keyscan ${{ secret.MYHOST }} >> ~/.ssh/known_hosts + + eval $(ssh-agent -s) + ssh-add -v ~/.ssh/textsender-models_deploy_key + cargo build --release From 1a6be5f541d64e80ae0db0abdfc3dddfd605d6e9 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 30 May 2026 23:29:21 -0400 Subject: [PATCH 08/29] Updating main rs --- src/main.rs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 0a99738..96a37e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,16 @@ -pub fn main() { - println!("textsender_auth"); +#[tokio::main] +async fn main() { + // initialize tracing + tracing_subscriber::fmt::init(); + + /* + let app = init::app().await; + + // run our app with hyper, listening globally on port 8001 + let url = config::get_full(); + let listener = tokio::net::TcpListener::bind(url).await.unwrap(); + axum::serve(listener, app).await.unwrap(); + */ } From 37e411f58f197cff12a682bceed1ae93cbffe8ec Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 30 May 2026 23:30:28 -0400 Subject: [PATCH 09/29] Adding dependencies --- Cargo.lock | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 3 + 2 files changed, 275 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index ad4cc38..0401ed8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -50,6 +50,119 @@ dependencies = [ "password-hash", ] +[[package]] +name = "async-channel" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81953c529336010edd6d8e358f886d9581267795c61b19475b71314bffa46d35" +dependencies = [ + "concurrent-queue", + "event-listener 2.5.3", + "futures-core", +] + +[[package]] +name = "async-channel" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" +dependencies = [ + "concurrent-queue", + "event-listener-strategy", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-executor" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a" +dependencies = [ + "async-task", + "concurrent-queue", + "fastrand", + "futures-lite", + "pin-project-lite", + "slab", +] + +[[package]] +name = "async-global-executor" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b1b633a2115cd122d73b955eadd9916c18c8f510ec9cd1686404c60ad1c29c" +dependencies = [ + "async-channel 2.5.0", + "async-executor", + "async-io", + "async-lock", + "blocking", + "futures-lite", + "once_cell", +] + +[[package]] +name = "async-io" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" +dependencies = [ + "autocfg", + "cfg-if", + "concurrent-queue", + "futures-io", + "futures-lite", + "parking", + "polling", + "rustix", + "slab", + "windows-sys 0.61.2", +] + +[[package]] +name = "async-lock" +version = "3.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311" +dependencies = [ + "event-listener 5.4.1", + "event-listener-strategy", + "pin-project-lite", +] + +[[package]] +name = "async-std" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8e079a4ab67ae52b7403632e4618815d6db36d2a010cfe41b02c1b1578f93b" +dependencies = [ + "async-channel 1.9.0", + "async-global-executor", + "async-io", + "async-lock", + "crossbeam-utils", + "futures-channel", + "futures-core", + "futures-io", + "futures-lite", + "gloo-timers", + "kv-log-macro", + "log", + "memchr", + "once_cell", + "pin-project-lite", + "pin-utils", + "slab", + "wasm-bindgen-futures", +] + +[[package]] +name = "async-task" +version = "4.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" + [[package]] name = "atoi" version = "2.0.0" @@ -162,6 +275,19 @@ dependencies = [ "generic-array", ] +[[package]] +name = "blocking" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21" +dependencies = [ + "async-channel 2.5.0", + "async-task", + "futures-io", + "futures-lite", + "piper", +] + [[package]] name = "bumpalo" version = "3.20.3" @@ -222,6 +348,27 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -402,6 +549,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "event-listener" +version = "2.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0206175f82b8d6bf6652ff7d71a1e27fd2e4efde587fd368662814d6ec1d9ce0" + [[package]] name = "event-listener" version = "5.4.1" @@ -413,6 +566,16 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener 5.4.1", + "pin-project-lite", +] + [[package]] name = "fastrand" version = "2.4.1" @@ -521,6 +684,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" +[[package]] +name = "futures-lite" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-sink" version = "0.3.32" @@ -583,6 +759,18 @@ dependencies = [ "wasip3", ] +[[package]] +name = "gloo-timers" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" +dependencies = [ + "futures-channel", + "futures-core", + "js-sys", + "wasm-bindgen", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -625,6 +813,12 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + [[package]] name = "hex" version = "0.4.3" @@ -894,6 +1088,30 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + +[[package]] +name = "kv-log-macro" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" +dependencies = [ + "log", +] + [[package]] name = "lazy_static" version = "1.5.0" @@ -969,6 +1187,9 @@ name = "log" version = "0.4.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "616ec5685824bcc94416c6d4a7a446eea774a31efd7062c8480ba6fd06d7a6e5" +dependencies = [ + "value-bag", +] [[package]] name = "matchit" @@ -1217,6 +1438,23 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "piper" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1" +dependencies = [ + "atomic-waker", + "fastrand", + "futures-io", +] + [[package]] name = "pkcs1" version = "0.7.5" @@ -1250,6 +1488,20 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi", + "pin-project-lite", + "rustix", + "windows-sys 0.61.2", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -1722,7 +1974,7 @@ dependencies = [ "crc", "crossbeam-queue", "either", - "event-listener", + "event-listener 5.4.1", "futures-core", "futures-intrusive", "futures-io", @@ -1964,7 +2216,10 @@ name = "textsender_auth" version = "0.1.14" dependencies = [ "argon2", + "async-std", "axum", + "const_format", + "dotenvy", "http-body-util", "hyper", "josekit", @@ -2359,6 +2614,12 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" +[[package]] +name = "value-bag" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ba6f5989077681266825251a52748b8c1d8a4ad098cc37e440103d0ea717fc0" + [[package]] name = "vcpkg" version = "0.2.15" @@ -2424,6 +2685,16 @@ dependencies = [ "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9473dbd2991ae90b6291c3c32c30c6187ac49aa32f9905d1cce280ec1e110b0f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.122" diff --git a/Cargo.toml b/Cargo.toml index 1a8c89d..7145a8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,8 +23,11 @@ utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.3.0-v0.3.0-migrate-638da70649-111" } # icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.7.0" } +dotenvy = { version = "0.15.7" } +const_format = { version = "0.2.36" } [dev-dependencies] http-body-util = { version = "0.1.3" } 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" } From b4cc08bfaaa372b089695b62c1c55bf82a081b20 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sat, 30 May 2026 23:39:38 -0400 Subject: [PATCH 10/29] Adding env module --- src/env/environment.rs | 106 +++++++++++++++++++++++++++++++++++++++++ src/env/keys.rs | 52 ++++++++++++++++++++ src/env/mod.rs | 40 ++++++++++++++++ src/env/utility.rs | 15 ++++++ src/main.rs | 1 + 5 files changed, 214 insertions(+) create mode 100644 src/env/environment.rs create mode 100644 src/env/keys.rs create mode 100644 src/env/mod.rs create mode 100644 src/env/utility.rs diff --git a/src/env/environment.rs b/src/env/environment.rs new file mode 100644 index 0000000..6ebf5c8 --- /dev/null +++ b/src/env/environment.rs @@ -0,0 +1,106 @@ +pub async fn get_db_url() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::DB_URL; + let value = std::env::var(key).expect(key); + + crate::env::init_envvar(key, &value) +} + +pub async fn get_secret_main_key() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::SECRET_MAIN_KEY; + let value = std::env::var(key).expect(key); + + crate::env::init_envvar(key, &value) +} + +pub async fn get_service_passphrase() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::SERVICE_PASSPHRASE; + let value = std::env::var(key).expect(key); + + crate::env::init_envvar(key, &value) +} + +pub async fn get_secret_key() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::SECRET_KEY; + let value = std::env::var(key).expect(key); + + crate::env::init_envvar(key, &value) +} + +pub async fn get_root_directory() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::ROOT_DIRECTORY; + let value = std::env::var(key).expect(key); + + crate::env::init_envvar(key, &value) +} + +pub async fn get_app_base_api_url() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::TEXTSENDER_BASE_API_URL; + let value = std::env::var(key).expect(key); + + crate::env::init_envvar(key, &value) +} + +pub async fn get_app_auth_base_api_url() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::TEXTSENDER_AUTH_BASE_API_URL; + let value = std::env::var(key).expect(key); + + crate::env::init_envvar(key, &value) +} + +pub async fn get_app_env() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::APP_ENV; + let value = std::env::var(key).expect(key); + + crate::env::init_envvar(key, &value) +} + +pub async fn get_backend_port() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::BACKEND_PORT; + let value = std::env::var(key).expect(key); + crate::env::init_envvar(key, &value) +} + +pub async fn get_frontend_url() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::FRONTEND_URL; + let value = std::env::var(key).expect(key); + + crate::env::init_envvar(key, &value) +} + +pub async fn get_rust_log() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::RUST_LOG; + let value = std::env::var(key).expect(key); + + crate::env::init_envvar(key, &value) +} + +pub async fn get_allowed_origins() -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let key = crate::env::keys::ALLOWED_ORIGINS; + let value = std::env::var(key).expect(key); + + let mut envvar = crate::env::init_envvar(key, &value); + crate::env::init_delimiter(&mut envvar, ','); + + envvar +} + +/// Get environment not specified in the code +pub async fn get_env(environment: &str) -> crate::env::EnvVar { + dotenvy::dotenv().ok(); + let my_error = format!("{environment} {}", crate::env::keys::error::GENERAL_ERROR); + let value = std::env::var(environment).expect(&my_error); + + crate::env::init_envvar(environment, &value) +} diff --git a/src/env/keys.rs b/src/env/keys.rs new file mode 100644 index 0000000..9b187bd --- /dev/null +++ b/src/env/keys.rs @@ -0,0 +1,52 @@ +/// Environment key for Database management +pub const DB_URL: &str = "DATABASE_URL"; + +/// Environment key for secret main key +/// Used for the textsender app +pub const SECRET_MAIN_KEY: &str = "SECRET_MAIN_KEY"; + +/// Environment key for service logins +pub const SERVICE_PASSPHRASE: &str = "SERVICE_PASSPHRASE"; + +/// Environment key for secret key +/// Generic use of secret key that could be found in various apps +pub const SECRET_KEY: &str = "SECRET_KEY"; + +/// Environment key for root directory for the textsender app +pub const ROOT_DIRECTORY: &str = "ROOT_DIRECTORY"; + +/// Environment key for textsender api url +pub const TEXTSENDER_BASE_API_URL: &str = "TEXTSENDER_BASE_API_URL"; + +/// Environment key for textsender auth api url +pub const TEXTSENDER_AUTH_BASE_API_URL: &str = "TEXTSENDER_AUTH_BASE_API_URL"; + +/// Environment key for App status +pub const APP_ENV: &str = "APP_ENV"; +/// Environment key for backend port. Used for both auth and core functionality +pub const BACKEND_PORT: &str = "BACKEND_PORT"; +/// Environment key for frontend url +pub const FRONTEND_URL: &str = "FRONTEND_URL"; +/// Environment key for application logging +pub const RUST_LOG: &str = "RUST_LOG"; +/// Environment key for allowed origins for CORS support +pub const ALLOWED_ORIGINS: &str = "ALLOWED_ORIGINS"; + +pub mod error { + use const_format::concatcp; + + pub const GENERAL_ERROR: &str = "must not be set in enviornment file"; + pub const DB_URL: &str = concatcp!(super::DB_URL, " ", GENERAL_ERROR); + pub const SECRET_MAIN_KEY: &str = concatcp!(super::SECRET_MAIN_KEY, " ", GENERAL_ERROR); + pub const SERVICE_LOGIN: &str = concatcp!(super::SERVICE_PASSPHRASE, " ", GENERAL_ERROR); + pub const SECRET_KEY: &str = concatcp!(super::SECRET_KEY, " ", GENERAL_ERROR); + pub const ROOT_DIRECTORY: &str = concatcp!(super::ROOT_DIRECTORY, " ", GENERAL_ERROR); + pub const TEXTSENDER_BASE_API_URL: &str = concatcp!(super::TEXTSENDER_BASE_API_URL, " ", GENERAL_ERROR); + pub const TEXTSENDER_AUTH_BASE_API_URL: &str = + concatcp!(super::TEXTSENDER_AUTH_BASE_API_URL, " ", GENERAL_ERROR); + pub const APP_ENV: &str = concatcp!(super::APP_ENV, " ", GENERAL_ERROR); + pub const BACKEND_PORT: &str = concatcp!(super::BACKEND_PORT, " ", GENERAL_ERROR); + pub const FRONTEND_URL: &str = concatcp!(super::FRONTEND_URL, " ", GENERAL_ERROR); + pub const RUST_LOG: &str = concatcp!(super::RUST_LOG, " ", GENERAL_ERROR); + pub const ALLOWED_ORIGINS: &str = concatcp!(super::ALLOWED_ORIGINS, " ", GENERAL_ERROR); +} diff --git a/src/env/mod.rs b/src/env/mod.rs new file mode 100644 index 0000000..20cc9b7 --- /dev/null +++ b/src/env/mod.rs @@ -0,0 +1,40 @@ +pub mod environment; +pub mod keys; +pub mod utility; + +#[derive(Debug, Default, Clone)] +pub struct EnvVar { + pub key: String, + pub value: String, + pub has_delimiter: bool, + pub delimiter: char, +} + +pub fn init_envvar(key: &str, value: &str) -> EnvVar { + EnvVar { + key: key.to_string(), + value: value.to_string(), + has_delimiter: false, + ..Default::default() + } +} + +pub fn init_delimiter(envvar: &mut EnvVar, delimiter: char) { + let mut amount_of_delimiters_found: i32 = 0; + + for v in envvar.value.chars() { + if v == delimiter { + amount_of_delimiters_found += 1; + } + } + + let has_delimiter = amount_of_delimiters_found >= 1; + + if has_delimiter { + envvar.has_delimiter = has_delimiter; + envvar.delimiter = delimiter; + } else { + envvar.has_delimiter = has_delimiter; + } +} + diff --git a/src/env/utility.rs b/src/env/utility.rs new file mode 100644 index 0000000..6abea3a --- /dev/null +++ b/src/env/utility.rs @@ -0,0 +1,15 @@ +/// Take the Environment variable and delimitize it. If the value has a delimiter, +/// extract it into some strings +pub fn delimitize(var: &crate::env::EnvVar) -> Result, std::io::Error> { + if var.has_delimiter { + Ok(var + .value + .split(var.delimiter) + .map(|c| c.parse::().unwrap()) + .collect()) + } else { + Err(std::io::Error::other( + "Environment variable does not have a delimiter", + )) + } +} diff --git a/src/main.rs b/src/main.rs index 96a37e1..b09d0f4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +pub mod env; #[tokio::main] From cfae02d68f933ea451255c30da38a2a493a55858 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 15:47:24 -0400 Subject: [PATCH 11/29] Removing env module --- src/env/environment.rs | 106 ----------------------------------------- src/env/keys.rs | 52 -------------------- src/env/mod.rs | 40 ---------------- src/env/utility.rs | 15 ------ 4 files changed, 213 deletions(-) delete mode 100644 src/env/environment.rs delete mode 100644 src/env/keys.rs delete mode 100644 src/env/mod.rs delete mode 100644 src/env/utility.rs diff --git a/src/env/environment.rs b/src/env/environment.rs deleted file mode 100644 index 6ebf5c8..0000000 --- a/src/env/environment.rs +++ /dev/null @@ -1,106 +0,0 @@ -pub async fn get_db_url() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::DB_URL; - let value = std::env::var(key).expect(key); - - crate::env::init_envvar(key, &value) -} - -pub async fn get_secret_main_key() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::SECRET_MAIN_KEY; - let value = std::env::var(key).expect(key); - - crate::env::init_envvar(key, &value) -} - -pub async fn get_service_passphrase() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::SERVICE_PASSPHRASE; - let value = std::env::var(key).expect(key); - - crate::env::init_envvar(key, &value) -} - -pub async fn get_secret_key() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::SECRET_KEY; - let value = std::env::var(key).expect(key); - - crate::env::init_envvar(key, &value) -} - -pub async fn get_root_directory() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::ROOT_DIRECTORY; - let value = std::env::var(key).expect(key); - - crate::env::init_envvar(key, &value) -} - -pub async fn get_app_base_api_url() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::TEXTSENDER_BASE_API_URL; - let value = std::env::var(key).expect(key); - - crate::env::init_envvar(key, &value) -} - -pub async fn get_app_auth_base_api_url() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::TEXTSENDER_AUTH_BASE_API_URL; - let value = std::env::var(key).expect(key); - - crate::env::init_envvar(key, &value) -} - -pub async fn get_app_env() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::APP_ENV; - let value = std::env::var(key).expect(key); - - crate::env::init_envvar(key, &value) -} - -pub async fn get_backend_port() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::BACKEND_PORT; - let value = std::env::var(key).expect(key); - crate::env::init_envvar(key, &value) -} - -pub async fn get_frontend_url() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::FRONTEND_URL; - let value = std::env::var(key).expect(key); - - crate::env::init_envvar(key, &value) -} - -pub async fn get_rust_log() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::RUST_LOG; - let value = std::env::var(key).expect(key); - - crate::env::init_envvar(key, &value) -} - -pub async fn get_allowed_origins() -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let key = crate::env::keys::ALLOWED_ORIGINS; - let value = std::env::var(key).expect(key); - - let mut envvar = crate::env::init_envvar(key, &value); - crate::env::init_delimiter(&mut envvar, ','); - - envvar -} - -/// Get environment not specified in the code -pub async fn get_env(environment: &str) -> crate::env::EnvVar { - dotenvy::dotenv().ok(); - let my_error = format!("{environment} {}", crate::env::keys::error::GENERAL_ERROR); - let value = std::env::var(environment).expect(&my_error); - - crate::env::init_envvar(environment, &value) -} diff --git a/src/env/keys.rs b/src/env/keys.rs deleted file mode 100644 index 9b187bd..0000000 --- a/src/env/keys.rs +++ /dev/null @@ -1,52 +0,0 @@ -/// Environment key for Database management -pub const DB_URL: &str = "DATABASE_URL"; - -/// Environment key for secret main key -/// Used for the textsender app -pub const SECRET_MAIN_KEY: &str = "SECRET_MAIN_KEY"; - -/// Environment key for service logins -pub const SERVICE_PASSPHRASE: &str = "SERVICE_PASSPHRASE"; - -/// Environment key for secret key -/// Generic use of secret key that could be found in various apps -pub const SECRET_KEY: &str = "SECRET_KEY"; - -/// Environment key for root directory for the textsender app -pub const ROOT_DIRECTORY: &str = "ROOT_DIRECTORY"; - -/// Environment key for textsender api url -pub const TEXTSENDER_BASE_API_URL: &str = "TEXTSENDER_BASE_API_URL"; - -/// Environment key for textsender auth api url -pub const TEXTSENDER_AUTH_BASE_API_URL: &str = "TEXTSENDER_AUTH_BASE_API_URL"; - -/// Environment key for App status -pub const APP_ENV: &str = "APP_ENV"; -/// Environment key for backend port. Used for both auth and core functionality -pub const BACKEND_PORT: &str = "BACKEND_PORT"; -/// Environment key for frontend url -pub const FRONTEND_URL: &str = "FRONTEND_URL"; -/// Environment key for application logging -pub const RUST_LOG: &str = "RUST_LOG"; -/// Environment key for allowed origins for CORS support -pub const ALLOWED_ORIGINS: &str = "ALLOWED_ORIGINS"; - -pub mod error { - use const_format::concatcp; - - pub const GENERAL_ERROR: &str = "must not be set in enviornment file"; - pub const DB_URL: &str = concatcp!(super::DB_URL, " ", GENERAL_ERROR); - pub const SECRET_MAIN_KEY: &str = concatcp!(super::SECRET_MAIN_KEY, " ", GENERAL_ERROR); - pub const SERVICE_LOGIN: &str = concatcp!(super::SERVICE_PASSPHRASE, " ", GENERAL_ERROR); - pub const SECRET_KEY: &str = concatcp!(super::SECRET_KEY, " ", GENERAL_ERROR); - pub const ROOT_DIRECTORY: &str = concatcp!(super::ROOT_DIRECTORY, " ", GENERAL_ERROR); - pub const TEXTSENDER_BASE_API_URL: &str = concatcp!(super::TEXTSENDER_BASE_API_URL, " ", GENERAL_ERROR); - pub const TEXTSENDER_AUTH_BASE_API_URL: &str = - concatcp!(super::TEXTSENDER_AUTH_BASE_API_URL, " ", GENERAL_ERROR); - pub const APP_ENV: &str = concatcp!(super::APP_ENV, " ", GENERAL_ERROR); - pub const BACKEND_PORT: &str = concatcp!(super::BACKEND_PORT, " ", GENERAL_ERROR); - pub const FRONTEND_URL: &str = concatcp!(super::FRONTEND_URL, " ", GENERAL_ERROR); - pub const RUST_LOG: &str = concatcp!(super::RUST_LOG, " ", GENERAL_ERROR); - pub const ALLOWED_ORIGINS: &str = concatcp!(super::ALLOWED_ORIGINS, " ", GENERAL_ERROR); -} diff --git a/src/env/mod.rs b/src/env/mod.rs deleted file mode 100644 index 20cc9b7..0000000 --- a/src/env/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ -pub mod environment; -pub mod keys; -pub mod utility; - -#[derive(Debug, Default, Clone)] -pub struct EnvVar { - pub key: String, - pub value: String, - pub has_delimiter: bool, - pub delimiter: char, -} - -pub fn init_envvar(key: &str, value: &str) -> EnvVar { - EnvVar { - key: key.to_string(), - value: value.to_string(), - has_delimiter: false, - ..Default::default() - } -} - -pub fn init_delimiter(envvar: &mut EnvVar, delimiter: char) { - let mut amount_of_delimiters_found: i32 = 0; - - for v in envvar.value.chars() { - if v == delimiter { - amount_of_delimiters_found += 1; - } - } - - let has_delimiter = amount_of_delimiters_found >= 1; - - if has_delimiter { - envvar.has_delimiter = has_delimiter; - envvar.delimiter = delimiter; - } else { - envvar.has_delimiter = has_delimiter; - } -} - diff --git a/src/env/utility.rs b/src/env/utility.rs deleted file mode 100644 index 6abea3a..0000000 --- a/src/env/utility.rs +++ /dev/null @@ -1,15 +0,0 @@ -/// Take the Environment variable and delimitize it. If the value has a delimiter, -/// extract it into some strings -pub fn delimitize(var: &crate::env::EnvVar) -> Result, std::io::Error> { - if var.has_delimiter { - Ok(var - .value - .split(var.delimiter) - .map(|c| c.parse::().unwrap()) - .collect()) - } else { - Err(std::io::Error::other( - "Environment variable does not have a delimiter", - )) - } -} From dd461222705e764349a49f617be0483b88340d37 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 15:48:33 -0400 Subject: [PATCH 12/29] bump: textsender-models --- Cargo.lock | 6 +++--- Cargo.toml | 6 +++--- src/main.rs | 1 - 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0401ed8..ce66fd3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2218,8 +2218,6 @@ dependencies = [ "argon2", "async-std", "axum", - "const_format", - "dotenvy", "http-body-util", "hyper", "josekit", @@ -2243,8 +2241,10 @@ dependencies = [ [[package]] name = "textsender_models" version = "0.3.0" -source = "git+ssh://git@git.kundeng.us/phoenix/textsender-models.git?tag=v0.3.0-v0.3.0-migrate-638da70649-111#638da70649b49735fa8244c95e25f9f8a28f1eac" +source = "git+ssh://git@git.kundeng.us/phoenix/textsender-models.git?tag=v0.3.0-v0.3.0-migrate-1340440fec-111#1340440fecd79463bad053539c47e65d8da73194" dependencies = [ + "const_format", + "dotenvy", "rand 0.10.1", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 7145a8c..63b06f8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,10 +21,10 @@ time = { version = "0.3.47", features = ["macros", "serde"] } josekit = { version = "0.10.3" } utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } -textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.3.0-v0.3.0-migrate-638da70649-111" } +textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.3.0-v0.3.0-migrate-1340440fec-111" } # icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.7.0" } -dotenvy = { version = "0.15.7" } -const_format = { version = "0.2.36" } +# dotenvy = { version = "0.15.7" } +# const_format = { version = "0.2.36" } [dev-dependencies] http-body-util = { version = "0.1.3" } diff --git a/src/main.rs b/src/main.rs index b09d0f4..96a37e1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,3 @@ -pub mod env; #[tokio::main] From 26842175fe292b70526cd2d0914a9cb0cab3989a Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 16:11:09 -0400 Subject: [PATCH 13/29] Adding config module --- src/config/mod.rs | 6 ++++++ src/main.rs | 2 ++ 2 files changed, 8 insertions(+) create mode 100644 src/config/mod.rs diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..5d626b4 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,6 @@ +const ADDRESS: &str = "0.0.0.0"; +const PORT: &str = "9080"; + +pub fn get_full() -> String { + format!("{ADDRESS}:{PORT}") +} diff --git a/src/main.rs b/src/main.rs index 96a37e1..df637ab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,5 @@ +pub mod config; +pub mod db; #[tokio::main] From 42dd25691cbd7231b92b01107bb88ce12199d24a Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 16:11:34 -0400 Subject: [PATCH 14/29] Adding db module --- src/db/init.rs | 24 ++++++++++++++++++++++++ src/db/mod.rs | 5 +++++ 2 files changed, 29 insertions(+) create mode 100644 src/db/init.rs create mode 100644 src/db/mod.rs diff --git a/src/db/init.rs b/src/db/init.rs new file mode 100644 index 0000000..06184dc --- /dev/null +++ b/src/db/init.rs @@ -0,0 +1,24 @@ + + + +use sqlx::postgres::PgPoolOptions; + +pub async fn create_pool() -> Result { + let database_url = textsender_models::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"); +} + diff --git a/src/db/mod.rs b/src/db/mod.rs new file mode 100644 index 0000000..823d2d7 --- /dev/null +++ b/src/db/mod.rs @@ -0,0 +1,5 @@ +pub mod init; + +mod connection_settings { + pub const MAXCONN: u32 = 5; +} From c053a0a6cf34ed55467ff699e9422507ebfa320a Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 16:17:34 -0400 Subject: [PATCH 15/29] Adding hashing module --- src/hashing/mod.rs | 101 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 1 + 2 files changed, 102 insertions(+) create mode 100644 src/hashing/mod.rs diff --git a/src/hashing/mod.rs b/src/hashing/mod.rs new file mode 100644 index 0000000..2c53f0b --- /dev/null +++ b/src/hashing/mod.rs @@ -0,0 +1,101 @@ +use argon2::{ + Argon2, // The Argon2 algorithm struct + PasswordVerifier, + password_hash::{ + PasswordHasher, + SaltString, + rand_core::OsRng, // Secure random number generator + }, +}; + +pub fn generate_salt() -> Result { + // Generate a random salt + // SaltString::generate uses OsRng internally for cryptographic security + Ok(SaltString::generate(&mut OsRng)) +} + +pub fn get_salt(s: &str) -> Result { + SaltString::from_b64(s) +} + +pub fn hash_password( + password: &String, + salt: &SaltString, +) -> Result { + let password_bytes = password.as_bytes(); + + // Create an Argon2 instance with default parameters (recommended) + // You could customize parameters here if needed, but defaults are strong + let argon2 = Argon2::default(); + + // Hash the password with the salt + // The output is a PasswordHash string format that includes algorithm, version, + // parameters, salt, and the hash itself. + Ok(argon2.hash_password(password_bytes, salt)?.to_string()) +} + +pub fn verify_password( + password_attempt: &String, + stored_hash: String, +) -> Result { + let password_bytes = password_attempt.as_bytes(); + + // Parse the stored hash string + // This extracts the salt, parameters, and hash digest + let parsed_hash = argon2::PasswordHash::new(stored_hash.as_str())?; + + // Create an Argon2 instance (it will use the parameters from the parsed hash) + // Verify the password against the parsed hash + // This automatically uses the correct salt and parameters embedded in `parsed_hash` + match Argon2::default().verify_password(password_bytes, &parsed_hash) { + Ok(()) => Ok(true), // Passwords match + Err(argon2::password_hash::Error::Password) => Ok(false), // Passwords don't match + Err(e) => Err(e), // Some other error occurred (e.g., invalid hash format) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_hash_password() { + let some_password = String::from("somethingrandom"); + match hash_password(&some_password, &generate_salt().unwrap()) { + Ok(p) => match verify_password(&some_password, p.clone()) { + Ok(res) => { + assert_eq!(res, true); + } + Err(err) => { + assert!(false, "Error: {:?}", err.to_string()); + } + }, + Err(eerr) => { + assert!(false, "Error: {:?}", eerr.to_string()); + } + } + } + + #[test] + fn test_wrong_password() { + let some_password = String::from("somethingrandom"); + match hash_password(&some_password, &generate_salt().unwrap()) { + Ok(p) => { + match verify_password(&some_password, p.clone()) { + Ok(res) => { + assert_eq!(res, true, "Passwords are not verified"); + } + Err(err) => { + assert!(false, "Error: {:?}", err.to_string()); + } + } + let wrong_password = String::from("Differentanotherlevel"); + let result = verify_password(&wrong_password, p.clone()).unwrap(); + assert_eq!(false, result, "Passwords should not match"); + } + Err(err) => { + assert!(false, "Error: {:?}", err.to_string()); + } + } + } +} diff --git a/src/main.rs b/src/main.rs index df637ab..1f0d5da 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,5 +1,6 @@ pub mod config; pub mod db; +pub mod hashing; #[tokio::main] From 2badd0ca4d0ba3a145ee3b603c2845e3cf0c6bcd Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 16:28:51 -0400 Subject: [PATCH 16/29] Adding repo module --- src/main.rs | 1 + src/repo/mod.rs | 201 ++++++++++++++++++++++++++++++++++++++++++++ src/repo/service.rs | 51 +++++++++++ 3 files changed, 253 insertions(+) create mode 100644 src/repo/mod.rs create mode 100644 src/repo/service.rs diff --git a/src/main.rs b/src/main.rs index 1f0d5da..ef34d8b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,7 @@ pub mod config; pub mod db; pub mod hashing; +pub mod repo; #[tokio::main] diff --git a/src/repo/mod.rs b/src/repo/mod.rs new file mode 100644 index 0000000..1083421 --- /dev/null +++ b/src/repo/mod.rs @@ -0,0 +1,201 @@ +pub mod service; + +/* +pub mod user { + use sqlx::Row; + + #[derive(Debug, serde::Serialize, sqlx::FromRow)] + pub struct InsertedData { + pub id: uuid::Uuid, + pub date_created: Option, + } + + pub async fn get( + pool: &sqlx::PgPool, + username: &String, + ) -> Result { + let result = sqlx::query( + r#" + SELECT * FROM "user" WHERE username = $1 + "#, + ) + .bind(username) + .fetch_optional(pool) + .await; + + match result { + Ok(r) => match r { + Some(r) => Ok(icarus_models::user::User { + id: r.try_get("id")?, + username: r.try_get("username")?, + password: r.try_get("password")?, + email: r.try_get("email")?, + email_verified: r.try_get("email_verified")?, + phone: r.try_get("phone")?, + salt_id: r.try_get("salt_id")?, + firstname: r.try_get("firstname")?, + lastname: r.try_get("lastname")?, + date_created: r.try_get("date_created")?, + last_login: r.try_get("last_login")?, + status: r.try_get("status")?, + }), + None => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } + } + + pub async fn update_last_login( + pool: &sqlx::PgPool, + user: &icarus_models::user::User, + time: &time::OffsetDateTime, + ) -> Result { + let result = sqlx::query( + r#" + UPDATE "user" SET last_login = $1 WHERE id = $2 RETURNING last_login + "#, + ) + .bind(time) + .bind(user.id) + .fetch_optional(pool) + .await + .map_err(|e| { + eprintln!("Error updating time: {e}"); + e + }); + + match result { + Ok(row) => match row { + Some(r) => { + let last_login: time::OffsetDateTime = r + .try_get("last_login") + .map_err(|_e| sqlx::Error::RowNotFound)?; + Ok(last_login) + } + None => Err(sqlx::Error::RowNotFound), + }, + Err(err) => Err(err), + } + } + + pub async fn exists(pool: &sqlx::PgPool, username: &String) -> Result { + let result = sqlx::query( + r#" + SELECT 1 FROM "user" WHERE username = $1 + "#, + ) + .bind(username) + .fetch_optional(pool) + .await; + + match result { + Ok(r) => Ok(r.is_some()), + Err(e) => Err(e), + } + } + + pub async fn insert( + pool: &sqlx::PgPool, + user: &icarus_models::user::User, + ) -> Result<(uuid::Uuid, std::option::Option), sqlx::Error> { + let row = sqlx::query( + r#" + INSERT INTO "user" (username, password, email, phone, firstname, lastname, email_verified, status, salt_id) + VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) + RETURNING id, date_created; + "#) + .bind(&user.username) + .bind(&user.password) + .bind(&user.email) + .bind(&user.phone) + .bind(&user.firstname) + .bind(&user.lastname) + .bind(user.email_verified) + .bind(&user.status) + .bind(user.salt_id) + .fetch_one(pool) + .await + .map_err(|e| { + eprintln!("Error inserting item: {e}"); + e + })?; + + let result = InsertedData { + id: row.try_get("id").map_err(|_e| sqlx::Error::RowNotFound)?, + date_created: row + .try_get("date_created") + .map_err(|_e| sqlx::Error::RowNotFound)?, + }; + + if result.id.is_nil() && result.date_created.is_none() { + Err(sqlx::Error::RowNotFound) + } else { + Ok((result.id, result.date_created)) + } + } +} + +pub mod salt { + use sqlx::Row; + + #[derive(Debug, serde::Serialize, sqlx::FromRow)] + pub struct InsertedData { + pub id: uuid::Uuid, + } + + pub async fn get( + pool: &sqlx::PgPool, + id: &uuid::Uuid, + ) -> Result { + let result = sqlx::query( + r#" + SELECT * FROM "salt" WHERE id = $1 + "#, + ) + .bind(id) + .fetch_optional(pool) + .await; + + match result { + Ok(r) => match r { + Some(r) => Ok(icarus_models::user::salt::Salt { + id: r.try_get("id")?, + salt: r.try_get("salt")?, + }), + None => Err(sqlx::Error::RowNotFound), + }, + Err(e) => Err(e), + } + } + + pub async fn insert( + pool: &sqlx::PgPool, + salt: &icarus_models::user::salt::Salt, + ) -> Result { + let row = sqlx::query( + r#" + INSERT INTO "salt" (salt) + VALUES ($1) + RETURNING id; + "#, + ) + .bind(&salt.salt) + .fetch_one(pool) + .await + .map_err(|e| { + eprintln!("Error inserting item: {e}"); + e + })?; + + let result = InsertedData { + id: row.try_get("id").map_err(|_e| sqlx::Error::RowNotFound)?, + }; + + if !result.id.is_nil() { + Ok(result.id) + } else { + Err(sqlx::Error::RowNotFound) + } + } +} +*/ diff --git a/src/repo/service.rs b/src/repo/service.rs new file mode 100644 index 0000000..355d21d --- /dev/null +++ b/src/repo/service.rs @@ -0,0 +1,51 @@ +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 = 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), + } +} + From 775410907baf951b38c1cb0d85ab88859fe2fa52 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 16:46:32 -0400 Subject: [PATCH 17/29] Adding token_stuff repo --- Cargo.lock | 3 +- Cargo.toml | 2 +- src/main.rs | 1 + src/token_stuff/mod.rs | 141 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 src/token_stuff/mod.rs diff --git a/Cargo.lock b/Cargo.lock index ce66fd3..9015e0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2241,10 +2241,11 @@ dependencies = [ [[package]] name = "textsender_models" version = "0.3.0" -source = "git+ssh://git@git.kundeng.us/phoenix/textsender-models.git?tag=v0.3.0-v0.3.0-migrate-1340440fec-111#1340440fecd79463bad053539c47e65d8da73194" +source = "git+ssh://git@git.kundeng.us/phoenix/textsender-models.git?tag=v0.3.0-17-f920e91ec0-111#f920e91ec07487dbe1a72207a8bd2be84c0e95b9" dependencies = [ "const_format", "dotenvy", + "josekit", "rand 0.10.1", "serde", "serde_json", diff --git a/Cargo.toml b/Cargo.toml index 63b06f8..568a863 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ time = { version = "0.3.47", features = ["macros", "serde"] } josekit = { version = "0.10.3" } utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } -textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.3.0-v0.3.0-migrate-1340440fec-111" } +textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.3.0-17-f920e91ec0-111" } # icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.7.0" } # dotenvy = { version = "0.15.7" } # const_format = { version = "0.2.36" } diff --git a/src/main.rs b/src/main.rs index ef34d8b..db58dac 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ pub mod config; pub mod db; pub mod hashing; pub mod repo; +pub mod token_stuff; #[tokio::main] diff --git a/src/token_stuff/mod.rs b/src/token_stuff/mod.rs new file mode 100644 index 0000000..a453f3e --- /dev/null +++ b/src/token_stuff/mod.rs @@ -0,0 +1,141 @@ +use josekit::{ + self, + jws::alg::hmac::HmacJwsAlgorithm::Hs256, + jwt::{self}, +}; + +use time; + +pub const KEY_ENV: &str = "SECRET_KEY"; +pub const MESSAGE: &str = "Something random"; +pub const ISSUER: &str = "textsender_auth"; +pub const AUDIENCE: &str = "textsender"; + +pub fn get_issued() -> time::Result { + Ok(time::OffsetDateTime::now_utc()) +} + +pub fn get_expiration(issued: &time::OffsetDateTime) -> Result { + let duration_expire = time::Duration::hours(4); + Ok(*issued + duration_expire) +} + +pub fn create_token( + provided_key: &String, + id: &uuid::Uuid, +) -> Result<(String, i64), josekit::JoseError> { + let resource = textsender_models::token::TokenResource { + message: String::from(MESSAGE), + issuer: String::from(ISSUER), + audiences: vec![String::from(AUDIENCE)], + id: *id, + }; + textsender_models::token::create_token(provided_key, &resource, time::Duration::hours(4)) +} + +pub fn create_service_token( + provided: &String, + id: &uuid::Uuid, +) -> Result<(String, i64), josekit::JoseError> { + let resource = textsender_models::token::TokenResource { + message: String::from(SERVICE_SUBJECT), + issuer: String::from(ISSUER), + audiences: vec![String::from(AUDIENCE)], + id: *id, + }; + textsender_models::token::create_token(provided, &resource, time::Duration::hours(1)) +} + +pub fn create_service_refresh_token( + key: &String, + id: &uuid::Uuid, +) -> Result<(String, i64), josekit::JoseError> { + let resource = textsender_models::token::TokenResource { + message: String::from(SERVICE_SUBJECT), + issuer: String::from(ISSUER), + audiences: vec![String::from(AUDIENCE)], + id: *id, + }; + textsender_models::token::create_token(key, &resource, time::Duration::hours(4)) +} + +pub fn verify_token(key: &String, token: &String) -> bool { + match get_payload(key, token) { + Ok((payload, _header)) => match payload.subject() { + Some(_sub) => true, + None => false, + }, + Err(_err) => false, + } +} + +pub fn extract_id_from_token(key: &String, token: &String) -> Result { + match get_payload(key, token) { + Ok((payload, _header)) => match payload.claim("id") { + Some(id) => match uuid::Uuid::parse_str(id.as_str().unwrap()) { + Ok(extracted) => Ok(extracted), + Err(err) => Err(std::io::Error::other(err.to_string())), + }, + None => Err(std::io::Error::other("No claim found")), + }, + Err(err) => Err(std::io::Error::other(err.to_string())), + } +} + +pub const APP_TOKEN_TYPE: &str = "Icarus_App"; +pub const APP_SUBJECT: &str = "Something random"; +pub const SERVICE_TOKEN_TYPE: &str = "Icarus_Service"; +pub const SERVICE_SUBJECT: &str = "Service random"; + +pub fn get_token_type(key: &String, token: &String) -> Result { + match get_payload(key, token) { + Ok((payload, _header)) => match payload.subject() { + Some(subject) => { + if subject == APP_SUBJECT { + Ok(String::from(APP_TOKEN_TYPE)) + } else if subject == SERVICE_SUBJECT { + Ok(String::from(SERVICE_TOKEN_TYPE)) + } else { + Err(std::io::Error::other(String::from("Invalid subject"))) + } + } + None => Err(std::io::Error::other(String::from("Invalid payload"))), + }, + Err(err) => Err(std::io::Error::other(err.to_string())), + } +} + +pub fn is_token_type_valid(token_type: &String) -> bool { + token_type == SERVICE_TOKEN_TYPE +} + +fn get_payload( + key: &String, + token: &String, +) -> Result<(josekit::jwt::JwtPayload, josekit::jws::JwsHeader), josekit::JoseError> { + let ver = Hs256.verifier_from_bytes(key.as_bytes()).unwrap(); + jwt::decode_with_verifier(token, &ver) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_tokenize() { + let rt = tokio::runtime::Runtime::new().unwrap(); + let special_key = rt + .block_on(textsender_models::envy::environment::get_secret_key()) + .value; + let id = uuid::Uuid::new_v4(); + match create_token(&special_key, &id) { + Ok((token, _duration)) => { + let result = verify_token(&special_key, &token); + assert!(result, "Token not verified"); + } + Err(err) => { + assert!(false, "Error: {:?}", err.to_string()); + } + }; + } +} From 112b94f59ef6653dd69bc52200003d61130f4054 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 16:48:16 -0400 Subject: [PATCH 18/29] Changed name and cargo fmt --- src/db/init.rs | 8 +++----- src/main.rs | 1 - src/repo/service.rs | 1 - src/token_stuff/mod.rs | 4 ++-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/db/init.rs b/src/db/init.rs index 06184dc..fd56ce8 100644 --- a/src/db/init.rs +++ b/src/db/init.rs @@ -1,10 +1,9 @@ - - - use sqlx::postgres::PgPoolOptions; pub async fn create_pool() -> Result { - let database_url = textsender_models::envy::environment::get_db_url().await.value; + let database_url = textsender_models::envy::environment::get_db_url() + .await + .value; println!("Database url: {database_url}"); PgPoolOptions::new() @@ -21,4 +20,3 @@ pub async fn migrations(pool: &sqlx::PgPool) { .await .expect("Failed to run migrations"); } - diff --git a/src/main.rs b/src/main.rs index db58dac..1921fb7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,7 +4,6 @@ pub mod hashing; pub mod repo; pub mod token_stuff; - #[tokio::main] async fn main() { // initialize tracing diff --git a/src/repo/service.rs b/src/repo/service.rs index 355d21d..14ced65 100644 --- a/src/repo/service.rs +++ b/src/repo/service.rs @@ -48,4 +48,3 @@ pub async fn get_passphrase( Err(err) => Err(err), } } - diff --git a/src/token_stuff/mod.rs b/src/token_stuff/mod.rs index a453f3e..1ca1704 100644 --- a/src/token_stuff/mod.rs +++ b/src/token_stuff/mod.rs @@ -82,9 +82,9 @@ pub fn extract_id_from_token(key: &String, token: &String) -> Result Result { From 6713508de9fe3b00f3600139e1c44a623069f074 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 16:51:24 -0400 Subject: [PATCH 19/29] Adding vacant callers module --- src/callers/common.rs | 0 src/callers/mod.rs | 0 src/callers/register.rs | 0 src/main.rs | 1 + 4 files changed, 1 insertion(+) create mode 100644 src/callers/common.rs create mode 100644 src/callers/mod.rs create mode 100644 src/callers/register.rs diff --git a/src/callers/common.rs b/src/callers/common.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/callers/mod.rs b/src/callers/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/callers/register.rs b/src/callers/register.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/main.rs b/src/main.rs index 1921fb7..e93f85c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,4 @@ +pub mod callers; pub mod config; pub mod db; pub mod hashing; From 776740e0871e46c2d02cee1a3750cd6aced489cf Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 17:25:05 -0400 Subject: [PATCH 20/29] Adding caller code --- src/callers/common.rs | 54 ++++++++++++++ src/callers/mod.rs | 8 ++ src/callers/register.rs | 159 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 221 insertions(+) diff --git a/src/callers/common.rs b/src/callers/common.rs index e69de29..b33a3fe 100644 --- a/src/callers/common.rs +++ b/src/callers/common.rs @@ -0,0 +1,54 @@ +pub mod response { + use serde::{Deserialize, Serialize}; + + #[derive(Deserialize, Serialize, utoipa::ToSchema)] + pub struct TestResult { + pub message: String, + } +} + +pub mod endpoint { + use super::*; + use axum::{Extension, Json, http::StatusCode}; + + /// Endpoint to hit the root + /// basic handler that responds with a static string + #[utoipa::path( + get, + path = super::super::endpoints::ROOT, + responses( + (status = 200, description = "Test", body = &str), + ) + )] + pub async fn root() -> &'static str { + "Hello, World!" + } + + /// Endpoint to do a database ping + #[utoipa::path( + get, + path = super::super::endpoints::DBTEST, + responses( + (status = 200, description = "Successful ping of the db", body = super::response::TestResult), + (status = 400, description = "Failure in pinging the db", body = super::response::TestResult) + ) + )] + pub async fn db_ping( + Extension(pool): Extension, + ) -> (StatusCode, Json) { + match sqlx::query("SELECT 1").execute(&pool).await { + Ok(_) => { + let tr = response::TestResult { + message: String::from("This works"), + }; + (StatusCode::OK, Json(tr)) + } + Err(e) => ( + StatusCode::BAD_REQUEST, + Json(response::TestResult { + message: e.to_string(), + }), + ), + } + } +} diff --git a/src/callers/mod.rs b/src/callers/mod.rs index e69de29..33ddec1 100644 --- a/src/callers/mod.rs +++ b/src/callers/mod.rs @@ -0,0 +1,8 @@ +pub mod common; +pub mod register; + +pub mod endpoints { + pub const ROOT: &str = "/"; + pub const REGISTER: &str = "/api/v2/register"; + pub const DBTEST: &str = "/api/v2/test/db"; +} diff --git a/src/callers/register.rs b/src/callers/register.rs index e69de29..dc644ff 100644 --- a/src/callers/register.rs +++ b/src/callers/register.rs @@ -0,0 +1,159 @@ +use axum::{Json, http::StatusCode}; + +use crate::hashing; +use crate::repo; + +pub mod request { + use serde::{Deserialize, Serialize}; + + #[derive(Default, Deserialize, Serialize, utoipa::ToSchema)] + pub struct Request { + #[serde(skip_serializing_if = "String::is_empty")] + pub username: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub password: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub email: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub phone: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub firstname: String, + #[serde(skip_serializing_if = "String::is_empty")] + pub lastname: String, + } +} + +pub mod response { + use serde::{Deserialize, Serialize}; + + #[derive(Deserialize, Serialize, utoipa::ToSchema)] + pub struct Response { + pub message: String, + pub data: Vec, + } +} + +/// Endpoint to register a user +#[utoipa::path( + post, + path = super::endpoints::REGISTER, + request_body( + content = request::Request, + description = "Data required to register", + content_type = "application/json" + ), + responses( + (status = 201, description = "User created", body = response::Response), + (status = 404, description = "User already exists", body = response::Response), + (status = 400, description = "Issue creating user", body = response::Response) + ) +)] +pub async fn register_user( + axum::Extension(pool): axum::Extension, + Json(payload): Json, +) -> (StatusCode, Json) { + let registration_enabled = match is_registration_enabled().await { + Ok(value) => value, + Err(err) => { + eprintln!("Error: {err:?}"); + return ( + axum::http::StatusCode::INTERNAL_SERVER_ERROR, + Json(response::Response { + message: String::from("Registration check failed"), + data: Vec::new(), + }), + ); + } + }; + + if registration_enabled { + let mut user = icarus_models::user::User { + username: payload.username.clone(), + password: payload.password.clone(), + email: payload.email.clone(), + phone: payload.phone.clone(), + firstname: payload.firstname.clone(), + lastname: payload.lastname.clone(), + status: String::from("Active"), + email_verified: true, + ..Default::default() + }; + + match repo::user::exists(&pool, &user.username).await { + Ok(res) => { + if res { + ( + StatusCode::BAD_REQUEST, + Json(response::Response { + message: String::from("Error"), + data: Vec::new(), + }), + ) + } else { + let salt_string = hashing::generate_salt().unwrap(); + let mut salt = textsender_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], + }), + ), + } + } else { + ( + axum::http::StatusCode::NOT_ACCEPTABLE, + Json(response::Response { + message: String::from("Registration is not enabled"), + data: Vec::new(), + }), + ) + } +} + +/// Checks to see if registration is enabled +async fn is_registration_enabled() -> Result { + let key = String::from("ENABLE_REGISTRATION"); + let var = textsender_models::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", + )) + } +} From 6dc22d0a1ddee83eed7a6635d8f143e5b91a0930 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 17:26:55 -0400 Subject: [PATCH 21/29] Adding salt table and updating user schema --- migrations/schema.sql | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/migrations/schema.sql b/migrations/schema.sql index 2eb2c19..aaedc1c 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -11,7 +11,13 @@ CREATE TABLE users ( username TEXT NOT NULL, password TEXT NOT NULL, created timestamptz DEFAULT now(), - last_login timestamptz NULL + last_login timestamptz NULL, + salt_id UUID NOT NULL +); + +CREATE TABLE IF NOT EXISTS "salt" ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + salt TEXT NOT NULL ); CREATE TABLE service_users ( From b60ce6ee5311df0482485902acb1878a74f0f16f Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 17:45:05 -0400 Subject: [PATCH 22/29] Adding more callers code --- Cargo.lock | 4 +++- Cargo.toml | 2 +- src/callers/register.rs | 15 +++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9015e0f..5e832f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2241,7 +2241,7 @@ dependencies = [ [[package]] name = "textsender_models" version = "0.3.0" -source = "git+ssh://git@git.kundeng.us/phoenix/textsender-models.git?tag=v0.3.0-17-f920e91ec0-111#f920e91ec07487dbe1a72207a8bd2be84c0e95b9" +source = "git+ssh://git@git.kundeng.us/phoenix/textsender-models.git?tag=v0.3.0-19-cf112a468d-111#cf112a468d4e653ec49c55bc890d59b730c85f79" dependencies = [ "const_format", "dotenvy", @@ -2250,6 +2250,7 @@ dependencies = [ "serde", "serde_json", "time", + "utoipa", "uuid", ] @@ -2577,6 +2578,7 @@ dependencies = [ "quote", "regex", "syn", + "uuid", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 568a863..e1792ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ time = { version = "0.3.47", features = ["macros", "serde"] } josekit = { version = "0.10.3" } utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } -textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.3.0-17-f920e91ec0-111" } +textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.3.0-19-cf112a468d-111" } # icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.7.0" } # dotenvy = { version = "0.15.7" } # const_format = { version = "0.2.36" } diff --git a/src/callers/register.rs b/src/callers/register.rs index dc644ff..b435543 100644 --- a/src/callers/register.rs +++ b/src/callers/register.rs @@ -29,7 +29,7 @@ pub mod response { #[derive(Deserialize, Serialize, utoipa::ToSchema)] pub struct Response { pub message: String, - pub data: Vec, + pub data: Vec, } } @@ -67,15 +67,14 @@ pub async fn register_user( }; if registration_enabled { - let mut user = icarus_models::user::User { + let mut user = textsender_models::user::User { username: payload.username.clone(), password: payload.password.clone(), - email: payload.email.clone(), - phone: payload.phone.clone(), + // email: payload.email.clone(), + phone_number: payload.phone.clone(), firstname: payload.firstname.clone(), lastname: payload.lastname.clone(), - status: String::from("Active"), - email_verified: true, + // email_verified: true, ..Default::default() }; @@ -91,7 +90,7 @@ pub async fn register_user( ) } else { let salt_string = hashing::generate_salt().unwrap(); - let mut salt = textsender_models::user::salt::Salt::default(); + let mut salt = textsender_models::user::Salt::default(); let generated_salt = salt_string; salt.salt = generated_salt.to_string(); salt.id = repo::salt::insert(&pool, &salt).await.unwrap(); @@ -103,7 +102,7 @@ pub async fn register_user( match repo::user::insert(&pool, &user).await { Ok((id, date_created)) => { user.id = id; - user.date_created = date_created; + user.created = date_created; ( StatusCode::CREATED, Json(response::Response { From be379543bb57023b54d47e9b369b80988f6beee8 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 17:45:15 -0400 Subject: [PATCH 23/29] Adding code to repo module --- src/repo/mod.rs | 34 +++++++++++++--------------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 1083421..20d409e 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -1,6 +1,5 @@ pub mod service; -/* pub mod user { use sqlx::Row; @@ -13,7 +12,7 @@ pub mod user { pub async fn get( pool: &sqlx::PgPool, username: &String, - ) -> Result { + ) -> Result { let result = sqlx::query( r#" SELECT * FROM "user" WHERE username = $1 @@ -25,19 +24,16 @@ pub mod user { match result { Ok(r) => match r { - Some(r) => Ok(icarus_models::user::User { + Some(r) => Ok(textsender_models::user::User { id: r.try_get("id")?, username: r.try_get("username")?, password: r.try_get("password")?, - email: r.try_get("email")?, - email_verified: r.try_get("email_verified")?, - phone: r.try_get("phone")?, + phone_number: r.try_get("phone_number")?, salt_id: r.try_get("salt_id")?, firstname: r.try_get("firstname")?, lastname: r.try_get("lastname")?, - date_created: r.try_get("date_created")?, + created: r.try_get("created")?, last_login: r.try_get("last_login")?, - status: r.try_get("status")?, }), None => Err(sqlx::Error::RowNotFound), }, @@ -47,7 +43,7 @@ pub mod user { pub async fn update_last_login( pool: &sqlx::PgPool, - user: &icarus_models::user::User, + user: &textsender_models::user::User, time: &time::OffsetDateTime, ) -> Result { let result = sqlx::query( @@ -96,22 +92,19 @@ pub mod user { pub async fn insert( pool: &sqlx::PgPool, - user: &icarus_models::user::User, + user: &textsender_models::user::User, ) -> Result<(uuid::Uuid, std::option::Option), sqlx::Error> { let row = sqlx::query( r#" - INSERT INTO "user" (username, password, email, phone, firstname, lastname, email_verified, status, salt_id) - VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9) - RETURNING id, date_created; + INSERT INTO "user" (username, password, phone, firstname, lastname, salt_id) + VALUES ($1, $2, $3, $4, $5, $6) + RETURNING id, created; "#) .bind(&user.username) .bind(&user.password) - .bind(&user.email) - .bind(&user.phone) + .bind(&user.phone_number) .bind(&user.firstname) .bind(&user.lastname) - .bind(user.email_verified) - .bind(&user.status) .bind(user.salt_id) .fetch_one(pool) .await @@ -146,7 +139,7 @@ pub mod salt { pub async fn get( pool: &sqlx::PgPool, id: &uuid::Uuid, - ) -> Result { + ) -> Result { let result = sqlx::query( r#" SELECT * FROM "salt" WHERE id = $1 @@ -158,7 +151,7 @@ pub mod salt { match result { Ok(r) => match r { - Some(r) => Ok(icarus_models::user::salt::Salt { + Some(r) => Ok(textsender_models::user::Salt { id: r.try_get("id")?, salt: r.try_get("salt")?, }), @@ -170,7 +163,7 @@ pub mod salt { pub async fn insert( pool: &sqlx::PgPool, - salt: &icarus_models::user::salt::Salt, + salt: &textsender_models::user::Salt, ) -> Result { let row = sqlx::query( r#" @@ -198,4 +191,3 @@ pub mod salt { } } } -*/ From dbebaaed392341de46a6ef717285bc63abfd9460 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 17:46:01 -0400 Subject: [PATCH 24/29] Cargo.toml cleanup and cargo fmt --- Cargo.toml | 3 --- src/repo/mod.rs | 15 ++++++++------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e1792ad..1390f63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,9 +22,6 @@ josekit = { version = "0.10.3" } utoipa = { version = "5.5.0", features = ["axum_extras"] } utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] } textsender_models = { git = "ssh://git@git.kundeng.us/phoenix/textsender-models.git", tag = "v0.3.0-19-cf112a468d-111" } -# icarus_envy = { git = "ssh://git@git.kundeng.us/phoenix/icarus_envy.git", tag = "v0.7.0" } -# dotenvy = { version = "0.15.7" } -# const_format = { version = "0.2.36" } [dev-dependencies] http-body-util = { version = "0.1.3" } diff --git a/src/repo/mod.rs b/src/repo/mod.rs index 20d409e..1682239 100644 --- a/src/repo/mod.rs +++ b/src/repo/mod.rs @@ -99,13 +99,14 @@ pub mod user { INSERT INTO "user" (username, password, phone, firstname, lastname, salt_id) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, created; - "#) - .bind(&user.username) - .bind(&user.password) - .bind(&user.phone_number) - .bind(&user.firstname) - .bind(&user.lastname) - .bind(user.salt_id) + "#, + ) + .bind(&user.username) + .bind(&user.password) + .bind(&user.phone_number) + .bind(&user.firstname) + .bind(&user.lastname) + .bind(user.salt_id) .fetch_one(pool) .await .map_err(|e| { From 1ae7dc8aab5184da3077a56c59725d1078f42b95 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 17:53:03 -0400 Subject: [PATCH 25/29] Adding init code --- src/main.rs | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 110 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index e93f85c..7e0d004 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,12 +10,119 @@ async fn main() { // initialize tracing tracing_subscriber::fmt::init(); - /* let app = init::app().await; - // run our app with hyper, listening globally on port 8001 + // run our app with hyper, listening globally on port 9080 let url = config::get_full(); let listener = tokio::net::TcpListener::bind(url).await.unwrap(); axum::serve(listener, app).await.unwrap(); - */ +} + +mod init { + use axum::{ + Router, + routing::{get, post}, + }; + use utoipa::OpenApi; + + use super::callers; + use callers::common as common_callers; + use callers::register as register_caller; + use register_caller::response as register_responses; + + #[derive(utoipa::OpenApi)] + #[openapi( + paths( + common_callers::endpoint::db_ping, common_callers::endpoint::root, + register_caller::register_user, + ), + components(schemas(common_callers::response::TestResult, + register_responses::Response)), + tags( + (name = "TextSender Auth API", description = "Auth API for TextSender API") + ) + )] + 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(textsender_models::envy::keys::APP_ENV).as_deref() { + Ok("production") => { + let allowed_origins_env = textsender_models::envy::environment::get_allowed_origins().await; + match textsender_models::envy::utility::delimitize(&allowed_origins_env) { + Ok(alwd) => { + let allowed_origins: Vec = alwd + .into_iter() + .map(|s| s.parse::().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 { + // build our application with a route + Router::new() + .route( + callers::endpoints::DBTEST, + get(callers::common::endpoint::db_ping), + ) + .route( + callers::endpoints::ROOT, + get(callers::common::endpoint::root), + ) + .route( + callers::endpoints::REGISTER, + post(callers::register::register_user), + ) + .layer(cors::configure_cors().await) + } + + pub async fn app() -> Router { + let pool = super::db::init::create_pool() + .await + .expect("Failed to create pool"); + + super::db::init::migrations(&pool).await; + + routes() + .await + .merge( + utoipa_swagger_ui::SwaggerUi::new("/swagger-ui") + .url("/api-docs/openapi.json", ApiDoc::openapi()), + ) + .layer(axum::Extension(pool)) + } } From 1cea3442ffb26b8288e7af15a3564cb8339e2e2b Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 17:55:55 -0400 Subject: [PATCH 26/29] Adding sample .env.local --- .env.local.sample | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 .env.local.sample diff --git a/.env.local.sample b/.env.local.sample new file mode 100644 index 0000000..11f68eb --- /dev/null +++ b/.env.local.sample @@ -0,0 +1,9 @@ +SECRET_KEY=NULqYIzgt28bTiyziCd7IOO7b6LnWDW! +DB_NAME=textsender_auth_db +DB_USER=textsender_auth +DB_PASSWORD=password +DB_HOST=localhost +DB_PORT=5432 +DB_SSLMODE=disable +ENABLE_REGISTRATION=true +ALLOWED_ORIGINS="http://textsender.com" From a19262d9a12a17eefed4b72a15b849a22446e6bd Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 18:00:23 -0400 Subject: [PATCH 27/29] Adding docker --- .dockerignore.yml | 17 +++++++++++ Dockerfile | 70 ++++++++++++++++++++++++++++++++++++++++++++++ docker-compose.yml | 45 +++++++++++++++++++++++++++++ 3 files changed, 132 insertions(+) create mode 100644 .dockerignore.yml create mode 100644 Dockerfile create mode 100644 docker-compose.yml diff --git a/.dockerignore.yml b/.dockerignore.yml new file mode 100644 index 0000000..4f436f8 --- /dev/null +++ b/.dockerignore.yml @@ -0,0 +1,17 @@ +# Ignore build artifacts +target/ +pkg/ + +# Ignore git directory +.git/ + +.gitea/ + +# Ignore environment files (configure via docker-compose instead) +.env* + +# Ignore OS specific files +*.DS_Store + +# Add any other files/directories you don't need in the image +# e.g., logs/, tmp/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..abad4dd --- /dev/null +++ b/Dockerfile @@ -0,0 +1,70 @@ +# Stage 1: Build the application +# Use a specific Rust version for reproducibility. Choose one that matches your development environment. +# Using slim variant for smaller base image +FROM rust:1.95 as builder + +# Set the working directory inside the container +WORKDIR /usr/src/app + +# Install build dependencies if needed (e.g., for certain crates like sqlx with native TLS) +# RUN apt-get update && apt-get install -y pkg-config libssl-dev + +# Install build dependencies if needed (e.g., git for cloning) +RUN apt-get update && apt-get install -y --no-install-recommends \ + pkg-config libssl3 \ + ca-certificates \ + openssh-client git \ + && rm -rf /var/lib/apt/lists/* + +# << --- ADD HOST KEY HERE --- >> +# Replace 'yourgithost.com' with the actual hostname (e.g., github.com) +RUN mkdir -p -m 0700 ~/.ssh && \ + ssh-keyscan git.kundeng.us >> ~/.ssh/known_hosts + +# Copy Cargo manifests +COPY Cargo.toml Cargo.lock ./ + +# Build *only* dependencies to leverage Docker cache +# This dummy build caches dependencies as a separate layer +RUN --mount=type=ssh mkdir src && \ + echo "fn main() {println!(\"if you see this, the build broke\")}" > src/main.rs && \ + cargo build --release --quiet && \ + rm -rf src target/release/deps/textsender_auth* # Clean up dummy build artifacts + +# Copy the actual source code +COPY src ./src +# If you have other directories like `templates` or `static`, copy them too +COPY .env ./.env +COPY migrations ./migrations + +# << --- SSH MOUNT ADDED HERE --- >> +# Build *only* dependencies to leverage Docker cache +# This dummy build caches dependencies as a separate layer +# Mount the SSH agent socket for this command +RUN --mount=type=ssh \ + cargo build --release --quiet + +# Stage 2: Create the final, smaller runtime image +# Use a minimal base image like debian-slim or even distroless for security/size +FROM debian:trixie-slim + +# Install runtime dependencies if needed (e.g., SSL certificates) +RUN apt-get update && apt-get install -y ca-certificates libssl-dev libssl3 && rm -rf /var/lib/apt/lists/* + +# Set the working directory +WORKDIR /usr/local/bin + +# Copy the compiled binary from the builder stage +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/migrations ./migrations + +# Expose the port your Axum app listens on (e.g., 3000 or 8000) +EXPOSE 8001 + +# Set the command to run your application +# Ensure this matches the binary name copied above +CMD ["./textsender_auth"] diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..018a6cc --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,45 @@ +version: '3.8' # Use a recent version + +services: + # Your Rust Application Service + auth_api: + build: # Tells docker-compose to build the Dockerfile in the current directory + context: . + ssh: ["default"] # Uses host's SSH agent + container_name: textsender_auth # Optional: Give the container a specific name + ports: + # Map host port 8000 to container port 3000 (adjust as needed) + - "9080:9080" + env_file: + - .env + depends_on: + auth_db: + condition: service_healthy # Wait for the DB to be healthy before starting the app + restart: unless-stopped # Optional: Restart policy + + # PostgreSQL Database Service + auth_db: + image: postgres:18.4-alpine # Use an official Postgres image (Alpine variant is smaller) + container_name: textsender_auth_db # Optional: Give the container a specific name + environment: + # These MUST match the user, password, and database name in the DATABASE_URL above + POSTGRES_USER: ${POSTGRES_AUTH_USER:-textsender_op} + POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password} + POSTGRES_DB: ${POSTGRES_AUTH_DB:-textsender_auth_db} + volumes: + # Persist database data using a named volume + - postgres_data:/var/lib/postgresql + ports: [] + healthcheck: + # Checks if Postgres is ready to accept connections + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + restart: always # Optional: Restart policy + +# Define the named volume for data persistence +volumes: + postgres_data: + driver: local # Use the default local driver From 7e7e561008b3d1a50bc0383c49dc965a00fbc78c Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 18:00:58 -0400 Subject: [PATCH 28/29] cargo fmt --- src/main.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 7e0d004..ca94b2a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -64,7 +64,8 @@ mod init { // Dynamically set the allowed origin based on the environment match std::env::var(textsender_models::envy::keys::APP_ENV).as_deref() { Ok("production") => { - let allowed_origins_env = textsender_models::envy::environment::get_allowed_origins().await; + let allowed_origins_env = + textsender_models::envy::environment::get_allowed_origins().await; match textsender_models::envy::utility::delimitize(&allowed_origins_env) { Ok(alwd) => { let allowed_origins: Vec = alwd From f596a1bbf9e79e4f4dd4f8e2d6cda1f08f54a946 Mon Sep 17 00:00:00 2001 From: phoenix Date: Sun, 31 May 2026 18:09:56 -0400 Subject: [PATCH 29/29] docker-compose fix --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 018a6cc..d40dfeb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,9 +23,9 @@ services: container_name: textsender_auth_db # Optional: Give the container a specific name environment: # These MUST match the user, password, and database name in the DATABASE_URL above - POSTGRES_USER: ${POSTGRES_AUTH_USER:-textsender_op} - POSTGRES_PASSWORD: ${POSTGRES_AUTH_PASSWORD:-password} - POSTGRES_DB: ${POSTGRES_AUTH_DB:-textsender_auth_db} + POSTGRES_USER: ${DB_USER:-textsender_op} + POSTGRES_PASSWORD: ${DB_PASSWORD:-password} + POSTGRES_DB: ${DB_NAME:-textsender_auth_db} volumes: # Persist database data using a named volume - postgres_data:/var/lib/postgresql