286 lines
6.9 KiB
Go
286 lines
6.9 KiB
Go
package usecase
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"git.sch9.ru/new_gate/ms-auth/config"
|
|
"git.sch9.ru/new_gate/ms-auth/internal/models"
|
|
"git.sch9.ru/new_gate/ms-auth/internal/users"
|
|
"git.sch9.ru/new_gate/ms-auth/pkg"
|
|
"github.com/golang-jwt/jwt/v4"
|
|
"github.com/google/uuid"
|
|
"time"
|
|
)
|
|
|
|
type UseCase struct {
|
|
userRepo users.PgRepository
|
|
sessionRepo users.ValkeyRepository
|
|
cfg config.Config
|
|
}
|
|
|
|
func NewUseCase(
|
|
userRepo users.PgRepository,
|
|
sessionRepo users.ValkeyRepository,
|
|
cfg config.Config,
|
|
) *UseCase {
|
|
return &UseCase{
|
|
userRepo: userRepo,
|
|
sessionRepo: sessionRepo,
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
func (u *UseCase) CreateUser(ctx context.Context, username string, password string, role models.Role) (int32, error) {
|
|
const op = "UseCase.CreateUser"
|
|
|
|
meId, ok := ctx.Value("userId").(int32)
|
|
if !ok {
|
|
return 0, pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no user id in context")
|
|
}
|
|
|
|
me, err := u.userRepo.C().ReadUserById(ctx, meId)
|
|
if err != nil {
|
|
return 0, pkg.Wrap(nil, err, op, "can't read user by id")
|
|
}
|
|
|
|
if !me.Role.AtLeast(models.RoleModerator) || me.Role.AtMost(role) && !me.Role.IsAdmin() {
|
|
return 0, pkg.Wrap(pkg.NoPermission, nil, op, "no permission")
|
|
}
|
|
|
|
id, err := u.userRepo.C().CreateUser(ctx, username, password, role)
|
|
if err != nil {
|
|
return 0, pkg.Wrap(nil, err, op, "can't create user")
|
|
}
|
|
|
|
return id, nil
|
|
}
|
|
|
|
func (u *UseCase) ReadUserById(ctx context.Context, id int32) (*models.User, error) {
|
|
const op = "UseCase.ReadUserById"
|
|
|
|
user, err := u.userRepo.C().ReadUserById(ctx, id)
|
|
if err != nil {
|
|
return nil, pkg.Wrap(nil, err, op, "can't read user by id")
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func (u *UseCase) ReadUserByUsername(ctx context.Context, username string) (*models.User, error) {
|
|
const op = "UseCase.ReadUserByUsername"
|
|
|
|
user, err := u.userRepo.C().ReadUserByUsername(ctx, username)
|
|
if err != nil {
|
|
return nil, pkg.Wrap(nil, err, op, "can't read user by username")
|
|
}
|
|
return user, nil
|
|
}
|
|
|
|
func (u *UseCase) UpdateUser(ctx context.Context, id int32, username *string, role *models.Role) error {
|
|
const op = "UseCase.UpdateUser"
|
|
|
|
meId, ok := ctx.Value("userId").(int32)
|
|
if !ok {
|
|
return pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no user id in context")
|
|
}
|
|
|
|
me, err := u.userRepo.C().ReadUserById(ctx, meId)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, err, op, "can't read user by id")
|
|
}
|
|
|
|
user, err := u.userRepo.C().ReadUserById(ctx, id)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, err, op, "can't read user by id")
|
|
}
|
|
|
|
hasPermission := func() bool {
|
|
if me.Id == user.Id && role != nil {
|
|
return false
|
|
}
|
|
if me.Role.IsAdmin() {
|
|
return true
|
|
}
|
|
if role != nil && me.Role.AtMost(*role) {
|
|
return false
|
|
}
|
|
if !me.Role.AtMost(user.Role) {
|
|
return true
|
|
}
|
|
return false
|
|
}()
|
|
|
|
if !hasPermission {
|
|
return pkg.Wrap(pkg.NoPermission, nil, op, "no permission")
|
|
}
|
|
|
|
tx, err := u.userRepo.BeginTx(ctx)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, err, op, "cannot start transaction")
|
|
}
|
|
|
|
err = tx.UpdateUser(ctx, id, username, role)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, errors.Join(err, tx.Rollback()), op, "cannot update user")
|
|
}
|
|
err = u.sessionRepo.DeleteAllSessions(ctx, id)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, errors.Join(err, tx.Rollback()), op, "cannot delete all sessions")
|
|
}
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
return pkg.Wrap(nil, err, op, "cannot commit transaction")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *UseCase) DeleteUser(ctx context.Context, id int32) error {
|
|
const op = "UseCase.DeleteUser"
|
|
|
|
userId, ok := ctx.Value("userId").(int32)
|
|
if !ok {
|
|
return pkg.Wrap(pkg.ErrUnauthenticated, nil, op, "no user id in context")
|
|
}
|
|
|
|
me, err := u.ReadUserById(ctx, userId)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, err, op, "can't read user by id")
|
|
}
|
|
|
|
if me.Id == id || !me.Role.IsAdmin() {
|
|
return pkg.Wrap(pkg.NoPermission, nil, op, "no permission")
|
|
}
|
|
|
|
tx, err := u.userRepo.BeginTx(ctx)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, err, op, "cannot start transaction")
|
|
}
|
|
|
|
err = tx.DeleteUser(ctx, id)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, errors.Join(err, tx.Rollback()), op, "cannot delete user")
|
|
}
|
|
|
|
err = u.sessionRepo.DeleteAllSessions(ctx, id)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, errors.Join(err, tx.Rollback()), op, "cannot delete all sessions")
|
|
}
|
|
err = tx.Commit()
|
|
if err != nil {
|
|
return pkg.Wrap(nil, err, op, "cannot commit transaction")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (u *UseCase) CreateSession(ctx context.Context, userId int32, role models.Role) (string, error) {
|
|
const op = "UseCase.CreateSession"
|
|
|
|
sessionId, err := u.sessionRepo.CreateSession(ctx, userId, role)
|
|
if err != nil {
|
|
return "", pkg.Wrap(nil, err, op, "cannot create session")
|
|
}
|
|
|
|
return sessionId, nil
|
|
}
|
|
|
|
func (u *UseCase) ReadSession(ctx context.Context, sessionId string) (*models.Session, error) {
|
|
const op = "UseCase.ReadSession"
|
|
|
|
session, err := u.sessionRepo.ReadSession(ctx, sessionId)
|
|
if err != nil {
|
|
return nil, pkg.Wrap(nil, err, op, "cannot read session")
|
|
}
|
|
return session, nil
|
|
}
|
|
|
|
func (u *UseCase) UpdateSession(ctx context.Context, sessionId string) error {
|
|
const op = "UseCase.UpdateSession"
|
|
|
|
err := u.sessionRepo.UpdateSession(ctx, sessionId)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, err, op, "cannot update session")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *UseCase) DeleteSession(ctx context.Context, sessionId string) error {
|
|
const op = "UseCase.DeleteSession"
|
|
|
|
err := u.sessionRepo.DeleteSession(ctx, sessionId)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, err, op, "cannot delete session")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *UseCase) DeleteAllSessions(ctx context.Context, userId int32) error {
|
|
const op = "UseCase.DeleteAllSessions"
|
|
|
|
err := u.sessionRepo.DeleteAllSessions(ctx, userId)
|
|
if err != nil {
|
|
return pkg.Wrap(nil, err, op, "cannot delete all sessions")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type Token struct {
|
|
SessionId string `json:"sid"`
|
|
UserId int32 `json:"sub"`
|
|
Role models.Role `json:"rle"`
|
|
ExpiresAt time.Time `json:"exp"`
|
|
IssuedAt time.Time `json:"iat"`
|
|
NotBefore time.Time `json:"nbf"`
|
|
}
|
|
|
|
func (t Token) Valid() error {
|
|
if err := uuid.Validate(t.SessionId); err != nil {
|
|
return err
|
|
}
|
|
if t.UserId <= 0 {
|
|
return errors.New("invalid user id")
|
|
}
|
|
if t.Role <= 0 {
|
|
return errors.New("invalid role")
|
|
}
|
|
if t.ExpiresAt.IsZero() {
|
|
return errors.New("invalid exp")
|
|
}
|
|
if t.IssuedAt.IsZero() {
|
|
return errors.New("invalid iat")
|
|
}
|
|
if t.NotBefore.IsZero() {
|
|
return errors.New("invalid nbf")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (u *UseCase) Verify(ctx context.Context, sessionId string) (string, error) {
|
|
const op = "UseCase.Verify"
|
|
|
|
session, err := u.sessionRepo.ReadSession(ctx, sessionId)
|
|
if err != nil {
|
|
return "", pkg.Wrap(nil, err, op, "cannot read session")
|
|
}
|
|
|
|
token := jwt.NewWithClaims(
|
|
jwt.SigningMethodHS256,
|
|
Token{
|
|
SessionId: sessionId,
|
|
UserId: session.UserId,
|
|
Role: session.Role,
|
|
ExpiresAt: time.Now().Add(time.Hour * 24),
|
|
IssuedAt: time.Now(),
|
|
NotBefore: time.Now(),
|
|
},
|
|
)
|
|
|
|
signedToken, err := token.SignedString([]byte(u.cfg.JWTSecret))
|
|
if err != nil {
|
|
return "", pkg.Wrap(pkg.ErrInternal, err, op, "cannot sign token")
|
|
}
|
|
|
|
return signedToken, nil
|
|
}
|