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 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", "offline", "offline_access"}) 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, } }