mirror of
https://github.com/raseels-repos/golang-saas-starter-kit.git
synced 2025-06-06 23:46:29 +02:00
108 lines
3.9 KiB
Go
108 lines
3.9 KiB
Go
package invite
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/platform/web/webcontext"
|
|
"geeks-accelerator/oss/saas-starter-kit/internal/user_account"
|
|
"github.com/pkg/errors"
|
|
"github.com/sudo-suhas/symcrypto"
|
|
)
|
|
|
|
// SendUserInvitesRequest defines the data needed to make an invite request.
|
|
type SendUserInvitesRequest struct {
|
|
AccountID string `json:"account_id" validate:"required,uuid" example:"c4653bf9-5978-48b7-89c5-95704aebb7e2"`
|
|
UserID string `json:"user_id" validate:"required,uuid" example:"c4653bf9-5978-48b7-89c5-95704aebb7e2"`
|
|
Emails []string `json:"emails" validate:"required,dive,email"`
|
|
Roles []user_account.UserAccountRole `json:"roles" validate:"required"`
|
|
TTL time.Duration `json:"ttl,omitempty" `
|
|
}
|
|
|
|
// InviteHash
|
|
type InviteHash struct {
|
|
UserID string `json:"user_id" validate:"required,uuid" example:"d69bdef7-173f-4d29-b52c-3edc60baf6a2"`
|
|
AccountID string `json:"account_id" validate:"required,uuid" example:"c4653bf9-5978-48b7-89c5-95704aebb7e2"`
|
|
CreatedAt int `json:"created_at" validate:"required"`
|
|
ExpiresAt int `json:"expires_at" validate:"required"`
|
|
RequestIP string `json:"request_ip" validate:"required,ip" example:"69.56.104.36"`
|
|
}
|
|
|
|
// AcceptInviteRequest defines the fields need to complete an invite request.
|
|
type AcceptInviteRequest struct {
|
|
InviteHash string `json:"invite_hash" validate:"required" example:"d69bdef7-173f-4d29-b52c-3edc60baf6a2"`
|
|
}
|
|
|
|
// AcceptInviteUserRequest defines the fields need to complete an invite request.
|
|
type AcceptInviteUserRequest struct {
|
|
InviteHash string `json:"invite_hash" validate:"required" example:"d69bdef7-173f-4d29-b52c-3edc60baf6a2"`
|
|
Email string `json:"email" validate:"required,email" example:"gabi@geeksinthewoods.com"`
|
|
FirstName string `json:"first_name" validate:"required" example:"Gabi"`
|
|
LastName string `json:"last_name" validate:"required" example:"May"`
|
|
Password string `json:"password" validate:"required" example:"SecretString"`
|
|
PasswordConfirm string `json:"password_confirm" validate:"required,eqfield=Password" example:"SecretString"`
|
|
Timezone *string `json:"timezone,omitempty" validate:"omitempty" example:"America/Anchorage"`
|
|
}
|
|
|
|
// NewInviteHash generates a new encrypt invite hash that is web safe for use in URLs.
|
|
func NewInviteHash(ctx context.Context, secretKey, userID, accountID, requestIp string, ttl time.Duration, now time.Time) (string, error) {
|
|
// Generate a string that embeds additional information.
|
|
hashPts := []string{
|
|
userID,
|
|
accountID,
|
|
strconv.Itoa(int(now.UTC().Unix())),
|
|
strconv.Itoa(int(now.UTC().Add(ttl).Unix())),
|
|
requestIp,
|
|
}
|
|
hashStr := strings.Join(hashPts, "|")
|
|
|
|
// This returns the nonce appended with the encrypted string.
|
|
crypto, err := symcrypto.New(secretKey)
|
|
if err != nil {
|
|
return "", errors.WithStack(err)
|
|
}
|
|
encrypted, err := crypto.Encrypt(hashStr)
|
|
if err != nil {
|
|
return "", errors.WithStack(err)
|
|
}
|
|
|
|
return encrypted, nil
|
|
}
|
|
|
|
// ParseInviteHash extracts the details encrypted in the hash string.
|
|
func ParseInviteHash(ctx context.Context, encrypted, secretKey string, now time.Time) (*InviteHash, error) {
|
|
crypto, err := symcrypto.New(secretKey)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
hashStr, err := crypto.Decrypt(encrypted)
|
|
if err != nil {
|
|
return nil, errors.WithStack(err)
|
|
}
|
|
hashPts := strings.Split(hashStr, "|")
|
|
|
|
var hash InviteHash
|
|
if len(hashPts) == 5 {
|
|
hash.UserID = hashPts[0]
|
|
hash.AccountID = hashPts[1]
|
|
hash.CreatedAt, _ = strconv.Atoi(hashPts[2])
|
|
hash.ExpiresAt, _ = strconv.Atoi(hashPts[3])
|
|
hash.RequestIP = hashPts[4]
|
|
}
|
|
|
|
// Validate the hash.
|
|
err = webcontext.Validator().StructCtx(ctx, hash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if int64(hash.ExpiresAt) < now.UTC().Unix() {
|
|
err = errors.WithMessage(ErrInviteExpired, "Invite has expired.")
|
|
return nil, err
|
|
}
|
|
|
|
return &hash, nil
|
|
}
|