add user management functionality with OTP verification and consent handling, DI introduced
This commit is contained in:
167
internal/api/auth/service/auth_service.go
Normal file
167
internal/api/auth/service/auth_service.go
Normal file
@ -0,0 +1,167 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/api/auth/domain"
|
||||
userDomain "git.logidex.ru/fakz9/logidex-id/internal/api/user/domain"
|
||||
"git.logidex.ru/fakz9/logidex-id/internal/phoneutil"
|
||||
hydraApi "github.com/ory/hydra-client-go"
|
||||
)
|
||||
|
||||
type AuthService interface {
|
||||
OtpRequest(ctx context.Context, phoneNumber string) error
|
||||
OtpVerify(ctx context.Context, phoneNumber, code string, loggingChallenge string) (string, error)
|
||||
AcceptConsent(ctx context.Context, phoneNumber string, challenge string) (string, error)
|
||||
}
|
||||
|
||||
type authService struct {
|
||||
repo domain.AuthRepository
|
||||
userRepo userDomain.UserRepository
|
||||
hydraClient *hydraApi.APIClient
|
||||
}
|
||||
|
||||
func (a authService) AcceptConsent(ctx context.Context, phoneNumber string, challenge string) (string, error) {
|
||||
phoneNumber, err := phoneutil.ParseAndFormatPhoneNumber(phoneNumber)
|
||||
if err != nil {
|
||||
return "", domain.ErrInvalidPhoneNumber{
|
||||
PhoneNumber: phoneNumber,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
user, err := a.userRepo.GetUserByPhoneNumber(ctx, phoneNumber)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if user == nil {
|
||||
return "", domain.ErrUserNotFound{PhoneNumber: phoneNumber}
|
||||
}
|
||||
request := hydraApi.AcceptConsentRequest{}
|
||||
request.SetGrantScope([]string{"openid"})
|
||||
request.SetRemember(true)
|
||||
request.SetRememberFor(3600)
|
||||
|
||||
rsp, rawRsp, err := a.hydraClient.AdminApi.
|
||||
AcceptConsentRequest(ctx).
|
||||
ConsentChallenge(challenge).
|
||||
AcceptConsentRequest(request).
|
||||
Execute()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if rawRsp.StatusCode != 200 {
|
||||
return "", domain.ErrInvalidHydraAccept{
|
||||
Message: "Hydra response is nil: " + strconv.Itoa(rawRsp.StatusCode),
|
||||
Uuid: "",
|
||||
}
|
||||
}
|
||||
redirectTo, ok := rsp.GetRedirectToOk()
|
||||
if !ok || redirectTo == nil {
|
||||
return "", domain.ErrInvalidHydraAccept{
|
||||
Message: "Hydra redirectTo is nil",
|
||||
Uuid: "",
|
||||
}
|
||||
}
|
||||
// TODO: Verify user in the database
|
||||
_, err = a.userRepo.VerifyUser(ctx, user.Uuid.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return *redirectTo, nil
|
||||
}
|
||||
|
||||
func (a authService) OtpRequest(ctx context.Context, phoneNumber string) error {
|
||||
phoneNumber, err := phoneutil.ParseAndFormatPhoneNumber(phoneNumber)
|
||||
if err != nil {
|
||||
return domain.ErrInvalidPhoneNumber{
|
||||
PhoneNumber: phoneNumber,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
user, err := a.userRepo.GetUserByPhoneNumber(ctx, phoneNumber)
|
||||
//if err != nil {
|
||||
// return err
|
||||
//}
|
||||
|
||||
if user == nil {
|
||||
// Create a new user if it does not exist
|
||||
user, err = a.userRepo.CreateUser(ctx, phoneNumber)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
code := "123456"
|
||||
err = a.repo.SaveOtpRequest(ctx, user.Uuid.String(), code)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO implement sending OTP code via SMS
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a authService) OtpVerify(ctx context.Context, phoneNumber string, code string, loggingChallenge string) (string, error) {
|
||||
phoneNumber, err := phoneutil.ParseAndFormatPhoneNumber(phoneNumber)
|
||||
if err != nil {
|
||||
return "", domain.ErrInvalidPhoneNumber{
|
||||
PhoneNumber: phoneNumber,
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
user, err := a.userRepo.GetUserByPhoneNumber(ctx, phoneNumber)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if user == nil {
|
||||
return "", domain.ErrUserNotFound{PhoneNumber: phoneNumber}
|
||||
}
|
||||
otp, err := a.repo.GetOtpRequest(ctx, user.Uuid.String())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if otp == nil {
|
||||
return "", domain.ErrOtpNotFound{Uuid: user.Uuid.String()}
|
||||
}
|
||||
if *otp != code {
|
||||
return "", domain.ErrOtpInvalid{Uuid: user.Uuid.String(), Code: code}
|
||||
}
|
||||
request := hydraApi.AcceptLoginRequest{}
|
||||
request.SetSubject(user.Uuid.String())
|
||||
request.SetRemember(true)
|
||||
request.SetRememberFor(3600) // 1 hour
|
||||
|
||||
rsp, rawRsp, err := a.hydraClient.AdminApi.
|
||||
AcceptLoginRequest(ctx).
|
||||
LoginChallenge(loggingChallenge).
|
||||
AcceptLoginRequest(request).
|
||||
Execute()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if rawRsp.StatusCode != 200 {
|
||||
return "", domain.ErrInvalidHydraAccept{
|
||||
Message: "Hydra response is nil: " + strconv.Itoa(rawRsp.StatusCode),
|
||||
Uuid: user.Uuid.String(),
|
||||
}
|
||||
}
|
||||
|
||||
redirectTo, ok := rsp.GetRedirectToOk()
|
||||
if !ok || redirectTo == nil {
|
||||
return "", domain.ErrInvalidHydraAccept{
|
||||
Message: "Hydra redirectTo is nil",
|
||||
Uuid: user.Uuid.String(),
|
||||
}
|
||||
}
|
||||
return *redirectTo, nil
|
||||
}
|
||||
|
||||
func NewAuthService(repo domain.AuthRepository, userRepo userDomain.UserRepository, hydraClient *hydraApi.APIClient) AuthService {
|
||||
return &authService{
|
||||
repo: repo,
|
||||
userRepo: userRepo,
|
||||
hydraClient: hydraClient,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user