Passport/pkg/security/sessions.go
2024-03-20 20:56:43 +08:00

166 lines
5.0 KiB
Go

package security
import (
"fmt"
"strconv"
"time"
"github.com/spf13/viper"
"git.solsynth.dev/hydrogen/identity/pkg/database"
"git.solsynth.dev/hydrogen/identity/pkg/models"
"github.com/google/uuid"
"github.com/samber/lo"
)
func GrantSession(challenge models.AuthChallenge, claims, audiences []string, expired, available *time.Time) (models.AuthSession, error) {
var session models.AuthSession
if err := challenge.IsAvailable(); err != nil {
return session, err
}
if challenge.Progress < challenge.Requirements {
return session, fmt.Errorf("challenge haven't passed")
}
challenge.State = models.FinishChallengeState
session = models.AuthSession{
Claims: claims,
Audiences: audiences,
Challenge: challenge,
GrantToken: uuid.NewString(),
AccessToken: uuid.NewString(),
RefreshToken: uuid.NewString(),
ExpiredAt: expired,
AvailableAt: available,
AccountID: challenge.AccountID,
}
if err := database.C.Save(&challenge).Error; err != nil {
return session, err
} else if err := database.C.Save(&session).Error; err != nil {
return session, err
}
return session, nil
}
func GrantOauthSession(user models.Account, client models.ThirdClient, claims, audiences []string, expired, available *time.Time, ip, ua string) (models.AuthSession, error) {
session := models.AuthSession{
Claims: claims,
Audiences: audiences,
Challenge: models.AuthChallenge{
IpAddress: ip,
UserAgent: ua,
RiskLevel: CalcRisk(user, ip, ua),
State: models.FinishChallengeState,
AccountID: user.ID,
},
GrantToken: uuid.NewString(),
AccessToken: uuid.NewString(),
RefreshToken: uuid.NewString(),
ExpiredAt: expired,
AvailableAt: available,
ClientID: &client.ID,
AccountID: user.ID,
}
if err := database.C.Save(&session).Error; err != nil {
return session, err
}
return session, nil
}
func RegenSession(session models.AuthSession) (models.AuthSession, error) {
session.GrantToken = uuid.NewString()
session.AccessToken = uuid.NewString()
session.RefreshToken = uuid.NewString()
err := database.C.Save(&session).Error
return session, err
}
func GetToken(session models.AuthSession) (string, string, error) {
var refresh, access string
if err := session.IsAvailable(); err != nil {
return refresh, access, err
}
accessDuration := time.Duration(viper.GetInt64("security.access_token_duration")) * time.Second
refreshDuration := time.Duration(viper.GetInt64("security.refresh_token_duration")) * time.Second
var err error
sub := strconv.Itoa(int(session.AccountID))
sed := strconv.Itoa(int(session.ID))
access, err = EncodeJwt(session.AccessToken, JwtAccessType, sub, sed, session.Audiences, time.Now().Add(accessDuration))
if err != nil {
return refresh, access, err
}
refresh, err = EncodeJwt(session.RefreshToken, JwtRefreshType, sub, sed, session.Audiences, time.Now().Add(refreshDuration))
if err != nil {
return refresh, access, err
}
session.LastGrantAt = lo.ToPtr(time.Now())
database.C.Save(&session)
return access, refresh, nil
}
func ExchangeToken(token string) (string, string, error) {
var session models.AuthSession
if err := database.C.Where(models.AuthSession{GrantToken: token}).First(&session).Error; err != nil {
return "404", "403", err
} else if session.LastGrantAt != nil {
return "404", "403", fmt.Errorf("session was granted the first token, use refresh token instead")
} else if len(session.Audiences) > 1 {
return "404", "403", fmt.Errorf("should use authorization code grant type")
}
return GetToken(session)
}
func ExchangeOauthToken(clientId, clientSecret, redirectUri, token string) (string, string, error) {
var client models.ThirdClient
if err := database.C.Where(models.ThirdClient{Alias: clientId}).First(&client).Error; err != nil {
return "404", "403", err
} else if client.Secret != clientSecret {
return "404", "403", fmt.Errorf("invalid client secret")
} else if !client.IsDraft && !lo.Contains(client.Callbacks, redirectUri) {
return "404", "403", fmt.Errorf("invalid redirect uri")
}
var session models.AuthSession
if err := database.C.Where(models.AuthSession{GrantToken: token}).First(&session).Error; err != nil {
return "404", "403", err
} else if session.LastGrantAt != nil {
return "404", "403", fmt.Errorf("session was granted the first token, use refresh token instead")
}
return GetToken(session)
}
func RefreshToken(token string) (string, string, error) {
parseInt := func(str string) int {
val, _ := strconv.Atoi(str)
return val
}
var session models.AuthSession
if claims, err := DecodeJwt(token); err != nil {
return "404", "403", err
} else if claims.Type != JwtRefreshType {
return "404", "403", fmt.Errorf("invalid token type, expected refresh token")
} else if err := database.C.Where(models.AuthSession{
BaseModel: models.BaseModel{ID: uint(parseInt(claims.SessionID))},
}).First(&session).Error; err != nil {
return "404", "403", err
}
if session, err := RegenSession(session); err != nil {
return "404", "403", err
} else {
return GetToken(session)
}
}