195 lines
5.0 KiB
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,
|
|
}
|
|
}
|