all repos

onasty @ 3b5e67f9c8eb6a915c65d5a30d0fffe901227f2e

a one-time notes service

onasty/internal/service/authsrv/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
package authsrv

import (
	"context"
	"errors"
	"log/slog"
	"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 (a *AuthSrv) GetOAuthURL(providerName string) (dtos.OAuthRedirect, error) {
	state := uuid.Must(uuid.NewV4()).String()

	switch providerName {
	case googleProvider:
		return dtos.OAuthRedirect{
			URL:   a.googleOauth.GetAuthURL(state),
			State: state,
		}, nil
	case githubProvider:
		return dtos.OAuthRedirect{
			URL:   a.githubOauth.GetAuthURL(state),
			State: state,
		}, nil
	default:
		return dtos.OAuthRedirect{}, ErrProviderNotSupported
	}
}

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

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

	if err = a.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
	}

	return a.issueTokens(ctx, userID)
}

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

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

	return userInfo, err
}

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

	return user.ID, nil
}