mirror of
https://github.com/steveiliop56/tinyauth.git
synced 2026-07-10 12:10:28 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ff271e7f18 |
@@ -184,8 +184,7 @@ export const AuthorizePage = () => {
|
|||||||
<CardFooter className="flex flex-col items-stretch gap-3">
|
<CardFooter className="flex flex-col items-stretch gap-3">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => authorizeMutate()}
|
onClick={() => authorizeMutate()}
|
||||||
loading={authorizePending}
|
loading={authorizePending || shouldAutoAuthorize}
|
||||||
disabled={shouldAutoAuthorize}
|
|
||||||
>
|
>
|
||||||
{t("authorizeTitle")}
|
{t("authorizeTitle")}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -177,8 +177,7 @@ func (app *BootstrapApp) Setup() error {
|
|||||||
cookieId := strings.Split(app.runtime.UUID, "-")[0] // first 8 characters of the uuid should be good enough
|
cookieId := strings.Split(app.runtime.UUID, "-")[0] // first 8 characters of the uuid should be good enough
|
||||||
|
|
||||||
app.runtime.SessionCookieName = fmt.Sprintf("%s-%s", model.SessionCookieName, cookieId)
|
app.runtime.SessionCookieName = fmt.Sprintf("%s-%s", model.SessionCookieName, cookieId)
|
||||||
app.runtime.CSRFCookieName = fmt.Sprintf("%s-%s", model.CSRFCookieName, cookieId)
|
app.runtime.ScopeCookieName = fmt.Sprintf("%s-%s", model.OIDCScopeCookieName, cookieId)
|
||||||
app.runtime.RedirectCookieName = fmt.Sprintf("%s-%s", model.RedirectCookieName, cookieId)
|
|
||||||
app.runtime.OAuthSessionCookieName = fmt.Sprintf("%s-%s", model.OAuthSessionCookieName, cookieId)
|
app.runtime.OAuthSessionCookieName = fmt.Sprintf("%s-%s", model.OAuthSessionCookieName, cookieId)
|
||||||
|
|
||||||
// database
|
// database
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ type OIDCController struct {
|
|||||||
log *logger.Logger
|
log *logger.Logger
|
||||||
oidc *service.OIDCService
|
oidc *service.OIDCService
|
||||||
runtime *model.RuntimeConfig
|
runtime *model.RuntimeConfig
|
||||||
|
config *model.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthorizeCallback struct {
|
type AuthorizeCallback struct {
|
||||||
@@ -87,6 +88,7 @@ type OIDCControllerInput struct {
|
|||||||
|
|
||||||
Log *logger.Logger
|
Log *logger.Logger
|
||||||
OIDCService *service.OIDCService
|
OIDCService *service.OIDCService
|
||||||
|
Config *model.Config
|
||||||
RuntimeConfig *model.RuntimeConfig
|
RuntimeConfig *model.RuntimeConfig
|
||||||
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
|
RouterGroup *gin.RouterGroup `name:"apiRouterGroup"`
|
||||||
MainRouter *gin.RouterGroup `name:"mainRouterGroup"`
|
MainRouter *gin.RouterGroup `name:"mainRouterGroup"`
|
||||||
@@ -97,6 +99,7 @@ func NewOIDCController(i OIDCControllerInput) *OIDCController {
|
|||||||
log: i.Log,
|
log: i.Log,
|
||||||
oidc: i.OIDCService,
|
oidc: i.OIDCService,
|
||||||
runtime: i.RuntimeConfig,
|
runtime: i.RuntimeConfig,
|
||||||
|
config: i.Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
i.MainRouter.POST("/authorize", controller.authorize)
|
i.MainRouter.POST("/authorize", controller.authorize)
|
||||||
@@ -241,6 +244,19 @@ func (controller *OIDCController) authorize(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cookieId := strings.SplitN(client.ClientID, "-", 2)[0]
|
||||||
|
cookieName := fmt.Sprintf("%s-%s", controller.runtime.ScopeCookieName, cookieId)
|
||||||
|
scopeCookie, err := c.Cookie(cookieName)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
scopes := fmt.Sprintf("scopes=%s;", req.Scope)
|
||||||
|
if controller.oidc.VerifySignedValue(client.ClientSecret, []byte(scopes), scopeCookie) {
|
||||||
|
if values.OIDCPrompt != service.OIDCPromptLogin {
|
||||||
|
values.OIDCPrompt = service.OIDCPromptNone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
queries, err := query.Values(values)
|
queries, err := query.Values(values)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -319,6 +335,19 @@ func (controller *OIDCController) authorizeComplete(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get the client
|
||||||
|
client, ok := controller.oidc.GetClient(authorizeReq.ClientID)
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
controller.authorizeError(c, authorizeErrorParams{
|
||||||
|
err: errors.New("client not found"),
|
||||||
|
reason: "Client not found",
|
||||||
|
reasonPublic: "The client is not configured",
|
||||||
|
json: true,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
// We no longer need the ticket
|
// We no longer need the ticket
|
||||||
controller.oidc.DeleteAuthorizeRequestTicket(req.Ticket)
|
controller.oidc.DeleteAuthorizeRequestTicket(req.Ticket)
|
||||||
|
|
||||||
@@ -361,6 +390,22 @@ func (controller *OIDCController) authorizeComplete(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Set a cookie for the consent screen (approved scopes)
|
||||||
|
cookieId := strings.SplitN(client.ClientID, "-", 2)[0]
|
||||||
|
cookieName := fmt.Sprintf("%s-%s", controller.runtime.ScopeCookieName, cookieId)
|
||||||
|
scopes := fmt.Sprintf("scopes=%s;", authorizeReq.Scope)
|
||||||
|
|
||||||
|
cookie := &http.Cookie{
|
||||||
|
Name: cookieName,
|
||||||
|
Value: controller.oidc.CreateSignedValue(client.ClientSecret, []byte(scopes)),
|
||||||
|
Path: "/",
|
||||||
|
Secure: controller.config.Auth.SecureCookie,
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteLaxMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
http.SetCookie(c.Writer, cookie)
|
||||||
|
|
||||||
c.JSON(200, gin.H{
|
c.JSON(200, gin.H{
|
||||||
"status": 200,
|
"status": 200,
|
||||||
"redirect_uri": fmt.Sprintf("%s?%s", authorizeReq.RedirectURI, queries.Encode()),
|
"redirect_uri": fmt.Sprintf("%s?%s", authorizeReq.RedirectURI, queries.Encode()),
|
||||||
|
|||||||
@@ -20,8 +20,7 @@ var OverrideProviders = map[string]string{
|
|||||||
var ReservedProviderNames = []string{"local", "ldap", "tailscale"}
|
var ReservedProviderNames = []string{"local", "ldap", "tailscale"}
|
||||||
|
|
||||||
const SessionCookieName = "tinyauth-session"
|
const SessionCookieName = "tinyauth-session"
|
||||||
const CSRFCookieName = "tinyauth-csrf"
|
|
||||||
const RedirectCookieName = "tinyauth-redirect"
|
|
||||||
const OAuthSessionCookieName = "tinyauth-oauth"
|
const OAuthSessionCookieName = "tinyauth-oauth"
|
||||||
|
const OIDCScopeCookieName = "tinyauth-scope"
|
||||||
|
|
||||||
const GracefulShutdownTimeout = 5 // seconds
|
const GracefulShutdownTimeout = 5 // seconds
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ type RuntimeConfig struct {
|
|||||||
UUID string
|
UUID string
|
||||||
CookieDomain string
|
CookieDomain string
|
||||||
SessionCookieName string
|
SessionCookieName string
|
||||||
CSRFCookieName string
|
ScopeCookieName string
|
||||||
RedirectCookieName string
|
|
||||||
OAuthSessionCookieName string
|
OAuthSessionCookieName string
|
||||||
LocalUsers []LocalUser
|
LocalUsers []LocalUser
|
||||||
OAuthProviders map[string]OAuthServiceConfig
|
OAuthProviders map[string]OAuthServiceConfig
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package service
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
|
"crypto/hmac"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
@@ -22,6 +23,7 @@ import (
|
|||||||
|
|
||||||
"github.com/go-jose/go-jose/v4"
|
"github.com/go-jose/go-jose/v4"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/steveiliop56/ding"
|
"github.com/steveiliop56/ding"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/model"
|
"github.com/tinyauthapp/tinyauth/internal/model"
|
||||||
"github.com/tinyauthapp/tinyauth/internal/repository"
|
"github.com/tinyauthapp/tinyauth/internal/repository"
|
||||||
@@ -305,6 +307,9 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
|
|||||||
|
|
||||||
for id, client := range i.Config.OIDC.Clients {
|
for id, client := range i.Config.OIDC.Clients {
|
||||||
client.ID = id
|
client.ID = id
|
||||||
|
if err := uuid.Validate(client.ClientID); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid client id: %w", err)
|
||||||
|
}
|
||||||
if client.Name == "" {
|
if client.Name == "" {
|
||||||
client.Name = utils.Capitalize(client.ID)
|
client.Name = utils.Capitalize(client.ID)
|
||||||
}
|
}
|
||||||
@@ -318,6 +323,9 @@ func NewOIDCService(i OIDCServiceInput) (*OIDCService, error) {
|
|||||||
client.ClientSecret = secret
|
client.ClientSecret = secret
|
||||||
}
|
}
|
||||||
client.ClientSecretFile = ""
|
client.ClientSecretFile = ""
|
||||||
|
if len(client.ClientSecret) < 32 {
|
||||||
|
return nil, fmt.Errorf("client secret for client %s is too short, must be >= 32 chars", client.ClientID)
|
||||||
|
}
|
||||||
clients[id] = client
|
clients[id] = client
|
||||||
i.Log.App.Debug().Str("clientId", client.ClientID).Msg("Loaded OIDC client configuration")
|
i.Log.App.Debug().Str("clientId", client.ClientID).Msg("Loaded OIDC client configuration")
|
||||||
}
|
}
|
||||||
@@ -969,3 +977,21 @@ func (service *OIDCService) GetPrompt(prompt string) []OIDCPrompt {
|
|||||||
|
|
||||||
return parsedPromps
|
return parsedPromps
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (service *OIDCService) CreateSignedValue(key string, data []byte) string {
|
||||||
|
// create the signature
|
||||||
|
h := hmac.New(sha256.New, []byte(key))
|
||||||
|
h.Write(data)
|
||||||
|
sig := base64.URLEncoding.EncodeToString(h.Sum(nil))
|
||||||
|
|
||||||
|
// hash the data
|
||||||
|
hasher := sha256.New()
|
||||||
|
hasher.Write(data)
|
||||||
|
hash := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s.%s", hash, sig)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (service *OIDCService) VerifySignedValue(key string, data []byte, signedValue string) bool {
|
||||||
|
return service.CreateSignedValue(key, data) == signedValue
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user