renommage lassistanoque
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
package password
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/subtle"
|
||||
"encoding/base64"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/crypto/argon2"
|
||||
)
|
||||
|
||||
type params struct {
|
||||
memory uint32 // en KiB
|
||||
iterations uint32
|
||||
parallelism uint8
|
||||
saltLength uint32
|
||||
keyLength uint32
|
||||
}
|
||||
|
||||
var defaultParams = ¶ms{
|
||||
memory: 64 * 1024, // 64 MB
|
||||
iterations: 3,
|
||||
parallelism: 2,
|
||||
saltLength: 16,
|
||||
keyLength: 32,
|
||||
}
|
||||
|
||||
// generateSalt crée un salt aléatoire cryptographiquement sûr
|
||||
func generateSalt(length uint32) ([]byte, error) {
|
||||
salt := make([]byte, length)
|
||||
if _, err := rand.Read(salt); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return salt, nil
|
||||
}
|
||||
|
||||
// HashPassword génère un hash Argon2id encodé (salt + hash + paramètres inclus)
|
||||
func HashPassword(password string) (string, error) {
|
||||
salt, err := generateSalt(defaultParams.saltLength)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hash := argon2.IDKey(
|
||||
[]byte(password),
|
||||
salt,
|
||||
defaultParams.iterations,
|
||||
defaultParams.memory,
|
||||
defaultParams.parallelism,
|
||||
defaultParams.keyLength,
|
||||
)
|
||||
|
||||
// Format standard: $argon2id$v=19$m=65536,t=3,p=2$salt$hash
|
||||
b64Salt := base64.RawStdEncoding.EncodeToString(salt)
|
||||
b64Hash := base64.RawStdEncoding.EncodeToString(hash)
|
||||
|
||||
encoded := fmt.Sprintf(
|
||||
"$argon2id$v=%d$m=%d,t=%d,p=%d$%s$%s",
|
||||
argon2.Version,
|
||||
defaultParams.memory,
|
||||
defaultParams.iterations,
|
||||
defaultParams.parallelism,
|
||||
b64Salt,
|
||||
b64Hash,
|
||||
)
|
||||
|
||||
return encoded, nil
|
||||
}
|
||||
|
||||
// VerifyPassword compare un mot de passe en clair avec un hash stocké
|
||||
func VerifyPassword(password, encodedHash string) (bool, error) {
|
||||
p, salt, hash, err := decodeHash(encodedHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
otherHash := argon2.IDKey(
|
||||
[]byte(password),
|
||||
salt,
|
||||
p.iterations,
|
||||
p.memory,
|
||||
p.parallelism,
|
||||
p.keyLength,
|
||||
)
|
||||
|
||||
// Comparaison en temps constant pour éviter les timing attacks
|
||||
if subtle.ConstantTimeCompare(hash, otherHash) == 1 {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// decodeHash parse la chaîne encodée pour en extraire params, salt et hash
|
||||
func decodeHash(encodedHash string) (*params, []byte, []byte, error) {
|
||||
parts := strings.Split(encodedHash, "$")
|
||||
if len(parts) != 6 {
|
||||
return nil, nil, nil, errors.New("format de hash invalide")
|
||||
}
|
||||
|
||||
var version int
|
||||
_, err := fmt.Sscanf(parts[2], "v=%d", &version)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if version != argon2.Version {
|
||||
return nil, nil, nil, errors.New("version argon2 incompatible")
|
||||
}
|
||||
|
||||
p := ¶ms{}
|
||||
_, err = fmt.Sscanf(parts[3], "m=%d,t=%d,p=%d", &p.memory, &p.iterations, &p.parallelism)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
salt, err := base64.RawStdEncoding.DecodeString(parts[4])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
p.saltLength = uint32(len(salt))
|
||||
|
||||
hash, err := base64.RawStdEncoding.DecodeString(parts[5])
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
p.keyLength = uint32(len(hash))
|
||||
|
||||
return p, salt, hash, nil
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package password
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||
"trankilou.fr/lassistanoque/backend/internal/service/auth"
|
||||
)
|
||||
|
||||
type PasswordAuthenticator struct {
|
||||
tokenManager auth.TokenManager
|
||||
userRepository domain.UserRepository
|
||||
}
|
||||
|
||||
func NewPasswordAuthenticator(
|
||||
tokenManager auth.TokenManager,
|
||||
userRepository domain.UserRepository,
|
||||
) *PasswordAuthenticator {
|
||||
return &PasswordAuthenticator{
|
||||
tokenManager,
|
||||
userRepository,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PasswordAuthenticator) Authenticate(ctx context.Context, creds auth.Credentials) (*auth.Session, error) {
|
||||
|
||||
user, err := s.userRepository.FindUserByEmail(creds.Email)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unauthorized user %s : %s", creds.Email, err)
|
||||
}
|
||||
|
||||
ok, err := VerifyPassword(creds.Password, user.Password)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Password don't match")
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Error verifying password: %s", err)
|
||||
}
|
||||
|
||||
accessToken, expiresAt, err := s.tokenManager.GenerateAccessToken(user)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to generate access token: %s", err)
|
||||
}
|
||||
refreshToken, _, err := s.tokenManager.GenerateRefreshToken(user.ID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Unable to generate refresh token: %s", err)
|
||||
}
|
||||
|
||||
return &auth.Session{
|
||||
User: user,
|
||||
AccessToken: accessToken,
|
||||
RefreshToken: refreshToken,
|
||||
ExpiresAt: expiresAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *PasswordAuthenticator) Register(ctx context.Context, registration auth.Registration) (*auth.Session, error) {
|
||||
|
||||
hashedPassord, err := HashPassword(registration.Password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := s.userRepository.CreateUser(&domain.User{
|
||||
Email: registration.Email,
|
||||
Firstname: registration.Firstname,
|
||||
Lastname: registration.Lastname,
|
||||
Password: hashedPassord,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = user
|
||||
return nil, nil
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package dberrors
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrNoRowUpdated = errors.New("No row was updated")
|
||||
)
|
||||
@@ -0,0 +1,26 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"trankilou.fr/lassistanoque/backend/internal/adapter/database/turso"
|
||||
"trankilou.fr/lassistanoque/backend/internal/config"
|
||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||
)
|
||||
|
||||
type Database interface {
|
||||
SettingsReporitory() domain.SettingsRepository
|
||||
UserReporitory() domain.UserRepository
|
||||
Migrate() error
|
||||
Close()
|
||||
}
|
||||
|
||||
func GetDatabase() (Database, error) {
|
||||
cfg := config.GetConfig()
|
||||
switch cfg.DatabaseType {
|
||||
case "turso":
|
||||
return turso.NewTursoDB(cfg)
|
||||
default:
|
||||
}
|
||||
return nil, fmt.Errorf("Database %s not implemented", cfg.DatabaseType)
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
package turso
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"fmt"
|
||||
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
"github.com/golang-migrate/migrate/v4/database/sqlite3"
|
||||
"github.com/golang-migrate/migrate/v4/source/iofs"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"trankilou.fr/lassistanoque/backend/internal/config"
|
||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||
_ "turso.tech/database/tursogo"
|
||||
)
|
||||
|
||||
//go:embed migrations/*
|
||||
var FS embed.FS
|
||||
|
||||
type TursoDB struct {
|
||||
DB *sqlx.DB
|
||||
}
|
||||
|
||||
func NewTursoDB(cfg *config.Config) (*TursoDB, error) {
|
||||
db, err := sqlx.Connect("turso", cfg.DatabaseURL)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error connecting to db %: %s", cfg.DatabaseURL, err)
|
||||
}
|
||||
return &TursoDB{
|
||||
DB: db,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (db *TursoDB) UserReporitory() domain.UserRepository {
|
||||
return &TursoUserRepository{
|
||||
DB: db.DB,
|
||||
}
|
||||
}
|
||||
|
||||
func (db *TursoDB) SettingsReporitory() domain.SettingsRepository {
|
||||
return &TursoSettingsRepository{
|
||||
DB: db.DB,
|
||||
}
|
||||
}
|
||||
|
||||
func (db *TursoDB) Close() {
|
||||
db.DB.Close()
|
||||
}
|
||||
|
||||
func (db *TursoDB) Migrate() error {
|
||||
sqldb := db.DB.DB
|
||||
dbDriver, err := sqlite3.WithInstance(sqldb, &sqlite3.Config{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceInstance, err := iofs.New(FS, "migrations")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m, err := migrate.NewWithInstance("iofs", sourceInstance, "sqlite3", dbDriver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.Up()
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
drop table providers;
|
||||
drop table models;
|
||||
drop table system;
|
||||
drop table oidc;
|
||||
drop table users;
|
||||
drop table user_addresses;
|
||||
drop table channels;
|
||||
drop table tools;
|
||||
drop table tasks;
|
||||
drop table history;
|
||||
@@ -0,0 +1,95 @@
|
||||
create table providers (
|
||||
id text not null primary key,
|
||||
name text,
|
||||
key text,
|
||||
url text,
|
||||
_version text
|
||||
);
|
||||
|
||||
create table models (
|
||||
id text not null primary key,
|
||||
provider_id text,
|
||||
name text,
|
||||
modelname text,
|
||||
configuration text,
|
||||
_version text
|
||||
);
|
||||
|
||||
create table settings (
|
||||
chat_model_id text,
|
||||
default_lang text,
|
||||
register_enabled numeric,
|
||||
password_enabled numeric,
|
||||
_version text
|
||||
);
|
||||
|
||||
create table oidc (
|
||||
id string not null primary key,
|
||||
label string,
|
||||
domain string,
|
||||
client_id string,
|
||||
client_secret string,
|
||||
wellknown_url string,
|
||||
_version text
|
||||
);
|
||||
|
||||
create table users (
|
||||
id string not null primary key,
|
||||
firstname string not null default '',
|
||||
lastname string not null default '',
|
||||
password string not null default '',
|
||||
email string not null unique,
|
||||
picture string not null default '',
|
||||
enabled numeric default true,
|
||||
administrator numeric default false,
|
||||
_version text not null
|
||||
);
|
||||
|
||||
create table user_addresses (
|
||||
id string not null primary key,
|
||||
user_id string,
|
||||
type string,
|
||||
address string,
|
||||
_version text
|
||||
);
|
||||
|
||||
create table channels (
|
||||
id string not null primary key,
|
||||
name string,
|
||||
type string,
|
||||
enabled numeric,
|
||||
configuration string,
|
||||
_version text
|
||||
);
|
||||
|
||||
create table tools (
|
||||
id string not null primary key,
|
||||
name string,
|
||||
type string,
|
||||
enabled numeric,
|
||||
configuration string,
|
||||
_version text
|
||||
);
|
||||
|
||||
create table tasks (
|
||||
id string not null primary key,
|
||||
owner_id string,
|
||||
model_id string,
|
||||
label string,
|
||||
prompt string,
|
||||
cron string,
|
||||
status string,
|
||||
next_datetime numeric,
|
||||
_version text
|
||||
);
|
||||
|
||||
create table history (
|
||||
id string not null primary key,
|
||||
task_id string,
|
||||
start_datetimle numeric,
|
||||
end_datetime numeric,
|
||||
prompt string,
|
||||
log string,
|
||||
response string,
|
||||
_version text
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
delete from sytem;
|
||||
@@ -0,0 +1,2 @@
|
||||
insert into settings (default_lang, register_enabled, password_enabled, _version)
|
||||
values ('en', true, true, 'init');
|
||||
@@ -0,0 +1,45 @@
|
||||
package turso
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"trankilou.fr/lassistanoque/backend/internal/adapter/database/dberrors"
|
||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||
"trankilou.fr/lassistanoque/backend/internal/utility"
|
||||
)
|
||||
|
||||
type TursoSettingsRepository struct {
|
||||
DB *sqlx.DB
|
||||
}
|
||||
|
||||
func (sr *TursoSettingsRepository) GetSettings() (*domain.Settings, error) {
|
||||
var settings domain.Settings
|
||||
err := sr.DB.Get(&settings, "select * from settings limit 1")
|
||||
return &settings, err
|
||||
}
|
||||
|
||||
func (sr *TursoSettingsRepository) UpdateSettings(setting *domain.Settings) (*domain.Settings, error) {
|
||||
newVersion := utility.GenID()
|
||||
|
||||
res, err := sr.DB.Exec(
|
||||
`update settings
|
||||
set chat_model_id=$1,
|
||||
default_lang=$2,
|
||||
register_enabled=$3,
|
||||
password_enabled=$4,
|
||||
_version=:$5
|
||||
where _version=$6`,
|
||||
setting.ChatModelID, setting.DefaultLang, setting.RegisterEnabled,
|
||||
setting.PasswordEnabled, newVersion, setting.VersionId,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if count, _ := res.RowsAffected(); count == 0 {
|
||||
return nil, dberrors.ErrNoRowUpdated
|
||||
}
|
||||
|
||||
setting.VersionId = newVersion
|
||||
|
||||
return setting, nil
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
package turso
|
||||
|
||||
import (
|
||||
"github.com/jmoiron/sqlx"
|
||||
"trankilou.fr/lassistanoque/backend/internal/adapter/database/dberrors"
|
||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||
"trankilou.fr/lassistanoque/backend/internal/utility"
|
||||
)
|
||||
|
||||
type TursoUserRepository struct {
|
||||
DB *sqlx.DB
|
||||
}
|
||||
|
||||
func (ur *TursoUserRepository) FindUser(id string) (*domain.User, error) {
|
||||
var user domain.User
|
||||
err := ur.DB.Get(&user, "select * from users where id=$1", id)
|
||||
return &user, err
|
||||
}
|
||||
|
||||
func (ur *TursoUserRepository) FindUserByEmail(email string) (*domain.User, error) {
|
||||
var user domain.User
|
||||
err := ur.DB.Get(&user, "select * from users where email=$1", email)
|
||||
return &user, err
|
||||
}
|
||||
|
||||
func (ur *TursoUserRepository) CountUsers() (int, error) {
|
||||
var count int
|
||||
err := ur.DB.Get(&count, "select count(*) from users")
|
||||
return count, err
|
||||
}
|
||||
|
||||
func (ur *TursoUserRepository) ListUsers() ([]*domain.User, error) {
|
||||
var users []*domain.User
|
||||
err := ur.DB.Select(&users, "select * from users order by lastname, firstname")
|
||||
return users, err
|
||||
}
|
||||
|
||||
func (ur *TursoUserRepository) CreateUser(user *domain.User) (*domain.User, error) {
|
||||
|
||||
count, err := ur.CountUsers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if count == 0 {
|
||||
user.Administrator = true
|
||||
}
|
||||
user.ID = utility.GenID()
|
||||
user.VersionId = utility.GenID()
|
||||
|
||||
ur.DB.NamedExec(
|
||||
`insert into users (id, email, firstname, lastname, enabled, password, administrator, _version)
|
||||
values (:id, :email, :firstname, :lastname, :enabled, :password, :administrator, :_version)`,
|
||||
user)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ur *TursoUserRepository) UpdateUser(user *domain.User) (*domain.User, error) {
|
||||
|
||||
newVersion := utility.GenID()
|
||||
|
||||
res, err := ur.DB.Exec(
|
||||
`update users
|
||||
set email=$1,
|
||||
firstname=$2,
|
||||
lastname=$3,
|
||||
enabled=$4,
|
||||
administrator=$5
|
||||
_version=:$6
|
||||
where id=$7 and _version=$8`,
|
||||
user.Email, user.Firstname, user.Lastname,
|
||||
user.Enabled, user.Administrator,
|
||||
newVersion, user.ID, user.VersionId,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if count, _ := res.RowsAffected(); count == 0 {
|
||||
return nil, dberrors.ErrNoRowUpdated
|
||||
}
|
||||
|
||||
user.VersionId = newVersion
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (ur *TursoUserRepository) DeleteUser(id string) error {
|
||||
res, err := ur.DB.Exec("delete from users where id=$1", id)
|
||||
if count, _ := res.RowsAffected(); count == 0 {
|
||||
return dberrors.ErrNoRowUpdated
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,129 @@
|
||||
package security
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/labstack/echo/v5"
|
||||
"trankilou.fr/lassistanoque/backend/internal/config"
|
||||
"trankilou.fr/lassistanoque/backend/internal/domain"
|
||||
"trankilou.fr/lassistanoque/backend/internal/service/auth"
|
||||
)
|
||||
|
||||
// Claims personnalisées embarquées dans le JWT.
|
||||
type Claims struct {
|
||||
UserID string `json:"uid"`
|
||||
Email string `json:"email"`
|
||||
Firstname string `json:"firstname"`
|
||||
Lastname string `json:"lastname"`
|
||||
Administrator bool `json:"administrator"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type JwtTokenManager struct {
|
||||
secret []byte
|
||||
accessTTL time.Duration
|
||||
refreshTTL time.Duration
|
||||
issuer string
|
||||
}
|
||||
|
||||
func NewJwtTokenManager(accessTTL, refreshTTL time.Duration, issuer string) *JwtTokenManager {
|
||||
cfg := config.GetConfig()
|
||||
return &JwtTokenManager{
|
||||
secret: []byte(cfg.JWTSecret),
|
||||
accessTTL: accessTTL,
|
||||
refreshTTL: refreshTTL,
|
||||
issuer: issuer,
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateAccessToken crée un JWT de courte durée (ex: 15 min) utilisé
|
||||
// pour authentifier les requêtes API.
|
||||
func (tm *JwtTokenManager) GenerateAccessToken(user *domain.User) (string, time.Time, error) {
|
||||
expiresAt := time.Now().Add(tm.accessTTL)
|
||||
claims := Claims{
|
||||
UserID: user.ID,
|
||||
Email: user.Email,
|
||||
Firstname: user.Firstname,
|
||||
Lastname: user.Lastname,
|
||||
Administrator: user.Administrator,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
Issuer: tm.issuer,
|
||||
Subject: user.ID,
|
||||
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
},
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
signed, err := token.SignedString(tm.secret)
|
||||
return signed, expiresAt, err
|
||||
}
|
||||
|
||||
// GenerateRefreshToken crée un token de longue durée (ex: 7 jours) utilisé
|
||||
// uniquement pour obtenir un nouvel access token, jamais pour appeler l'API directement.
|
||||
func (tm *JwtTokenManager) GenerateRefreshToken(userID string) (string, time.Time, error) {
|
||||
expiresAt := time.Now().Add(tm.refreshTTL)
|
||||
claims := jwt.RegisteredClaims{
|
||||
Issuer: tm.issuer,
|
||||
Subject: userID,
|
||||
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||
IssuedAt: jwt.NewNumericDate(time.Now()),
|
||||
}
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
|
||||
signed, err := token.SignedString(tm.secret)
|
||||
return signed, expiresAt, err
|
||||
}
|
||||
|
||||
// ParseAndValidate décode et vérifie la signature + l'expiration d'un JWT.
|
||||
func (tm *JwtTokenManager) ParseAndValidate(tokenString string) (*domain.User, error) {
|
||||
claims := &Claims{}
|
||||
token, err := jwt.ParseWithClaims(tokenString, claims, func(t *jwt.Token) (interface{}, error) {
|
||||
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("méthode de signature inattendue")
|
||||
}
|
||||
return tm.secret, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !token.Valid {
|
||||
return nil, errors.New("token invalide")
|
||||
}
|
||||
return &domain.User{
|
||||
ID: claims.UserID,
|
||||
Email: claims.Email,
|
||||
Firstname: claims.Firstname,
|
||||
Lastname: claims.Lastname,
|
||||
Administrator: claims.Administrator,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (tm *JwtTokenManager) TokenMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c *echo.Context) error {
|
||||
header := c.Request().Header.Get("Authorization")
|
||||
if header == "" {
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, "en-tête Authorization manquant")
|
||||
}
|
||||
|
||||
parts := strings.SplitN(header, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, "format attendu: Bearer <token>")
|
||||
}
|
||||
|
||||
claims, err := tm.ParseAndValidate(parts[1])
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusUnauthorized, "token invalide ou expiré")
|
||||
}
|
||||
|
||||
// Rend les infos disponibles aux handlers suivants via c.Get(...)
|
||||
c.Set(auth.ContextUserIDKey, claims.ID)
|
||||
c.Set(auth.ContextEmailKey, claims.Email)
|
||||
c.Set(auth.ContextNameKey, claims.Firstname+" "+claims.Lastname)
|
||||
c.Set(auth.ContextAdminKey, claims.Administrator)
|
||||
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user