Initial commit

This commit is contained in:
Arnaud (Arhuman) ASSAD 2024-05-10 04:24:08 +02:00
parent 01859cf8f5
commit 19d9ab1391
12 changed files with 679 additions and 2 deletions

23
auth/auth.go Normal file
View File

@ -0,0 +1,23 @@
package auth
import (
link "git.doolta.com/doolta/kit/pkg/link"
)
var validURL = "https://api.mailcape.com"
var linkService link.Service
// LoginResponse is the struct holding JWT token information
type LoginResponse struct {
UserID int64 `json:"user_id"`
Email string `json:"email"`
IsAdmin bool `json:"is_admin"`
Token string `json:"token"`
User string `json:"user"`
}
type LoginData struct {
Email string `json:"email"`
Password string `json:"password"`
}

170
auth/service.go Normal file
View File

@ -0,0 +1,170 @@
package auth
import (
"encoding/json"
"fmt"
"intranet/internal/dependencies"
"intranet/internal/models"
"intranet/pkg/token"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
jwt "github.com/golang-jwt/jwt/v5"
"golang.org/x/crypto/bcrypt"
)
// Service for auth (Interface AND implementation)
// All the business logic should be here
type Service interface {
// FindByEmail(id string) ([]*models.Auth, error)
GetLoginHandler() func(http.ResponseWriter, *http.Request)
GetSignupHandler() func(http.ResponseWriter, *http.Request)
GetAdminRoleRequiredMiddleware() gin.HandlerFunc
}
// AuthService godoc
type AuthServiceImplementation struct {
DB dependencies.DB
Log dependencies.Logger
}
// NewAuthService returns a service to manipulate auth
func NewAuthService(db dependencies.DB, log dependencies.Logger) *AuthServiceImplementation {
return &AuthServiceImplementation{db, log}
}
// GetLoginHandler returns an handled for the login route
func (s *AuthServiceImplementation) GetLoginHandler() http.HandlerFunc {
log := s.Log
db := s.DB
log.Info("[LoginHandler] Installed")
//@return a http.handlerFunc that will check for loginDataand return a JWT token if login is successful
return func(w http.ResponseWriter, r *http.Request) {
log.Info("[LoginHandler] Start")
loginData := LoginData{}
// Decode the incoming note json
err := json.NewDecoder(r.Body).Decode(&loginData)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
email := loginData.Email
password := loginData.Password
var u models.Account
db.Where("email = ?", email).First(&u)
if (u == models.Account{}) {
http.Error(w, "Invalid login/pass", http.StatusUnauthorized)
return
}
// Check password
log.Debug("Before check")
err = bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
log.Debug("After check (%s)", err)
if err != nil {
log.Info(err)
http.Error(w, "Invalid login/pass", http.StatusUnauthorized)
return
}
// Return JWT if password valid
// TODO: add role from database (not hardcoded)
tokenString, err := token.CreateToken(strconv.FormatInt(int64(u.ID), 9), token.TokenSecret, "admin")
if err != nil {
log.Info(err)
http.Error(w, "Unable to create token", http.StatusUnauthorized)
return
}
log.Debug("Token generated " + tokenString)
expire := time.Now().AddDate(-1, 0, 1)
cookie := http.Cookie{Name: "token", Value: tokenString, Expires: expire}
http.SetCookie(w, &cookie)
j, err := json.Marshal(LoginResponse{UserID: u.ID, Email: u.Email, IsAdmin: true, Token: tokenString, User: u.Email})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
w.Write(j)
}
}
// GetSignupHandler returns a handler for the signup route
func (s *AuthServiceImplementation) GetSignupHandler() http.HandlerFunc {
log := s.Log
log.Info("[SignupHandler] Installed")
return func(w http.ResponseWriter, r *http.Request) {
email := r.FormValue("email")
password := r.FormValue("password")
log.Infof("In signup handler %s", email)
if email == "" {
http.Error(w, "Email missing", http.StatusInternalServerError)
return
}
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 13)
if err != nil {
log.Error("Can't generate hash for password: " + err.Error())
http.Error(w, "Can't generate hash for password", http.StatusInternalServerError)
return
}
u := models.Account{Email: email, Password: string(bytes), Status: "pending"}
u, err = s.createUser(&u)
if err != nil {
log.Infof("Could'nt create Account = (%+v)", u)
log.Error(err)
http.Error(w, "Can't create account in database", http.StatusInternalServerError)
return
}
http.Redirect(w, r, "/", http.StatusMovedPermanently)
}
}
// createUser is a method on AuthServiceImplementation that create an user
// copying the part in GetSignupHandler that create a user in database
func (s *AuthServiceImplementation) createUser(u *models.Account) (models.Account, error) {
db := s.DB
db.Create(u)
if u.ID == 0 {
return *u, fmt.Errorf("Can't create account in database")
}
return *u, nil
}
// GetAdminRoleRequiredMiddleware returns a middleware that checks if the user is an admin
func (s *AuthServiceImplementation) GetAdminRoleRequiredMiddleware() http.HandlerFunc {
log := s.Log
log.Info("Installing RoleMiddleware")
return func(next http.HandlerFunc) http.HandlerFunc {
log.Info("[RoleMiddleware]")
claims, OK := c.Get("claims")
if !OK {
http.Error(w, "No claims found", http.StatusForbidden)
return
}
claimsMap := claims.(jwt.MapClaims)
log.Infof("[RoleMiddleware] claimsMap[role] = (%s)", claimsMap["Role"])
log.Infof("[RoleMiddleware] claims = (%+v)", claims)
if claimsMap["Role"] != "admin" {
http.Error(w, "Not an admin", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
}
}

14
go.mod
View File

@ -3,10 +3,15 @@ module git.doolta.com/doolta/go-kit
go 1.22.1
require (
git.doolta.com/doolta/kit v1.2.3
github.com/epfl-si/go-toolbox v0.6.15
github.com/gin-gonic/gin v1.9.1
github.com/golang-jwt/jwt/v5 v5.2.1
go.uber.org/zap v1.27.0
golang.org/x/crypto v0.19.0
gorm.io/driver/mysql v1.5.4
gorm.io/driver/postgres v1.5.7
gorm.io/gorm v1.25.10
)
require (
@ -18,7 +23,14 @@ require (
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.18.0 // indirect
github.com/go-sql-driver/mysql v1.7.1 // indirect
github.com/goccy/go-json v0.10.2 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
github.com/jackc/pgx/v5 v5.4.3 // indirect
github.com/jinzhu/gorm v1.9.16 // indirect
github.com/jinzhu/inflection v1.0.0 // indirect
github.com/jinzhu/now v1.1.5 // 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
@ -26,11 +38,11 @@ require (
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.1.1 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.7.0 // indirect
golang.org/x/crypto v0.19.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.17.0 // indirect
golang.org/x/text v0.14.0 // indirect

58
go.sum
View File

@ -1,3 +1,7 @@
git.doolta.com/doolta/kit v1.2.3 h1:xJ+LbWDopzVEyCwEI+gUh0RtSF4wvg6CSgadvH1SX/4=
git.doolta.com/doolta/kit v1.2.3/go.mod h1:JU4jwlc7qrNZgzlKupVTd7iYIqO1odGJA3zlPxkPUQ8=
github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM=
github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM=
github.com/bytedance/sonic v1.11.0 h1:FwNNv6Vu4z2Onf1++LNzxB/QhitD8wuTdpZzMTGITWo=
@ -12,8 +16,12 @@ github.com/chenzhuoyu/iasm v0.9.1/go.mod h1:Xjy2NpN3h7aUqeqM+woSuuvxmIe6+DDsiNLI
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM=
github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/epfl-si/go-toolbox v0.6.15 h1:Bo9t4MAVr7LN6mV/BGitZAVytbMkLfPpIwSsVpva8T8=
github.com/epfl-si/go-toolbox v0.6.15/go.mod h1:q48G3oNV2nWDTMBHfcG7qiUV8kAkLHc8AmndJZQlRIc=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y=
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
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=
@ -28,23 +36,50 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.18.0 h1:BvolUXjp4zuvkZ5YN5t7ebzbhlUtPsPm2S9NAZ5nl9U=
github.com/go-playground/validator/v10 v10.18.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI=
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
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/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgx/v5 v5.4.3 h1:cxFyXhxlvAifxnkKKdlxv8XqUf59tDlYjnV5YYfsJJY=
github.com/jackc/pgx/v5 v5.4.3/go.mod h1:Ig06C2Vu0t5qXC60W8sqIthScaEnFvojjj9dSljmHRA=
github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o=
github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs=
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
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=
github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/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.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4=
github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
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/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA=
github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus=
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=
@ -54,6 +89,8 @@ github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOS
github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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=
@ -77,24 +114,43 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8=
golang.org/x/arch v0.7.0 h1:pskyeJh/3AmoQ8CPE95vxHLqp1G1GfGNXTmcl9NEKTc=
golang.org/x/arch v0.7.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gorm.io/driver/mysql v1.5.4 h1:igQmHfKcbaTVyAIHNhhB888vvxh8EdQ2uSUT0LPcBso=
gorm.io/driver/mysql v1.5.4/go.mod h1:9rYxJph/u9SWkWc9yY4XJ1F/+xO0S/ChOmbk3+Z5Tvs=
gorm.io/driver/postgres v1.5.7 h1:8ptbNJTDbEmhdr62uReG5BGkdQyeasu/FZHxI0IMGnM=
gorm.io/driver/postgres v1.5.7/go.mod h1:3e019WlBaYI5o5LIdNV+LyxCMNtLOQETBXL2h4chKpA=
gorm.io/gorm v1.25.7-0.20240204074919-46816ad31dde/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
gorm.io/gorm v1.25.10 h1:dQpO+33KalOA+aFYGlK+EfxcI5MbO7EP2yYygwh9h+s=
gorm.io/gorm v1.25.10/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4=

61
gormdb/mysql.go Normal file
View File

@ -0,0 +1,61 @@
package gormdb
import (
"fmt"
"os"
"time"
"go.uber.org/zap"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// GetGormDB returns a Gorm database connection.
//
// Parameters:
// - log *zap.Logger: a logger instance
// - host string: the database host
// - name string: the database name
// - user string: the database user
// - pass string: the database password
// - port string: the database port
// - param string: the specific database parameters
// - maxIdle int: the maximum number of idle connections
// - maxOpen int: the maximum number of open connections
//
// Return type(s):
// - *gorm.DB: the Gorm database connection
// - error: an error, if any, encountered during the connection
func GetMySQLDB(log *zap.Logger, host, name, user, pass, port, param string, maxIdle int, maxOpen int) (*gorm.DB, error) {
//log.Infof("[GetGormDB] Connecting to 'database' %s on host %s as user '%s' (%s)", name, host, user, param)
logLevel := logger.Silent
if os.Getenv("LOG_LEVEL") == "info" {
logLevel = logger.Info
}
var dsn string
if param != "" {
dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?%s", user, pass, host, port, name, param)
} else {
dsn = fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", user, pass, host, port, name)
}
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logLevel),
})
if err != nil {
log.Error(fmt.Sprintf("GetGormDB:%s", err))
return db, err
}
log.Info(fmt.Sprintf("GetGormDB:successfully connected on host '%s' to database '%s' as user '%s' (%s)", host, name, user, param))
sqlDB, err := db.DB()
sqlDB.SetMaxIdleConns(maxIdle)
sqlDB.SetMaxOpenConns(maxOpen)
sqlDB.SetConnMaxLifetime(time.Hour)
sqlDB.SetConnMaxIdleTime(2 * time.Minute)
return db, nil
}

27
gormdb/postgresql.go Normal file
View File

@ -0,0 +1,27 @@
package gormdb
import (
"fmt"
"go.uber.org/zap"
"gorm.io/driver/postgres"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
// GetGormDB initializes a connection to the database and returns a handle
func GetPostgresqlDB(log *zap.Logger, host, name, user, pass, port string) (*gorm.DB, error) {
log.Info(fmt.Sprintf("[GetPostgresqlDB] Connecting to 'database' %s on host %s as user '%s'", name, host, user))
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", host, port, user, pass, name)
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info),
})
if err != nil {
log.Error(fmt.Sprintf("[GetPostgresqlDB] err=%s\n", err))
return db, err
}
log.Info(fmt.Sprintf("[GetPostgresqlDB] Successfully connected on host '%s' to database '%s' as user '%s'", host, name, user))
return db, nil
}

57
log/log.go Normal file
View File

@ -0,0 +1,57 @@
package log
import (
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
)
func GetLoggerConfig(logLevel string, stdouts []string, stderrs []string, encoding string) zap.Config {
level := zap.InfoLevel
if logLevel == "debug" {
level = zap.DebugLevel
}
if logLevel == "error" {
level = zap.ErrorLevel
}
if logLevel == "warn" {
level = zap.WarnLevel
}
if logLevel == "fatal" {
level = zap.FatalLevel
}
// Get a new logger
encoderCfg := zap.NewProductionEncoderConfig()
encoderCfg.TimeKey = "timegenerated"
encoderCfg.LevelKey = "log.level"
encoderCfg.EncodeTime = zapcore.RFC3339TimeEncoder
encoderCfg.EncodeLevel = zapcore.CapitalLevelEncoder
config := zap.Config{
Level: zap.NewAtomicLevelAt(level),
Development: false,
DisableCaller: true,
DisableStacktrace: false,
Sampling: nil,
Encoding: encoding,
EncoderConfig: encoderCfg,
OutputPaths: stdouts,
ErrorOutputPaths: stderrs,
InitialFields: map[string]interface{}{
//"pid": os.Getpid(),
},
}
return config
}
// GetLogger returns a new logger with the specified log level.
//
// Parameters:
// - logLevel: a string representing the log level ("debug", "error", "warn", "fatal")
//
// Return type(s):
// - *zap.Logger: a pointer to the logger
func GetLogger(logLevel string) *zap.Logger {
return zap.Must(GetLoggerConfig(logLevel, []string{"stdout"}, []string{"stderr"}, "json").Build())
}

149
token/token.go Normal file
View File

@ -0,0 +1,149 @@
// Package token handles JWT tokens manipulation
package token
import (
"errors"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
jwt "github.com/golang-jwt/jwt/v5"
"go.uber.org/zap"
)
// Authenticater is the interface that wraps the Authenticate method
type Authenticater interface {
Authenticate(login, pass string) (CustomClaims, error)
}
// CustomClaims is the struct that represents the claims of a JWT token in EPFL context
type CustomClaims struct {
Sciper string `json:"sciper"`
jwt.RegisteredClaims
}
// Validate validates the claims of a JWT token
func (m CustomClaims) Validate() error {
if m.Sciper == "" {
return errors.New("sciper must be set")
}
return nil
}
// Token is the struct that represents a JWT token
type Token struct {
JWT *jwt.Token
}
// New creates a new JWT token
func New(claims CustomClaims) *Token {
jwt := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return &Token{JWT: jwt}
}
// Parse parses a JWT token
func Parse(tokenString string, secret []byte) (*Token, error) {
t, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"])
}
return secret, nil
})
if err != nil {
return nil, err
}
return &Token{t}, nil
}
// Sign signs a JWT token
func (t *Token) Sign(secret []byte) (string, error) {
return t.JWT.SignedString([]byte(secret))
}
// Claims returns the claims of a JWT token
func (t *Token) Claims() jwt.MapClaims {
return t.JWT.Claims.(jwt.MapClaims)
}
// Set sets a claim in a JWT token
func (t *Token) Set(key string, value interface{}) {
t.Claims()[key] = value
}
// Get gets a claim from a JWT token
func (t *Token) Get(key string) interface{} {
return t.Claims()[key]
}
// GetString gets a claim from a JWT token as a string
func (t *Token) GetString(key string) string {
return t.Claims()[key].(string)
}
// ToJSON converts a JWT token to JSON
func (t *Token) ToJSON() (string, error) {
return t.JWT.Raw, nil
}
// PostLoginHandler is the handler that checks the login and password and returns a JWT token
func PostLoginHandler(log *zap.Logger, auth Authenticater, secret []byte) gin.HandlerFunc {
log.Info("Creating login handler")
return func(c *gin.Context) {
login := c.PostForm("login")
pass := c.PostForm("pass")
log.Info("Login attempt", zap.String("login", login))
claims, err := auth.Authenticate(login, pass)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
return
}
t := New(claims)
encoded, err := t.Sign(secret)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"access_token": encoded})
}
}
// GinMiddleware is the middleware that checks the JWT token
func GinMiddleware(secret []byte) gin.HandlerFunc {
return func(c *gin.Context) {
authorizationHeaderString := c.GetHeader("Authorization")
if authorizationHeaderString == "" {
c.JSON(http.StatusUnauthorized, gin.H{"error": "No token provided"})
c.Abort()
return
}
// Check that the authorization header starts with "Bearer"
if len(authorizationHeaderString) < 7 || authorizationHeaderString[:7] != "Bearer " {
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
c.Abort()
return
}
// Extract the token from the authorization header
tokenString := authorizationHeaderString[7:]
t, err := Parse(tokenString, secret)
if err != nil {
c.JSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
c.Abort()
return
}
c.Set("token", t)
c.Next()
}
}

View File

@ -0,0 +1,37 @@
package token_test
import (
"fmt"
"github.com/epfl-si/go-toolbox/token"
)
func Example_creating() {
t := token.New(token.CustomClaims{Sciper: "321014"})
encoded, err := t.Sign([]byte("secret"))
if err != nil {
fmt.Println(err)
return
}
fmt.Println(encoded)
// Output: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY2lwZXIiOiIzMjEwMTQifQ.7Nf7BUmLmN2RGXwf2nr-cOwkcsCkWO2i6YgLZdItrek
}
func Example_decoding() {
tokenString := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY2lwZXIiOiIzMjEwMTQifQ.7Nf7BUmLmN2RGXwf2nr-cOwkcsCkWO2i6YgLZdItrek"
decoded, err := token.Parse(tokenString, []byte("secret"))
if err != nil {
fmt.Println(err)
return
}
fmt.Println(decoded.JWT.Raw)
fmt.Println(decoded.JWT.Header["alg"])
fmt.Println(decoded.JWT.Header["typ"])
// fmt.Printf("%+v", decoded.JWT)
// Output:
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzY2lwZXIiOiIzMjEwMTQifQ.7Nf7BUmLmN2RGXwf2nr-cOwkcsCkWO2i6YgLZdItrek
// HS256
// JWT
}

48
web/http_servemux.go Normal file
View File

@ -0,0 +1,48 @@
// Package web provides HTTP server related functions and structures.
package web
import (
"context"
"net/http"
"go.uber.org/zap"
)
// NewHTTPServer takes a database connection and a logger as parameters
// It returns a pointer to a Server struct
func NewHTTPServer(log *zap.Logger) *Server {
router := http.NewServeMux()
services := make(map[string]interface{})
return &Server{
Log: log,
Router: router,
Services: services,
}
}
// Run starts the HTTP server on port passed as parameter (":12345").
func (s *Server) Run(port string) {
http.ListenAndServe(port, s.Router)
}
// AddRoute adds a new route to the server's router.
// Adding server resources in context
func (s *Server) AddRoute(pattern string, handlefunc http.HandlerFunc) {
s.Router.Handle(pattern, s.WithServerContext(handlefunc))
}
// WithServerContext adds the logger and services to the request context.
func (s *Server) WithServerContext(next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := context.WithValue(r.Context(), "log", s.Log)
ctx = context.WithValue(ctx, "services", s.Services)
next.ServeHTTP(w, r.WithContext(ctx))
}
}
func GetServerContext(r *http.Request) (*zap.Logger, map[string]interface{}) {
services := r.Context().Value("services").(map[string]interface{})
log := r.Context().Value("log").(*zap.Logger)
return log, services
}

17
web/routes.go Normal file
View File

@ -0,0 +1,17 @@
// Package web provides the web server for the application.
package web
import (
"fmt"
"net/http"
)
// Routes sets up the routes for the server.
func (s *Server) Routes() {
s.Log.Info("Health check handler installed")
s.AddRoute("GET /health", func(w http.ResponseWriter, r *http.Request) {
s.Log.Debug("Health check handler called")
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Hello, World!")
})
}

20
web/server.go Normal file
View File

@ -0,0 +1,20 @@
// Package web provides HTTP server related functions and structures.
package web
import (
"net/http"
"go.uber.org/zap"
)
// Server represents a HTTP server with logging and routing capabilities.
type Server struct {
Log *zap.Logger
Router *http.ServeMux
Services map[string]interface{}
}
// AddService adds a service to the server's service map.
func (s *Server) AddService(name string, service interface{}) {
s.Services[name] = service
}