Добавлен основные классы для сервиса авторизаци

This commit is contained in:
Ганеев Артем
2025-10-28 20:49:54 +03:00
parent def3552a67
commit 736b8031f8
26 changed files with 904 additions and 40 deletions

2
.gitignore vendored
View File

@@ -22,4 +22,4 @@ go.work
go.work.sum
# env file
.env
*.env

View File

@@ -2,15 +2,62 @@ package main
import (
"authorization/internal"
"authorization/internal/config"
"authorization/internal/handler"
"log"
"authorization/internal/repository"
"authorization/internal/service"
"os"
"github.com/golang-migrate/migrate/v4"
_ "github.com/golang-migrate/migrate/v4/database/postgres" // Импорт драйвера PostgreSQL
_ "github.com/golang-migrate/migrate/v4/source/file" // Импорт файлового драйвера
"github.com/joho/godotenv"
"github.com/sirupsen/logrus"
)
func main() {
handlers := new(handler.Handler)
cfg, err := initConfigs()
if err != nil {
logrus.Fatalf("Ошибка считывания конфига: %s", err.Error())
}
if err := godotenv.Load(); err != nil {
logrus.Fatalf("Ошибка загрузки конфига %s", err.Error())
}
print(cfg.Token.AccessToken.SecretWord)
print(cfg.Token.RefreshToken.SecretWord)
db, err := repository.NewPostgresDB(repository.Config{
Host: cfg.DB.Host,
Port: cfg.DB.Port,
Username: cfg.DB.Username,
Password: os.Getenv("DB_PASSWORD"),
DBName: cfg.DB.DBname,
SSLMode: cfg.DB.Sslmode,
})
if err != nil {
logrus.Fatalf("Ошибка подключения к базе данных %s", err.Error())
}
m, err := migrate.New(
"file://schema",
"postgres://postgres:postgres@localhost:5432/authorization?sslmode=disable")
if err != nil {
logrus.Fatalf("Ошибка инициализации миграций: %s", err.Error())
}
// Применение миграций
if err := m.Up(); err != nil && err != migrate.ErrNoChange {
logrus.Fatalf("Ошибка применения миграций: %s", err.Error())
}
repository := repository.NewRepository(db)
services := service.NewServices(repository)
handlers := handler.NewHandler(services)
srv := new(internal.Server)
if err := srv.Run("8080", handlers.InitRoutes()); err != nil {
log.Fatalf("Ошибка запуска сервера: %s", err.Error())
if err := srv.Run(cfg.Server.Port, handlers.InitRoutes()); err != nil {
logrus.Fatalf("Ошибка запуска сервера: %s", err.Error())
}
}
func initConfigs() (*config.Config, error) {
return config.LoadConfig("configs/application.yaml")
}

15
configs/application.yaml Normal file
View File

@@ -0,0 +1,15 @@
server:
port: 8081
db:
username: postgres
host: localhost
port: 5432
sslmode: disable
dbname: authorization
token:
accessToken:
TTL-in-min: 15
secretWord: kdfmklsdlmk;asdmkl;ds
refreshToken:
TTL-in-min: 90
secretWord: asdflmkasdfklmsdafklm

11
docker-compose.yaml Normal file
View File

@@ -0,0 +1,11 @@
services:
postgres:
image: postgres:15.0
environment:
POSTGRES_DB: authorization
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432

40
go.mod
View File

@@ -4,31 +4,61 @@ go 1.23.3
require github.com/gin-gonic/gin v1.10.0
require (
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
)
require (
github.com/bytedance/sonic v1.11.6 // indirect
github.com/bytedance/sonic/loader v0.1.1 // indirect
github.com/cloudwego/base64x v0.1.4 // indirect
github.com/cloudwego/iasm v0.2.0 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
github.com/gin-contrib/sse v0.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.20.0 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang-migrate/migrate/v4 v4.18.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9 // indirect
github.com/magiconair/properties v1.8.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.6.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.19.0 // indirect
github.com/stretchr/testify v1.11.1
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/atomic v1.9.0 // indirect
go.uber.org/multierr v1.9.0 // indirect
golang.org/x/arch v0.8.0 // indirect
golang.org/x/crypto v0.23.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
golang.org/x/text v0.15.0 // indirect
google.golang.org/protobuf v1.34.1 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect
golang.org/x/net v0.29.0 // indirect
golang.org/x/sys v0.33.0 // indirect
golang.org/x/text v0.18.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

68
go.sum
View File

@@ -8,6 +8,10 @@ github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0=
github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -22,7 +26,22 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-jwt/jwt/v5 v5.2.2 h1:Rl4B7itRWVtYIHFrSNd7vhTiz9UpLdi6gZhZ3wEeDy8=
github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y=
github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
@@ -31,8 +50,14 @@ github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZY
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw=
github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -41,6 +66,24 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0=
github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@@ -52,26 +95,51 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI=
go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc=
golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g=
golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg=
google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
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=

View File

@@ -1,37 +1,49 @@
package internal
type RegistrationUser struct {
Username string `json:"username"`
Password string `json:"password"`
}
const (
Student UserRole = iota
Teacher
Admin
import (
"errors"
"fmt"
)
type User struct {
Username string
Password string
UserRole UserRole
Id int `json:"-" db:"id"`
Name string `json:"name" binding:"required"`
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
UserRole string `json:"-" db:"role"`
}
func (user *User) ChangeRole(userRole UserRole) {
user.UserRole = userRole
type AuthUser struct {
Username string `json:"username" binding:"required"`
Password string `json:"password" binding:"required"`
}
type UserRole int
type RefreshTokenRequest struct {
RefreshToken string `json:"refreshToken" binding:"required"`
}
func (role UserRole) String() string {
switch role {
case Student:
return "student"
case Teacher:
return "teacher"
case Admin:
return "admin"
default:
return "unknown"
type UserRole string
const (
Student UserRole = "student"
Teacher UserRole = "teacher"
Admin UserRole = "admin"
)
func (userrole UserRole) ToString() string {
return string(userrole)
}
var ErrInvalidRole = errors.New("invalid role")
func FromString(in string) (UserRole, error) {
switch in {
case Student.ToString():
return Student, nil
case Teacher.ToString():
return Teacher, nil
case Admin.ToString():
return Admin, nil
}
return Student, fmt.Errorf("%q is not a valid role: %w", in, ErrInvalidRole)
}

42
internal/config/config.go Normal file
View File

@@ -0,0 +1,42 @@
package config
import (
"os"
"gopkg.in/yaml.v3"
)
type Config struct {
Token TokenConfig `yaml:"token" json:"token"`
Server ServerConfig `yaml:"server" json:"server"`
DB DatabaseConfig `yaml:"db" json:"db"`
}
type ServerConfig struct {
Port string `yaml:"port" json:"port"`
}
type DatabaseConfig struct {
Username string `yaml:"username" json:"username"`
Host string `yaml:"host" json:"host"`
Port string `yaml:"port" json:"port"`
Sslmode string `yaml:"sslmode" json:"sslmode"`
DBname string `yaml:"dbname" json:"dbname"`
}
func LoadConfig(absolutePath string) (*Config, error) {
config := &Config{}
file, err := os.Open(absolutePath)
if err != nil {
return nil, err
}
defer file.Close()
decoder := yaml.NewDecoder(file)
if err := decoder.Decode(config); err != nil {
return nil, err
}
return config, nil
}

22
internal/config/token.go Normal file
View File

@@ -0,0 +1,22 @@
package config
import "time"
type TokenConfig struct {
AccessToken TokenSettings `yaml:"accessToken" json:"accessToken"`
RefreshToken TokenSettings `yaml:"refreshToken" json:"refreshToken"`
}
type TokenSettings struct {
TTLInMinutes int `yaml:"TTL-in-min" json:"TTL-in-min"`
SecretWord string `yaml:"secretWord" json:"secretWord"`
}
// Методы для удобства
func (t *TokenSettings) GetTTL() time.Duration {
return time.Duration(t.TTLInMinutes) * time.Minute
}
func (t *TokenSettings) GetSecretBytes() []byte {
return []byte(t.SecretWord)
}

View File

@@ -1,13 +1,65 @@
package handler
import (
"authorization/internal"
"net/http"
"github.com/gin-gonic/gin"
)
func (h *Handler) signUp(c *gin.Context) {
var input internal.User
if err := c.BindJSON(&input); err != nil {
newErrorResponse(c, http.StatusBadRequest, err.Error())
return
}
id, err := h.services.Authorization.CreateUser(input)
if err != nil {
newErrorResponse(c, http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, map[string]interface{}{
"id": id,
})
}
func (h *Handler) signIn(c *gin.Context) {
var input internal.AuthUser
if err := c.BindJSON(&input); err != nil {
newErrorResponse(c, http.StatusBadRequest, err.Error())
return
}
accesstoken, refreshToken, err := h.services.Authorization.GenerateToken(input.Username, input.Password)
if err != nil {
newErrorResponse(c, http.StatusInternalServerError, err.Error())
return
}
c.JSON(http.StatusOK, map[string]interface{}{
"accessToken": accesstoken,
"refreshToken": refreshToken,
})
}
func (h *Handler) refresh(c *gin.Context) {
var input internal.RefreshTokenRequest
if err := c.BindJSON(&input); err != nil {
newErrorResponse(c, http.StatusBadRequest, err.Error())
return
}
accessToken, refreshToken, err := h.services.Authorization.RefreshToken(input.RefreshToken)
if err != nil {
newErrorResponse(c, http.StatusUnauthorized, err.Error())
return
}
c.JSON(http.StatusOK, map[string]interface{}{
"accessToken": accessToken,
"refreshToken": refreshToken,
})
}

View File

@@ -1,17 +1,40 @@
package handler
import "github.com/gin-gonic/gin"
import (
"authorization/internal/service"
"github.com/gin-gonic/gin"
)
type Handler struct {
services *service.Service
}
func NewHandler(services *service.Service) *Handler {
return &Handler{
services: services,
}
}
func (h *Handler) InitRoutes() *gin.Engine {
router := gin.New()
auth := router.Group("/auth")
serviceRouter := router.Group("/auth-service")
{
auth.POST("/sign-up", h.signUp)
auth.POST("/sign-in", h.signIn)
auth := serviceRouter.Group("/auth")
{
auth.POST("/sign-up", h.signUp)
auth.POST("/sign-in", h.signIn)
auth.POST("/refresh", h.refresh)
}
api := router.Group("/api")
{
users := api.Group("/users", h.checkAdminIdentity)
{
users.POST("/:username", h.changeUserRole)
}
}
}
return router
}

View File

@@ -0,0 +1,42 @@
package handler
import (
"authorization/internal"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
const (
authorizationHeader = "Authorization"
roleKey = "user_role"
)
func (h *Handler) checkAdminIdentity(c *gin.Context) {
header := c.GetHeader(authorizationHeader)
if header == "" {
newErrorResponse(c, http.StatusUnauthorized, "Пустой header авторизации")
return
}
headerParts := strings.Split(header, " ")
if len(headerParts) != 2 {
newErrorResponse(c, http.StatusUnauthorized, "Невалидный токен JWT")
return
}
userRole, err := h.services.ParseToken(headerParts[1])
if userRole != string(internal.Admin) {
newErrorResponse(c, http.StatusUnauthorized, "Недостаточно прав для выполнения запроса")
return
}
if err != nil {
newErrorResponse(c, http.StatusUnauthorized, "Ошибка при извлечении claims")
return
}
c.Set(roleKey, userRole)
}

View File

@@ -0,0 +1,15 @@
package handler
import (
"github.com/gin-gonic/gin"
"github.com/sirupsen/logrus"
)
type error struct {
Message string `json:"message"`
}
func newErrorResponse(c *gin.Context, statusCode int, message string) {
logrus.Error(message)
c.AbortWithStatusJSON(statusCode, error{Message: message})
}

36
internal/handler/users.go Normal file
View File

@@ -0,0 +1,36 @@
package handler
import (
"net/http"
"github.com/gin-gonic/gin"
)
type ChangeUserRoleRequest struct {
Role string `json:"role"`
}
func (h *Handler) changeUserRole(c *gin.Context) {
var input ChangeUserRoleRequest
if err := c.BindJSON(&input); err != nil {
newErrorResponse(c, http.StatusBadRequest, err.Error())
return
}
username := c.Param("username")
if username == "" {
newErrorResponse(c, http.StatusBadRequest, "Ошибка в строке запроса")
return
}
role, err := h.services.ChangeUserRole(username, input.Role)
if err != nil {
newErrorResponse(c, http.StatusBadRequest, err.Error())
return
}
c.JSON(http.StatusOK, map[string]interface{}{
"newRole": role,
})
}

View File

@@ -0,0 +1,35 @@
package repository
import (
"database/sql"
"fmt"
_ "github.com/lib/pq"
)
const (
usersTable = "users"
)
type Config struct {
Host string
Port string
Username string
Password string
DBName string
SSLMode string
}
func NewPostgresDB(cfg Config) (*sql.DB, error) {
db, err := sql.Open("postgres",
fmt.Sprintf("user=%s password=%s host=%s dbname=%s sslmode=%s",
cfg.Username, cfg.Password, cfg.Host, cfg.DBName, cfg.SSLMode))
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
return nil, err
}
return db, nil
}

View File

@@ -0,0 +1,22 @@
package repository
import (
"authorization/internal"
"database/sql"
)
type UserResository interface {
CreateUser(user internal.User) (int, error)
GetUser(username, password string) (internal.User, error)
UpdateUserRole(username string, userrole internal.UserRole) (string, error)
}
type Repository struct {
UserResository
}
func NewRepository(db *sql.DB) *Repository {
return &Repository{
UserResository: NewUserPostgres(db),
}
}

View File

@@ -0,0 +1,44 @@
package repository
import (
"authorization/internal"
"database/sql"
"fmt"
)
type UserPostgres struct {
db *sql.DB
}
func NewUserPostgres(db *sql.DB) *UserPostgres {
return &UserPostgres{db: db}
}
func (r *UserPostgres) CreateUser(user internal.User) (int, error) {
var id int
query := fmt.Sprintf("INSERT INTO %s(name,username,password_hash,role) values ($1,$2,$3,$4) RETURNING id", usersTable)
row := r.db.QueryRow(query, user.Name, user.Username, user.Password, internal.Student)
if err := row.Scan(&id); err != nil {
return 0, err
}
return id, nil
}
func (r *UserPostgres) GetUser(username, password string) (internal.User, error) {
var user internal.User
query := fmt.Sprintf("SELECT * from %s where username = $1 AND password_hash=$2", usersTable)
row := r.db.QueryRow(query, username, password)
err := row.Scan(&user.Id, &user.Name, &user.Username, &user.Password, &user.UserRole)
return user, err
}
func (r *UserPostgres) UpdateUserRole(username string, userrole internal.UserRole) (string, error) {
query := fmt.Sprintf("UPDATE %s SET role = $1 WHERE username = $2 RETURNING role", usersTable)
var newRole string
err := r.db.QueryRow(query, userrole, username).Scan(&newRole)
if err != nil {
return "", fmt.Errorf("failed to update user role: %v", err)
}
return newRole, nil
}

142
internal/service/auth.go Normal file
View File

@@ -0,0 +1,142 @@
package service
import (
"authorization/internal"
"authorization/internal/config"
"crypto/sha1"
"errors"
"fmt"
"time"
"github.com/golang-jwt/jwt"
)
const (
refresh = "refresh"
access = "access"
)
type tokenClaims struct {
jwt.StandardClaims
UserId int `json:"user_id"`
UserRole string `json:"user_role"`
TokenType string `json:"token_type"`
}
type AuthService struct {
userService UserService
tokenConfigs config.TokenConfig
}
func newAuthService(userService UserService) *AuthService {
return &AuthService{userService: userService}
}
func (s *AuthService) CreateUser(user internal.User) (int, error) {
user.Password = s.generatePasswordHash(user.Password)
return s.userService.CreateUser(user)
}
func (s *AuthService) GenerateToken(username string, password string) (string, string, error) {
user, err := s.userService.GetUser(username, s.generatePasswordHash(password))
if err != nil {
return "", "", err
}
accessTokenClaims := s.generateClaims(user.Id, user.UserRole, access)
refreshTokenClaims := s.generateClaims(user.Id, user.UserRole, refresh)
accessToken, err := accessTokenClaims.SignedString([]byte(s.tokenConfigs.AccessToken.GetSecretBytes()))
if err != nil {
return "", "", err
}
refreshToken, err := refreshTokenClaims.SignedString([]byte(s.tokenConfigs.RefreshToken.GetSecretBytes()))
if err != nil {
return "", "", err
}
return accessToken, refreshToken, nil
}
func (s *AuthService) ChangeUserRole(username string, userrole string) (string, error) {
user, err := s.userService.ChangeUserRole(username, userrole)
if err != nil {
return "", err
}
return user, nil
}
func (s *AuthService) generateClaims(userId int, userRole string, tokenType string) *jwt.Token {
tokenTTL := s.tokenConfigs.RefreshToken.GetTTL()
if tokenType == access {
tokenTTL = s.tokenConfigs.AccessToken.GetTTL()
}
return jwt.NewWithClaims(jwt.SigningMethodHS256, &tokenClaims{
jwt.StandardClaims{
ExpiresAt: time.Now().Add(tokenTTL).Unix(),
IssuedAt: time.Now().Unix(),
},
userId,
userRole,
tokenType,
})
}
func (s *AuthService) generatePasswordHash(password string) string {
hash := sha1.New()
hash.Write([]byte(password))
return fmt.Sprintf("%x", hash)
}
func (s *AuthService) ParseToken(accessToken string) (string, error) {
claims, err := s.parseTokenWithSecret(accessToken, s.tokenConfigs.AccessToken.GetSecretBytes())
if err != nil {
return "", err
}
return claims.UserRole, nil
}
func (s *AuthService) RefreshToken(refreshToken string) (string, string, error) {
// Парсим refresh token
claims, err := s.parseTokenWithSecret(refreshToken, s.tokenConfigs.RefreshToken.GetSecretBytes())
if err != nil {
return "", "", errors.New("invalid refresh token")
}
// Проверяем, что это именно refresh token
if claims.TokenType != refresh {
return "", "", errors.New("token is not a refresh token")
}
// Генерируем новую пару токенов
newAccessTokenClaims := s.generateClaims(claims.UserId, claims.UserRole, access)
newRefreshTokenClaims := s.generateClaims(claims.UserId, claims.UserRole, refresh)
newAccessToken, err := newAccessTokenClaims.SignedString([]byte(s.tokenConfigs.AccessToken.GetSecretBytes()))
if err != nil {
return "", "", err
}
newRefreshToken, err := newRefreshTokenClaims.SignedString([]byte(s.tokenConfigs.RefreshToken.GetSecretBytes()))
if err != nil {
return "", "", err
}
return newAccessToken, newRefreshToken, nil
}
// parseTokenWithSecret - общий метод для парсинга токена с заданным секретным ключом
func (s *AuthService) parseTokenWithSecret(tokenString string, secret []byte) (*tokenClaims, error) {
token, err := jwt.ParseWithClaims(tokenString, &tokenClaims{}, func(t *jwt.Token) (interface{}, error) {
return secret, nil
})
if err != nil {
return nil, err
}
claims, ok := token.Claims.(*tokenClaims)
if !ok {
return nil, errors.New("invalid token claims")
}
return claims, nil
}

View File

@@ -0,0 +1,117 @@
package service
import (
"authorization/internal/config"
"testing"
"time"
"github.com/golang-jwt/jwt"
"github.com/stretchr/testify/assert"
)
func TestAuthService_ParseToken(t *testing.T) {
// Настройка тестового сервиса
service := &AuthService{
tokenConfigs: config.TokenConfig{
AccessToken: config.TokenSettings{
TTLInMinutes: 15,
SecretWord: "test-secret-key",
},
},
}
t.Run("Valid token", func(t *testing.T) {
// Создаем валидный токен
claims := &tokenClaims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
IssuedAt: time.Now().Unix(),
},
UserId: 1,
UserRole: "admin",
TokenType: "access",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte("test-secret-key"))
assert.NoError(t, err)
// Парсим токен
role, err := service.ParseToken(tokenString)
// Проверяем результат
assert.NoError(t, err)
assert.Equal(t, "admin", role)
})
t.Run("Expired token", func(t *testing.T) {
// Создаем истекший токен (истек 1 час назад)
claims := &tokenClaims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(-1 * time.Hour).Unix(),
IssuedAt: time.Now().Add(-2 * time.Hour).Unix(),
},
UserId: 1,
UserRole: "admin",
TokenType: "access",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte("test-secret-key"))
assert.NoError(t, err)
// Парсим истекший токен
role, err := service.ParseToken(tokenString)
// Проверяем, что получили ошибку
assert.Error(t, err)
assert.Empty(t, role)
// Проверяем, что это именно ошибка истечения срока
if ve, ok := err.(*jwt.ValidationError); ok {
assert.True(t, ve.Errors&jwt.ValidationErrorExpired != 0, "Expected token expired error")
}
})
t.Run("Invalid signature", func(t *testing.T) {
// Создаем токен с другим секретным ключом
claims := &tokenClaims{
StandardClaims: jwt.StandardClaims{
ExpiresAt: time.Now().Add(15 * time.Minute).Unix(),
IssuedAt: time.Now().Unix(),
},
UserId: 1,
UserRole: "admin",
TokenType: "access",
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
tokenString, err := token.SignedString([]byte("wrong-secret-key"))
assert.NoError(t, err)
// Пытаемся парсить токен с неправильной подписью
role, err := service.ParseToken(tokenString)
// Проверяем, что получили ошибку
assert.Error(t, err)
assert.Empty(t, role)
})
t.Run("Malformed token", func(t *testing.T) {
// Пытаемся парсить невалидный токен
role, err := service.ParseToken("invalid.token.string")
// Проверяем, что получили ошибку
assert.Error(t, err)
assert.Empty(t, role)
})
t.Run("Empty token", func(t *testing.T) {
// Пытаемся парсить пустой токен
role, err := service.ParseToken("")
// Проверяем, что получили ошибку
assert.Error(t, err)
assert.Empty(t, role)
})
}

View File

@@ -0,0 +1,29 @@
package service
import (
"authorization/internal"
"authorization/internal/repository"
)
type Authorization interface {
CreateUser(internal.User) (int, error)
GenerateToken(username string, password string) (accessToken string, refreshToken string, err error)
ParseToken(token string) (string, error)
RefreshToken(refreshToken string) (accessToken string, newRefreshToken string, err error)
ChangeUserRole(username string, newRole string) (string, error)
}
type Service struct {
Authorization
}
func NewServices(repository *repository.Repository) *Service {
userService := newUserService(repository)
authService := newAuthService(userService)
return &Service{
Authorization: authService,
}
}

44
internal/service/user.go Normal file
View File

@@ -0,0 +1,44 @@
package service
import (
"authorization/internal"
"authorization/internal/repository"
)
type UserService interface {
CreateUser(internal.User) (int, error)
ChangeUserRole(username string, Role string) (string, error)
GetUser(username string, hashedPassword string) (*internal.User, error)
}
type UserServiceImpl struct {
repo repository.UserResository
}
func newUserService(repo repository.UserResository) *UserServiceImpl {
return &UserServiceImpl{repo: repo}
}
func (s *UserServiceImpl) CreateUser(user internal.User) (int, error) {
return s.repo.CreateUser(user)
}
func (s *UserServiceImpl) ChangeUserRole(username string, userRole string) (string, error) {
newRole, err := internal.FromString(userRole)
if err != nil {
return "", err
}
user, err := s.repo.UpdateUserRole(username, newRole)
if err != nil {
return "", err
}
return user, nil
}
func (s *UserServiceImpl) GetUser(username string, hashedPassword string) (*internal.User, error) {
user, err := s.repo.GetUser(username, hashedPassword)
if err != nil {
return nil, err
}
return &user, nil
}

5
run.ps1 Normal file
View File

@@ -0,0 +1,5 @@
docker compose down
docker compose up -d
go clean
go build cmd/main.go
./main.exe

View File

@@ -0,0 +1 @@
drop table if exists USERS

View File

@@ -0,0 +1,6 @@
CREATE TABLE USERS(
id serial not null unique,
name varchar(255) not null,
username varchar(255) unique not null,
password_hash varchar(255) not null
)

View File

@@ -0,0 +1,2 @@
ALTER TABLE USERS
drop COLUMN role

View File

@@ -0,0 +1,2 @@
ALTER TABLE USERS
add role varchar(30)