5 Commits
Author SHA1 Message Date
phoenixandphoenix f556d729a5 Update go (#40)
Reviewed-on: phoenix/textsender-auth#40
Co-authored-by: phoenix <mail@kundeng.us>
Co-committed-by: phoenix <mail@kundeng.us>
2026-04-04 22:09:10 -04:00
phoenixandphoenix e06a54947f tsk-6: Populate IssuedAt for LoginResult (#39)
Closes #36

Reviewed-on: phoenix/textsender-auth#39
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2026-01-04 02:00:39 +00:00
phoenixandphoenix 689762c72a tsk-35: Update name of user (#38)
Closes #35

Reviewed-on: phoenix/textsender-auth#38
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2026-01-01 22:41:08 +00:00
phoenixandphoenix de171b790d tsk-34: Update last login of user (#37)
Closes #34

Reviewed-on: phoenix/textsender-auth#37
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-12-31 23:13:02 +00:00
phoenixandphoenix d34cad033d tsk-30: Update password (#33)
Closes #30

Reviewed-on: phoenix/textsender-auth#33
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
2025-12-26 21:14:40 +00:00
29 changed files with 1339 additions and 202 deletions
+3 -3
View File
@@ -16,7 +16,7 @@ jobs:
- name: Set up Go - name: Set up Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.25.4' # You can specify a specific version or 'stable' go-version: '1.26.1' # You can specify a specific version or 'stable'
- name: Build - name: Build
run: | run: |
@@ -49,7 +49,7 @@ jobs:
runs-on: ubuntu-24.04 runs-on: ubuntu-24.04
services: services:
postgres: postgres:
image: postgres:18.0 image: postgres:18.3-alpine
env: env:
POSTGRES_USER: ${{ secrets.DB_TEST_USER }} POSTGRES_USER: ${{ secrets.DB_TEST_USER }}
POSTGRES_PASSWORD: ${{ secrets.DB_TEST_PASSWORD }} POSTGRES_PASSWORD: ${{ secrets.DB_TEST_PASSWORD }}
@@ -68,7 +68,7 @@ jobs:
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v6 uses: actions/setup-go@v6
with: with:
go-version: '1.25.4' go-version: '1.26.1'
- name: Install PostgreSQL client - name: Install PostgreSQL client
run: sudo apt update && sudo apt-get install -y postgresql-client run: sudo apt update && sudo apt-get install -y postgresql-client
+1 -1
View File
@@ -1,5 +1,5 @@
# Multi-stage Dockerfile for Go application # Multi-stage Dockerfile for Go application
FROM golang:1.25.4 AS builder FROM golang:1.26.1 AS builder
WORKDIR /app WORKDIR /app
-1
View File
@@ -23,7 +23,6 @@ make build
Generate API documentation Generate API documentation
``` ```
go install github.com/swaggo/swag/cmd/swag@latest go install github.com/swaggo/swag/cmd/swag@latest
go get -u github.com/swaggo/http-swagger/v2
swag init --generalInfo main.go --dir ./cmd/api,./internal/handler --output docs/ --parseDependency --parseInternal swag init --generalInfo main.go --dir ./cmd/api,./internal/handler --output docs/ --parseDependency --parseInternal
``` ```
+3
View File
@@ -21,6 +21,7 @@ import (
"git.kundeng.us/phoenix/textsender-auth/internal/handler" "git.kundeng.us/phoenix/textsender-auth/internal/handler"
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint" "git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
mdleware "git.kundeng.us/phoenix/textsender-auth/internal/middleware" 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" "git.kundeng.us/phoenix/textsender-auth/internal/store"
) )
@@ -82,6 +83,7 @@ func main() {
refreshHandler := handler.NewRefreshHandler(cfg, userStore, serviceStore) refreshHandler := handler.NewRefreshHandler(cfg, userStore, serviceStore)
router := chi.NewRouter() router := chi.NewRouter()
jwtService := services.NewJWTService(config.GetSecretKey())
// Configure CORS // Configure CORS
router.Use(cors.Handler(cors.Options{ router.Use(cors.Handler(cors.Options{
@@ -102,6 +104,7 @@ func main() {
router.Method("Post", endpoint.CreateServiceUser, http.HandlerFunc(serviceHandler.Register)) router.Method("Post", endpoint.CreateServiceUser, http.HandlerFunc(serviceHandler.Register))
router.Method("Post", endpoint.LoginServiceUser, http.HandlerFunc(serviceHandler.Login)) router.Method("Post", endpoint.LoginServiceUser, http.HandlerFunc(serviceHandler.Login))
router.Method("Post", endpoint.TokenRefresh, http.HandlerFunc(refreshHandler.Refresh)) 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( router.Method("GET", "/swagger/*", httpSwagger.Handler(
httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.Port)), httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.Port)),
+13 -8
View File
@@ -4,21 +4,24 @@ import (
"context" "context"
"flag" "flag"
"fmt" "fmt"
"net/http"
"os" "os"
"path" "path"
"testing" "testing"
"github.com/gorilla/mux" "github.com/go-chi/chi/v5"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"git.kundeng.us/phoenix/textsender-auth/internal/config" "git.kundeng.us/phoenix/textsender-auth/internal/config"
"git.kundeng.us/phoenix/textsender-auth/internal/db" "git.kundeng.us/phoenix/textsender-auth/internal/db"
"git.kundeng.us/phoenix/textsender-auth/internal/handler" "git.kundeng.us/phoenix/textsender-auth/internal/handler"
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint" "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" "git.kundeng.us/phoenix/textsender-auth/internal/store"
) )
var testRouter *mux.Router var testRouter *chi.Mux
func TestMain(m *testing.M) { func TestMain(m *testing.M) {
cfg := load() cfg := load()
@@ -44,12 +47,14 @@ func TestMain(m *testing.M) {
serviceHandler := handler.NewServiceHandler(cfg, serviceStore) serviceHandler := handler.NewServiceHandler(cfg, serviceStore)
refreshHandler := handler.NewRefreshHandler(cfg, userStore, serviceStore) refreshHandler := handler.NewRefreshHandler(cfg, userStore, serviceStore)
testRouter = mux.NewRouter() testRouter = chi.NewRouter()
testRouter.HandleFunc(endpoint.Register, userHandler.Register).Methods("POST") jwtService := services.NewJWTService(config.GetSecretKey())
testRouter.HandleFunc(endpoint.Login, loginHandler.Login).Methods("POST") testRouter.Method("POST", endpoint.Register, http.HandlerFunc(userHandler.Register))
testRouter.HandleFunc(endpoint.CreateServiceUser, serviceHandler.Register).Methods("POST") testRouter.Method("POST", endpoint.Login, http.HandlerFunc(loginHandler.Login))
testRouter.HandleFunc(endpoint.LoginServiceUser, serviceHandler.Login).Methods("POST") testRouter.Method("POST", endpoint.CreateServiceUser, http.HandlerFunc(serviceHandler.Register))
testRouter.HandleFunc(endpoint.TokenRefresh, refreshHandler.Refresh).Methods("POST") 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() code := m.Run()
os.Exit(code) os.Exit(code)
+1 -1
View File
@@ -18,7 +18,7 @@ services:
# PostgreSQL Database Service # PostgreSQL Database Service
auth_db: auth_db:
image: postgres:18.0-alpine # Use an official Postgres image (Alpine variant is smaller) image: postgres:18.3-alpine # Use an official Postgres image (Alpine variant is smaller)
container_name: textsender_auth_db # Optional: Give the container a specific name container_name: textsender_auth_db # Optional: Give the container a specific name
environment: environment:
# These MUST match the user, password, and database name in the DATABASE_URL above # These MUST match the user, password, and database name in the DATABASE_URL above
+207 -1
View File
@@ -275,9 +275,146 @@ const docTemplate = `{
} }
} }
} }
},
"/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": { "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": { "handler.LoginAccount": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -417,6 +554,65 @@ const docTemplate = `{
} }
} }
}, },
"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": { "token.Login": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -426,24 +622,34 @@ const docTemplate = `{
"expires_in": { "expires_in": {
"type": "integer" "type": "integer"
}, },
"issued_at": {
"type": "integer"
},
"token_type": { "token_type": {
"type": "string" "type": "string"
},
"user_id": {
"type": "string"
} }
} }
}, },
"user.ServiceUser": { "user.ServiceUser": {
"type": "object", "type": "object",
"properties": { "properties": {
"date_created": { "created": {
"type": "string" "type": "string"
}, },
"id": { "id": {
"type": "string" "type": "string"
}, },
"last_login": {
"type": "string"
},
"passphrase": { "passphrase": {
"type": "string" "type": "string"
}, },
"username": { "username": {
"description": "TODO: Remove the omitempty tags at a later point",
"type": "string" "type": "string"
} }
} }
+207 -1
View File
@@ -269,9 +269,146 @@
} }
} }
} }
},
"/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": { "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": { "handler.LoginAccount": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -411,6 +548,65 @@
} }
} }
}, },
"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": { "token.Login": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -420,24 +616,34 @@
"expires_in": { "expires_in": {
"type": "integer" "type": "integer"
}, },
"issued_at": {
"type": "integer"
},
"token_type": { "token_type": {
"type": "string" "type": "string"
},
"user_id": {
"type": "string"
} }
} }
}, },
"user.ServiceUser": { "user.ServiceUser": {
"type": "object", "type": "object",
"properties": { "properties": {
"date_created": { "created": {
"type": "string" "type": "string"
}, },
"id": { "id": {
"type": "string" "type": "string"
}, },
"last_login": {
"type": "string"
},
"passphrase": { "passphrase": {
"type": "string" "type": "string"
}, },
"username": { "username": {
"description": "TODO: Remove the omitempty tags at a later point",
"type": "string" "type": "string"
} }
} }
+133 -1
View File
@@ -1,5 +1,24 @@
basePath: /api/v1 basePath: /api/v1
definitions: 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: handler.LoginAccount:
properties: properties:
password: password:
@@ -89,24 +108,69 @@ definitions:
message: message:
type: string type: string
type: object 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: token.Login:
properties: properties:
access_token: access_token:
type: string type: string
expires_in: expires_in:
type: integer type: integer
issued_at:
type: integer
token_type: token_type:
type: string type: string
user_id:
type: string
type: object type: object
user.ServiceUser: user.ServiceUser:
properties: properties:
date_created: created:
type: string type: string
id: id:
type: string type: string
last_login:
type: string
passphrase: passphrase:
type: string type: string
username: username:
description: 'TODO: Remove the omitempty tags at a later point'
type: string type: string
type: object type: object
host: localhost:9080 host: localhost:9080
@@ -280,6 +344,74 @@ paths:
summary: Obtain a refresh token summary: Obtain a refresh token
tags: tags:
- refresh - 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: securityDefinitions:
BearerAuth: BearerAuth:
description: JWT Bearer Token description: JWT Bearer Token
+23 -19
View File
@@ -1,41 +1,45 @@
module git.kundeng.us/phoenix/textsender-auth module git.kundeng.us/phoenix/textsender-auth
go 1.25.4 go 1.26.1
require ( require (
git.kundeng.us/phoenix/textsender-models v0.0.12 git.kundeng.us/phoenix/textsender-models v0.2.0
github.com/go-chi/chi/v5 v5.2.3 github.com/go-chi/chi/v5 v5.2.5
github.com/go-chi/cors v1.2.2 github.com/go-chi/cors v1.2.2
github.com/golang-jwt/jwt/v5 v5.3.0 github.com/golang-jwt/jwt/v5 v5.3.1
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/gorilla/mux v1.8.1 github.com/jackc/pgx/v5 v5.9.1
github.com/jackc/pgx/v5 v5.7.5
github.com/joho/godotenv v1.5.1 github.com/joho/godotenv v1.5.1
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
github.com/swaggo/http-swagger/v2 v2.0.2 github.com/swaggo/http-swagger/v2 v2.0.2
github.com/swaggo/swag v1.16.6 github.com/swaggo/swag v1.16.6
golang.org/x/crypto v0.42.0 golang.org/x/crypto v0.49.0
) )
require ( require (
github.com/KyleBanks/depth v1.2.1 // indirect github.com/KyleBanks/depth v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/jsonpointer v0.22.5 // indirect
github.com/go-openapi/jsonreference v0.20.0 // indirect github.com/go-openapi/jsonreference v0.21.5 // indirect
github.com/go-openapi/spec v0.20.6 // indirect github.com/go-openapi/spec v0.22.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect github.com/go-openapi/swag/conv v0.25.5 // indirect
github.com/go-openapi/swag/jsonname v0.25.5 // indirect
github.com/go-openapi/swag/jsonutils v0.25.5 // indirect
github.com/go-openapi/swag/loading v0.25.5 // indirect
github.com/go-openapi/swag/stringutils v0.25.5 // indirect
github.com/go-openapi/swag/typeutils v0.25.5 // indirect
github.com/go-openapi/swag/yamlutils v0.25.5 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/puddle/v2 v2.2.2 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rogpeppe/go-internal v1.14.1 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect
github.com/swaggo/files/v2 v2.0.0 // indirect github.com/swaggo/files/v2 v2.0.2 // indirect
golang.org/x/mod v0.27.0 // indirect go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/mod v0.34.0 // indirect
golang.org/x/text v0.29.0 // indirect golang.org/x/sync v0.20.0 // indirect
golang.org/x/tools v0.36.0 // indirect golang.org/x/text v0.35.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect golang.org/x/tools v0.43.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+48 -48
View File
@@ -1,92 +1,92 @@
git.kundeng.us/phoenix/textsender-models v0.0.12 h1:ps9H3FS5LyCwQhAiIvg4vYyfLZZ64dex1y9ytb0o9C4= git.kundeng.us/phoenix/textsender-models v0.2.0 h1:smz8Fs8VOs1Ya23txbOM0YPRidZIsM0yE9unHF0D/nQ=
git.kundeng.us/phoenix/textsender-models v0.0.12/go.mod h1:9iPDQJg1Tc6WMNoW5+f8YKmnosMwlWHJ++hmxNLDEe0= git.kundeng.us/phoenix/textsender-models v0.2.0/go.mod h1:3CkqA/HFKPhpMYxkKn5uVbZEzEbG3sofLZE8pZ1BHO4=
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= 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/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.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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE= github.com/go-chi/chi/v5 v5.2.5 h1:Eg4myHZBjyvJmAFjFvWgrqDTXFyOzjj7YIm3L3mu6Ug=
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 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 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA=
github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= github.com/go-openapi/jsonpointer v0.22.5/go.mod h1:gyUR3sCvGSWchA2sUBJGluYMbe1zazrYWIkWPjjMUY0=
github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe+pJyVWRdiE=
github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw=
github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ=
github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ=
github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA=
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM=
github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo=
github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU=
github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo=
github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U=
github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo=
github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU=
github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g=
github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M=
github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII=
github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E=
github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc=
github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ=
github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0 h1:7SgOMTvJkM8yWrQlU8Jm18VeDPuAvB/xWrdxFJkoFag=
github.com/go-openapi/testify/enable/yaml/v2 v2.4.0/go.mod h1:14iV8jyyQlinc9StD7w1xVPW3CO3q1Gj04Jy//Kw4VM=
github.com/go-openapi/testify/v2 v2.4.0 h1:8nsPrHVCWkQ4p8h1EsRVymA2XABB4OT40gcvAu+voFM=
github.com/go-openapi/testify/v2 v2.4.0/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54=
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 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 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 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY=
github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= 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/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 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.7.5 h1:JHGfMnQY+IEtGM63d+NGMjoRpysB2JBwDr5fsngwmJs= github.com/jackc/pgx/v5 v5.9.1 h1:uwrxJXBnx76nyISkhr33kQLlUqjv7et7b9FjCen/tdc=
github.com/jackc/pgx/v5 v5.7.5/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M= github.com/jackc/pgx/v5 v5.9.1/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 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= 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 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/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 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= 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/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.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU=
github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= 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 h1:FKCdLsl+sFCx60KFsyM0rDarwiUSZ8DqbfSyIKC9OBg=
github.com/swaggo/http-swagger/v2 v2.0.2/go.mod h1:r7/GBkAWIfK6E/OLnE8fXnviHiDeAHmgIyooa4xm3AQ= 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 h1:qBNcx53ZaX+M5dxVyTrgQ0PJ/ACK+NzhwcbieTt+9yI=
github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg= github.com/swaggo/swag v1.16.6/go.mod h1:ngP2etMK5a0P3QBizic5MEwpRmluJZPHjXcMoj4Xesg=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ= golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4=
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8=
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA=
golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s=
golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+12 -6
View File
@@ -1,8 +1,14 @@
package endpoint package endpoint
// Endpoint for registering a user const (
const Register = "/api/v1/register" // Endpoint for registering a user
const Login = "/api/v1/login" Register = "/api/v1/register"
const CreateServiceUser = "/api/v1/service/register" Login = "/api/v1/login"
const LoginServiceUser = "/api/v1/service/login" UpdatePassword = "/api/v1/user/password/update"
const TokenRefresh = "/api/v1/token/refresh" UpdateName = "/api/v1/user/name/update"
CreateServiceUser = "/api/v1/service/register"
LoginServiceUser = "/api/v1/service/login"
TokenRefresh = "/api/v1/token/refresh"
)
+111 -13
View File
@@ -1,16 +1,28 @@
package handler package handler
import ( import (
"fmt" "log"
"net/http" "net/http"
"time"
"git.kundeng.us/phoenix/textsender-models/tx0/token" "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/config"
"git.kundeng.us/phoenix/textsender-auth/internal/store" "git.kundeng.us/phoenix/textsender-auth/internal/store"
"git.kundeng.us/phoenix/textsender-auth/internal/utility" "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 { type LoginAccount struct {
Username string `json:"username"` Username string `json:"username"`
Password string `json:"password"` Password string `json:"password"`
@@ -21,15 +33,6 @@ type LoginResponse struct {
Data []token.Login `json:"data"` Data []token.Login `json:"data"`
} }
type LoginHandler struct {
Config *config.Config
UserStore store.UserStore
}
func NewLoginHandler(cfg *config.Config, userStore store.UserStore) *LoginHandler {
return &LoginHandler{Config: cfg, UserStore: userStore}
}
// Login godoc // Login godoc
// @Summary Login // @Summary Login
// @Description Login and be given an access token (requires JWT) // @Description Login and be given an access token (requires JWT)
@@ -55,7 +58,7 @@ func (l *LoginHandler) Login(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx := r.Context()
if exists, err := l.UserStore.UserExists(ctx, req.Username); err != nil { if exists, err := l.UserStore.UserExists(ctx, req.Username); err != nil {
fmt.Printf("Error: %v", err) log.Println("Error:", err)
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = err.Error() resp.Message = err.Error()
} else { } else {
@@ -67,20 +70,32 @@ func (l *LoginHandler) Login(w http.ResponseWriter, r *http.Request) {
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = err.Error() resp.Message = err.Error()
} else { } else {
hashing := utility.HashMash{Password: req.Password} 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) { if hashing.CheckPasswordHash(req.Password, user.Password) {
lastLogin := time.Now()
var tokGen utility.TokenGenerator var tokGen utility.TokenGenerator
secretKey := config.GetSecretKey() secretKey := config.GetSecretKey()
tokGen.SetSecretKey(secretKey) tokGen.SetSecretKey(secretKey)
if myToken, err := tokGen.GenerateToken(*user); err != nil { if myToken, err := tokGen.GenerateToken(*user); err != nil {
fmt.Println(err.Error()) log.Println(err.Error())
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = "Error generating token" resp.Message = "Error generating token"
} else { } 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 statusCode = http.StatusOK
resp.Data = append(resp.Data, *myToken) resp.Data = append(resp.Data, *myToken)
resp.Message = "Successful" resp.Message = "Successful"
} }
}
} else { } else {
statusCode = http.StatusNotFound statusCode = http.StatusNotFound
resp.Message = "User not found" resp.Message = "User not found"
@@ -88,6 +103,89 @@ func (l *LoginHandler) Login(w http.ResponseWriter, r *http.Request) {
} }
} }
} }
}
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) RespondWithJson(w, statusCode, &resp)
} }
+29 -11
View File
@@ -13,7 +13,6 @@ import (
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint" "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/store/mock"
"git.kundeng.us/phoenix/textsender-auth/internal/utility"
) )
func TestLogin(t *testing.T) { func TestLogin(t *testing.T) {
@@ -21,20 +20,13 @@ func TestLogin(t *testing.T) {
mockstore := mock.NewMockUserStore() mockstore := mock.NewMockUserStore()
handler := NewLoginHandler(cfg, mockstore) handler := NewLoginHandler(cfg, mockstore)
testUser := GetTestUser()
unhashedPassword := testUser.Password
hashing := utility.HashMash{Password: testUser.Password}
hashedPassword, err := hashing.HashPassword()
assert.NoError(t, err)
testUser.Password = hashedPassword
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
mockstore.CreateUser(ctx, &testUser) testUser, unhashedPassword, err := createUser(ctx, mockstore)
assert.NoError(t, err, "Error Creating user")
loginUser := LoginAccount{Username: testUser.Username, Password: unhashedPassword} loginUser := LoginAccount{Username: testUser.Username, Password: *unhashedPassword}
jsonValue, _ := json.Marshal(loginUser) jsonValue, _ := json.Marshal(loginUser)
req, _ := http.NewRequest("POST", endpoint.Login, strings.NewReader(string(jsonValue))) req, _ := http.NewRequest("POST", endpoint.Login, strings.NewReader(string(jsonValue)))
@@ -50,3 +42,29 @@ func TestLogin(t *testing.T) {
assert.NotEmpty(t, response.Data, "An access token should have been returned") 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)
}
+6 -2
View File
@@ -19,8 +19,12 @@ func TestRefreshTokenWithMock(t *testing.T) {
var serviceUser user.ServiceUser var serviceUser user.ServiceUser
var hashedPassword string var hashedPassword string
var err error var err error
unhashed := "9328nr29nudx3292m320!" unhashed := "A9328nr29nudx3292m320!"
hashing := utility.HashMash{Password: unhashed} hashing := utility.HashMash{}
if err := hashing.SetPassword(unhashed); err != nil {
assert.NoError(t, err, "Error setting password")
}
if hashedPassword, err = hashing.HashPassword(); err != nil { if hashedPassword, err = hashing.HashPassword(); err != nil {
assert.NoError(t, err, "Error hashing password: %v", err) assert.NoError(t, err, "Error hashing password: %v", err)
} else { } else {
+89 -10
View File
@@ -2,6 +2,7 @@ package handler
import ( import (
"fmt" "fmt"
"log"
"net/http" "net/http"
"git.kundeng.us/phoenix/textsender-models/tx0/user" "git.kundeng.us/phoenix/textsender-models/tx0/user"
@@ -12,6 +13,15 @@ import (
"git.kundeng.us/phoenix/textsender-auth/internal/utility" "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 { type RegisterUser struct {
PhoneNumber string `json:"phone_number"` PhoneNumber string `json:"phone_number"`
Username string `json:"username"` Username string `json:"username"`
@@ -29,15 +39,6 @@ type RegisterResponse struct {
Data []RegisterResponseItem `json:"data"` Data []RegisterResponseItem `json:"data"`
} }
type UserHandler struct {
Config *config.Config
UserStore store.UserStore
}
func NewUserHandler(cfg *config.Config, userStore store.UserStore) *UserHandler {
return &UserHandler{Config: cfg, UserStore: userStore}
}
// Register godoc // Register godoc
// @Summary Register user // @Summary Register user
// @Description Create a user that can send texts (requires JWT) // @Description Create a user that can send texts (requires JWT)
@@ -85,7 +86,11 @@ func (u *UserHandler) Register(w http.ResponseWriter, r *http.Request) {
statusCode = http.StatusBadRequest statusCode = http.StatusBadRequest
resp.Message = "Failure in creating User" resp.Message = "Failure in creating User"
} else { } else {
hashing := utility.HashMash{Password: user.Password} 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 { if hashedPassword, err := hashing.HashPassword(); err != nil {
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = err.Error() resp.Message = err.Error()
@@ -102,8 +107,82 @@ func (u *UserHandler) Register(w http.ResponseWriter, r *http.Request) {
} }
} }
} }
}
} }
RespondWithJson(w, statusCode, &resp) 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)
}
+29
View File
@@ -34,3 +34,32 @@ func TestCreateUserWithMock(t *testing.T) {
assert.NotNil(t, response.Data[0].Id, "Id should not be nil") 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)
}
+22 -2
View File
@@ -1,7 +1,9 @@
package handler package handler
import ( import (
"log"
"net/http" "net/http"
"time"
"git.kundeng.us/phoenix/textsender-models/tx0/token" "git.kundeng.us/phoenix/textsender-models/tx0/token"
"git.kundeng.us/phoenix/textsender-models/tx0/user" "git.kundeng.us/phoenix/textsender-models/tx0/user"
@@ -70,7 +72,11 @@ func (s *ServiceHandler) Register(w http.ResponseWriter, r *http.Request) {
statusCode = http.StatusBadRequest statusCode = http.StatusBadRequest
resp.Message = "Service user already exists" resp.Message = "Service user already exists"
} else { } else {
hashing := utility.HashMash{Password: req.Passphrase} 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 { if hashedPassword, err := hashing.HashPassword(); err != nil {
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = err.Error() resp.Message = err.Error()
@@ -87,6 +93,7 @@ func (s *ServiceHandler) Register(w http.ResponseWriter, r *http.Request) {
} }
} }
} }
}
RespondWithJson(w, statusCode, &resp) RespondWithJson(w, statusCode, &resp)
} }
@@ -139,11 +146,16 @@ func (s *ServiceHandler) Login(w http.ResponseWriter, r *http.Request) {
statusCode = http.StatusNotFound statusCode = http.StatusNotFound
resp.Message = "Not found" resp.Message = "Not found"
} else { } else {
hashing := utility.HashMash{Password: req.Passphrase} 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) { if !hashing.CheckPasswordHash(req.Passphrase, serviceUser.Passphrase) {
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = "Not valid" resp.Message = "Not valid"
} else { } else {
lastLogin := time.Now()
var tokGen utility.TokenGenerator var tokGen utility.TokenGenerator
tokGen.SetHourOffset(8) tokGen.SetHourOffset(8)
secretKey := config.GetSecretKey() secretKey := config.GetSecretKey()
@@ -153,6 +165,12 @@ func (s *ServiceHandler) Login(w http.ResponseWriter, r *http.Request) {
statusCode = http.StatusInternalServerError statusCode = http.StatusInternalServerError
resp.Message = err.Error() resp.Message = err.Error()
} else { } 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 statusCode = http.StatusOK
resp.Data = append(resp.Data, myToken) resp.Data = append(resp.Data, myToken)
resp.Message = "Successful" resp.Message = "Successful"
@@ -160,6 +178,8 @@ func (s *ServiceHandler) Login(w http.ResponseWriter, r *http.Request) {
} }
} }
} }
}
}
RespondWithJson(w, statusCode, &resp) RespondWithJson(w, statusCode, &resp)
} }
+7 -3
View File
@@ -20,7 +20,7 @@ func TestCreateServiceUserWithMock(t *testing.T) {
mockStore := mock.NewMockServiceUserStore() mockStore := mock.NewMockServiceUserStore()
handler := NewServiceHandler(cfg, mockStore) handler := NewServiceHandler(cfg, mockStore)
testService := ServiceCreationRequest{Username: "swoon", Passphrase: "ewrewr329n12y3x2!2"} testService := ServiceCreationRequest{Username: "swoon", Passphrase: "Ewrewr329n12y3x2!2"}
jsonValue, err := json.Marshal(testService) jsonValue, err := json.Marshal(testService)
assert.NoError(t, err, "Error marshaling request") assert.NoError(t, err, "Error marshaling request")
@@ -39,8 +39,12 @@ func TestLoginServiceUserWithMock(t *testing.T) {
var serviceUser user.ServiceUser var serviceUser user.ServiceUser
var hashedPassword string var hashedPassword string
var err error var err error
unhashed := "9328nr29nudx3292m320!" unhashed := "A9328nr29nudx3292m320!"
hashing := utility.HashMash{Password: unhashed} hashing := utility.HashMash{}
if err := hashing.SetPassword(unhashed); err != nil {
assert.NoError(t, err, "Error setting password")
}
if hashedPassword, err = hashing.HashPassword(); err != nil { if hashedPassword, err = hashing.HashPassword(); err != nil {
assert.NoError(t, err, "Error hashing password: %v", err) assert.NoError(t, err, "Error hashing password: %v", err)
} else { } else {
+24 -1
View File
@@ -1,6 +1,8 @@
package handler package handler
import ( import (
"context"
"fmt"
"log" "log"
"os" "os"
"path" "path"
@@ -9,10 +11,12 @@ import (
"github.com/joho/godotenv" "github.com/joho/godotenv"
"git.kundeng.us/phoenix/textsender-auth/internal/config" "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 { func GetTestUser() user.User {
return user.User{Username: "ghost", PhoneNumber: "+1234567890", Password: "dfgdffddfd"} return user.User{Username: "ghost", PhoneNumber: "+1234567890", Password: "Dfgdffd343dfd!"}
} }
func GetConfig() *config.Config { func GetConfig() *config.Config {
@@ -38,3 +42,22 @@ func GetConfig() *config.Config {
EnableRegistration: config.CheckRegistration(), 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
}
+47
View File
@@ -0,0 +1,47 @@
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 <token>"
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))
})
}
}
+49
View File
@@ -0,0 +1,49 @@
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
}
+22
View File
@@ -5,6 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"sync" "sync"
"time"
"git.kundeng.us/phoenix/textsender-models/tx0/user" "git.kundeng.us/phoenix/textsender-models/tx0/user"
"github.com/google/uuid" "github.com/google/uuid"
@@ -101,3 +102,24 @@ func (m *MockServiceUserStore) GetWithId(ctx context.Context, id uuid.UUID) (*us
return nil, 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
}
@@ -3,7 +3,9 @@ package mock
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"sync" "sync"
"time"
"github.com/google/uuid" "github.com/google/uuid"
@@ -37,9 +39,11 @@ func (m *MockUserStore) CreateUser(ctx context.Context, user *user.User) error {
} }
if _, exists := m.UsersByUsername[user.Username]; exists { if _, exists := m.UsersByUsername[user.Username]; exists {
return errors.New("User with email already exists") return errors.New("User with username already exists")
} }
user.Created = time.Now()
m.Users[user.Id] = user m.Users[user.Id] = user
m.UsersByUsername[user.Username] = user m.UsersByUsername[user.Username] = user
return nil return nil
@@ -112,3 +116,77 @@ func (m *MockUserStore) UserExists(ctx context.Context, username string) (bool,
return exists, nil 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
}
+12
View File
@@ -3,6 +3,7 @@ package store
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"git.kundeng.us/phoenix/textsender-models/tx0/user" "git.kundeng.us/phoenix/textsender-models/tx0/user"
"github.com/google/uuid" "github.com/google/uuid"
@@ -10,11 +11,13 @@ import (
"github.com/jackc/pgx/v5/pgxpool" "github.com/jackc/pgx/v5/pgxpool"
) )
// TODO: Rename this to ServiceUserStore
type ServiceStore interface { type ServiceStore interface {
CheckWithUsername(ctx context.Context, username string) (bool, error) CheckWithUsername(ctx context.Context, username string) (bool, error)
GetWithUsername(ctx context.Context, username string) (*user.ServiceUser, error) GetWithUsername(ctx context.Context, username string) (*user.ServiceUser, error)
GetWithId(ctx context.Context, id uuid.UUID) (*user.ServiceUser, error) GetWithId(ctx context.Context, id uuid.UUID) (*user.ServiceUser, error)
Create(ctx context.Context, serviceUser *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 { type PGServiceStore struct {
@@ -81,3 +84,12 @@ func (s *PGServiceStore) Create(ctx context.Context, serviceUser *user.ServiceUs
&serviceUser.Id, &serviceUser.Created, &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
}
}
+66 -8
View File
@@ -3,6 +3,7 @@ package store
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/jackc/pgx/v5" "github.com/jackc/pgx/v5"
@@ -17,6 +18,9 @@ type UserStore interface {
GetUserByUsername(ctx context.Context, username string) (*user.User, error) GetUserByUsername(ctx context.Context, username string) (*user.User, error)
GetAllUsers(ctx context.Context) ([]*user.User, error) GetAllUsers(ctx context.Context) ([]*user.User, error)
UserExists(ctx context.Context, username string) (bool, 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 { type PGUserStore struct {
@@ -31,20 +35,20 @@ func (s *PGUserStore) CreateUser(ctx context.Context, user *user.User) error {
query := ` query := `
INSERT INTO users (phone_number, username, password) INSERT INTO users (phone_number, username, password)
VALUES ($1, $2, $3) VALUES ($1, $2, $3)
RETURNING id, phone_number, username RETURNING id, phone_number, username, created
` `
return s.db.QueryRow(ctx, query, user.PhoneNumber, user.Username, user.Password).Scan( return s.db.QueryRow(ctx, query, user.PhoneNumber, user.Username, user.Password).Scan(
&user.Id, &user.PhoneNumber, &user.Username, &user.Id, &user.PhoneNumber, &user.Username, &user.Created,
) )
} }
func (s *PGUserStore) GetUserByID(ctx context.Context, id uuid.UUID) (*user.User, error) { func (s *PGUserStore) GetUserByID(ctx context.Context, id uuid.UUID) (*user.User, error) {
query := `SELECT id, username, password, phone_number FROM users WHERE id = $1` query := `SELECT id, username, password, phone_number, created FROM users WHERE id = $1`
var user user.User var user user.User
err := s.db.QueryRow(ctx, query, id).Scan( err := s.db.QueryRow(ctx, query, id).Scan(
&user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Created,
) )
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
@@ -58,11 +62,11 @@ func (s *PGUserStore) GetUserByID(ctx context.Context, id uuid.UUID) (*user.User
} }
func (s *PGUserStore) GetUserByUsername(ctx context.Context, username string) (*user.User, error) { func (s *PGUserStore) GetUserByUsername(ctx context.Context, username string) (*user.User, error) {
query := `SELECT id, username, password, phone_number FROM users WHERE username = $1` query := `SELECT id, username, password, phone_number, created FROM users WHERE username = $1`
var user user.User var user user.User
err := s.db.QueryRow(ctx, query, username).Scan( err := s.db.QueryRow(ctx, query, username).Scan(
&user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Created,
) )
if err == pgx.ErrNoRows { if err == pgx.ErrNoRows {
@@ -76,7 +80,7 @@ func (s *PGUserStore) GetUserByUsername(ctx context.Context, username string) (*
} }
func (s *PGUserStore) GetAllUsers(ctx context.Context) ([]*user.User, error) { func (s *PGUserStore) GetAllUsers(ctx context.Context) ([]*user.User, error) {
query := `SELECT id, username, password, phone_number FROM users` query := `SELECT id, username, password, phone_number, created FROM users`
rows, err := s.db.Query(ctx, query) rows, err := s.db.Query(ctx, query)
if err != nil { if err != nil {
@@ -88,7 +92,7 @@ func (s *PGUserStore) GetAllUsers(ctx context.Context) ([]*user.User, error) {
for rows.Next() { for rows.Next() {
var user user.User var user user.User
if err := rows.Scan( if err := rows.Scan(
&user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Id, &user.Username, &user.Password, &user.PhoneNumber, &user.Created,
); err != nil { ); err != nil {
return nil, fmt.Errorf("scanning user row: %w", err) return nil, fmt.Errorf("scanning user row: %w", err)
} }
@@ -113,3 +117,57 @@ func (s *PGUserStore) UserExists(ctx context.Context, username string) (bool, er
return exists, nil 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
}
}
}
}
+43 -4
View File
@@ -1,15 +1,19 @@
package utility package utility
import ( import (
"fmt"
"strings"
"unicode"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
) )
type HashMash struct { type HashMash struct {
Password string password string
} }
func (h *HashMash) HashPassword() (string, error) { func (h *HashMash) HashPassword() (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(h.Password), bcrypt.DefaultCost) bytes, err := bcrypt.GenerateFromPassword([]byte(h.password), bcrypt.DefaultCost)
return string(bytes), err return string(bytes), err
} }
@@ -18,6 +22,41 @@ func (h *HashMash) CheckPasswordHash(password string, hash string) bool {
return err == nil return err == nil
} }
func (h *HashMash) SetPassword(password string) { func (h *HashMash) SetPassword(password string) error {
h.Password = password 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")
}
}
} }
+1 -10
View File
@@ -38,7 +38,6 @@ func (t *TokenGenerator) GenerateToken(usr any) (*token.Login, error) {
if t.hourOffset == 0 { if t.hourOffset == 0 {
t.hourOffset = 4 t.hourOffset = 4
} }
expirationTime := time.Now().Add(t.hourOffset * time.Hour) expirationTime := time.Now().Add(t.hourOffset * time.Hour)
if claims, err := t.generateClaims(usr, TOKEN_TYPE, issuedAt, expirationTime); err != nil { if claims, err := t.generateClaims(usr, TOKEN_TYPE, issuedAt, expirationTime); err != nil {
@@ -48,7 +47,7 @@ func (t *TokenGenerator) GenerateToken(usr any) (*token.Login, error) {
if tokenString, err := myToken.SignedString(t.SecretKey); err != nil { if tokenString, err := myToken.SignedString(t.SecretKey); err != nil {
return nil, err return nil, err
} else { } else {
return &token.Login{UserId: claims.UserId, AccessToken: tokenString, TokenType: TOKEN_TYPE, ExpiresIn: expirationTime.Unix()}, nil return &token.Login{UserId: claims.UserId, AccessToken: tokenString, TokenType: TOKEN_TYPE, ExpiresIn: expirationTime.Unix(), IssuedAt: issuedAt.Unix()}, nil
} }
} }
} }
@@ -60,15 +59,11 @@ func (t *TokenGenerator) VerifyToken(accessToken string) (bool, error) {
return false, nil return false, nil
} else { } else {
if tken.Valid { if tken.Valid {
if clms != nil {
if clms.UserId != uuid.Nil { if clms.UserId != uuid.Nil {
return true, nil return true, nil
} else { } else {
return false, fmt.Errorf("User Id was not set") return false, fmt.Errorf("User Id was not set")
} }
} else {
return false, fmt.Errorf("Claims not parsed")
}
} else { } else {
return false, fmt.Errorf("Invalid access token") return false, fmt.Errorf("Invalid access token")
} }
@@ -82,15 +77,11 @@ func (t *TokenGenerator) ExtractIdFromToken(accessToken string) (uuid.UUID, erro
return uuid.Nil, nil return uuid.Nil, nil
} else { } else {
if tken.Valid { if tken.Valid {
if clms != nil {
if clms.UserId != uuid.Nil { if clms.UserId != uuid.Nil {
return clms.UserId, nil return clms.UserId, nil
} else { } else {
return uuid.Nil, fmt.Errorf("User Id was not set") return uuid.Nil, fmt.Errorf("User Id was not set")
} }
} else {
return uuid.Nil, fmt.Errorf("Claims not parsed")
}
} else { } else {
return uuid.Nil, fmt.Errorf("Invalid access token") return uuid.Nil, fmt.Errorf("Invalid access token")
} }
+7 -2
View File
@@ -5,14 +5,19 @@ DROP TABLE IF EXISTS service_users CASCADE;
CREATE TABLE users ( CREATE TABLE users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
first_name TEXT NULL,
last_name TEXT NULL,
phone_number TEXT NOT NULL, phone_number TEXT NOT NULL,
username TEXT NOT NULL, username TEXT NOT NULL,
password TEXT NOT NULL password TEXT NOT NULL,
created timestamptz DEFAULT now(),
last_login timestamptz NULL
); );
CREATE TABLE service_users ( CREATE TABLE service_users (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
username TEXT NOT NULL, username TEXT NOT NULL,
passphrase TEXT NOT NULL, passphrase TEXT NOT NULL,
created timestamptz DEFAULT now() created timestamptz DEFAULT now(),
last_login timestamptz NULL
); );