Files
IDP-Backend/internal/api/auth/service/auth_service.go

195 lines
5.0 KiB
Go

package service
import (
"context"
"net/http"
"strconv"
"git.logidex.ru/fakz9/logidex-id/internal/api/auth/domain"
userDomain "git.logidex.ru/fakz9/logidex-id/internal/api/user/domain"
userService "git.logidex.ru/fakz9/logidex-id/internal/api/user/service"
"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
userService userService.UserService
hydraClient *hydraApi.APIClient
}
func (a authService) validateAndFormatPhoneNumber(phoneNumber string) (string, error) {
formattedPhone, err := phoneutil.ParseAndFormatPhoneNumber(phoneNumber)
if err != nil {
return "", domain.ErrInvalidPhoneNumber{
PhoneNumber: phoneNumber,
Err: err,
}
}
return formattedPhone, nil
}
func (a authService) getUserByPhoneNumber(ctx context.Context, phoneNumber string) (*userDomain.User, error) {
user, err := a.userService.GetUserByPhoneNumber(ctx, phoneNumber)
if err != nil {
return nil, err
}
if user == nil {
return nil, domain.ErrUserNotFound{PhoneNumber: phoneNumber}
}
return user, nil
}
func (a authService) getOrCreateUser(ctx context.Context, phoneNumber string) (*userDomain.User, error) {
user, err := a.userService.GetUserByPhoneNumber(ctx, phoneNumber)
if err != nil {
return nil, err
}
if user == nil {
user, err = a.userService.CreateUser(ctx, phoneNumber)
if err != nil {
return nil, err
}
}
return user, nil
}
func (a authService) validateHydraResponse(rawRsp *http.Response, userUuid string) error {
if rawRsp.StatusCode != 200 {
return domain.ErrInvalidHydraAccept{
Message: "Hydra response status: " + strconv.Itoa(rawRsp.StatusCode),
Uuid: userUuid,
}
}
return nil
}
func (a authService) extractRedirectUrl(rsp interface{ GetRedirectToOk() (*string, bool) }, userUuid string) (string, error) {
redirectTo, ok := rsp.GetRedirectToOk()
if !ok || redirectTo == nil {
return "", domain.ErrInvalidHydraAccept{
Message: "Hydra redirectTo is nil",
Uuid: userUuid,
}
}
return *redirectTo, nil
}
func (a authService) AcceptConsent(ctx context.Context, phoneNumber string, challenge string) (string, error) {
phoneNumber, err := a.validateAndFormatPhoneNumber(phoneNumber)
if err != nil {
return "", err
}
user, err := a.getUserByPhoneNumber(ctx, phoneNumber)
if err != nil {
return "", err
}
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 err = a.validateHydraResponse(rawRsp, user.Uuid); err != nil {
return "", err
}
redirectUrl, err := a.extractRedirectUrl(rsp, user.Uuid)
if err != nil {
return "", err
}
// TODO: Verify user in the database
_, err = a.userService.VerifyUser(ctx, user.Uuid)
if err != nil {
return "", err
}
return redirectUrl, nil
}
func (a authService) OtpRequest(ctx context.Context, phoneNumber string) error {
phoneNumber, err := a.validateAndFormatPhoneNumber(phoneNumber)
if err != nil {
return err
}
user, err := a.getOrCreateUser(ctx, phoneNumber)
if err != nil {
return err
}
code := "123456"
err = a.repo.SaveOtpRequest(ctx, user.Uuid, 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 := a.validateAndFormatPhoneNumber(phoneNumber)
if err != nil {
return "", err
}
user, err := a.getUserByPhoneNumber(ctx, phoneNumber)
if err != nil {
return "", err
}
otp, err := a.repo.GetOtpRequest(ctx, user.Uuid)
if err != nil {
return "", err
}
if otp == nil {
return "", domain.ErrOtpNotFound{Uuid: user.Uuid}
}
if *otp != code {
return "", domain.ErrOtpInvalid{Uuid: user.Uuid, Code: code}
}
request := hydraApi.AcceptLoginRequest{}
request.SetSubject(user.Uuid)
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 err = a.validateHydraResponse(rawRsp, user.Uuid); err != nil {
return "", err
}
redirectUrl, err := a.extractRedirectUrl(rsp, user.Uuid)
if err != nil {
return "", err
}
return redirectUrl, nil
}
func NewAuthService(repo domain.AuthRepository, userService userService.UserService, hydraClient *hydraApi.APIClient) AuthService {
return &authService{
repo: repo,
userService: userService,
hydraClient: hydraClient,
}
}