feat(backend): analytics server initial setup

This commit is contained in:
Shekar Siri 2024-10-18 18:06:40 +02:00
parent c6a55b18a8
commit de87e1ad16
8 changed files with 125 additions and 0 deletions

View file

@ -2,6 +2,7 @@ package analytics
import (
"openreplay/backend/internal/config/analytics"
"openreplay/backend/pkg/common/api/auth"
"openreplay/backend/pkg/db/postgres/pool"
"openreplay/backend/pkg/flakeid"
"openreplay/backend/pkg/logger"
@ -12,6 +13,7 @@ import (
type ServicesBuilder struct {
Flaker *flakeid.Flaker
ObjStorage objectstorage.ObjectStorage
Auth auth.Auth
}
func NewServiceBuilder(log logger.Logger, cfg *analytics.Config, pgconn pool.Pool) (*ServicesBuilder, error) {
@ -23,5 +25,6 @@ func NewServiceBuilder(log logger.Logger, cfg *analytics.Config, pgconn pool.Poo
return &ServicesBuilder{
Flaker: flaker,
ObjStorage: objStore,
Auth: auth.NewAuth(log, cfg.JWTSecret, pgconn),
}, nil
}

View file

@ -0,0 +1 @@
package service

View file

@ -0,0 +1,51 @@
package auth
import (
"fmt"
"strings"
"github.com/golang-jwt/jwt/v5"
"openreplay/backend/pkg/db/postgres/pool"
"openreplay/backend/pkg/logger"
)
type Auth interface {
IsAuthorized(authHeader string, permissions []string, isExtension bool) (*User, error)
}
type authImpl struct {
log logger.Logger
secret string
pgconn pool.Pool
}
func NewAuth(log logger.Logger, jwtSecret string, conn pool.Pool) Auth {
return &authImpl{
log: log,
secret: jwtSecret,
pgconn: conn,
}
}
func parseJWT(authHeader, secret string) (*JWTClaims, error) {
if authHeader == "" {
return nil, fmt.Errorf("authorization header missing")
}
tokenParts := strings.Split(authHeader, "Bearer ")
if len(tokenParts) != 2 {
return nil, fmt.Errorf("invalid authorization header")
}
tokenString := tokenParts[1]
claims := &JWTClaims{}
token, err := jwt.ParseWithClaims(tokenString, claims,
func(token *jwt.Token) (interface{}, error) {
return []byte(secret), nil
})
if err != nil || !token.Valid {
fmt.Printf("token err: %v\n", err)
return nil, fmt.Errorf("invalid token")
}
return claims, nil
}

View file

@ -0,0 +1,10 @@
package auth
func (a *authImpl) IsAuthorized(authHeader string, permissions []string, isExtension bool) (*User, error) {
secret := a.secret
jwtInfo, err := parseJWT(authHeader, secret)
if err != nil {
return nil, err
}
return authUser(a.pgconn, jwtInfo.UserId, jwtInfo.TenantID, int(jwtInfo.IssuedAt.Unix()), isExtension)
}

View file

@ -0,0 +1,34 @@
package auth
import "github.com/golang-jwt/jwt/v5"
type JWTClaims struct {
UserId int `json:"userId"`
TenantID int `json:"tenantId"`
jwt.RegisteredClaims
}
type User struct {
ID uint64 `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
TenantID uint64 `json:"tenantId"`
JwtIat int `json:"jwtIat"`
Permissions map[string]bool `json:"permissions"`
AuthMethod string
}
func (u *User) HasPermission(perm string) bool {
if u.Permissions == nil {
return true // no permissions
}
_, ok := u.Permissions[perm]
return ok
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}

View file

@ -0,0 +1,22 @@
package auth
import (
"fmt"
"openreplay/backend/pkg/db/postgres/pool"
)
func authUser(conn pool.Pool, userID, tenantID, jwtIAT int, isExtension bool) (*User, error) {
sql := `
SELECT user_id, name, email
FROM public.users
WHERE user_id = $1 AND deleted_at IS NULL
LIMIT 1;`
user := &User{TenantID: 1, AuthMethod: "jwt"}
if err := conn.QueryRow(sql, userID).Scan(&user.ID, &user.Name, &user.Email, &user.JwtIat); err != nil {
return nil, fmt.Errorf("user not found")
}
if user.JwtIat == 0 || abs(jwtIAT-user.JwtIat) > 1 {
return nil, fmt.Errorf("token has been updated")
}
return user, nil
}

View file

@ -0,0 +1,3 @@
package service
var getUserSQL = `SELECT 1, name, email FROM public.users WHERE user_id = $1 AND deleted_at IS NULL LIMIT 1`

View file

@ -0,0 +1 @@
package common