auth/internal/auth/usecase/auth_ucase.go

86 lines
2.6 KiB
Go

package usecase
import (
"context"
"fmt"
"source.toby3d.me/toby3d/auth/internal/auth"
"source.toby3d.me/toby3d/auth/internal/domain"
"source.toby3d.me/toby3d/auth/internal/profile"
"source.toby3d.me/toby3d/auth/internal/random"
"source.toby3d.me/toby3d/auth/internal/session"
)
type authUseCase struct {
config *domain.Config
sessions session.Repository
profiles profile.Repository
}
// NewAuthUseCase creates a new authentication use case.
func NewAuthUseCase(sessions session.Repository, profiles profile.Repository, config *domain.Config) auth.UseCase {
return &authUseCase{
config: config,
sessions: sessions,
profiles: profiles,
}
}
func (uc *authUseCase) Generate(ctx context.Context, opts auth.GenerateOptions) (string, error) {
code, err := random.String(uc.config.Code.Length)
if err != nil {
return "", fmt.Errorf("cannot generate random code: %w", err)
}
var userInfo *domain.Profile
// NOTE(toby3d): We request information about the profile only if there
// is a corresponding Scope. However, the availability of this
// information in the token is not guaranteed and is completely optional:
// https://indieauth.net/source/#profile-information
if opts.Scope.Has(domain.ScopeProfile) {
if userInfo, err = uc.profiles.Get(ctx, opts.Me); err == nil &&
userInfo.Email != nil && !opts.Scope.Has(domain.ScopeEmail) {
userInfo.Email = nil
}
}
if err = uc.sessions.Create(ctx, &domain.Session{
ClientID: opts.ClientID,
Code: code,
CodeChallenge: opts.CodeChallenge,
CodeChallengeMethod: opts.CodeChallengeMethod,
Me: opts.Me,
Profile: userInfo,
RedirectURI: opts.RedirectURI,
Scope: opts.Scope,
}); err != nil {
return "", fmt.Errorf("cannot save session in store: %w", err)
}
return code, nil
}
func (uc *authUseCase) Exchange(ctx context.Context, opts auth.ExchangeOptions) (*domain.Me, *domain.Profile, error) {
session, err := uc.sessions.GetAndDelete(ctx, opts.Code)
if err != nil {
return nil, nil, fmt.Errorf("cannot find session in store: %w", err)
}
if opts.ClientID.String() != session.ClientID.String() {
return nil, nil, auth.ErrMismatchClientID
}
if opts.RedirectURI.String() != session.RedirectURI.String() {
return nil, nil, auth.ErrMismatchRedirectURI
}
if session.CodeChallenge != "" &&
session.CodeChallengeMethod != domain.CodeChallengeMethodUndefined &&
!session.CodeChallengeMethod.Validate(session.CodeChallenge, opts.CodeVerifier) {
return nil, nil, auth.ErrMismatchPKCE
}
return session.Me, session.Profile, nil
}