all repos

onasty @ 3f33118

a one-time notes service

onasty/internal/service/authsrv/oauth.go (view raw)

Olexandr Smirnov Olexandr Smirnov
olexsmir@gmail.com
refactor(api): split `usersrv` responsibilities (#195)..., 9 months ago
1
package authsrv
2
3
import (
4
	"context"
5
	"errors"
6
	"log/slog"
7
	"time"
8
9
	"github.com/gofrs/uuid/v5"
10
	"github.com/olexsmir/onasty/internal/dtos"
11
	"github.com/olexsmir/onasty/internal/models"
12
	"github.com/olexsmir/onasty/internal/oauth"
13
)
14
15
var ErrProviderNotSupported = errors.New("oauth2 provider not supported")
16
17
const (
18
	googleProvider = "google"
19
	githubProvider = "github"
20
)
21
22
func (a *AuthSrv) GetOAuthURL(providerName string) (dtos.OAuthRedirect, error) {
23
	state := uuid.Must(uuid.NewV4()).String()
24
25
	switch providerName {
26
	case googleProvider:
27
		return dtos.OAuthRedirect{
28
			URL:   a.googleOauth.GetAuthURL(state),
29
			State: state,
30
		}, nil
31
	case githubProvider:
32
		return dtos.OAuthRedirect{
33
			URL:   a.githubOauth.GetAuthURL(state),
34
			State: state,
35
		}, nil
36
	default:
37
		return dtos.OAuthRedirect{}, ErrProviderNotSupported
38
	}
39
}
40
41
func (a *AuthSrv) HandleOAuthLogin(
42
	ctx context.Context,
43
	providerName, code string,
44
) (dtos.Tokens, error) {
45
	userInfo, err := a.getUserInfoBasedOnProvider(ctx, providerName, code)
46
	if err != nil {
47
		return dtos.Tokens{}, err
48
	}
49
50
	userID, err := a.getUserByOAuthIDOrCreateOne(ctx, userInfo)
51
	if err != nil {
52
		return dtos.Tokens{}, err
53
	}
54
55
	if err = a.userstore.LinkOAuthIdentity(ctx, userID, userInfo.Provider, userInfo.ProviderID); err != nil {
56
		slog.ErrorContext(ctx, "failed to link user identity", "user_id", userID, "err", err)
57
		return dtos.Tokens{}, err
58
	}
59
60
	return a.issueTokens(ctx, userID)
61
}
62
63
func (a *AuthSrv) getUserInfoBasedOnProvider(
64
	ctx context.Context,
65
	providerName, code string,
66
) (oauth.UserInfo, error) {
67
	var userInfo oauth.UserInfo
68
	var err error
69
70
	switch providerName {
71
	case googleProvider:
72
		userInfo, err = a.googleOauth.ExchangeCode(ctx, code)
73
	case githubProvider:
74
		userInfo, err = a.githubOauth.ExchangeCode(ctx, code)
75
	default:
76
		return oauth.UserInfo{}, ErrProviderNotSupported
77
	}
78
79
	return userInfo, err
80
}
81
82
func (a *AuthSrv) getUserByOAuthIDOrCreateOne(
83
	ctx context.Context,
84
	info oauth.UserInfo,
85
) (uuid.UUID, error) {
86
	user, err := a.userstore.GetByOAuthID(ctx, info.Provider, info.ProviderID)
87
	if err != nil {
88
		if errors.Is(err, models.ErrUserNotFound) {
89
			uid, cerr := a.userstore.Create(ctx, models.User{
90
				ID:          uuid.Nil,
91
				Email:       info.Email,
92
				Activated:   true,
93
				Password:    "",
94
				CreatedAt:   time.Now(),
95
				LastLoginAt: time.Now(),
96
			})
97
			return uid, cerr
98
		}
99
		return uuid.Nil, err
100
	}
101
102
	return user.ID, nil
103
}