template-monorepo/internal/logic/auth/login_helper.go

133 lines
3.8 KiB
Go

package auth
import (
"context"
"strings"
errs "gateway/internal/library/errors"
"gateway/internal/library/errors/code"
"gateway/internal/library/zitadel"
domauth "gateway/internal/model/auth/domain/usecase"
memberdom "gateway/internal/model/member/domain"
memberenum "gateway/internal/model/member/domain/enum"
dommember "gateway/internal/model/member/domain/usecase"
"gateway/internal/svc"
"gateway/internal/types"
)
func issueAuthToken(ctx context.Context, sc *svc.ServiceContext, tenantID, uid string) (*types.AuthTokenData, error) {
if sc.AuthToken == nil {
return nil, errb.SysNotImplemented("auth token not configured")
}
pair, err := sc.AuthToken.IssuePair(ctx, &domauth.IssuePairRequest{
TenantID: tenantID,
UID: uid,
})
if err != nil {
return nil, err
}
return &types.AuthTokenData{
AccessToken: pair.AccessToken,
RefreshToken: pair.RefreshToken,
ExpiresIn: pair.ExpiresIn,
UID: uid,
TokenType: pair.TokenType,
}, nil
}
func tokenDataFromRefresh(ctx context.Context, sc *svc.ServiceContext, refreshToken string) (*types.AuthTokenData, error) {
if sc.AuthToken == nil {
return nil, errb.SysNotImplemented("auth token not configured")
}
pair, err := sc.AuthToken.Refresh(ctx, refreshToken)
if err != nil {
return nil, err
}
claims, err := sc.AuthToken.ParseAccessToken(ctx, pair.AccessToken)
if err != nil {
return nil, err
}
return &types.AuthTokenData{
AccessToken: pair.AccessToken,
RefreshToken: pair.RefreshToken,
ExpiresIn: pair.ExpiresIn,
UID: claims.UID,
TokenType: pair.TokenType,
}, nil
}
func zitadelIdentityFromToken(ctx context.Context, client *zitadel.Client, tok *zitadel.TokenResult) (*zitadel.IDTokenClaims, error) {
if tok == nil {
return nil, errb.SvcThirdParty("empty token result")
}
if tok.Subject != "" {
return &zitadel.IDTokenClaims{
Sub: tok.Subject,
Email: tok.Email,
}, nil
}
if tok.IDToken != "" {
claims, err := zitadel.ParseIDTokenClaims(tok.IDToken)
if err != nil {
return nil, errb.SvcThirdParty("parse id_token failed").WithCause(err)
}
return claims, nil
}
claims, err := client.FetchUserInfo(ctx, tok.AccessToken)
if err != nil {
return nil, wrapZitadelErr(err)
}
return claims, nil
}
func memberForLogin(ctx context.Context, sc *svc.ServiceContext, tenantID, zitadelSub string) (*dommember.MemberDTO, error) {
if sc.MemberProfile == nil {
return nil, errb.SysNotImplemented("member profile not configured")
}
dto, err := sc.MemberProfile.GetByZitadelUserID(ctx, tenantID, zitadelSub)
if err != nil {
if e := errs.FromError(err); e != nil && e.Category() == code.ResNotFound {
return nil, errb.AuthUnauthorized("invalid credentials").WithCause(memberdom.ErrNotFound)
}
return nil, err
}
if err := ensureLoginEligible(dto.Status); err != nil {
return nil, err
}
return dto, nil
}
func ensureLoginEligible(status memberenum.MemberStatus) error {
switch status {
case memberenum.MemberStatusActive:
return nil
case memberenum.MemberStatusUnverified:
return errb.AuthForbidden("account is not verified")
case memberenum.MemberStatusSuspended:
return errb.AuthForbidden("account is suspended")
case memberenum.MemberStatusDeleted:
return errb.AuthUnauthorized("invalid credentials")
default:
return errb.AuthForbidden("account is not allowed to login")
}
}
func normalizeLoginEmail(email string) string {
return strings.TrimSpace(strings.ToLower(email))
}
func requireLoginDeps(sc *svc.ServiceContext) error {
if sc.Zitadel == nil {
return errb.SysNotImplemented("zitadel not configured")
}
if sc.MemberProfile == nil {
return errb.SysNotImplemented("member profile not configured")
}
return nil
}
func isMemberNotFound(err error) bool {
e := errs.FromError(err)
return e != nil && e.Category() == code.ResNotFound
}