mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-06-12 22:40:13 +00:00
chore: merge oidc-authorize branch
This commit is contained in:
@@ -30,17 +30,14 @@ var (
|
||||
ErrUserNotFound = errors.New("user not found")
|
||||
)
|
||||
|
||||
// slightly modified version of the AuthorizeRequest from the OIDC service to basically accept all
|
||||
// parameters and pass them to the authorize page if needed
|
||||
type OAuthURLParams struct {
|
||||
Scope string `form:"scope" url:"scope"`
|
||||
ResponseType string `form:"response_type" url:"response_type"`
|
||||
ClientID string `form:"client_id" url:"client_id"`
|
||||
RedirectURI string `form:"redirect_uri" url:"redirect_uri"`
|
||||
State string `form:"state" url:"state"`
|
||||
Nonce string `form:"nonce" url:"nonce"`
|
||||
CodeChallenge string `form:"code_challenge" url:"code_challenge"`
|
||||
CodeChallengeMethod string `form:"code_challenge_method" url:"code_challenge_method"`
|
||||
// We either store params for redirecting to an app after OAuth login,
|
||||
// or for redirecting back to the authorize screen to continue OIDC
|
||||
type OAuthCallbackParams struct {
|
||||
LoginFor string `form:"login_for" url:"login_for"`
|
||||
OIDCTicket string `form:"oidc_ticket" url:"oidc_ticket"`
|
||||
OIDCScope string `form:"oidc_scope" url:"oidc_scope"`
|
||||
OIDCName string `form:"oidc_name" url:"oidc_name"`
|
||||
RedirectURI string `form:"redirect_uri" url:"redirect_uri"`
|
||||
}
|
||||
|
||||
type OAuthPendingSession struct {
|
||||
@@ -49,7 +46,7 @@ type OAuthPendingSession struct {
|
||||
Token *oauth2.Token
|
||||
Service *OAuthServiceImpl
|
||||
ExpiresAt time.Time
|
||||
CallbackParams OAuthURLParams
|
||||
CallbackParams OAuthCallbackParams
|
||||
}
|
||||
|
||||
type LoginAttempt struct {
|
||||
@@ -516,17 +513,17 @@ func (auth *AuthService) LDAPAuthConfigured() bool {
|
||||
return auth.ldap != nil
|
||||
}
|
||||
|
||||
func (auth *AuthService) NewOAuthSession(serviceName string, params OAuthURLParams) (string, OAuthPendingSession, error) {
|
||||
func (auth *AuthService) NewOAuthSession(serviceName string, params OAuthCallbackParams) (string, error) {
|
||||
service, ok := auth.oauthBroker.GetService(serviceName)
|
||||
|
||||
if !ok {
|
||||
return "", OAuthPendingSession{}, fmt.Errorf("oauth service not found: %s", serviceName)
|
||||
return "", fmt.Errorf("oauth service not found: %s", serviceName)
|
||||
}
|
||||
|
||||
sessionId, err := uuid.NewRandom()
|
||||
|
||||
if err != nil {
|
||||
return "", OAuthPendingSession{}, fmt.Errorf("failed to generate session ID: %w", err)
|
||||
return "", fmt.Errorf("failed to generate session ID: %w", err)
|
||||
}
|
||||
|
||||
state := service.NewRandom()
|
||||
@@ -542,7 +539,7 @@ func (auth *AuthService) NewOAuthSession(serviceName string, params OAuthURLPara
|
||||
|
||||
auth.caches.oauth.Set(sessionId.String(), session, time.Minute*10)
|
||||
|
||||
return sessionId.String(), session, nil
|
||||
return sessionId.String(), nil
|
||||
}
|
||||
|
||||
func (auth *AuthService) GetOAuthURL(sessionId string) (string, error) {
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"slices"
|
||||
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/steveiliop56/ding"
|
||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||
@@ -106,14 +107,15 @@ type TokenResponse struct {
|
||||
}
|
||||
|
||||
type AuthorizeRequest struct {
|
||||
Scope string `json:"scope" binding:"required"`
|
||||
ResponseType string `json:"response_type" binding:"required"`
|
||||
ClientID string `json:"client_id" binding:"required"`
|
||||
RedirectURI string `json:"redirect_uri" binding:"required"`
|
||||
State string `json:"state"`
|
||||
Nonce string `json:"nonce"`
|
||||
CodeChallenge string `json:"code_challenge"`
|
||||
CodeChallengeMethod string `json:"code_challenge_method"`
|
||||
jwt.Claims
|
||||
Scope string `form:"scope" json:"scope" url:"scope"`
|
||||
ResponseType string `form:"response_type" json:"response_type" url:"response_type"`
|
||||
ClientID string `form:"client_id" json:"client_id" url:"client_id"`
|
||||
RedirectURI string `form:"redirect_uri" json:"redirect_uri" url:"redirect_uri"`
|
||||
State string `form:"state" json:"state" url:"state"`
|
||||
Nonce string `form:"nonce" json:"nonce" url:"nonce"`
|
||||
CodeChallenge string `form:"code_challenge" json:"code_challenge" url:"code_challenge"`
|
||||
CodeChallengeMethod string `form:"code_challenge_method" json:"code_challenge_method" url:"code_challenge_method"`
|
||||
}
|
||||
|
||||
type AuthorizeCodeEntry struct {
|
||||
@@ -142,8 +144,9 @@ type OIDCService struct {
|
||||
issuer string
|
||||
|
||||
caches struct {
|
||||
code *CacheStore[AuthorizeCodeEntry]
|
||||
usedCode *CacheStore[UsedCodeEntry]
|
||||
code *CacheStore[AuthorizeCodeEntry]
|
||||
usedCode *CacheStore[UsedCodeEntry]
|
||||
authorize *CacheStore[AuthorizeRequest]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,8 +314,11 @@ func NewOIDCService(
|
||||
// Create caches
|
||||
codeCash := NewCacheStore[AuthorizeCodeEntry](256)
|
||||
usedCode := NewCacheStore[UsedCodeEntry](256)
|
||||
authorize := NewCacheStore[AuthorizeRequest](256)
|
||||
|
||||
service.caches.code = codeCash
|
||||
service.caches.usedCode = usedCode
|
||||
service.caches.authorize = authorize
|
||||
|
||||
// Start cache cleanup routine
|
||||
dg.Go(func(ctx context.Context) {
|
||||
@@ -324,6 +330,7 @@ func NewOIDCService(
|
||||
case <-ticker.C:
|
||||
service.caches.code.Sweep()
|
||||
service.caches.usedCode.Sweep()
|
||||
service.caches.authorize.Sweep()
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
@@ -856,3 +863,44 @@ func (service *OIDCService) MarkCodeAsUsed(codeHash string, sub string) {
|
||||
func (service *OIDCService) DeleteSessionBySub(ctx context.Context, sub string) error {
|
||||
return service.queries.DeleteOIDCSessionBySub(ctx, sub)
|
||||
}
|
||||
|
||||
func (service *OIDCService) CreateAuthorizeRequestTicket(req AuthorizeRequest) string {
|
||||
ticket := utils.GenerateString(32)
|
||||
|
||||
service.caches.authorize.Set(ticket, req, 10*time.Minute)
|
||||
|
||||
return ticket
|
||||
}
|
||||
|
||||
func (service *OIDCService) GetAuthorizeRequestByTicket(ticket string) (*AuthorizeRequest, bool) {
|
||||
entry, ok := service.caches.authorize.Get(ticket)
|
||||
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &entry, true
|
||||
}
|
||||
|
||||
func (service *OIDCService) DeleteAuthorizeRequestTicket(ticket string) {
|
||||
service.caches.authorize.Delete(ticket)
|
||||
}
|
||||
|
||||
// TODO: support signed request objects in the future
|
||||
func (service *OIDCService) DecodeAuthorizeJWT(tokenString string) (*AuthorizeRequest, error) {
|
||||
var req AuthorizeRequest
|
||||
|
||||
token, _, err := jwt.NewParser().ParseUnverified(tokenString, &req)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse authorize request jwt: %w", err)
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*AuthorizeRequest)
|
||||
|
||||
if !ok {
|
||||
return nil, errors.New("failed to parse claims from authorize request jwt")
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user