all repos

onasty @ 36f59cd

a one-time notes service

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

Smirnov Oleksandr Smirnov Oleksandr
ss2316544@gmail.com
fix: oauth state (#128)..., 12 months ago
1
package usersrv
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 (u *UserSrv) 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:   u.googleOauth.GetAuthURL(state),
29
			State: state,
30
		}, nil
31
	case githubProvider:
32
		return dtos.OAuthRedirect{
33
			URL:   u.githubOauth.GetAuthURL(state),
34
			State: state,
35
		}, nil
36
	default:
37
		return dtos.OAuthRedirect{}, ErrProviderNotSupported
38
	}
39
}
40
41
func (u *UserSrv) HandleOAuthLogin(
42
	ctx context.Context,
43
	providerName, code string,
44
) (dtos.Tokens, error) {
45
	userInfo, err := u.getUserInfoBasedOnProvider(ctx, providerName, code)
46
	if err != nil {
47
		return dtos.Tokens{}, err
48
	}
49
50
	userID, err := u.getUserByOAuthIDOrCreateOne(ctx, userInfo)
51
	if err != nil {
52
		return dtos.Tokens{}, err
53
	}
54
55
	if err = u.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
	tokens, err := u.issueTokens(ctx, userID)
61
62
	return tokens, err
63
}
64
65
func (u *UserSrv) getUserInfoBasedOnProvider(
66
	ctx context.Context,
67
	providerName, code string,
68
) (oauth.UserInfo, error) {
69
	var userInfo oauth.UserInfo
70
	var err error
71
72
	switch providerName {
73
	case googleProvider:
74
		userInfo, err = u.googleOauth.ExchangeCode(ctx, code)
75
	case githubProvider:
76
		userInfo, err = u.githubOauth.ExchangeCode(ctx, code)
77
	default:
78
		return oauth.UserInfo{}, ErrProviderNotSupported
79
	}
80
81
	return userInfo, err
82
}
83
84
func (u *UserSrv) getUserByOAuthIDOrCreateOne(
85
	ctx context.Context,
86
	info oauth.UserInfo,
87
) (uuid.UUID, error) {
88
	user, err := u.userstore.GetByOAuthID(ctx, info.Provider, info.ProviderID)
89
	if err != nil {
90
		if errors.Is(err, models.ErrUserNotFound) {
91
			uid, cerr := u.userstore.Create(ctx, models.User{
92
				ID:          uuid.Nil,
93
				Email:       info.Email,
94
				Activated:   true,
95
				Password:    "",
96
				CreatedAt:   time.Now(),
97
				LastLoginAt: time.Now(),
98
			})
99
			return uid, cerr
100
		}
101
		return uuid.Nil, err
102
	}
103
104
	return user.ID, nil
105
}