all repos

onasty @ 5495cb456caa00f4564d1c1eed06a766811debaf

a one-time notes service

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
package usersrv

import (
	"context"
	"errors"
	"log/slog"
	"strings"
	"time"

	"github.com/gofrs/uuid/v5"
	"github.com/olexsmir/onasty/internal/dtos"
	"github.com/olexsmir/onasty/internal/models"
	"github.com/olexsmir/onasty/internal/oauth"
)

var ErrProviderNotSupported = errors.New("oauth2 provider not supported")

const (
	googleProvider = "google"
	githubProvider = "github"
)

func (u *UserSrv) GetOAuthURL(providerName string) (string, error) {
	switch providerName {
	case googleProvider:
		return u.googleOauth.GetAuthURL(""), nil
	case githubProvider:
		return u.githubOauth.GetAuthURL(""), nil
	default:
		return "", ErrProviderNotSupported
	}
}

func (u *UserSrv) HandleOAuthLogin(
	ctx context.Context,
	providerName, code string,
) (dtos.Tokens, error) {
	userInfo, err := u.getUserInfoBasedOnProvider(ctx, providerName, code)
	if err != nil {
		return dtos.Tokens{}, err
	}

	userID, err := u.getUserByOAuthIDOrCreateOne(ctx, userInfo)
	if err != nil {
		return dtos.Tokens{}, err
	}

	if err = u.userstore.LinkOAuthIdentity(ctx, userID, userInfo.Provider, userInfo.ProviderID); err != nil {
		slog.ErrorContext(ctx, "failed to link user identity", "user_id", userID, "err", err)
		return dtos.Tokens{}, err
	}

	tokens, err := u.issueTokens(ctx, userID)

	return tokens, err
}

func (u *UserSrv) getUserInfoBasedOnProvider(
	ctx context.Context,
	providerName, code string,
) (oauth.UserInfo, error) {
	var userInfo oauth.UserInfo
	var err error

	switch providerName {
	case googleProvider:
		userInfo, err = u.googleOauth.ExchangeCode(ctx, code)
	case githubProvider:
		userInfo, err = u.githubOauth.ExchangeCode(ctx, code)
	default:
		return oauth.UserInfo{}, ErrProviderNotSupported
	}

	return userInfo, err
}

func getUsernameFromEmail(email string) string {
	p := strings.Split(email, "@")
	return p[0]
}

func (u *UserSrv) getUserByOAuthIDOrCreateOne(
	ctx context.Context,
	info oauth.UserInfo,
) (uuid.UUID, error) {
	user, err := u.userstore.GetByOAuthID(ctx, info.Provider, info.ProviderID)
	if err != nil {
		if errors.Is(err, models.ErrUserNotFound) {
			uid, cerr := u.userstore.Create(ctx, models.User{
				ID:          uuid.Nil,
				Username:    getUsernameFromEmail(info.Email),
				Email:       info.Email,
				Activated:   true,
				Password:    "",
				CreatedAt:   time.Now(),
				LastLoginAt: time.Now(),
			})
			return uid, cerr
		}
		return uuid.Nil, err
	}

	return user.ID, nil
}