all repos

onasty @ 7236f0cad88cebacb9801d31c8f707fec0cb6978

a one-time notes service

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

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