Initial commit
This commit is contained in:
commit
2fa110e760
28 changed files with 2346 additions and 0 deletions
35
internal/app/app.go
Normal file
35
internal/app/app.go
Normal file
|
@ -0,0 +1,35 @@
|
|||
package app
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"ms-auth/internal/lib"
|
||||
|
||||
_ "github.com/jackc/pgx/v5/stdlib"
|
||||
)
|
||||
|
||||
type Server interface {
|
||||
Start()
|
||||
GracefullyStop()
|
||||
}
|
||||
|
||||
type App struct {
|
||||
server Server
|
||||
cfg *lib.Config
|
||||
}
|
||||
|
||||
func NewApp(cfg *lib.Config, server Server) *App {
|
||||
return &App{
|
||||
server: server,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (app *App) Start() {
|
||||
app.server.Start()
|
||||
slog.Info("app started")
|
||||
}
|
||||
|
||||
func (app *App) GracefullyStop() {
|
||||
app.server.GracefullyStop()
|
||||
slog.Info("app stopped")
|
||||
}
|
27
internal/lib/config.go
Normal file
27
internal/lib/config.go
Normal file
|
@ -0,0 +1,27 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/ilyakaznacheev/cleanenv"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Env string `env:"ENV" env-default:"prod"`
|
||||
|
||||
PostgresDSN string `env:"POSTGRES_DSN" required:"true"`
|
||||
RedisDSN string `env:"REDIS_DSN" required:"true"`
|
||||
|
||||
Email string `env:"EMAIL" required:"true"`
|
||||
Password string `env:"PASSWORD" required:"true"`
|
||||
|
||||
JWTSecret string `env:"JWT_SECRET" required:"true"`
|
||||
}
|
||||
|
||||
func MustSetupConfig() *Config {
|
||||
var cfg Config
|
||||
err := cleanenv.ReadConfig(".env", &cfg)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("error reading config: %s", err.Error()))
|
||||
}
|
||||
return &cfg
|
||||
}
|
22
internal/lib/errors.go
Normal file
22
internal/lib/errors.go
Normal file
|
@ -0,0 +1,22 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInternal = errors.New("internal")
|
||||
ErrUnexpected = errors.New("unexpected")
|
||||
ErrNoPermission = errors.New("no permission")
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBadHandleOrPassword = errors.New("bad handle or password")
|
||||
ErrBadRole = errors.New("bad role")
|
||||
ErrTooShortPassword = errors.New("too short password")
|
||||
ErrTooLongPassword = errors.New("too long password")
|
||||
ErrBadEmail = errors.New("bad email")
|
||||
ErrBadUsername = errors.New("bad username")
|
||||
ErrTooShortUsername = errors.New("too short username")
|
||||
ErrTooLongUsername = errors.New("too long username")
|
||||
)
|
40
internal/lib/lib.go
Normal file
40
internal/lib/lib.go
Normal file
|
@ -0,0 +1,40 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
RoleSpectator int32 = 0
|
||||
RoleParticipant int32 = 1
|
||||
RoleModerator int32 = 2
|
||||
RoleAdmin int32 = 3
|
||||
)
|
||||
|
||||
func IsAdmin(role int32) bool {
|
||||
return role == RoleAdmin
|
||||
}
|
||||
|
||||
func IsModerator(role int32) bool {
|
||||
return role == RoleModerator
|
||||
}
|
||||
|
||||
func IsParticipant(role int32) bool {
|
||||
return role == RoleParticipant
|
||||
}
|
||||
|
||||
func IsSpectator(role int32) bool {
|
||||
return role == RoleSpectator
|
||||
}
|
||||
|
||||
func AsTimeP(t time.Time) *time.Time {
|
||||
return &t
|
||||
}
|
||||
|
||||
func AsInt32P(v int32) *int32 {
|
||||
return &v
|
||||
}
|
||||
|
||||
func AsStringP(str string) *string {
|
||||
return &str
|
||||
}
|
19
internal/lib/mail.go
Normal file
19
internal/lib/mail.go
Normal file
|
@ -0,0 +1,19 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/smtp"
|
||||
)
|
||||
|
||||
func SendMail(cfg Config, to []string, subject, body string) error {
|
||||
auth := smtp.PlainAuth("", cfg.Email, cfg.Password, "smtp.gmail.com")
|
||||
|
||||
msg := fmt.Sprintf("From: %s\nTo: %s\nSubject: %s\n%s", cfg.Email, "", subject, body)
|
||||
|
||||
err := smtp.SendMail("smtp.gmail.com:587", auth, cfg.Email, to, []byte(msg))
|
||||
if err != nil {
|
||||
return err // FIXME
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
44
internal/lib/validation.go
Normal file
44
internal/lib/validation.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package lib
|
||||
|
||||
import (
|
||||
"net/mail"
|
||||
)
|
||||
|
||||
func ValidPassword(str string) error {
|
||||
if len(str) < 5 {
|
||||
return ErrTooShortPassword
|
||||
}
|
||||
if len(str) > 70 {
|
||||
return ErrTooLongPassword
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidUsername(str string) error {
|
||||
if len(str) < 5 {
|
||||
return ErrTooShortUsername
|
||||
}
|
||||
if len(str) > 70 {
|
||||
return ErrTooLongUsername
|
||||
}
|
||||
if err := ValidEmail(str); err == nil {
|
||||
return ErrBadUsername
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidEmail(str string) error {
|
||||
emailAddress, err := mail.ParseAddress(str)
|
||||
if err != nil || emailAddress.Address != str {
|
||||
return ErrBadEmail
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidRole(role int32) error {
|
||||
switch role {
|
||||
case RoleSpectator, RoleParticipant, RoleModerator, RoleAdmin:
|
||||
return nil
|
||||
}
|
||||
return ErrBadRole
|
||||
}
|
1
internal/services/email.go
Normal file
1
internal/services/email.go
Normal file
|
@ -0,0 +1 @@
|
|||
package services
|
147
internal/services/session.go
Normal file
147
internal/services/session.go
Normal file
|
@ -0,0 +1,147 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"ms-auth/internal/lib"
|
||||
"ms-auth/internal/storage"
|
||||
)
|
||||
|
||||
type SessionProvider interface {
|
||||
CreateSession(ctx context.Context, userId int32) error
|
||||
ReadSessionByToken(ctx context.Context, token string) (*storage.Session, error)
|
||||
ReadSessionByUserId(ctx context.Context, userId int32) (*storage.Session, error)
|
||||
UpdateSession(ctx context.Context, session *storage.Session) error
|
||||
DeleteSessionByToken(ctx context.Context, token string) error
|
||||
DeleteSessionByUserId(ctx context.Context, userId int32) error
|
||||
}
|
||||
|
||||
// SessionService represents a service for managing sessions.
|
||||
type SessionService struct {
|
||||
sessionProvider SessionProvider
|
||||
userProvider UserProvider
|
||||
cfg *lib.Config
|
||||
}
|
||||
|
||||
// NewSessionService creates a new SessionService instance.
|
||||
//
|
||||
// Parameters:
|
||||
// - sessionProvider: The SessionProvider implementation used by the SessionService.
|
||||
// - userProvider: The UserProvider implementation used by the SessionService.
|
||||
// - cfg: The lib.Config object used by the SessionService.
|
||||
//
|
||||
// Returns:
|
||||
// - *SessionService: A pointer to the SessionService instance.
|
||||
func NewSessionService(sessionProvider SessionProvider, userProvider UserProvider, cfg *lib.Config) *SessionService {
|
||||
return &SessionService{
|
||||
sessionProvider: sessionProvider,
|
||||
userProvider: userProvider,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// Create creates a new session for a user with the given handle and password.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: The context.Context object for the request.
|
||||
// - handle: The handle (username or email) of the user.
|
||||
// - password: The password of the user.
|
||||
//
|
||||
// Returns:
|
||||
// - *string: A pointer to the token of the newly created session, or nil if there was an error.
|
||||
// - error: An error if the creation of the session or the retrieval of the session's token failed.
|
||||
func (s *SessionService) Create(ctx context.Context, handle, password string) (*string, error) {
|
||||
var (
|
||||
err error
|
||||
user *storage.User
|
||||
)
|
||||
|
||||
if lib.ValidUsername(handle) == nil {
|
||||
user, err = s.userProvider.ReadUserByUsername(ctx, handle)
|
||||
} else if lib.ValidEmail(handle) == nil {
|
||||
user, err = s.userProvider.ReadUserByEmail(ctx, handle)
|
||||
} else {
|
||||
return nil, lib.ErrBadHandleOrPassword
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = user.ComparePassword(password)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.sessionProvider.CreateSession(ctx, user.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
session, err := s.sessionProvider.ReadSessionByUserId(ctx, user.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
token, err := session.Token(s.cfg.JWTSecret)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &token, nil
|
||||
}
|
||||
|
||||
// Read retrieves the user ID associated with the given session token.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: The context.Context object for the request.
|
||||
// - token: The session token.
|
||||
//
|
||||
// Returns:
|
||||
// - *int32: The user ID associated with the session token, or nil if an error occurs.
|
||||
// - error: An error object if any error occurs during the retrieval process.
|
||||
func (s *SessionService) Read(ctx context.Context, token string) (*int32, error) {
|
||||
session, err := s.sessionProvider.ReadSessionByToken(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return session.UserId, nil
|
||||
}
|
||||
|
||||
// Update updates the session associated with the given token.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: The context.Context object for the request.
|
||||
// - token: The session token.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error object if any error occurs during the update process.
|
||||
func (s *SessionService) Update(ctx context.Context, token string) error {
|
||||
session, err := s.sessionProvider.ReadSessionByToken(ctx, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.sessionProvider.UpdateSession(ctx, session)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete deletes the session associated with the given token.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: The context.Context object for the request.
|
||||
// - token: The session token.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error object if any error occurs during the deletion process.
|
||||
func (s *SessionService) Delete(ctx context.Context, token string) error {
|
||||
session, err := s.sessionProvider.ReadSessionByToken(ctx, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = s.sessionProvider.DeleteSessionByUserId(ctx, *session.UserId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
244
internal/services/user.go
Normal file
244
internal/services/user.go
Normal file
|
@ -0,0 +1,244 @@
|
|||
package services
|
||||
|
||||
import (
|
||||
"context"
|
||||
"ms-auth/internal/lib"
|
||||
"ms-auth/internal/storage"
|
||||
"time"
|
||||
)
|
||||
|
||||
type UserProvider interface {
|
||||
CreateUser(
|
||||
ctx context.Context,
|
||||
username string,
|
||||
password string,
|
||||
email *string,
|
||||
expiresAt *time.Time,
|
||||
role *int32,
|
||||
) (*int32, error)
|
||||
ReadUserByEmail(ctx context.Context, email string) (*storage.User, error)
|
||||
ReadUserByUsername(ctx context.Context, username string) (*storage.User, error)
|
||||
ReadUserById(ctx context.Context, id int32) (*storage.User, error)
|
||||
UpdateUser(
|
||||
ctx context.Context,
|
||||
id int32,
|
||||
username *string,
|
||||
password *string,
|
||||
email *string,
|
||||
expiresAt *time.Time,
|
||||
role *int32,
|
||||
) error
|
||||
DeleteUser(ctx context.Context, id int32) error
|
||||
}
|
||||
|
||||
type ConfirmationProvider interface {
|
||||
CreateConfirmation(ctx context.Context, conf *storage.Confirmation) error
|
||||
ReadConfirmation(ctx context.Context, confId string) (*storage.Confirmation, error)
|
||||
DeleteConfirmation(ctx context.Context, confId string) error
|
||||
}
|
||||
|
||||
type EmailProvider interface {
|
||||
SendMail(ctx context.Context, to []string, subject string, body string) error
|
||||
}
|
||||
|
||||
// UserService represents a service for managing users.
|
||||
type UserService struct {
|
||||
userProvider UserProvider
|
||||
sessionProvider SessionProvider
|
||||
confirmationProvider ConfirmationProvider
|
||||
//emailProvider EmailProvider
|
||||
cfg *lib.Config
|
||||
}
|
||||
|
||||
// NewUserService creates a new UserService instance.
|
||||
//
|
||||
// Parameters:
|
||||
// - userProvider: The UserProvider implementation used by the UserService.
|
||||
// - sessionProvider: The SessionProvider implementation used by the UserService.
|
||||
// - confirmationProvider: The ConfirmationProvider implementation used by the UserService.
|
||||
// - emailProvider: The EmailProvider implementation used by the UserService.
|
||||
// - cfg: The lib.Config object used by the UserService.
|
||||
//
|
||||
// Returns:
|
||||
// - *UserService: A pointer to the newly created UserService instance.
|
||||
func NewUserService(
|
||||
userProvider UserProvider,
|
||||
sessionProvider SessionProvider,
|
||||
confirmationProvider ConfirmationProvider,
|
||||
//emailProvider EmailProvider,
|
||||
cfg *lib.Config,
|
||||
) *UserService {
|
||||
return &UserService{
|
||||
userProvider: userProvider,
|
||||
sessionProvider: sessionProvider,
|
||||
confirmationProvider: confirmationProvider,
|
||||
//emailProvider: emailProvider,
|
||||
cfg: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUser creates a new user with the provided information.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: The context for the operation.
|
||||
// - token: The token associated with the session.
|
||||
// - username: The username of the new user.
|
||||
// - password: The password of the new user.
|
||||
// - email: The email of the new user (can be nil).
|
||||
// - expiresAt: The expiration time for the user account (can be nil).
|
||||
// - role: The role of the new user.
|
||||
//
|
||||
// Returns:
|
||||
// - *int32: The ID of the created user.
|
||||
// - error: An error if the operation fails.
|
||||
func (u *UserService) CreateUser(ctx context.Context, token, username, password string, email *string, expiresAt *time.Time, role *int32) (*int32, error) {
|
||||
user, err := u.ReadUserBySessionToken(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
canCreate := func() bool {
|
||||
if !user.IsAdmin() && !user.IsModerator() {
|
||||
return false
|
||||
}
|
||||
|
||||
if role != nil && user.IsModerator() {
|
||||
if lib.IsModerator(*role) || lib.IsAdmin(*role) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}()
|
||||
|
||||
if !canCreate {
|
||||
return nil, lib.ErrNoPermission
|
||||
}
|
||||
|
||||
return u.userProvider.CreateUser(ctx, username, password, email, expiresAt, role)
|
||||
}
|
||||
|
||||
// ReadUserBySessionToken reads a user by session token.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: The context of the request.
|
||||
// - token: The session token to identify the user.
|
||||
//
|
||||
// Returns:
|
||||
// - *storage.User: The user information.
|
||||
// - error: An error if the operation fails.
|
||||
func (u *UserService) ReadUserBySessionToken(ctx context.Context, token string) (*storage.User, error) {
|
||||
session, err := u.sessionProvider.ReadSessionByToken(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u.userProvider.ReadUserById(ctx, *session.UserId)
|
||||
}
|
||||
|
||||
// ReadUser reads a user by ID.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: The context of the request.
|
||||
// - token: The session token to identify the user.
|
||||
// - id: The ID of the user to read.
|
||||
//
|
||||
// Returns:
|
||||
// - *storage.User: The user information.
|
||||
// - error: An error if the operation fails.
|
||||
func (u *UserService) ReadUser(ctx context.Context, token string, id int32) (*storage.User, error) {
|
||||
_, err := u.ReadUserBySessionToken(ctx, token)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return u.userProvider.ReadUserById(ctx, id)
|
||||
}
|
||||
|
||||
// UpdateUser updates a user's information.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: The context of the request.
|
||||
// - token: The session token to identify the user.
|
||||
// - id: The ID of the user to update.
|
||||
// - username: The new username (can be nil).
|
||||
// - password: The new password (can be nil).
|
||||
// - email: The new email (can be nil).
|
||||
// - expiresAt: The new expiration time (can be nil).
|
||||
// - role: The new role (can be nil).
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
func (u *UserService) UpdateUser(
|
||||
ctx context.Context,
|
||||
token string,
|
||||
id int32,
|
||||
username *string,
|
||||
password *string,
|
||||
email *string,
|
||||
expiresAt *time.Time,
|
||||
role *int32,
|
||||
) error {
|
||||
me, err := u.ReadUserBySessionToken(ctx, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
user, err := u.userProvider.ReadUserById(ctx, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hasAccess := func() bool {
|
||||
if me.Id == user.Id {
|
||||
return false
|
||||
}
|
||||
if me.IsAdmin() {
|
||||
return true
|
||||
}
|
||||
if me.IsModerator() && (user.IsParticipant() || user.IsSpectator()) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}()
|
||||
|
||||
if !hasAccess {
|
||||
return lib.ErrNoPermission
|
||||
}
|
||||
|
||||
return u.userProvider.UpdateUser(ctx, id, username, password, email, expiresAt, role)
|
||||
}
|
||||
|
||||
// DeleteUser deletes a user by id.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: The context of the request.
|
||||
// - token: The session token to identify the authenticated user.
|
||||
// - id: The ID of the user to delete.
|
||||
//
|
||||
// Returns:
|
||||
// - error: An error if the operation fails.
|
||||
func (u *UserService) DeleteUser(ctx context.Context, token string, id int32) error {
|
||||
user, err := u.ReadUserBySessionToken(ctx, token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if user.Id == id || !user.IsAdmin() {
|
||||
return lib.ErrNoPermission
|
||||
}
|
||||
|
||||
return u.userProvider.DeleteUser(ctx, id)
|
||||
}
|
||||
|
||||
// ReadUserByEmail reads a user by email.
|
||||
//
|
||||
// Parameters:
|
||||
// - ctx: The context of the request.
|
||||
// - email: The email of the user to read.
|
||||
//
|
||||
// Returns:
|
||||
// - *storage.User: The user information.
|
||||
// - error: An error if the operation fails.
|
||||
func (u *UserService) ReadUserByEmail(ctx context.Context, email string) (*storage.User, error) {
|
||||
return u.userProvider.ReadUserByEmail(ctx, email)
|
||||
}
|
267
internal/storage/postgresql.go
Normal file
267
internal/storage/postgresql.go
Normal file
|
@ -0,0 +1,267 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"github.com/jackc/pgerrcode"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"ms-auth/internal/lib"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
)
|
||||
|
||||
type PostgresqlStorage struct {
|
||||
db *sqlx.DB
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewUserStorage(dsn string, logger *zap.Logger) *PostgresqlStorage {
|
||||
db, err := sqlx.Connect("pgx", dsn)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
return &PostgresqlStorage{db: db, logger: logger}
|
||||
}
|
||||
|
||||
func (storage *PostgresqlStorage) Stop() error {
|
||||
return storage.db.Close()
|
||||
}
|
||||
|
||||
const (
|
||||
shortUserLifetime = time.Hour * 24 * 30
|
||||
defaultUserLifetime = time.Hour * 24 * 365 * 100
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id int32 `db:"id"`
|
||||
|
||||
Username string `db:"username"`
|
||||
HashedPassword [60]byte `db:"hashed_pwd"`
|
||||
|
||||
Email *string `db:"email"`
|
||||
|
||||
ExpiresAt time.Time `db:"expires_at"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
|
||||
Role int32 `db:"role"`
|
||||
}
|
||||
|
||||
func (user *User) IsAdmin() bool {
|
||||
return lib.IsAdmin(user.Role)
|
||||
}
|
||||
|
||||
func (user *User) IsModerator() bool {
|
||||
return lib.IsModerator(user.Role)
|
||||
}
|
||||
|
||||
func (user *User) IsParticipant() bool {
|
||||
return lib.IsParticipant(user.Role)
|
||||
}
|
||||
|
||||
func (user *User) IsSpectator() bool {
|
||||
return lib.IsSpectator(user.Role)
|
||||
}
|
||||
|
||||
func (user *User) AtLeast(role int32) bool {
|
||||
return user.Role >= role
|
||||
}
|
||||
|
||||
func (user *User) ComparePassword(password string) error {
|
||||
if bcrypt.CompareHashAndPassword(user.HashedPassword[:], []byte(password)) != nil {
|
||||
return lib.ErrBadHandleOrPassword
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (storage *PostgresqlStorage) CreateUser(
|
||||
ctx context.Context,
|
||||
username string,
|
||||
password string,
|
||||
email *string,
|
||||
expiresAt *time.Time,
|
||||
role *int32,
|
||||
) (*int32, error) {
|
||||
if err := lib.ValidUsername(username); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := lib.ValidPassword(password); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if email != nil {
|
||||
if err := lib.ValidEmail(*email); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if role != nil {
|
||||
if err := lib.ValidRole(*role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
username = strings.ToLower(username)
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return nil, lib.ErrInternal
|
||||
}
|
||||
now := time.Now()
|
||||
username = strings.ToLower(username)
|
||||
if email != nil {
|
||||
*email = strings.ToLower(*email)
|
||||
}
|
||||
if role == nil {
|
||||
role = lib.AsInt32P(lib.RoleSpectator)
|
||||
}
|
||||
if expiresAt == nil {
|
||||
if email == nil {
|
||||
expiresAt = lib.AsTimeP(now.Add(shortUserLifetime))
|
||||
} else {
|
||||
expiresAt = lib.AsTimeP(now.Add(defaultUserLifetime))
|
||||
}
|
||||
}
|
||||
|
||||
query := storage.db.Rebind(`
|
||||
INSERT INTO users
|
||||
(username, hashed_pwd, email, expires_at, role)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
RETURNING id
|
||||
`)
|
||||
|
||||
rows, err := storage.db.QueryxContext(ctx, query, username, hashedPassword, email, expiresAt, role)
|
||||
if err != nil {
|
||||
return nil, storage.handlePgErr(err)
|
||||
}
|
||||
defer rows.Close()
|
||||
var id int32
|
||||
err = rows.StructScan(&id)
|
||||
if err != nil {
|
||||
return nil, storage.handlePgErr(err)
|
||||
}
|
||||
return &id, nil
|
||||
}
|
||||
func (storage *PostgresqlStorage) ReadUserByEmail(ctx context.Context, email string) (*User, error) {
|
||||
if err := lib.ValidEmail(email); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
email = strings.ToLower(email)
|
||||
|
||||
var user User
|
||||
query := storage.db.Rebind("SELECT * from users WHERE email=? LIMIT 1")
|
||||
err := storage.db.GetContext(ctx, &user, query, email)
|
||||
if err != nil {
|
||||
return nil, storage.handlePgErr(err)
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
func (storage *PostgresqlStorage) ReadUserByUsername(ctx context.Context, username string) (*User, error) {
|
||||
if err := lib.ValidUsername(username); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
username = strings.ToLower(username)
|
||||
|
||||
var user User
|
||||
query := storage.db.Rebind("SELECT * from users WHERE username=? LIMIT 1")
|
||||
err := storage.db.GetContext(ctx, &user, query, username)
|
||||
if err != nil {
|
||||
return nil, storage.handlePgErr(err)
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
func (storage *PostgresqlStorage) ReadUserById(ctx context.Context, id int32) (*User, error) {
|
||||
var user User
|
||||
query := storage.db.Rebind("SELECT * from users WHERE id=? LIMIT 1")
|
||||
err := storage.db.GetContext(ctx, &user, query, id)
|
||||
if err != nil {
|
||||
return nil, storage.handlePgErr(err)
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
func (storage *PostgresqlStorage) UpdateUser(
|
||||
ctx context.Context,
|
||||
id int32,
|
||||
username *string,
|
||||
password *string,
|
||||
email *string,
|
||||
expiresAt *time.Time,
|
||||
role *int32,
|
||||
) error {
|
||||
var err error
|
||||
if username != nil {
|
||||
if err = lib.ValidUsername(*username); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
var hashedPassword []byte
|
||||
if password != nil {
|
||||
if err = lib.ValidPassword(*password); err != nil {
|
||||
return err
|
||||
}
|
||||
hashedPassword, err = bcrypt.GenerateFromPassword([]byte(*password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return lib.ErrInternal
|
||||
}
|
||||
}
|
||||
if email != nil {
|
||||
if err = lib.ValidEmail(*email); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if role != nil {
|
||||
if err = lib.ValidRole(*role); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if username != nil {
|
||||
*username = strings.ToLower(*username)
|
||||
}
|
||||
if email != nil {
|
||||
*email = strings.ToLower(*email)
|
||||
}
|
||||
|
||||
query := storage.db.Rebind(`
|
||||
UPDATE users
|
||||
SET username = COALESCE(?, username),
|
||||
hashed_pwd = COALESCE(?, hashed_pwd),
|
||||
email = COALESCE(?, email),
|
||||
expires_at = COALESCE(?, expires_at),
|
||||
role = COALESCE(?, role)
|
||||
WHERE id = ?`)
|
||||
|
||||
_, err = storage.db.ExecContext(ctx, query, username, hashedPassword, email, expiresAt, role, id)
|
||||
if err != nil {
|
||||
return storage.handlePgErr(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (storage *PostgresqlStorage) DeleteUser(ctx context.Context, id int32) error {
|
||||
query := storage.db.Rebind("UPDATE users SET expired_at=NOW() WHERE id = ?")
|
||||
_, err := storage.db.ExecContext(ctx, query, id)
|
||||
if err != nil {
|
||||
return storage.handlePgErr(err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (storage *PostgresqlStorage) handlePgErr(err error) error {
|
||||
var pgErr *pgconn.PgError
|
||||
if !errors.As(err, &pgErr) {
|
||||
storage.logger.DPanic("unexpected error from postgres", zap.String("err", err.Error()))
|
||||
return lib.ErrUnexpected
|
||||
}
|
||||
if pgerrcode.IsIntegrityConstraintViolation(pgErr.Code) {
|
||||
return errors.New("unique key violation") // FIXME
|
||||
}
|
||||
storage.logger.DPanic("unexpected internal error from postgres", zap.String("err", err.Error()))
|
||||
return lib.ErrInternal
|
||||
}
|
332
internal/storage/valkey.go
Normal file
332
internal/storage/valkey.go
Normal file
|
@ -0,0 +1,332 @@
|
|||
package storage
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"go.uber.org/zap"
|
||||
"time"
|
||||
|
||||
"ms-auth/internal/lib"
|
||||
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/google/uuid"
|
||||
"github.com/valkey-io/valkey-go"
|
||||
"github.com/valkey-io/valkey-go/valkeylock"
|
||||
)
|
||||
|
||||
type ValkeyStorage struct {
|
||||
db valkey.Client
|
||||
locker valkeylock.Locker
|
||||
cfg *lib.Config
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
func NewValkeyStorage(dsn string, cfg *lib.Config, logger *zap.Logger) *ValkeyStorage {
|
||||
opts, err := valkey.ParseURL(dsn)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
db, err := valkey.NewClient(opts)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
locker, err := valkeylock.NewLocker(valkeylock.LockerOption{
|
||||
ClientOption: opts,
|
||||
KeyMajority: 1,
|
||||
NoLoopTracking: true,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
|
||||
return &ValkeyStorage{
|
||||
db: db,
|
||||
locker: locker,
|
||||
cfg: cfg,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (storage *ValkeyStorage) Stop() error {
|
||||
storage.db.Close()
|
||||
storage.locker.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
const (
|
||||
sessionLifetime = time.Minute * 40
|
||||
confirmationLifetime = time.Hour * 5
|
||||
)
|
||||
|
||||
func (storage *ValkeyStorage) CreateSession(
|
||||
ctx context.Context,
|
||||
user_id int32,
|
||||
) error {
|
||||
session := NewSession(user_id)
|
||||
|
||||
resp := storage.db.Do(ctx, storage.db.
|
||||
B().Set().
|
||||
Key(string(*session.UserId)).
|
||||
Value(*session.Id).
|
||||
Nx().
|
||||
Exat(time.Now().Add(sessionLifetime)).
|
||||
Build(),
|
||||
)
|
||||
|
||||
if err := resp.Error(); err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return lib.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (storage *ValkeyStorage) ReadSessionByToken(ctx context.Context, token string) (*Session, error) {
|
||||
session, err := Parse(token, storage.cfg.JWTSecret)
|
||||
if err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
real_session, err := storage.ReadSessionByUserId(ctx, *session.UserId)
|
||||
if err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if *session.Id != *real_session.Id {
|
||||
storage.logger.Error(err.Error())
|
||||
return nil, lib.ErrInternal
|
||||
}
|
||||
|
||||
return session, err
|
||||
}
|
||||
|
||||
func (storage *ValkeyStorage) ReadSessionByUserId(ctx context.Context, user_id int32) (*Session, error) {
|
||||
resp := storage.db.Do(ctx, storage.db.B().Get().Key(string(user_id)).Build())
|
||||
if err := resp.Error(); err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return nil, lib.ErrInternal
|
||||
}
|
||||
|
||||
id, err := resp.ToString()
|
||||
if err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return nil, lib.ErrInternal
|
||||
}
|
||||
|
||||
return &Session{
|
||||
Id: &id,
|
||||
UserId: &user_id,
|
||||
}, err
|
||||
}
|
||||
|
||||
func (storage *ValkeyStorage) UpdateSession(ctx context.Context, session *Session) error {
|
||||
resp := storage.db.Do(ctx, storage.db.
|
||||
B().Set().
|
||||
Key(string(*session.UserId)).
|
||||
Value(*session.Id).
|
||||
Xx().
|
||||
Exat(time.Now().Add(sessionLifetime)).
|
||||
Build(),
|
||||
)
|
||||
|
||||
if err := resp.Error(); err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return lib.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (storage *ValkeyStorage) DeleteSessionByToken(ctx context.Context, token string) error {
|
||||
session, err := Parse(token, storage.cfg.JWTSecret)
|
||||
if err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
err = storage.DeleteSessionByUserId(ctx, *session.UserId)
|
||||
if err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (storage *ValkeyStorage) DeleteSessionByUserId(ctx context.Context, user_id int32) error {
|
||||
resp := storage.db.Do(ctx, storage.db.
|
||||
B().Del().
|
||||
Key(string(user_id)).
|
||||
Build(),
|
||||
)
|
||||
|
||||
if err := resp.Error(); err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return lib.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (storage *ValkeyStorage) CreateConfirmation(ctx context.Context, conf *Confirmation) error {
|
||||
resp := storage.db.Do(ctx, storage.db.
|
||||
B().Set().
|
||||
Key(*conf.Id).
|
||||
Value(string(conf.JSON())).
|
||||
Exat(time.Now().Add(confirmationLifetime)).
|
||||
Build(),
|
||||
)
|
||||
|
||||
if err := resp.Error(); err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return lib.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (storage *ValkeyStorage) ReadConfirmation(ctx context.Context, conf_id string) (*Confirmation, error) {
|
||||
resp := storage.db.Do(ctx, storage.db.
|
||||
B().Get().
|
||||
Key(conf_id).
|
||||
Build(),
|
||||
)
|
||||
|
||||
if err := resp.Error(); err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return nil, lib.ErrInternal
|
||||
}
|
||||
|
||||
b, err := resp.AsBytes()
|
||||
if err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return nil, lib.ErrInternal
|
||||
}
|
||||
|
||||
var conf Confirmation
|
||||
err = json.Unmarshal(b, &conf)
|
||||
if err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return nil, lib.ErrInternal
|
||||
}
|
||||
|
||||
return &conf, nil
|
||||
}
|
||||
|
||||
func (storage *ValkeyStorage) DeleteConfirmation(ctx context.Context, conf_id string) error {
|
||||
resp := storage.db.Do(ctx, storage.db.
|
||||
B().Del().
|
||||
Key(conf_id).
|
||||
Build(),
|
||||
)
|
||||
|
||||
if err := resp.Error(); err != nil {
|
||||
storage.logger.Error(err.Error())
|
||||
return lib.ErrInternal
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
ErrBadSession = errors.New("bad session")
|
||||
ErrBadConfirmation = errors.New("bad confirmation")
|
||||
)
|
||||
|
||||
type Confirmation struct {
|
||||
Id *string `json:"id"`
|
||||
UserId *int32 `json:"user_id,omitempty"`
|
||||
Email *string `json:"email"`
|
||||
}
|
||||
|
||||
func NewConfirmation(userId *int32, email string) (*Confirmation, error) {
|
||||
c := &Confirmation{
|
||||
Id: lib.AsStringP(uuid.NewString()),
|
||||
UserId: userId,
|
||||
Email: &email,
|
||||
}
|
||||
|
||||
if err := c.Valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *Confirmation) Valid() error {
|
||||
if c.Id == nil {
|
||||
return ErrBadConfirmation
|
||||
}
|
||||
// FIXME
|
||||
// if c.userId == nil {
|
||||
// return ErrBadConfirmation
|
||||
// }
|
||||
if c.Email == nil {
|
||||
return ErrBadConfirmation
|
||||
}
|
||||
if err := lib.ValidEmail(*c.Email); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Confirmation) JSON() []byte {
|
||||
b, err := json.Marshal(c)
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
type Session struct {
|
||||
Id *string
|
||||
UserId *int32
|
||||
}
|
||||
|
||||
func NewSession(userId int32) *Session {
|
||||
return &Session{
|
||||
Id: lib.AsStringP(uuid.NewString()),
|
||||
UserId: &userId,
|
||||
}
|
||||
}
|
||||
|
||||
func (s Session) Valid() error {
|
||||
if s.Id == nil {
|
||||
return ErrBadSession
|
||||
}
|
||||
if s.UserId == nil {
|
||||
return ErrBadSession
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s Session) Token(secret string) (string, error) {
|
||||
if err := s.Valid(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, s)
|
||||
str, err := refreshToken.SignedString([]byte(secret))
|
||||
if err != nil {
|
||||
return "", ErrBadSession
|
||||
}
|
||||
return str, nil
|
||||
}
|
||||
|
||||
func Parse(tkn string, secret string) (*Session, error) {
|
||||
parsedToken, err := jwt.ParseWithClaims(tkn, &Session{}, func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(secret), nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, ErrBadSession
|
||||
}
|
||||
session := parsedToken.Claims.(*Session)
|
||||
if err := session.Valid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return session, nil
|
||||
}
|
11
internal/transport/email_server.go
Normal file
11
internal/transport/email_server.go
Normal file
|
@ -0,0 +1,11 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
emailv1 "ms-auth/pkg/go/gen/email/v1"
|
||||
)
|
||||
|
||||
func (s *AuthServer) SendEmail(ctx context.Context, req *emailv1.SendEmailRequest) (*emptypb.Empty, error) {
|
||||
panic("not implemented")
|
||||
}
|
1
internal/transport/interceptors.go
Normal file
1
internal/transport/interceptors.go
Normal file
|
@ -0,0 +1 @@
|
|||
package transport
|
123
internal/transport/server.go
Normal file
123
internal/transport/server.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"go.uber.org/zap"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
"ms-auth/internal/storage"
|
||||
emailv1 "ms-auth/pkg/go/gen/email/v1"
|
||||
sessionv1 "ms-auth/pkg/go/gen/session/v1"
|
||||
userv1 "ms-auth/pkg/go/gen/user/v1"
|
||||
"net"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type SessionServiceI interface {
|
||||
Create(ctx context.Context, handle, password string) (*string, error)
|
||||
Read(ctx context.Context, token string) (*int32, error)
|
||||
Update(ctx context.Context, token string) error
|
||||
Delete(ctx context.Context, token string) error
|
||||
}
|
||||
|
||||
type UserServiceI interface {
|
||||
CreateUser(ctx context.Context, token, username, password string, email *string, expiresAt *time.Time, role *int32) (*int32, error)
|
||||
ReadUser(ctx context.Context, token string, id int32) (*storage.User, error)
|
||||
UpdateUser(ctx context.Context, token string, id int32, username *string, password *string, email *string, expiresAt *time.Time, role *int32) error
|
||||
DeleteUser(ctx context.Context, token string, id int32) error
|
||||
}
|
||||
|
||||
type AuthServer struct {
|
||||
emailv1.UnimplementedEmailServiceServer
|
||||
|
||||
sessionv1.UnimplementedSessionServiceServer
|
||||
sessionService SessionServiceI
|
||||
|
||||
userv1.UnimplementedUserServiceServer
|
||||
userService UserServiceI
|
||||
|
||||
gRPCServer *grpc.Server
|
||||
logger *zap.Logger
|
||||
}
|
||||
|
||||
// NewAuthServer creates a new instance of the AuthServer struct.
|
||||
//
|
||||
// Parameters:
|
||||
// - sessionService: A pointer to the SessionServiceI interface.
|
||||
// - gRPCServer: A pointer to the grpc.Server struct.
|
||||
// - logger: A pointer to the zap.Logger struct.
|
||||
//
|
||||
// Returns:
|
||||
// - *AuthServer: A pointer to the AuthServer struct.
|
||||
func NewAuthServer(sessionService SessionServiceI, userService UserServiceI, gRPCServer *grpc.Server, logger *zap.Logger) *AuthServer {
|
||||
return &AuthServer{
|
||||
sessionService: sessionService,
|
||||
userService: userService,
|
||||
gRPCServer: gRPCServer,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the AuthServer and listens on port :8090.
|
||||
//
|
||||
// It creates a listener on the specified address and starts serving incoming requests.
|
||||
// It also logs the server start and any errors that occur during serving.
|
||||
//
|
||||
// No parameters.
|
||||
// No return values.
|
||||
func (s *AuthServer) Start() {
|
||||
lis, err := net.Listen("tcp", ":8090")
|
||||
if err != nil {
|
||||
s.logger.Fatal("")
|
||||
}
|
||||
|
||||
sessionv1.RegisterSessionServiceServer(s.gRPCServer, s)
|
||||
go func() {
|
||||
s.logger.Info("Listening on :8090")
|
||||
if err := s.gRPCServer.Serve(lis); err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
}()
|
||||
s.logger.Info("server started")
|
||||
}
|
||||
|
||||
// GracefullyStop stops the server gracefully.
|
||||
//
|
||||
// No parameters.
|
||||
// No return values.
|
||||
func (s *AuthServer) GracefullyStop() {
|
||||
s.gRPCServer.GracefulStop()
|
||||
s.logger.Info("server stopped")
|
||||
}
|
||||
|
||||
func AsTimeP(t *timestamppb.Timestamp) *time.Time {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
tt := t.AsTime()
|
||||
return &tt
|
||||
}
|
||||
|
||||
func AsInt32P(v *userv1.Role) *int32 {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
vv := int32(v.Number())
|
||||
return &vv
|
||||
}
|
||||
|
||||
func AsTimestampP(t *time.Time) *timestamppb.Timestamp {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
return timestamppb.New(*t)
|
||||
}
|
||||
|
||||
func AsRoleP(r *int32) *userv1.Role {
|
||||
if r == nil {
|
||||
return nil
|
||||
}
|
||||
rr := userv1.Role(*r)
|
||||
return &rr
|
||||
}
|
45
internal/transport/session_server.go
Normal file
45
internal/transport/session_server.go
Normal file
|
@ -0,0 +1,45 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
sessionv1 "ms-auth/pkg/go/gen/session/v1"
|
||||
)
|
||||
|
||||
func (s *AuthServer) Create(ctx context.Context, req *sessionv1.CreateSessionRequest) (*sessionv1.CreateSessionResponse, error) {
|
||||
token, err := s.sessionService.Create(ctx, req.GetHandle(), req.GetPassword())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
||||
}
|
||||
return &sessionv1.CreateSessionResponse{
|
||||
Token: *token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) Read(ctx context.Context, req *sessionv1.ReadSessionRequest) (*sessionv1.ReadSessionResponse, error) {
|
||||
id, err := s.sessionService.Read(ctx, req.GetToken())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
||||
}
|
||||
return &sessionv1.ReadSessionResponse{
|
||||
UserId: *id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) Update(ctx context.Context, req *sessionv1.UpdateSessionRequest) (*emptypb.Empty, error) {
|
||||
err := s.sessionService.Update(ctx, req.GetToken())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) Delete(ctx context.Context, req *sessionv1.DeleteSessionRequest) (*emptypb.Empty, error) {
|
||||
err := s.sessionService.Delete(ctx, req.GetToken())
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
124
internal/transport/user_server.go
Normal file
124
internal/transport/user_server.go
Normal file
|
@ -0,0 +1,124 @@
|
|||
package transport
|
||||
|
||||
import (
|
||||
"context"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/types/known/emptypb"
|
||||
userv1 "ms-auth/pkg/go/gen/user/v1"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func (s *AuthServer) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) {
|
||||
user := req.GetUser()
|
||||
if user == nil {
|
||||
return nil, status.Errorf(codes.Unknown, "") // FIXME
|
||||
}
|
||||
id, err := s.userService.CreateUser(
|
||||
ctx,
|
||||
req.GetToken(),
|
||||
user.GetUsername(),
|
||||
user.GetPassword(),
|
||||
user.Email,
|
||||
AsTimeP(user.ExpiresAt),
|
||||
AsInt32P(user.Role),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
||||
}
|
||||
|
||||
return &userv1.CreateUserResponse{
|
||||
Id: *id,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) ReadUser(ctx context.Context, req *userv1.ReadUserRequest) (*userv1.ReadUserResponse, error) {
|
||||
user, err := s.userService.ReadUser(
|
||||
ctx,
|
||||
req.GetToken(),
|
||||
req.GetId(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
||||
}
|
||||
|
||||
return &userv1.ReadUserResponse{
|
||||
User: &userv1.ReadUserResponse_User{
|
||||
Id: user.Id,
|
||||
Username: user.Username,
|
||||
Email: user.Email,
|
||||
ExpiresAt: AsTimestampP(&user.ExpiresAt),
|
||||
CreatedAt: AsTimestampP(&user.CreatedAt),
|
||||
Role: *AsRoleP(&user.Role),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) UpdateUser(ctx context.Context, req *userv1.UpdateUserRequest) (*emptypb.Empty, error) {
|
||||
user := req.GetUser()
|
||||
if user == nil {
|
||||
return nil, status.Errorf(codes.Unknown, "") // FIXME
|
||||
}
|
||||
err := s.userService.UpdateUser(
|
||||
ctx,
|
||||
req.GetToken(),
|
||||
user.GetId(),
|
||||
user.Username,
|
||||
user.Password,
|
||||
user.Email,
|
||||
AsTimeP(user.ExpiresAt),
|
||||
AsInt32P(user.Role),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) DeleteUser(ctx context.Context, req *userv1.DeleteUserRequest) (*emptypb.Empty, error) {
|
||||
err := s.userService.DeleteUser(
|
||||
ctx,
|
||||
req.GetToken(),
|
||||
req.GetId(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, status.Errorf(codes.Unknown, err.Error()) // FIXME
|
||||
}
|
||||
return &emptypb.Empty{}, nil
|
||||
}
|
||||
|
||||
func (s *AuthServer) ConfirmEmail(ctx context.Context, req *userv1.ConfirmEmailRequest) (*emptypb.Empty, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (s *AuthServer) RegisterUser(ctx context.Context, req *userv1.RegisterUserRequest) (*emptypb.Empty, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (s *AuthServer) ConfirmRegisterUser(ctx context.Context, req *userv1.ConfirmRegisterUserRequest) (*emptypb.Empty, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (s *AuthServer) ResetPassword(ctx context.Context, req *userv1.ResetPasswordRequest) (*emptypb.Empty, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func (s *AuthServer) ConfirmResetPassword(ctx context.Context, req *userv1.ConfirmResetPasswordRequest) (*emptypb.Empty, error) {
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
func shortenEmail(email *string) *string {
|
||||
if email == nil {
|
||||
return nil
|
||||
}
|
||||
parts := strings.Split(*email, "@")
|
||||
p1 := parts[0]
|
||||
p2 := parts[1]
|
||||
a := "****"
|
||||
if len(p1) <= 4 {
|
||||
e := a + "@" + p2
|
||||
return &e
|
||||
}
|
||||
e := p1[:len(p1)-4] + a + "@" + p2
|
||||
return &e
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue