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, - ) -}